Add support for paged LDAP search results

fixes #8261
refs #6176
This commit is contained in:
Johannes Meyer 2015-01-29 15:53:15 +01:00
parent 7e792f5d2e
commit 2a115e71d4
3 changed files with 97 additions and 40 deletions

View File

@ -213,7 +213,7 @@ class LdapUserBackend extends UserBackend
*/ */
public function count() public function count()
{ {
return $this->conn->count($this->selectUsers()); return $this->selectUsers()->count();
} }
/** /**

View File

@ -4,6 +4,7 @@
namespace Icinga\Protocol\Ldap; namespace Icinga\Protocol\Ldap;
use Exception;
use Icinga\Protocol\Ldap\Exception as LdapException; use Icinga\Protocol\Ldap\Exception as LdapException;
use Icinga\Application\Platform; use Icinga\Application\Platform;
use Icinga\Application\Config; use Icinga\Application\Config;
@ -97,6 +98,9 @@ class Connection
protected $namingContexts; protected $namingContexts;
protected $discoverySuccess = false; protected $discoverySuccess = false;
protected $lastResult;
protected $pageCookie;
/** /**
* Constructor * Constructor
* *
@ -250,44 +254,55 @@ class Connection
*/ */
public function count(Query $query) public function count(Query $query)
{ {
$results = $this->runQuery($query, '+'); $this->connect();
if (! $results) { $this->bind();
return 0;
$count = 0;
$results = $this->runQuery($query);
while (! empty($results)) {
$count += ldap_count_entries($this->ds, $results);
$results = $this->runQuery($query);
} }
return ldap_count_entries($this->ds, $results);
return $count;
} }
public function fetchAll($query, $fields = array()) public function fetchAll(Query $query, $fields = array())
{ {
$offset = null; $this->connect();
$limit = null; $this->bind();
$offset = $limit = null;
if ($query->hasLimit()) { if ($query->hasLimit()) {
$offset = $query->getOffset(); $offset = $query->getOffset();
$limit = $query->getLimit(); $limit = $query->getLimit();
} }
$count = 0;
$entries = array(); $entries = array();
$results = $this->runQuery($query, $fields); $results = $this->runQuery($query, $fields);
if (! $results) { while (! empty($results)) {
return array(); $entry = ldap_first_entry($this->ds, $results);
} while ($entry) {
$entry = ldap_first_entry($this->ds, $results); $count++;
$count = 0; if (($offset === null || $offset <= $count)
while ($entry) { && ($limit === null || ($offset + $limit) > $count)
if (($offset === null || $offset <= $count) ) {
&& ($limit === null || ($offset + $limit) >= $count) $entries[ldap_get_dn($this->ds, $entry)] = $this->cleanupAttributes(
) { ldap_get_attributes($this->ds, $entry)
$attrs = ldap_get_attributes($this->ds, $entry); );
$entries[ldap_get_dn($this->ds, $entry)] }
= $this->cleanupAttributes($attrs);
$entry = ldap_next_entry($this->ds, $entry);
} }
$count++;
$entry = ldap_next_entry($this->ds, $entry); $results = $this->runQuery($query, $fields);
} }
ldap_free_result($results);
return $entries; return $entries;
} }
public function cleanupAttributes(& $attrs) protected function cleanupAttributes($attrs)
{ {
$clean = (object) array(); $clean = (object) array();
for ($i = 0; $i < $attrs['count']; $i++) { for ($i = 0; $i < $attrs['count']; $i++) {
@ -303,26 +318,55 @@ class Connection
return $clean; return $clean;
} }
protected function runQuery($query, $fields) protected function runQuery(Query $query, $fields = array())
{ {
$this->connect(); if ($query->getUsePagedResults()) {
$this->bind(); if ($this->pageCookie === null) {
if ($query instanceof Query) { $this->pageCookie = '';
$fields = $query->listFields(); } else {
try {
ldap_control_paged_result_response($this->ds, $this->lastResult, $this->pageCookie);
} catch (Exception $e) {
$this->pageCookie = '';
Logger::error(
'Unable to request paged LDAP results. Does the server allow paged search requests? (%s)',
$e->getMessage()
);
}
ldap_free_result($this->lastResult);
if (! $this->pageCookie) {
$this->pageCookie = $this->lastResult = null;
// Abandon the paged search request so that subsequent requests succeed
ldap_control_paged_result($this->ds, 0);
return false;
}
}
// Does not matter whether we'll use a valid page size here,
// as the server applies its hard limit in case its too high
ldap_control_paged_result(
$this->ds,
$query->hasLimit() ? $query->getLimit() : 500,
true,
$this->pageCookie
);
} elseif ($this->lastResult !== null) {
ldap_free_result($this->lastResult);
$this->lastResult = null;
return false;
} }
// WARNING:
// We do not support pagination right now, and there is no chance to
// do so for PHP < 5.4. Warnings about "Sizelimit exceeded" will
// therefore not be hidden right now.
$base = $query->hasBase() ? $query->getBase() : $this->root_dn; $base = $query->hasBase() ? $query->getBase() : $this->root_dn;
$results = @ldap_search( $results = @ldap_search(
$this->ds, $this->ds,
$base, $base,
$query->create(), $query->create(),
$fields, empty($fields) ? $query->listFields() : $fields,
0, // Attributes and values 0, // Attributes and values
0 // No limit - at least where possible 0 // No limit - at least where possible
); );
if ($results === false) { if ($results === false) {
if (ldap_errno($this->ds) === self::LDAP_NO_SUCH_OBJECT) { if (ldap_errno($this->ds) === self::LDAP_NO_SUCH_OBJECT) {
return false; return false;
@ -336,12 +380,12 @@ class Connection
) )
); );
} }
$list = array();
if ($query instanceof Query) { foreach ($query->getSortColumns() as $col) {
foreach ($query->getSortColumns() as $col) { ldap_sort($this->ds, $results, $col[0]);
ldap_sort($this->ds, $results, $col[0]);
}
} }
$this->lastResult = $results;
return $results; return $results;
} }

View File

@ -33,6 +33,7 @@ class Query
protected $sort_columns = array(); protected $sort_columns = array();
protected $count; protected $count;
protected $base; protected $base;
protected $usePagedResults;
/** /**
* Constructor * Constructor
@ -43,6 +44,7 @@ class Query
public function __construct(Connection $connection) public function __construct(Connection $connection)
{ {
$this->connection = $connection; $this->connection = $connection;
$this->usePagedResults = version_compare(PHP_VERSION, '5.4.0') >= 0;
} }
public function setBase($base) public function setBase($base)
@ -61,6 +63,17 @@ class Query
return $this->base; return $this->base;
} }
public function setUsePagedResults($state = true)
{
$this->usePagedResults = (bool) $state && version_compare(PHP_VERSION, '5.4.0') >= 0;
return $this;
}
public function getUsePagedResults()
{
return $this->usePagedResults;
}
/** /**
* Count result set, ignoring limits * Count result set, ignoring limits
* *