2009-09-24 55 views
12

Me gustaría crear un script php que se ejecute como un cron diario. Lo que me gustaría hacer es enumerar a través de todos los usuarios dentro de un Directorio Activo, extraer ciertos campos de cada entrada, y usar esta información para actualizar campos dentro de una base de datos MySQL.Enumerar todos los usuarios en LDAP con PHP

Básicamente, lo que quiero hacer es sincronizar cierta información de usuario entre Active Directory y una tabla MySQL.

El problema que tengo es que sizelimit en el servidor de Active Directory a menudo se establece en 1000 entradas por resultado de búsqueda. Tenía la esperanza de que la función php "ldap_next_entry" solucionara esto solo obteniendo una entrada a la vez, pero antes de que pueda llamar a "ldap_next_entry", primero debe llamar a "ldap_search", lo que puede disparar el error SizeLimit excedido.

¿Hay alguna manera además de eliminar el sizelimit del servidor? ¿De alguna manera puedo obtener "páginas" de resultados?

BTW - Actualmente no estoy utilizando ninguna biblioteca o código de terceros. Solo métodos PHP ldap. Aunque, ciertamente estoy dispuesto a usar una biblioteca si eso ayuda.

Respuesta

15

Me ha sorprendido el mismo problema al desarrollar Zend_Ldap para el Zend Framework. Trataré de explicar cuál es el problema real, pero para abreviar: hasta PHP 5.4, no fue posible usar resultados paginados de un Active Directory con una versión de PHP no enmascarada (ext/ldap) debido a las limitaciones en exactamente esto extensión.

Tratemos de resolver todo ... Microsoft Active Directory usa el llamado control de servidor para lograr la paginación de resultados del lado del servidor. Este control se describe en RFC 2696 "LDAP Control Extension for Simple Paged Results Manipulation".

ext/php ofrece un acceso a extensiones de control LDAP a través de su y la opción ldap_set_option()LDAP_OPT_SERVER_CONTROLS y LDAP_OPT_CLIENT_CONTROLS respectivamente. Para establecer el control paginado necesita el control-oid, que es 1.2.840.113556.1.4.319, y necesitamos saber cómo codificar el valor de control (esto se describe en el RFC).El valor es una cadena de octetos envolver la versión REC codificada de la siguiente secuencia (copiado del RFC):

realSearchControlValue ::= SEQUENCE { 
     size   INTEGER (0..maxInt), 
           -- requested page size from client 
           -- result set size estimate from server 
     cookie   OCTET STRING 
} 

para que podamos establecer el control de servidor apropiado antes de ejecutar la consulta 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 
); 

Esto nos permite enviar una consulta paginada al servidor LDAP/AD. ¿Pero cómo sabemos si hay más páginas para seguir y cómo especificamos con qué valor de control tenemos que enviar nuestra próxima consulta?

Aquí es donde nos trabamos ... El servidor responde con un conjunto de resultados que incluye la información de paginación requerida, pero PHP no tiene un método para recuperar exactamente esta información del conjunto de resultados. PHP proporciona un contenedor para la función LDAP API ldap_parse_result() pero el último parámetro requerido serverctrlsp no está expuesto a la función PHP, por lo que no hay forma de recuperar la información requerida. Un bug report se ha presentado para este problema, pero no ha habido ninguna respuesta desde el año 2005. Si la función ldap_parse_result() proporciona el parámetro requerido, utilizando los resultados paginados funcionaría como

$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); 
    } 
} 

Como se puede ver hay una sola línea de código (*) eso hace que todo el asunto sea inútil. En el camino a través de la escasa información sobre este tema encontré un parche contra el PHP 4.3.10 ext/ldap por Iñaki Arenaza, pero tampoco lo intenté ni sé si el parche se puede aplicar en un PHP5 ext/ldap. El parche se extiende ldap_parse_result() para exponer el séptimo parámetro para 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) {

En realidad, la única opción que queda sería cambiar la configuración de Active Directory y elevar el límite máximo resultado. La opción relevante se llama MaxPageSize y puede modificarse usando ntdsutil.exe - consulte "How to view and set LDAP policy in Active Directory by using Ntdsutil.exe".

EDITAR (referencia a COM):

O puede ir al revés y utilizar el COM-enfoque a través de ADODB como se sugiere en la link proporcionada por eykanal.

+0

¡Respuesta! Muchas gracias, ¡esto me ayudó mucho! – Christian

+2

¿Cuál es el estado de esto ahora que PHP 5.4 admite resultados de LDAP paginado? – Squazic

+1

@Squazic: vea la respuesta http://stackoverflow.com/a/10140166/11354 abajo. Parece factible a partir de PHP 5.4. –

3

Esta no es una respuesta completa, pero this guy fue capaz de hacerlo. Aunque no entiendo lo que hizo.

Por cierto, una respuesta parcial es que PUEDE obtener "páginas" de resultados. Desde el documentation:

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

sizelimit le permite limitar el número de entradas devueltas. Establecer esto en 0 significa que no hay límite.

Nota: Este parámetro NO puede anular sizelimit preestablecido del lado del servidor. Aunque puedes establecerlo más bajo. Algunos hosts de servidor de directorio serán configurados para devolver no más de un número preestablecido de entradas. Si se produce este , el servidor indicará que solo ha devuelto un conjunto de resultados parcial de . Esto también ocurre si usa este parámetro para limitar el recuento de de entradas obtenidas.

No sé cómo especificar que desea buscar INICIANDO desde una posición determinada, sin embargo. Es decir, después de obtener los primeros 1000, no sé cómo especificar que ahora necesita los siguientes 1000. Ojalá alguien más pueda ayudarlo :)

0

Aquí hay una alternativa (que funciona antes de PHP 5.4). Si usted tiene 10.000 registros que necesita para obtener, pero su servidor de anuncios sólo devuelve 5.000 por página:

$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

yo era capaz de moverse por la limitación de tamaño usando ldap_control_paged_result

ldap_control_paged_result se utiliza para habilitar la paginación LDAP mediante el envío el control de paginación La siguiente función funcionó perfectamente en mi caso.

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; 
    } 
Cuestiones relacionadas