From e37091541c2cd3a3478d9a9f7b66806557f7a02d Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Mon, 20 Jul 2020 20:36:06 +0200 Subject: [PATCH] Use NullToken while checking authorization This allows to e.g. have some objects that can be viewed by anyone (even unauthenticated users). --- UPGRADE-5.2.md | 6 + src/Symfony/Component/Security/CHANGELOG.md | 3 +- .../AuthenticationTrustResolver.php | 3 +- .../Core/Authentication/Token/NullToken.php | 105 ++++++++++++++++++ .../Authorization/AuthorizationChecker.php | 11 +- .../AuthorizationCheckerTest.php | 9 +- .../Security/Http/Firewall/AccessListener.php | 15 +-- .../Tests/Firewall/AccessListenerTest.php | 29 +++-- .../Component/Security/Http/composer.json | 2 +- 9 files changed, 154 insertions(+), 29 deletions(-) create mode 100644 src/Symfony/Component/Security/Core/Authentication/Token/NullToken.php diff --git a/UPGRADE-5.2.md b/UPGRADE-5.2.md index 0eaced68a90ef..112866537f47f 100644 --- a/UPGRADE-5.2.md +++ b/UPGRADE-5.2.md @@ -43,3 +43,9 @@ Validator * }) */ ``` + +Security +-------- + + * [BC break] In the experimental authenticator-based system, * `TokenInterface::getUser()` + returns `null` in case of unauthenticated session. diff --git a/src/Symfony/Component/Security/CHANGELOG.md b/src/Symfony/Component/Security/CHANGELOG.md index c0981d698c8d8..37171f723fca8 100644 --- a/src/Symfony/Component/Security/CHANGELOG.md +++ b/src/Symfony/Component/Security/CHANGELOG.md @@ -4,7 +4,8 @@ CHANGELOG 5.2.0 ----- - * Added attributes on ``Passport`` + * Added attributes on `Passport` + * Changed `AuthorizationChecker` to call the access decision manager in unauthenticated sessions with a `NullToken` 5.1.0 ----- diff --git a/src/Symfony/Component/Security/Core/Authentication/AuthenticationTrustResolver.php b/src/Symfony/Component/Security/Core/Authentication/AuthenticationTrustResolver.php index cbc411fad730b..249d8d1cf15fc 100644 --- a/src/Symfony/Component/Security/Core/Authentication/AuthenticationTrustResolver.php +++ b/src/Symfony/Component/Security/Core/Authentication/AuthenticationTrustResolver.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Security\Core\Authentication; use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; +use Symfony\Component\Security\Core\Authentication\Token\NullToken; use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; @@ -31,7 +32,7 @@ public function isAnonymous(TokenInterface $token = null) return false; } - return $token instanceof AnonymousToken; + return $token instanceof AnonymousToken || $token instanceof NullToken; } /** diff --git a/src/Symfony/Component/Security/Core/Authentication/Token/NullToken.php b/src/Symfony/Component/Security/Core/Authentication/Token/NullToken.php new file mode 100644 index 0000000000000..9cc2ac4afe7e5 --- /dev/null +++ b/src/Symfony/Component/Security/Core/Authentication/Token/NullToken.php @@ -0,0 +1,105 @@ + + * + * 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; + +/** + * @author Wouter de Jong + */ +class NullToken implements TokenInterface +{ + public function __toString(): string + { + return ''; + } + + public function getRoleNames(): array + { + return []; + } + + public function getCredentials() + { + return ''; + } + + public function getUser() + { + return null; + } + + public function setUser($user) + { + throw new \BadMethodCallException('Cannot set user on a NullToken.'); + } + + public function getUsername() + { + return ''; + } + + public function isAuthenticated() + { + return true; + } + + public function setAuthenticated(bool $isAuthenticated) + { + throw new \BadMethodCallException('Cannot change authentication state of NullToken.'); + } + + public function eraseCredentials() + { + } + + public function getAttributes() + { + return []; + } + + public function setAttributes(array $attributes) + { + throw new \BadMethodCallException('Cannot set attributes of NullToken.'); + } + + public function hasAttribute(string $name) + { + return false; + } + + public function getAttribute(string $name) + { + return null; + } + + public function setAttribute(string $name, $value) + { + throw new \BadMethodCallException('Cannot add attribute to NullToken.'); + } + + public function __serialize(): array + { + return []; + } + + public function __unserialize(array $data): void + { + } + + public function serialize() + { + return ''; + } + + public function unserialize($serialized) + { + } +} diff --git a/src/Symfony/Component/Security/Core/Authorization/AuthorizationChecker.php b/src/Symfony/Component/Security/Core/Authorization/AuthorizationChecker.php index ac24795d99827..c51551a0d5807 100644 --- a/src/Symfony/Component/Security/Core/Authorization/AuthorizationChecker.php +++ b/src/Symfony/Component/Security/Core/Authorization/AuthorizationChecker.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Security\Core\Authorization; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; +use Symfony\Component\Security\Core\Authentication\Token\NullToken; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException; @@ -52,11 +53,11 @@ final public function isGranted($attribute, $subject = null): bool throw new AuthenticationCredentialsNotFoundException('The token storage contains no authentication token. One possible reason may be that there is no firewall configured for this URL.'); } - return false; - } - - if ($this->alwaysAuthenticate || !$token->isAuthenticated()) { - $this->tokenStorage->setToken($token = $this->authenticationManager->authenticate($token)); + $token = new NullToken(); + } else { + if ($this->alwaysAuthenticate || !$token->isAuthenticated()) { + $this->tokenStorage->setToken($token = $this->authenticationManager->authenticate($token)); + } } return $this->accessDecisionManager->decide($token, [$attribute], $subject); diff --git a/src/Symfony/Component/Security/Core/Tests/Authorization/AuthorizationCheckerTest.php b/src/Symfony/Component/Security/Core/Tests/Authorization/AuthorizationCheckerTest.php index 0c066aeee3b65..12e78ad46065f 100644 --- a/src/Symfony/Component/Security/Core/Tests/Authorization/AuthorizationCheckerTest.php +++ b/src/Symfony/Component/Security/Core/Tests/Authorization/AuthorizationCheckerTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Security\Core\Tests\Authorization; use PHPUnit\Framework\TestCase; +use Symfony\Component\Security\Core\Authentication\Token\NullToken; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Authorization\AuthorizationChecker; @@ -77,7 +78,13 @@ public function testVoteWithoutAuthenticationTokenAndExceptionOnNoTokenIsFalse() { $authorizationChecker = new AuthorizationChecker($this->tokenStorage, $this->authenticationManager, $this->accessDecisionManager, false, false); - $this->assertFalse($authorizationChecker->isGranted('ROLE_FOO')); + $this->accessDecisionManager + ->expects($this->once()) + ->method('decide') + ->with($this->isInstanceOf(NullToken::class)) + ->willReturn(true); + + $this->assertTrue($authorizationChecker->isGranted('ANONYMOUS')); } /** diff --git a/src/Symfony/Component/Security/Http/Firewall/AccessListener.php b/src/Symfony/Component/Security/Http/Firewall/AccessListener.php index b218e1086c62a..14f62d38b051d 100644 --- a/src/Symfony/Component/Security/Http/Firewall/AccessListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/AccessListener.php @@ -14,6 +14,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; +use Symfony\Component\Security\Core\Authentication\Token\NullToken; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter; @@ -89,19 +90,7 @@ public function authenticate(RequestEvent $event) throw new AuthenticationCredentialsNotFoundException('A Token was not found in the TokenStorage.'); } - if ([AuthenticatedVoter::IS_AUTHENTICATED_ANONYMOUSLY] === $attributes) { - trigger_deprecation('symfony/security-http', '5.1', 'Using "IS_AUTHENTICATED_ANONYMOUSLY" in your access_control rules when using the authenticator Security system is deprecated, use "PUBLIC_ACCESS" instead.'); - - return; - } - - if ([self::PUBLIC_ACCESS] !== $attributes) { - throw $this->createAccessDeniedException($request, $attributes); - } - } - - if ([self::PUBLIC_ACCESS] === $attributes) { - return; + $token = new NullToken(); } if (!$token->isAuthenticated()) { diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/AccessListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/AccessListenerTest.php index 154addc7c4095..e99a12b35b051 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/AccessListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/AccessListenerTest.php @@ -16,6 +16,7 @@ use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; +use Symfony\Component\Security\Core\Authentication\Token\NullToken; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; @@ -245,9 +246,15 @@ public function testHandleWhenTheSecurityTokenStorageHasNoTokenAndExceptionOnTok ->willReturn([['foo' => 'bar'], null]) ; + $accessDecisionManager = $this->createMock(AccessDecisionManagerInterface::class); + $accessDecisionManager->expects($this->once()) + ->method('decide') + ->with($this->isInstanceOf(NullToken::class)) + ->willReturn(false); + $listener = new AccessListener( $tokenStorage, - $this->getMockBuilder('Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface')->getMock(), + $accessDecisionManager, $accessMap, $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface')->getMock(), false @@ -268,17 +275,21 @@ public function testHandleWhenPublicAccessIsAllowedAndExceptionOnTokenIsFalse() ->willReturn([[AccessListener::PUBLIC_ACCESS], null]) ; + $accessDecisionManager = $this->createMock(AccessDecisionManagerInterface::class); + $accessDecisionManager->expects($this->once()) + ->method('decide') + ->with($this->isInstanceOf(NullToken::class), [AccessListener::PUBLIC_ACCESS]) + ->willReturn(true); + $listener = new AccessListener( $tokenStorage, - $this->getMockBuilder('Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface')->getMock(), + $accessDecisionManager, $accessMap, $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface')->getMock(), false ); $listener(new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST)); - - $this->expectNotToPerformAssertions(); } public function testHandleWhenPublicAccessWhileAuthenticated() @@ -295,17 +306,21 @@ public function testHandleWhenPublicAccessWhileAuthenticated() ->willReturn([[AccessListener::PUBLIC_ACCESS], null]) ; + $accessDecisionManager = $this->createMock(AccessDecisionManagerInterface::class); + $accessDecisionManager->expects($this->once()) + ->method('decide') + ->with($this->equalTo($token), [AccessListener::PUBLIC_ACCESS]) + ->willReturn(true); + $listener = new AccessListener( $tokenStorage, - $this->getMockBuilder('Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface')->getMock(), + $accessDecisionManager, $accessMap, $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface')->getMock(), false ); $listener(new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST)); - - $this->expectNotToPerformAssertions(); } public function testHandleMWithultipleAttributesShouldBeHandledAsAnd() diff --git a/src/Symfony/Component/Security/Http/composer.json b/src/Symfony/Component/Security/Http/composer.json index 7dfb787d4aa07..cb924c6f1e792 100644 --- a/src/Symfony/Component/Security/Http/composer.json +++ b/src/Symfony/Component/Security/Http/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=7.2.5", "symfony/deprecation-contracts": "^2.1", - "symfony/security-core": "^5.1", + "symfony/security-core": "^5.2", "symfony/http-foundation": "^4.4.7|^5.0.7", "symfony/http-kernel": "^4.4|^5.0", "symfony/polyfill-php80": "^1.15", 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