From d7aaa615b98270ff5b5c801e8a6a82ac8f6730d8 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 6 Mar 2017 18:12:20 +0100 Subject: [PATCH] deprecate the Role and SwitchUserRole classes --- UPGRADE-4.3.md | 7 + UPGRADE-5.0.md | 5 + .../Monolog/Processor/TokenProcessor.php | 8 +- .../Tests/Processor/TokenProcessorTest.php | 3 +- .../DataCollector/SecurityDataCollector.php | 32 +++- .../Resources/config/security.xml | 1 + .../SecurityDataCollectorTest.php | 70 ++++---- .../Bundle/SecurityBundle/composer.json | 2 +- src/Symfony/Component/Security/CHANGELOG.md | 7 + .../Provider/UserAuthenticationProvider.php | 10 +- .../Authentication/Token/AbstractToken.php | 23 ++- .../Authentication/Token/AnonymousToken.php | 4 +- .../Token/PreAuthenticatedToken.php | 10 +- .../Token/Storage/TokenStorage.php | 4 + .../Authentication/Token/SwitchUserToken.php | 60 +++++++ .../Authentication/Token/TokenInterface.php | 4 + .../Token/UsernamePasswordToken.php | 10 +- .../Authorization/Voter/ExpressionVoter.php | 26 ++- .../Voter/RoleHierarchyVoter.php | 19 ++- .../Core/Authorization/Voter/RoleVoter.php | 10 +- .../Component/Security/Core/Role/Role.php | 11 ++ .../Security/Core/Role/RoleHierarchy.php | 33 ++++ .../Core/Role/RoleHierarchyInterface.php | 2 + .../Security/Core/Role/SwitchUserRole.php | 17 +- ...uthenticatedAuthenticationProviderTest.php | 2 +- .../RememberMeAuthenticationProviderTest.php | 3 +- .../UserAuthenticationProviderTest.php | 37 ++++- .../Token/AbstractTokenTest.php | 150 +++++++++--------- .../Token/AnonymousTokenTest.php | 3 +- .../Token/PreAuthenticatedTokenTest.php | 3 +- .../Token/RememberMeTokenTest.php | 3 +- .../Token/Storage/TokenStorageTest.php | 3 +- .../Token/SwitchUserTokenTest.php | 41 +++++ .../Token/UsernamePasswordTokenTest.php | 3 +- .../AuthorizationCheckerTest.php | 11 +- .../Voter/ExpressionVoterTest.php | 25 +++ .../Voter/RoleHierarchyVoterTest.php | 33 ++++ .../Authorization/Voter/RoleVoterTest.php | 39 +++++ .../Core/Tests/Role/RoleHierarchyTest.php | 17 ++ .../Security/Core/Tests/Role/RoleTest.php | 3 + .../Core/Tests/Role/SwitchUserRoleTest.php | 3 + .../Token/PostAuthenticationGuardToken.php | 7 +- .../Http/Firewall/ContextListener.php | 13 +- .../Http/Firewall/SwitchUserListener.php | 25 ++- .../Controller/UserValueResolverTest.php | 11 +- .../Tests/Firewall/SwitchUserListenerTest.php | 23 ++- .../Tests/Logout/LogoutUrlGeneratorTest.php | 2 +- .../Component/Security/Http/composer.json | 2 +- .../Workflow/EventListener/GuardListener.php | 18 ++- .../Tests/EventListener/GuardListenerTest.php | 4 +- 50 files changed, 654 insertions(+), 208 deletions(-) create mode 100644 src/Symfony/Component/Security/Core/Authentication/Token/SwitchUserToken.php create mode 100644 src/Symfony/Component/Security/Core/Tests/Authentication/Token/SwitchUserTokenTest.php diff --git a/UPGRADE-4.3.md b/UPGRADE-4.3.md index 7a2dca20633fa..a0d5e52b17632 100644 --- a/UPGRADE-4.3.md +++ b/UPGRADE-4.3.md @@ -53,6 +53,13 @@ HttpFoundation Security -------- + * The `Role` and `SwitchUserRole` classes are deprecated and will be removed in 5.0. Use strings for roles + instead. + * The `RoleHierarchyInterface` is deprecated and will be removed in 5.0. + * The `getReachableRoles()` method of the `RoleHierarchy` class is deprecated and will be removed in 5.0. + Use the `getReachableRoleNames()` method instead. + * The `getRoles()` method of the `TokenInterface` is deprecated. Tokens must implement the `getRoleNames()` + method instead and return roles as strings. * The `AbstractToken::serialize()`, `AbstractToken::unserialize()`, `AuthenticationException::serialize()` and `AuthenticationException::unserialize()` methods are now final, use `getState()` and `setState()` instead. diff --git a/UPGRADE-5.0.md b/UPGRADE-5.0.md index f61211c1cc15e..a959f2d06a179 100644 --- a/UPGRADE-5.0.md +++ b/UPGRADE-5.0.md @@ -226,6 +226,11 @@ Process Security -------- + * The `Role` and `SwitchUserRole` classes have been removed. + * The `RoleHierarchyInterface` has been removed. + * The `getReachableRoles()` method of the `RoleHierarchy` class has been removed. + * The `getRoles()` method has been removed from the `TokenInterface`. It has been replaced by the new + `getRoleNames()` method. * The `ContextListener::setLogoutOnUserChange()` method has been removed. * The `Symfony\Component\Security\Core\User\AdvancedUserInterface` has been removed. * The `ExpressionVoter::addExpressionLanguageProvider()` method has been removed. diff --git a/src/Symfony/Bridge/Monolog/Processor/TokenProcessor.php b/src/Symfony/Bridge/Monolog/Processor/TokenProcessor.php index 7bf03a036a257..7613d01361962 100644 --- a/src/Symfony/Bridge/Monolog/Processor/TokenProcessor.php +++ b/src/Symfony/Bridge/Monolog/Processor/TokenProcessor.php @@ -31,10 +31,16 @@ public function __invoke(array $records) { $records['extra']['token'] = null; if (null !== $token = $this->tokenStorage->getToken()) { + if (method_exists($token, 'getRoleNames')) { + $roles = $token->getRoleNames(); + } else { + $roles = array_map(function ($role) { return $role->getRole(); }, $token->getRoles(false)); + } + $records['extra']['token'] = [ 'username' => $token->getUsername(), 'authenticated' => $token->isAuthenticated(), - 'roles' => array_map(function ($role) { return $role->getRole(); }, $token->getRoles()), + 'roles' => $roles, ]; } diff --git a/src/Symfony/Bridge/Monolog/Tests/Processor/TokenProcessorTest.php b/src/Symfony/Bridge/Monolog/Tests/Processor/TokenProcessorTest.php index 1b01348639b0d..ef3f6cc9f5c6a 100644 --- a/src/Symfony/Bridge/Monolog/Tests/Processor/TokenProcessorTest.php +++ b/src/Symfony/Bridge/Monolog/Tests/Processor/TokenProcessorTest.php @@ -36,7 +36,6 @@ public function testProcessor() $this->assertArrayHasKey('token', $record['extra']); $this->assertEquals($token->getUsername(), $record['extra']['token']['username']); $this->assertEquals($token->isAuthenticated(), $record['extra']['token']['authenticated']); - $roles = array_map(function ($role) { return $role->getRole(); }, $token->getRoles()); - $this->assertEquals($roles, $record['extra']['token']['roles']); + $this->assertEquals(['ROLE_USER'], $record['extra']['token']['roles']); } } diff --git a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php index 3e55022f16429..d33d227ff137c 100644 --- a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php +++ b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php @@ -18,10 +18,12 @@ use Symfony\Component\HttpKernel\DataCollector\DataCollector; use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken; use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; use Symfony\Component\Security\Core\Authorization\TraceableAccessDecisionManager; use Symfony\Component\Security\Core\Authorization\Voter\TraceableVoter; use Symfony\Component\Security\Core\Role\Role; +use Symfony\Component\Security\Core\Role\RoleHierarchy; use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; use Symfony\Component\Security\Core\Role\SwitchUserRole; use Symfony\Component\Security\Http\Firewall\SwitchUserListener; @@ -91,18 +93,32 @@ public function collect(Request $request, Response $response, \Exception $except ]; } else { $inheritedRoles = []; - $assignedRoles = $token->getRoles(); + + if (method_exists($token, 'getRoleNames')) { + $assignedRoles = $token->getRoleNames(); + } else { + $assignedRoles = array_map(function (Role $role) { return $role->getRole(); }, $token->getRoles(false)); + } $impersonatorUser = null; - foreach ($assignedRoles as $role) { - if ($role instanceof SwitchUserRole) { - $impersonatorUser = $role->getSource()->getUsername(); - break; + if ($token instanceof SwitchUserToken) { + $impersonatorUser = $token->getOriginalToken()->getUsername(); + } else { + foreach ($token->getRoles(false) as $role) { + if ($role instanceof SwitchUserRole) { + $impersonatorUser = $role->getSource()->getUsername(); + break; + } } } if (null !== $this->roleHierarchy) { - $allRoles = $this->roleHierarchy->getReachableRoles($assignedRoles); + if ($this->roleHierarchy instanceof RoleHierarchy) { + $allRoles = $this->roleHierarchy->getReachableRoleNames($assignedRoles); + } else { + $allRoles = array_map(function (Role $role) { return (string) $role; }, $this->roleHierarchy->getReachableRoles($token->getRoles(false))); + } + foreach ($allRoles as $role) { if (!\in_array($role, $assignedRoles, true)) { $inheritedRoles[] = $role; @@ -129,8 +145,8 @@ public function collect(Request $request, Response $response, \Exception $except 'token_class' => $this->hasVarDumper ? new ClassStub(\get_class($token)) : \get_class($token), 'logout_url' => $logoutUrl, 'user' => $token->getUsername(), - 'roles' => array_map(function (Role $role) { return $role->getRole(); }, $assignedRoles), - 'inherited_roles' => array_unique(array_map(function (Role $role) { return $role->getRole(); }, $inheritedRoles)), + 'roles' => $assignedRoles, + 'inherited_roles' => array_unique($inheritedRoles), 'supports_role_hierarchy' => null !== $this->roleHierarchy, ]; } diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml index 9fbdae0bdd46f..08931ac718b98 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml @@ -98,6 +98,7 @@ %security.role_hierarchy.roles% + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php index e95b354d99851..ac264d2937ecc 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php @@ -18,9 +18,12 @@ use Symfony\Bundle\SecurityBundle\Security\FirewallMap; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; +use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Authorization\AccessDecisionManager; use Symfony\Component\Security\Core\Authorization\TraceableAccessDecisionManager; @@ -38,7 +41,7 @@ class SecurityDataCollectorTest extends TestCase public function testCollectWhenSecurityIsDisabled() { $collector = new SecurityDataCollector(); - $collector->collect($this->getRequest(), $this->getResponse()); + $collector->collect(new Request(), new Response()); $this->assertSame('security', $collector->getName()); $this->assertFalse($collector->isEnabled()); @@ -58,7 +61,7 @@ public function testCollectWhenAuthenticationTokenIsNull() { $tokenStorage = new TokenStorage(); $collector = new SecurityDataCollector($tokenStorage, $this->getRoleHierarchy()); - $collector->collect($this->getRequest(), $this->getResponse()); + $collector->collect(new Request(), new Response()); $this->assertTrue($collector->isEnabled()); $this->assertFalse($collector->isAuthenticated()); @@ -80,7 +83,7 @@ public function testCollectAuthenticationTokenAndRoles(array $roles, array $norm $tokenStorage->setToken(new UsernamePasswordToken('hhamon', 'P4$$w0rD', 'provider', $roles)); $collector = new SecurityDataCollector($tokenStorage, $this->getRoleHierarchy()); - $collector->collect($this->getRequest(), $this->getResponse()); + $collector->collect(new Request(), new Response()); $collector->lateCollect(); $this->assertTrue($collector->isEnabled()); @@ -95,6 +98,9 @@ public function testCollectAuthenticationTokenAndRoles(array $roles, array $norm $this->assertSame('hhamon', $collector->getUser()); } + /** + * @group legacy + */ public function testCollectImpersonatedToken() { $adminToken = new UsernamePasswordToken('yceruto', 'P4$$w0rD', 'provider', ['ROLE_ADMIN']); @@ -108,7 +114,7 @@ public function testCollectImpersonatedToken() $tokenStorage->setToken(new UsernamePasswordToken('hhamon', 'P4$$w0rD', 'provider', $userRoles)); $collector = new SecurityDataCollector($tokenStorage, $this->getRoleHierarchy()); - $collector->collect($this->getRequest(), $this->getResponse()); + $collector->collect(new Request(), new Response()); $collector->lateCollect(); $this->assertTrue($collector->isEnabled()); @@ -122,10 +128,32 @@ public function testCollectImpersonatedToken() $this->assertSame('hhamon', $collector->getUser()); } + public function testCollectSwitchUserToken() + { + $adminToken = new UsernamePasswordToken('yceruto', 'P4$$w0rD', 'provider', ['ROLE_ADMIN']); + + $tokenStorage = new TokenStorage(); + $tokenStorage->setToken(new SwitchUserToken('hhamon', 'P4$$w0rD', 'provider', ['ROLE_USER', 'ROLE_PREVIOUS_ADMIN'], $adminToken)); + + $collector = new SecurityDataCollector($tokenStorage, $this->getRoleHierarchy()); + $collector->collect(new Request(), new Response()); + $collector->lateCollect(); + + $this->assertTrue($collector->isEnabled()); + $this->assertTrue($collector->isAuthenticated()); + $this->assertTrue($collector->isImpersonated()); + $this->assertSame('yceruto', $collector->getImpersonatorUser()); + $this->assertSame(SwitchUserToken::class, $collector->getTokenClass()->getValue()); + $this->assertTrue($collector->supportsRoleHierarchy()); + $this->assertSame(['ROLE_USER', 'ROLE_PREVIOUS_ADMIN'], $collector->getRoles()->getValue(true)); + $this->assertSame([], $collector->getInheritedRoles()->getValue(true)); + $this->assertSame('hhamon', $collector->getUser()); + } + public function testGetFirewall() { $firewallConfig = new FirewallConfig('dummy', 'security.request_matcher.dummy', 'security.user_checker.dummy'); - $request = $this->getRequest(); + $request = new Request(); $firewallMap = $this ->getMockBuilder(FirewallMap::class) @@ -138,7 +166,7 @@ public function testGetFirewall() ->willReturn($firewallConfig); $collector = new SecurityDataCollector(null, null, null, null, $firewallMap, new TraceableFirewallListener($firewallMap, new EventDispatcher(), new LogoutUrlGenerator())); - $collector->collect($request, $this->getResponse()); + $collector->collect($request, new Response()); $collector->lateCollect(); $collected = $collector->getFirewall(); @@ -158,8 +186,8 @@ public function testGetFirewall() public function testGetFirewallReturnsNull() { - $request = $this->getRequest(); - $response = $this->getResponse(); + $request = new Request(); + $response = new Response(); // Don't inject any firewall map $collector = new SecurityDataCollector(); @@ -192,9 +220,9 @@ public function testGetFirewallReturnsNull() */ public function testGetListeners() { - $request = $this->getRequest(); + $request = new Request(); $event = new GetResponseEvent($this->getMockBuilder(HttpKernelInterface::class)->getMock(), $request, HttpKernelInterface::MASTER_REQUEST); - $event->setResponse($response = $this->getResponse()); + $event->setResponse($response = new Response()); $listener = $this->getMockBuilder(ListenerInterface::class)->getMock(); $listener ->expects($this->once()) @@ -345,7 +373,7 @@ public function testCollectDecisionLog(string $strategy, array $decisionLog, arr ->willReturn($decisionLog); $dataCollector = new SecurityDataCollector(null, null, null, $accessDecisionManager); - $dataCollector->collect($this->getRequest(), $this->getResponse()); + $dataCollector->collect(new Request(), new Response()); $this->assertEquals($dataCollector->getAccessDecisionLog(), $expectedDecisionLog, 'Wrong value returned by getAccessDecisionLog'); @@ -367,7 +395,7 @@ public function provideRoles() [], ], [ - [new Role('ROLE_USER')], + [new Role('ROLE_USER', false)], ['ROLE_USER'], [], ], @@ -378,7 +406,7 @@ public function provideRoles() ['ROLE_USER', 'ROLE_ALLOWED_TO_SWITCH'], ], [ - [new Role('ROLE_ADMIN')], + [new Role('ROLE_ADMIN', false)], ['ROLE_ADMIN'], ['ROLE_USER', 'ROLE_ALLOWED_TO_SWITCH'], ], @@ -397,20 +425,4 @@ private function getRoleHierarchy() 'ROLE_OPERATOR' => ['ROLE_USER'], ]); } - - private function getRequest() - { - return $this - ->getMockBuilder('Symfony\Component\HttpFoundation\Request') - ->disableOriginalConstructor() - ->getMock(); - } - - private function getResponse() - { - return $this - ->getMockBuilder('Symfony\Component\HttpFoundation\Response') - ->disableOriginalConstructor() - ->getMock(); - } } diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index 23b40ea144635..074e8593178d6 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -21,7 +21,7 @@ "symfony/config": "^4.2", "symfony/dependency-injection": "^4.2", "symfony/http-kernel": "^4.1", - "symfony/security-core": "~4.2", + "symfony/security-core": "~4.3", "symfony/security-csrf": "~4.2", "symfony/security-guard": "~4.2", "symfony/security-http": "~4.2" diff --git a/src/Symfony/Component/Security/CHANGELOG.md b/src/Symfony/Component/Security/CHANGELOG.md index 23d3ea43beed9..011d802ea1ff7 100644 --- a/src/Symfony/Component/Security/CHANGELOG.md +++ b/src/Symfony/Component/Security/CHANGELOG.md @@ -4,6 +4,13 @@ CHANGELOG 4.3.0 ----- +* The `Role` and `SwitchUserRole` classes are deprecated and will be removed in 5.0. Use strings for roles + instead. +* The `RoleHierarchyInterface` is deprecated and will be removed in 5.0. +* The `getReachableRoles()` method of the `RoleHierarchy` class is deprecated and will be removed in 5.0. + Use the `getReachableRoleNames()` method instead. +* The `getRoles()` method of the `TokenInterface` is deprecated. Tokens must implement the `getRoleNames()` + method instead and return roles as strings. * Made the `serialize()` and `unserialize()` methods of `AbstractToken` and `AuthenticationException` final, use `getState()`/`setState()` instead diff --git a/src/Symfony/Component/Security/Core/Authentication/Provider/UserAuthenticationProvider.php b/src/Symfony/Component/Security/Core/Authentication/Provider/UserAuthenticationProvider.php index 5f9128fe7b50e..7a35bb056a720 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Provider/UserAuthenticationProvider.php +++ b/src/Symfony/Component/Security/Core/Authentication/Provider/UserAuthenticationProvider.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Security\Core\Authentication\Provider; +use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Exception\AuthenticationException; @@ -87,7 +88,12 @@ public function authenticate(TokenInterface $token) throw $e; } - $authenticatedToken = new UsernamePasswordToken($user, $token->getCredentials(), $this->providerKey, $this->getRoles($user, $token)); + if ($token instanceof SwitchUserToken) { + $authenticatedToken = new SwitchUserToken($user, $token->getCredentials(), $this->providerKey, $this->getRoles($user, $token), $token->getOriginalToken()); + } else { + $authenticatedToken = new UsernamePasswordToken($user, $token->getCredentials(), $this->providerKey, $this->getRoles($user, $token)); + } + $authenticatedToken->setAttributes($token->getAttributes()); return $authenticatedToken; @@ -110,7 +116,7 @@ private function getRoles(UserInterface $user, TokenInterface $token) { $roles = $user->getRoles(); - foreach ($token->getRoles() as $role) { + foreach ($token->getRoles(false) as $role) { if ($role instanceof SwitchUserRole) { $roles[] = $role; diff --git a/src/Symfony/Component/Security/Core/Authentication/Token/AbstractToken.php b/src/Symfony/Component/Security/Core/Authentication/Token/AbstractToken.php index f879cf8a87fdd..700634e7dcdac 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Token/AbstractToken.php +++ b/src/Symfony/Component/Security/Core/Authentication/Token/AbstractToken.php @@ -26,11 +26,12 @@ abstract class AbstractToken implements TokenInterface { private $user; private $roles = []; + private $roleNames = []; private $authenticated = false; private $attributes = []; /** - * @param (Role|string)[] $roles An array of roles + * @param string[] $roles An array of roles * * @throws \InvalidArgumentException */ @@ -38,20 +39,30 @@ public function __construct(array $roles = []) { foreach ($roles as $role) { if (\is_string($role)) { - $role = new Role($role); + $role = new Role($role, false); } elseif (!$role instanceof Role) { throw new \InvalidArgumentException(sprintf('$roles must be an array of strings, or Role instances, but got %s.', \gettype($role))); } $this->roles[] = $role; + $this->roleNames[] = (string) $role; } } + public function getRoleNames(): array + { + return $this->roleNames; + } + /** * {@inheritdoc} */ public function getRoles() { + if (0 === \func_num_args() || func_get_arg(0)) { + @trigger_error(sprintf('The %s() method is deprecated since Symfony 4.3. Use the getRoleNames() method instead.', __METHOD__), E_USER_DEPRECATED); + } + return $this->roles; } @@ -172,7 +183,7 @@ public function unserialize($serialized) */ protected function getState(): array { - return [$this->user, $this->authenticated, $this->roles, $this->attributes]; + return [$this->user, $this->authenticated, $this->roles, $this->attributes, $this->roleNames]; } /** @@ -193,7 +204,7 @@ protected function getState(): array */ protected function setState(array $data) { - [$this->user, $this->authenticated, $this->roles, $this->attributes] = $data; + [$this->user, $this->authenticated, $this->roles, $this->attributes, $this->roleNames] = $data; } /** @@ -225,7 +236,7 @@ public function setAttributes(array $attributes) */ public function hasAttribute($name) { - return array_key_exists($name, $this->attributes); + return \array_key_exists($name, $this->attributes); } /** @@ -239,7 +250,7 @@ public function hasAttribute($name) */ public function getAttribute($name) { - if (!array_key_exists($name, $this->attributes)) { + if (!\array_key_exists($name, $this->attributes)) { throw new \InvalidArgumentException(sprintf('This token has no "%s" attribute.', $name)); } diff --git a/src/Symfony/Component/Security/Core/Authentication/Token/AnonymousToken.php b/src/Symfony/Component/Security/Core/Authentication/Token/AnonymousToken.php index 18b2481330972..c233c2bd16dc3 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Token/AnonymousToken.php +++ b/src/Symfony/Component/Security/Core/Authentication/Token/AnonymousToken.php @@ -11,8 +11,6 @@ namespace Symfony\Component\Security\Core\Authentication\Token; -use Symfony\Component\Security\Core\Role\Role; - /** * AnonymousToken represents an anonymous token. * @@ -25,7 +23,7 @@ class AnonymousToken extends AbstractToken /** * @param string $secret A secret used to make sure the token is created by the app and not by a malicious client * @param string|object $user The user can be a UserInterface instance, or an object implementing a __toString method or the username as a regular string - * @param Role[] $roles An array of roles + * @param string[] $roles An array of roles */ public function __construct(string $secret, $user, array $roles = []) { diff --git a/src/Symfony/Component/Security/Core/Authentication/Token/PreAuthenticatedToken.php b/src/Symfony/Component/Security/Core/Authentication/Token/PreAuthenticatedToken.php index 733f29159a51c..eb407e50c9c66 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Token/PreAuthenticatedToken.php +++ b/src/Symfony/Component/Security/Core/Authentication/Token/PreAuthenticatedToken.php @@ -11,8 +11,6 @@ namespace Symfony\Component\Security\Core\Authentication\Token; -use Symfony\Component\Security\Core\Role\Role; - /** * PreAuthenticatedToken implements a pre-authenticated token. * @@ -24,10 +22,10 @@ class PreAuthenticatedToken extends AbstractToken private $providerKey; /** - * @param string|object $user The user can be a UserInterface instance, or an object implementing a __toString method or the username as a regular string - * @param mixed $credentials The user credentials - * @param string $providerKey The provider key - * @param (Role|string)[] $roles An array of roles + * @param string|object $user The user can be a UserInterface instance, or an object implementing a __toString method or the username as a regular string + * @param mixed $credentials The user credentials + * @param string $providerKey The provider key + * @param string[] $roles An array of roles */ public function __construct($user, $credentials, string $providerKey, array $roles = []) { 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 f1f5391ba7800..97534b8f70044 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Token/Storage/TokenStorage.php +++ b/src/Symfony/Component/Security/Core/Authentication/Token/Storage/TokenStorage.php @@ -39,6 +39,10 @@ public function getToken() */ public function setToken(TokenInterface $token = null) { + if (null !== $token && !method_exists($token, 'getRoleNames')) { + @trigger_error(sprintf('Not implementing the getRoleNames() method in %s which implements %s is deprecated since Symfony 4.3.', \get_class($token), TokenInterface::class), E_USER_DEPRECATED); + } + $this->token = $token; } diff --git a/src/Symfony/Component/Security/Core/Authentication/Token/SwitchUserToken.php b/src/Symfony/Component/Security/Core/Authentication/Token/SwitchUserToken.php new file mode 100644 index 0000000000000..8e73876306aa9 --- /dev/null +++ b/src/Symfony/Component/Security/Core/Authentication/Token/SwitchUserToken.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authentication\Token; + +/** + * Token representing a user who temporarily impersonates another one. + * + * @author Christian Flothmann + */ +class SwitchUserToken extends UsernamePasswordToken +{ + private $originalToken; + + /** + * @param string|object $user The username (like a nickname, email address, etc.), or a UserInterface instance or an object implementing a __toString method + * @param mixed $credentials This usually is the password of the user + * @param string $providerKey The provider key + * @param string[] $roles An array of roles + * @param TokenInterface $originalToken The token of the user who switched to the current user + * + * @throws \InvalidArgumentException + */ + public function __construct($user, $credentials, string $providerKey, array $roles = [], TokenInterface $originalToken) + { + parent::__construct($user, $credentials, $providerKey, $roles); + + $this->originalToken = $originalToken; + } + + public function getOriginalToken(): TokenInterface + { + return $this->originalToken; + } + + /** + * {@inheritdoc} + */ + protected function getState(): array + { + return [$this->originalToken, parent::getState()]; + } + + /** + * {@inheritdoc} + */ + protected function setState(array $data) + { + [$this->originalToken, $parentData] = $data; + parent::setState($parentData); + } +} diff --git a/src/Symfony/Component/Security/Core/Authentication/Token/TokenInterface.php b/src/Symfony/Component/Security/Core/Authentication/Token/TokenInterface.php index bcf6fbdd94621..4d8c2522abd6d 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Token/TokenInterface.php +++ b/src/Symfony/Component/Security/Core/Authentication/Token/TokenInterface.php @@ -18,6 +18,8 @@ * * @author Fabien Potencier * @author Johannes M. Schmitt + * + * @method string[] getRoleNames() The associated roles - not implementing it is deprecated since Symfony 4.3 */ interface TokenInterface extends \Serializable { @@ -34,6 +36,8 @@ public function __toString(); * Returns the user roles. * * @return Role[] An array of Role instances + * + * @deprecated since Symfony 4.3, use the getRoleNames() method instead */ public function getRoles(); diff --git a/src/Symfony/Component/Security/Core/Authentication/Token/UsernamePasswordToken.php b/src/Symfony/Component/Security/Core/Authentication/Token/UsernamePasswordToken.php index d731906eb7e0f..79e5780f09ce6 100644 --- a/src/Symfony/Component/Security/Core/Authentication/Token/UsernamePasswordToken.php +++ b/src/Symfony/Component/Security/Core/Authentication/Token/UsernamePasswordToken.php @@ -11,8 +11,6 @@ namespace Symfony\Component\Security\Core\Authentication\Token; -use Symfony\Component\Security\Core\Role\Role; - /** * UsernamePasswordToken implements a username and password token. * @@ -24,10 +22,10 @@ class UsernamePasswordToken extends AbstractToken private $providerKey; /** - * @param string|object $user The username (like a nickname, email address, etc.), or a UserInterface instance or an object implementing a __toString method - * @param mixed $credentials This usually is the password of the user - * @param string $providerKey The provider key - * @param (Role|string)[] $roles An array of roles + * @param string|object $user The username (like a nickname, email address, etc.), or a UserInterface instance or an object implementing a __toString method + * @param mixed $credentials This usually is the password of the user + * @param string $providerKey The provider key + * @param string[] $roles An array of roles * * @throws \InvalidArgumentException */ diff --git a/src/Symfony/Component/Security/Core/Authorization/Voter/ExpressionVoter.php b/src/Symfony/Component/Security/Core/Authorization/Voter/ExpressionVoter.php index d9530e071ce72..c04c2ab322737 100644 --- a/src/Symfony/Component/Security/Core/Authorization/Voter/ExpressionVoter.php +++ b/src/Symfony/Component/Security/Core/Authorization/Voter/ExpressionVoter.php @@ -18,6 +18,8 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\Authorization\ExpressionLanguage; +use Symfony\Component\Security\Core\Role\Role; +use Symfony\Component\Security\Core\Role\RoleHierarchy; use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; /** @@ -90,18 +92,34 @@ public function vote(TokenInterface $token, $subject, array $attributes) private function getVariables(TokenInterface $token, $subject) { - if (null !== $this->roleHierarchy) { - $roles = $this->roleHierarchy->getReachableRoles($token->getRoles()); + if ($this->roleHierarchy instanceof RoleHierarchy) { + if (method_exists($token, 'getRoleNames')) { + $rolesFromToken = $token->getRoleNames(); + } else { + @trigger_error(sprintf('Not implementing the getRoleNames() method in %s which implements %s is deprecated since Symfony 4.3.', \get_class($token), TokenInterface::class), E_USER_DEPRECATED); + + $rolesFromToken = $token->getRoles(false); + } + + $roles = $this->roleHierarchy->getReachableRoleNames($rolesFromToken); + } elseif (null !== $this->roleHierarchy) { + $roles = $this->roleHierarchy->getReachableRoles($token->getRoles(false)); + } elseif (method_exists($token, 'getRoleNames')) { + $roles = $token->getRoleNames(); } else { - $roles = $token->getRoles(); + @trigger_error(sprintf('Not implementing the getRoleNames() method in %s which implements %s is deprecated since Symfony 4.3.', \get_class($token), TokenInterface::class), E_USER_DEPRECATED); + + $roles = $token->getRoles(false); } + $roles = array_map(function ($role) { return $role instanceof Role ? $role->getRole() : $role; }, $roles); + $variables = [ 'token' => $token, 'user' => $token->getUser(), 'object' => $subject, 'subject' => $subject, - 'roles' => array_map(function ($role) { return $role->getRole(); }, $roles), + 'roles' => $roles, 'trust_resolver' => $this->trustResolver, 'auth_checker' => $this->authChecker, ]; diff --git a/src/Symfony/Component/Security/Core/Authorization/Voter/RoleHierarchyVoter.php b/src/Symfony/Component/Security/Core/Authorization/Voter/RoleHierarchyVoter.php index 06dfb4228ea89..4a1de12cf7fa8 100644 --- a/src/Symfony/Component/Security/Core/Authorization/Voter/RoleHierarchyVoter.php +++ b/src/Symfony/Component/Security/Core/Authorization/Voter/RoleHierarchyVoter.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Security\Core\Authorization\Voter; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Role\RoleHierarchy; use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; /** @@ -26,6 +27,10 @@ class RoleHierarchyVoter extends RoleVoter public function __construct(RoleHierarchyInterface $roleHierarchy, string $prefix = 'ROLE_') { + if (!$roleHierarchy instanceof RoleHierarchy) { + @trigger_error(sprintf('Passing a role hierarchy to "%s" that is not an instance of "%s" is deprecated since Symfony 4.3 and support for it will be dropped in Symfony 5.0 ("%s" given).', __CLASS__, RoleHierarchy::class, \get_class($roleHierarchy)), E_USER_DEPRECATED); + } + $this->roleHierarchy = $roleHierarchy; parent::__construct($prefix); @@ -36,6 +41,18 @@ public function __construct(RoleHierarchyInterface $roleHierarchy, string $prefi */ protected function extractRoles(TokenInterface $token) { - return $this->roleHierarchy->getReachableRoles($token->getRoles()); + if ($this->roleHierarchy instanceof RoleHierarchy) { + if (method_exists($token, 'getRoleNames')) { + $roles = $token->getRoleNames(); + } else { + @trigger_error(sprintf('Not implementing the getRoleNames() method in %s which implements %s is deprecated since Symfony 4.3.', \get_class($token), TokenInterface::class), E_USER_DEPRECATED); + + $roles = $token->getRoles(false); + } + + return $this->roleHierarchy->getReachableRoleNames($roles); + } + + return $this->roleHierarchy->getReachableRoles($token->getRoles(false)); } } diff --git a/src/Symfony/Component/Security/Core/Authorization/Voter/RoleVoter.php b/src/Symfony/Component/Security/Core/Authorization/Voter/RoleVoter.php index fc2e6883e473f..deb542255c513 100644 --- a/src/Symfony/Component/Security/Core/Authorization/Voter/RoleVoter.php +++ b/src/Symfony/Component/Security/Core/Authorization/Voter/RoleVoter.php @@ -47,7 +47,7 @@ public function vote(TokenInterface $token, $subject, array $attributes) $result = VoterInterface::ACCESS_DENIED; foreach ($roles as $role) { - if ($attribute === $role->getRole()) { + if ($attribute === $role) { return VoterInterface::ACCESS_GRANTED; } } @@ -58,6 +58,12 @@ public function vote(TokenInterface $token, $subject, array $attributes) protected function extractRoles(TokenInterface $token) { - return $token->getRoles(); + if (method_exists($token, 'getRoleNames')) { + return $token->getRoleNames(); + } + + @trigger_error(sprintf('Not implementing the getRoleNames() method in %s which implements %s is deprecated since Symfony 4.3.', \get_class($token), TokenInterface::class), E_USER_DEPRECATED); + + return array_map(function (Role $role) { return $role->getRole(); }, $token->getRoles(false)); } } diff --git a/src/Symfony/Component/Security/Core/Role/Role.php b/src/Symfony/Component/Security/Core/Role/Role.php index 208fe2f6b7e06..df6ccf7636225 100644 --- a/src/Symfony/Component/Security/Core/Role/Role.php +++ b/src/Symfony/Component/Security/Core/Role/Role.php @@ -15,6 +15,8 @@ * Role is a simple implementation representing a role identified by a string. * * @author Fabien Potencier + * + * @deprecated since Symfony 4.3, to be removed in 5.0. Use strings as roles instead. */ class Role { @@ -22,6 +24,10 @@ class Role public function __construct(string $role) { + if (\func_num_args() < 2 || func_get_arg(1)) { + @trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3 and will be removed in 5.0. Use strings as roles instead.', __CLASS__), E_USER_DEPRECATED); + } + $this->role = $role; } @@ -34,4 +40,9 @@ public function getRole() { return $this->role; } + + public function __toString(): string + { + return $this->role; + } } diff --git a/src/Symfony/Component/Security/Core/Role/RoleHierarchy.php b/src/Symfony/Component/Security/Core/Role/RoleHierarchy.php index f4a2e5b622974..d725000d1b826 100644 --- a/src/Symfony/Component/Security/Core/Role/RoleHierarchy.php +++ b/src/Symfony/Component/Security/Core/Role/RoleHierarchy.php @@ -36,6 +36,8 @@ public function __construct(array $hierarchy) */ public function getReachableRoles(array $roles) { + @trigger_error(sprintf('The %s() method is deprecated since Symfony 4.3 and will be removed in 5.0. Use roles as strings and the getReachableRoleNames() method instead.', __METHOD__), E_USER_DEPRECATED); + $reachableRoles = $roles; foreach ($roles as $role) { if (!isset($this->map[$role->getRole()])) { @@ -50,6 +52,37 @@ public function getReachableRoles(array $roles) return $reachableRoles; } + /** + * @param string[] $roles + * + * @return string[] + */ + public function getReachableRoleNames(array $roles): array + { + $reachableRoles = $roles = array_map( + function ($role) { + if ($role instanceof Role) { + return $role->getRole(); + } + + return $role; + }, + $roles + ); + + foreach ($roles as $role) { + if (!isset($this->map[$role])) { + continue; + } + + foreach ($this->map[$role] as $r) { + $reachableRoles[] = $r; + } + } + + return $reachableRoles; + } + protected function buildRoleMap() { $this->map = []; diff --git a/src/Symfony/Component/Security/Core/Role/RoleHierarchyInterface.php b/src/Symfony/Component/Security/Core/Role/RoleHierarchyInterface.php index 1a86db9901603..3a5112f7e5b91 100644 --- a/src/Symfony/Component/Security/Core/Role/RoleHierarchyInterface.php +++ b/src/Symfony/Component/Security/Core/Role/RoleHierarchyInterface.php @@ -15,6 +15,8 @@ * RoleHierarchyInterface is the interface for a role hierarchy. * * @author Fabien Potencier + * + * @deprecated since Symfony 4.3, to be removed in 5.0. */ interface RoleHierarchyInterface { diff --git a/src/Symfony/Component/Security/Core/Role/SwitchUserRole.php b/src/Symfony/Component/Security/Core/Role/SwitchUserRole.php index d26c7067a2d90..85d7ddb1a56a6 100644 --- a/src/Symfony/Component/Security/Core/Role/SwitchUserRole.php +++ b/src/Symfony/Component/Security/Core/Role/SwitchUserRole.php @@ -18,9 +18,12 @@ * another one. * * @author Fabien Potencier + * + * @deprecated since version 4.3, to be removed in 5.0. Use strings as roles instead. */ class SwitchUserRole extends Role { + private $deprecationTriggered = false; private $source; /** @@ -29,7 +32,13 @@ class SwitchUserRole extends Role */ public function __construct(string $role, TokenInterface $source) { - parent::__construct($role); + if ($triggerDeprecation = \func_num_args() < 3 || func_get_arg(2)) { + @trigger_error(sprintf('The "%s" class is deprecated since Symfony 4.3 and will be removed in 5.0. Use strings as roles instead.', __CLASS__), E_USER_DEPRECATED); + + $this->deprecationTriggered = true; + } + + parent::__construct($role, $triggerDeprecation); $this->source = $source; } @@ -41,6 +50,12 @@ public function __construct(string $role, TokenInterface $source) */ public function getSource() { + if (!$this->deprecationTriggered && (\func_num_args() < 1 || func_get_arg(0))) { + @trigger_error(sprintf('The "%s" class is deprecated since version 4.3 and will be removed in 5.0. Use strings as roles instead.', __CLASS__), E_USER_DEPRECATED); + + $this->deprecationTriggered = true; + } + return $this->source; } } 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 a101208d681cc..d8d18ddeb9a42 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/PreAuthenticatedAuthenticationProviderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/PreAuthenticatedAuthenticationProviderTest.php @@ -70,7 +70,7 @@ public function testAuthenticate() $this->assertInstanceOf('Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken', $token); $this->assertEquals('pass', $token->getCredentials()); $this->assertEquals('key', $token->getProviderKey()); - $this->assertEquals([], $token->getRoles()); + $this->assertEquals([], $token->getRoleNames()); $this->assertEquals(['foo' => 'bar'], $token->getAttributes(), '->authenticate() copies token attributes'); $this->assertSame($user, $token->getUser()); } diff --git a/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/RememberMeAuthenticationProviderTest.php b/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/RememberMeAuthenticationProviderTest.php index 26287693d6b08..37d9a42a96319 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/RememberMeAuthenticationProviderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/RememberMeAuthenticationProviderTest.php @@ -14,7 +14,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Security\Core\Authentication\Provider\RememberMeAuthenticationProvider; use Symfony\Component\Security\Core\Exception\DisabledException; -use Symfony\Component\Security\Core\Role\Role; class RememberMeAuthenticationProviderTest extends TestCase { @@ -78,7 +77,7 @@ public function testAuthenticate() $this->assertInstanceOf('Symfony\Component\Security\Core\Authentication\Token\RememberMeToken', $authToken); $this->assertSame($user, $authToken->getUser()); - $this->assertEquals([new Role('ROLE_FOO')], $authToken->getRoles()); + $this->assertEquals(['ROLE_FOO'], $authToken->getRoleNames()); $this->assertEquals('', $authToken->getCredentials()); } 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 7c306d2f1f0f6..e62ac3f9f5f29 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/UserAuthenticationProviderTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/Provider/UserAuthenticationProviderTest.php @@ -12,11 +12,11 @@ namespace Symfony\Component\Security\Core\Tests\Authentication\Provider; use PHPUnit\Framework\TestCase; +use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken; use Symfony\Component\Security\Core\Exception\AccountExpiredException; 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\Role\Role; use Symfony\Component\Security\Core\Role\SwitchUserRole; class UserAuthenticationProviderTest extends TestCase @@ -189,11 +189,14 @@ public function testAuthenticate() $this->assertInstanceOf('Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken', $authToken); $this->assertSame($user, $authToken->getUser()); - $this->assertEquals([new Role('ROLE_FOO')], $authToken->getRoles()); + $this->assertEquals(['ROLE_FOO'], $authToken->getRoleNames()); $this->assertEquals('foo', $authToken->getCredentials()); $this->assertEquals(['foo' => 'bar'], $authToken->getAttributes(), '->authenticate() copies token attributes'); } + /** + * @group legacy + */ public function testAuthenticateWithPreservingRoleSwitchUserRole() { $user = $this->getMockBuilder('Symfony\Component\Security\Core\User\UserInterface')->getMock(); @@ -224,12 +227,40 @@ public function testAuthenticateWithPreservingRoleSwitchUserRole() $this->assertInstanceOf('Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken', $authToken); $this->assertSame($user, $authToken->getUser()); - $this->assertContains(new Role('ROLE_FOO'), $authToken->getRoles(), '', false, false); + $this->assertContains('ROLE_FOO', $authToken->getRoleNames(), '', false, false); $this->assertContains($switchUserRole, $authToken->getRoles(), '', false, false); $this->assertEquals('foo', $authToken->getCredentials()); $this->assertEquals(['foo' => 'bar'], $authToken->getAttributes(), '->authenticate() copies token attributes'); } + public function testAuthenticatePreservesOriginalToken() + { + $user = $this->getMockBuilder('Symfony\Component\Security\Core\User\UserInterface')->getMock(); + $user->expects($this->once()) + ->method('getRoles') + ->will($this->returnValue(['ROLE_FOO'])) + ; + + $provider = $this->getProvider(); + $provider->expects($this->once()) + ->method('retrieveUser') + ->will($this->returnValue($user)) + ; + + $originalToken = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock(); + $token = new SwitchUserToken($this->getMockBuilder('Symfony\Component\Security\Core\User\UserInterface')->getMock(), 'foo', 'key', [], $originalToken); + $token->setAttributes(['foo' => 'bar']); + + $authToken = $provider->authenticate($token); + + $this->assertInstanceOf(SwitchUserToken::class, $authToken); + $this->assertSame($originalToken, $authToken->getOriginalToken()); + $this->assertSame($user, $authToken->getUser()); + $this->assertContains('ROLE_FOO', $authToken->getRoleNames(), '', false, false); + $this->assertEquals('foo', $authToken->getCredentials()); + $this->assertEquals(['foo' => 'bar'], $authToken->getAttributes(), '->authenticate() copies token attributes'); + } + protected function getSupportedToken() { $mock = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken')->setMethods(['getCredentials', 'getProviderKey', 'getRoles'])->disableOriginalConstructor()->getMock(); 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 066ca6892ce53..e87d25a789f1d 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authentication/Token/AbstractTokenTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/Token/AbstractTokenTest.php @@ -14,61 +14,14 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Security\Core\Authentication\Token\AbstractToken; use Symfony\Component\Security\Core\Role\Role; -use Symfony\Component\Security\Core\Role\SwitchUserRole; use Symfony\Component\Security\Core\User\User; - -class TestUser -{ - protected $name; - - public function __construct($name) - { - $this->name = $name; - } - - public function __toString() - { - return $this->name; - } -} - -class ConcreteToken extends AbstractToken -{ - private $credentials = 'credentials_value'; - - public function __construct($user, array $roles = []) - { - parent::__construct($roles); - - $this->setUser($user); - } - - /** - * {@inheritdoc} - */ - public function serialize() - { - $serialized = [$this->credentials, parent::serialize(true)]; - - return $this->doSerialize($serialized, \func_num_args() ? \func_get_arg(0) : null); - } - - public function unserialize($serialized) - { - list($this->credentials, $parentStr) = unserialize($serialized); - parent::unserialize($parentStr); - } - - public function getCredentials() - { - } -} +use Symfony\Component\Security\Core\User\UserInterface; class AbstractTokenTest extends TestCase { public function testGetUsername() { - $token = $this->getToken(['ROLE_FOO']); + $token = new ConcreteToken(['ROLE_FOO']); $token->setUser('fabien'); $this->assertEquals('fabien', $token->getUsername()); @@ -83,7 +36,7 @@ public function testGetUsername() public function testEraseCredentials() { - $token = $this->getToken(['ROLE_FOO']); + $token = new ConcreteToken(['ROLE_FOO']); $user = $this->getMockBuilder('Symfony\Component\Security\Core\User\UserInterface')->getMock(); $user->expects($this->once())->method('eraseCredentials'); @@ -94,19 +47,22 @@ public function testEraseCredentials() public function testSerialize() { - $token = $this->getToken(['ROLE_FOO', new Role('ROLE_BAR')]); + $token = new ConcreteToken(['ROLE_FOO', new Role('ROLE_BAR', false)]); $token->setAttributes(['foo' => 'bar']); $uToken = unserialize(serialize($token)); - $this->assertEquals($token->getRoles(), $uToken->getRoles()); + $this->assertEquals($token->getRoleNames(), $uToken->getRoleNames()); $this->assertEquals($token->getAttributes(), $uToken->getAttributes()); } + /** + * @group legacy + */ public function testSerializeWithRoleObjects() { $user = new User('name', 'password', [new Role('ROLE_FOO'), new Role('ROLE_BAR')]); - $token = new ConcreteToken($user, $user->getRoles()); + $token = new ConcreteToken($user->getRoles(), $user); $serialized = serialize($token); $unserialized = unserialize($serialized); @@ -116,35 +72,42 @@ public function testSerializeWithRoleObjects() $this->assertEquals($roles, $user->getRoles()); } - public function testSerializeParent() + public function testConstructor() { - $user = new TestUser('fabien'); - $token = new ConcreteToken($user, ['ROLE_FOO']); + $token = new ConcreteToken(['ROLE_FOO']); + $this->assertEquals(['ROLE_FOO'], $token->getRoleNames()); + } - $parentToken = new ConcreteToken($user, [new SwitchUserRole('ROLE_PREVIOUS', $token)]); - $uToken = unserialize(serialize($parentToken)); + /** + * @group legacy + */ + public function testConstructorWithRoleObjects() + { + $token = new ConcreteToken([new Role('ROLE_FOO')]); + $this->assertEquals(['ROLE_FOO'], $token->getRoleNames()); - $this->assertEquals( - current($parentToken->getRoles())->getSource()->getUser(), - current($uToken->getRoles())->getSource()->getUser() - ); + $token = new ConcreteToken([new Role('ROLE_FOO'), 'ROLE_BAR']); + $this->assertEquals(['ROLE_FOO', 'ROLE_BAR'], $token->getRoleNames()); } - public function testConstructor() + /** + * @group legacy + */ + public function testGetRoles() { - $token = $this->getToken(['ROLE_FOO']); + $token = new ConcreteToken(['ROLE_FOO']); $this->assertEquals([new Role('ROLE_FOO')], $token->getRoles()); - $token = $this->getToken([new Role('ROLE_FOO')]); + $token = new ConcreteToken([new Role('ROLE_FOO')]); $this->assertEquals([new Role('ROLE_FOO')], $token->getRoles()); - $token = $this->getToken([new Role('ROLE_FOO'), 'ROLE_BAR']); + $token = new ConcreteToken([new Role('ROLE_FOO'), 'ROLE_BAR']); $this->assertEquals([new Role('ROLE_FOO'), new Role('ROLE_BAR')], $token->getRoles()); } public function testAuthenticatedFlag() { - $token = $this->getToken(); + $token = new ConcreteToken(); $this->assertFalse($token->isAuthenticated()); $token->setAuthenticated(true); @@ -157,7 +120,7 @@ public function testAuthenticatedFlag() public function testAttributes() { $attributes = ['foo' => 'bar']; - $token = $this->getToken(); + $token = new ConcreteToken(); $token->setAttributes($attributes); $this->assertEquals($attributes, $token->getAttributes(), '->getAttributes() returns the token attributes'); @@ -181,7 +144,7 @@ public function testAttributes() */ public function testSetUser($user) { - $token = $this->getToken(); + $token = new ConcreteToken(); $token->setUser($user); $this->assertSame($user, $token->getUser()); } @@ -202,7 +165,7 @@ public function getUsers() */ public function testSetUserSetsAuthenticatedToFalseWhenUserChanges($firstUser, $secondUser) { - $token = $this->getToken(); + $token = new ConcreteToken(); $token->setAuthenticated(true); $this->assertTrue($token->isAuthenticated()); @@ -236,7 +199,7 @@ public function getUserChanges() */ public function testSetUserSetsAuthenticatedToFalseWhenUserChangesAdvancedUser($firstUser, $secondUser) { - $token = $this->getToken(); + $token = new ConcreteToken(); $token->setAuthenticated(true); $this->assertTrue($token->isAuthenticated()); @@ -275,7 +238,7 @@ public function getUserChangesAdvancedUser() */ public function testSetUserDoesNotSetAuthenticatedToFalseWhenUserDoesNotChange($user) { - $token = $this->getToken(); + $token = new ConcreteToken(); $token->setAuthenticated(true); $this->assertTrue($token->isAuthenticated()); @@ -285,9 +248,48 @@ public function testSetUserDoesNotSetAuthenticatedToFalseWhenUserDoesNotChange($ $token->setUser($user); $this->assertTrue($token->isAuthenticated()); } +} + +class TestUser +{ + protected $name; + + public function __construct($name) + { + $this->name = $name; + } + + public function __toString() + { + return $this->name; + } +} + +class ConcreteToken extends AbstractToken +{ + private $credentials = 'credentials_value'; + + public function __construct(array $roles = [], UserInterface $user = null) + { + parent::__construct($roles); + + if (null !== $user) { + $this->setUser($user); + } + } - protected function getToken(array $roles = []) + public function serialize() + { + return serialize([$this->credentials, parent::serialize()]); + } + + public function unserialize($serialized) + { + list($this->credentials, $parentStr) = unserialize($serialized); + parent::unserialize($parentStr); + } + + public function getCredentials() { - return $this->getMockForAbstractClass('Symfony\Component\Security\Core\Authentication\Token\AbstractToken', [$roles]); } } diff --git a/src/Symfony/Component/Security/Core/Tests/Authentication/Token/AnonymousTokenTest.php b/src/Symfony/Component/Security/Core/Tests/Authentication/Token/AnonymousTokenTest.php index 7024cc5356cd4..1b00fd4e76aec 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authentication/Token/AnonymousTokenTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/Token/AnonymousTokenTest.php @@ -13,7 +13,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; -use Symfony\Component\Security\Core\Role\Role; class AnonymousTokenTest extends TestCase { @@ -23,7 +22,7 @@ public function testConstructor() $this->assertTrue($token->isAuthenticated()); $token = new AnonymousToken('foo', 'bar', ['ROLE_FOO']); - $this->assertEquals([new Role('ROLE_FOO')], $token->getRoles()); + $this->assertEquals(['ROLE_FOO'], $token->getRoleNames()); } public function testGetKey() diff --git a/src/Symfony/Component/Security/Core/Tests/Authentication/Token/PreAuthenticatedTokenTest.php b/src/Symfony/Component/Security/Core/Tests/Authentication/Token/PreAuthenticatedTokenTest.php index 7e64aa1a5d4e6..78cda619b2aff 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authentication/Token/PreAuthenticatedTokenTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/Token/PreAuthenticatedTokenTest.php @@ -13,7 +13,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken; -use Symfony\Component\Security\Core\Role\Role; class PreAuthenticatedTokenTest extends TestCase { @@ -24,7 +23,7 @@ public function testConstructor() $token = new PreAuthenticatedToken('foo', 'bar', 'key', ['ROLE_FOO']); $this->assertTrue($token->isAuthenticated()); - $this->assertEquals([new Role('ROLE_FOO')], $token->getRoles()); + $this->assertEquals(['ROLE_FOO'], $token->getRoleNames()); $this->assertEquals('key', $token->getProviderKey()); } diff --git a/src/Symfony/Component/Security/Core/Tests/Authentication/Token/RememberMeTokenTest.php b/src/Symfony/Component/Security/Core/Tests/Authentication/Token/RememberMeTokenTest.php index 8fa0307108f54..fea6161d775a0 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authentication/Token/RememberMeTokenTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/Token/RememberMeTokenTest.php @@ -13,7 +13,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken; -use Symfony\Component\Security\Core\Role\Role; class RememberMeTokenTest extends TestCase { @@ -24,7 +23,7 @@ public function testConstructor() $this->assertEquals('fookey', $token->getProviderKey()); $this->assertEquals('foo', $token->getSecret()); - $this->assertEquals([new Role('ROLE_FOO')], $token->getRoles()); + $this->assertEquals(['ROLE_FOO'], $token->getRoleNames()); $this->assertSame($user, $token->getUser()); $this->assertTrue($token->isAuthenticated()); } diff --git a/src/Symfony/Component/Security/Core/Tests/Authentication/Token/Storage/TokenStorageTest.php b/src/Symfony/Component/Security/Core/Tests/Authentication/Token/Storage/TokenStorageTest.php index fd30eea3c57c7..43261b3bd2b60 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authentication/Token/Storage/TokenStorageTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/Token/Storage/TokenStorageTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; class TokenStorageTest extends TestCase { @@ -20,7 +21,7 @@ public function testGetSetToken() { $tokenStorage = new TokenStorage(); $this->assertNull($tokenStorage->getToken()); - $token = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock(); + $token = new UsernamePasswordToken('username', 'password', 'provider'); $tokenStorage->setToken($token); $this->assertSame($token, $tokenStorage->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 new file mode 100644 index 0000000000000..5841250959b09 --- /dev/null +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/Token/SwitchUserTokenTest.php @@ -0,0 +1,41 @@ + + * + * 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\Authentication\Token; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken; +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; + +class SwitchUserTokenTest extends TestCase +{ + public function testSerialize() + { + $originalToken = new UsernamePasswordToken('user', 'foo', 'provider-key', ['ROLE_ADMIN', 'ROLE_ALLOWED_TO_SWITCH']); + $token = new SwitchUserToken('admin', 'bar', 'provider-key', ['ROLE_USER'], $originalToken); + + $unserializedToken = unserialize(serialize($token)); + + $this->assertInstanceOf(SwitchUserToken::class, $unserializedToken); + $this->assertSame('admin', $unserializedToken->getUsername()); + $this->assertSame('bar', $unserializedToken->getCredentials()); + $this->assertSame('provider-key', $unserializedToken->getProviderKey()); + $this->assertEquals(['ROLE_USER'], $unserializedToken->getRoleNames()); + + $unserializedOriginalToken = $unserializedToken->getOriginalToken(); + + $this->assertInstanceOf(UsernamePasswordToken::class, $unserializedOriginalToken); + $this->assertSame('user', $unserializedOriginalToken->getUsername()); + $this->assertSame('foo', $unserializedOriginalToken->getCredentials()); + $this->assertSame('provider-key', $unserializedOriginalToken->getProviderKey()); + $this->assertEquals(['ROLE_ADMIN', 'ROLE_ALLOWED_TO_SWITCH'], $unserializedOriginalToken->getRoleNames()); + } +} diff --git a/src/Symfony/Component/Security/Core/Tests/Authentication/Token/UsernamePasswordTokenTest.php b/src/Symfony/Component/Security/Core/Tests/Authentication/Token/UsernamePasswordTokenTest.php index 87dceea3d8422..ab0abaf6530c9 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authentication/Token/UsernamePasswordTokenTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authentication/Token/UsernamePasswordTokenTest.php @@ -13,7 +13,6 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; -use Symfony\Component\Security\Core\Role\Role; class UsernamePasswordTokenTest extends TestCase { @@ -23,7 +22,7 @@ public function testConstructor() $this->assertFalse($token->isAuthenticated()); $token = new UsernamePasswordToken('foo', 'bar', 'key', ['ROLE_FOO']); - $this->assertEquals([new Role('ROLE_FOO')], $token->getRoles()); + $this->assertEquals(['ROLE_FOO'], $token->getRoleNames()); $this->assertTrue($token->isAuthenticated()); $this->assertEquals('key', $token->getProviderKey()); } diff --git a/src/Symfony/Component/Security/Core/Tests/Authorization/AuthorizationCheckerTest.php b/src/Symfony/Component/Security/Core/Tests/Authorization/AuthorizationCheckerTest.php index 6a07c94f2039c..f2dcb6fbc3c18 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authorization/AuthorizationCheckerTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authorization/AuthorizationCheckerTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Authorization\AuthorizationChecker; class AuthorizationCheckerTest extends TestCase @@ -37,10 +38,10 @@ protected function setUp() public function testVoteAuthenticatesTokenIfNecessary() { - $token = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock(); + $token = new UsernamePasswordToken('username', 'password', 'provider'); $this->tokenStorage->setToken($token); - $newToken = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock(); + $newToken = new UsernamePasswordToken('username', 'password', 'provider'); $this->authenticationManager ->expects($this->once()) @@ -79,11 +80,7 @@ public function testVoteWithoutAuthenticationToken() */ public function testIsGranted($decide) { - $token = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock(); - $token - ->expects($this->once()) - ->method('isAuthenticated') - ->will($this->returnValue(true)); + $token = new UsernamePasswordToken('username', 'password', 'provider', ['ROLE_USER']); $this->accessDecisionManager ->expects($this->once()) diff --git a/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/ExpressionVoterTest.php b/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/ExpressionVoterTest.php index 632c6d0ab07ca..d377718842456 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/ExpressionVoterTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/ExpressionVoterTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Security\Core\Tests\Authorization\Voter; use PHPUnit\Framework\TestCase; +use Symfony\Component\Security\Core\Authentication\Token\AbstractToken; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\Authorization\Voter\ExpressionVoter; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; @@ -20,6 +21,7 @@ class ExpressionVoterTest extends TestCase { /** + * @group legacy * @dataProvider getVoteTests */ public function testVote($roles, $attributes, $expected, $tokenExpectsGetRoles = true, $expressionLanguageExpectsEvaluate = true) @@ -29,6 +31,16 @@ public function testVote($roles, $attributes, $expected, $tokenExpectsGetRoles = $this->assertSame($expected, $voter->vote($this->getToken($roles, $tokenExpectsGetRoles), null, $attributes)); } + /** + * @dataProvider getVoteTests + */ + public function testVoteWithTokenThatReturnsRoleNames($roles, $attributes, $expected, $tokenExpectsGetRoles = true, $expressionLanguageExpectsEvaluate = true) + { + $voter = new ExpressionVoter($this->createExpressionLanguage($expressionLanguageExpectsEvaluate), $this->createTrustResolver(), $this->createAuthorizationChecker()); + + $this->assertSame($expected, $voter->vote($this->getTokenWithRoleNames($roles, $tokenExpectsGetRoles), null, $attributes)); + } + public function getVoteTests() { return [ @@ -58,6 +70,19 @@ protected function getToken(array $roles, $tokenExpectsGetRoles = true) return $token; } + protected function getTokenWithRoleNames(array $roles, $tokenExpectsGetRoles = true) + { + $token = $this->getMockBuilder(AbstractToken::class)->getMock(); + + if ($tokenExpectsGetRoles) { + $token->expects($this->once()) + ->method('getRoleNames') + ->will($this->returnValue($roles)); + } + + return $token; + } + protected function createExpressionLanguage($expressionLanguageExpectsEvaluate = true) { $mock = $this->getMockBuilder('Symfony\Component\Security\Core\Authorization\ExpressionLanguage')->getMock(); diff --git a/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/RoleHierarchyVoterTest.php b/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/RoleHierarchyVoterTest.php index 14705fbf960e5..ec21779a68ea8 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/RoleHierarchyVoterTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/RoleHierarchyVoterTest.php @@ -18,6 +18,7 @@ class RoleHierarchyVoterTest extends RoleVoterTest { /** + * @group legacy * @dataProvider getVoteTests */ public function testVote($roles, $attributes, $expected) @@ -27,6 +28,16 @@ public function testVote($roles, $attributes, $expected) $this->assertSame($expected, $voter->vote($this->getToken($roles), null, $attributes)); } + /** + * @dataProvider getVoteTests + */ + public function testVoteUsingTokenThatReturnsRoleNames($roles, $attributes, $expected) + { + $voter = new RoleHierarchyVoter(new RoleHierarchy(['ROLE_FOO' => ['ROLE_FOOBAR']])); + + $this->assertSame($expected, $voter->vote($this->getTokenWithRoleNames($roles), null, $attributes)); + } + public function getVoteTests() { return array_merge(parent::getVoteTests(), [ @@ -35,6 +46,18 @@ public function getVoteTests() } /** + * @group legacy + * @dataProvider getLegacyVoteOnRoleObjectsTests + */ + public function testVoteOnRoleObjects($roles, $attributes, $expected) + { + $voter = new RoleHierarchyVoter(new RoleHierarchy(['ROLE_FOO' => ['ROLE_FOOBAR']])); + + $this->assertSame($expected, $voter->vote($this->getToken($roles), null, $attributes)); + } + + /** + * @group legacy * @dataProvider getVoteWithEmptyHierarchyTests */ public function testVoteWithEmptyHierarchy($roles, $attributes, $expected) @@ -44,6 +67,16 @@ public function testVoteWithEmptyHierarchy($roles, $attributes, $expected) $this->assertSame($expected, $voter->vote($this->getToken($roles), null, $attributes)); } + /** + * @dataProvider getVoteWithEmptyHierarchyTests + */ + public function testVoteWithEmptyHierarchyUsingTokenThatReturnsRoleNames($roles, $attributes, $expected) + { + $voter = new RoleHierarchyVoter(new RoleHierarchy([])); + + $this->assertSame($expected, $voter->vote($this->getTokenWithRoleNames($roles), null, $attributes)); + } + public function getVoteWithEmptyHierarchyTests() { return parent::getVoteTests(); diff --git a/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/RoleVoterTest.php b/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/RoleVoterTest.php index 5fb45e08b3925..6a1034417c837 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/RoleVoterTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authorization/Voter/RoleVoterTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Security\Core\Tests\Authorization\Voter; use PHPUnit\Framework\TestCase; +use Symfony\Component\Security\Core\Authentication\Token\AbstractToken; use Symfony\Component\Security\Core\Authorization\Voter\RoleVoter; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; use Symfony\Component\Security\Core\Role\Role; @@ -19,6 +20,7 @@ class RoleVoterTest extends TestCase { /** + * @group legacy * @dataProvider getVoteTests */ public function testVote($roles, $attributes, $expected) @@ -28,6 +30,16 @@ public function testVote($roles, $attributes, $expected) $this->assertSame($expected, $voter->vote($this->getToken($roles), null, $attributes)); } + /** + * @dataProvider getVoteTests + */ + public function testVoteUsingTokenThatReturnsRoleNames($roles, $attributes, $expected) + { + $voter = new RoleVoter(); + + $this->assertSame($expected, $voter->vote($this->getTokenWithRoleNames($roles), null, $attributes)); + } + public function getVoteTests() { return [ @@ -41,6 +53,23 @@ public function getVoteTests() // Test mixed Types [[], [[]], VoterInterface::ACCESS_ABSTAIN], [[], [new \stdClass()], VoterInterface::ACCESS_ABSTAIN], + ]; + } + + /** + * @group legacy + * @dataProvider getLegacyVoteOnRoleObjectsTests + */ + public function testVoteOnRoleObjects($roles, $attributes, $expected) + { + $voter = new RoleVoter(); + + $this->assertSame($expected, $voter->vote($this->getToken($roles), null, $attributes)); + } + + public function getLegacyVoteOnRoleObjectsTests() + { + return [ [['ROLE_BAR'], [new Role('ROLE_BAR')], VoterInterface::ACCESS_GRANTED], [['ROLE_BAR'], [new Role('ROLE_FOO')], VoterInterface::ACCESS_DENIED], ]; @@ -58,4 +87,14 @@ protected function getToken(array $roles) return $token; } + + protected function getTokenWithRoleNames(array $roles) + { + $token = $this->getMockBuilder(AbstractToken::class)->getMock(); + $token->expects($this->once()) + ->method('getRoleNames') + ->will($this->returnValue($roles)); + + return $token; + } } diff --git a/src/Symfony/Component/Security/Core/Tests/Role/RoleHierarchyTest.php b/src/Symfony/Component/Security/Core/Tests/Role/RoleHierarchyTest.php index 4451f391adb30..c33fb953a1877 100644 --- a/src/Symfony/Component/Security/Core/Tests/Role/RoleHierarchyTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Role/RoleHierarchyTest.php @@ -17,6 +17,9 @@ class RoleHierarchyTest extends TestCase { + /** + * @group legacy + */ public function testGetReachableRoles() { $role = new RoleHierarchy([ @@ -30,4 +33,18 @@ public function testGetReachableRoles() $this->assertEquals([new Role('ROLE_FOO'), new Role('ROLE_ADMIN'), new Role('ROLE_USER')], $role->getReachableRoles([new Role('ROLE_FOO'), new Role('ROLE_ADMIN')])); $this->assertEquals([new Role('ROLE_SUPER_ADMIN'), new Role('ROLE_ADMIN'), new Role('ROLE_FOO'), new Role('ROLE_USER')], $role->getReachableRoles([new Role('ROLE_SUPER_ADMIN')])); } + + public function testGetReachableRoleNames() + { + $role = new RoleHierarchy(array( + 'ROLE_ADMIN' => array('ROLE_USER'), + 'ROLE_SUPER_ADMIN' => array('ROLE_ADMIN', 'ROLE_FOO'), + )); + + $this->assertEquals(array('ROLE_USER'), $role->getReachableRoleNames(array('ROLE_USER'))); + $this->assertEquals(array('ROLE_FOO'), $role->getReachableRoleNames(array('ROLE_FOO'))); + $this->assertEquals(array('ROLE_ADMIN', 'ROLE_USER'), $role->getReachableRoleNames(array('ROLE_ADMIN'))); + $this->assertEquals(array('ROLE_FOO', 'ROLE_ADMIN', 'ROLE_USER'), $role->getReachableRoleNames(array('ROLE_FOO', 'ROLE_ADMIN'))); + $this->assertEquals(array('ROLE_SUPER_ADMIN', 'ROLE_ADMIN', 'ROLE_FOO', 'ROLE_USER'), $role->getReachableRoleNames(array('ROLE_SUPER_ADMIN'))); + } } diff --git a/src/Symfony/Component/Security/Core/Tests/Role/RoleTest.php b/src/Symfony/Component/Security/Core/Tests/Role/RoleTest.php index edf779413b636..e872a8c36b4ab 100644 --- a/src/Symfony/Component/Security/Core/Tests/Role/RoleTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Role/RoleTest.php @@ -14,6 +14,9 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Security\Core\Role\Role; +/** + * @group legacy + */ class RoleTest extends TestCase { public function testGetRole() diff --git a/src/Symfony/Component/Security/Core/Tests/Role/SwitchUserRoleTest.php b/src/Symfony/Component/Security/Core/Tests/Role/SwitchUserRoleTest.php index e40471733ce11..88f6a18abf7d1 100644 --- a/src/Symfony/Component/Security/Core/Tests/Role/SwitchUserRoleTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Role/SwitchUserRoleTest.php @@ -14,6 +14,9 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Security\Core\Role\SwitchUserRole; +/** + * @group legacy + */ class SwitchUserRoleTest extends TestCase { public function testGetSource() diff --git a/src/Symfony/Component/Security/Guard/Token/PostAuthenticationGuardToken.php b/src/Symfony/Component/Security/Guard/Token/PostAuthenticationGuardToken.php index 9e3646da45ccb..ac792b6a8713a 100644 --- a/src/Symfony/Component/Security/Guard/Token/PostAuthenticationGuardToken.php +++ b/src/Symfony/Component/Security/Guard/Token/PostAuthenticationGuardToken.php @@ -12,7 +12,6 @@ namespace Symfony\Component\Security\Guard\Token; use Symfony\Component\Security\Core\Authentication\Token\AbstractToken; -use Symfony\Component\Security\Core\Role\Role; use Symfony\Component\Security\Core\User\UserInterface; /** @@ -28,9 +27,9 @@ class PostAuthenticationGuardToken extends AbstractToken implements GuardTokenIn private $providerKey; /** - * @param UserInterface $user The user! - * @param string $providerKey The provider (firewall) key - * @param (Role|string)[] $roles An array of roles + * @param UserInterface $user The user! + * @param string $providerKey The provider (firewall) key + * @param string[] $roles An array of roles * * @throws \InvalidArgumentException */ diff --git a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php index c7a6dc96c88fd..e86dc5f2302ec 100644 --- a/src/Symfony/Component/Security/Http/Firewall/ContextListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/ContextListener.php @@ -21,6 +21,7 @@ use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +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; @@ -189,10 +190,14 @@ protected function refreshUser(TokenInterface $token) if (null !== $this->logger) { $context = ['provider' => \get_class($provider), 'username' => $refreshedUser->getUsername()]; - foreach ($token->getRoles() as $role) { - if ($role instanceof SwitchUserRole) { - $context['impersonator_username'] = $role->getSource()->getUsername(); - break; + if ($token instanceof SwitchUserToken) { + $context['impersonator_username'] = $token->getOriginalToken()->getUsername(); + } else { + foreach ($token->getRoles(false) as $role) { + if ($role instanceof SwitchUserRole) { + $context['impersonator_username'] = $role->getSource(false)->getUsername(); + break; + } } } diff --git a/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php b/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php index 4aff3b45811fa..102ab30615209 100644 --- a/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/SwitchUserListener.php @@ -17,8 +17,8 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException; @@ -122,7 +122,7 @@ private function attemptSwitchUser(Request $request, $username) $token = $this->tokenStorage->getToken(); $originalToken = $this->getOriginalToken($token); - if (false !== $originalToken) { + if (null !== $originalToken) { if ($token->getUsername() === $username) { return $token; } @@ -146,9 +146,9 @@ private function attemptSwitchUser(Request $request, $username) $this->userChecker->checkPostAuth($user); $roles = $user->getRoles(); - $roles[] = new SwitchUserRole('ROLE_PREVIOUS_ADMIN', $this->tokenStorage->getToken()); + $roles[] = new SwitchUserRole('ROLE_PREVIOUS_ADMIN', $this->tokenStorage->getToken(), false); - $token = new UsernamePasswordToken($user, $user->getPassword(), $this->providerKey, $roles); + $token = new SwitchUserToken($user, $user->getPassword(), $this->providerKey, $roles, $token); if (null !== $this->dispatcher) { $switchEvent = new SwitchUserEvent($request, $token->getUser(), $token); @@ -169,7 +169,7 @@ private function attemptSwitchUser(Request $request, $username) */ private function attemptExitUser(Request $request) { - if (false === $original = $this->getOriginalToken($this->tokenStorage->getToken())) { + if (null === ($currentToken = $this->tokenStorage->getToken()) || null === $original = $this->getOriginalToken($currentToken)) { throw new AuthenticationCredentialsNotFoundException('Could not find original Token object.'); } @@ -183,19 +183,18 @@ private function attemptExitUser(Request $request) return $original; } - /** - * Gets the original Token from a switched one. - * - * @return TokenInterface|false The original TokenInterface instance, false if the current TokenInterface is not switched - */ - private function getOriginalToken(TokenInterface $token) + private function getOriginalToken(TokenInterface $token): ?TokenInterface { - foreach ($token->getRoles() as $role) { + if ($token instanceof SwitchUserToken) { + return $token->getOriginalToken(); + } + + foreach ($token->getRoles(false) as $role) { if ($role instanceof SwitchUserRole) { return $role->getSource(); } } - return false; + return null; } } diff --git a/src/Symfony/Component/Security/Http/Tests/Controller/UserValueResolverTest.php b/src/Symfony/Component/Security/Http/Tests/Controller/UserValueResolverTest.php index 25108e482d287..8bcb960aa66ed 100644 --- a/src/Symfony/Component/Security/Http/Tests/Controller/UserValueResolverTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Controller/UserValueResolverTest.php @@ -18,6 +18,7 @@ use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Http\Controller\UserValueResolver; @@ -35,7 +36,7 @@ public function testResolveNoToken() public function testResolveNoUser() { $mock = $this->getMockBuilder(UserInterface::class)->getMock(); - $token = $this->getMockBuilder(TokenInterface::class)->getMock(); + $token = new UsernamePasswordToken('username', 'password', 'provider'); $tokenStorage = new TokenStorage(); $tokenStorage->setToken($token); @@ -57,8 +58,7 @@ public function testResolveWrongType() public function testResolve() { $user = $this->getMockBuilder(UserInterface::class)->getMock(); - $token = $this->getMockBuilder(TokenInterface::class)->getMock(); - $token->expects($this->any())->method('getUser')->willReturn($user); + $token = new UsernamePasswordToken($user, 'password', 'provider'); $tokenStorage = new TokenStorage(); $tokenStorage->setToken($token); @@ -72,8 +72,7 @@ public function testResolve() public function testIntegration() { $user = $this->getMockBuilder(UserInterface::class)->getMock(); - $token = $this->getMockBuilder(TokenInterface::class)->getMock(); - $token->expects($this->any())->method('getUser')->willReturn($user); + $token = new UsernamePasswordToken($user, 'password', 'provider'); $tokenStorage = new TokenStorage(); $tokenStorage->setToken($token); @@ -83,7 +82,7 @@ public function testIntegration() public function testIntegrationNoUser() { - $token = $this->getMockBuilder(TokenInterface::class)->getMock(); + $token = new UsernamePasswordToken('username', 'password', 'provider'); $tokenStorage = new TokenStorage(); $tokenStorage->setToken($token); diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/SwitchUserListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/SwitchUserListenerTest.php index f26b72fc148e1..1468df66126bf 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/SwitchUserListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/SwitchUserListenerTest.php @@ -16,6 +16,7 @@ use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; +use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Role\SwitchUserRole; use Symfony\Component\Security\Core\User\User; @@ -93,7 +94,7 @@ public function testExitUserThrowsAuthenticationExceptionIfOriginalTokenCannotBe public function testExitUserUpdatesToken() { $originalToken = new UsernamePasswordToken('username', '', 'key', []); - $this->tokenStorage->setToken(new UsernamePasswordToken('username', '', 'key', [new SwitchUserRole('ROLE_PREVIOUS', $originalToken)])); + $this->tokenStorage->setToken(new SwitchUserToken('username', '', 'key', [new SwitchUserRole('ROLE_PREVIOUS', $originalToken, false)], $originalToken)); $this->request->query->set('_switch_user', SwitchUserListener::EXIT_VALUE); @@ -107,6 +108,22 @@ public function testExitUserUpdatesToken() $this->assertSame($originalToken, $this->tokenStorage->getToken()); } + /** + * @group legacy + */ + public function testExitUserBasedOnSwitchUserRoleUpdatesToken() + { + $originalToken = new UsernamePasswordToken('username', '', 'key', array()); + $this->tokenStorage->setToken(new UsernamePasswordToken('username', '', 'key', array(new SwitchUserRole('ROLE_PREVIOUS', $originalToken, false)), $originalToken)); + + $this->request->query->set('_switch_user', SwitchUserListener::EXIT_VALUE); + + $listener = new SwitchUserListener($this->tokenStorage, $this->userProvider, $this->userChecker, 'provider123', $this->accessDecisionManager); + $listener->handle($this->event); + + $this->assertSame($originalToken, $this->tokenStorage->getToken()); + } + public function testExitUserDispatchesEventWithRefreshedUser() { $originalUser = $this->getMockBuilder('Symfony\Component\Security\Core\User\UserInterface')->getMock(); @@ -118,7 +135,7 @@ public function testExitUserDispatchesEventWithRefreshedUser() ->with($originalUser) ->willReturn($refreshedUser); $originalToken = new UsernamePasswordToken($originalUser, '', 'key'); - $this->tokenStorage->setToken(new UsernamePasswordToken('username', '', 'key', [new SwitchUserRole('ROLE_PREVIOUS', $originalToken)])); + $this->tokenStorage->setToken(new SwitchUserToken('username', '', 'key', [new SwitchUserRole('ROLE_PREVIOUS', $originalToken, false)], $originalToken)); $this->request->query->set('_switch_user', SwitchUserListener::EXIT_VALUE); $dispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock(); @@ -142,7 +159,7 @@ public function testExitUserDoesNotDispatchEventWithStringUser() ->expects($this->never()) ->method('refreshUser'); $originalToken = new UsernamePasswordToken($originalUser, '', 'key'); - $this->tokenStorage->setToken(new UsernamePasswordToken('username', '', 'key', [new SwitchUserRole('ROLE_PREVIOUS', $originalToken)])); + $this->tokenStorage->setToken(new SwitchUserToken('username', '', 'key', [new SwitchUserRole('ROLE_PREVIOUS', $originalToken, false)], $originalToken)); $this->request->query->set('_switch_user', SwitchUserListener::EXIT_VALUE); $dispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock(); diff --git a/src/Symfony/Component/Security/Http/Tests/Logout/LogoutUrlGeneratorTest.php b/src/Symfony/Component/Security/Http/Tests/Logout/LogoutUrlGeneratorTest.php index 727dde0f81b30..12166602474ba 100644 --- a/src/Symfony/Component/Security/Http/Tests/Logout/LogoutUrlGeneratorTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Logout/LogoutUrlGeneratorTest.php @@ -95,7 +95,7 @@ public function testGuessFromCurrentFirewallContext() public function testGuessFromTokenWithoutProviderKeyFallbacksToCurrentFirewall() { - $this->tokenStorage->setToken($this->getMockBuilder(TokenInterface::class)->getMock()); + $this->tokenStorage->setToken(new UsernamePasswordToken('username', 'password', 'provider')); $this->generator->registerListener('secured_area', '/logout', null, null); $this->generator->setCurrentFirewall('secured_area'); diff --git a/src/Symfony/Component/Security/Http/composer.json b/src/Symfony/Component/Security/Http/composer.json index 7047eba06e9e8..7bfac0fdcf85f 100644 --- a/src/Symfony/Component/Security/Http/composer.json +++ b/src/Symfony/Component/Security/Http/composer.json @@ -17,7 +17,7 @@ ], "require": { "php": "^7.1.3", - "symfony/security-core": "~3.4|~4.0", + "symfony/security-core": "~4.3", "symfony/event-dispatcher": "~3.4|~4.0", "symfony/http-foundation": "~3.4|~4.0", "symfony/http-kernel": "~3.4|~4.0", diff --git a/src/Symfony/Component/Workflow/EventListener/GuardListener.php b/src/Symfony/Component/Workflow/EventListener/GuardListener.php index 3a9b417f67978..60cd00ed2084f 100644 --- a/src/Symfony/Component/Workflow/EventListener/GuardListener.php +++ b/src/Symfony/Component/Workflow/EventListener/GuardListener.php @@ -14,6 +14,8 @@ use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Symfony\Component\Security\Core\Role\Role; +use Symfony\Component\Security\Core\Role\RoleHierarchy; use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Workflow\Event\GuardEvent; @@ -80,19 +82,23 @@ private function getVariables(GuardEvent $event): array throw new InvalidTokenConfigurationException(sprintf('There are no tokens available for workflow %s.', $event->getWorkflowName())); } - if (null !== $this->roleHierarchy) { - $roles = $this->roleHierarchy->getReachableRoles($token->getRoles()); + if (method_exists($token, 'getRoleNames')) { + $roles = $token->getRoleNames(); } else { - $roles = $token->getRoles(); + $roles = array_map(function (Role $role) { return $role->getRole(); }, $token->getRoles(false)); + } + + if ($this->roleHierarchy instanceof RoleHierarchy) { + $roles = $this->roleHierarchy->getReachableRoleNames($roles); + } elseif (null !== $this->roleHierarchy) { + $roles = $this->roleHierarchy->getReachableRoles($token->getRoles(false)); } $variables = [ 'token' => $token, 'user' => $token->getUser(), 'subject' => $event->getSubject(), - 'roles' => array_map(function ($role) { - return $role->getRole(); - }, $roles), + 'roles' => $roles, // needed for the is_granted expression function 'auth_checker' => $this->authorizationChecker, // needed for the is_* expression function diff --git a/src/Symfony/Component/Workflow/Tests/EventListener/GuardListenerTest.php b/src/Symfony/Component/Workflow/Tests/EventListener/GuardListenerTest.php index 64df594b235b8..8e7a9cb779749 100644 --- a/src/Symfony/Component/Workflow/Tests/EventListener/GuardListenerTest.php +++ b/src/Symfony/Component/Workflow/Tests/EventListener/GuardListenerTest.php @@ -6,6 +6,7 @@ use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\Role\Role; use Symfony\Component\Validator\Validator\ValidatorInterface; @@ -35,8 +36,7 @@ protected function setUp() ], ]; $expressionLanguage = new ExpressionLanguage(); - $token = $this->getMockBuilder(TokenInterface::class)->getMock(); - $token->expects($this->any())->method('getRoles')->willReturn([new Role('ROLE_USER')]); + $token = new UsernamePasswordToken('username', 'credentials', 'provider', ['ROLE_USER']); $tokenStorage = $this->getMockBuilder(TokenStorageInterface::class)->getMock(); $tokenStorage->expects($this->any())->method('getToken')->willReturn($token); $this->authenticationChecker = $this->getMockBuilder(AuthorizationCheckerInterface::class)->getMock(); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy