diff --git a/UPGRADE-5.2.md b/UPGRADE-5.2.md index 0eaced68a90e..112866537f47 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 c0981d698c8d..37171f723fca 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 cbc411fad730..249d8d1cf15f 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 000000000000..9cc2ac4afe7e --- /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 ac24795d9982..c51551a0d580 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 0c066aeee3b6..12e78ad46065 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 b218e1086c62..14f62d38b051 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 154addc7c409..e99a12b35b05 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 7dfb787d4aa0..cb924c6f1e79 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