PHP ldap_search大小限制超出限制

7

我对查询 Microsoft 的 Active Directory 还很陌生,遇到了一些困难:

AD 每个请求的元素数量有 1000 个的限制。我无法更改大小限制。PHP 似乎不支持分页(我使用的是版本 5.2,没有办法更新生产服务器。)

到目前为止,我遇到了两种可能的解决方案:

  1. 按 objectSid 排序条目并使用过滤器获取所有对象。示例代码
    我不喜欢这个方法,有以下几个原因:
    • 看起来捣鼓 objectSid 是不可预测的,因为你必须将其拆开,转换为十进制,再转换回来...
    • 我不知道如何比较这些 id。
      (我尝试过:'&((objectClass=user)(objectSid>=0))')


  2. 根据对象名称的前几个字母进行过滤(如此处所建议的):
    这不是最佳的解决方案,因为我们系统中许多用户/组都以相同的几个字母作为前缀。

所以我的问题是:

在这里使用哪种方法最好?
如果是第一种方法,如何确保正确处理 objectSid?

还有其他可能性吗? 我是否忽略了一些明显的东西?

更新:
- 这个相关问题提供了关于为什么简单分页结果扩展不起作用的信息。
- Web 服务器运行在 Linux 服务器上,因此 COM 对象/adoDB 不是一个选项。

4个回答

5

我可以使用ldap_control_paged_result绕过大小限制。

ldap_control_paged_result用于通过发送分页控件启用LDAP分页。以下函数在我这里完美地工作。适用于(PHP 5 >= 5.4.0, PHP 7)

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

如果您现在已成功更新了服务器,则上述函数可以获取所有条目。我正在使用此函数获取我们AD中的所有用户。

1
在 PHP 7.4 中,ldap_control_paged_result() 已被弃用。现在我们可以在 ldap_search() 中传递 cookie。请参见 https://www.php.net/manual/fr/ldap.examples-controls.php 中的示例#5。 - Worst

2

由于我没有找到任何干净的解决方案,所以我决定采用第一种方法:通过对象SID进行过滤。

这种解决方法有其局限性:

  1. 它仅适用于具有对象SID的对象,即用户和组。
  2. 它假设所有用户/组都是由同一权限创建的。
  3. 它假设不存在更多丢失的相关SID,超出了大小限制。

思路是首先读取所有可能的对象,并挑选出相对SID最低的对象。相对SID是SID中的最后一个块:

S-1-5-21-3188256696-111411151-3922474875-1158

假设这是在仅返回“部分搜索结果”的搜索中最低的相对SID。 再假设大小限制为1000。

然后程序执行以下操作: 搜索所有SID位于以下范围内的对象

S-1-5-21-3188256696-111411151-3922474875-1158

S-1-5-21-3188256696-111411151-3922474875-0159

然后搜索所有位于以下范围内的对象

S-1-5-21-3188256696-111411151-3922474875-1158

S-1-5-21-3188256696-111411151-3922474875-2157

以此类推,直到其中一个搜索返回零个对象。

这种方法存在几个问题,但对于我的目的而言已足够。
代码如下:

$filter = '(objectClass=Group)';
$attributes = array('objectsid','cn'); //objectsid needs to be set

$result = array();

$maxPageSize = 1000;
$searchStep = $maxPageSize-1;

$adResult = @$adConn->search($filter,$attributes); //Supress warning for first query (because it exceeds the size limit)

//Read smallest RID from the resultset
$minGroupRID = '';

for($i=0;$i<$adResult['count'];$i++){
    $groupRID = unpack('V',substr($adResult[$i]['objectsid'][0],24));
    if($minGroupRID == '' || $minGroupRID>$groupRID[1]){
        $minGroupRID = $groupRID[1];
    }    
}

$sidPrefix =  substr($adResult[$i-1]['objectsid'][0],0,24);   //Read last objectsid and cut off the prefix

$nextStepGroupRID = $minGroupRID;

do{ //Search for all objects with a lower objectsid than minGroupRID
    $adResult = $adConn->search('(&'.$filter.'(objectsid<='.preg_replace('/../','\\\\$0',bin2hex($sidPrefix.pack('V',$nextStepGroupRID))).')(objectsid>='.preg_replace('/../','\\\\$0',bin2hex($sidPrefix.pack('V',$nextStepGroupRID-$searchStep))).'))', $attributes);

    for($i=0;$i<$adResult['count'];$i++){
        $RID = unpack('V',substr($adResult[$i]['objectsid'][0],24));    //Extract the relative SID from the SID
        $RIDs[] = $RID[1];

        $resultSet = array();
        foreach($attributes as $attribute){
            $resultSet[$attribute] = $adResult[$i][$attribute][0];
        }
        $result[$RID[1]] = $resultSet;
    }

    $nextStepGroupRID = $nextStepGroupRID-$searchStep;

}while($adResult['count']>1);

$nextStepGroupRID = $minGroupRID;

do{ //Search for all object with a higher objectsid than minGroupRID  
    $adResult = $adConn->search('(&'.$filter.'(objectsid>='.preg_replace('/../','\\\\$0',bin2hex($sidPrefix.pack('V',$nextStepGroupRID))).')(objectsid<='.preg_replace('/../','\\\\$0',bin2hex($sidPrefix.pack('V',$nextStepGroupRID+$searchStep))).'))', $attributes);

    for($i=0;$i<$adResult['count'];$i++){
        $RID = unpack('V',substr($adResult[$i]['objectsid'][0],24));    //Extract the relative SID from the SID
        $RIDs[] = $RID[1];

        $resultSet = array();
        foreach($attributes as $attribute){
            $resultSet[$attribute] = $adResult[$i][$attribute][0];
        }
        $result[$RID[1]] = $resultSet;
    }

    $nextStepGroupRID = $nextStepGroupRID+$searchStep;

}while($adResult['count']>1);

var_dump($result);

$adConn->search方法的样式如下:

function search($filter, $attributes = false, $base_dn = null) {
        if(!isset($base_dn)){
            $base_dn = $this->baseDN;
        }

        $entries = false;
        if (is_string($filter) && $this->bind) {
                if (is_array($attributes)) {
                        $search  = ldap_search($this->resource, $base_dn, $filter, $attributes);
                } else {
                        $search  = ldap_search($this->resource, $base_dn, $filter);
                }
                if ($search !== false) {
                        $entries = ldap_get_entries($this->resource, $search);
                }
        }
        return $entries;
}

1

永远不要对服务器或服务器配置做出假设,这会导致代码脆弱且意外的失败,有时甚至是惊人的失败。仅仅因为今天是AD并不意味着明天还是,或者微软不会更改服务器上的默认限制。最近我处理了一个情况,客户端代码是根据团队知识编写的,认为大小限制为2000,当管理员出于自己的原因更改了大小限制时,客户端代码遭受了可怕的失败。

你确定PHP不支持请求控件(简单分页结果扩展是一种请求控件)吗?我写了一篇关于"LDAP:简单分页结果"的文章,虽然文章示例代码是Java,但概念很重要,而不是语言。另请参阅"LDAP:编程实践"


1
谢谢,关键词“Simple Paged Results”导致了这个问题,其中有一个很好的答案。看起来唯一的方法是使用adoDB,好奇它会怎么运作。 - Envyrus

1

当最近的SIDs之间距离超过999时,可能会出现上一个脚本错误。

例如:

S-1-5-21-3188256696-111411151-3922474875-1158

S-1-5-21-3188256696-111411151-3922474875-3359

3359-1158 > 999

为避免此类错误,您需要使用严格的结构。

例如:

$tt = '1';
do {
    ...
    $nextStepGroupRID = $nextStepGroupRID - $searchStep;
    $tt++;
} while ($tt < '30');

在这个例子中,我们被迫检查999 * 30 * 2 = 59940个值。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接