mirror of
https://github.com/Icinga/icingaweb2.git
synced 2025-07-21 21:04:25 +02:00
Fix ldap authentication when authenticating against ActiveDirectory
Unlike OpenLDAP, ActiveDirectory does not seem to react on the size limit passed to ldap_search() in global manner causing it to not to respond with LDAP_SIZELIMIT_EXCEEDED (4) in case a requested page contains more entries than the requested maximum. fixes #7993
This commit is contained in:
parent
3852feb069
commit
b828f8b13a
@ -32,6 +32,8 @@ class Connection
|
|||||||
{
|
{
|
||||||
const LDAP_NO_SUCH_OBJECT = 32;
|
const LDAP_NO_SUCH_OBJECT = 32;
|
||||||
const LDAP_SIZELIMIT_EXCEEDED = 4;
|
const LDAP_SIZELIMIT_EXCEEDED = 4;
|
||||||
|
const LDAP_ADMINLIMIT_EXCEEDED = 11;
|
||||||
|
const PAGE_SIZE = 1000;
|
||||||
|
|
||||||
protected $ds;
|
protected $ds;
|
||||||
protected $hostname;
|
protected $hostname;
|
||||||
@ -98,9 +100,6 @@ class Connection
|
|||||||
protected $namingContexts;
|
protected $namingContexts;
|
||||||
protected $discoverySuccess = false;
|
protected $discoverySuccess = false;
|
||||||
|
|
||||||
protected $lastResult;
|
|
||||||
protected $pageCookie;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*
|
*
|
||||||
@ -238,6 +237,7 @@ class Connection
|
|||||||
{
|
{
|
||||||
$query = clone $query;
|
$query = clone $query;
|
||||||
$query->limit(1);
|
$query->limit(1);
|
||||||
|
$query->setUsePagedResults(false);
|
||||||
$results = $this->fetchAll($query, $fields);
|
$results = $this->fetchAll($query, $fields);
|
||||||
return array_shift($results);
|
return array_shift($results);
|
||||||
}
|
}
|
||||||
@ -267,37 +267,146 @@ class Connection
|
|||||||
$this->connect();
|
$this->connect();
|
||||||
$this->bind();
|
$this->bind();
|
||||||
|
|
||||||
$offset = $limit = null;
|
if ($query->getUsePagedResults() && version_compare(PHP_VERSION, '5.4.0') >= 0) {
|
||||||
if ($query->hasLimit()) {
|
return $this->runPagedQuery($query, $fields);
|
||||||
$offset = $query->getOffset();
|
} else {
|
||||||
|
return $this->runQuery($query, $fields);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function runQuery(Query $query, $fields = array())
|
||||||
|
{
|
||||||
$limit = $query->getLimit();
|
$limit = $query->getLimit();
|
||||||
|
$offset = $query->hasOffset() ? $query->getOffset() - 1 : 0;
|
||||||
|
|
||||||
|
$results = @ldap_search(
|
||||||
|
$this->ds,
|
||||||
|
$query->hasBase() ? $query->getBase() : $this->root_dn,
|
||||||
|
$query->create(),
|
||||||
|
empty($fields) ? $query->listFields() : $fields,
|
||||||
|
0, // Attributes and values
|
||||||
|
$limit ? $offset + $limit : 0
|
||||||
|
);
|
||||||
|
if ($results === false) {
|
||||||
|
if (ldap_errno($this->ds) === self::LDAP_NO_SUCH_OBJECT) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new LdapException(
|
||||||
|
'LDAP query "%s" (base %s) failed. Error: %s',
|
||||||
|
$query->create(),
|
||||||
|
$query->hasBase() ? $query->getBase() : $this->root_dn,
|
||||||
|
ldap_error($this->ds)
|
||||||
|
);
|
||||||
|
} elseif (ldap_count_entries($this->ds, $results) === 0) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($query->getSortColumns() as $col) {
|
||||||
|
ldap_sort($this->ds, $results, $col[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$count = 0;
|
$count = 0;
|
||||||
$entries = array();
|
$entries = array();
|
||||||
$results = $this->runQuery($query, $fields);
|
|
||||||
while (! empty($results)) {
|
|
||||||
$entry = ldap_first_entry($this->ds, $results);
|
$entry = ldap_first_entry($this->ds, $results);
|
||||||
while ($entry) {
|
do {
|
||||||
$count++;
|
$count += 1;
|
||||||
if (
|
if ($offset === 0 || $offset < $count) {
|
||||||
($offset === null || $offset <= $count)
|
|
||||||
&& ($limit === null || $limit > count($entries))
|
|
||||||
) {
|
|
||||||
$entries[ldap_get_dn($this->ds, $entry)] = $this->cleanupAttributes(
|
$entries[ldap_get_dn($this->ds, $entry)] = $this->cleanupAttributes(
|
||||||
ldap_get_attributes($this->ds, $entry)
|
ldap_get_attributes($this->ds, $entry)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} while (($limit === 0 || $limit !== count($entries)) && ($entry = ldap_next_entry($this->ds, $entry)));
|
||||||
|
|
||||||
$entry = ldap_next_entry($this->ds, $entry);
|
ldap_free_result($results);
|
||||||
}
|
|
||||||
|
|
||||||
$results = $this->runQuery($query, $fields);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $entries;
|
return $entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function runPagedQuery(Query $query, $fields = array())
|
||||||
|
{
|
||||||
|
$limit = $query->getLimit();
|
||||||
|
$offset = $query->hasOffset() ? $query->getOffset() - 1 : 0;
|
||||||
|
$queryString = $query->create();
|
||||||
|
$base = $query->hasBase() ? $query->getBase() : $this->root_dn;
|
||||||
|
|
||||||
|
if (empty($fields)) {
|
||||||
|
$fields = $query->listFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
$count = 0;
|
||||||
|
$cookie = '';
|
||||||
|
$entries = array();
|
||||||
|
do {
|
||||||
|
ldap_control_paged_result($this->ds, static::PAGE_SIZE, true, $cookie);
|
||||||
|
$results = @ldap_search($this->ds, $base, $queryString, $fields, 0, $limit ? $offset + $limit : 0);
|
||||||
|
if ($results === false) {
|
||||||
|
if (ldap_errno($this->ds) === self::LDAP_NO_SUCH_OBJECT) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new LdapException(
|
||||||
|
'LDAP query "%s" (base %s) failed. Error: %s',
|
||||||
|
$queryString,
|
||||||
|
$base,
|
||||||
|
ldap_error($this->ds)
|
||||||
|
);
|
||||||
|
} elseif (ldap_count_entries($this->ds, $results) === 0) {
|
||||||
|
if (in_array(
|
||||||
|
ldap_errno($this->ds),
|
||||||
|
array(static::LDAP_SIZELIMIT_EXCEEDED, static::LDAP_ADMINLIMIT_EXCEEDED)
|
||||||
|
)) {
|
||||||
|
Logger::warning(
|
||||||
|
'Unable to request more than %u results. Does the server allow paged search requests? (%s)',
|
||||||
|
$count,
|
||||||
|
ldap_error($this->ds)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$entry = ldap_first_entry($this->ds, $results);
|
||||||
|
do {
|
||||||
|
$count += 1;
|
||||||
|
if ($offset === 0 || $offset < $count) {
|
||||||
|
$entries[ldap_get_dn($this->ds, $entry)] = $this->cleanupAttributes(
|
||||||
|
ldap_get_attributes($this->ds, $entry)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} while (($limit === 0 || $limit !== count($entries)) && ($entry = ldap_next_entry($this->ds, $entry)));
|
||||||
|
|
||||||
|
try {
|
||||||
|
ldap_control_paged_result_response($this->ds, $results, $cookie);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// If the page size is greater than or equal to the sizeLimit value, the server should ignore the
|
||||||
|
// control as the request can be satisfied in a single page: https://www.ietf.org/rfc/rfc2696.txt
|
||||||
|
// This applies no matter whether paged search requests are permitted or not. You're done once you
|
||||||
|
// got everything you were out for.
|
||||||
|
if (count($entries) !== $limit) {
|
||||||
|
Logger::warning(
|
||||||
|
'Unable to request paged LDAP results. Does the server allow paged search requests? (%s)',
|
||||||
|
$e->getMessage()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ldap_free_result($results);
|
||||||
|
} while ($cookie && ($limit === 0 || count($entries) < $limit));
|
||||||
|
|
||||||
|
if ($cookie) {
|
||||||
|
// A sequence of paged search requests is abandoned by the client sending a search request containing a
|
||||||
|
// pagedResultsControl with the size set to zero (0) and the cookie set to the last cookie returned by
|
||||||
|
// the server: https://www.ietf.org/rfc/rfc2696.txt
|
||||||
|
ldap_control_paged_result($this->ds, 0, false, $cookie);
|
||||||
|
ldap_search($this->ds, $base, $queryString, $fields); // Returns no entries, due to the page size
|
||||||
|
} else {
|
||||||
|
// Reset the paged search request so that subsequent requests succeed
|
||||||
|
ldap_control_paged_result($this->ds, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $entries; // TODO(7693): Sort entries post-processed
|
||||||
|
}
|
||||||
|
|
||||||
protected function cleanupAttributes($attrs)
|
protected function cleanupAttributes($attrs)
|
||||||
{
|
{
|
||||||
$clean = (object) array();
|
$clean = (object) array();
|
||||||
@ -314,79 +423,6 @@ class Connection
|
|||||||
return $clean;
|
return $clean;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function runQuery(Query $query, $fields = array())
|
|
||||||
{
|
|
||||||
if ($query->getUsePagedResults() && version_compare(PHP_VERSION, '5.4.0') >= 0) {
|
|
||||||
if ($this->pageCookie === null) {
|
|
||||||
$this->pageCookie = '';
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
ldap_control_paged_result_response($this->ds, $this->lastResult, $this->pageCookie);
|
|
||||||
} catch (Exception $e) {
|
|
||||||
$this->pageCookie = '';
|
|
||||||
if (! $query->hasLimit() || ldap_errno($this->ds) !== static::LDAP_SIZELIMIT_EXCEEDED) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
$base = $query->hasBase() ? $query->getBase() : $this->root_dn;
|
|
||||||
$results = @ldap_search(
|
|
||||||
$this->ds,
|
|
||||||
$base,
|
|
||||||
$query->create(),
|
|
||||||
empty($fields) ? $query->listFields() : $fields,
|
|
||||||
0, // Attributes and values
|
|
||||||
$query->hasLimit() ? $query->getOffset() + $query->getLimit() : 0 // No limit - at least where possible
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($results === false) {
|
|
||||||
if (ldap_errno($this->ds) === self::LDAP_NO_SUCH_OBJECT) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
throw new LdapException(
|
|
||||||
sprintf(
|
|
||||||
'LDAP query "%s" (root %s) failed: %s',
|
|
||||||
$query->create(),
|
|
||||||
$this->root_dn,
|
|
||||||
ldap_error($this->ds)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($query->getSortColumns() as $col) {
|
|
||||||
ldap_sort($this->ds, $results, $col[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->lastResult = $results;
|
|
||||||
return $results;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testCredentials($username, $password)
|
public function testCredentials($username, $password)
|
||||||
{
|
{
|
||||||
$this->connect();
|
$this->connect();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user