LdapConnection: Establish a connection lazily

This commit is contained in:
Johannes Meyer 2015-07-10 09:51:39 +02:00
parent 39df25f043
commit a8ac420897
4 changed files with 82 additions and 84 deletions

View File

@ -156,7 +156,6 @@ class LdapResourceForm extends Form
{ {
try { try {
$resource = ResourceFactory::createResource(new ConfigObject($form->getValues())); $resource = ResourceFactory::createResource(new ConfigObject($form->getValues()));
$resource->connect();
$resource->bind(); $resource->bind();
} catch (Exception $e) { } catch (Exception $e) {
$msg = $form->translate('Connectivity validation failed, connection to the given resource not possible.'); $msg = $form->translate('Connectivity validation failed, connection to the given resource not possible.');

View File

@ -13,13 +13,6 @@ class Discovery {
*/ */
private $connection; private $connection;
/**
* If discovery was already performed
*
* @var bool
*/
private $discovered = false;
/** /**
* @param LdapConnection $conn The ldap connection to use for the discovery * @param LdapConnection $conn The ldap connection to use for the discovery
*/ */
@ -28,17 +21,6 @@ class Discovery {
$this->connection = $conn; $this->connection = $conn;
} }
/**
* Execute the discovery on the underlying connection
*/
private function execDiscovery()
{
if (! $this->discovered) {
$this->connection->connect();
$this->discovered = true;
}
}
/** /**
* Suggests a resource configuration of hostname, port and root_dn * Suggests a resource configuration of hostname, port and root_dn
* based on the discovery * based on the discovery
@ -47,10 +29,6 @@ class Discovery {
*/ */
public function suggestResourceSettings() public function suggestResourceSettings()
{ {
if (! $this->discovered) {
$this->execDiscovery();
}
return array( return array(
'hostname' => $this->connection->getHostname(), 'hostname' => $this->connection->getHostname(),
'port' => $this->connection->getPort(), 'port' => $this->connection->getPort(),
@ -66,7 +44,6 @@ class Discovery {
*/ */
public function suggestBackendSettings() public function suggestBackendSettings()
{ {
$this->execDiscovery();
if ($this->isAd()) { if ($this->isAd()) {
return array( return array(
'base_dn' => $this->connection->getCapabilities()->getDefaultNamingContext(), 'base_dn' => $this->connection->getCapabilities()->getDefaultNamingContext(),
@ -89,7 +66,6 @@ class Discovery {
*/ */
public function isAd() public function isAd()
{ {
$this->execDiscovery();
return $this->connection->getCapabilities()->hasAdOid(); return $this->connection->getCapabilities()->hasAdOid();
} }
@ -100,7 +76,6 @@ class Discovery {
*/ */
public function isSuccess() public function isSuccess()
{ {
$this->execDiscovery();
return $this->connection->discoverySuccessful(); return $this->connection->discoverySuccessful();
} }

View File

@ -147,7 +147,7 @@ class LdapConnection implements Selectable
protected $capabilities; protected $capabilities;
/** /**
* Whether discovery was successful or not * Whether discovery was successful
* *
* @var bool * @var bool
*/ */
@ -217,6 +217,22 @@ class LdapConnection implements Selectable
return $this->root; return $this->root;
} }
/**
* Return the LDAP link identifier being used
*
* Establishes a connection if necessary.
*
* @return resource
*/
public function getConnection()
{
if ($this->ds === null) {
$this->ds = $this->prepareNewConnection();
}
return $this->ds;
}
/** /**
* Return the capabilities of the current connection * Return the capabilities of the current connection
* *
@ -232,12 +248,16 @@ class LdapConnection implements Selectable
} }
/** /**
* Return whether discovery was successful or not * Return whether discovery was successful
* *
* @return bool true if the capabilities were successfully determined, false if the capabilities were guessed * @return bool true if the capabilities were successfully determined, false if the capabilities were guessed
*/ */
public function discoverySuccessful() public function discoverySuccessful()
{ {
if ($this->discoverySuccess === null) {
$this->getCapabilities(); // Initializes self::$discoverySuccess
}
return $this->discoverySuccess; return $this->discoverySuccess;
} }
@ -245,12 +265,12 @@ class LdapConnection implements Selectable
* Establish a connection * Establish a connection
* *
* @throws LdapException In case the connection could not be established * @throws LdapException In case the connection could not be established
*
* @deprecated The connection is established lazily now
*/ */
public function connect() public function connect()
{ {
if ($this->ds === null) { $this->getConnection();
$this->ds = $this->prepareNewConnection();
}
} }
/** /**
@ -264,7 +284,9 @@ class LdapConnection implements Selectable
return; return;
} }
$success = @ldap_bind($this->ds, $this->bindDn, $this->bindPw); $ds = $this->getConnection();
$success = @ldap_bind($ds, $this->bindDn, $this->bindPw);
if (! $success) { if (! $success) {
throw new LdapException( throw new LdapException(
'LDAP connection to %s:%s (%s / %s) failed: %s', 'LDAP connection to %s:%s (%s / %s) failed: %s',
@ -272,7 +294,7 @@ class LdapConnection implements Selectable
$this->port, $this->port,
$this->bindDn, $this->bindDn,
'***' /* $this->bindPw */, '***' /* $this->bindPw */,
ldap_error($this->ds) ldap_error($ds)
); );
} }
@ -310,7 +332,6 @@ class LdapConnection implements Selectable
*/ */
public function count(LdapQuery $query) public function count(LdapQuery $query)
{ {
$this->connect();
$this->bind(); $this->bind();
$res = $this->runQuery($query, array()); $res = $this->runQuery($query, array());
@ -327,7 +348,6 @@ class LdapConnection implements Selectable
*/ */
public function fetchAll(LdapQuery $query, array $fields = null) public function fetchAll(LdapQuery $query, array $fields = null)
{ {
$this->connect();
$this->bind(); $this->bind();
if ( if (
@ -465,21 +485,20 @@ class LdapConnection implements Selectable
*/ */
public function testCredentials($bindDn, $bindPw) public function testCredentials($bindDn, $bindPw)
{ {
$this->connect(); $ds = $this->getConnection();
$success = @ldap_bind($ds, $bindDn, $bindPw);
$success = @ldap_bind($this->ds, $bindDn, $bindPw);
if (! $success) { if (! $success) {
if (ldap_errno($this->ds) === self::LDAP_INVALID_CREDENTIALS) { if (ldap_errno($ds) === self::LDAP_INVALID_CREDENTIALS) {
Logger::debug( Logger::debug(
'Testing LDAP credentials (%s / %s) failed: %s', 'Testing LDAP credentials (%s / %s) failed: %s',
$bindDn, $bindDn,
'***', '***',
ldap_error($this->ds) ldap_error($ds)
); );
return false; return false;
} }
throw new LdapException(ldap_error($this->ds)); throw new LdapException(ldap_error($ds));
} }
return true; return true;
@ -494,11 +513,11 @@ class LdapConnection implements Selectable
*/ */
public function hasDn($dn) public function hasDn($dn)
{ {
$this->connect();
$this->bind(); $this->bind();
$result = ldap_read($this->ds, $dn, '(objectClass=*)', array('objectClass')); $ds = $this->getConnection();
return ldap_count_entries($this->ds, $result) > 0; $result = ldap_read($ds, $dn, '(objectClass=*)', array('objectClass'));
return ldap_count_entries($ds, $result) > 0;
} }
/** /**
@ -512,19 +531,19 @@ class LdapConnection implements Selectable
*/ */
public function deleteRecursively($dn) public function deleteRecursively($dn)
{ {
$this->connect();
$this->bind(); $this->bind();
$result = @ldap_list($this->ds, $dn, '(objectClass=*)', array('objectClass')); $ds = $this->getConnection();
$result = @ldap_list($ds, $dn, '(objectClass=*)', array('objectClass'));
if ($result === false) { if ($result === false) {
if (ldap_errno($this->ds) === self::LDAP_NO_SUCH_OBJECT) { if (ldap_errno($ds) === self::LDAP_NO_SUCH_OBJECT) {
return false; return false;
} }
throw new LdapException('LDAP list for "%s" failed: %s', $dn, ldap_error($this->ds)); throw new LdapException('LDAP list for "%s" failed: %s', $dn, ldap_error($ds));
} }
$children = ldap_get_entries($this->ds, $result); $children = ldap_get_entries($ds, $result);
for ($i = 0; $i < $children['count']; $i++) { for ($i = 0; $i < $children['count']; $i++) {
$result = $this->deleteRecursively($children[$i]['dn']); $result = $this->deleteRecursively($children[$i]['dn']);
if (! $result) { if (! $result) {
@ -547,16 +566,16 @@ class LdapConnection implements Selectable
*/ */
public function deleteDn($dn) public function deleteDn($dn)
{ {
$this->connect();
$this->bind(); $this->bind();
$result = @ldap_delete($this->ds, $dn); $ds = $this->getConnection();
$result = @ldap_delete($ds, $dn);
if ($result === false) { if ($result === false) {
if (ldap_errno($this->ds) === self::LDAP_NO_SUCH_OBJECT) { if (ldap_errno($ds) === self::LDAP_NO_SUCH_OBJECT) {
return false; // TODO: Isn't it a success if something i'd like to remove is not existing at all??? return false; // TODO: Isn't it a success if something i'd like to remove is not existing at all???
} }
throw new LdapException('LDAP delete for "%s" failed: %s', $dn, ldap_error($this->ds)); throw new LdapException('LDAP delete for "%s" failed: %s', $dn, ldap_error($ds));
} }
return true; return true;
@ -600,9 +619,11 @@ class LdapConnection implements Selectable
$fields = $query->getColumns(); $fields = $query->getColumns();
} }
$ds = $this->getConnection();
$serverSorting = false;//$this->capabilities->hasOid(Capability::LDAP_SERVER_SORT_OID); $serverSorting = false;//$this->capabilities->hasOid(Capability::LDAP_SERVER_SORT_OID);
if ($serverSorting && $query->hasOrder()) { if ($serverSorting && $query->hasOrder()) {
ldap_set_option($this->ds, LDAP_OPT_SERVER_CONTROLS, array( ldap_set_option($ds, LDAP_OPT_SERVER_CONTROLS, array(
array( array(
'oid' => Capability::LDAP_SERVER_SORT_OID, 'oid' => Capability::LDAP_SERVER_SORT_OID,
'value' => $this->encodeSortRules($query->getOrder()) 'value' => $this->encodeSortRules($query->getOrder())
@ -617,7 +638,7 @@ class LdapConnection implements Selectable
} }
$results = @ldap_search( $results = @ldap_search(
$this->ds, $ds,
$query->getBase() ?: $this->rootDn, $query->getBase() ?: $this->rootDn,
(string) $query, (string) $query,
array_values($fields), array_values($fields),
@ -625,7 +646,7 @@ class LdapConnection implements Selectable
$serverSorting && $limit ? $offset + $limit : 0 $serverSorting && $limit ? $offset + $limit : 0
); );
if ($results === false) { if ($results === false) {
if (ldap_errno($this->ds) === self::LDAP_NO_SUCH_OBJECT) { if (ldap_errno($ds) === self::LDAP_NO_SUCH_OBJECT) {
return array(); return array();
} }
@ -633,25 +654,25 @@ class LdapConnection implements Selectable
'LDAP query "%s" (base %s) failed. Error: %s', 'LDAP query "%s" (base %s) failed. Error: %s',
$query, $query,
$query->getBase() ?: $this->rootDn, $query->getBase() ?: $this->rootDn,
ldap_error($this->ds) ldap_error($ds)
); );
} elseif (ldap_count_entries($this->ds, $results) === 0) { } elseif (ldap_count_entries($ds, $results) === 0) {
return array(); return array();
} }
$count = 0; $count = 0;
$entries = array(); $entries = array();
$entry = ldap_first_entry($this->ds, $results); $entry = ldap_first_entry($ds, $results);
do { do {
$count += 1; $count += 1;
if (! $serverSorting || $offset === 0 || $offset < $count) { if (! $serverSorting || $offset === 0 || $offset < $count) {
$entries[ldap_get_dn($this->ds, $entry)] = $this->cleanupAttributes( $entries[ldap_get_dn($ds, $entry)] = $this->cleanupAttributes(
ldap_get_attributes($this->ds, $entry), array_flip($fields) ldap_get_attributes($ds, $entry), array_flip($fields)
); );
} }
} while ( } while (
(! $serverSorting || $limit === 0 || $limit !== count($entries)) (! $serverSorting || $limit === 0 || $limit !== count($entries))
&& ($entry = ldap_next_entry($this->ds, $entry)) && ($entry = ldap_next_entry($ds, $entry))
); );
if (! $serverSorting && $query->hasOrder()) { if (! $serverSorting && $query->hasOrder()) {
@ -693,9 +714,11 @@ class LdapConnection implements Selectable
$fields = $query->getColumns(); $fields = $query->getColumns();
} }
$ds = $this->getConnection();
$serverSorting = false;//$this->capabilities->hasOid(Capability::LDAP_SERVER_SORT_OID); $serverSorting = false;//$this->capabilities->hasOid(Capability::LDAP_SERVER_SORT_OID);
if ($serverSorting && $query->hasOrder()) { if ($serverSorting && $query->hasOrder()) {
ldap_set_option($this->ds, LDAP_OPT_SERVER_CONTROLS, array( ldap_set_option($ds, LDAP_OPT_SERVER_CONTROLS, array(
array( array(
'oid' => Capability::LDAP_SERVER_SORT_OID, 'oid' => Capability::LDAP_SERVER_SORT_OID,
'value' => $this->encodeSortRules($query->getOrder()) 'value' => $this->encodeSortRules($query->getOrder())
@ -715,10 +738,10 @@ class LdapConnection implements Selectable
do { do {
// Do not request the pagination control as a critical extension, as we want the // Do not request the pagination control as a critical extension, as we want the
// server to return results even if the paged search request cannot be satisfied // server to return results even if the paged search request cannot be satisfied
ldap_control_paged_result($this->ds, $pageSize, false, $cookie); ldap_control_paged_result($ds, $pageSize, false, $cookie);
$results = @ldap_search( $results = @ldap_search(
$this->ds, $ds,
$base, $base,
$queryString, $queryString,
array_values($fields), array_values($fields),
@ -726,7 +749,7 @@ class LdapConnection implements Selectable
$serverSorting && $limit ? $offset + $limit : 0 $serverSorting && $limit ? $offset + $limit : 0
); );
if ($results === false) { if ($results === false) {
if (ldap_errno($this->ds) === self::LDAP_NO_SUCH_OBJECT) { if (ldap_errno($ds) === self::LDAP_NO_SUCH_OBJECT) {
break; break;
} }
@ -734,37 +757,37 @@ class LdapConnection implements Selectable
'LDAP query "%s" (base %s) failed. Error: %s', 'LDAP query "%s" (base %s) failed. Error: %s',
$queryString, $queryString,
$base, $base,
ldap_error($this->ds) ldap_error($ds)
); );
} elseif (ldap_count_entries($this->ds, $results) === 0) { } elseif (ldap_count_entries($ds, $results) === 0) {
if (in_array( if (in_array(
ldap_errno($this->ds), ldap_errno($ds),
array(static::LDAP_SIZELIMIT_EXCEEDED, static::LDAP_ADMINLIMIT_EXCEEDED) array(static::LDAP_SIZELIMIT_EXCEEDED, static::LDAP_ADMINLIMIT_EXCEEDED)
)) { )) {
Logger::warning( Logger::warning(
'Unable to request more than %u results. Does the server allow paged search requests? (%s)', 'Unable to request more than %u results. Does the server allow paged search requests? (%s)',
$count, $count,
ldap_error($this->ds) ldap_error($ds)
); );
} }
break; break;
} }
$entry = ldap_first_entry($this->ds, $results); $entry = ldap_first_entry($ds, $results);
do { do {
$count += 1; $count += 1;
if (! $serverSorting || $offset === 0 || $offset < $count) { if (! $serverSorting || $offset === 0 || $offset < $count) {
$entries[ldap_get_dn($this->ds, $entry)] = $this->cleanupAttributes( $entries[ldap_get_dn($ds, $entry)] = $this->cleanupAttributes(
ldap_get_attributes($this->ds, $entry), array_flip($fields) ldap_get_attributes($ds, $entry), array_flip($fields)
); );
} }
} while ( } while (
(! $serverSorting || $limit === 0 || $limit !== count($entries)) (! $serverSorting || $limit === 0 || $limit !== count($entries))
&& ($entry = ldap_next_entry($this->ds, $entry)) && ($entry = ldap_next_entry($ds, $entry))
); );
if (false === @ldap_control_paged_result_response($this->ds, $results, $cookie)) { if (false === @ldap_control_paged_result_response($ds, $results, $cookie)) {
// If the page size is greater than or equal to the sizeLimit value, the server should ignore the // 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 // 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 // This applies no matter whether paged search requests are permitted or not. You're done once you
@ -785,11 +808,11 @@ class LdapConnection implements Selectable
// A sequence of paged search requests is abandoned by the client sending a search request containing a // 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 // 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 // the server: https://www.ietf.org/rfc/rfc2696.txt
ldap_control_paged_result($this->ds, 0, false, $cookie); ldap_control_paged_result($ds, 0, false, $cookie);
ldap_search($this->ds, $base, $queryString); // Returns no entries, due to the page size ldap_search($ds, $base, $queryString); // Returns no entries, due to the page size
} else { } else {
// Reset the paged search request so that subsequent requests succeed // Reset the paged search request so that subsequent requests succeed
ldap_control_paged_result($this->ds, 0); ldap_control_paged_result($ds, 0);
} }
if (! $serverSorting && $query->hasOrder()) { if (! $serverSorting && $query->hasOrder()) {
@ -1031,7 +1054,7 @@ class LdapConnection implements Selectable
*/ */
public function addEntry($dn, array $attributes) public function addEntry($dn, array $attributes)
{ {
return ldap_add($this->ds, $dn, $attributes); return ldap_add($this->getConnection(), $dn, $attributes);
} }
/** /**
@ -1044,7 +1067,7 @@ class LdapConnection implements Selectable
*/ */
public function modifyEntry($dn, array $attributes) public function modifyEntry($dn, array $attributes)
{ {
return ldap_modify($this->ds, $dn, $attributes); return ldap_modify($this->getConnection(), $dn, $attributes);
} }
/** /**
@ -1060,9 +1083,10 @@ class LdapConnection implements Selectable
*/ */
public function moveEntry($dn, $newRdn, $newParentDn) public function moveEntry($dn, $newRdn, $newParentDn)
{ {
$result = ldap_rename($this->ds, $dn, $newRdn, $newParentDn, false); $ds = $this->getConnection();
$result = ldap_rename($ds, $dn, $newRdn, $newParentDn, false);
if ($result === false) { if ($result === false) {
throw new LdapException('Could not move entry "%s" to "%s": %s', $dn, $newRdn, ldap_error($this->ds)); throw new LdapException('Could not move entry "%s" to "%s": %s', $dn, $newRdn, ldap_error($ds));
} }
return $result; return $result;

View File

@ -26,7 +26,7 @@ class LdapResourceFormTest extends BaseTestCase
public function testValidLdapResourceIsValid() public function testValidLdapResourceIsValid()
{ {
$this->setUpResourceFactoryMock( $this->setUpResourceFactoryMock(
Mockery::mock()->shouldReceive('connect')->once()->shouldReceive('bind')->once()->getMock() Mockery::mock()->shouldReceive('bind')->once()->getMock()
); );
// Passing array(null) is required to make Mockery call the constructor... // Passing array(null) is required to make Mockery call the constructor...
@ -49,7 +49,7 @@ class LdapResourceFormTest extends BaseTestCase
public function testInvalidLdapResourceIsNotValid() public function testInvalidLdapResourceIsNotValid()
{ {
$this->setUpResourceFactoryMock( $this->setUpResourceFactoryMock(
Mockery::mock()->shouldReceive('connect')->once()->shouldReceive('bind')->andThrow('\Exception')->getMock() Mockery::mock()->shouldReceive('bind')->andThrow('\Exception')->getMock()
); );
// Passing array(null) is required to make Mockery call the constructor... // Passing array(null) is required to make Mockery call the constructor...