diff --git a/UPGRADE-5.3.md b/UPGRADE-5.3.md index 32615caf15b6f..a69a34ce62ff8 100644 --- a/UPGRADE-5.3.md +++ b/UPGRADE-5.3.md @@ -9,6 +9,7 @@ Asset DoctrineBridge -------------- + * Deprecate `UserLoaderInterface::loadUserByUsername()` in favor of `UserLoaderInterface::loadUserByIdentifier() * Remove `UuidV*Generator` classes DomCrawler @@ -178,6 +179,11 @@ Security } ``` + * Deprecate `UserInterface::getUsername()` in favor of `UserInterface::getUserIdentifier()` + * Deprecate `TokenInterface::getUsername()` in favor of `TokenInterface::getUserIdentifier()` + * Deprecate `UserProviderInterface::loadUserByUsername()` in favor of `UserProviderInterface::loadUserByIdentifier()` + * Deprecate `UsernameNotFoundException` in favor of `UserNotFoundException` and `getUsername()`/`setUsername()` in favor of `getUserIdentifier()`/`setUserIdentifier()` + * Deprecate `PersistentTokenInterface::getUsername()` in favor of `PersistentTokenInterface::getUserIdentifier()` * Deprecate calling `PasswordUpgraderInterface::upgradePassword()` with a `UserInterface` instance that does not implement `PasswordAuthenticatedUserInterface` * Deprecate calling methods `hashPassword()`, `isPasswordValid()` and `needsRehash()` on `UserPasswordHasherInterface` with a `UserInterface` instance that does not implement `PasswordAuthenticatedUserInterface` * Deprecate all classes in the `Core\Encoder\` sub-namespace, use the `PasswordHasher` component instead diff --git a/UPGRADE-6.0.md b/UPGRADE-6.0.md index 997029ae61a6a..728475eed3710 100644 --- a/UPGRADE-6.0.md +++ b/UPGRADE-6.0.md @@ -6,6 +6,11 @@ Asset * Removed `RemoteJsonManifestVersionStrategy`, use `JsonManifestVersionStrategy` instead. +DoctrineBridge +-------------- + + * Remove `UserLoaderInterface::loadUserByUsername()` in favor of `UserLoaderInterface::loadUserByIdentifier() + Config ------ @@ -262,6 +267,11 @@ Security } ``` + * Remove `UserInterface::getUsername()` in favor of `UserInterface::getUserIdentifier()` + * Remove `TokenInterface::getUsername()` in favor of `TokenInterface::getUserIdentifier()` + * Remove `UserProviderInterface::loadUserByUsername()` in favor of `UserProviderInterface::loadUserByIdentifier()` + * Remove `UsernameNotFoundException` in favor of `UserNotFoundException` and `getUsername()`/`setUsername()` in favor of `getUserIdentifier()`/`setUserIdentifier()` + * Remove `PersistentTokenInterface::getUsername()` in favor of `PersistentTokenInterface::getUserIdentifier()` * Calling `PasswordUpgraderInterface::upgradePassword()` with a `UserInterface` instance that does not implement `PasswordAuthenticatedUserInterface` now throws a `\TypeError`. * Calling methods `hashPassword()`, `isPasswordValid()` and `needsRehash()` on `UserPasswordHasherInterface` diff --git a/src/Symfony/Bridge/Doctrine/CHANGELOG.md b/src/Symfony/Bridge/Doctrine/CHANGELOG.md index 7afda2ec2d167..7b627edbfa33b 100644 --- a/src/Symfony/Bridge/Doctrine/CHANGELOG.md +++ b/src/Symfony/Bridge/Doctrine/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 5.3 --- + * Deprecate `UserLoaderInterface::loadUserByUsername()` in favor of `UserLoaderInterface::loadUserByIdentifier() * Deprecate `DoctrineTestHelper` and `TestRepositoryFactory` * [BC BREAK] Remove `UuidV*Generator` classes * Add `UuidGenerator` diff --git a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php index 4116a6c9c6cb8..d2ac616db7439 100644 --- a/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php +++ b/src/Symfony/Bridge/Doctrine/Security/RememberMe/DoctrineTokenProvider.php @@ -119,7 +119,8 @@ public function createNewToken(PersistentTokenInterface $token) .' VALUES (:class, :username, :series, :value, :lastUsed)'; $paramValues = [ 'class' => $token->getClass(), - 'username' => $token->getUsername(), + // @deprecated since 5.3, change to $token->getUserIdentifier() in 6.0 + 'username' => method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername(), 'series' => $token->getSeries(), 'value' => $token->getTokenValue(), 'lastUsed' => $token->getLastUsed(), diff --git a/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php b/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php index e0174e6bbb1b2..f965b8173f1ac 100644 --- a/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php +++ b/src/Symfony/Bridge/Doctrine/Security/User/EntityUserProvider.php @@ -16,7 +16,7 @@ use Doctrine\Persistence\ObjectManager; use Doctrine\Persistence\ObjectRepository; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use Symfony\Component\Security\Core\User\UserInterface; @@ -50,21 +50,35 @@ public function __construct(ManagerRegistry $registry, string $classOrAlias, str * {@inheritdoc} */ public function loadUserByUsername(string $username) + { + trigger_deprecation('symfony/doctrine-bridge', '5.3', 'Method "%s()" is deprecated, use loadUserByIdentifier() instead.', __METHOD__); + + return $this->loadUserByIdentifier($username); + } + + public function loadUserByIdentifier(string $identifier): UserInterface { $repository = $this->getRepository(); if (null !== $this->property) { - $user = $repository->findOneBy([$this->property => $username]); + $user = $repository->findOneBy([$this->property => $identifier]); } else { if (!$repository instanceof UserLoaderInterface) { throw new \InvalidArgumentException(sprintf('You must either make the "%s" entity Doctrine Repository ("%s") implement "Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface" or set the "property" option in the corresponding entity provider configuration.', $this->classOrAlias, get_debug_type($repository))); } - $user = $repository->loadUserByUsername($username); + // @deprecated since 5.3, change to $repository->loadUserByIdentifier() in 6.0 + if (method_exists($repository, 'loadUserByIdentifier')) { + $user = $repository->loadUserByIdentifier($identifier); + } else { + trigger_deprecation('symfony/doctrine-bridge', '5.3', 'Not implementing method "loadUserByIdentifier()" in user loader "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', get_debug_type($repository)); + + $user = $repository->loadUserByUsername($identifier); + } } if (null === $user) { - $e = new UsernameNotFoundException(sprintf('User "%s" not found.', $username)); - $e->setUsername($username); + $e = new UserNotFoundException(sprintf('User "%s" not found.', $identifier)); + $e->setUserIdentifier($identifier); throw $e; } @@ -96,8 +110,8 @@ public function refreshUser(UserInterface $user) $refreshedUser = $repository->find($id); if (null === $refreshedUser) { - $e = new UsernameNotFoundException('User with id '.json_encode($id).' not found.'); - $e->setUsername(json_encode($id)); + $e = new UserNotFoundException('User with id '.json_encode($id).' not found.'); + $e->setUserIdentifier(json_encode($id)); throw $e; } diff --git a/src/Symfony/Bridge/Doctrine/Security/User/UserLoaderInterface.php b/src/Symfony/Bridge/Doctrine/Security/User/UserLoaderInterface.php index d996f71702291..b190eb249967a 100644 --- a/src/Symfony/Bridge/Doctrine/Security/User/UserLoaderInterface.php +++ b/src/Symfony/Bridge/Doctrine/Security/User/UserLoaderInterface.php @@ -22,16 +22,11 @@ * * @see UserInterface * + * @method UserInterface|null loadUserByIdentifier(string $identifier) loads the user for the given user identifier (e.g. username or email). + * This method must return null if the user is not found. + * * @author Michal Trojanowski */ interface UserLoaderInterface { - /** - * Loads the user for the given username. - * - * This method must return null if the user is not found. - * - * @return UserInterface|null - */ - public function loadUserByUsername(string $username); } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/BaseUser.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/BaseUser.php index 416d5b20bf7b1..f03157637b256 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/BaseUser.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/BaseUser.php @@ -37,4 +37,9 @@ public function getUsername(): string { return $this->username; } + + public function getUserIdentifier(): string + { + return $this->username; + } } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/User.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/User.php index f4ff7c2840ce7..0ffaf19883361 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/User.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/User.php @@ -53,6 +53,11 @@ public function getUsername(): string return $this->name; } + public function getUserIdentifier(): string + { + return $this->name; + } + public function eraseCredentials() { } diff --git a/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php b/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php index 216eee0e3f89c..20d1e487a23d2 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Security/User/EntityUserProviderTest.php @@ -21,7 +21,7 @@ use Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface; use Symfony\Bridge\Doctrine\Tests\DoctrineTestHelper; use Symfony\Bridge\Doctrine\Tests\Fixtures\User; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use Symfony\Component\Security\Core\User\UserInterface; @@ -60,7 +60,7 @@ public function testLoadUserByUsername() $provider = new EntityUserProvider($this->getManager($em), 'Symfony\Bridge\Doctrine\Tests\Fixtures\User', 'name'); - $this->assertSame($user, $provider->loadUserByUsername('user1')); + $this->assertSame($user, $provider->loadUserByIdentifier('user1')); } public function testLoadUserByUsernameWithUserLoaderRepositoryAndWithoutProperty() @@ -70,7 +70,7 @@ public function testLoadUserByUsernameWithUserLoaderRepositoryAndWithoutProperty $repository = $this->createMock(UserLoaderRepository::class); $repository ->expects($this->once()) - ->method('loadUserByUsername') + ->method('loadUserByIdentifier') ->with('user1') ->willReturn($user); @@ -82,7 +82,7 @@ public function testLoadUserByUsernameWithUserLoaderRepositoryAndWithoutProperty ->willReturn($repository); $provider = new EntityUserProvider($this->getManager($em), 'Symfony\Bridge\Doctrine\Tests\Fixtures\User'); - $this->assertSame($user, $provider->loadUserByUsername('user1')); + $this->assertSame($user, $provider->loadUserByIdentifier('user1')); } public function testLoadUserByUsernameWithNonUserLoaderRepositoryAndWithoutProperty() @@ -98,7 +98,7 @@ public function testLoadUserByUsernameWithNonUserLoaderRepositoryAndWithoutPrope $em->flush(); $provider = new EntityUserProvider($this->getManager($em), 'Symfony\Bridge\Doctrine\Tests\Fixtures\User'); - $provider->loadUserByUsername('user1'); + $provider->loadUserByIdentifier('user1'); } public function testRefreshUserRequiresId() @@ -126,7 +126,7 @@ public function testRefreshInvalidUser() $provider = new EntityUserProvider($this->getManager($em), 'Symfony\Bridge\Doctrine\Tests\Fixtures\User', 'name'); $user2 = new User(1, 2, 'user2'); - $this->expectException(UsernameNotFoundException::class); + $this->expectException(UserNotFoundException::class); $this->expectExceptionMessage('User with id {"id1":1,"id2":2} not found'); $provider->refreshUser($user2); @@ -153,7 +153,7 @@ public function testLoadUserByUserNameShouldLoadUserWhenProperInterfaceProvided( { $repository = $this->createMock(UserLoaderRepository::class); $repository->expects($this->once()) - ->method('loadUserByUsername') + ->method('loadUserByIdentifier') ->with('name') ->willReturn( $this->createMock(UserInterface::class) @@ -164,7 +164,7 @@ public function testLoadUserByUserNameShouldLoadUserWhenProperInterfaceProvided( 'Symfony\Bridge\Doctrine\Tests\Fixtures\User' ); - $provider->loadUserByUsername('name'); + $provider->loadUserByIdentifier('name'); } public function testLoadUserByUserNameShouldDeclineInvalidInterface() @@ -177,7 +177,7 @@ public function testLoadUserByUserNameShouldDeclineInvalidInterface() 'Symfony\Bridge\Doctrine\Tests\Fixtures\User' ); - $provider->loadUserByUsername('name'); + $provider->loadUserByIdentifier('name'); } public function testPasswordUpgrades() @@ -231,6 +231,7 @@ private function createSchema($em) abstract class UserLoaderRepository implements ObjectRepository, UserLoaderInterface { + abstract public function loadUserByIdentifier(string $identifier): ?UserInterface; } abstract class PasswordUpgraderRepository implements ObjectRepository, PasswordUpgraderInterface diff --git a/src/Symfony/Bridge/Monolog/Processor/AbstractTokenProcessor.php b/src/Symfony/Bridge/Monolog/Processor/AbstractTokenProcessor.php index ed37c94b81c00..15919978857c3 100644 --- a/src/Symfony/Bridge/Monolog/Processor/AbstractTokenProcessor.php +++ b/src/Symfony/Bridge/Monolog/Processor/AbstractTokenProcessor.php @@ -42,10 +42,16 @@ public function __invoke(array $record): array if (null !== $token = $this->getToken()) { $record['extra'][$this->getKey()] = [ - 'username' => $token->getUsername(), 'authenticated' => $token->isAuthenticated(), 'roles' => $token->getRoleNames(), ]; + + // @deprecated since 5.3, change to $token->getUserIdentifier() in 6.0 + if (method_exists($token, 'getUserIdentifier')) { + $record['extra'][$this->getKey()]['username'] = $record['extra'][$this->getKey()]['user_identifier'] = $token->getUserIdentifier(); + } else { + $record['extra'][$this->getKey()]['username'] = $token->getUsername(); + } } return $record; diff --git a/src/Symfony/Bridge/Monolog/Tests/Processor/SwitchUserTokenProcessorTest.php b/src/Symfony/Bridge/Monolog/Tests/Processor/SwitchUserTokenProcessorTest.php index 7107993b9c849..7d9aaede008c4 100644 --- a/src/Symfony/Bridge/Monolog/Tests/Processor/SwitchUserTokenProcessorTest.php +++ b/src/Symfony/Bridge/Monolog/Tests/Processor/SwitchUserTokenProcessorTest.php @@ -37,11 +37,15 @@ public function testProcessor() $expected = [ 'impersonator_token' => [ - 'username' => 'original_user', 'authenticated' => true, 'roles' => ['ROLE_SUPER_ADMIN'], + 'username' => 'original_user', ], ]; - $this->assertSame($expected, $record['extra']); + if (method_exists($originalToken, 'getUserIdentifier')) { + $expected['impersonator_token']['user_identifier'] = 'original_user'; + } + + $this->assertEquals($expected, $record['extra']); } } diff --git a/src/Symfony/Bridge/Monolog/Tests/Processor/TokenProcessorTest.php b/src/Symfony/Bridge/Monolog/Tests/Processor/TokenProcessorTest.php index dcaf0f647e301..b9fa51658e0d4 100644 --- a/src/Symfony/Bridge/Monolog/Tests/Processor/TokenProcessorTest.php +++ b/src/Symfony/Bridge/Monolog/Tests/Processor/TokenProcessorTest.php @@ -23,8 +23,12 @@ */ class TokenProcessorTest extends TestCase { - public function testProcessor() + public function testLegacyProcessor() { + if (method_exists(UsernamePasswordToken::class, 'getUserIdentifier')) { + $this->markTestSkipped('This test requires symfony/security-core <5.3'); + } + $token = new UsernamePasswordToken('user', 'password', 'provider', ['ROLE_USER']); $tokenStorage = $this->createMock(TokenStorageInterface::class); $tokenStorage->method('getToken')->willReturn($token); @@ -38,4 +42,24 @@ public function testProcessor() $this->assertEquals($token->isAuthenticated(), $record['extra']['token']['authenticated']); $this->assertEquals(['ROLE_USER'], $record['extra']['token']['roles']); } + + public function testProcessor() + { + if (!method_exists(UsernamePasswordToken::class, 'getUserIdentifier')) { + $this->markTestSkipped('This test requires symfony/security-core 5.3+'); + } + + $token = new UsernamePasswordToken('user', 'password', 'provider', ['ROLE_USER']); + $tokenStorage = $this->createMock(TokenStorageInterface::class); + $tokenStorage->method('getToken')->willReturn($token); + + $processor = new TokenProcessor($tokenStorage); + $record = ['extra' => []]; + $record = $processor($record); + + $this->assertArrayHasKey('token', $record['extra']); + $this->assertEquals($token->getUserIdentifier(), $record['extra']['token']['user_identifier']); + $this->assertEquals($token->isAuthenticated(), $record['extra']['token']['authenticated']); + $this->assertEquals(['ROLE_USER'], $record['extra']['token']['roles']); + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/SecurityController.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/SecurityController.php index 6bf27e1ca2d9d..540a5b088a6b1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/SecurityController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/SecurityController.php @@ -21,6 +21,6 @@ class SecurityController implements ContainerAwareInterface public function profileAction() { - return new Response('Welcome '.$this->container->get('security.token_storage')->getToken()->getUsername().'!'); + return new Response('Welcome '.$this->container->get('security.token_storage')->getToken()->getUserIdentifier().'!'); } } diff --git a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php index 9bd7c005757bb..e346d34800413 100644 --- a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php +++ b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php @@ -97,7 +97,9 @@ public function collect(Request $request, Response $response, \Throwable $except $impersonatorUser = null; if ($token instanceof SwitchUserToken) { - $impersonatorUser = $token->getOriginalToken()->getUsername(); + $originalToken = $token->getOriginalToken(); + // @deprecated since 5.3, change to $originalToken->getUserIdentifier() in 6.0 + $impersonatorUser = method_exists($originalToken, 'getUserIdentifier') ? $originalToken->getUserIdentifier() : $originalToken->getUsername(); } if (null !== $this->roleHierarchy) { @@ -126,7 +128,8 @@ public function collect(Request $request, Response $response, \Throwable $except 'token' => $token, 'token_class' => $this->hasVarDumper ? new ClassStub(\get_class($token)) : \get_class($token), 'logout_url' => $logoutUrl, - 'user' => $token->getUsername(), + // @deprecated since 5.3, change to $token->getUserIdentifier() in 6.0 + 'user' => method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername(), 'roles' => $assignedRoles, 'inherited_roles' => array_unique($inheritedRoles), 'supports_role_hierarchy' => null !== $this->roleHierarchy, diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/InMemoryFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/InMemoryFactory.php index a19a9eb163b3f..ceb04e340c8ea 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/InMemoryFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/UserProvider/InMemoryFactory.php @@ -48,8 +48,30 @@ public function addConfiguration(NodeDefinition $node) ->fixXmlConfig('user') ->children() ->arrayNode('users') - ->useAttributeAsKey('name') + ->useAttributeAsKey('identifier') ->normalizeKeys(false) + ->beforeNormalization() + ->always() + ->then(function ($v) { + $deprecation = false; + foreach ($v as $i => $child) { + if (!isset($child['name'])) { + continue; + } + + $deprecation = true; + + $v[$i]['identifier'] = $child['name']; + unset($v[$i]['name']); + } + + if ($deprecation) { + trigger_deprecation('symfony/security-bundle', '5.3', 'The "in_memory.user.name" option is deprecated, use "identifier" instead.'); + } + + return $v; + }) + ->end() ->prototype('array') ->children() ->scalarNode('password')->defaultNull()->end() diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/schema/security-1.0.xsd b/src/Symfony/Bundle/SecurityBundle/Resources/config/schema/security-1.0.xsd index 2196017ae99a9..f7b9790f4a810 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/schema/security-1.0.xsd +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/schema/security-1.0.xsd @@ -136,7 +136,8 @@ - + + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php index a3c0cd6ffb0cf..f192ee614cbb6 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTest.php @@ -297,7 +297,7 @@ public function testAccess() } elseif (3 === $i) { $this->assertEquals('IS_AUTHENTICATED_ANONYMOUSLY', $attributes[0]); $expression = $container->getDefinition((string) $attributes[1])->getArgument(0); - $this->assertEquals("token.getUsername() matches '/^admin/'", $expression); + $this->assertEquals("token.getUserIdentifier() matches '/^admin/'", $expression); } } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php index f551131f00639..6118929a36f69 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/container1.php @@ -97,7 +97,7 @@ 'access_control' => [ ['path' => '/blog/524', 'role' => 'ROLE_USER', 'requires_channel' => 'https', 'methods' => ['get', 'POST'], 'port' => 8000], ['path' => '/blog/.*', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY'], - ['path' => '/blog/524', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY', 'allow_if' => "token.getUsername() matches '/^admin/'"], + ['path' => '/blog/524', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY', 'allow_if' => "token.getUserIdentifier() matches '/^admin/'"], ], 'role_hierarchy' => [ diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/legacy_encoders.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/legacy_encoders.php index 3c9e6104eecc3..d6206527e6180 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/legacy_encoders.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/php/legacy_encoders.php @@ -97,7 +97,7 @@ 'access_control' => [ ['path' => '/blog/524', 'role' => 'ROLE_USER', 'requires_channel' => 'https', 'methods' => ['get', 'POST'], 'port' => 8000], ['path' => '/blog/.*', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY'], - ['path' => '/blog/524', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY', 'allow_if' => "token.getUsername() matches '/^admin/'"], + ['path' => '/blog/524', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY', 'allow_if' => "token.getUserIdentifier() matches '/^admin/'"], ], 'role_hierarchy' => [ diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_customized_config.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_customized_config.xml index b58028b2fbfe3..012c8dac7b069 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_customized_config.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_customized_config.xml @@ -12,7 +12,7 @@ - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_default_strategy.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_default_strategy.xml index 5bffea64f5bf5..1011f45c4accc 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_default_strategy.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_default_strategy.xml @@ -10,7 +10,7 @@ - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_service.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_service.xml index 9f9f9d5a34e27..ebc208c057168 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_service.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_service.xml @@ -12,7 +12,7 @@ - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_service_and_strategy.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_service_and_strategy.xml index 06ee3435e5a7f..1f2133ffe02f1 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_service_and_strategy.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/access_decision_manager_service_and_strategy.xml @@ -12,7 +12,7 @@ - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml index 097a726db58d2..ed7afe5e833ee 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/container1.xml @@ -25,20 +25,20 @@ - + - + - - + + @@ -78,6 +78,6 @@ - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/legacy_encoders.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/legacy_encoders.xml index 84d68cc4fd59b..a362a59a15b80 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/legacy_encoders.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/legacy_encoders.xml @@ -25,20 +25,20 @@ - + - + - - + + @@ -78,6 +78,6 @@ - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/no_custom_user_checker.xml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/no_custom_user_checker.xml index 9dd035b7c47e3..3c545ecedc0be 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/no_custom_user_checker.xml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/xml/no_custom_user_checker.xml @@ -10,7 +10,7 @@ - + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml index 0ac2c94b0680b..3eb50b91b7370 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/container1.yml @@ -84,4 +84,4 @@ security: - path: /blog/.* role: IS_AUTHENTICATED_ANONYMOUSLY - - { path: /blog/524, role: IS_AUTHENTICATED_ANONYMOUSLY, allow_if: "token.getUsername() matches '/^admin/'" } + - { path: /blog/524, role: IS_AUTHENTICATED_ANONYMOUSLY, allow_if: "token.getUserIdentifier() matches '/^admin/'" } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/legacy_encoders.yml b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/legacy_encoders.yml index 03b9aaf6ef5b9..d80a99afcfca3 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/legacy_encoders.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Fixtures/yml/legacy_encoders.yml @@ -84,4 +84,4 @@ security: - path: /blog/.* role: IS_AUTHENTICATED_ANONYMOUSLY - - { path: /blog/524, role: IS_AUTHENTICATED_ANONYMOUSLY, allow_if: "token.getUsername() matches '/^admin/'" } + - { path: /blog/524, role: IS_AUTHENTICATED_ANONYMOUSLY, allow_if: "token.getUserIdentifier() matches '/^admin/'" } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AuthenticatorBundle/ProfileController.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AuthenticatorBundle/ProfileController.php index 3e23d86e37483..3e1598ea593cd 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AuthenticatorBundle/ProfileController.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AuthenticatorBundle/ProfileController.php @@ -19,6 +19,6 @@ public function __invoke() { $this->denyAccessUnlessGranted('ROLE_USER'); - return $this->json(['email' => $this->getUser()->getUsername()]); + return $this->json(['email' => $this->getUser()->getUserIdentifier()]); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Resources/views/Login/after_login.html.twig b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Resources/views/Login/after_login.html.twig index a117cb94f8778..9a9bfbc731397 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Resources/views/Login/after_login.html.twig +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/CsrfFormLoginBundle/Resources/views/Login/after_login.html.twig @@ -1,7 +1,7 @@ {% extends "base.html.twig" %} {% block body %} - Hello {{ app.user.username }}!

+ Hello {{ app.user.userIdentifier }}!

You're browsing to path "{{ app.request.pathInfo }}".

Log out. Log out. diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Resources/views/Login/after_login.html.twig b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Resources/views/Login/after_login.html.twig index 3f88aae903536..fd51df2a4383f 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Resources/views/Login/after_login.html.twig +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/FormLoginBundle/Resources/views/Login/after_login.html.twig @@ -1,7 +1,7 @@ {% extends "base.html.twig" %} {% block body %} - Hello {{ user.username }}!

+ Hello {{ user.userIdentifier }}!

You're browsing to path "{{ app.request.pathInfo }}". Log out. diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/GuardedBundle/AuthenticationController.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/GuardedBundle/AuthenticationController.php index b8af5cee433a3..21a2ea9e4b8f6 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/GuardedBundle/AuthenticationController.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/GuardedBundle/AuthenticationController.php @@ -33,6 +33,6 @@ public function profileAction(UserInterface $user = null) return new Response('Not logged in.'); } - return new Response('Username: '.$user->getUsername()); + return new Response('Username: '.$user->getUserIdentifier()); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/JsonLoginBundle/Controller/TestController.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/JsonLoginBundle/Controller/TestController.php index cba75a1526ace..6bd571d15e217 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/JsonLoginBundle/Controller/TestController.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/JsonLoginBundle/Controller/TestController.php @@ -21,6 +21,6 @@ class TestController { public function loginCheckAction(UserInterface $user) { - return new JsonResponse(['message' => sprintf('Welcome @%s!', $user->getUsername())]); + return new JsonResponse(['message' => sprintf('Welcome @%s!', $user->getUserIdentifier())]); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/JsonLoginBundle/Security/Http/JsonAuthenticationSuccessHandler.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/JsonLoginBundle/Security/Http/JsonAuthenticationSuccessHandler.php index a0300d4d78387..4aabaacd4889c 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/JsonLoginBundle/Security/Http/JsonAuthenticationSuccessHandler.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/JsonLoginBundle/Security/Http/JsonAuthenticationSuccessHandler.php @@ -21,6 +21,6 @@ class JsonAuthenticationSuccessHandler implements AuthenticationSuccessHandlerIn { public function onAuthenticationSuccess(Request $request, TokenInterface $token): Response { - return new JsonResponse(['message' => sprintf('Good game @%s!', $token->getUsername())]); + return new JsonResponse(['message' => sprintf('Good game @%s!', $token->getUserIdentifier())]); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/LoginLink/TestCustomLoginLinkSuccessHandler.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/LoginLink/TestCustomLoginLinkSuccessHandler.php index 329a288584a77..a20866c8cfd91 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/LoginLink/TestCustomLoginLinkSuccessHandler.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/LoginLink/TestCustomLoginLinkSuccessHandler.php @@ -11,6 +11,6 @@ class TestCustomLoginLinkSuccessHandler implements AuthenticationSuccessHandlerI { public function onAuthenticationSuccess(Request $request, TokenInterface $token) { - return new JsonResponse(['message' => sprintf('Welcome %s!', $token->getUsername())]); + return new JsonResponse(['message' => sprintf('Welcome %s!', $token->getUserIdentifier())]); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/SecuredPageBundle/Security/Core/User/ArrayUserProvider.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/SecuredPageBundle/Security/Core/User/ArrayUserProvider.php index 43ea364a525db..a5ca99a41b6b7 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/SecuredPageBundle/Security/Core/User/ArrayUserProvider.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/SecuredPageBundle/Security/Core/User/ArrayUserProvider.php @@ -4,7 +4,7 @@ use Symfony\Bundle\SecurityBundle\Tests\Functional\UserWithoutEquatable; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; use Symfony\Component\Security\Core\User\InMemoryUser; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; @@ -16,7 +16,7 @@ class ArrayUserProvider implements UserProviderInterface public function addUser(UserInterface $user) { - $this->users[$user->getUsername()] = $user; + $this->users[$user->getUserIdentifier()] = $user; } public function setUser($username, UserInterface $user) @@ -31,11 +31,16 @@ public function getUser($username) public function loadUserByUsername($username) { - $user = $this->getUser($username); + return $this->loadUserByIdentifier($username); + } + + public function loadUserByIdentifier(string $identifier): UserInterface + { + $user = $this->getUser($identifier); if (null === $user) { - $e = new UsernameNotFoundException(sprintf('User "%s" not found.', $username)); - $e->setUsername($username); + $e = new UserNotFoundException(sprintf('User "%s" not found.', $identifier)); + $e->setUsername($identifier); throw $e; } @@ -49,10 +54,10 @@ public function refreshUser(UserInterface $user) throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_debug_type($user))); } - $storedUser = $this->getUser($user->getUsername()); + $storedUser = $this->getUser($user->getUserIdentifier()); $class = \get_class($storedUser); - return new $class($storedUser->getUsername(), $storedUser->getPassword(), $storedUser->getRoles(), $storedUser->isEnabled()); + return new $class($storedUser->getUserIdentifier(), $storedUser->getPassword(), $storedUser->getRoles(), $storedUser->isEnabled()); } public function supportsClass($class) diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/ClearRememberMeTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/ClearRememberMeTest.php index a917e66c572c9..66a6676375436 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/ClearRememberMeTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/ClearRememberMeTest.php @@ -51,7 +51,7 @@ class RememberMeFooController { public function __invoke(UserInterface $user) { - return new Response($user->getUsername()); + return new Response($user->getUserIdentifier()); } } @@ -66,7 +66,12 @@ public function __construct(InMemoryUserProvider $inner) public function loadUserByUsername($username) { - return $this->inner->loadUserByUsername($username); + return $this->loadUserByIdentifier($username); + } + + public function loadUserByIdentifier(string $identifier): UserInterface + { + return $this->inner->loadUserByIdentifier($identifier); } public function refreshUser(UserInterface $user) diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LoginLinkAuthenticationTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LoginLinkAuthenticationTest.php index f45ec58055ffe..2c767349287d8 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LoginLinkAuthenticationTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/LoginLinkAuthenticationTest.php @@ -28,7 +28,7 @@ public function testLoginLinkSuccess() $this->markTestSkipped('Login link auth requires symfony/security-http:^5.2'); } - $client = $this->createClient(['test_case' => 'LoginLink', 'root_config' => 'config.yml']); + $client = $this->createClient(['test_case' => 'LoginLink', 'root_config' => 'config.yml', 'debug' => true]); // we need an active request that is under the firewall to use the linker $request = Request::create('/get-login-link'); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityTest.php index e6f06fa2ccaf3..9aae384648fa7 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/SecurityTest.php @@ -106,7 +106,7 @@ public function __construct(?string $username, ?string $password, array $roles = public function __toString() { - return $this->getUsername(); + return $this->getUserIdentifier(); } /** @@ -141,6 +141,11 @@ public function getUsername() return $this->username; } + public function getUserIdentifier() + { + return $this->username; + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Ldap/Security/CheckLdapCredentialsListener.php b/src/Symfony/Component/Ldap/Security/CheckLdapCredentialsListener.php index e1b79443ac039..e32793c3d3bf4 100644 --- a/src/Symfony/Component/Ldap/Security/CheckLdapCredentialsListener.php +++ b/src/Symfony/Component/Ldap/Security/CheckLdapCredentialsListener.php @@ -83,7 +83,8 @@ 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->getUsername(), '', LdapInterface::ESCAPE_FILTER); + // @deprecated since 5.3, change to $user->getUserIdentifier() in 6.0 + $username = $ldap->escape(method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername(), '', LdapInterface::ESCAPE_FILTER); $query = str_replace('{username}', $username, $ldapBadge->getQueryString()); $result = $ldap->query($ldapBadge->getDnString(), $query)->execute(); if (1 !== $result->count()) { @@ -92,7 +93,8 @@ public function onCheckPassport(CheckPassportEvent $event) $dn = $result[0]->getDn(); } else { - $username = $ldap->escape($user->getUsername(), '', LdapInterface::ESCAPE_DN); + // @deprecated since 5.3, change to $user->getUserIdentifier() in 6.0 + $username = $ldap->escape(method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername(), '', LdapInterface::ESCAPE_DN); $dn = str_replace('{username}', $username, $ldapBadge->getDnString()); } diff --git a/src/Symfony/Component/Ldap/Security/LdapUser.php b/src/Symfony/Component/Ldap/Security/LdapUser.php index 7c6a2c261c02e..86d6b0c4ad19c 100644 --- a/src/Symfony/Component/Ldap/Security/LdapUser.php +++ b/src/Symfony/Component/Ldap/Security/LdapUser.php @@ -75,6 +75,13 @@ public function getSalt(): ?string * {@inheritdoc} */ public function getUsername(): string + { + trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated and will be removed in 6.0, use getUserIdentifier() instead.', __METHOD__); + + return $this->username; + } + + public function getUserIdentifier(): string { return $this->username; } diff --git a/src/Symfony/Component/Ldap/Security/LdapUserProvider.php b/src/Symfony/Component/Ldap/Security/LdapUserProvider.php index 6b0b4304ba8f6..319d6c605c43c 100644 --- a/src/Symfony/Component/Ldap/Security/LdapUserProvider.php +++ b/src/Symfony/Component/Ldap/Security/LdapUserProvider.php @@ -17,7 +17,7 @@ use Symfony\Component\Ldap\LdapInterface; use Symfony\Component\Security\Core\Exception\InvalidArgumentException; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; @@ -48,7 +48,7 @@ public function __construct(LdapInterface $ldap, string $baseDn, string $searchD } if (null === $filter) { - $filter = '({uid_key}={username})'; + $filter = '({uid_key}={user_identifier})'; } $this->ldap = $ldap; @@ -66,15 +66,22 @@ public function __construct(LdapInterface $ldap, string $baseDn, string $searchD * {@inheritdoc} */ public function loadUserByUsername(string $username) + { + trigger_deprecation('symfony/ldap', '5.3', 'Method "%s()" is deprecated, use loadUserByIdentifier() instead.', __METHOD__); + + return $this->loadUserByIdentifier($username); + } + + public function loadUserByIdentifier(string $identifier): UserInterface { try { $this->ldap->bind($this->searchDn, $this->searchPassword); - $username = $this->ldap->escape($username, '', LdapInterface::ESCAPE_FILTER); - $query = str_replace('{username}', $username, $this->defaultSearch); + $identifier = $this->ldap->escape($identifier, '', LdapInterface::ESCAPE_FILTER); + $query = str_replace(['{username}', '{user_identifier}'], $identifier, $this->defaultSearch); $search = $this->ldap->query($this->baseDn, $query); } catch (ConnectionException $e) { - $e = new UsernameNotFoundException(sprintf('User "%s" not found.', $username), 0, $e); - $e->setUsername($username); + $e = new UserNotFoundException(sprintf('User "%s" not found.', $identifier), 0, $e); + $e->setUserIdentifier($identifier); throw $e; } @@ -83,15 +90,15 @@ public function loadUserByUsername(string $username) $count = \count($entries); if (!$count) { - $e = new UsernameNotFoundException(sprintf('User "%s" not found.', $username)); - $e->setUsername($username); + $e = new UserNotFoundException(sprintf('User "%s" not found.', $identifier)); + $e->setUserIdentifier($identifier); throw $e; } if ($count > 1) { - $e = new UsernameNotFoundException('More than one user found.'); - $e->setUsername($username); + $e = new UserNotFoundException('More than one user found.'); + $e->setUserIdentifier($identifier); throw $e; } @@ -100,12 +107,12 @@ public function loadUserByUsername(string $username) try { if (null !== $this->uidKey) { - $username = $this->getAttributeValue($entry, $this->uidKey); + $identifier = $this->getAttributeValue($entry, $this->uidKey); } } catch (InvalidArgumentException $e) { } - return $this->loadUser($username, $entry); + return $this->loadUser($identifier, $entry); } /** @@ -117,7 +124,7 @@ public function refreshUser(UserInterface $user) throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_debug_type($user))); } - return new LdapUser($user->getEntry(), $user->getUsername(), $user->getPassword(), $user->getRoles(), $user->getExtraFields()); + return new LdapUser($user->getEntry(), $user->getUserIdentifier(), $user->getPassword(), $user->getRoles(), $user->getExtraFields()); } /** @@ -157,7 +164,7 @@ public function supportsClass(string $class) * * @return UserInterface */ - protected function loadUser(string $username, Entry $entry) + protected function loadUser(string $identifier, Entry $entry) { $password = null; $extraFields = []; @@ -170,7 +177,7 @@ protected function loadUser(string $username, Entry $entry) $extraFields[$field] = $this->getAttributeValue($entry, $field); } - return new LdapUser($entry, $username, $password, $this->defaultRoles, $extraFields); + return new LdapUser($entry, $identifier, $password, $this->defaultRoles, $extraFields); } private function getAttributeValue(Entry $entry, string $attribute) diff --git a/src/Symfony/Component/Ldap/Tests/Security/LdapUserProviderTest.php b/src/Symfony/Component/Ldap/Tests/Security/LdapUserProviderTest.php index f6cd4c7414c51..32f6d60d5df3b 100644 --- a/src/Symfony/Component/Ldap/Tests/Security/LdapUserProviderTest.php +++ b/src/Symfony/Component/Ldap/Tests/Security/LdapUserProviderTest.php @@ -20,7 +20,7 @@ use Symfony\Component\Ldap\Security\LdapUser; use Symfony\Component\Ldap\Security\LdapUserProvider; use Symfony\Component\Security\Core\Exception\InvalidArgumentException; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; /** * @requires extension ldap @@ -29,7 +29,7 @@ class LdapUserProviderTest extends TestCase { public function testLoadUserByUsernameFailsIfCantConnectToLdap() { - $this->expectException(UsernameNotFoundException::class); + $this->expectException(UserNotFoundException::class); $ldap = $this->createMock(LdapInterface::class); $ldap @@ -39,12 +39,12 @@ public function testLoadUserByUsernameFailsIfCantConnectToLdap() ; $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com'); - $provider->loadUserByUsername('foo'); + $provider->loadUserByIdentifier('foo'); } public function testLoadUserByUsernameFailsIfNoLdapEntries() { - $this->expectException(UsernameNotFoundException::class); + $this->expectException(UserNotFoundException::class); $result = $this->createMock(CollectionInterface::class); $query = $this->createMock(QueryInterface::class); @@ -71,12 +71,12 @@ public function testLoadUserByUsernameFailsIfNoLdapEntries() ; $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com'); - $provider->loadUserByUsername('foo'); + $provider->loadUserByIdentifier('foo'); } public function testLoadUserByUsernameFailsIfMoreThanOneLdapEntry() { - $this->expectException(UsernameNotFoundException::class); + $this->expectException(UserNotFoundException::class); $result = $this->createMock(CollectionInterface::class); $query = $this->createMock(QueryInterface::class); @@ -103,7 +103,7 @@ public function testLoadUserByUsernameFailsIfMoreThanOneLdapEntry() ; $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com'); - $provider->loadUserByUsername('foo'); + $provider->loadUserByIdentifier('foo'); } public function testLoadUserByUsernameFailsIfMoreThanOneLdapPasswordsInEntry() @@ -144,7 +144,7 @@ public function testLoadUserByUsernameFailsIfMoreThanOneLdapPasswordsInEntry() ; $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com', null, null, [], 'sAMAccountName', '({uid_key}={username})', 'userpassword'); - $this->assertInstanceOf(LdapUser::class, $provider->loadUserByUsername('foo')); + $this->assertInstanceOf(LdapUser::class, $provider->loadUserByIdentifier('foo')); } public function testLoadUserByUsernameShouldNotFailIfEntryHasNoUidKeyAttribute() @@ -180,7 +180,7 @@ public function testLoadUserByUsernameShouldNotFailIfEntryHasNoUidKeyAttribute() ; $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com', null, null, [], 'sAMAccountName', '({uid_key}={username})'); - $this->assertInstanceOf(LdapUser::class, $provider->loadUserByUsername('foo')); + $this->assertInstanceOf(LdapUser::class, $provider->loadUserByIdentifier('foo')); } public function testLoadUserByUsernameFailsIfEntryHasNoPasswordAttribute() @@ -218,7 +218,7 @@ public function testLoadUserByUsernameFailsIfEntryHasNoPasswordAttribute() ; $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com', null, null, [], 'sAMAccountName', '({uid_key}={username})', 'userpassword'); - $this->assertInstanceOf(LdapUser::class, $provider->loadUserByUsername('foo')); + $this->assertInstanceOf(LdapUser::class, $provider->loadUserByIdentifier('foo')); } public function testLoadUserByUsernameIsSuccessfulWithoutPasswordAttribute() @@ -254,7 +254,7 @@ public function testLoadUserByUsernameIsSuccessfulWithoutPasswordAttribute() ; $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com'); - $this->assertInstanceOf(LdapUser::class, $provider->loadUserByUsername('foo')); + $this->assertInstanceOf(LdapUser::class, $provider->loadUserByIdentifier('foo')); } public function testLoadUserByUsernameIsSuccessfulWithoutPasswordAttributeAndWrongCase() @@ -290,7 +290,7 @@ public function testLoadUserByUsernameIsSuccessfulWithoutPasswordAttributeAndWro ; $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com'); - $this->assertSame('foo', $provider->loadUserByUsername('Foo')->getUsername()); + $this->assertSame('foo', $provider->loadUserByIdentifier('Foo')->getUserIdentifier()); } public function testLoadUserByUsernameIsSuccessfulWithPasswordAttribute() @@ -330,7 +330,7 @@ public function testLoadUserByUsernameIsSuccessfulWithPasswordAttribute() ; $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com', null, null, [], 'sAMAccountName', '({uid_key}={username})', 'userpassword', ['email']); - $this->assertInstanceOf(LdapUser::class, $provider->loadUserByUsername('foo')); + $this->assertInstanceOf(LdapUser::class, $provider->loadUserByIdentifier('foo')); } public function testRefreshUserShouldReturnUserWithSameProperties() diff --git a/src/Symfony/Component/PasswordHasher/Tests/Hasher/PasswordHasherFactoryTest.php b/src/Symfony/Component/PasswordHasher/Tests/Hasher/PasswordHasherFactoryTest.php index 46d430f51a8c4..633e38e39eef0 100644 --- a/src/Symfony/Component/PasswordHasher/Tests/Hasher/PasswordHasherFactoryTest.php +++ b/src/Symfony/Component/PasswordHasher/Tests/Hasher/PasswordHasherFactoryTest.php @@ -196,6 +196,10 @@ public function getUsername(): string { } + public function getUserIdentifier(): string + { + } + public function eraseCredentials() { } diff --git a/src/Symfony/Component/Security/CHANGELOG.md b/src/Symfony/Component/Security/CHANGELOG.md index d3f7ca0322823..adfd240992712 100644 --- a/src/Symfony/Component/Security/CHANGELOG.md +++ b/src/Symfony/Component/Security/CHANGELOG.md @@ -4,6 +4,11 @@ CHANGELOG 5.3 --- + * Deprecate `PersistentTokenInterface::getUsername()` in favor of `PersistentTokenInterface::getUserIdentifier()` + * Deprecate `UsernameNotFoundException` in favor of `UserNotFoundException` and `getUsername()`/`setUsername()` in favor of `getUserIdentifier()`/`setUserIdentifier()` + * Deprecate `UserProviderInterface::loadUserByUsername()` in favor of `UserProviderInterface::loadUserByIdentifier()` + * Deprecate `TokenInterface::getUsername()` in favor of `TokenInterface::getUserIdentifier()` + * Deprecate `UserInterface::getUsername()` in favor of `getUserIdentifier()` * Add `PassportInterface:getBadges()`, implemented by `PassportTrait` * [BC BREAK] Remove method `checkIfCompletelyResolved()` from `PassportInterface`, checking that passport badges are resolved is up to `AuthenticatorManager` diff --git a/src/Symfony/Component/Security/Core/Authentication/AuthenticationProviderManager.php b/src/Symfony/Component/Security/Core/Authentication/AuthenticationProviderManager.php index c4099603ef59f..ddf098306d4b3 100644 --- a/src/Symfony/Component/Security/Core/Authentication/AuthenticationProviderManager.php +++ b/src/Symfony/Component/Security/Core/Authentication/AuthenticationProviderManager.php @@ -21,6 +21,7 @@ use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\BadCredentialsException; use Symfony\Component\Security\Core\Exception\ProviderNotFoundException; +use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; // Help opcache.preload discover always-needed symbols @@ -105,6 +106,11 @@ public function authenticate(TokenInterface $token) $this->eventDispatcher->dispatch(new AuthenticationSuccessEvent($result), AuthenticationEvents::AUTHENTICATION_SUCCESS); } + // @deprecated since 5.3 + if ($user = $result->getUser() instanceof UserInterface && !method_exists($result->getUser(), 'getUserIdentifier')) { + trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "getUserIdentifier(): string" in user class "%s" is deprecated. This method will replace "getUsername()" in Symfony 6.0.', get_debug_type($result->getUser())); + } + return $result; } diff --git a/src/Symfony/Component/Security/Core/Authentication/Provider/DaoAuthenticationProvider.php b/src/Symfony/Component/Security/Core/Authentication/Provider/DaoAuthenticationProvider.php index eca9357f63a67..4ef55664dc513 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Provider/DaoAuthenticationProvider.php +++ b/src/Symfony/Component/Security/Core/Authentication/Provider/DaoAuthenticationProvider.php @@ -16,7 +16,7 @@ use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; use Symfony\Component\Security\Core\Exception\AuthenticationServiceException; use Symfony\Component\Security\Core\Exception\BadCredentialsException; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; use Symfony\Component\Security\Core\User\LegacyPasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; @@ -108,7 +108,7 @@ protected function checkAuthentication(UserInterface $user, UsernamePasswordToke /** * {@inheritdoc} */ - protected function retrieveUser(string $username, UsernamePasswordToken $token) + protected function retrieveUser(string $userIdentifier, UsernamePasswordToken $token) { $user = $token->getUser(); if ($user instanceof UserInterface) { @@ -116,15 +116,22 @@ protected function retrieveUser(string $username, UsernamePasswordToken $token) } try { - $user = $this->userProvider->loadUserByUsername($username); + // @deprecated since 5.3, change to $this->userProvider->loadUserByIdentifier() in 6.0 + if (method_exists($this->userProvider, 'loadUserByIdentifier')) { + $user = $this->userProvider->loadUserByIdentifier($userIdentifier); + } else { + trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', get_debug_type($this->userProvider)); + + $user = $this->userProvider->loadUserByUsername($userIdentifier); + } if (!$user instanceof UserInterface) { throw new AuthenticationServiceException('The user provider must return a UserInterface object.'); } return $user; - } catch (UsernameNotFoundException $e) { - $e->setUsername($username); + } catch (UserNotFoundException $e) { + $e->setUserIdentifier($userIdentifier); throw $e; } catch (\Exception $e) { $e = new AuthenticationServiceException($e->getMessage(), 0, $e); diff --git a/src/Symfony/Component/Security/Core/Authentication/Provider/LdapBindAuthenticationProvider.php b/src/Symfony/Component/Security/Core/Authentication/Provider/LdapBindAuthenticationProvider.php index 3705ae8289a19..e9a3ab029413f 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Provider/LdapBindAuthenticationProvider.php +++ b/src/Symfony/Component/Security/Core/Authentication/Provider/LdapBindAuthenticationProvider.php @@ -16,7 +16,7 @@ use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Exception\BadCredentialsException; use Symfony\Component\Security\Core\Exception\LogicException; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; use Symfony\Component\Security\Core\User\UserCheckerInterface; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; @@ -38,7 +38,7 @@ class LdapBindAuthenticationProvider extends UserAuthenticationProvider private $searchDn; private $searchPassword; - public function __construct(UserProviderInterface $userProvider, UserCheckerInterface $userChecker, string $providerKey, LdapInterface $ldap, string $dnString = '{username}', bool $hideUserNotFoundExceptions = true, string $searchDn = '', string $searchPassword = '') + public function __construct(UserProviderInterface $userProvider, UserCheckerInterface $userChecker, string $providerKey, LdapInterface $ldap, string $dnString = '{user_identifier}', bool $hideUserNotFoundExceptions = true, string $searchDn = '', string $searchPassword = '') { parent::__construct($userChecker, $providerKey, $hideUserNotFoundExceptions); @@ -50,7 +50,7 @@ public function __construct(UserProviderInterface $userProvider, UserCheckerInte } /** - * Set a query string to use in order to find a DN for the username. + * Set a query string to use in order to find a DN for the user identifier. */ public function setQueryString(string $queryString) { @@ -60,13 +60,20 @@ public function setQueryString(string $queryString) /** * {@inheritdoc} */ - protected function retrieveUser(string $username, UsernamePasswordToken $token) + protected function retrieveUser(string $userIdentifier, UsernamePasswordToken $token) { - if (AuthenticationProviderInterface::USERNAME_NONE_PROVIDED === $username) { - throw new UsernameNotFoundException('Username can not be null.'); + if (AuthenticationProviderInterface::USERNAME_NONE_PROVIDED === $userIdentifier) { + throw new UserNotFoundException('User identifier can not be null.'); } - return $this->userProvider->loadUserByUsername($username); + // @deprecated since 5.3, change to $this->userProvider->loadUserByIdentifier() in 6.0 + if (method_exists($this->userProvider, 'loadUserByIdentifier')) { + return $this->userProvider->loadUserByIdentifier($userIdentifier); + } else { + trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', get_debug_type($this->userProvider)); + + return $this->userProvider->loadUserByUsername($userIdentifier); + } } /** @@ -74,7 +81,8 @@ protected function retrieveUser(string $username, UsernamePasswordToken $token) */ protected function checkAuthentication(UserInterface $user, UsernamePasswordToken $token) { - $username = $token->getUsername(); + // @deprecated since 5.3, change to $token->getUserIdentifier() in 6.0 + $userIdentifier = method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername(); $password = $token->getCredentials(); if ('' === (string) $password) { @@ -88,8 +96,8 @@ protected function checkAuthentication(UserInterface $user, UsernamePasswordToke } else { throw new LogicException('Using the "query_string" config without using a "search_dn" and a "search_password" is not supported.'); } - $username = $this->ldap->escape($username, '', LdapInterface::ESCAPE_FILTER); - $query = str_replace('{username}', $username, $this->queryString); + $userIdentifier = $this->ldap->escape($userIdentifier, '', LdapInterface::ESCAPE_FILTER); + $query = str_replace(['{username}', '{user_identifier}'], $userIdentifier, $this->queryString); $result = $this->ldap->query($this->dnString, $query)->execute(); if (1 !== $result->count()) { throw new BadCredentialsException('The presented username is invalid.'); @@ -97,8 +105,8 @@ protected function checkAuthentication(UserInterface $user, UsernamePasswordToke $dn = $result[0]->getDn(); } else { - $username = $this->ldap->escape($username, '', LdapInterface::ESCAPE_DN); - $dn = str_replace('{username}', $username, $this->dnString); + $userIdentifier = $this->ldap->escape($userIdentifier, '', LdapInterface::ESCAPE_DN); + $dn = str_replace(['{username}', '{user_identifier}'], $userIdentifier, $this->dnString); } $this->ldap->bind($dn, $password); diff --git a/src/Symfony/Component/Security/Core/Authentication/Provider/PreAuthenticatedAuthenticationProvider.php b/src/Symfony/Component/Security/Core/Authentication/Provider/PreAuthenticatedAuthenticationProvider.php index c0612bc0b61c4..292b8b9fed0f2 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Provider/PreAuthenticatedAuthenticationProvider.php +++ b/src/Symfony/Component/Security/Core/Authentication/Provider/PreAuthenticatedAuthenticationProvider.php @@ -24,7 +24,7 @@ * This authentication provider will not perform any checks on authentication * requests, as they should already be pre-authenticated. However, the * UserProviderInterface implementation may still throw a - * UsernameNotFoundException, for example. + * UserNotFoundException, for example. * * @author Fabien Potencier */ @@ -54,7 +54,15 @@ public function authenticate(TokenInterface $token) throw new BadCredentialsException('No pre-authenticated principal found in request.'); } - $user = $this->userProvider->loadUserByUsername($user); + $userIdentifier = method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername(); + // @deprecated since 5.3, change to $this->userProvider->loadUserByIdentifier() in 6.0 + if (method_exists($this->userProvider, 'loadUserByIdentifier')) { + $user = $this->userProvider->loadUserByIdentifier($userIdentifier); + } else { + trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', get_debug_type($this->userProvider)); + + $user = $this->userProvider->loadUserByUsername($userIdentifier); + } $this->userChecker->checkPostAuth($user); diff --git a/src/Symfony/Component/Security/Core/Authentication/Provider/RememberMeAuthenticationProvider.php b/src/Symfony/Component/Security/Core/Authentication/Provider/RememberMeAuthenticationProvider.php index 630064af447ba..8ee8109b80170 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Provider/RememberMeAuthenticationProvider.php +++ b/src/Symfony/Component/Security/Core/Authentication/Provider/RememberMeAuthenticationProvider.php @@ -51,7 +51,7 @@ public function authenticate(TokenInterface $token) $user = $token->getUser(); - if (!$token->getUser() instanceof UserInterface) { + if (!$user instanceof UserInterface) { throw new LogicException(sprintf('Method "%s::getUser()" must return a "%s" instance, "%s" returned.', get_debug_type($token), UserInterface::class, get_debug_type($user))); } diff --git a/src/Symfony/Component/Security/Core/Authentication/Provider/UserAuthenticationProvider.php b/src/Symfony/Component/Security/Core/Authentication/Provider/UserAuthenticationProvider.php index 21c1787ea9d52..4dfff685a7706 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Provider/UserAuthenticationProvider.php +++ b/src/Symfony/Component/Security/Core/Authentication/Provider/UserAuthenticationProvider.php @@ -17,7 +17,7 @@ use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\AuthenticationServiceException; use Symfony\Component\Security\Core\Exception\BadCredentialsException; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; use Symfony\Component\Security\Core\User\UserCheckerInterface; use Symfony\Component\Security\Core\User\UserInterface; @@ -55,18 +55,18 @@ public function authenticate(TokenInterface $token) throw new AuthenticationException('The token is not supported by this authentication provider.'); } - $username = $token->getUsername(); + $username = method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername(); if ('' === $username || null === $username) { $username = AuthenticationProviderInterface::USERNAME_NONE_PROVIDED; } try { $user = $this->retrieveUser($username, $token); - } catch (UsernameNotFoundException $e) { + } catch (UserNotFoundException $e) { if ($this->hideUserNotFoundExceptions) { throw new BadCredentialsException('Bad credentials.', 0, $e); } - $e->setUsername($username); + $e->setUserIdentifier($username); throw $e; } diff --git a/src/Symfony/Component/Security/Core/Authentication/RememberMe/InMemoryTokenProvider.php b/src/Symfony/Component/Security/Core/Authentication/RememberMe/InMemoryTokenProvider.php index a1b30443f3573..571bbe02ef716 100644 --- a/src/Symfony/Component/Security/Core/Authentication/RememberMe/InMemoryTokenProvider.php +++ b/src/Symfony/Component/Security/Core/Authentication/RememberMe/InMemoryTokenProvider.php @@ -45,7 +45,7 @@ public function updateToken(string $series, string $tokenValue, \DateTime $lastU $token = new PersistentToken( $this->tokens[$series]->getClass(), - $this->tokens[$series]->getUsername(), + method_exists($this->tokens[$series], 'getUserIdentifier') ? $this->tokens[$series]->getUserIdentifier() : $this->tokens[$series]->getUsername(), $series, $tokenValue, $lastUsed diff --git a/src/Symfony/Component/Security/Core/Authentication/RememberMe/PersistentToken.php b/src/Symfony/Component/Security/Core/Authentication/RememberMe/PersistentToken.php index 1f0e485c50ef2..b8337adff57f3 100644 --- a/src/Symfony/Component/Security/Core/Authentication/RememberMe/PersistentToken.php +++ b/src/Symfony/Component/Security/Core/Authentication/RememberMe/PersistentToken.php @@ -19,18 +19,18 @@ final class PersistentToken implements PersistentTokenInterface { private $class; - private $username; + private $userIdentifier; private $series; private $tokenValue; private $lastUsed; - public function __construct(string $class, string $username, string $series, string $tokenValue, \DateTime $lastUsed) + public function __construct(string $class, string $userIdentifier, string $series, string $tokenValue, \DateTime $lastUsed) { if (empty($class)) { throw new \InvalidArgumentException('$class must not be empty.'); } - if ('' === $username) { - throw new \InvalidArgumentException('$username must not be empty.'); + if ('' === $userIdentifier) { + throw new \InvalidArgumentException('$userIdentifier must not be empty.'); } if (empty($series)) { throw new \InvalidArgumentException('$series must not be empty.'); @@ -40,7 +40,7 @@ public function __construct(string $class, string $username, string $series, str } $this->class = $class; - $this->username = $username; + $this->userIdentifier = $userIdentifier; $this->series = $series; $this->tokenValue = $tokenValue; $this->lastUsed = $lastUsed; @@ -59,7 +59,14 @@ public function getClass(): string */ public function getUsername(): string { - return $this->username; + trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, use getUserIdentifier() instead.', __METHOD__); + + return $this->userIdentifier; + } + + public function getUserIdentifier(): string + { + return $this->userIdentifier; } /** diff --git a/src/Symfony/Component/Security/Core/Authentication/RememberMe/PersistentTokenInterface.php b/src/Symfony/Component/Security/Core/Authentication/RememberMe/PersistentTokenInterface.php index ba31ffa6d022e..85c5bc385e580 100644 --- a/src/Symfony/Component/Security/Core/Authentication/RememberMe/PersistentTokenInterface.php +++ b/src/Symfony/Component/Security/Core/Authentication/RememberMe/PersistentTokenInterface.php @@ -15,6 +15,8 @@ * Interface to be implemented by persistent token classes (such as * Doctrine entities representing a remember-me token). * + * @method string getUserIdentifier() returns the identifier used to authenticate (e.g. their e-mailaddress or username) + * * @author Johannes M. Schmitt */ interface PersistentTokenInterface @@ -26,13 +28,6 @@ interface PersistentTokenInterface */ public function getClass(); - /** - * Returns the username. - * - * @return string - */ - public function getUsername(); - /** * Returns the series. * diff --git a/src/Symfony/Component/Security/Core/Authentication/Token/AbstractToken.php b/src/Symfony/Component/Security/Core/Authentication/Token/AbstractToken.php index 0083ae3957728..b7934137e607f 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Token/AbstractToken.php +++ b/src/Symfony/Component/Security/Core/Authentication/Token/AbstractToken.php @@ -51,10 +51,32 @@ public function getRoleNames(): array /** * {@inheritdoc} */ - public function getUsername() + public function getUsername(/* $legacy = true */) { + if (1 === func_num_args() && false === func_get_arg(0)) { + return null; + } + + trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, use getUserIdentifier() instead.', __METHOD__); + + if ($this->user instanceof UserInterface) { + return method_exists($this->user, 'getUserIdentifier') ? $this->user->getUserIdentifier() : $this->user->getUsername(); + } + + return (string) $this->user; + } + + public function getUserIdentifier(): string + { + // method returns "null" in non-legacy mode if not overriden + $username = $this->getUsername(false); + if (null !== $username) { + trigger_deprecation('symfony/security-core', '5.3', 'Method "%s::getUsername()" is deprecated, override "getUserIdentifier()" instead.', get_debug_type($this)); + } + if ($this->user instanceof UserInterface) { - return $this->user->getUsername(); + // @deprecated since 5.3, change to $user->getUserIdentifier() in 6.0 + return method_exists($this->user, 'getUserIdentifier') ? $this->user->getUserIdentifier() : $this->user->getUsername(); } return (string) $this->user; @@ -234,7 +256,7 @@ public function __toString() $roles[] = $role; } - return sprintf('%s(user="%s", authenticated=%s, roles="%s")', $class, $this->getUsername(), json_encode($this->authenticated), implode(', ', $roles)); + return sprintf('%s(user="%s", authenticated=%s, roles="%s")', $class, $this->getUserIdentifier(), json_encode($this->authenticated), implode(', ', $roles)); } /** @@ -283,7 +305,11 @@ private function hasUserChanged(UserInterface $user): bool return true; } - if ($this->user->getUsername() !== $user->getUsername()) { + // @deprecated since Symfony 5.3, drop getUsername() in 6.0 + $userIdentifier = function ($user) { + return method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername(); + }; + if ($userIdentifier($this->user) !== $userIdentifier($user)) { return true; } diff --git a/src/Symfony/Component/Security/Core/Authentication/Token/NullToken.php b/src/Symfony/Component/Security/Core/Authentication/Token/NullToken.php index 589ad1b47f9c7..5c8a1c24b1e12 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Token/NullToken.php +++ b/src/Symfony/Component/Security/Core/Authentication/Token/NullToken.php @@ -42,6 +42,13 @@ public function setUser($user) } public function getUsername() + { + trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, use getUserIdentifier() instead.', __METHOD__); + + return ''; + } + + public function getUserIdentifier(): string { return ''; } diff --git a/src/Symfony/Component/Security/Core/Authentication/Token/Storage/TokenStorage.php b/src/Symfony/Component/Security/Core/Authentication/Token/Storage/TokenStorage.php index 850c05e752672..1fc30bfcd1473 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Token/Storage/TokenStorage.php +++ b/src/Symfony/Component/Security/Core/Authentication/Token/Storage/TokenStorage.php @@ -48,6 +48,11 @@ public function setToken(TokenInterface $token = null) if ($token) { // ensure any initializer is called $this->getToken(); + + // @deprecated since 5.3 + if (!method_exists($token, 'getUserIdentifier')) { + trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "getUserIdentifier(): string" in token class "%s" is deprecated. This method will replace "getUsername()" in Symfony 6.0.', get_debug_type($token)); + } } $this->initializer = null; diff --git a/src/Symfony/Component/Security/Core/Authentication/Token/TokenInterface.php b/src/Symfony/Component/Security/Core/Authentication/Token/TokenInterface.php index ad48ec6455f6a..047f571ae4a20 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Token/TokenInterface.php +++ b/src/Symfony/Component/Security/Core/Authentication/Token/TokenInterface.php @@ -16,6 +16,8 @@ /** * TokenInterface is the interface for the user authentication information. * + * @method string getUserIdentifier() returns the user identifier used during authentication (e.g. a user's e-mailaddress or username) + * * @author Fabien Potencier * @author Johannes M. Schmitt */ @@ -65,13 +67,6 @@ public function getUser(); */ public function setUser($user); - /** - * Returns the username. - * - * @return string - */ - public function getUsername(); - /** * Returns whether the user is authenticated or not. * diff --git a/src/Symfony/Component/Security/Core/Exception/UserNotFoundException.php b/src/Symfony/Component/Security/Core/Exception/UserNotFoundException.php new file mode 100644 index 0000000000000..d730f7d7719f5 --- /dev/null +++ b/src/Symfony/Component/Security/Core/Exception/UserNotFoundException.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +/** + * UserNotFoundException is thrown if a User cannot be found for the given identifier. + * + * @author Fabien Potencier + * @author Alexander + */ +class UserNotFoundException extends AuthenticationException +{ + private $identifier; + + /** + * {@inheritdoc} + */ + public function getMessageKey() + { + return 'Username could not be found.'; + } + + /** + * Get the user identifier (e.g. username or e-mailaddress). + */ + public function getUserIdentifier(): string + { + return $this->identifier; + } + + /** + * @return string + * + * @deprecated + */ + public function getUsername() + { + trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, use getUserIdentifier() instead.', __METHOD__); + + return $this->identifier; + } + + /** + * Set the user identifier (e.g. username or e-mailaddress). + */ + public function setUserIdentifier(string $identifier): void + { + $this->identifier = $identifier; + } + + /** + * @deprecated + */ + public function setUsername(string $username) + { + trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, use getUserIdentifier() instead.', __METHOD__); + + $this->identifier = $username; + } + + /** + * {@inheritdoc} + */ + public function getMessageData() + { + return ['{{ username }}' => $this->identifier, '{{ user_identifier }}' => $this->identifier]; + } + + /** + * {@inheritdoc} + */ + public function __serialize(): array + { + return [$this->identifier, parent::__serialize()]; + } + + /** + * {@inheritdoc} + */ + public function __unserialize(array $data): void + { + [$this->identifier, $parentData] = $data; + $parentData = \is_array($parentData) ? $parentData : unserialize($parentData); + parent::__unserialize($parentData); + } +} +class_alias(UserNotFoundException::class, UsernameNotFoundException::class); diff --git a/src/Symfony/Component/Security/Core/Exception/UsernameNotFoundException.php b/src/Symfony/Component/Security/Core/Exception/UsernameNotFoundException.php index f46013236c12a..e0d2d4a2ef113 100644 --- a/src/Symfony/Component/Security/Core/Exception/UsernameNotFoundException.php +++ b/src/Symfony/Component/Security/Core/Exception/UsernameNotFoundException.php @@ -11,65 +11,15 @@ namespace Symfony\Component\Security\Core\Exception; -/** - * UsernameNotFoundException is thrown if a User cannot be found by its username. - * - * @author Fabien Potencier - * @author Alexander - */ -class UsernameNotFoundException extends AuthenticationException -{ - private $username; - - /** - * {@inheritdoc} - */ - public function getMessageKey() - { - return 'Username could not be found.'; - } - - /** - * Get the username. - * - * @return string - */ - public function getUsername() - { - return $this->username; - } - - /** - * Set the username. - */ - public function setUsername(string $username) - { - $this->username = $username; - } +trigger_deprecation('symfony/security-core', '5.3', 'The "%s" class is deprecated, use "%s" instead.', UsernameNotFoundException::class, UserNotFoundException::class); - /** - * {@inheritdoc} - */ - public function getMessageData() - { - return ['{{ username }}' => $this->username]; - } - - /** - * {@inheritdoc} - */ - public function __serialize(): array - { - return [$this->username, parent::__serialize()]; - } +class_exists(UserNotFoundException::class); +if (false) { /** - * {@inheritdoc} + * @deprecated since Symfony 5.3 to be removed in 6.0, use UserNotFoundException instead. */ - public function __unserialize(array $data): void + class UsernameNotFoundException extends AuthenticationException { - [$this->username, $parentData] = $data; - $parentData = \is_array($parentData) ? $parentData : unserialize($parentData); - parent::__unserialize($parentData); } } diff --git a/src/Symfony/Component/Security/Core/Tests/Authentication/AuthenticationProviderManagerTest.php b/src/Symfony/Component/Security/Core/Tests/Authentication/AuthenticationProviderManagerTest.php index db1e388703bb6..d41805bffb371 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authentication/AuthenticationProviderManagerTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/AuthenticationProviderManagerTest.php @@ -23,6 +23,7 @@ use Symfony\Component\Security\Core\Exception\AccountStatusException; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\ProviderNotFoundException; +use Symfony\Component\Security\Core\User\InMemoryUser; class AuthenticationProviderManagerTest extends TestCase { @@ -90,9 +91,12 @@ public function testAuthenticateWhenProviderReturnsAuthenticationException() public function testAuthenticateWhenOneReturnsAuthenticationExceptionButNotAll() { + $expected = $this->createMock(TokenInterface::class); + $expected->expects($this->any())->method('getUser')->willReturn(new InMemoryUser('wouter', null)); + $manager = new AuthenticationProviderManager([ $this->getAuthenticationProvider(true, null, AuthenticationException::class), - $this->getAuthenticationProvider(true, $expected = $this->createMock(TokenInterface::class)), + $this->getAuthenticationProvider(true, $expected), ]); $token = $manager->authenticate($this->createMock(TokenInterface::class)); @@ -106,8 +110,10 @@ public function testAuthenticateReturnsTokenOfTheFirstMatchingProvider() ->expects($this->never()) ->method('supports') ; + $expected = $this->createMock(TokenInterface::class); + $expected->expects($this->any())->method('getUser')->willReturn(new InMemoryUser('wouter', null)); $manager = new AuthenticationProviderManager([ - $this->getAuthenticationProvider(true, $expected = $this->createMock(TokenInterface::class)), + $this->getAuthenticationProvider(true, $expected), $second, ]); diff --git a/src/Symfony/Component/Security/Core/Tests/Authentication/AuthenticationTrustResolverTest.php b/src/Symfony/Component/Security/Core/Tests/Authentication/AuthenticationTrustResolverTest.php index cd1924c63edf6..adb14975bc949 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authentication/AuthenticationTrustResolverTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/AuthenticationTrustResolverTest.php @@ -155,6 +155,10 @@ public function getUsername(): string { } + public function getUserIdentifier(): string + { + } + public function isAuthenticated(): bool { } diff --git a/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/DaoAuthenticationProviderTest.php b/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/DaoAuthenticationProviderTest.php index 46b5624bb215e..05340afa8537d 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/DaoAuthenticationProviderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/DaoAuthenticationProviderTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Security\Core\Tests\Authentication\Provider; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface; use Symfony\Component\PasswordHasher\Hasher\PlaintextPasswordHasher; use Symfony\Component\PasswordHasher\PasswordHasherInterface; @@ -20,8 +21,9 @@ use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; use Symfony\Component\Security\Core\Exception\AuthenticationServiceException; use Symfony\Component\Security\Core\Exception\BadCredentialsException; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; use Symfony\Component\Security\Core\User\InMemoryUser; +use Symfony\Component\Security\Core\User\InMemoryUserProvider; use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use Symfony\Component\Security\Core\User\UserCheckerInterface; use Symfony\Component\Security\Core\User\UserInterface; @@ -29,13 +31,25 @@ class DaoAuthenticationProviderTest extends TestCase { + use ExpectDeprecationTrait; + + /** + * @group legacy + */ public function testRetrieveUserWhenProviderDoesNotReturnAnUserInterface() { $this->expectException(AuthenticationServiceException::class); - $provider = $this->getProvider('fabien'); + $userProvider = $this->createMock(DaoAuthenticationProviderTest_UserProvider::class); + $userProvider->expects($this->once()) + ->method('loadUserByUsername') + ->willReturn('fabien') + ; + $provider = $this->getProvider(null, null, null, $userProvider); $method = new \ReflectionMethod($provider, 'retrieveUser'); $method->setAccessible(true); + $this->expectDeprecation('Since symfony/security-core 5.3: Not implementing method "loadUserByIdentifier()" in user provider "'.get_debug_type($userProvider).'" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.'); + $method->invoke($provider, 'fabien', $this->getSupportedToken()); } @@ -44,12 +58,8 @@ public function testRetrieveUserWhenProviderDoesNotReturnAnUserInterface() */ public function testRetrieveUserWhenUsernameIsNotFoundWithLegacyEncoderFactory() { - $this->expectException(UsernameNotFoundException::class); - $userProvider = $this->createMock(UserProviderInterface::class); - $userProvider->expects($this->once()) - ->method('loadUserByUsername') - ->willThrowException(new UsernameNotFoundException()) - ; + $this->expectException(UserNotFoundException::class); + $userProvider = new InMemoryUserProvider(); $provider = new DaoAuthenticationProvider($userProvider, $this->createMock(UserCheckerInterface::class), 'key', $this->createMock(EncoderFactoryInterface::class)); $method = new \ReflectionMethod($provider, 'retrieveUser'); @@ -60,12 +70,8 @@ public function testRetrieveUserWhenUsernameIsNotFoundWithLegacyEncoderFactory() public function testRetrieveUserWhenUsernameIsNotFound() { - $this->expectException(UsernameNotFoundException::class); - $userProvider = $this->createMock(UserProviderInterface::class); - $userProvider->expects($this->once()) - ->method('loadUserByUsername') - ->willThrowException(new UsernameNotFoundException()) - ; + $this->expectException(UserNotFoundException::class); + $userProvider = new InMemoryUserProvider(); $provider = new DaoAuthenticationProvider($userProvider, $this->createMock(UserCheckerInterface::class), 'key', $this->createMock(PasswordHasherFactoryInterface::class)); $method = new \ReflectionMethod($provider, 'retrieveUser'); @@ -77,9 +83,9 @@ public function testRetrieveUserWhenUsernameIsNotFound() public function testRetrieveUserWhenAnExceptionOccurs() { $this->expectException(AuthenticationServiceException::class); - $userProvider = $this->createMock(UserProviderInterface::class); + $userProvider = $this->createMock(InMemoryUserProvider::class); $userProvider->expects($this->once()) - ->method('loadUserByUsername') + ->method('loadUserByIdentifier') ->willThrowException(new \RuntimeException()) ; @@ -92,9 +98,9 @@ public function testRetrieveUserWhenAnExceptionOccurs() public function testRetrieveUserReturnsUserFromTokenOnReauthentication() { - $userProvider = $this->createMock(UserProviderInterface::class); + $userProvider = $this->createMock(InMemoryUserProvider::class); $userProvider->expects($this->never()) - ->method('loadUserByUsername') + ->method('loadUserByIdentifier') ; $user = new TestUser(); @@ -114,19 +120,13 @@ public function testRetrieveUserReturnsUserFromTokenOnReauthentication() public function testRetrieveUser() { - $user = new TestUser(); - - $userProvider = $this->createMock(UserProviderInterface::class); - $userProvider->expects($this->once()) - ->method('loadUserByUsername') - ->willReturn($user) - ; + $userProvider = new InMemoryUserProvider(['fabien' => []]); $provider = new DaoAuthenticationProvider($userProvider, $this->createMock(UserCheckerInterface::class), 'key', $this->createMock(PasswordHasherFactoryInterface::class)); $method = new \ReflectionMethod($provider, 'retrieveUser'); $method->setAccessible(true); - $this->assertSame($user, $method->invoke($provider, 'fabien', $this->getSupportedToken())); + $this->assertEquals('fabien', $method->invoke($provider, 'fabien', $this->getSupportedToken())->getUserIdentifier()); } public function testCheckAuthenticationWhenCredentialsAreEmpty() @@ -323,14 +323,16 @@ protected function getSupportedToken() return $mock; } - protected function getProvider($user = null, $userChecker = null, $passwordHasher = null) + protected function getProvider($user = null, $userChecker = null, $passwordHasher = null, $userProvider = null) { - $userProvider = $this->createMock(PasswordUpgraderProvider::class); - if (null !== $user) { - $userProvider->expects($this->once()) - ->method('loadUserByUsername') - ->willReturn($user) - ; + if (null === $userProvider) { + $userProvider = $this->createMock(PasswordUpgraderProvider::class); + if (null !== $user) { + $userProvider->expects($this->once()) + ->method('loadUserByIdentifier') + ->willReturn($user) + ; + } } if (null === $userChecker) { @@ -374,6 +376,11 @@ public function getUsername(): string return 'jane_doe'; } + public function getUserIdentifier(): string + { + return 'jane_doe'; + } + public function eraseCredentials() { } @@ -381,4 +388,10 @@ public function eraseCredentials() interface PasswordUpgraderProvider extends UserProviderInterface, PasswordUpgraderInterface { public function upgradePassword(UserInterface $user, string $newHashedPassword): void; + public function loadUserByIdentifier(string $identifier): UserInterface; +} + +interface DaoAuthenticationProviderTest_UserProvider extends UserProviderInterface +{ + public function loadUserByUsername($username); } 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 c47508449ffad..4507e6a9cfc72 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/LdapBindAuthenticationProviderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/LdapBindAuthenticationProviderTest.php @@ -21,6 +21,7 @@ use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Exception\BadCredentialsException; use Symfony\Component\Security\Core\User\InMemoryUser; +use Symfony\Component\Security\Core\User\InMemoryUserProvider; use Symfony\Component\Security\Core\User\UserCheckerInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; @@ -81,10 +82,10 @@ public function testBindFailureShouldThrowAnException() public function testRetrieveUser() { - $userProvider = $this->createMock(UserProviderInterface::class); + $userProvider = $this->createMock(InMemoryUserProvider::class); $userProvider ->expects($this->once()) - ->method('loadUserByUsername') + ->method('loadUserByIdentifier') ->with('foo') ; $ldap = $this->createMock(LdapInterface::class); diff --git a/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/PreAuthenticatedAuthenticationProviderTest.php b/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/PreAuthenticatedAuthenticationProviderTest.php index a0d6041327f73..15c079b8c398e 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/PreAuthenticatedAuthenticationProviderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/PreAuthenticatedAuthenticationProviderTest.php @@ -18,6 +18,7 @@ use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\BadCredentialsException; use Symfony\Component\Security\Core\Exception\LockedException; +use Symfony\Component\Security\Core\User\InMemoryUserProvider; use Symfony\Component\Security\Core\User\UserCheckerInterface; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; @@ -120,10 +121,10 @@ protected function getSupportedToken($user = false, $credentials = false) protected function getProvider($user = null, $userChecker = null) { - $userProvider = $this->createMock(UserProviderInterface::class); + $userProvider = $this->createMock(InMemoryUserProvider::class); if (null !== $user) { $userProvider->expects($this->once()) - ->method('loadUserByUsername') + ->method('loadUserByIdentifier') ->willReturn($user) ; } diff --git a/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/UserAuthenticationProviderTest.php b/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/UserAuthenticationProviderTest.php index f006d37ed1818..92b71448d5c7d 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/UserAuthenticationProviderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/UserAuthenticationProviderTest.php @@ -21,7 +21,8 @@ use Symfony\Component\Security\Core\Exception\AuthenticationServiceException; use Symfony\Component\Security\Core\Exception\BadCredentialsException; use Symfony\Component\Security\Core\Exception\CredentialsExpiredException; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; +use Symfony\Component\Security\Core\User\InMemoryUser; use Symfony\Component\Security\Core\User\UserCheckerInterface; use Symfony\Component\Security\Core\User\UserInterface; @@ -46,11 +47,11 @@ public function testAuthenticateWhenTokenIsNotSupported() public function testAuthenticateWhenUsernameIsNotFound() { - $this->expectException(UsernameNotFoundException::class); + $this->expectException(UserNotFoundException::class); $provider = $this->getProvider(false, false); $provider->expects($this->once()) ->method('retrieveUser') - ->willThrowException(new UsernameNotFoundException()) + ->willThrowException(new UserNotFoundException()) ; $provider->authenticate($this->getSupportedToken()); @@ -62,7 +63,7 @@ public function testAuthenticateWhenUsernameIsNotFoundAndHideIsTrue() $provider = $this->getProvider(false, true); $provider->expects($this->once()) ->method('retrieveUser') - ->willThrowException(new UsernameNotFoundException()) + ->willThrowException(new UserNotFoundException()) ; $provider->authenticate($this->getSupportedToken()); @@ -194,7 +195,7 @@ public function testAuthenticatePreservesOriginalToken() ; $originalToken = $this->createMock(TokenInterface::class); - $token = new SwitchUserToken($this->createMock(UserInterface::class), 'foo', 'key', [], $originalToken); + $token = new SwitchUserToken(new InMemoryUser('wouter', null), 'foo', 'key', [], $originalToken); $token->setAttributes(['foo' => 'bar']); $authToken = $provider->authenticate($token); diff --git a/src/Symfony/Component/Security/Core/Tests/Authentication/RememberMe/PersistentTokenTest.php b/src/Symfony/Component/Security/Core/Tests/Authentication/RememberMe/PersistentTokenTest.php index 12c133f52df57..9df545a4c0c6e 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authentication/RememberMe/PersistentTokenTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/RememberMe/PersistentTokenTest.php @@ -12,19 +12,33 @@ namespace Symfony\Component\Security\Core\Tests\Authentication\RememberMe; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken; class PersistentTokenTest extends TestCase { + use ExpectDeprecationTrait; + public function testConstructor() { $lastUsed = new \DateTime(); $token = new PersistentToken('fooclass', 'fooname', 'fooseries', 'footokenvalue', $lastUsed); $this->assertEquals('fooclass', $token->getClass()); - $this->assertEquals('fooname', $token->getUsername()); + $this->assertEquals('fooname', $token->getUserIdentifier()); $this->assertEquals('fooseries', $token->getSeries()); $this->assertEquals('footokenvalue', $token->getTokenValue()); $this->assertSame($lastUsed, $token->getLastUsed()); } + + /** + * @group legacy + */ + public function testLegacyGetUsername() + { + $token = new PersistentToken('fooclass', 'fooname', 'fooseries', 'footokenvalue', new \DateTime()); + + $this->expectDeprecation('Since symfony/security-core 5.3: Method "Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken::getUsername()" is deprecated, use getUserIdentifier() instead.'); + $this->assertEquals('fooname', $token->getUsername()); + } } diff --git a/src/Symfony/Component/Security/Core/Tests/Authentication/Token/AbstractTokenTest.php b/src/Symfony/Component/Security/Core/Tests/Authentication/Token/AbstractTokenTest.php index 98f84e1f1e077..dcf479c84e710 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authentication/Token/AbstractTokenTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/Token/AbstractTokenTest.php @@ -12,12 +12,19 @@ namespace Symfony\Component\Security\Core\Tests\Authentication\Token; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Security\Core\Authentication\Token\AbstractToken; +use Symfony\Component\Security\Core\User\InMemoryUser; use Symfony\Component\Security\Core\User\UserInterface; class AbstractTokenTest extends TestCase { - public function testGetUsername() + use ExpectDeprecationTrait; + + /** + * @group legacy + */ + public function testLegacyGetUsername() { $token = new ConcreteToken(['ROLE_FOO']); $token->setUser('fabien'); @@ -26,10 +33,43 @@ public function testGetUsername() $token->setUser(new TestUser('fabien')); $this->assertEquals('fabien', $token->getUsername()); - $user = $this->createMock(UserInterface::class); - $user->expects($this->once())->method('getUsername')->willReturn('fabien'); - $token->setUser($user); + $legacyUser = new class implements UserInterface { + public function getUsername() + { + return 'fabien'; + } + + public function getRoles() + {} + + public function getPassword() + {} + + public function getSalt() + {} + + public function eraseCredentials() + {} + }; + $token->setUser($legacyUser); $this->assertEquals('fabien', $token->getUsername()); + + $token->setUser($legacyUser); + $this->assertEquals('fabien', $token->getUserIdentifier()); + } + + public function testGetUserIdentifier() + { + $token = new ConcreteToken(['ROLE_FOO']); + $token->setUser('fabien'); + $this->assertEquals('fabien', $token->getUserIdentifier()); + + $token->setUser(new TestUser('fabien')); + $this->assertEquals('fabien', $token->getUserIdentifier()); + + $user = new InMemoryUser('fabien', null); + $token->setUser($user); + $this->assertEquals('fabien', $token->getUserIdentifier()); } public function testEraseCredentials() @@ -106,10 +146,8 @@ public function testSetUser($user) public function getUsers() { - $user = $this->createMock(UserInterface::class); - return [ - [$user], + [new InMemoryUser('foo', null)], [new TestUser('foo')], ['foo'], ]; @@ -210,6 +248,11 @@ public function getUsername() return $this->name; } + public function getUserIdentifier() + { + return $this->name; + } + public function getPassword() { return '***'; diff --git a/src/Symfony/Component/Security/Core/Tests/Authentication/Token/Storage/UsageTrackingTokenStorageTest.php b/src/Symfony/Component/Security/Core/Tests/Authentication/Token/Storage/UsageTrackingTokenStorageTest.php index c5d2eaf543203..38806efa8a20d 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authentication/Token/Storage/UsageTrackingTokenStorageTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/Token/Storage/UsageTrackingTokenStorageTest.php @@ -16,6 +16,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\Security\Core\Authentication\Token\NullToken; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; use Symfony\Component\Security\Core\Authentication\Token\Storage\UsageTrackingTokenStorage; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; @@ -46,7 +47,7 @@ public function testGetSetToken() $trackingStorage = new UsageTrackingTokenStorage($tokenStorage, $sessionLocator); $this->assertNull($trackingStorage->getToken()); - $token = $this->createMock(TokenInterface::class); + $token = new NullToken(); $trackingStorage->setToken($token); $this->assertSame($token, $trackingStorage->getToken()); diff --git a/src/Symfony/Component/Security/Core/Tests/Authentication/Token/SwitchUserTokenTest.php b/src/Symfony/Component/Security/Core/Tests/Authentication/Token/SwitchUserTokenTest.php index 8138f7659639b..477247e76da4f 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authentication/Token/SwitchUserTokenTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/Token/SwitchUserTokenTest.php @@ -26,7 +26,7 @@ public function testSerialize() $unserializedToken = unserialize(serialize($token)); $this->assertInstanceOf(SwitchUserToken::class, $unserializedToken); - $this->assertSame('admin', $unserializedToken->getUsername()); + $this->assertSame('admin', $unserializedToken->getUserIdentifier()); $this->assertSame('bar', $unserializedToken->getCredentials()); $this->assertSame('provider-key', $unserializedToken->getFirewallName()); $this->assertEquals(['ROLE_USER'], $unserializedToken->getRoleNames()); @@ -35,7 +35,7 @@ public function testSerialize() $unserializedOriginalToken = $unserializedToken->getOriginalToken(); $this->assertInstanceOf(UsernamePasswordToken::class, $unserializedOriginalToken); - $this->assertSame('user', $unserializedOriginalToken->getUsername()); + $this->assertSame('user', $unserializedOriginalToken->getUserIdentifier()); $this->assertSame('foo', $unserializedOriginalToken->getCredentials()); $this->assertSame('provider-key', $unserializedOriginalToken->getFirewallName()); $this->assertEquals(['ROLE_ADMIN', 'ROLE_ALLOWED_TO_SWITCH'], $unserializedOriginalToken->getRoleNames()); @@ -49,6 +49,11 @@ public function getUsername() return 'impersonated'; } + public function getUserIdentifier() + { + return 'impersonated'; + } + public function getPassword() { return null; @@ -92,7 +97,7 @@ public function testUnserializeOldToken() self::assertInstanceOf(SwitchUserToken::class, $token); self::assertInstanceOf(UsernamePasswordToken::class, $token->getOriginalToken()); - self::assertSame('john', $token->getUsername()); + self::assertSame('john', $token->getUserIdentifier()); self::assertSame(['foo' => 'bar'], $token->getCredentials()); self::assertSame('main', $token->getFirewallName()); self::assertEquals(['ROLE_USER'], $token->getRoleNames()); diff --git a/src/Symfony/Component/Security/Core/Tests/Encoder/EncoderFactoryTest.php b/src/Symfony/Component/Security/Core/Tests/Encoder/EncoderFactoryTest.php index 7b79986b826a6..3744e05bc0efa 100644 --- a/src/Symfony/Component/Security/Core/Tests/Encoder/EncoderFactoryTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Encoder/EncoderFactoryTest.php @@ -213,6 +213,10 @@ public function getUsername(): string { } + public function getUserIdentifier(): string + { + } + public function eraseCredentials() { } diff --git a/src/Symfony/Component/Security/Core/Tests/Exception/UserNotFoundExceptionTest.php b/src/Symfony/Component/Security/Core/Tests/Exception/UserNotFoundExceptionTest.php new file mode 100644 index 0000000000000..559e62acd97d0 --- /dev/null +++ b/src/Symfony/Component/Security/Core/Tests/Exception/UserNotFoundExceptionTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Tests\Exception; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; +use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; + +class UserNotFoundExceptionTest extends TestCase +{ + public function testGetMessageData() + { + $exception = new UserNotFoundException('Username could not be found.'); + $this->assertEquals(['{{ username }}' => null, '{{ user_identifier }}' => null], $exception->getMessageData()); + $exception->setUserIdentifier('username'); + $this->assertEquals(['{{ username }}' => 'username', '{{ user_identifier }}' => 'username'], $exception->getMessageData()); + } + + /** + * @group legacy + */ + public function testUsernameNotFoundException() + { + $exception = new UsernameNotFoundException(); + $this->assertInstanceOf(UserNotFoundException::class, $exception); + + $exception->setUsername('username'); + $this->assertEquals('username', $exception->getUserIdentifier()); + } +} diff --git a/src/Symfony/Component/Security/Core/Tests/Exception/UsernameNotFoundExceptionTest.php b/src/Symfony/Component/Security/Core/Tests/Exception/UsernameNotFoundExceptionTest.php deleted file mode 100644 index 8e256aac2ea11..0000000000000 --- a/src/Symfony/Component/Security/Core/Tests/Exception/UsernameNotFoundExceptionTest.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Core\Tests\Exception; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; - -class UsernameNotFoundExceptionTest extends TestCase -{ - public function testGetMessageData() - { - $exception = new UsernameNotFoundException('Username could not be found.'); - $this->assertEquals(['{{ username }}' => null], $exception->getMessageData()); - $exception->setUsername('username'); - $this->assertEquals(['{{ username }}' => 'username'], $exception->getMessageData()); - } -} diff --git a/src/Symfony/Component/Security/Core/Tests/User/ChainUserProviderTest.php b/src/Symfony/Component/Security/Core/Tests/User/ChainUserProviderTest.php index 74d0cc138ce6f..5a4770064fc2e 100644 --- a/src/Symfony/Component/Security/Core/Tests/User/ChainUserProviderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/User/ChainUserProviderTest.php @@ -13,9 +13,10 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; use Symfony\Component\Security\Core\User\ChainUserProvider; use Symfony\Component\Security\Core\User\InMemoryUser; +use Symfony\Component\Security\Core\User\InMemoryUserProvider; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use Symfony\Component\Security\Core\User\UserInterface; @@ -25,59 +26,59 @@ class ChainUserProviderTest extends TestCase { public function testLoadUserByUsername() { - $provider1 = $this->createMock(UserProviderInterface::class); + $provider1 = $this->createMock(InMemoryUserProvider::class); $provider1 ->expects($this->once()) - ->method('loadUserByUsername') + ->method('loadUserByIdentifier') ->with($this->equalTo('foo')) - ->willThrowException(new UsernameNotFoundException('not found')) + ->willThrowException(new UserNotFoundException('not found')) ; - $provider2 = $this->createMock(UserProviderInterface::class); + $provider2 = $this->createMock(InMemoryUserProvider::class); $provider2 ->expects($this->once()) - ->method('loadUserByUsername') + ->method('loadUserByIdentifier') ->with($this->equalTo('foo')) ->willReturn($account = $this->createMock(UserInterface::class)) ; $provider = new ChainUserProvider([$provider1, $provider2]); - $this->assertSame($account, $provider->loadUserByUsername('foo')); + $this->assertSame($account, $provider->loadUserByIdentifier('foo')); } - public function testLoadUserByUsernameThrowsUsernameNotFoundException() + public function testLoadUserByUsernameThrowsUserNotFoundException() { - $this->expectException(UsernameNotFoundException::class); - $provider1 = $this->createMock(UserProviderInterface::class); + $this->expectException(UserNotFoundException::class); + $provider1 = $this->createMock(InMemoryUserProvider::class); $provider1 ->expects($this->once()) - ->method('loadUserByUsername') + ->method('loadUserByIdentifier') ->with($this->equalTo('foo')) - ->willThrowException(new UsernameNotFoundException('not found')) + ->willThrowException(new UserNotFoundException('not found')) ; - $provider2 = $this->createMock(UserProviderInterface::class); + $provider2 = $this->createMock(InMemoryUserProvider::class); $provider2 ->expects($this->once()) - ->method('loadUserByUsername') + ->method('loadUserByIdentifier') ->with($this->equalTo('foo')) - ->willThrowException(new UsernameNotFoundException('not found')) + ->willThrowException(new UserNotFoundException('not found')) ; $provider = new ChainUserProvider([$provider1, $provider2]); - $provider->loadUserByUsername('foo'); + $provider->loadUserByIdentifier('foo'); } public function testRefreshUser() { - $provider1 = $this->createMock(UserProviderInterface::class); + $provider1 = $this->createMock(InMemoryUserProvider::class); $provider1 ->expects($this->once()) ->method('supportsClass') ->willReturn(false) ; - $provider2 = $this->createMock(UserProviderInterface::class); + $provider2 = $this->createMock(InMemoryUserProvider::class); $provider2 ->expects($this->once()) ->method('supportsClass') @@ -90,7 +91,7 @@ public function testRefreshUser() ->willThrowException(new UnsupportedUserException('unsupported')) ; - $provider3 = $this->createMock(UserProviderInterface::class); + $provider3 = $this->createMock(InMemoryUserProvider::class); $provider3 ->expects($this->once()) ->method('supportsClass') @@ -109,7 +110,7 @@ public function testRefreshUser() public function testRefreshUserAgain() { - $provider1 = $this->createMock(UserProviderInterface::class); + $provider1 = $this->createMock(InMemoryUserProvider::class); $provider1 ->expects($this->once()) ->method('supportsClass') @@ -119,10 +120,10 @@ public function testRefreshUserAgain() $provider1 ->expects($this->once()) ->method('refreshUser') - ->willThrowException(new UsernameNotFoundException('not found')) + ->willThrowException(new UserNotFoundException('not found')) ; - $provider2 = $this->createMock(UserProviderInterface::class); + $provider2 = $this->createMock(InMemoryUserProvider::class); $provider2 ->expects($this->once()) ->method('supportsClass') @@ -142,7 +143,7 @@ public function testRefreshUserAgain() public function testRefreshUserThrowsUnsupportedUserException() { $this->expectException(UnsupportedUserException::class); - $provider1 = $this->createMock(UserProviderInterface::class); + $provider1 = $this->createMock(InMemoryUserProvider::class); $provider1 ->expects($this->once()) ->method('supportsClass') @@ -155,7 +156,7 @@ public function testRefreshUserThrowsUnsupportedUserException() ->willThrowException(new UnsupportedUserException('unsupported')) ; - $provider2 = $this->createMock(UserProviderInterface::class); + $provider2 = $this->createMock(InMemoryUserProvider::class); $provider2 ->expects($this->once()) ->method('supportsClass') @@ -174,7 +175,7 @@ public function testRefreshUserThrowsUnsupportedUserException() public function testSupportsClass() { - $provider1 = $this->createMock(UserProviderInterface::class); + $provider1 = $this->createMock(InMemoryUserProvider::class); $provider1 ->expects($this->once()) ->method('supportsClass') @@ -182,7 +183,7 @@ public function testSupportsClass() ->willReturn(false) ; - $provider2 = $this->createMock(UserProviderInterface::class); + $provider2 = $this->createMock(InMemoryUserProvider::class); $provider2 ->expects($this->once()) ->method('supportsClass') @@ -196,7 +197,7 @@ public function testSupportsClass() public function testSupportsClassWhenNotSupported() { - $provider1 = $this->createMock(UserProviderInterface::class); + $provider1 = $this->createMock(InMemoryUserProvider::class); $provider1 ->expects($this->once()) ->method('supportsClass') @@ -204,7 +205,7 @@ public function testSupportsClassWhenNotSupported() ->willReturn(false) ; - $provider2 = $this->createMock(UserProviderInterface::class); + $provider2 = $this->createMock(InMemoryUserProvider::class); $provider2 ->expects($this->once()) ->method('supportsClass') @@ -218,7 +219,7 @@ public function testSupportsClassWhenNotSupported() public function testAcceptsTraversable() { - $provider1 = $this->createMock(UserProviderInterface::class); + $provider1 = $this->createMock(InMemoryUserProvider::class); $provider1 ->expects($this->once()) ->method('supportsClass') @@ -231,7 +232,7 @@ public function testAcceptsTraversable() ->willThrowException(new UnsupportedUserException('unsupported')) ; - $provider2 = $this->createMock(UserProviderInterface::class); + $provider2 = $this->createMock(InMemoryUserProvider::class); $provider2 ->expects($this->once()) ->method('supportsClass') diff --git a/src/Symfony/Component/Security/Core/Tests/User/InMemoryUserProviderTest.php b/src/Symfony/Component/Security/Core/Tests/User/InMemoryUserProviderTest.php index f9d27c8f844a3..d4d4964c7be59 100644 --- a/src/Symfony/Component/Security/Core/Tests/User/InMemoryUserProviderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/User/InMemoryUserProviderTest.php @@ -13,7 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; use Symfony\Component\Security\Core\User\InMemoryUser; use Symfony\Component\Security\Core\User\InMemoryUserProvider; use Symfony\Component\Security\Core\User\User; @@ -26,7 +26,7 @@ public function testConstructor() { $provider = $this->createProvider(); - $user = $provider->loadUserByUsername('fabien'); + $user = $provider->loadUserByIdentifier('fabien'); $this->assertEquals('foo', $user->getPassword()); $this->assertEquals(['ROLE_USER'], $user->getRoles()); $this->assertFalse($user->isEnabled()); @@ -76,7 +76,7 @@ public function testCreateUser() $provider = new InMemoryUserProvider(); $provider->createUser(new InMemoryUser('fabien', 'foo')); - $user = $provider->loadUserByUsername('fabien'); + $user = $provider->loadUserByIdentifier('fabien'); $this->assertEquals('foo', $user->getPassword()); } @@ -90,8 +90,8 @@ public function testCreateUserAlreadyExist() public function testLoadUserByUsernameDoesNotExist() { - $this->expectException(UsernameNotFoundException::class); + $this->expectException(UserNotFoundException::class); $provider = new InMemoryUserProvider(); - $provider->loadUserByUsername('fabien'); + $provider->loadUserByIdentifier('fabien'); } } diff --git a/src/Symfony/Component/Security/Core/Tests/User/InMemoryUserTest.php b/src/Symfony/Component/Security/Core/Tests/User/InMemoryUserTest.php index 885d1f73c0690..a5496ef325b85 100644 --- a/src/Symfony/Component/Security/Core/Tests/User/InMemoryUserTest.php +++ b/src/Symfony/Component/Security/Core/Tests/User/InMemoryUserTest.php @@ -12,12 +12,15 @@ namespace Symfony\Component\Security\Core\Tests\User; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Security\Core\User\EquatableInterface; use Symfony\Component\Security\Core\User\InMemoryUser; use Symfony\Component\Security\Core\User\UserInterface; class InMemoryUserTest extends TestCase { + use ExpectDeprecationTrait; + public function testConstructorException() { $this->expectException(\InvalidArgumentException::class); @@ -39,12 +42,23 @@ public function testGetPassword() $this->assertEquals('superpass', $user->getPassword()); } + /** + * @group legacy + */ public function testGetUsername() { $user = new InMemoryUser('fabien', 'superpass'); + + $this->expectDeprecation('Since symfony/security-core 5.3: Method "Symfony\Component\Security\Core\User\User::getUsername()" is deprecated, use getUserIdentifier() instead.'); $this->assertEquals('fabien', $user->getUsername()); } + public function testGetUserIdentifier() + { + $user = new InMemoryUser('fabien', 'superpass'); + $this->assertEquals('fabien', $user->getUserIdentifier()); + } + public function testGetSalt() { $user = new InMemoryUser('fabien', 'superpass'); diff --git a/src/Symfony/Component/Security/Core/Tests/User/UserTest.php b/src/Symfony/Component/Security/Core/Tests/User/UserTest.php index 143479de7940d..81b8705d1041f 100644 --- a/src/Symfony/Component/Security/Core/Tests/User/UserTest.php +++ b/src/Symfony/Component/Security/Core/Tests/User/UserTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Security\Core\Tests\User; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Security\Core\User\EquatableInterface; use Symfony\Component\Security\Core\User\User; use Symfony\Component\Security\Core\User\UserInterface; @@ -21,6 +22,8 @@ */ class UserTest extends TestCase { + use ExpectDeprecationTrait; + public function testConstructorException() { $this->expectException(\InvalidArgumentException::class); @@ -42,12 +45,23 @@ public function testGetPassword() $this->assertEquals('superpass', $user->getPassword()); } + /** + * @group legacy + */ public function testGetUsername() { $user = new User('fabien', 'superpass'); + + $this->expectDeprecation('Since symfony/security-core 5.3: Method "Symfony\Component\Security\Core\User\User::getUsername()" is deprecated, use getUserIdentifier() instead.'); $this->assertEquals('fabien', $user->getUsername()); } + public function testGetUserIdentifier() + { + $user = new User('fabien', 'superpass'); + $this->assertEquals('fabien', $user->getUserIdentifier()); + } + public function testGetSalt() { $user = new User('fabien', 'superpass'); diff --git a/src/Symfony/Component/Security/Core/User/ChainUserProvider.php b/src/Symfony/Component/Security/Core/User/ChainUserProvider.php index fedcdb6a53c10..35207d62cc924 100644 --- a/src/Symfony/Component/Security/Core/User/ChainUserProvider.php +++ b/src/Symfony/Component/Security/Core/User/ChainUserProvider.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Security\Core\User; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; /** * Chain User Provider. @@ -50,17 +50,31 @@ public function getProviders() * {@inheritdoc} */ public function loadUserByUsername(string $username) + { + trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, use loadUserByIdentifier() instead.', __METHOD__); + + return $this->loadUserByIdentifier($username); + } + + public function loadUserByIdentifier(string $userIdentifier): UserInterface { foreach ($this->providers as $provider) { try { - return $provider->loadUserByUsername($username); - } catch (UsernameNotFoundException $e) { + // @deprecated since 5.3, change to $provider->loadUserByIdentifier() in 6.0 + if (!method_exists($provider, 'loadUserByIdentifier')) { + trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', \get_debug_type($provider)); + + return $provider->loadUserByUsername($userIdentifier); + } + + return $provider->loadUserByIdentifier($userIdentifier); + } catch (UserNotFoundException $e) { // try next one } } - $ex = new UsernameNotFoundException(sprintf('There is no user with name "%s".', $username)); - $ex->setUsername($username); + $ex = new UserNotFoundException(sprintf('There is no user with identifier "%s".', $userIdentifier)); + $ex->setUserIdentifier($userIdentifier); throw $ex; } @@ -80,15 +94,17 @@ public function refreshUser(UserInterface $user) return $provider->refreshUser($user); } catch (UnsupportedUserException $e) { // try next one - } catch (UsernameNotFoundException $e) { + } catch (UserNotFoundException $e) { $supportedUserFound = true; // try next one } } if ($supportedUserFound) { - $e = new UsernameNotFoundException(sprintf('There is no user with name "%s".', $user->getUsername())); - $e->setUsername($user->getUsername()); + // @deprecated since 5.3, change to $user->getUserIdentifier() in 6.0 + $username = method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername(); + $e = new UserNotFoundException(sprintf('There is no user with name "%s".', $username)); + $e->setUserIdentifier($username); throw $e; } else { throw new UnsupportedUserException(sprintf('There is no user provider for user "%s". Shouldn\'t the "supportsClass()" method of your user provider return true for this classname?', get_debug_type($user))); diff --git a/src/Symfony/Component/Security/Core/User/InMemoryUserProvider.php b/src/Symfony/Component/Security/Core/User/InMemoryUserProvider.php index c79f96e2542ee..2e9ea5a27f675 100644 --- a/src/Symfony/Component/Security/Core/User/InMemoryUserProvider.php +++ b/src/Symfony/Component/Security/Core/User/InMemoryUserProvider.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Security\Core\User; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; /** * InMemoryUserProvider is a simple non persistent user provider. @@ -51,11 +51,13 @@ public function __construct(array $users = []) */ public function createUser(UserInterface $user) { - if (isset($this->users[strtolower($user->getUsername())])) { + // @deprecated since 5.3, change to $user->getUserIdentifier() in 6.0 + $userIdentifier = strtolower(method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername()); + if (isset($this->users[$userIdentifier])) { throw new \LogicException('Another user with the same username already exists.'); } - $this->users[strtolower($user->getUsername())] = $user; + $this->users[$userIdentifier] = $user; } /** @@ -63,9 +65,17 @@ public function createUser(UserInterface $user) */ public function loadUserByUsername(string $username) { - $user = $this->getUser($username); + trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, use loadUserByIdentifier() instead.', __METHOD__); - return new InMemoryUser($user->getUsername(), $user->getPassword(), $user->getRoles(), $user->isEnabled()); + return $this->loadUserByIdentifier($username); + } + + public function loadUserByIdentifier(string $identifier): UserInterface + { + $user = $this->getUser($identifier); + + // @deprecated since 5.3, change to $user->getUserIdentifier() in 6.0 + return new InMemoryUser(method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername(), $user->getPassword(), $user->getRoles(), $user->isEnabled()); } /** @@ -77,7 +87,9 @@ public function refreshUser(UserInterface $user) throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_debug_type($user))); } - $storedUser = $this->getUser($user->getUsername()); + // @deprecated since 5.3, change to $user->getUserIdentifier() in 6.0 + $storedUser = $this->getUser(method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername()); + $userIdentifier = method_exists($storedUser, 'getUserIdentifier') ? $storedUser->getUserIdentifier() : $storedUser->getUsername(); // @deprecated since Symfony 5.3 if (User::class === \get_class($user)) { @@ -91,10 +103,10 @@ public function refreshUser(UserInterface $user) $accountNonLocked = $storedUser->isAccountNonLocked(); } - return new User($storedUser->getUsername(), $storedUser->getPassword(), $storedUser->getRoles(), $storedUser->isEnabled(), $accountNonExpired, $credentialsNonExpired, $accountNonLocked); + return new User($userIdentifier, $storedUser->getPassword(), $storedUser->getRoles(), $storedUser->isEnabled(), $accountNonExpired, $credentialsNonExpired, $accountNonLocked); } - return new InMemoryUser($storedUser->getUsername(), $storedUser->getPassword(), $storedUser->getRoles(), $storedUser->isEnabled()); + return new InMemoryUser($userIdentifier, $storedUser->getPassword(), $storedUser->getRoles(), $storedUser->isEnabled()); } /** @@ -113,13 +125,13 @@ public function supportsClass(string $class) /** * Returns the user by given username. * - * @throws UsernameNotFoundException if user whose given username does not exist + * @throws UserNotFoundException if user whose given username does not exist */ private function getUser(string $username)/*: InMemoryUser */ { if (!isset($this->users[strtolower($username)])) { - $ex = new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username)); - $ex->setUsername($username); + $ex = new UserNotFoundException(sprintf('Username "%s" does not exist.', $username)); + $ex->setUserIdentifier($username); throw $ex; } diff --git a/src/Symfony/Component/Security/Core/User/MissingUserProvider.php b/src/Symfony/Component/Security/Core/User/MissingUserProvider.php index 605aad6dfc74b..02df05169e9b8 100644 --- a/src/Symfony/Component/Security/Core/User/MissingUserProvider.php +++ b/src/Symfony/Component/Security/Core/User/MissingUserProvider.php @@ -37,6 +37,11 @@ public function loadUserByUsername(string $username): UserInterface throw new \BadMethodCallException(); } + public function loadUserByIdentifier(string $identifier): UserInterface + { + throw new \BadMethodCallException(); + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Security/Core/User/User.php b/src/Symfony/Component/Security/Core/User/User.php index 045f03bff2554..d583e5a8cbd4f 100644 --- a/src/Symfony/Component/Security/Core/User/User.php +++ b/src/Symfony/Component/Security/Core/User/User.php @@ -53,7 +53,7 @@ public function __construct(?string $username, ?string $password, array $roles = public function __toString(): string { - return $this->getUsername(); + return $this->getUserIdentifier(); } /** @@ -84,6 +84,16 @@ public function getSalt(): ?string * {@inheritdoc} */ public function getUsername(): string + { + trigger_deprecation('symfony/security-core', '5.3', 'Method "%s()" is deprecated, use getUserIdentifier() instead.', __METHOD__); + + return $this->username; + } + + /** + * Returns the identifier for this user (e.g. its username or e-mailaddress). + */ + public function getUserIdentifier(): string { return $this->username; } @@ -184,7 +194,7 @@ public function isEqualTo(UserInterface $user): bool return false; } - if ($this->getUsername() !== $user->getUsername()) { + if ($this->getUserIdentifier() !== $user->getUserIdentifier()) { return false; } diff --git a/src/Symfony/Component/Security/Core/User/UserInterface.php b/src/Symfony/Component/Security/Core/User/UserInterface.php index 47661de0b7317..6448ab519248d 100644 --- a/src/Symfony/Component/Security/Core/User/UserInterface.php +++ b/src/Symfony/Component/Security/Core/User/UserInterface.php @@ -26,6 +26,8 @@ * * @see UserProviderInterface * + * @method string getUserIdentifier() returns the identifier for this user (e.g. its username or e-mailaddress) + * * @author Fabien Potencier */ interface UserInterface @@ -69,13 +71,6 @@ public function getPassword(); */ public function getSalt(); - /** - * Returns the username used to authenticate the user. - * - * @return string The username - */ - public function getUsername(); - /** * Removes sensitive data from the user. * diff --git a/src/Symfony/Component/Security/Core/User/UserProviderInterface.php b/src/Symfony/Component/Security/Core/User/UserProviderInterface.php index 708a97f4be404..5ab67836c730f 100644 --- a/src/Symfony/Component/Security/Core/User/UserProviderInterface.php +++ b/src/Symfony/Component/Security/Core/User/UserProviderInterface.php @@ -12,16 +12,16 @@ namespace Symfony\Component\Security\Core\User; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; /** * Represents a class that loads UserInterface objects from some source for the authentication system. * - * In a typical authentication configuration, a username (i.e. some unique - * user identifier) credential enters the system (via form login, or any - * method). The user provider that is configured with that authentication - * method is asked to load the UserInterface object for the given username - * (via loadUserByUsername) so that the rest of the process can continue. + * In a typical authentication configuration, a user identifier (e.g. a + * username or e-mailaddress) credential enters the system (via form login, or + * any method). The user provider that is configured with that authentication + * method is asked to load the UserInterface object for the given identifier (via + * loadUserByIdentifier) so that the rest of the process can continue. * * Internally, a user provider can load users from any source (databases, * configuration, web service). This is totally independent of how the authentication @@ -29,22 +29,13 @@ * * @see UserInterface * + * @method UserInterface loadUserByIdentifier(string $identifier) loads the user for the given user identifier (e.g. username or email). + * This method must throw UserNotFoundException if the user is not found. + * * @author Fabien Potencier */ interface UserProviderInterface { - /** - * Loads the user for the given username. - * - * This method must throw UsernameNotFoundException if the user is not - * found. - * - * @return UserInterface - * - * @throws UsernameNotFoundException if the user is not found - */ - public function loadUserByUsername(string $username); - /** * Refreshes the user. * @@ -55,8 +46,8 @@ public function loadUserByUsername(string $username); * * @return UserInterface * - * @throws UnsupportedUserException if the user is not supported - * @throws UsernameNotFoundException if the user is not found + * @throws UnsupportedUserException if the user is not supported + * @throws UserNotFoundException if the user is not found */ public function refreshUser(UserInterface $user); diff --git a/src/Symfony/Component/Security/Guard/Authenticator/GuardBridgeAuthenticator.php b/src/Symfony/Component/Security/Guard/Authenticator/GuardBridgeAuthenticator.php index 8e678227806fa..b3384111008df 100644 --- a/src/Symfony/Component/Security/Guard/Authenticator/GuardBridgeAuthenticator.php +++ b/src/Symfony/Component/Security/Guard/Authenticator/GuardBridgeAuthenticator.php @@ -15,7 +15,7 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use Symfony\Component\Security\Core\User\UserInterface; @@ -98,7 +98,7 @@ private function getUser($credentials): UserInterface $user = $this->guard->getUser($credentials, $this->userProvider); if (null === $user) { - throw new UsernameNotFoundException(sprintf('Null returned from "%s::getUser()".', get_debug_type($this->guard))); + throw new UserNotFoundException(sprintf('Null returned from "%s::getUser()".', get_debug_type($this->guard))); } if (!$user instanceof UserInterface) { diff --git a/src/Symfony/Component/Security/Guard/AuthenticatorInterface.php b/src/Symfony/Component/Security/Guard/AuthenticatorInterface.php index 36c1be3115855..870ba387822a3 100644 --- a/src/Symfony/Component/Security/Guard/AuthenticatorInterface.php +++ b/src/Symfony/Component/Security/Guard/AuthenticatorInterface.php @@ -70,7 +70,7 @@ public function getCredentials(Request $request); * The *credentials* are the return value from getCredentials() * * You may throw an AuthenticationException if you wish. If you return - * null, then a UsernameNotFoundException is thrown for you. + * null, then a UserNotFoundException is thrown for you. * * @param mixed $credentials * diff --git a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php index 71d663e3dcf85..880d275f68e65 100644 --- a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php +++ b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php @@ -18,7 +18,7 @@ use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\AuthenticationExpiredException; use Symfony\Component\Security\Core\Exception\BadCredentialsException; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use Symfony\Component\Security\Core\User\UserCheckerInterface; @@ -112,8 +112,9 @@ private function authenticateViaGuard(AuthenticatorInterface $guardAuthenticator $user = $guardAuthenticator->getUser($token->getCredentials(), $this->userProvider); if (null === $user) { - $e = new UsernameNotFoundException(sprintf('Null returned from "%s::getUser()".', get_debug_type($guardAuthenticator))); - $e->setUsername($token->getUsername()); + $e = new UserNotFoundException(sprintf('Null returned from "%s::getUser()".', get_debug_type($guardAuthenticator))); + // @deprecated since 5.3, change to $token->getUserIdentifier() in 6.0 + $e->setUserIdentifier(method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername()); throw $e; } diff --git a/src/Symfony/Component/Security/Guard/Tests/Authenticator/GuardBridgeAuthenticatorTest.php b/src/Symfony/Component/Security/Guard/Tests/Authenticator/GuardBridgeAuthenticatorTest.php index 678e1f17ceb7c..24a4d71c509d5 100644 --- a/src/Symfony/Component/Security/Guard/Tests/Authenticator/GuardBridgeAuthenticatorTest.php +++ b/src/Symfony/Component/Security/Guard/Tests/Authenticator/GuardBridgeAuthenticatorTest.php @@ -15,7 +15,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Exception\AuthenticationException; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; use Symfony\Component\Security\Core\User\InMemoryUser; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Guard\Authenticator\GuardBridgeAuthenticator; @@ -97,7 +97,7 @@ public function testAuthenticate() public function testAuthenticateNoUser() { - $this->expectException(UsernameNotFoundException::class); + $this->expectException(UserNotFoundException::class); $request = new Request(); diff --git a/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php b/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php index 501a9e2fb1a3c..b5e609c74196b 100644 --- a/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php +++ b/src/Symfony/Component/Security/Http/Authentication/AuthenticatorManager.php @@ -70,7 +70,8 @@ public function __construct(iterable $authenticators, TokenStorageInterface $tok public function authenticateUser(UserInterface $user, AuthenticatorInterface $authenticator, Request $request, array $badges = []): ?Response { // create an authenticated token for the User - $token = $authenticator->createAuthenticatedToken($passport = new SelfValidatingPassport(new UserBadge($user->getUsername(), function () use ($user) { return $user; }), $badges), $this->firewallName); + // @deprecated since 5.3, change to $user->getUserIdentifier() in 6.0 + $token = $authenticator->createAuthenticatedToken($passport = new SelfValidatingPassport(new UserBadge(method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername(), function () use ($user) { return $user; }), $badges), $this->firewallName); // announce the authenticated token $token = $this->eventDispatcher->dispatch(new AuthenticationTokenCreatedEvent($token))->getAuthenticatedToken(); @@ -215,6 +216,11 @@ private function executeAuthenticator(AuthenticatorInterface $authenticator, Req private function handleAuthenticationSuccess(TokenInterface $authenticatedToken, PassportInterface $passport, Request $request, AuthenticatorInterface $authenticator): ?Response { + // @deprecated since 5.3 + if (!method_exists($authenticatedToken->getUser(), 'getUserIdentifier')) { + trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "getUserIdentifier(): string" in user class "%s" is deprecated. This method will replace "getUsername()" in Symfony 6.0.', get_debug_type($authenticatedToken->getUser())); + } + $this->tokenStorage->setToken($authenticatedToken); $response = $authenticator->onAuthenticationSuccess($request, $authenticatedToken, $this->firewallName); diff --git a/src/Symfony/Component/Security/Http/Authenticator/AbstractPreAuthenticatedAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/AbstractPreAuthenticatedAuthenticator.php index 8ccd356ca1d09..cb8470f4e856c 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/AbstractPreAuthenticatedAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/AbstractPreAuthenticatedAuthenticator.php @@ -87,9 +87,18 @@ public function supports(Request $request): ?bool public function authenticate(Request $request): PassportInterface { - return new SelfValidatingPassport(new UserBadge($request->attributes->get('_pre_authenticated_username'), function ($username) { - return $this->userProvider->loadUserByUsername($username); - }), [new PreAuthenticatedUserBadge()]); + // @deprecated since 5.3, change to $this->userProvider->loadUserByIdentifier() in 6.0 + $method = 'loadUserByIdentifier'; + if (!method_exists($this->userProvider, 'loadUserByIdentifier')) { + trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', get_debug_type($this->userProvider)); + + $method = 'loadUserByUsername'; + } + + return new SelfValidatingPassport( + new UserBadge($request->attributes->get('_pre_authenticated_username'), [$this->userProvider, $method]), + [new PreAuthenticatedUserBadge()] + ); } public function createAuthenticatedToken(PassportInterface $passport, string $firewallName): TokenInterface diff --git a/src/Symfony/Component/Security/Http/Authenticator/AuthenticatorInterface.php b/src/Symfony/Component/Security/Http/Authenticator/AuthenticatorInterface.php index 0d7e9cb7bb6f4..d6ec043d15cfc 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/AuthenticatorInterface.php +++ b/src/Symfony/Component/Security/Http/Authenticator/AuthenticatorInterface.php @@ -46,7 +46,7 @@ public function supports(Request $request): ?bool; * presented password and the CSRF token value. * * You may throw any AuthenticationException in this method in case of error (e.g. - * a UsernameNotFoundException when the user cannot be found). + * a UserNotFoundException when the user cannot be found). * * @throws AuthenticationException */ diff --git a/src/Symfony/Component/Security/Http/Authenticator/FormLoginAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/FormLoginAuthenticator.php index 234b2dc0a3d5a..965cbfa4ec99b 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/FormLoginAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/FormLoginAuthenticator.php @@ -84,14 +84,19 @@ public function authenticate(Request $request): PassportInterface { $credentials = $this->getCredentials($request); - $passport = new Passport(new UserBadge($credentials['username'], function ($username) { - $user = $this->userProvider->loadUserByUsername($username); - if (!$user instanceof UserInterface) { - throw new AuthenticationServiceException('The user provider must return a UserInterface object.'); - } - - return $user; - }), new PasswordCredentials($credentials['password']), [new RememberMeBadge()]); + // @deprecated since 5.3, change to $this->userProvider->loadUserByIdentifier() in 6.0 + $method = 'loadUserByIdentifier'; + if (!method_exists($this->userProvider, 'loadUserByIdentifier')) { + trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', get_debug_type($this->userProvider)); + + $method = 'loadUserByUsername'; + } + + $passport = new Passport( + new UserBadge($credentials['username'], [$this->userProvider, $method]), + new PasswordCredentials($credentials['password']), + [new RememberMeBadge()] + ); if ($this->options['enable_csrf']) { $passport->addBadge(new CsrfTokenBadge($this->options['csrf_token_id'], $credentials['csrf_token'])); } diff --git a/src/Symfony/Component/Security/Http/Authenticator/HttpBasicAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/HttpBasicAuthenticator.php index 179e1fdd1a94c..c184de8477747 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/HttpBasicAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/HttpBasicAuthenticator.php @@ -67,14 +67,18 @@ public function authenticate(Request $request): PassportInterface $username = $request->headers->get('PHP_AUTH_USER'); $password = $request->headers->get('PHP_AUTH_PW', ''); - $passport = new Passport(new UserBadge($username, function ($username) { - $user = $this->userProvider->loadUserByUsername($username); - if (!$user instanceof UserInterface) { - throw new AuthenticationServiceException('The user provider must return a UserInterface object.'); - } - - return $user; - }), new PasswordCredentials($password)); + // @deprecated since 5.3, change to $this->userProvider->loadUserByIdentifier() in 6.0 + $method = 'loadUserByIdentifier'; + if (!method_exists($this->userProvider, 'loadUserByIdentifier')) { + trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', get_debug_type($this->userProvider)); + + $method = 'loadUserByUsername'; + } + + $passport = new Passport( + new UserBadge($username, [$this->userProvider, $method]), + new PasswordCredentials($password) + ); if ($this->userProvider instanceof PasswordUpgraderInterface) { $passport->addBadge(new PasswordUpgradeBadge($password, $this->userProvider)); } diff --git a/src/Symfony/Component/Security/Http/Authenticator/JsonLoginAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/JsonLoginAuthenticator.php index 69ef4d28f5969..8e5d3afd5c920 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/JsonLoginAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/JsonLoginAuthenticator.php @@ -94,14 +94,18 @@ public function authenticate(Request $request): PassportInterface throw $e; } - $passport = new Passport(new UserBadge($credentials['username'], function ($username) { - $user = $this->userProvider->loadUserByUsername($username); - if (!$user instanceof UserInterface) { - throw new AuthenticationServiceException('The user provider must return a UserInterface object.'); - } + // @deprecated since 5.3, change to $this->userProvider->loadUserByIdentifier() in 6.0 + $method = 'loadUserByIdentifier'; + if (!method_exists($this->userProvider, 'loadUserByIdentifier')) { + trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', get_debug_type($this->userProvider)); + + $method = 'loadUserByUsername'; + } - return $user; - }), new PasswordCredentials($credentials['password'])); + $passport = new Passport( + new UserBadge($credentials['username'], [$this->userProvider, $method]), + new PasswordCredentials($credentials['password']) + ); if ($this->userProvider instanceof PasswordUpgraderInterface) { $passport->addBadge(new PasswordUpgradeBadge($credentials['password'], $this->userProvider)); } diff --git a/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/UserBadge.php b/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/UserBadge.php index a58f86cd13e4c..41e14141f71f8 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/UserBadge.php +++ b/src/Symfony/Component/Security/Http/Authenticator/Passport/Badge/UserBadge.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Security\Http\Authenticator\Passport\Badge; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\Exception\AuthenticationServiceException; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Http\EventListener\UserProviderListener; @@ -40,7 +40,7 @@ class UserBadge implements BadgeInterface * based on email *and* company name). This string can be used for e.g. login throttling. * * Optionally, you may pass a user loader. This callable receives the $userIdentifier - * as argument and must return a UserInterface object (otherwise a UsernameNotFoundException + * as argument and must return a UserInterface object (otherwise an AuthenticationServiceException * is thrown). If this is not set, the default user provider will be used with * $userIdentifier as username. */ @@ -64,7 +64,7 @@ public function getUser(): UserInterface $this->user = ($this->userLoader)($this->userIdentifier); if (!$this->user instanceof UserInterface) { - throw new UsernameNotFoundException(); + throw new AuthenticationServiceException(sprintf('The user provider must return a UserInterface object, "%s" given.', \get_debug_type($this->user))); } } diff --git a/src/Symfony/Component/Security/Http/Authenticator/RememberMeAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/RememberMeAuthenticator.php index bc7ecc3996407..b663a275062dc 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/RememberMeAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/RememberMeAuthenticator.php @@ -82,7 +82,8 @@ public function authenticate(Request $request): PassportInterface throw new \LogicException('No remember me token is set.'); } - return new SelfValidatingPassport(new UserBadge($token->getUsername(), [$token, 'getUser'])); + // @deprecated since 5.3, change to $token->getUserIdentifier() in 6.0 + return new SelfValidatingPassport(new UserBadge(method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername(), [$token, 'getUser'])); } public function createAuthenticatedToken(PassportInterface $passport, string $firewallName): TokenInterface diff --git a/src/Symfony/Component/Security/Http/EventListener/UserProviderListener.php b/src/Symfony/Component/Security/Http/EventListener/UserProviderListener.php index 715ae675c0bef..21fbf97daafad 100644 --- a/src/Symfony/Component/Security/Http/EventListener/UserProviderListener.php +++ b/src/Symfony/Component/Security/Http/EventListener/UserProviderListener.php @@ -46,6 +46,13 @@ public function checkPassport(CheckPassportEvent $event): void return; } - $badge->setUserLoader([$this->userProvider, 'loadUserByUsername']); + // @deprecated since 5.3, change to $this->userProvider->loadUserByIdentifier() in 6.0 + if (method_exists($this->userProvider, 'loadUserByIdentifier')) { + $badge->setUserLoader([$this->userProvider, 'loadUserByIdentifier']); + } else { + trigger_deprecation('symfony/security-http', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', get_debug_type($this->userProvider)); + + $badge->setUserLoader([$this->userProvider, 'loadUserByUsername']); + } } } diff --git a/src/Symfony/Component/Security/Http/Firewall/AbstractAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/AbstractAuthenticationListener.php index 1e4405e629158..616a4efdd7b9b 100644 --- a/src/Symfony/Component/Security/Http/Firewall/AbstractAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/AbstractAuthenticationListener.php @@ -195,7 +195,8 @@ private function onFailure(Request $request, AuthenticationException $failed): R private function onSuccess(Request $request, TokenInterface $token): Response { if (null !== $this->logger) { - $this->logger->info('User has been authenticated successfully.', ['username' => $token->getUsername()]); + // @deprecated since 5.3, change to $token->getUserIdentifier() in 6.0 + $this->logger->info('User has been authenticated successfully.', ['username' => method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername()]); } $this->tokenStorage->setToken($token); diff --git a/src/Symfony/Component/Security/Http/Firewall/AbstractPreAuthenticatedListener.php b/src/Symfony/Component/Security/Http/Firewall/AbstractPreAuthenticatedListener.php index 2cd905157b70b..7f8fcc1d82136 100644 --- a/src/Symfony/Component/Security/Http/Firewall/AbstractPreAuthenticatedListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/AbstractPreAuthenticatedListener.php @@ -83,7 +83,8 @@ public function authenticate(RequestEvent $event) } if (null !== $token = $this->tokenStorage->getToken()) { - if ($token instanceof PreAuthenticatedToken && $this->providerKey == $token->getFirewallName() && $token->isAuthenticated() && $token->getUsername() === $user) { + // @deprecated since 5.3, change to $token->getUserIdentifier() in 6.0 + if ($token instanceof PreAuthenticatedToken && $this->providerKey == $token->getFirewallName() && $token->isAuthenticated() && (method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername()) === $user) { return; } } diff --git a/src/Symfony/Component/Security/Http/Firewall/BasicAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/BasicAuthenticationListener.php index a9ef56705fdd0..bf18dc168753b 100644 --- a/src/Symfony/Component/Security/Http/Firewall/BasicAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/BasicAuthenticationListener.php @@ -73,7 +73,8 @@ public function authenticate(RequestEvent $event) } if (null !== $token = $this->tokenStorage->getToken()) { - if ($token instanceof UsernamePasswordToken && $token->isAuthenticated() && $token->getUsername() === $username) { + // @deprecated since 5.3, change to $token->getUserIdentifier() in 6.0 + if ($token instanceof UsernamePasswordToken && $token->isAuthenticated() && (method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername()) === $username) { return; } } diff --git a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php index a3511067ac9b6..6b3984687b7f4 100644 --- a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php @@ -27,7 +27,7 @@ use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Http\Event\DeauthenticatedEvent; @@ -222,7 +222,8 @@ protected function refreshUser(TokenInterface $token): ?TokenInterface $userDeauthenticated = true; if (null !== $this->logger) { - $this->logger->debug('Cannot refresh token because user has changed.', ['username' => $refreshedUser->getUsername(), 'provider' => \get_class($provider)]); + // @deprecated since 5.3, change to $refreshedUser->getUserIdentifier() in 6.0 + $this->logger->debug('Cannot refresh token because user has changed.', ['username' => method_exists($refreshedUser, 'getUserIdentifier') ? $refreshedUser->getUserIdentifier() : $refreshedUser->getUsername(), 'provider' => \get_class($provider)]); } continue; @@ -231,10 +232,12 @@ protected function refreshUser(TokenInterface $token): ?TokenInterface $token->setUser($refreshedUser); if (null !== $this->logger) { - $context = ['provider' => \get_class($provider), 'username' => $refreshedUser->getUsername()]; + // @deprecated since 5.3, change to $refreshedUser->getUserIdentifier() in 6.0 + $context = ['provider' => \get_class($provider), 'username' => method_exists($refreshedUser, 'getUserIdentifier') ? $refreshedUser->getUserIdentifier() : $refreshedUser->getUsername()]; if ($token instanceof SwitchUserToken) { - $context['impersonator_username'] = $token->getOriginalToken()->getUsername(); + // @deprecated since 5.3, change to $token->getUserIdentifier() in 6.0 + $context['impersonator_username'] = method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getOriginalToken()->getUsername(); } $this->logger->debug('User was reloaded from a user provider.', $context); @@ -243,9 +246,9 @@ protected function refreshUser(TokenInterface $token): ?TokenInterface return $token; } catch (UnsupportedUserException $e) { // let's try the next user provider - } catch (UsernameNotFoundException $e) { + } catch (UserNotFoundException $e) { if (null !== $this->logger) { - $this->logger->warning('Username could not be found in the selected user provider.', ['username' => $e->getUsername(), 'provider' => \get_class($provider)]); + $this->logger->warning('Username could not be found in the selected user provider.', ['username' => method_exists($e, 'getUserIdentifier') ? $e->getUserIdentifier() : $e->getUsername(), 'provider' => \get_class($provider)]); } $userNotFoundByProvider = true; diff --git a/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php b/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php index 15b794c0a7503..b765b89da7d18 100644 --- a/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php @@ -140,7 +140,8 @@ private function attemptSwitchUser(Request $request, string $username): ?TokenIn $originalToken = $this->getOriginalToken($token); if (null !== $originalToken) { - if ($token->getUsername() === $username) { + // @deprecated since 5.3, change to $token->getUserIdentifier() in 6.0 + if ((method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername()) === $username) { return $token; } @@ -148,20 +149,27 @@ private function attemptSwitchUser(Request $request, string $username): ?TokenIn $token = $this->attemptExitUser($request); } - $currentUsername = $token->getUsername(); + // @deprecated since 5.3, change to $token->getUserIdentifier() in 6.0 + $currentUsername = method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername(); $nonExistentUsername = '_'.md5(random_bytes(8).$username); // To protect against user enumeration via timing measurements // we always load both successfully and unsuccessfully + $methodName = 'loadUserByIdentifier'; + if (!method_exists($this->provider, $methodName)) { + trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', get_debug_type($this->provider)); + + $methodName = 'loadUserByUsername'; + } try { - $user = $this->provider->loadUserByUsername($username); + $user = $this->provider->$methodName($username); try { - $this->provider->loadUserByUsername($nonExistentUsername); + $this->provider->$methodName($nonExistentUsername); } catch (\Exception $e) { } } catch (AuthenticationException $e) { - $this->provider->loadUserByUsername($currentUsername); + $this->provider->$methodName($currentUsername); throw $e; } diff --git a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php index 9276e7f2ceb9e..5029cb95361c3 100644 --- a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordJsonAuthenticationListener.php @@ -151,7 +151,8 @@ public function authenticate(RequestEvent $event) private function onSuccess(Request $request, TokenInterface $token): ?Response { if (null !== $this->logger) { - $this->logger->info('User has been authenticated successfully.', ['username' => $token->getUsername()]); + // @deprecated since 5.3, change to $token->getUserIdentifier() in 6.0 + $this->logger->info('User has been authenticated successfully.', ['username' => method_exists($token, 'getUserIdentifier') ? $token->getUserIdentifier() : $token->getUsername()]); } $this->migrateSession($request, $token); diff --git a/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandler.php b/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandler.php index 1f4ddcc1ddc11..bfa67d6b6eb2a 100644 --- a/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandler.php +++ b/src/Symfony/Component/Security/Http/LoginLink/LoginLinkHandler.php @@ -15,7 +15,7 @@ use Symfony\Component\PropertyAccess\PropertyAccessorInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Routing\RequestContext; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Http\LoginLink\Exception\ExpiredLoginLinkException; @@ -56,7 +56,8 @@ public function createLoginLink(UserInterface $user, Request $request = null): L $expires = $expiresAt->format('U'); $parameters = [ - 'user' => $user->getUsername(), + // @deprecated since 5.3, change to $user->getUserIdentifier() in 6.0 + 'user' => method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername(), 'expires' => $expires, 'hash' => $this->computeSignatureHash($user, $expires), ]; @@ -83,11 +84,18 @@ public function createLoginLink(UserInterface $user, Request $request = null): L public function consumeLoginLink(Request $request): UserInterface { - $username = $request->get('user'); + $userIdentifier = $request->get('user'); try { - $user = $this->userProvider->loadUserByUsername($username); - } catch (UsernameNotFoundException $exception) { + // @deprecated since 5.3, change to $this->userProvider->loadUserByIdentifier() in 6.0 + if (method_exists($this->userProvider, 'loadUserByIdentifier')) { + $user = $this->userProvider->loadUserByIdentifier($userIdentifier); + } else { + trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', get_debug_type($this->userProvider)); + + $user = $this->userProvider->loadUserByUsername($userIdentifier); + } + } catch (UserNotFoundException $exception) { throw new InvalidLoginLinkException('User not found.', 0, $exception); } @@ -115,7 +123,8 @@ public function consumeLoginLink(Request $request): UserInterface private function computeSignatureHash(UserInterface $user, int $expires): string { - $signatureFields = [base64_encode($user->getUsername()), $expires]; + // @deprecated since 5.3, change to $user->getUserIdentifier() in 6.0 + $signatureFields = [base64_encode(method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername()), $expires]; foreach ($this->signatureProperties as $property) { $value = $this->propertyAccessor->getValue($user, $property) ?? ''; diff --git a/src/Symfony/Component/Security/Http/RememberMe/AbstractRememberMeServices.php b/src/Symfony/Component/Security/Http/RememberMe/AbstractRememberMeServices.php index 295e7d80a4951..aa2ab934d13b9 100644 --- a/src/Symfony/Component/Security/Http/RememberMe/AbstractRememberMeServices.php +++ b/src/Symfony/Component/Security/Http/RememberMe/AbstractRememberMeServices.php @@ -20,7 +20,7 @@ use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\CookieTheftException; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Http\Logout\LogoutHandlerInterface; @@ -40,6 +40,8 @@ abstract class AbstractRememberMeServices implements RememberMeServicesInterface 'secure' => false, 'httponly' => true, 'samesite' => null, + 'path' => null, + 'domain' => null, ]; private $firewallName; private $secret; @@ -128,7 +130,7 @@ final public function autoLogin(Request $request): ?TokenInterface $this->loginFail($request, $e); throw $e; - } catch (UsernameNotFoundException $e) { + } catch (UserNotFoundException $e) { if (null !== $this->logger) { $this->logger->info('User for remember-me cookie not found.', ['exception' => $e]); } diff --git a/src/Symfony/Component/Security/Http/RememberMe/PersistentTokenBasedRememberMeServices.php b/src/Symfony/Component/Security/Http/RememberMe/PersistentTokenBasedRememberMeServices.php index 5711f0f5acca8..c7376faa910d9 100644 --- a/src/Symfony/Component/Security/Http/RememberMe/PersistentTokenBasedRememberMeServices.php +++ b/src/Symfony/Component/Security/Http/RememberMe/PersistentTokenBasedRememberMeServices.php @@ -93,7 +93,24 @@ protected function processAutoLoginCookie(array $cookieParts, Request $request) ) ); - return $this->getUserProvider($persistentToken->getClass())->loadUserByUsername($persistentToken->getUsername()); + $userProvider = $this->getUserProvider($persistentToken->getClass()); + // @deprecated since 5.3, change to $persistentToken->getUserIdentifier() in 6.0 + if (method_exists($persistentToken, 'getUserIdentifier')) { + $userIdentifier = $persistentToken->getUserIdentifier(); + } else { + trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "getUserIdentifier()" in persistent token "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', get_debug_type($persistentToken)); + + $userIdentifier = $persistentToken->getUsername(); + } + + // @deprecated since 5.3, change to $userProvider->loadUserByIdentifier() in 6.0 + if (method_exists($userProvider, 'loadUserByIdentifier')) { + return $userProvider->loadUserByIdentifier($userIdentifier); + } else { + trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', get_debug_type($userProvider)); + + return $userProvider->loadUserByUsername($userIdentifier); + } } /** @@ -107,7 +124,8 @@ protected function onLoginSuccess(Request $request, Response $response, TokenInt $this->tokenProvider->createNewToken( new PersistentToken( \get_class($user = $token->getUser()), - $user->getUsername(), + // @deprecated since 5.3, change to $user->getUserIdentifier() in 6.0 + method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername(), $series, $this->generateHash($tokenValue), new \DateTime() diff --git a/src/Symfony/Component/Security/Http/RememberMe/TokenBasedRememberMeServices.php b/src/Symfony/Component/Security/Http/RememberMe/TokenBasedRememberMeServices.php index 80e0b058157e2..a6e79f42e56d7 100644 --- a/src/Symfony/Component/Security/Http/RememberMe/TokenBasedRememberMeServices.php +++ b/src/Symfony/Component/Security/Http/RememberMe/TokenBasedRememberMeServices.php @@ -35,12 +35,20 @@ protected function processAutoLoginCookie(array $cookieParts, Request $request) throw new AuthenticationException('The cookie is invalid.'); } - [$class, $username, $expires, $hash] = $cookieParts; - if (false === $username = base64_decode($username, true)) { - throw new AuthenticationException('$username contains a character from outside the base64 alphabet.'); + [$class, $userIdentifier, $expires, $hash] = $cookieParts; + if (false === $userIdentifier = base64_decode($userIdentifier, true)) { + throw new AuthenticationException('$userIdentifier contains a character from outside the base64 alphabet.'); } try { - $user = $this->getUserProvider($class)->loadUserByUsername($username); + $userProvider = $this->getUserProvider($class); + // @deprecated since 5.3, change to $userProvider->loadUserByIdentifier() in 6.0 + if (method_exists($userProvider, 'loadUserByIdentifier')) { + $user = $userProvider->loadUserByIdentifier($userIdentifier); + } else { + trigger_deprecation('symfony/security-core', '5.3', 'Not implementing method "loadUserByIdentifier()" in user provider "%s" is deprecated. This method will replace "loadUserByUsername()" in Symfony 6.0.', get_debug_type($userProvider)); + + $user = $userProvider->loadUserByUsername($userIdentifier); + } } catch (\Exception $e) { if (!$e instanceof AuthenticationException) { $e = new AuthenticationException($e->getMessage(), $e->getCode(), $e); @@ -53,7 +61,7 @@ protected function processAutoLoginCookie(array $cookieParts, Request $request) throw new \RuntimeException(sprintf('The UserProviderInterface implementation must return an instance of UserInterface, but returned "%s".', get_debug_type($user))); } - if (true !== hash_equals($this->generateCookieHash($class, $username, $expires, $user->getPassword()), $hash)) { + if (true !== hash_equals($this->generateCookieHash($class, $userIdentifier, $expires, $user->getPassword()), $hash)) { throw new AuthenticationException('The cookie\'s hash is invalid.'); } @@ -71,7 +79,8 @@ protected function onLoginSuccess(Request $request, Response $response, TokenInt { $user = $token->getUser(); $expires = time() + $this->options['lifetime']; - $value = $this->generateCookieValue(\get_class($user), $user->getUsername(), $expires, $user->getPassword()); + // @deprecated since 5.3, change to $user->getUserIdentifier() in 6.0 + $value = $this->generateCookieValue(\get_class($user), method_exists($user, 'getUserIdentifier') ? $user->getUserIdentifier() : $user->getUsername(), $expires, $user->getPassword()); $response->headers->setCookie( new Cookie( @@ -96,15 +105,15 @@ protected function onLoginSuccess(Request $request, Response $response, TokenInt * * @return string */ - protected function generateCookieValue(string $class, string $username, int $expires, ?string $password) + protected function generateCookieValue(string $class, string $userIdentifier, int $expires, ?string $password) { - // $username is encoded because it might contain COOKIE_DELIMITER, + // $userIdentifier is encoded because it might contain COOKIE_DELIMITER, // we assume other values don't return $this->encodeCookie([ $class, - base64_encode($username), + base64_encode($userIdentifier), $expires, - $this->generateCookieHash($class, $username, $expires, $password), + $this->generateCookieHash($class, $userIdentifier, $expires, $password), ]); } @@ -116,8 +125,8 @@ protected function generateCookieValue(string $class, string $username, int $exp * * @return string */ - protected function generateCookieHash(string $class, string $username, int $expires, ?string $password) + protected function generateCookieHash(string $class, string $userIdentifier, int $expires, ?string $password) { - return hash_hmac('sha256', $class.self::COOKIE_DELIMITER.$username.self::COOKIE_DELIMITER.$expires.self::COOKIE_DELIMITER.$password, $this->getSecret()); + return hash_hmac('sha256', $class.self::COOKIE_DELIMITER.$userIdentifier.self::COOKIE_DELIMITER.$expires.self::COOKIE_DELIMITER.$password, $this->getSecret()); } } diff --git a/src/Symfony/Component/Security/Http/Tests/Authentication/AuthenticatorManagerTest.php b/src/Symfony/Component/Security/Http/Tests/Authentication/AuthenticatorManagerTest.php index 969243f83e862..89f0ddf07a10a 100644 --- a/src/Symfony/Component/Security/Http/Tests/Authentication/AuthenticatorManagerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Authentication/AuthenticatorManagerTest.php @@ -44,6 +44,7 @@ protected function setUp(): void $this->request = new Request(); $this->user = new InMemoryUser('wouter', null); $this->token = $this->createMock(TokenInterface::class); + $this->token->expects($this->any())->method('getUser')->willReturn($this->user); $this->response = $this->createMock(Response::class); } @@ -166,6 +167,7 @@ public function testAuthenticateRequestCanModifyTokenFromEvent() $authenticator->expects($this->any())->method('createAuthenticatedToken')->willReturn($this->token); $modifiedToken = $this->createMock(TokenInterface::class); + $modifiedToken->expects($this->any())->method('getUser')->willReturn($this->user); $listenerCalled = false; $this->eventDispatcher->addListener(AuthenticationTokenCreatedEvent::class, function (AuthenticationTokenCreatedEvent $event) use (&$listenerCalled, $modifiedToken) { $event->setAuthenticatedToken($modifiedToken); @@ -198,6 +200,7 @@ public function testAuthenticateUserCanModifyTokenFromEvent() $authenticator->expects($this->any())->method('onAuthenticationSuccess')->willReturn($this->response); $modifiedToken = $this->createMock(TokenInterface::class); + $modifiedToken->expects($this->any())->method('getUser')->willReturn($this->user); $listenerCalled = false; $this->eventDispatcher->addListener(AuthenticationTokenCreatedEvent::class, function (AuthenticationTokenCreatedEvent $event) use (&$listenerCalled, $modifiedToken) { $event->setAuthenticatedToken($modifiedToken); diff --git a/src/Symfony/Component/Security/Http/Tests/Authenticator/JsonLoginAuthenticatorTest.php b/src/Symfony/Component/Security/Http/Tests/Authenticator/JsonLoginAuthenticatorTest.php index 1f86aa648abe7..47e02689ead93 100644 --- a/src/Symfony/Component/Security/Http/Tests/Authenticator/JsonLoginAuthenticatorTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Authenticator/JsonLoginAuthenticatorTest.php @@ -17,7 +17,7 @@ use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\BadCredentialsException; use Symfony\Component\Security\Core\Security; -use Symfony\Component\Security\Core\User\UserProviderInterface; +use Symfony\Component\Security\Core\User\InMemoryUserProvider; use Symfony\Component\Security\Http\Authenticator\JsonLoginAuthenticator; use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials; use Symfony\Component\Security\Http\HttpUtils; @@ -32,7 +32,7 @@ class JsonLoginAuthenticatorTest extends TestCase protected function setUp(): void { - $this->userProvider = $this->createMock(UserProviderInterface::class); + $this->userProvider = new InMemoryUserProvider(); } /** diff --git a/src/Symfony/Component/Security/Http/Tests/Authenticator/X509AuthenticatorTest.php b/src/Symfony/Component/Security/Http/Tests/Authenticator/X509AuthenticatorTest.php index 0acf418068078..7ee6aaa973894 100644 --- a/src/Symfony/Component/Security/Http/Tests/Authenticator/X509AuthenticatorTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Authenticator/X509AuthenticatorTest.php @@ -48,7 +48,7 @@ public function testAuthentication($username, $credentials) $this->userProvider->createUser(new InMemoryUser($username, null)); $passport = $this->authenticator->authenticate($request); - $this->assertEquals($username, $passport->getUser()->getUsername()); + $this->assertEquals($username, $passport->getUser()->getUserIdentifier()); } public static function provideServerVars() @@ -69,7 +69,7 @@ public function testAuthenticationNoUser($emailAddress, $credentials) $this->userProvider->createUser(new InMemoryUser($emailAddress, null)); $passport = $this->authenticator->authenticate($request); - $this->assertEquals($emailAddress, $passport->getUser()->getUsername()); + $this->assertEquals($emailAddress, $passport->getUser()->getUserIdentifier()); } public static function provideServerVarsNoUser() @@ -102,7 +102,7 @@ public function testAuthenticationCustomUserKey() $this->userProvider->createUser(new InMemoryUser('TheUser', null)); $passport = $this->authenticator->authenticate($request); - $this->assertEquals('TheUser', $passport->getUser()->getUsername()); + $this->assertEquals('TheUser', $passport->getUser()->getUserIdentifier()); } public function testAuthenticationCustomCredentialsKey() @@ -117,7 +117,7 @@ public function testAuthenticationCustomCredentialsKey() $this->userProvider->createUser(new InMemoryUser('cert@example.com', null)); $passport = $authenticator->authenticate($request); - $this->assertEquals('cert@example.com', $passport->getUser()->getUsername()); + $this->assertEquals('cert@example.com', $passport->getUser()->getUserIdentifier()); } private function createRequest(array $server) diff --git a/src/Symfony/Component/Security/Http/Tests/EventListener/LoginThrottlingListenerTest.php b/src/Symfony/Component/Security/Http/Tests/EventListener/LoginThrottlingListenerTest.php index 1374fc39b0a4a..bd53763273b50 100644 --- a/src/Symfony/Component/Security/Http/Tests/EventListener/LoginThrottlingListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/EventListener/LoginThrottlingListenerTest.php @@ -90,7 +90,7 @@ private function createPassport($username) private function createLoginSuccessfulEvent($passport, $username = 'wouter') { $token = $this->createMock(TokenInterface::class); - $token->expects($this->any())->method('getUsername')->willReturn($username); + $token->expects($this->any())->method('getUserIdentifier')->willReturn($username); return new LoginSuccessEvent($this->createMock(AuthenticatorInterface::class), $passport, $token, $this->requestStack->getCurrentRequest(), null, 'main'); } diff --git a/src/Symfony/Component/Security/Http/Tests/EventListener/PasswordMigratingListenerTest.php b/src/Symfony/Component/Security/Http/Tests/EventListener/PasswordMigratingListenerTest.php index b347276b0f673..e07b502a80c35 100644 --- a/src/Symfony/Component/Security/Http/Tests/EventListener/PasswordMigratingListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/EventListener/PasswordMigratingListenerTest.php @@ -99,14 +99,14 @@ public function testUpgradeWithUpgrader() public function testUpgradeWithoutUpgrader() { $userLoader = $this->getMockForAbstractClass(TestMigratingUserProvider::class); - $userLoader->expects($this->any())->method('loadUserByUsername')->willReturn($this->user); + $userLoader->expects($this->any())->method('loadUserByIdentifier')->willReturn($this->user); $userLoader->expects($this->once()) ->method('upgradePassword') ->with($this->user, 'new-hash') ; - $event = $this->createEvent(new SelfValidatingPassport(new UserBadge('test', [$userLoader, 'loadUserByUsername']), [new PasswordUpgradeBadge('pa$$word')])); + $event = $this->createEvent(new SelfValidatingPassport(new UserBadge('test', [$userLoader, 'loadUserByIdentifier']), [new PasswordUpgradeBadge('pa$$word')])); $this->listener->onLoginSuccess($event); } @@ -124,6 +124,7 @@ private function createEvent(PassportInterface $passport) abstract class TestMigratingUserProvider implements UserProviderInterface, PasswordUpgraderInterface { abstract public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void; + abstract public function loadUserByIdentifier(string $identifier): UserInterface; } abstract class TestPasswordAuthenticatedUser implements UserInterface, PasswordAuthenticatedUserInterface diff --git a/src/Symfony/Component/Security/Http/Tests/EventListener/UserProviderListenerTest.php b/src/Symfony/Component/Security/Http/Tests/EventListener/UserProviderListenerTest.php index 8901b95658896..1f5a65ac0b926 100644 --- a/src/Symfony/Component/Security/Http/Tests/EventListener/UserProviderListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/EventListener/UserProviderListenerTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Security\Http\Tests\EventListener; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Security\Core\User\InMemoryUser; use Symfony\Component\Security\Core\User\InMemoryUserProvider; use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface; @@ -22,6 +23,8 @@ class UserProviderListenerTest extends TestCase { + use ExpectDeprecationTrait; + private $userProvider; private $listener; @@ -38,7 +41,7 @@ public function testSetUserProvider() $this->listener->checkPassport(new CheckPassportEvent($this->createMock(AuthenticatorInterface::class), $passport)); $badge = $passport->getBadge(UserBadge::class); - $this->assertEquals([$this->userProvider, 'loadUserByUsername'], $badge->getUserLoader()); + $this->assertEquals([$this->userProvider, 'loadUserByIdentifier'], $badge->getUserLoader()); $user = new InMemoryUser('wouter', null); $this->userProvider->createUser($user); diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/AccessListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/AccessListenerTest.php index 525456b59ebd4..aac67379b4e7f 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/AccessListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/AccessListenerTest.php @@ -338,12 +338,7 @@ public function testHandleMWithultipleAttributesShouldBeHandledAsAnd() ->willReturn([['foo' => 'bar', 'bar' => 'baz'], null]) ; - $authenticatedToken = $this->createMock(TokenInterface::class); - $authenticatedToken - ->expects($this->any()) - ->method('isAuthenticated') - ->willReturn(true) - ; + $authenticatedToken = new UsernamePasswordToken('test', 'test', 'test', ['ROLE_USER']); $tokenStorage = new TokenStorage(); $tokenStorage->setToken($authenticatedToken); diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php index a48c8f7cb0515..6a612cdb80928 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/ContextListenerTest.php @@ -31,7 +31,7 @@ use Symfony\Component\Security\Core\Authentication\Token\Storage\UsageTrackingTokenStorage; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; use Symfony\Component\Security\Core\User\InMemoryUser; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; @@ -69,7 +69,7 @@ public function testOnKernelResponseWillAddSession() $token = unserialize($session->get('_security_session')); $this->assertInstanceOf(UsernamePasswordToken::class, $token); - $this->assertEquals('test1', $token->getUsername()); + $this->assertEquals('test1', $token->getUserIdentifier()); } public function testOnKernelResponseWillReplaceSession() @@ -81,7 +81,7 @@ public function testOnKernelResponseWillReplaceSession() $token = unserialize($session->get('_security_session')); $this->assertInstanceOf(UsernamePasswordToken::class, $token); - $this->assertEquals('test1', $token->getUsername()); + $this->assertEquals('test1', $token->getUserIdentifier()); } public function testOnKernelResponseWillRemoveSession() @@ -478,7 +478,12 @@ public function __construct($throwsUnsupportedException) public function loadUserByUsername($username): UserInterface { - throw new UsernameNotFoundException(); + throw new UserNotFoundException(); + } + + public function loadUserByIdentifier(string $identifier): UserInterface + { + throw new UserNotFoundException(); } public function refreshUser(UserInterface $user): UserInterface @@ -509,6 +514,10 @@ public function loadUserByUsername($username): UserInterface { } + public function loadUserByIdentifier(string $identifier): UserInterface + { + } + public function refreshUser(UserInterface $user): UserInterface { if (!$user instanceof InMemoryUser) { @@ -516,7 +525,7 @@ public function refreshUser(UserInterface $user): UserInterface } if (null === $this->refreshedUser) { - throw new UsernameNotFoundException(); + throw new UserNotFoundException(); } return $this->refreshedUser; diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/SwitchUserListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/SwitchUserListenerTest.php index 9e5adefab7036..64cc07e15d6b3 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/SwitchUserListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/SwitchUserListenerTest.php @@ -22,7 +22,6 @@ use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; use Symfony\Component\Security\Core\User\InMemoryUser; use Symfony\Component\Security\Core\User\InMemoryUserProvider; use Symfony\Component\Security\Core\User\UserCheckerInterface; @@ -200,11 +199,11 @@ public function testSwitchUser() $this->request->query->set('_switch_user', 'kuba'); $this->accessDecisionManager->expects($this->once()) - ->method('decide')->with($token, ['ROLE_ALLOWED_TO_SWITCH'], $this->callback(function ($user) { return 'kuba' === $user->getUsername(); })) + ->method('decide')->with($token, ['ROLE_ALLOWED_TO_SWITCH'], $this->callback(function ($user) { return 'kuba' === $user->getUserIdentifier(); })) ->willReturn(true); $this->userChecker->expects($this->once()) - ->method('checkPostAuth')->with($this->callback(function ($user) { return 'kuba' === $user->getUsername(); })); + ->method('checkPostAuth')->with($this->callback(function ($user) { return 'kuba' === $user->getUserIdentifier(); })); $listener = new SwitchUserListener($this->tokenStorage, $this->userProvider, $this->userChecker, 'provider123', $this->accessDecisionManager); $listener($this->event); @@ -222,11 +221,9 @@ public function testSwitchUserAlreadySwitched() $tokenStorage = new TokenStorage(); $tokenStorage->setToken($alreadySwitchedToken); - $targetUser = new InMemoryUser('kuba', 'password', ['ROLE_FOO', 'ROLE_BAR']); - $this->request->query->set('_switch_user', 'kuba'); - $targetsUser = $this->callback(function ($user) { return 'kuba' === $user->getUsername(); }); + $targetsUser = $this->callback(function ($user) { return 'kuba' === $user->getUserIdentifier(); }); $this->accessDecisionManager->expects($this->once()) ->method('decide')->with($originalToken, ['ROLE_ALLOWED_TO_SWITCH'], $targetsUser) ->willReturn(true); @@ -240,7 +237,7 @@ public function testSwitchUserAlreadySwitched() $this->assertSame([], $this->request->query->all()); $this->assertSame('', $this->request->server->get('QUERY_STRING')); $this->assertInstanceOf(SwitchUserToken::class, $tokenStorage->getToken()); - $this->assertSame('kuba', $tokenStorage->getToken()->getUsername()); + $this->assertSame('kuba', $tokenStorage->getToken()->getUserIdentifier()); $this->assertSame($originalToken, $tokenStorage->getToken()->getOriginalToken()); } @@ -279,7 +276,7 @@ public function testSwitchUserKeepsOtherQueryStringParameters() 'section' => 2, ]); - $targetsUser = $this->callback(function ($user) { return 'kuba' === $user->getUsername(); }); + $targetsUser = $this->callback(function ($user) { return 'kuba' === $user->getUserIdentifier(); }); $this->accessDecisionManager->expects($this->once()) ->method('decide')->with($token, ['ROLE_ALLOWED_TO_SWITCH'], $targetsUser) ->willReturn(true); @@ -306,7 +303,7 @@ public function testSwitchUserWithReplacedToken() $this->request->query->set('_switch_user', 'kuba'); $this->accessDecisionManager->expects($this->any()) - ->method('decide')->with($token, ['ROLE_ALLOWED_TO_SWITCH'], $this->callback(function ($user) { return 'kuba' === $user->getUsername(); })) + ->method('decide')->with($token, ['ROLE_ALLOWED_TO_SWITCH'], $this->callback(function ($user) { return 'kuba' === $user->getUserIdentifier(); })) ->willReturn(true); $dispatcher = $this->createMock(EventDispatcherInterface::class); @@ -314,8 +311,8 @@ public function testSwitchUserWithReplacedToken() ->expects($this->once()) ->method('dispatch') ->with( - $this->callback(function (SwitchUserEvent $event) use ($replacedToken) { - if ('kuba' !== $event->getTargetUser()->getUsername()) { + $this->callback(function (SwitchUserEvent $event) use ($replacedToken, $user) { + if ('kuba' !== $event->getTargetUser()->getUserIdentifier()) { return false; } $event->setToken($replacedToken); @@ -343,12 +340,11 @@ public function testSwitchUserThrowsAuthenticationExceptionIfNoCurrentToken() public function testSwitchUserStateless() { $token = new UsernamePasswordToken('username', '', 'key', ['ROLE_FOO']); - $user = new InMemoryUser('username', 'password', []); $this->tokenStorage->setToken($token); $this->request->query->set('_switch_user', 'kuba'); - $targetsUser = $this->callback(function ($user) { return 'kuba' === $user->getUsername(); }); + $targetsUser = $this->callback(function ($user) { return 'kuba' === $user->getUserIdentifier(); }); $this->accessDecisionManager->expects($this->once()) ->method('decide')->with($token, ['ROLE_ALLOWED_TO_SWITCH'], $targetsUser) ->willReturn(true); diff --git a/src/Symfony/Component/Security/Http/Tests/LoginLink/LoginLinkHandlerTest.php b/src/Symfony/Component/Security/Http/Tests/LoginLink/LoginLinkHandlerTest.php index 9d063f6630290..74050396ac077 100644 --- a/src/Symfony/Component/Security/Http/Tests/LoginLink/LoginLinkHandlerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/LoginLink/LoginLinkHandlerTest.php @@ -18,7 +18,7 @@ use Symfony\Component\PropertyAccess\PropertyAccessorInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Routing\RequestContext; -use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\Exception\UserNotFoundException; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Http\LoginLink\Exception\ExpiredLoginLinkException; @@ -208,13 +208,13 @@ public function createUser(TestLoginLinkHandlerUser $user): void $this->users[$user->getUsername()] = $user; } - public function loadUserByUsername(string $username): TestLoginLinkHandlerUser + public function loadUserByIdentifier(string $userIdentifier): TestLoginLinkHandlerUser { - if (!isset($this->users[$username])) { - throw new UsernameNotFoundException(); + if (!isset($this->users[$userIdentifier])) { + throw new UserNotFoundException(); } - return clone $this->users[$username]; + return clone $this->users[$userIdentifier]; } public function refreshUser(UserInterface $user) @@ -263,6 +263,11 @@ public function getUsername() return $this->username; } + public function getUserIdentifier() + { + return $this->username; + } + public function eraseCredentials() { }