2009-09-24 36 views
12

Tôi muốn tạo tập lệnh php chạy dưới dạng cron hàng ngày. Những gì tôi muốn làm là liệt kê tất cả người dùng trong Active Directory, trích xuất một số trường từ mỗi mục và sử dụng thông tin này để cập nhật các trường trong cơ sở dữ liệu MySQL.Liệt kê tất cả người dùng trong LDAP bằng PHP

Về cơ bản những gì tôi muốn làm là đồng bộ hóa thông tin người dùng nhất định giữa Active Directory và bảng MySQL.

Vấn đề tôi gặp phải là sizelimit trên máy chủ Active Directory thường được đặt ở 1000 mục nhập cho mỗi kết quả tìm kiếm. Tôi đã hy vọng rằng chức năng php "ldap_next_entry" sẽ nhận được điều này bằng cách chỉ lấy một mục tại một thời điểm, nhưng trước khi bạn có thể gọi "ldap_next_entry", trước tiên bạn phải gọi "ldap_search", có thể kích hoạt lỗi SizeLimit vượt quá.

Có cách nào ngoài việc xóa sizelimit khỏi máy chủ không? Tôi có thể bằng cách nào đó có được "trang" của kết quả?

BTW - Tôi hiện không sử dụng bất kỳ thư viện hoặc mã của bên thứ ba nào. Chỉ cần các phương thức ldap của PHP. Mặc dù, tôi chắc chắn mở để sử dụng một thư viện nếu điều đó sẽ giúp đỡ.

Trả lời

15

Tôi đã bị tấn công bởi cùng một vấn đề trong khi phát triển Zend_Ldap cho khung công tác Zend. Tôi sẽ cố gắng giải thích vấn đề thực sự là gì, nhưng để ngắn gọn: cho đến khi PHP 5.4, không thể sử dụng kết quả được phân trang từ Active Directory với phiên bản PHP chưa được vá (ext/ldap) do giới hạn chính xác tiện ích mở rộng.

Hãy cố gắng làm sáng tỏ toàn bộ điều ... Microsoft Active Directory sử dụng một điều khiển máy chủ được gọi là để thực hiện phân trang kết quả phía máy chủ. Điều khiển này được mô tả trong RFC 2696 "LDAP Control Extension for Simple Paged Results Manipulation".

ext/php cung cấp quyền truy cập vào tiện ích mở rộng kiểm soát LDAP qua số ldap_set_option() và tùy chọn LDAP_OPT_SERVER_CONTROLSLDAP_OPT_CLIENT_CONTROLS tương ứng. Để đặt điều khiển phân trang, bạn cần control-oid, là 1.2.840.113556.1.4.319 và chúng tôi cần biết cách mã hóa giá trị điều khiển (điều này được mô tả trong số RFC).Giá trị là một chuỗi octet gói phiên bản BER-mã hóa của trình tự sau đây (sao chép từ RFC):

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

Vì vậy, chúng ta có thể thiết lập sự kiểm soát máy chủ thích hợp trước khi thực hiện truy vấn 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 
); 

Điều này cho phép chúng tôi gửi truy vấn được phân trang đến máy chủ LDAP/AD. Nhưng làm thế nào để chúng tôi biết nếu có nhiều trang để làm theo và làm thế nào để chúng tôi xác định với giá trị kiểm soát chúng tôi phải gửi truy vấn tiếp theo của chúng tôi?

Đây là nơi chúng tôi gặp khó khăn ... Máy chủ phản hồi với tập kết quả bao gồm thông tin phân trang yêu cầu nhưng PHP thiếu phương thức để lấy chính xác thông tin này từ tập kết quả. PHP cung cấp một trình bao bọc cho hàm LDAP API ldap_parse_result() nhưng tham số cuối cùng được yêu cầu serverctrlsp không được tiếp xúc với hàm PHP, do đó không có cách nào để truy xuất thông tin được yêu cầu. Một bug report đã được nộp cho vấn đề này nhưng vẫn chưa có phản ứng từ năm 2005. Nếu ldap_parse_result() chức năng cung cấp các thông số cần thiết, sử dụng kết quả paged sẽ làm việc như

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

Như bạn thấy có một dòng mã (*) làm cho toàn bộ điều vô dụng. Trên con đường của tôi mặc dù thông tin thưa thớt về chủ đề này, tôi tìm thấy một bản vá chống lại PHP 4.3.10 ext/ldap bởi Iñaki Arenaza nhưng tôi cũng không thử nó cũng không biết liệu bản vá có thể được áp dụng trên PHP5 ext/ldap hay không. Các miếng vá kéo dài ldap_parse_result() để lộ các thông số 7 đến 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) {

Trên thực tế lựa chọn duy nhất còn lại sẽ được thay đổi cấu hình Active Directory và nâng cao giới hạn kết quả tối đa. Tùy chọn có liên quan được gọi là MaxPageSize và có thể được thay đổi bằng cách sử dụng ntdsutil.exe - vui lòng xem "How to view and set LDAP policy in Active Directory by using Ntdsutil.exe".

EDIT (tham chiếu đến COM):

Hoặc bạn có thể đi theo chiều ngược lại và sử dụng COM-cách tiếp cận qua ADODB như đề xuất trong link cung cấp bởi eykanal.

+0

Câu trả lời tuyệt vời! Cảm ơn rất nhiều, điều này đã giúp tôi rất nhiều! – Christian

+2

Trạng thái của việc này là gì khi PHP 5.4 hỗ trợ các kết quả LDAP được phân trang? – Squazic

+1

@Squazic: Xem câu trả lời http://stackoverflow.com/a/10140166/11354 xuống dưới đây. Dường như có thể làm được bắt đầu với PHP 5.4. –

3

Đây không phải là câu trả lời đầy đủ, nhưng this guy đã có thể thực hiện. Tôi không hiểu anh ta đã làm gì.

Nhân tiện, câu trả lời một phần là bạn CÓ THỂ nhận được "trang" của kết quả. Từ documentation:

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

sizelimit Cho phép bạn giới hạn số lượng các mục vời. Đặt giá trị này thành 0 có nghĩa là không có giới hạn.

Lưu ý: Tham số này KHÔNG thể ghi đè lên sizelimit được đặt trước phía máy chủ. Bạn có thể đặt giá thấp hơn. Một số máy chủ lưu trữ thư mục sẽ được đặt cấu hình để trả về không quá số lần nhập trước. Nếu điều này xảy ra , máy chủ sẽ chỉ ra rằng nó chỉ trả về một phần kết quả được đặt. Điều này cũng xảy ra nếu bạn sử dụng thông số này để giới hạn số lượng các mục nhập đã tìm nạp là .

Tôi không biết cách xác định rằng bạn muốn tìm kiếm BẮT ĐẦU từ một vị trí nhất định. Tức là, sau khi bạn nhận được 1000 đầu tiên của mình, tôi không biết làm thế nào để xác định rằng bây giờ bạn cần 1000. Hy vọng rằng ai đó có thể giúp bạn ở đó :)

6

Hỗ trợ cho các kết quả phân trang đã được thêm vào trong PHP 5.4.

Xem ldap_control_paged_result để biết thêm chi tiết.

0

Đây là giải pháp thay thế (hoạt động trước PHP 5.4). Nếu bạn có 10.000 hồ sơ bạn cần phải nhận được nhưng máy chủ AD của bạn chỉ trả 5.000 mỗi trang:

$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

tôi đã có thể để có được xung quanh giới hạn kích thước sử dụng ldap_control_paged_result

ldap_control_paged_result được sử dụng để Enable pagination LDAP bằng cách gửi điều khiển phân trang. Hàm dưới đây hoạt động hoàn hảo trong trường hợp của tôi.

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; 
    } 
Các vấn đề liên quan