2009-09-24 12 views
12

Chciałbym utworzyć skrypt php, który działa jako codzienna crona. Co chcę zrobić, to wyliczyć wszystkich użytkowników w Active Directory, wyodrębnić pewne pola z każdego wpisu i użyć tych informacji do aktualizacji pól w bazie danych MySQL.Wyliczenie wszystkich użytkowników w LDAP za pomocą PHP

Zasadniczo chcę zsynchronizować określone dane użytkownika między Active Directory a tabelą MySQL.

Problem polega na tym, że sizelimit na serwerze Active Directory jest często ustawiony na 1000 wpisów na wynik wyszukiwania. Miałem nadzieję, że funkcja php "ldap_next_entry" obejmie to poprzez pobieranie tylko jednego wpisu na raz, ale zanim będzie można wywołać "ldap_next_entry", najpierw musisz wywołać "ldap_search", co może spowodować przekroczenie błędu SizeLimit.

Czy jest jakiś sposób poza usunięciem sedzimit z serwera? Czy mogę w jakiś sposób uzyskać "strony" wyników?

BTW - obecnie nie używam żadnych bibliotek ani kodu innej firmy. Tylko metody Ldap w PHP. Chociaż z pewnością jestem otwarty na korzystanie z biblioteki, jeśli to pomoże.

Odpowiedz

15

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.

+0

Fantastyczna odpowiedź! Dziękuję bardzo, bardzo mi to pomogło! – Christian

+2

Jaki jest teraz stan, gdy PHP 5.4 obsługuje stronicowane wyniki LDAP? – Squazic

+1

@Squazic: zobacz odpowiedź http://stackoverflow.com/a/10140166/11354 poniżej. Wydaje się być wykonalne począwszy od PHP 5.4. –

3

To nie jest pełna odpowiedź, ale this guy był w stanie to zrobić. Nie rozumiem jednak, co zrobił.

Nawiasem mówiąc, częściową odpowiedzią jest to, że MOŻESZ uzyskać "strony" wyników. Z documentation:

resource ldap_search (resource $link_identifier , string $base_dn , 
    string $filter [, array $attributes [, int $attrsonly [, int $sizelimit [, 
    int $timelimit [, int $deref ]]]]]) 
... 

sizelimit Umożliwia ograniczenie liczby wpisów naciągane. Ustawienie na 0 oznacza brak limitu.

Uwaga: Ten parametr NIE może zastępować presetu sizelimit po stronie serwera. Możesz ją jednak zmniejszyć. Niektóre hosty serwerów katalogów będą skonfigurowane tak, aby zwracały nie więcej niż wstępnie ustawioną liczbę wpisów. Jeśli pojawi się ta , serwer wskaże, że zwrócił tylko częściowy zestaw wyników . Dzieje się tak również, jeśli parametr ten zostanie użyty do ograniczenia liczby pobranych wpisów z numeru .

Nie wiem, jak określić, że chcesz wyszukać STARTING z określonej pozycji. Tj, po otrzymaniu pierwszego 1000, nie wiem jak określić, że teraz potrzebujesz następnego 1000. Mam nadzieję, że ktoś inny może ci pomóc :)

0

Oto alternatywa (która działa przed PHP 5.4). Jeśli masz 10.000 zapisów trzeba się jednak serwer AD zwraca tylko 5000 na stronie:

$ldapSearch = ldap_search($ldapResource, $basedn, $filter, array('member;range=0-4999')); 
$ldapResults = ldap_get_entries($dn, $ldapSearch); 
$members = $ldapResults[0]['member;range=0-4999']; 

$ldapSearch = ldap_search($ldapResource, $basedn, $filter, array('member;range=5000-10000')); 
$ldapResults = ldap_get_entries($dn, $ldapSearch); 
$members = array_merge($members, $ldapResults[0]['member;range=5000-*']); 
0

udało mi się obejść ograniczenia wielkości przy użyciu ldap_control_paged_result

ldap_control_paged_result służy do włączania paginacji LDAP wysyłając kontrola paginacji. Poniższa funkcja działała idealnie w moim przypadku.

function retrieves_users($conn) 
    { 
     $dn  = 'ou=,dc=,dc='; 
     $filter = "(&(objectClass=user)(objectCategory=person)(sn=*))"; 
     $justthese = array(); 

     // enable pagination with a page size of 100. 
     $pageSize = 100; 

     $cookie = ''; 

     do { 
      ldap_control_paged_result($conn, $pageSize, true, $cookie); 

      $result = ldap_search($conn, $dn, $filter, $justthese); 
      $entries = ldap_get_entries($conn, $result); 

      if(!empty($entries)){ 
       for ($i = 0; $i < $entries["count"]; $i++) { 
        $data['usersLdap'][] = array(
          'name' => $entries[$i]["cn"][0], 
          'username' => $entries[$i]["userprincipalname"][0] 
        ); 
       } 
      } 
      ldap_control_paged_result_response($conn, $result, $cookie); 

     } while($cookie !== null && $cookie != ''); 

     return $data; 
    } 
Powiązane problemy