diff --git a/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php b/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php index e78f21826f362..83aba4166a6e4 100644 --- a/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php @@ -87,7 +87,7 @@ public function authenticate(RequestEvent $event) if (null !== $this->csrfTokenManager) { $csrfToken = ParameterBagUtils::getRequestParameterValue($request, $this->options['csrf_parameter']); - if (false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['csrf_token_id'], $csrfToken))) { + if (!\is_string($csrfToken) || false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['csrf_token_id'], $csrfToken))) { throw new LogoutException('Invalid CSRF token.'); } } diff --git a/src/Symfony/Component/Security/Http/Firewall/SimpleFormAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/SimpleFormAuthenticationListener.php index f158d906a4c5f..997e2e7ba04eb 100644 --- a/src/Symfony/Component/Security/Http/Firewall/SimpleFormAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/SimpleFormAuthenticationListener.php @@ -84,7 +84,7 @@ protected function attemptAuthentication(Request $request) if (null !== $this->csrfTokenManager) { $csrfToken = ParameterBagUtils::getRequestParameterValue($request, $this->options['csrf_parameter']); - if (false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['csrf_token_id'], $csrfToken))) { + if (!\is_string($csrfToken) || false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['csrf_token_id'], $csrfToken))) { throw new InvalidCsrfTokenException('Invalid CSRF token.'); } } diff --git a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php index b3661eae8afd1..3aaa6dd71a1af 100644 --- a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php @@ -72,7 +72,7 @@ protected function attemptAuthentication(Request $request) if (null !== $this->csrfTokenManager) { $csrfToken = ParameterBagUtils::getRequestParameterValue($request, $this->options['csrf_parameter']); - if (false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['csrf_token_id'], $csrfToken))) { + if (!\is_string($csrfToken) || false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['csrf_token_id'], $csrfToken))) { throw new InvalidCsrfTokenException('Invalid CSRF token.'); } } diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/LogoutListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/LogoutListenerTest.php index 397639fd940f7..4e86dccdd8c39 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/LogoutListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/LogoutListenerTest.php @@ -152,7 +152,10 @@ public function testSuccessHandlerReturnsNonResponse() $listener($event); } - public function testCsrfValidationFails() + /** + * @dataProvider provideInvalidCsrfTokens + */ + public function testCsrfValidationFails($invalidToken) { $this->expectException(LogoutException::class); $tokenManager = $this->getTokenManager(); @@ -160,20 +163,31 @@ public function testCsrfValidationFails() [$listener, , $httpUtils, $options] = $this->getListener(null, $tokenManager); $request = new Request(); - $request->query->set('_csrf_token', 'token'); + if (null !== $invalidToken) { + $request->query->set('_csrf_token', $invalidToken); + } $httpUtils->expects($this->once()) ->method('checkRequestPath') ->with($request, $options['logout_path']) ->willReturn(true); - $tokenManager->expects($this->once()) + $tokenManager ->method('isTokenValid') ->willReturn(false); $listener(new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST)); } + public function provideInvalidCsrfTokens(): array + { + return [ + ['invalid'], + [['in' => 'valid']], + [null], + ]; + } + private function getTokenManager() { return $this->createMock(CsrfTokenManagerInterface::class); diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/UsernamePasswordFormAuthenticationListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/UsernamePasswordFormAuthenticationListenerTest.php index 312014cd1a6f5..e6d9e06d8b698 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/UsernamePasswordFormAuthenticationListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/UsernamePasswordFormAuthenticationListenerTest.php @@ -24,6 +24,7 @@ use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Security; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler; use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler; @@ -37,7 +38,7 @@ class UsernamePasswordFormAuthenticationListenerTest extends TestCase /** * @dataProvider getUsernameForLength */ - public function testHandleWhenUsernameLength($username, $ok) + public function testHandleWhenUsernameLength(string $username, bool $ok) { $request = Request::create('/login_check', 'POST', ['_username' => $username]); $request->setSession($this->createMock(SessionInterface::class)); @@ -84,10 +85,8 @@ public function testHandleWhenUsernameLength($username, $ok) /** * @dataProvider postOnlyDataProvider */ - public function testHandleNonStringUsernameWithArray($postOnly) + public function testHandleNonStringUsernameWithArray(bool $postOnly) { - $this->expectException(BadRequestHttpException::class); - $this->expectExceptionMessage('The key "_username" must be a string, "array" given.'); $request = Request::create('/login_check', 'POST', ['_username' => []]); $request->setSession($this->createMock(SessionInterface::class)); $listener = new UsernamePasswordFormAuthenticationListener( @@ -101,16 +100,18 @@ public function testHandleNonStringUsernameWithArray($postOnly) ['require_previous_session' => false, 'post_only' => $postOnly] ); $event = new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST); + + $this->expectException(BadRequestHttpException::class); + $this->expectExceptionMessage('The key "_username" must be a string, "array" given.'); + $listener($event); } /** * @dataProvider postOnlyDataProvider */ - public function testHandleNonStringUsernameWithInt($postOnly) + public function testHandleNonStringUsernameWithInt(bool $postOnly) { - $this->expectException(BadRequestHttpException::class); - $this->expectExceptionMessage('The key "_username" must be a string, "integer" given.'); $request = Request::create('/login_check', 'POST', ['_username' => 42]); $request->setSession($this->createMock(SessionInterface::class)); $listener = new UsernamePasswordFormAuthenticationListener( @@ -124,16 +125,18 @@ public function testHandleNonStringUsernameWithInt($postOnly) ['require_previous_session' => false, 'post_only' => $postOnly] ); $event = new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST); + + $this->expectException(BadRequestHttpException::class); + $this->expectExceptionMessage('The key "_username" must be a string, "integer" given.'); + $listener($event); } /** * @dataProvider postOnlyDataProvider */ - public function testHandleNonStringUsernameWithObject($postOnly) + public function testHandleNonStringUsernameWithObject(bool $postOnly) { - $this->expectException(BadRequestHttpException::class); - $this->expectExceptionMessage('The key "_username" must be a string, "object" given.'); $request = Request::create('/login_check', 'POST', ['_username' => new \stdClass()]); $request->setSession($this->createMock(SessionInterface::class)); $listener = new UsernamePasswordFormAuthenticationListener( @@ -147,13 +150,17 @@ public function testHandleNonStringUsernameWithObject($postOnly) ['require_previous_session' => false, 'post_only' => $postOnly] ); $event = new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST); + + $this->expectException(BadRequestHttpException::class); + $this->expectExceptionMessage('The key "_username" must be a string, "object" given.'); + $listener($event); } /** * @dataProvider postOnlyDataProvider */ - public function testHandleNonStringUsernameWith__toString($postOnly) + public function testHandleNonStringUsernameWith__toString(bool $postOnly) { $usernameClass = $this->createMock(DummyUserClass::class); $usernameClass @@ -177,7 +184,63 @@ public function testHandleNonStringUsernameWith__toString($postOnly) $listener($event); } - public function postOnlyDataProvider() + /** + * @dataProvider provideInvalidCsrfTokens + */ + public function testInvalidCsrfToken($invalidToken) + { + $formBody = ['_username' => 'fabien', '_password' => 'symfony']; + if (null !== $invalidToken) { + $formBody['_csrf_token'] = $invalidToken; + } + + $request = Request::create('/login_check', 'POST', $formBody); + $request->setSession($this->createMock(SessionInterface::class)); + + $httpUtils = $this->createMock(HttpUtils::class); + $httpUtils + ->method('checkRequestPath') + ->willReturn(true) + ; + $httpUtils + ->method('createRedirectResponse') + ->willReturn(new RedirectResponse('/hello')) + ; + + $failureHandler = $this->createMock(AuthenticationFailureHandlerInterface::class); + $failureHandler + ->expects($this->once()) + ->method('onAuthenticationFailure') + ->willReturn(new Response()) + ; + + $authenticationManager = $this->createMock(AuthenticationProviderManager::class); + $authenticationManager + ->expects($this->never()) + ->method('authenticate') + ; + + $csrfTokenManager = $this->createMock(CsrfTokenManagerInterface::class); + $csrfTokenManager->method('isTokenValid')->willReturn(false); + + $listener = new UsernamePasswordFormAuthenticationListener( + $this->createMock(TokenStorageInterface::class), + $authenticationManager, + $this->createMock(SessionAuthenticationStrategyInterface::class), + $httpUtils, + 'TheProviderKey', + new DefaultAuthenticationSuccessHandler($httpUtils), + $failureHandler, + ['require_previous_session' => false], + null, + null, + $csrfTokenManager + ); + + $listener(new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MASTER_REQUEST)); + } + + public function postOnlyDataProvider(): array { return [ [true], @@ -185,13 +248,22 @@ public function postOnlyDataProvider() ]; } - public function getUsernameForLength() + public function getUsernameForLength(): array { return [ [str_repeat('x', Security::MAX_USERNAME_LENGTH + 1), false], [str_repeat('x', Security::MAX_USERNAME_LENGTH - 1), true], ]; } + + public function provideInvalidCsrfTokens(): array + { + return [ + ['invalid'], + [['in' => 'valid']], + [null], + ]; + } } class DummyUserClass
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: