diff --git a/Domain/SecurityIdentityRetrievalStrategy.php b/Domain/SecurityIdentityRetrievalStrategy.php index 175cbde..d1e9453 100644 --- a/Domain/SecurityIdentityRetrievalStrategy.php +++ b/Domain/SecurityIdentityRetrievalStrategy.php @@ -14,6 +14,7 @@ use Symfony\Component\Security\Acl\Model\SecurityIdentityRetrievalStrategyInterface; use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface; use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; +use Symfony\Component\Security\Core\Authentication\Token\NullToken; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter; use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; @@ -39,13 +40,15 @@ public function __construct(RoleHierarchyInterface $roleHierarchy, Authenticatio /** * {@inheritdoc} + * + * @return RoleSecurityIdentity[] */ public function getSecurityIdentities(TokenInterface $token) { $sids = []; // add user security identity - if (!$token instanceof AnonymousToken) { + if (!$token instanceof AnonymousToken && !$token instanceof NullToken) { try { $sids[] = UserSecurityIdentity::fromToken($token); } catch (\InvalidArgumentException $e) { @@ -62,14 +65,31 @@ public function getSecurityIdentities(TokenInterface $token) if ($this->authenticationTrustResolver->isFullFledged($token)) { $sids[] = new RoleSecurityIdentity(AuthenticatedVoter::IS_AUTHENTICATED_FULLY); $sids[] = new RoleSecurityIdentity(AuthenticatedVoter::IS_AUTHENTICATED_REMEMBERED); - $sids[] = new RoleSecurityIdentity(AuthenticatedVoter::IS_AUTHENTICATED_ANONYMOUSLY); + $this->addAnonymousRoles($sids); } elseif ($this->authenticationTrustResolver->isRememberMe($token)) { $sids[] = new RoleSecurityIdentity(AuthenticatedVoter::IS_AUTHENTICATED_REMEMBERED); - $sids[] = new RoleSecurityIdentity(AuthenticatedVoter::IS_AUTHENTICATED_ANONYMOUSLY); - } elseif ($this->authenticationTrustResolver->isAnonymous($token)) { - $sids[] = new RoleSecurityIdentity(AuthenticatedVoter::IS_AUTHENTICATED_ANONYMOUSLY); + $this->addAnonymousRoles($sids); + } elseif ($this->isNotAuthenticated($token)) { + $this->addAnonymousRoles($sids); } return $sids; } + + private function isNotAuthenticated(TokenInterface $token): bool + { + if (\defined('\Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter::PUBLIC_ACCESS')) { + return !$this->authenticationTrustResolver->isAuthenticated($token); + } + + return $this->authenticationTrustResolver->isAnonymous($token); + } + + private function addAnonymousRoles(array &$sids) + { + $sids[] = new RoleSecurityIdentity('IS_AUTHENTICATED_ANONYMOUSLY'); + if (\defined('\Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter::PUBLIC_ACCESS')) { + $sids[] = new RoleSecurityIdentity(AuthenticatedVoter::PUBLIC_ACCESS); + } + } } diff --git a/Tests/Domain/SecurityIdentityRetrievalStrategyTest.php b/Tests/Domain/SecurityIdentityRetrievalStrategyTest.php index 81e3ef8..68ff67b 100644 --- a/Tests/Domain/SecurityIdentityRetrievalStrategyTest.php +++ b/Tests/Domain/SecurityIdentityRetrievalStrategyTest.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Security\Acl\Tests\Domain; -use PHPUnit\Framework\Assert; use PHPUnit\Framework\TestCase; use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity; use Symfony\Component\Security\Acl\Domain\SecurityIdentityRetrievalStrategy; @@ -20,7 +19,10 @@ use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface; use Symfony\Component\Security\Core\Authentication\Token\AbstractToken; use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; +use Symfony\Component\Security\Core\Authentication\Token\NullToken; +use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter; use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; +use Symfony\Component\Security\Core\User\UserInterface; class SecurityIdentityRetrievalStrategyTest extends TestCase { @@ -29,6 +31,54 @@ class SecurityIdentityRetrievalStrategyTest extends TestCase */ public function testGetSecurityIdentities($user, array $roles, string $authenticationStatus, array $sids) { + $token = class_exists(NullToken::class) ? new NullToken() : new AnonymousToken('', ''); + if ('anonymous' !== $authenticationStatus) { + $class = ''; + if (\is_string($user)) { + $class = 'MyCustomTokenImpl'; + } + + $token = $this->getMockBuilder(AbstractToken::class) + ->setMockClassName($class) + ->getMock(); + + $token + ->expects($this->once()) + ->method('getRoleNames') + ->willReturn(['foo']) + ; + + $token + ->expects($this->once()) + ->method('getUser') + ->willReturn($user) + ; + } + + $strategy = $this->getStrategy($roles, $authenticationStatus); + $extractedSids = $strategy->getSecurityIdentities($token); + + foreach ($extractedSids as $index => $extractedSid) { + if (!isset($sids[$index])) { + $this->fail(sprintf('Expected SID at index %d, but there was none.', $index)); + } + + if (false === $sids[$index]->equals($extractedSid)) { + $this->fail(sprintf('Index: %d, expected SID "%s", but got "%s".', $index, $sids[$index], (string) $extractedSid)); + } + } + } + + /** + * @group legacy + * @dataProvider getDeprecatedSecurityIdentityRetrievalTests + */ + public function testDeprecatedGetSecurityIdentities($user, array $roles, string $authenticationStatus, array $sids) + { + if (method_exists(AuthenticationTrustResolverInterface::class, 'isAuthenticated')) { + $this->markTestSkipped(); + } + if ('anonymous' === $authenticationStatus) { $token = $this->getMockBuilder(AnonymousToken::class) ->disableOriginalConstructor() @@ -69,50 +119,62 @@ public function testGetSecurityIdentities($user, array $roles, string $authentic foreach ($extractedSids as $index => $extractedSid) { if (!isset($sids[$index])) { - $this->fail(sprintf('Expected SID at index %d, but there was none.', true)); + $this->fail(sprintf('Expected SID at index %d, but there was none.', $index)); } if (false === $sids[$index]->equals($extractedSid)) { - $this->fail(sprintf('Index: %d, expected SID "%s", but got "%s".', $index, $sids[$index], $extractedSid)); + $this->fail(sprintf('Index: %d, expected SID "%s", but got "%s".', $index, $sids[$index], (string) $extractedSid)); } } } public function getSecurityIdentityRetrievalTests(): array { + $anonymousRoles = [new RoleSecurityIdentity('IS_AUTHENTICATED_ANONYMOUSLY')]; + if (\defined('\Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter::PUBLIC_ACCESS')) { + $anonymousRoles[] = new RoleSecurityIdentity(AuthenticatedVoter::PUBLIC_ACCESS); + } + return [ - [new Account('johannes'), ['ROLE_USER', 'ROLE_SUPERADMIN'], 'fullFledged', [ + [new Account('johannes'), ['ROLE_USER', 'ROLE_SUPERADMIN'], 'fullFledged', array_merge([ new UserSecurityIdentity('johannes', Account::class), new RoleSecurityIdentity('ROLE_USER'), new RoleSecurityIdentity('ROLE_SUPERADMIN'), new RoleSecurityIdentity('IS_AUTHENTICATED_FULLY'), new RoleSecurityIdentity('IS_AUTHENTICATED_REMEMBERED'), - new RoleSecurityIdentity('IS_AUTHENTICATED_ANONYMOUSLY'), - ]], - ['johannes', ['ROLE_FOO'], 'fullFledged', [ - new UserSecurityIdentity('johannes', 'MyCustomTokenImpl'), - new RoleSecurityIdentity('ROLE_FOO'), - new RoleSecurityIdentity('IS_AUTHENTICATED_FULLY'), - new RoleSecurityIdentity('IS_AUTHENTICATED_REMEMBERED'), - new RoleSecurityIdentity('IS_AUTHENTICATED_ANONYMOUSLY'), - ]], - [new CustomUserImpl('johannes'), ['ROLE_FOO'], 'fullFledged', [ + ], $anonymousRoles)], + [new CustomUserImpl('johannes'), ['ROLE_FOO'], 'fullFledged', array_merge([ new UserSecurityIdentity('johannes', CustomUserImpl::class), new RoleSecurityIdentity('ROLE_FOO'), new RoleSecurityIdentity('IS_AUTHENTICATED_FULLY'), new RoleSecurityIdentity('IS_AUTHENTICATED_REMEMBERED'), - new RoleSecurityIdentity('IS_AUTHENTICATED_ANONYMOUSLY'), - ]], - [new Account('foo'), ['ROLE_FOO'], 'rememberMe', [ + ], $anonymousRoles)], + [new Account('foo'), ['ROLE_FOO'], 'rememberMe', array_merge([ new UserSecurityIdentity('foo', Account::class), new RoleSecurityIdentity('ROLE_FOO'), new RoleSecurityIdentity('IS_AUTHENTICATED_REMEMBERED'), - new RoleSecurityIdentity('IS_AUTHENTICATED_ANONYMOUSLY'), - ]], - ['guest', ['ROLE_FOO'], 'anonymous', [ + ], $anonymousRoles)], + ['guest', [], 'anonymous', $anonymousRoles], + ]; + } + + public function getDeprecatedSecurityIdentityRetrievalTests() + { + $anonymousRoles = [new RoleSecurityIdentity('IS_AUTHENTICATED_ANONYMOUSLY')]; + if (\defined('\Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter::PUBLIC_ACCESS')) { + $anonymousRoles[] = new RoleSecurityIdentity(AuthenticatedVoter::PUBLIC_ACCESS); + } + + return [ + ['johannes', ['ROLE_FOO'], 'fullFledged', array_merge([ + new UserSecurityIdentity('johannes', 'MyCustomTokenImpl'), + new RoleSecurityIdentity('ROLE_FOO'), + new RoleSecurityIdentity('IS_AUTHENTICATED_FULLY'), + new RoleSecurityIdentity('IS_AUTHENTICATED_REMEMBERED'), + ], $anonymousRoles)], + ['guest', ['ROLE_FOO'], 'anonymous', array_merge([ new RoleSecurityIdentity('ROLE_FOO'), - new RoleSecurityIdentity('IS_AUTHENTICATED_ANONYMOUSLY'), - ]], + ], $anonymousRoles)], ]; } @@ -128,18 +190,30 @@ public function __construct(array $roles) public function getReachableRoleNames(array $roles): array { - Assert::assertSame(['foo'], $roles); - return $this->roles; } }; - $trustResolver = $this->createMock(AuthenticationTrustResolverInterface::class); - - $trustResolver - ->method('isAnonymous') - ->willReturn('anonymous' === $authenticationStatus) - ; + $trustResolverMockBuild = $this->getMockBuilder(AuthenticationTrustResolverInterface::class); + if (\defined('\Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter::PUBLIC_ACCESS')) { + if (method_exists(AuthenticationTrustResolverInterface::class, 'isAuthenticated')) { + $trustResolver = $trustResolverMockBuild->getMock(); + } else { + $trustResolver = $trustResolverMockBuild + ->onlyMethods(['isAnonymous', 'isRememberMe', 'isFullFledged']) + ->addMethods(['isAuthenticated']) + ->getMock() + ; + } + $trustResolver + ->method('isAuthenticated') + ->willReturn('anonymous' !== $authenticationStatus); + } else { + $trustResolver = $trustResolverMockBuild->getMock(); + $trustResolver + ->method('isAnonymous') + ->willReturn('anonymous' === $authenticationStatus); + } if ('fullFledged' === $authenticationStatus) { $trustResolver @@ -163,10 +237,17 @@ public function getReachableRoleNames(array $roles): array ->willReturn(true) ; } else { - $trustResolver - ->method('isAnonymous') - ->willReturn(true) - ; + if (method_exists(AuthenticationTrustResolverInterface::class, 'isAuthenticated')) { + $trustResolver + ->method('isAuthenticated') + ->willReturn(false) + ; + } else { + $trustResolver + ->method('isAnonymous') + ->willReturn(true); + } + $trustResolver ->expects($this->once()) ->method('isFullFledged') @@ -183,7 +264,7 @@ public function getReachableRoleNames(array $roles): array } } -class CustomUserImpl +class CustomUserImpl implements UserInterface { protected $name; @@ -196,4 +277,33 @@ public function __toString() { return $this->name; } + + public function getRoles(): array + { + return []; + } + + public function eraseCredentials() + { + } + + public function getUserIdentifier(): string + { + return $this->name; + } + + public function getPassword() + { + return null; + } + + public function getSalt() + { + return null; + } + + public function getUsername(): string + { + return $this->getUserIdentifier(); + } }