From 080c04576a2d9d1525cbab3719337bcff01be888 Mon Sep 17 00:00:00 2001 From: Dominic Tubach Date: Mon, 26 Aug 2019 15:18:32 +0200 Subject: [PATCH 1/3] [Ldap] Add LDAP error codes to exceptions #28677 This PR ensures that an LdapException is only used when an LDAP operations fails. In cases an LdapException was used for non LDAP operations it is replaced by an appropriate exception. --- .../Ldap/Adapter/ExtLdap/Adapter.php | 4 +-- .../Ldap/Adapter/ExtLdap/Collection.php | 16 ++++----- .../Ldap/Adapter/ExtLdap/Connection.php | 26 +++++++-------- .../Adapter/ExtLdap/ConnectionOptions.php | 6 ++-- .../Ldap/Adapter/ExtLdap/EntryManager.php | 24 +++++++------- .../Component/Ldap/Adapter/ExtLdap/Query.php | 22 +++++++------ .../Ldap/Adapter/ExtLdap/UpdateOperation.php | 8 ++--- .../Ldap/Exception/ConnectionException.php | 2 +- .../Ldap/Exception/DomainException.php | 16 +++++++++ .../Exception/DriverNotFoundException.php | 2 +- .../Exception/ExtensionNotLoadedException.php | 21 ++++++++++++ .../Ldap/Exception/LdapException.php | 33 ++++++++++++++++++- .../MalformedDistinguishedNameException.php | 21 ++++++++++++ .../Exception/UnexpectedValueException.php | 16 +++++++++ .../Adapter/ExtLdap/EntryManagerTest.php | 3 +- .../Tests/Security/LdapUserProviderTest.php | 2 +- 16 files changed, 165 insertions(+), 57 deletions(-) create mode 100644 src/Symfony/Component/Ldap/Exception/DomainException.php create mode 100644 src/Symfony/Component/Ldap/Exception/ExtensionNotLoadedException.php create mode 100644 src/Symfony/Component/Ldap/Exception/MalformedDistinguishedNameException.php create mode 100644 src/Symfony/Component/Ldap/Exception/UnexpectedValueException.php diff --git a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Adapter.php b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Adapter.php index 0700f6ec5877c..11ed375574a73 100644 --- a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Adapter.php +++ b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Adapter.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Ldap\Adapter\ExtLdap; use Symfony\Component\Ldap\Adapter\AdapterInterface; -use Symfony\Component\Ldap\Exception\LdapException; +use Symfony\Component\Ldap\Exception\ExtensionNotLoadedException; /** * @author Charles Sarrazin @@ -26,7 +26,7 @@ class Adapter implements AdapterInterface public function __construct(array $config = []) { if (!\extension_loaded('ldap')) { - throw new LdapException('The LDAP PHP extension is not enabled.'); + throw new ExtensionNotLoadedException('The LDAP PHP extension is not enabled.'); } $this->config = $config; diff --git a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Collection.php b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Collection.php index 573ab0ce99475..9127e9652564b 100644 --- a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Collection.php +++ b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Collection.php @@ -51,9 +51,9 @@ public function count() $searches = $this->search->getResources(); $count = 0; foreach ($searches as $search) { - $searchCount = ldap_count_entries($con, $search); + $searchCount = @ldap_count_entries($con, $search); if (false === $searchCount) { - throw new LdapException(sprintf('Error while retrieving entry count: %s.', ldap_error($con))); + throw LdapException::create('Error while retrieving entry count: [{errorCode}] {errorMsg}.', ldap_errno($con)); } $count += $searchCount; } @@ -73,10 +73,10 @@ public function getIterator() $con = $this->connection->getResource(); $searches = $this->search->getResources(); foreach ($searches as $search) { - $current = ldap_first_entry($con, $search); + $current = @ldap_first_entry($con, $search); if (false === $current) { - throw new LdapException(sprintf('Could not rewind entries array: %s.', ldap_error($con))); + throw LdapException::create('Could not rewind entries array: [{errorCode}] {errorMsg}.', ldap_errno($con)); } yield $this->getSingleEntry($con, $current); @@ -120,18 +120,18 @@ public function offsetUnset($offset) private function getSingleEntry($con, $current): Entry { - $attributes = ldap_get_attributes($con, $current); + $attributes = @ldap_get_attributes($con, $current); if (false === $attributes) { - throw new LdapException(sprintf('Could not fetch attributes: %s.', ldap_error($con))); + throw LdapException::create('Could not fetch attributes: [{errorCode}] {errorMsg}.', ldap_errno($con)); } $attributes = $this->cleanupAttributes($attributes); - $dn = ldap_get_dn($con, $current); + $dn = @ldap_get_dn($con, $current); if (false === $dn) { - throw new LdapException(sprintf('Could not fetch DN: %s.', ldap_error($con))); + throw LdapException::create('Could not fetch DN: [{errorCode}] {errorMsg}.', ldap_errno($con)); } return new Entry($dn, $attributes); diff --git a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Connection.php b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Connection.php index 3c6262c467ade..94ffd25c43934 100644 --- a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Connection.php +++ b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Connection.php @@ -58,16 +58,16 @@ public function bind($dn = null, $password = null) } if (false === @ldap_bind($this->connection, $dn, $password)) { - $error = ldap_error($this->connection); - switch (ldap_errno($this->connection)) { + $errorCode = ldap_errno($this->connection); + switch ($errorCode) { case self::LDAP_INVALID_CREDENTIALS: - throw new InvalidCredentialsException($error); + throw InvalidCredentialsException::create('{errorMsg}', $errorCode); case self::LDAP_TIMEOUT: - throw new ConnectionTimeoutException($error); + throw ConnectionTimeoutException::create('{errorMsg}', $errorCode); case self::LDAP_ALREADY_EXISTS: - throw new AlreadyExistsException($error); + throw AlreadyExistsException::create('{errorMsg}', $errorCode); } - throw new ConnectionException($error); + throw ConnectionException::create('{errorMsg}', $errorCode); } $this->bound = true; @@ -88,14 +88,14 @@ public function getResource() public function setOption($name, $value) { if (!@ldap_set_option($this->connection, ConnectionOptions::getOption($name), $value)) { - throw new LdapException(sprintf('Could not set value "%s" for option "%s".', $value, $name)); + throw LdapException::create(sprintf('Could not set value "%s" for option "%s": [{errorCode}] {errorMsg}.', $value, $name), ldap_errno($this->connection)); } } public function getOption($name) { if (!@ldap_get_option($this->connection, ConnectionOptions::getOption($name), $ret)) { - throw new LdapException(sprintf('Could not retrieve value for option "%s".', $name)); + throw LdapException::create(sprintf('Could not retrieve value for option "%s": [{errorCode}] {errorMsg}.', $name), ldap_errno($this->connection)); } return $ret; @@ -135,19 +135,17 @@ private function connect() $this->setOption($name, $value); } - if (false === $this->connection) { - throw new LdapException(sprintf('Could not connect to Ldap server: %s.', ldap_error($this->connection))); - } - if ('tls' === $this->config['encryption'] && false === @ldap_start_tls($this->connection)) { - throw new LdapException(sprintf('Could not initiate TLS connection: %s.', ldap_error($this->connection))); + throw LdapException::create('Could not initiate TLS connection: [{errorCode}] {errorMsg}.', ldap_errno($this->connection)); } } private function disconnect() { if ($this->connection && \is_resource($this->connection)) { - ldap_unbind($this->connection); + if (!@ldap_unbind($this->connection)) { + throw LdapException::create('Could not unbind from LDAP: [{errorCode}] {errorMsg}', ldap_errno($this->connection)); + } } $this->connection = null; diff --git a/src/Symfony/Component/Ldap/Adapter/ExtLdap/ConnectionOptions.php b/src/Symfony/Component/Ldap/Adapter/ExtLdap/ConnectionOptions.php index 225f107ffdc2d..44defd467eff2 100644 --- a/src/Symfony/Component/Ldap/Adapter/ExtLdap/ConnectionOptions.php +++ b/src/Symfony/Component/Ldap/Adapter/ExtLdap/ConnectionOptions.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Ldap\Adapter\ExtLdap; -use Symfony\Component\Ldap\Exception\LdapException; +use Symfony\Component\Ldap\Exception\UnexpectedValueException; /** * A class representing the Ldap extension's options, which can be used with @@ -71,7 +71,7 @@ public static function getOptionName(string $name): string * Fetches an option's corresponding constant value from an option name. * The option name can either be in snake or camel case. * - * @throws LdapException + * @throws UnexpectedValueException */ public static function getOption(string $name): int { @@ -79,7 +79,7 @@ public static function getOption(string $name): int $constantName = self::getOptionName($name); if (!\defined($constantName)) { - throw new LdapException(sprintf('Unknown option "%s".', $name)); + throw new UnexpectedValueException(sprintf('Unknown option "%s".', $name)); } return \constant($constantName); diff --git a/src/Symfony/Component/Ldap/Adapter/ExtLdap/EntryManager.php b/src/Symfony/Component/Ldap/Adapter/ExtLdap/EntryManager.php index 20055c2f3b24f..a3c30b3df61dc 100644 --- a/src/Symfony/Component/Ldap/Adapter/ExtLdap/EntryManager.php +++ b/src/Symfony/Component/Ldap/Adapter/ExtLdap/EntryManager.php @@ -14,6 +14,7 @@ use Symfony\Component\Ldap\Adapter\EntryManagerInterface; use Symfony\Component\Ldap\Entry; use Symfony\Component\Ldap\Exception\LdapException; +use Symfony\Component\Ldap\Exception\MalformedDistinguishedNameException; use Symfony\Component\Ldap\Exception\NotBoundException; use Symfony\Component\Ldap\Exception\UpdateOperationException; @@ -38,7 +39,7 @@ public function add(Entry $entry) $con = $this->getConnectionResource(); if (!@ldap_add($con, $entry->getDn(), $entry->getAttributes())) { - throw new LdapException(sprintf('Could not add entry "%s": %s.', $entry->getDn(), ldap_error($con))); + throw LdapException::create(sprintf('Could not add entry "%s": [{errorCode}] {errorMsg}.', $entry->getDn()), ldap_errno($con)); } return $this; @@ -52,7 +53,7 @@ public function update(Entry $entry) $con = $this->getConnectionResource(); if (!@ldap_modify($con, $entry->getDn(), $entry->getAttributes())) { - throw new LdapException(sprintf('Could not update entry "%s": %s.', $entry->getDn(), ldap_error($con))); + throw LdapException::create(sprintf('Could not update entry "%s": [{errorCode}] {errorMsg}.', $entry->getDn()), ldap_errno($con)); } } @@ -64,7 +65,7 @@ public function remove(Entry $entry) $con = $this->getConnectionResource(); if (!@ldap_delete($con, $entry->getDn())) { - throw new LdapException(sprintf('Could not remove entry "%s": %s.', $entry->getDn(), ldap_error($con))); + throw LdapException::create(sprintf('Could not remove entry "%s": [{errorCode}] {errorMsg}.', $entry->getDn()), ldap_errno($con)); } } @@ -79,7 +80,7 @@ public function addAttributeValues(Entry $entry, string $attribute, array $value $con = $this->getConnectionResource(); if (!@ldap_mod_add($con, $entry->getDn(), [$attribute => $values])) { - throw new LdapException(sprintf('Could not add values to entry "%s", attribute %s: %s.', $entry->getDn(), $attribute, ldap_error($con))); + throw LdapException::create(sprintf('Could not add values to entry "%s", attribute "%s": [{errorCode}] {errorMsg}.', $entry->getDn(), $attribute), ldap_errno($con)); } } @@ -94,7 +95,7 @@ public function removeAttributeValues(Entry $entry, string $attribute, array $va $con = $this->getConnectionResource(); if (!@ldap_mod_del($con, $entry->getDn(), [$attribute => $values])) { - throw new LdapException(sprintf('Could not remove values from entry "%s", attribute %s: %s.', $entry->getDn(), $attribute, ldap_error($con))); + throw LdapException::create(sprintf('Could not remove values from entry "%s", attribute "%s": [{errorCode}] {errorMsg}.', $entry->getDn(), $attribute), ldap_errno($con)); } } @@ -106,15 +107,16 @@ public function rename(Entry $entry, $newRdn, $removeOldRdn = true) $con = $this->getConnectionResource(); if (!@ldap_rename($con, $entry->getDn(), $newRdn, null, $removeOldRdn)) { - throw new LdapException(sprintf('Could not rename entry "%s" to "%s": %s.', $entry->getDn(), $newRdn, ldap_error($con))); + throw LdapException::create(sprintf('Could not rename entry "%s" to "%s": [{errorCode}] {errorMsg}.', $entry->getDn(), $newRdn), ldap_errno($con)); } } /** * Moves an entry on the Ldap server. * - * @throws NotBoundException if the connection has not been previously bound - * @throws LdapException if an error is thrown during the rename operation + * @throws NotBoundException if the connection has not been previously bound + * @throws LdapException if an error is thrown during the rename operation + * @throws MalformedDistinguishedNameException if entry contains a malformed DN. */ public function move(Entry $entry, string $newParent) { @@ -122,7 +124,7 @@ public function move(Entry $entry, string $newParent) $rdn = $this->parseRdnFromEntry($entry); // deleteOldRdn does not matter here, since the Rdn will not be changing in the move. if (!@ldap_rename($con, $entry->getDn(), $rdn, $newParent, true)) { - throw new LdapException(sprintf('Could not move entry "%s" to "%s": %s.', $entry->getDn(), $newParent, ldap_error($con))); + throw LdapException::create(sprintf('Could not move entry "%s" to "%s": [{errorCode}] {errorMsg}.', $entry->getDn(), $newParent), ldap_errno($con)); } } @@ -152,14 +154,14 @@ public function applyOperations(string $dn, iterable $operations): void } if (!@ldap_modify_batch($this->getConnectionResource(), $dn, $operationsMapped)) { - throw new UpdateOperationException(sprintf('Error executing UpdateOperation on "%s": "%s".', $dn, ldap_error($this->getConnectionResource()))); + throw UpdateOperationException::create(sprintf('Error executing UpdateOperation on "%s": [{errorCode}] {errorMsg}.', $dn), ldap_errno($this->getConnectionResource())); } } private function parseRdnFromEntry(Entry $entry): string { if (!preg_match('/^([^,]+),/', $entry->getDn(), $matches)) { - throw new LdapException(sprintf('Entry "%s" malformed, could not parse RDN.', $entry->getDn())); + throw new MalformedDistinguishedNameException(sprintf('Entry "%s" malformed, could not parse RDN.', $entry->getDn())); } return $matches[1]; diff --git a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Query.php b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Query.php index b84991e31b82d..97007365b5594 100644 --- a/src/Symfony/Component/Ldap/Adapter/ExtLdap/Query.php +++ b/src/Symfony/Component/Ldap/Adapter/ExtLdap/Query.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Ldap\Adapter\ExtLdap; use Symfony\Component\Ldap\Adapter\AbstractQuery; +use Symfony\Component\Ldap\Exception\DomainException; use Symfony\Component\Ldap\Exception\LdapException; use Symfony\Component\Ldap\Exception\NotBoundException; @@ -48,8 +49,8 @@ public function __destruct() if (false === $result || null === $result) { continue; } - if (!ldap_free_result($result)) { - throw new LdapException(sprintf('Could not free results: %s.', ldap_error($con))); + if (!@ldap_free_result($result)) { + throw LdapException::create('Could not free results: [{errorCode}] {errorMsg}.', ldap_errno($con)); } } $this->results = null; @@ -80,7 +81,7 @@ public function execute() $func = 'ldap_search'; break; default: - throw new LdapException(sprintf('Could not search in scope "%s".', $this->options['scope'])); + throw new DomainException(sprintf('Could not search in scope "%s".', $this->options['scope'])); } $itemsLeft = $maxItems = $this->options['maxItems']; @@ -97,7 +98,9 @@ public function execute() $cookie = ''; do { if ($pageControl) { - ldap_control_paged_result($con, $pageSize, true, $cookie); + if (!@ldap_control_paged_result($con, $pageSize, true, $cookie)) { + throw LdapException::create('Could not send the LDAP pagination control: [{errorCode}] {errorMsg}.', ldap_errno($con)); + } } $sizeLimit = $itemsLeft; if ($pageSize > 0 && $sizeLimit >= $pageSize) { @@ -115,15 +118,12 @@ public function execute() ); if (false === $search) { - $ldapError = ''; - if ($errno = ldap_errno($con)) { - $ldapError = sprintf(' LDAP error was [%d] %s', $errno, ldap_error($con)); - } + $errorCode = ldap_errno($con); if ($pageControl) { $this->resetPagination(); } - throw new LdapException(sprintf('Could not complete search with dn "%s", query "%s" and filters "%s".%s', $this->dn, $this->query, implode(',', $this->options['filter']), $ldapError)); + throw LdapException::create(sprintf('Could not complete search with dn "%s", query "%s" and filters "%s": [{errorCode}] {errorMsg}.', $this->dn, $this->query, implode(',', $this->options['filter'])), $errorCode); } $this->results[] = $search; @@ -133,7 +133,9 @@ public function execute() break; } if ($pageControl) { - ldap_control_paged_result_response($con, $search, $cookie); + if (!@ldap_control_paged_result_response($con, $search, $cookie)) { + throw LdapException::create('Could not retrieve the LDAP pagination cookie: [{errorCode}] {errorMsg}.', ldap_errno($con)); + } } } while (null !== $cookie && '' !== $cookie); diff --git a/src/Symfony/Component/Ldap/Adapter/ExtLdap/UpdateOperation.php b/src/Symfony/Component/Ldap/Adapter/ExtLdap/UpdateOperation.php index c09b1b8e108a7..6b51abbb0476d 100644 --- a/src/Symfony/Component/Ldap/Adapter/ExtLdap/UpdateOperation.php +++ b/src/Symfony/Component/Ldap/Adapter/ExtLdap/UpdateOperation.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Ldap\Adapter\ExtLdap; -use Symfony\Component\Ldap\Exception\UpdateOperationException; +use Symfony\Component\Ldap\Exception\UnexpectedValueException; class UpdateOperation { @@ -30,15 +30,15 @@ class UpdateOperation * @param int $operationType An LDAP_MODIFY_BATCH_* constant * @param string $attribute The attribute to batch modify on * - * @throws UpdateOperationException on consistency errors during construction + * @throws UnexpectedValueException on consistency errors during construction */ public function __construct(int $operationType, string $attribute, ?array $values) { if (!\in_array($operationType, $this->validOperationTypes, true)) { - throw new UpdateOperationException(sprintf('"%s" is not a valid modification type.', $operationType)); + throw new UnexpectedValueException(sprintf('"%s" is not a valid modification type.', $operationType)); } if (LDAP_MODIFY_BATCH_REMOVE_ALL === $operationType && null !== $values) { - throw new UpdateOperationException(sprintf('$values must be null for LDAP_MODIFY_BATCH_REMOVE_ALL operation, "%s" given.', \gettype($values))); + throw new UnexpectedValueException(sprintf('$values must be null for LDAP_MODIFY_BATCH_REMOVE_ALL operation, "%s" given.', \gettype($values))); } $this->operationType = $operationType; diff --git a/src/Symfony/Component/Ldap/Exception/ConnectionException.php b/src/Symfony/Component/Ldap/Exception/ConnectionException.php index 7fa8e89f6f4a2..5d30e4253154d 100644 --- a/src/Symfony/Component/Ldap/Exception/ConnectionException.php +++ b/src/Symfony/Component/Ldap/Exception/ConnectionException.php @@ -16,6 +16,6 @@ * * @author Grégoire Pineau */ -class ConnectionException extends \RuntimeException implements ExceptionInterface +class ConnectionException extends LdapException implements ExceptionInterface { } diff --git a/src/Symfony/Component/Ldap/Exception/DomainException.php b/src/Symfony/Component/Ldap/Exception/DomainException.php new file mode 100644 index 0000000000000..5b40da414fb08 --- /dev/null +++ b/src/Symfony/Component/Ldap/Exception/DomainException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Ldap\Exception; + +class DomainException extends \DomainException implements ExceptionInterface +{ +} diff --git a/src/Symfony/Component/Ldap/Exception/DriverNotFoundException.php b/src/Symfony/Component/Ldap/Exception/DriverNotFoundException.php index 382cdf5ca6686..9f29b1b625cc2 100644 --- a/src/Symfony/Component/Ldap/Exception/DriverNotFoundException.php +++ b/src/Symfony/Component/Ldap/Exception/DriverNotFoundException.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Ldap\Exception; /** - * LdapException is thrown if php ldap module is not loaded. + * DriverNotFoundException is thrown if adapter is not found in adapter map. * * @author Charles Sarrazin */ diff --git a/src/Symfony/Component/Ldap/Exception/ExtensionNotLoadedException.php b/src/Symfony/Component/Ldap/Exception/ExtensionNotLoadedException.php new file mode 100644 index 0000000000000..b69053ef2c051 --- /dev/null +++ b/src/Symfony/Component/Ldap/Exception/ExtensionNotLoadedException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Ldap\Exception; + +/** + * ExtensionNotLoadedException is thrown is a required PHP extension is not loaded. + * + * @author Dominic Tubach + */ +class ExtensionNotLoadedException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/src/Symfony/Component/Ldap/Exception/LdapException.php b/src/Symfony/Component/Ldap/Exception/LdapException.php index df8eabfbcba88..f810648df40f2 100644 --- a/src/Symfony/Component/Ldap/Exception/LdapException.php +++ b/src/Symfony/Component/Ldap/Exception/LdapException.php @@ -12,10 +12,41 @@ namespace Symfony\Component\Ldap\Exception; /** - * LdapException is thrown if php ldap module is not loaded. + * LdapException is thrown if an LDAP operation fails. * * @author Grégoire Pineau + * @author Dominic Tubach */ class LdapException extends \RuntimeException implements ExceptionInterface { + /** + * This constructor ensures that an error code is specified. + * + * @param string $message + * @param int $code The LDAP error code. + * @param \Throwable $previous + */ + public function __construct(string $message, int $code, \Throwable $previous = null) + { + parent::__construct($message, $code, $previous); + } + + /** + * @param string $messageFormat The exception message. Possible placeholders + * are {errorCode} and {errorMsg} that will be + * replaced by the LDAP error code and the LDAP + * error message, respectively. + * @param int $errorCode The LDAP error code. + * + * @return static + */ + public static function create(string $messageFormat, int $errorCode) + { + $message = strtr($messageFormat, [ + '{errorCode}' => $errorCode, + '{errorMsg}' => ldap_err2str($errorCode), + ]); + + return new static($message, $errorCode); + } } diff --git a/src/Symfony/Component/Ldap/Exception/MalformedDistinguishedNameException.php b/src/Symfony/Component/Ldap/Exception/MalformedDistinguishedNameException.php new file mode 100644 index 0000000000000..fa510b5bf6bf4 --- /dev/null +++ b/src/Symfony/Component/Ldap/Exception/MalformedDistinguishedNameException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Ldap\Exception; + +/** + * MalformedDistinguishedNameException is thrown in case of an malformed DN. + * + * @author Dominic Tubach + */ +class MalformedDistinguishedNameException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/src/Symfony/Component/Ldap/Exception/UnexpectedValueException.php b/src/Symfony/Component/Ldap/Exception/UnexpectedValueException.php new file mode 100644 index 0000000000000..0b05253dc8ced --- /dev/null +++ b/src/Symfony/Component/Ldap/Exception/UnexpectedValueException.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Ldap\Exception; + +class UnexpectedValueException extends \UnexpectedValueException implements ExceptionInterface +{ +} diff --git a/src/Symfony/Component/Ldap/Tests/Adapter/ExtLdap/EntryManagerTest.php b/src/Symfony/Component/Ldap/Tests/Adapter/ExtLdap/EntryManagerTest.php index a4aed634f5ea0..7e1d7d97bc3e6 100644 --- a/src/Symfony/Component/Ldap/Tests/Adapter/ExtLdap/EntryManagerTest.php +++ b/src/Symfony/Component/Ldap/Tests/Adapter/ExtLdap/EntryManagerTest.php @@ -14,12 +14,13 @@ use Symfony\Component\Ldap\Adapter\ExtLdap\Connection; use Symfony\Component\Ldap\Adapter\ExtLdap\EntryManager; use Symfony\Component\Ldap\Entry; +use Symfony\Component\Ldap\Exception\MalformedDistinguishedNameException; class EntryManagerTest extends TestCase { public function testMove() { - $this->expectException('Symfony\Component\Ldap\Exception\LdapException'); + $this->expectException(MalformedDistinguishedNameException::class); $this->expectExceptionMessage('Entry "$$$$$$" malformed, could not parse RDN.'); $connection = $this->createMock(Connection::class); $connection diff --git a/src/Symfony/Component/Ldap/Tests/Security/LdapUserProviderTest.php b/src/Symfony/Component/Ldap/Tests/Security/LdapUserProviderTest.php index 8d0a7a3517584..919664e20afc9 100644 --- a/src/Symfony/Component/Ldap/Tests/Security/LdapUserProviderTest.php +++ b/src/Symfony/Component/Ldap/Tests/Security/LdapUserProviderTest.php @@ -33,7 +33,7 @@ public function testLoadUserByUsernameFailsIfCantConnectToLdap() $ldap ->expects($this->once()) ->method('bind') - ->willThrowException(new ConnectionException()) + ->willThrowException(new ConnectionException('Test', 80)) ; $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com'); From 861503bb46ffe02ca6415a46344f51c7439f8357 Mon Sep 17 00:00:00 2001 From: Dominic Tubach Date: Fri, 30 Aug 2019 12:01:26 +0200 Subject: [PATCH 2/3] Ensure BC Style fixes --- .../Ldap/Adapter/ExtLdap/EntryManager.php | 2 +- .../Exception/ExtensionNotLoadedException.php | 12 ++++++++++-- .../Component/Ldap/Exception/LdapException.php | 18 ++++++++++++------ .../MalformedDistinguishedNameException.php | 10 +++++++++- .../Exception/UnexpectedValueException.php | 13 ++++++++++++- .../LdapBindAuthenticationProviderTest.php | 2 +- .../Core/Tests/User/LdapUserProviderTest.php | 2 +- 7 files changed, 46 insertions(+), 13 deletions(-) diff --git a/src/Symfony/Component/Ldap/Adapter/ExtLdap/EntryManager.php b/src/Symfony/Component/Ldap/Adapter/ExtLdap/EntryManager.php index a3c30b3df61dc..4c489a0d1fe23 100644 --- a/src/Symfony/Component/Ldap/Adapter/ExtLdap/EntryManager.php +++ b/src/Symfony/Component/Ldap/Adapter/ExtLdap/EntryManager.php @@ -116,7 +116,7 @@ public function rename(Entry $entry, $newRdn, $removeOldRdn = true) * * @throws NotBoundException if the connection has not been previously bound * @throws LdapException if an error is thrown during the rename operation - * @throws MalformedDistinguishedNameException if entry contains a malformed DN. + * @throws MalformedDistinguishedNameException if entry contains a malformed DN */ public function move(Entry $entry, string $newParent) { diff --git a/src/Symfony/Component/Ldap/Exception/ExtensionNotLoadedException.php b/src/Symfony/Component/Ldap/Exception/ExtensionNotLoadedException.php index b69053ef2c051..4fab2f75d1dbe 100644 --- a/src/Symfony/Component/Ldap/Exception/ExtensionNotLoadedException.php +++ b/src/Symfony/Component/Ldap/Exception/ExtensionNotLoadedException.php @@ -12,10 +12,18 @@ namespace Symfony\Component\Ldap\Exception; /** - * ExtensionNotLoadedException is thrown is a required PHP extension is not loaded. + * ExtensionNotLoadedException is thrown if a required PHP extension is not loaded. + * + * This class should extend \RuntimeException, but extends LdapException for BC + * compatibility. * * @author Dominic Tubach */ -class ExtensionNotLoadedException extends \RuntimeException implements ExceptionInterface +class ExtensionNotLoadedException extends LdapException implements ExceptionInterface { + public function __construct(string $message = '', int $code = 0, \Throwable $previous = null) + { + // avoid deprecation error + parent::__construct($message, $code, $previous); + } } diff --git a/src/Symfony/Component/Ldap/Exception/LdapException.php b/src/Symfony/Component/Ldap/Exception/LdapException.php index f810648df40f2..bd66e28913851 100644 --- a/src/Symfony/Component/Ldap/Exception/LdapException.php +++ b/src/Symfony/Component/Ldap/Exception/LdapException.php @@ -12,7 +12,10 @@ namespace Symfony\Component\Ldap\Exception; /** - * LdapException is thrown if an LDAP operation fails. + * LdapException is thrown if an LDAP operation fails. For BC compatibility + * the classes ExtensionNotLoadedException, MalformedDistinguishedNameException, + * and UnexpectedValueException extend this class, though they are not thrown + * because of a failed LDAP operation. * * @author Grégoire Pineau * @author Dominic Tubach @@ -22,12 +25,15 @@ class LdapException extends \RuntimeException implements ExceptionInterface /** * This constructor ensures that an error code is specified. * - * @param string $message - * @param int $code The LDAP error code. + * @param int $code the LDAP error code * @param \Throwable $previous */ - public function __construct(string $message, int $code, \Throwable $previous = null) + public function __construct(string $message = '', int $code = 0, \Throwable $previous = null) { + if (2 > func_num_args()) { + @trigger_error(sprintf('Not specifying the LDAP error code in "%s::__construct()" is deprecated since Symfony 4.4.', __CLASS__), E_USER_DEPRECATED); + } + parent::__construct($message, $code, $previous); } @@ -36,7 +42,7 @@ public function __construct(string $message, int $code, \Throwable $previous = n * are {errorCode} and {errorMsg} that will be * replaced by the LDAP error code and the LDAP * error message, respectively. - * @param int $errorCode The LDAP error code. + * @param int $errorCode the LDAP error code * * @return static */ @@ -44,7 +50,7 @@ public static function create(string $messageFormat, int $errorCode) { $message = strtr($messageFormat, [ '{errorCode}' => $errorCode, - '{errorMsg}' => ldap_err2str($errorCode), + '{errorMsg}' => ldap_err2str($errorCode), ]); return new static($message, $errorCode); diff --git a/src/Symfony/Component/Ldap/Exception/MalformedDistinguishedNameException.php b/src/Symfony/Component/Ldap/Exception/MalformedDistinguishedNameException.php index fa510b5bf6bf4..109196225be0b 100644 --- a/src/Symfony/Component/Ldap/Exception/MalformedDistinguishedNameException.php +++ b/src/Symfony/Component/Ldap/Exception/MalformedDistinguishedNameException.php @@ -14,8 +14,16 @@ /** * MalformedDistinguishedNameException is thrown in case of an malformed DN. * + * This class should extend \RuntimeException, but extends LdapException for BC + * compatibility. + * * @author Dominic Tubach */ -class MalformedDistinguishedNameException extends \RuntimeException implements ExceptionInterface +class MalformedDistinguishedNameException extends LdapException implements ExceptionInterface { + public function __construct(string $message = '', int $code = 0, \Throwable $previous = null) + { + // avoid deprecation error + parent::__construct($message, $code, $previous); + } } diff --git a/src/Symfony/Component/Ldap/Exception/UnexpectedValueException.php b/src/Symfony/Component/Ldap/Exception/UnexpectedValueException.php index 0b05253dc8ced..aeb556fbcf181 100644 --- a/src/Symfony/Component/Ldap/Exception/UnexpectedValueException.php +++ b/src/Symfony/Component/Ldap/Exception/UnexpectedValueException.php @@ -11,6 +11,17 @@ namespace Symfony\Component\Ldap\Exception; -class UnexpectedValueException extends \UnexpectedValueException implements ExceptionInterface +/** + * This class should extend \UnexpectedValueException, but extends LdapException + * for BC compatibility. + * + * @author Dominic Tubach + */ +class UnexpectedValueException extends LdapException implements ExceptionInterface { + public function __construct(string $message = '', int $code = 0, \Throwable $previous = null) + { + // avoid deprecation error + parent::__construct($message, $code, $previous); + } } diff --git a/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/LdapBindAuthenticationProviderTest.php b/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/LdapBindAuthenticationProviderTest.php index 893c8909719fa..2a8890654c661 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/LdapBindAuthenticationProviderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/LdapBindAuthenticationProviderTest.php @@ -67,7 +67,7 @@ public function testBindFailureShouldThrowAnException() $ldap ->expects($this->once()) ->method('bind') - ->willThrowException(new ConnectionException()) + ->willThrowException(new ConnectionException('Test', 80)) ; $userChecker = $this->getMockBuilder(UserCheckerInterface::class)->getMock(); diff --git a/src/Symfony/Component/Security/Core/Tests/User/LdapUserProviderTest.php b/src/Symfony/Component/Security/Core/Tests/User/LdapUserProviderTest.php index 90f74584b67f7..5694a6f6251f5 100644 --- a/src/Symfony/Component/Security/Core/Tests/User/LdapUserProviderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/User/LdapUserProviderTest.php @@ -32,7 +32,7 @@ public function testLoadUserByUsernameFailsIfCantConnectToLdap() $ldap ->expects($this->once()) ->method('bind') - ->willThrowException(new ConnectionException()) + ->willThrowException(new ConnectionException('Test', 80)) ; $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com'); From 2dede53131422ca5c411badd4a8fdb084d94021c Mon Sep 17 00:00:00 2001 From: Dominic Tubach Date: Fri, 30 Aug 2019 12:16:12 +0200 Subject: [PATCH 3/3] Style fix --- src/Symfony/Component/Ldap/Exception/LdapException.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Ldap/Exception/LdapException.php b/src/Symfony/Component/Ldap/Exception/LdapException.php index bd66e28913851..43ffd792d1cd5 100644 --- a/src/Symfony/Component/Ldap/Exception/LdapException.php +++ b/src/Symfony/Component/Ldap/Exception/LdapException.php @@ -30,7 +30,7 @@ class LdapException extends \RuntimeException implements ExceptionInterface */ public function __construct(string $message = '', int $code = 0, \Throwable $previous = null) { - if (2 > func_num_args()) { + if (2 > \func_num_args()) { @trigger_error(sprintf('Not specifying the LDAP error code in "%s::__construct()" is deprecated since Symfony 4.4.', __CLASS__), E_USER_DEPRECATED); }