From 6dd12215803b06efd34d43a81d8bf0ce9701a998 Mon Sep 17 00:00:00 2001 From: EXT - THERAGE Kevin Date: Wed, 29 Jun 2022 14:51:17 +0200 Subject: [PATCH] [Ldap] Deprecate '{username}' parameter use in favour of '{user_identifier}' in LDAP configuration --- UPGRADE-6.2.md | 9 +++- src/Symfony/Component/Ldap/CHANGELOG.md | 5 +++ .../Security/CheckLdapCredentialsListener.php | 10 ++--- .../Ldap/Security/LdapAuthenticator.php | 2 +- .../Component/Ldap/Security/LdapBadge.php | 10 ++++- .../Component/Ldap/Security/LdapUser.php | 10 ++--- .../Ldap/Security/LdapUserProvider.php | 6 ++- .../CheckLdapCredentialsListenerTest.php | 45 +++++++++++++++++-- .../Tests/Security/LdapUserProviderTest.php | 28 ++++++------ src/Symfony/Component/Ldap/composer.json | 1 + 10 files changed, 93 insertions(+), 33 deletions(-) diff --git a/UPGRADE-6.2.md b/UPGRADE-6.2.md index 60c5b84b43793..015dde43b155f 100644 --- a/UPGRADE-6.2.md +++ b/UPGRADE-6.2.md @@ -14,10 +14,15 @@ HttpFoundation * Deprecate `Request::getContentType()`, use `Request::getContentTypeFormat()` instead +Ldap +---- + + * Deprecate `{username}` parameter use in favour of `{user_identifier}` + Mailer --------- +------ -* Deprecate the `OhMySMTP` transport, use `MailPace` instead + * Deprecate the `OhMySMTP` transport, use `MailPace` instead Security -------- diff --git a/src/Symfony/Component/Ldap/CHANGELOG.md b/src/Symfony/Component/Ldap/CHANGELOG.md index 297ff65e0d445..eb4df15c95e57 100644 --- a/src/Symfony/Component/Ldap/CHANGELOG.md +++ b/src/Symfony/Component/Ldap/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.2 +--- + + * Deprecate `{username}` parameter use in favour of `{user_identifier}` + 6.1 --- diff --git a/src/Symfony/Component/Ldap/Security/CheckLdapCredentialsListener.php b/src/Symfony/Component/Ldap/Security/CheckLdapCredentialsListener.php index 19b7f7af1f2d6..5cfed46007b5e 100644 --- a/src/Symfony/Component/Ldap/Security/CheckLdapCredentialsListener.php +++ b/src/Symfony/Component/Ldap/Security/CheckLdapCredentialsListener.php @@ -83,17 +83,17 @@ public function onCheckPassport(CheckPassportEvent $event) } else { throw new LogicException('Using the "query_string" config without using a "search_dn" and a "search_password" is not supported.'); } - $username = $ldap->escape($user->getUserIdentifier(), '', LdapInterface::ESCAPE_FILTER); - $query = str_replace('{username}', $username, $ldapBadge->getQueryString()); + $identifier = $ldap->escape($user->getUserIdentifier(), '', LdapInterface::ESCAPE_FILTER); + $query = str_replace('{user_identifier}', $identifier, $ldapBadge->getQueryString()); $result = $ldap->query($ldapBadge->getDnString(), $query)->execute(); if (1 !== $result->count()) { - throw new BadCredentialsException('The presented username is invalid.'); + throw new BadCredentialsException('The presented user identifier is invalid.'); } $dn = $result[0]->getDn(); } else { - $username = $ldap->escape($user->getUserIdentifier(), '', LdapInterface::ESCAPE_DN); - $dn = str_replace('{username}', $username, $ldapBadge->getDnString()); + $identifier = $ldap->escape($user->getUserIdentifier(), '', LdapInterface::ESCAPE_DN); + $dn = str_replace('{user_identifier}', $identifier, $ldapBadge->getDnString()); } $ldap->bind($dn, $presentedPassword); diff --git a/src/Symfony/Component/Ldap/Security/LdapAuthenticator.php b/src/Symfony/Component/Ldap/Security/LdapAuthenticator.php index f3de1f9310d28..c2999e9efc6f1 100644 --- a/src/Symfony/Component/Ldap/Security/LdapAuthenticator.php +++ b/src/Symfony/Component/Ldap/Security/LdapAuthenticator.php @@ -42,7 +42,7 @@ class LdapAuthenticator implements AuthenticationEntryPointInterface, Interactiv private string $searchPassword; private string $queryString; - public function __construct(AuthenticatorInterface $authenticator, string $ldapServiceId, string $dnString = '{username}', string $searchDn = '', string $searchPassword = '', string $queryString = '') + public function __construct(AuthenticatorInterface $authenticator, string $ldapServiceId, string $dnString = '{user_identifier}', string $searchDn = '', string $searchPassword = '', string $queryString = '') { $this->authenticator = $authenticator; $this->ldapServiceId = $ldapServiceId; diff --git a/src/Symfony/Component/Ldap/Security/LdapBadge.php b/src/Symfony/Component/Ldap/Security/LdapBadge.php index 4f78a39f9d0ba..2f8b1d7bd307d 100644 --- a/src/Symfony/Component/Ldap/Security/LdapBadge.php +++ b/src/Symfony/Component/Ldap/Security/LdapBadge.php @@ -31,12 +31,20 @@ class LdapBadge implements BadgeInterface private string $searchPassword; private ?string $queryString; - public function __construct(string $ldapServiceId, string $dnString = '{username}', string $searchDn = '', string $searchPassword = '', string $queryString = null) + public function __construct(string $ldapServiceId, string $dnString = '{user_identifier}', string $searchDn = '', string $searchPassword = '', string $queryString = null) { $this->ldapServiceId = $ldapServiceId; + $dnString = str_replace('{username}', '{user_identifier}', $dnString, $replaceCount); + if ($replaceCount > 0) { + trigger_deprecation('symfony/ldap', '6.2', 'Using "{username}" parameter in LDAP configuration is deprecated, consider using "{user_identifier}" instead.'); + } $this->dnString = $dnString; $this->searchDn = $searchDn; $this->searchPassword = $searchPassword; + $queryString = str_replace('{username}', '{user_identifier}', $queryString ?? '', $replaceCount); + if ($replaceCount > 0) { + trigger_deprecation('symfony/ldap', '6.2', 'Using "{username}" parameter in LDAP configuration is deprecated, consider using "{user_identifier}" instead.'); + } $this->queryString = $queryString; } diff --git a/src/Symfony/Component/Ldap/Security/LdapUser.php b/src/Symfony/Component/Ldap/Security/LdapUser.php index 71cc96355d848..6e1b1ceb09869 100644 --- a/src/Symfony/Component/Ldap/Security/LdapUser.php +++ b/src/Symfony/Component/Ldap/Security/LdapUser.php @@ -24,19 +24,19 @@ class LdapUser implements UserInterface, PasswordAuthenticatedUserInterface, EquatableInterface { private Entry $entry; - private string $username; + private string $identifier; private ?string $password; private array $roles; private array $extraFields; - public function __construct(Entry $entry, string $username, #[\SensitiveParameter] ?string $password, array $roles = [], array $extraFields = []) + public function __construct(Entry $entry, string $identifier, #[\SensitiveParameter] ?string $password, array $roles = [], array $extraFields = []) { - if (!$username) { + if (!$identifier) { throw new \InvalidArgumentException('The username cannot be empty.'); } $this->entry = $entry; - $this->username = $username; + $this->identifier = $identifier; $this->password = $password; $this->roles = $roles; $this->extraFields = $extraFields; @@ -81,7 +81,7 @@ public function getUsername(): string public function getUserIdentifier(): string { - return $this->username; + return $this->identifier; } /** diff --git a/src/Symfony/Component/Ldap/Security/LdapUserProvider.php b/src/Symfony/Component/Ldap/Security/LdapUserProvider.php index 3d028f5cb7995..01cfb0907ac28 100644 --- a/src/Symfony/Component/Ldap/Security/LdapUserProvider.php +++ b/src/Symfony/Component/Ldap/Security/LdapUserProvider.php @@ -81,7 +81,11 @@ public function loadUserByIdentifier(string $identifier): UserInterface } $identifier = $this->ldap->escape($identifier, '', LdapInterface::ESCAPE_FILTER); - $query = str_replace(['{username}', '{user_identifier}'], $identifier, $this->defaultSearch); + $query = str_replace('{username}', '{user_identifier}', $this->defaultSearch, $replaceCount); + if ($replaceCount > 0) { + trigger_deprecation('symfony/ldap', '6.2', 'Using "{username}" parameter in LDAP configuration is deprecated, consider using "{user_identifier}" instead.'); + } + $query = str_replace('{user_identifier}', $identifier, $query); $search = $this->ldap->query($this->baseDn, $query, ['filter' => 0 == \count($this->extraFields) ? '*' : $this->extraFields]); $entries = $search->execute(); diff --git a/src/Symfony/Component/Ldap/Tests/Security/CheckLdapCredentialsListenerTest.php b/src/Symfony/Component/Ldap/Tests/Security/CheckLdapCredentialsListenerTest.php index 1ff3503cac2de..cf1e614e94305 100644 --- a/src/Symfony/Component/Ldap/Tests/Security/CheckLdapCredentialsListenerTest.php +++ b/src/Symfony/Component/Ldap/Tests/Security/CheckLdapCredentialsListenerTest.php @@ -127,6 +127,43 @@ public function testBindFailureShouldThrowAnException() $listener->onCheckPassport($this->createEvent()); } + /** + * @group legacy + * + * @dataProvider queryForDnProvider + */ + public function testLegacyQueryForDn(string $dnString, string $queryString) + { + $collection = new class([new Entry('')]) extends \ArrayObject implements CollectionInterface { + public function toArray(): array + { + return $this->getArrayCopy(); + } + }; + + $query = $this->createMock(QueryInterface::class); + $query->expects($this->once())->method('execute')->willReturn($collection); + + $this->ldap + ->method('bind') + ->withConsecutive( + ['elsa', 'test1234A$'] + ); + $this->ldap->expects($this->any())->method('escape')->with('Wouter', '', LdapInterface::ESCAPE_FILTER)->willReturn('wouter'); + $this->ldap->expects($this->once())->method('query')->with('{user_identifier}', 'wouter_test')->willReturn($query); + + $listener = $this->createListener(); + $listener->onCheckPassport($this->createEvent('s3cr3t', new LdapBadge('app.ldap', $dnString, 'elsa', 'test1234A$', $queryString))); + } + + public function queryForDnProvider(): iterable + { + yield ['{username}', '{username}_test']; + yield ['{user_identifier}', '{username}_test']; + yield ['{username}', '{user_identifier}_test']; + yield ['{user_identifier}', '{user_identifier}_test']; + } + public function testQueryForDn() { $collection = new class([new Entry('')]) extends \ArrayObject implements CollectionInterface { @@ -145,16 +182,16 @@ public function toArray(): array ['elsa', 'test1234A$'] ); $this->ldap->expects($this->any())->method('escape')->with('Wouter', '', LdapInterface::ESCAPE_FILTER)->willReturn('wouter'); - $this->ldap->expects($this->once())->method('query')->with('{username}', 'wouter_test')->willReturn($query); + $this->ldap->expects($this->once())->method('query')->with('{user_identifier}', 'wouter_test')->willReturn($query); $listener = $this->createListener(); - $listener->onCheckPassport($this->createEvent('s3cr3t', new LdapBadge('app.ldap', '{username}', 'elsa', 'test1234A$', '{username}_test'))); + $listener->onCheckPassport($this->createEvent('s3cr3t', new LdapBadge('app.ldap', '{user_identifier}', 'elsa', 'test1234A$', '{user_identifier}_test'))); } public function testEmptyQueryResultShouldThrowAnException() { $this->expectException(BadCredentialsException::class); - $this->expectExceptionMessage('The presented username is invalid.'); + $this->expectExceptionMessage('The presented user identifier is invalid.'); $collection = $this->createMock(CollectionInterface::class); @@ -170,7 +207,7 @@ public function testEmptyQueryResultShouldThrowAnException() $this->ldap->expects($this->once())->method('query')->willReturn($query); $listener = $this->createListener(); - $listener->onCheckPassport($this->createEvent('s3cr3t', new LdapBadge('app.ldap', '{username}', 'elsa', 'test1234A$', '{username}_test'))); + $listener->onCheckPassport($this->createEvent('s3cr3t', new LdapBadge('app.ldap', '{user_identifier}', 'elsa', 'test1234A$', '{user_identifier}_test'))); } private function createEvent($password = 's3cr3t', $ldapBadge = null) diff --git a/src/Symfony/Component/Ldap/Tests/Security/LdapUserProviderTest.php b/src/Symfony/Component/Ldap/Tests/Security/LdapUserProviderTest.php index 083d0b7e8d7b1..4db7757ee4683 100644 --- a/src/Symfony/Component/Ldap/Tests/Security/LdapUserProviderTest.php +++ b/src/Symfony/Component/Ldap/Tests/Security/LdapUserProviderTest.php @@ -27,7 +27,7 @@ */ class LdapUserProviderTest extends TestCase { - public function testLoadUserByUsernameFailsIfCantConnectToLdap() + public function testLoadUserByIdentifierFailsIfCantConnectToLdap() { $this->expectException(ConnectionException::class); @@ -42,7 +42,7 @@ public function testLoadUserByUsernameFailsIfCantConnectToLdap() $provider->loadUserByIdentifier('foo'); } - public function testLoadUserByUsernameFailsIfNoLdapEntries() + public function testLoadUserByIdentifierFailsIfNoLdapEntries() { $this->expectException(UserNotFoundException::class); @@ -74,7 +74,7 @@ public function testLoadUserByUsernameFailsIfNoLdapEntries() $provider->loadUserByIdentifier('foo'); } - public function testLoadUserByUsernameFailsIfMoreThanOneLdapEntry() + public function testLoadUserByIdentifierFailsIfMoreThanOneLdapEntry() { $this->expectException(UserNotFoundException::class); @@ -106,7 +106,7 @@ public function testLoadUserByUsernameFailsIfMoreThanOneLdapEntry() $provider->loadUserByIdentifier('foo'); } - public function testLoadUserByUsernameFailsIfMoreThanOneLdapPasswordsInEntry() + public function testLoadUserByIdentifierFailsIfMoreThanOneLdapPasswordsInEntry() { $this->expectException(InvalidArgumentException::class); @@ -143,11 +143,11 @@ public function testLoadUserByUsernameFailsIfMoreThanOneLdapPasswordsInEntry() ->willReturn($query) ; - $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com', null, null, [], 'sAMAccountName', '({uid_key}={username})', 'userpassword'); + $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com', null, null, [], 'sAMAccountName', '({uid_key}={user_identifier})', 'userpassword'); $this->assertInstanceOf(LdapUser::class, $provider->loadUserByIdentifier('foo')); } - public function testLoadUserByUsernameShouldNotFailIfEntryHasNoUidKeyAttribute() + public function testLoadUserByIdentifierShouldNotFailIfEntryHasNoUidKeyAttribute() { $result = $this->createMock(CollectionInterface::class); $query = $this->createMock(QueryInterface::class); @@ -179,11 +179,11 @@ public function testLoadUserByUsernameShouldNotFailIfEntryHasNoUidKeyAttribute() ->willReturn($query) ; - $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com', null, null, [], 'sAMAccountName', '({uid_key}={username})'); + $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com', null, null, [], 'sAMAccountName', '({uid_key}={user_identifier})'); $this->assertInstanceOf(LdapUser::class, $provider->loadUserByIdentifier('foo')); } - public function testLoadUserByUsernameFailsIfEntryHasNoPasswordAttribute() + public function testLoadUserByIdentifierFailsIfEntryHasNoPasswordAttribute() { $this->expectException(InvalidArgumentException::class); @@ -217,11 +217,11 @@ public function testLoadUserByUsernameFailsIfEntryHasNoPasswordAttribute() ->willReturn($query) ; - $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com', null, null, [], 'sAMAccountName', '({uid_key}={username})', 'userpassword'); + $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com', null, null, [], 'sAMAccountName', '({uid_key}={user_identifier})', 'userpassword'); $this->assertInstanceOf(LdapUser::class, $provider->loadUserByIdentifier('foo')); } - public function testLoadUserByUsernameIsSuccessfulWithoutPasswordAttribute() + public function testLoadUserByIdentifierIsSuccessfulWithoutPasswordAttribute() { $result = $this->createMock(CollectionInterface::class); $query = $this->createMock(QueryInterface::class); @@ -257,7 +257,7 @@ public function testLoadUserByUsernameIsSuccessfulWithoutPasswordAttribute() $this->assertInstanceOf(LdapUser::class, $provider->loadUserByIdentifier('foo')); } - public function testLoadUserByUsernameIsSuccessfulWithoutPasswordAttributeAndWrongCase() + public function testLoadUserByIdentifierIsSuccessfulWithoutPasswordAttributeAndWrongCase() { $result = $this->createMock(CollectionInterface::class); $query = $this->createMock(QueryInterface::class); @@ -293,7 +293,7 @@ public function testLoadUserByUsernameIsSuccessfulWithoutPasswordAttributeAndWro $this->assertSame('foo', $provider->loadUserByIdentifier('Foo')->getUserIdentifier()); } - public function testLoadUserByUsernameIsSuccessfulWithPasswordAttribute() + public function testLoadUserByIdentifierIsSuccessfulWithPasswordAttribute() { $result = $this->createMock(CollectionInterface::class); $query = $this->createMock(QueryInterface::class); @@ -329,14 +329,14 @@ public function testLoadUserByUsernameIsSuccessfulWithPasswordAttribute() ->willReturn($query) ; - $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com', null, null, [], 'sAMAccountName', '({uid_key}={username})', 'userpassword', ['email']); + $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com', null, null, [], 'sAMAccountName', '({uid_key}={user_identifier})', 'userpassword', ['email']); $this->assertInstanceOf(LdapUser::class, $provider->loadUserByIdentifier('foo')); } public function testRefreshUserShouldReturnUserWithSameProperties() { $ldap = $this->createMock(LdapInterface::class); - $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com', null, null, [], 'sAMAccountName', '({uid_key}={username})', 'userpassword', ['email']); + $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com', null, null, [], 'sAMAccountName', '({uid_key}={user_identifier})', 'userpassword', ['email']); $user = new LdapUser(new Entry('foo'), 'foo', 'bar', ['ROLE_DUMMY'], ['email' => 'foo@symfony.com']); diff --git a/src/Symfony/Component/Ldap/composer.json b/src/Symfony/Component/Ldap/composer.json index 0d8c9db8f365b..a2ab982ea63f8 100644 --- a/src/Symfony/Component/Ldap/composer.json +++ b/src/Symfony/Component/Ldap/composer.json @@ -18,6 +18,7 @@ "require": { "php": ">=8.1", "ext-ldap": "*", + "symfony/deprecation-contracts": "^2.1|^3", "symfony/options-resolver": "^5.4|^6.0" }, "require-dev": {