Uderzyło mnie ten sam problem podczas rozwijania Zend_Ldap dla Zend Framework. Spróbuję wyjaśnić, jaki jest prawdziwy problem, ale aby go krótko: do wersji 5.4, nie można było używać stronicowanych wyników z Active Directory z niezałataną wersją PHP (ext/ldap
) z powodu ograniczeń w tym właśnie rozszerzenie.
Spróbujmy rozwikłać całość ... Microsoft Active Directory używa tzw. Kontroli serwera do realizacji stronicowania po stronie serwera. Ta kontrola jest opisana w RFC 2696 "LDAP Control Extension for Simple Paged Results Manipulation".
oferuje dostęp do rozszerzeń sterowania LDAP za pomocą odpowiednio opcji ldap_set_option()
i LDAP_OPT_SERVER_CONTROLS
i LDAP_OPT_CLIENT_CONTROLS
. Aby ustawić kontrolkę stronicowaną, potrzebujesz kontrolki oid, która jest 1.2.840.113556.1.4.319
, i musimy wiedzieć, jak zakodować wartość kontrolną (jest to opisane w RFC).Wartością jest ciąg oktet owijania wersję BER zakodowany w następującej kolejności (skopiowany z RFC):
realSearchControlValue ::= SEQUENCE {
size INTEGER (0..maxInt),
-- requested page size from client
-- result set size estimate from server
cookie OCTET STRING
}
Więc możemy ustawić odpowiednią kontrolę serwera przed wykonaniem kwerendy LDAP:
$pageSize = 100;
$pageControl = array(
'oid' => '1.2.840.113556.1.4.319', // the control-oid
'iscritical' => true, // the operation should fail if the server is not able to support this control
'value' => sprintf ("%c%c%c%c%c%c%c", 48, 5, 2, 1, $pageSize, 4, 0) // the required BER-encoded control-value
);
Umożliwia to wysyłanie kwerendy stronicowanej do serwera LDAP/AD. Ale skąd mamy wiedzieć, czy jest więcej stron do naśladowania i jak określić, z jaką wartością kontrolną musimy wysłać następne zapytanie?
To tutaj utknęliśmy ... Serwer odpowiada zestawem wyników, który zawiera wymagane informacje stronicowania, ale PHP nie ma metody, aby pobrać dokładnie te informacje z zestawu wyników. PHP dostarcza wrapper dla funkcji API LDAP ldap_parse_result()
, ale wymagany ostatni parametr serverctrlsp
nie jest wystawiony na działanie PHP, więc nie ma sposobu na odzyskanie wymaganych informacji. bug report został złożony do tej kwestii, ale nie było żadnej odpowiedzi od 2005. Jeśli funkcja ldap_parse_result()
dostarczyła wymaganych parametrów, za pomocą stronicowanych wyników będzie działać jak
$l = ldap_connect('somehost.mydomain.com');
$pageSize = 100;
$pageControl = array(
'oid' => '1.2.840.113556.1.4.319',
'iscritical' => true,
'value' => sprintf ("%c%c%c%c%c%c%c", 48, 5, 2, 1, $pageSize, 4, 0)
);
$controls = array($pageControl);
ldap_set_option($l, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_bind($l, 'CN=bind-user,OU=my-users,DC=mydomain,DC=com', 'bind-user-password');
$continue = true;
while ($continue) {
ldap_set_option($l, LDAP_OPT_SERVER_CONTROLS, $controls);
$sr = ldap_search($l, 'OU=some-ou,DC=mydomain,DC=com', 'cn=*', array('sAMAccountName'), null, null, null, null);
ldap_parse_result ($l, $sr, $errcode, $matcheddn, $errmsg, $referrals, $serverctrls); // (*)
if (isset($serverctrls)) {
foreach ($serverctrls as $i) {
if ($i["oid"] == '1.2.840.113556.1.4.319') {
$i["value"]{8} = chr($pageSize);
$i["iscritical"] = true;
$controls = array($i);
break;
}
}
}
$info = ldap_get_entries($l, $sr);
if ($info["count"] < $pageSize) {
$continue = false;
}
for ($entry = ldap_first_entry($l, $sr); $entry != false; $entry = ldap_next_entry($l, $entry)) {
$dn = ldap_get_dn($l, $entry);
}
}
Jak widać istnieje pojedyncza linia kodu (*)
czyni to bezużytecznym. Na mojej drodze, choć nieliczne informacje na ten temat, znalazłem łatkę przeciwko PHP 4.3.10 ext/ldap
autorstwa Iñaki Arenaza, ale też nie próbowałem tego, ani nie wiem, czy łatkę można zastosować na PHP5 ext/ldap
. Plaster rozciąga ldap_parse_result()
narażać 7. parametr PHP:
--- ldap.c 2004-06-01 23:05:33.000000000 +0200
+++ /usr/src/php4/php4-4.3.10/ext/ldap/ldap.c 2005-09-03 17:02:03.000000000 +0200
@@ -74,7 +74,7 @@
ZEND_DECLARE_MODULE_GLOBALS(ldap)
static unsigned char third_argument_force_ref[] = { 3, BYREF_NONE, BYREF_NONE, BYREF_FORCE };
-static unsigned char arg3to6of6_force_ref[] = { 6, BYREF_NONE, BYREF_NONE, BYREF_FORCE, BYREF_FORCE, BYREF_FORCE, BYREF_FORCE };
+static unsigned char arg3to7of7_force_ref[] = { 7, BYREF_NONE, BYREF_NONE, BYREF_FORCE, BYREF_FORCE, BYREF_FORCE, BYREF_FORCE, BYREF_FORCE };
static int le_link, le_result, le_result_entry, le_ber_entry;
@@ -124,7 +124,7 @@
#if (LDAP_API_VERSION > 2000) || HAVE_NSLDAP
PHP_FE(ldap_get_option, third_argument_force_ref)
PHP_FE(ldap_set_option, NULL)
- PHP_FE(ldap_parse_result, arg3to6of6_force_ref)
+ PHP_FE(ldap_parse_result, arg3to7of7_force_ref)
PHP_FE(ldap_first_reference, NULL)
PHP_FE(ldap_next_reference, NULL)
#ifdef HAVE_LDAP_PARSE_REFERENCE
@@ -1775,14 +1775,15 @@
Extract information from result */
PHP_FUNCTION(ldap_parse_result)
{
- pval **link, **result, **errcode, **matcheddn, **errmsg, **referrals;
+ pval **link, **result, **errcode, **matcheddn, **errmsg, **referrals, **serverctrls;
ldap_linkdata *ld;
LDAPMessage *ldap_result;
+ LDAPControl **lserverctrls, **ctrlp, *ctrl;
char **lreferrals, **refp;
char *lmatcheddn, *lerrmsg;
int rc, lerrcode, myargcount = ZEND_NUM_ARGS();
- if (myargcount 6 || zend_get_parameters_ex(myargcount, &link, &result, &errcode, &matcheddn, &errmsg, &referrals) == FAILURE) {
+ if (myargcount 7 || zend_get_parameters_ex(myargcount, &link, &result, &errcode, &matcheddn, &errmsg, &referrals, &serverctrls) == FAILURE) {
WRONG_PARAM_COUNT;
}
@@ -1793,7 +1794,7 @@
myargcount > 3 ? &lmatcheddn : NULL,
myargcount > 4 ? &lerrmsg : NULL,
myargcount > 5 ? &lreferrals : NULL,
- NULL /* &serverctrls */,
+ myargcount > 6 ? &lserverctrls : NULL,
0);
if (rc != LDAP_SUCCESS) {
php_error(E_WARNING, "%s(): Unable to parse result: %s", get_active_function_name(TSRMLS_C), ldap_err2string(rc));
@@ -1805,6 +1806,29 @@
/* Reverse -> fall through */
switch(myargcount) {
+ case 7 :
+ zval_dtor(*serverctrls);
+
+ if (lserverctrls != NULL) {
+ array_init(*serverctrls);
+ ctrlp = lserverctrls;
+
+ while (*ctrlp != NULL) {
+ zval *ctrl_array;
+
+ ctrl = *ctrlp;
+ MAKE_STD_ZVAL(ctrl_array);
+ array_init(ctrl_array);
+
+ add_assoc_string(ctrl_array, "oid", ctrl->ldctl_oid,1);
+ add_assoc_bool(ctrl_array, "iscritical", ctrl->ldctl_iscritical);
+ add_assoc_stringl(ctrl_array, "value", ctrl->ldctl_value.bv_val,
+ ctrl->ldctl_value.bv_len,1);
+ add_next_index_zval (*serverctrls, ctrl_array);
+ ctrlp++;
+ }
+ ldap_controls_free (lserverctrls);
+ }
case 6 :
zval_dtor(*referrals);
if (array_init(*referrals) == FAILURE) {
Właściwie jedyną opcją lewej byłoby zmienić konfigurację usługi Active Directory i podnieść maksymalny limit wynik. Odpowiednia opcja nazywa się MaxPageSize
i może być zmieniana przy użyciu ntdsutil.exe
- patrz "How to view and set LDAP policy in Active Directory by using Ntdsutil.exe".
EDIT (odniesienie do COM):
Można też pójść w drugą stronę i skorzystać z COM-podejście pośrednictwem ADODB jak zasugerowano w link dostarczonych przez eykanal.
Fantastyczna odpowiedź! Dziękuję bardzo, bardzo mi to pomogło! – Christian
Jaki jest teraz stan, gdy PHP 5.4 obsługuje stronicowane wyniki LDAP? – Squazic
@Squazic: zobacz odpowiedź http://stackoverflow.com/a/10140166/11354 poniżej. Wydaje się być wykonalne począwszy od PHP 5.4. –