From 980900104c14b6c72e57adba9044d8484130ea5b Mon Sep 17 00:00:00 2001 From: Jeroeny Date: Tue, 22 Nov 2022 15:12:23 +0100 Subject: [PATCH] [Security] Support loading UserBadge directly from accessToken --- .../Security/Factory/AccessTokenFactory.php | 6 +++--- .../security_authenticator_access_token.php | 2 +- .../Security/Handler/AccessTokenHandler.php | 5 +++-- .../AccessTokenHandlerInterface.php | 3 ++- .../AccessTokenAuthenticator.php | 13 ++++++------ .../ChainedAccessTokenExtractorsTest.php | 13 ++++++------ ...ncodedBodyAccessTokenAuthenticatorTest.php | 19 +++++++++-------- .../HeaderAccessTokenAuthenticatorTest.php | 21 ++++++++++--------- .../QueryAccessTokenAuthenticatorTest.php | 19 +++++++++-------- .../InMemoryAccessTokenHandler.php | 7 ++++--- 10 files changed, 58 insertions(+), 50 deletions(-) diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AccessTokenFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AccessTokenFactory.php index 1e38c58643638..b59f7974f1e87 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AccessTokenFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AccessTokenFactory.php @@ -79,9 +79,9 @@ public function createAuthenticator(ContainerBuilder $container, string $firewal $container ->setDefinition($authenticatorId, new ChildDefinition('security.authenticator.access_token')) - ->replaceArgument(0, $userProvider) - ->replaceArgument(1, new Reference($config['token_handler'])) - ->replaceArgument(2, new Reference($extractorId)) + ->replaceArgument(0, new Reference($config['token_handler'])) + ->replaceArgument(1, new Reference($extractorId)) + ->replaceArgument(2, $userProvider) ->replaceArgument(3, $successHandler) ->replaceArgument(4, $failureHandler) ->replaceArgument(5, $config['realm']) diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator_access_token.php b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator_access_token.php index 17bc916c8d314..f1aea7cb2c3d1 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator_access_token.php +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator_access_token.php @@ -26,12 +26,12 @@ ->set('security.authenticator.access_token', AccessTokenAuthenticator::class) ->abstract() ->args([ - abstract_arg('user provider'), abstract_arg('access token handler'), abstract_arg('access token extractor'), null, null, null, + null, ]) ->call('setTranslator', [service('translator')->ignoreOnInvalid()]) diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Security/Handler/AccessTokenHandler.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Security/Handler/AccessTokenHandler.php index 0d1e9e0c0e7fc..4f94cc6936a05 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Security/Handler/AccessTokenHandler.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/Bundle/AccessTokenBundle/Security/Handler/AccessTokenHandler.php @@ -13,6 +13,7 @@ use Symfony\Component\Security\Core\Exception\BadCredentialsException; use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; class AccessTokenHandler implements AccessTokenHandlerInterface { @@ -20,10 +21,10 @@ public function __construct() { } - public function getUserIdentifierFrom(string $accessToken): string + public function getUserBadgeFrom(string $accessToken): UserBadge { return match ($accessToken) { - 'VALID_ACCESS_TOKEN' => 'dunglas', + 'VALID_ACCESS_TOKEN' => new UserBadge('dunglas'), default => throw new BadCredentialsException('Invalid credentials.'), }; } diff --git a/src/Symfony/Component/Security/Http/AccessToken/AccessTokenHandlerInterface.php b/src/Symfony/Component/Security/Http/AccessToken/AccessTokenHandlerInterface.php index 8044a371293aa..5cbc857a1c6f0 100644 --- a/src/Symfony/Component/Security/Http/AccessToken/AccessTokenHandlerInterface.php +++ b/src/Symfony/Component/Security/Http/AccessToken/AccessTokenHandlerInterface.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Security\Http\AccessToken; use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; /** * The token handler retrieves the user identifier from the token. @@ -24,5 +25,5 @@ interface AccessTokenHandlerInterface /** * @throws AuthenticationException */ - public function getUserIdentifierFrom(#[\SensitiveParameter] string $accessToken): string; + public function getUserBadgeFrom(#[\SensitiveParameter] string $accessToken): UserBadge; } diff --git a/src/Symfony/Component/Security/Http/Authenticator/AccessTokenAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/AccessTokenAuthenticator.php index ae8f0a4ea3da2..d3ade9b0be73b 100644 --- a/src/Symfony/Component/Security/Http/Authenticator/AccessTokenAuthenticator.php +++ b/src/Symfony/Component/Security/Http/Authenticator/AccessTokenAuthenticator.php @@ -21,7 +21,6 @@ use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface; use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; -use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use Symfony\Component\Security\Http\Authenticator\Passport\Passport; use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; use Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken; @@ -38,9 +37,9 @@ class AccessTokenAuthenticator implements AuthenticatorInterface private ?TranslatorInterface $translator = null; public function __construct( - private readonly UserProviderInterface $userProvider, private readonly AccessTokenHandlerInterface $accessTokenHandler, private readonly AccessTokenExtractorInterface $accessTokenExtractor, + private readonly ?UserProviderInterface $userProvider = null, private readonly ?AuthenticationSuccessHandlerInterface $successHandler = null, private readonly ?AuthenticationFailureHandlerInterface $failureHandler = null, private readonly ?string $realm = null, @@ -58,11 +57,13 @@ public function authenticate(Request $request): Passport if (!$accessToken) { throw new BadCredentialsException('Invalid credentials.'); } - $userIdentifier = $this->accessTokenHandler->getUserIdentifierFrom($accessToken); - return new SelfValidatingPassport( - new UserBadge($userIdentifier, $this->userProvider->loadUserByIdentifier(...)) - ); + $userBadge = $this->accessTokenHandler->getUserBadgeFrom($accessToken); + if (null === $userBadge->getUserLoader() && $this->userProvider) { + $userBadge->setUserLoader($this->userProvider->loadUserByIdentifier(...)); + } + + return new SelfValidatingPassport($userBadge); } public function createToken(Passport $passport, string $firewallName): TokenInterface diff --git a/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/ChainedAccessTokenExtractorsTest.php b/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/ChainedAccessTokenExtractorsTest.php index e59ce918b0651..8dd3188eb9587 100644 --- a/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/ChainedAccessTokenExtractorsTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/ChainedAccessTokenExtractorsTest.php @@ -22,6 +22,7 @@ use Symfony\Component\Security\Http\AccessToken\HeaderAccessTokenExtractor; use Symfony\Component\Security\Http\AccessToken\QueryAccessTokenExtractor; use Symfony\Component\Security\Http\Authenticator\AccessTokenAuthenticator; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; use Symfony\Component\Security\Http\Tests\Authenticator\InMemoryAccessTokenHandler; @@ -40,7 +41,7 @@ protected function setUp(): void /** * @dataProvider provideSupportData */ - public function testSupport($request): void + public function testSupport($request) { $this->setUpAuthenticator(); @@ -53,9 +54,9 @@ public function provideSupportData(): iterable yield [new Request([], [], [], [], [], ['HTTP_AUTHORIZATION' => 'Bearer INVALID_ACCESS_TOKEN'])]; } - public function testAuthenticate(): void + public function testAuthenticate() { - $this->accessTokenHandler->add('VALID_ACCESS_TOKEN', 'foo'); + $this->accessTokenHandler->add('VALID_ACCESS_TOKEN', new UserBadge('foo')); $this->setUpAuthenticator(); $request = new Request([], [], [], [], [], ['HTTP_AUTHORIZATION' => 'Bearer VALID_ACCESS_TOKEN']); @@ -66,7 +67,7 @@ public function testAuthenticate(): void /** * @dataProvider provideInvalidAuthenticateData */ - public function testAuthenticateInvalid($request, $errorMessage, $exceptionType = BadRequestHttpException::class): void + public function testAuthenticateInvalid($request, $errorMessage, $exceptionType = BadRequestHttpException::class) { $this->expectException($exceptionType); $this->expectExceptionMessage($errorMessage); @@ -100,13 +101,13 @@ public function provideInvalidAuthenticateData(): iterable private function setUpAuthenticator(): void { $this->authenticator = new AccessTokenAuthenticator( - $this->userProvider, $this->accessTokenHandler, new ChainAccessTokenExtractor([ new FormEncodedBodyExtractor(), new QueryAccessTokenExtractor(), new HeaderAccessTokenExtractor(), - ]) + ]), + $this->userProvider ); } } diff --git a/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/FormEncodedBodyAccessTokenAuthenticatorTest.php b/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/FormEncodedBodyAccessTokenAuthenticatorTest.php index 5f251bb71f197..b915a5d10631c 100644 --- a/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/FormEncodedBodyAccessTokenAuthenticatorTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/FormEncodedBodyAccessTokenAuthenticatorTest.php @@ -19,6 +19,7 @@ use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface; use Symfony\Component\Security\Http\AccessToken\FormEncodedBodyExtractor; use Symfony\Component\Security\Http\Authenticator\AccessTokenAuthenticator; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; use Symfony\Component\Security\Http\Tests\Authenticator\InMemoryAccessTokenHandler; @@ -34,7 +35,7 @@ protected function setUp(): void $this->accessTokenHandler = new InMemoryAccessTokenHandler(); } - public function testSupport(): void + public function testSupport() { $this->setUpAuthenticator(); $request = new Request([], [], [], [], [], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']); @@ -44,7 +45,7 @@ public function testSupport(): void $this->assertNull($this->authenticator->supports($request)); } - public function testSupportsWithCustomParameter(): void + public function testSupportsWithCustomParameter() { $this->setUpAuthenticator('protection-token'); $request = new Request([], [], [], [], [], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']); @@ -54,9 +55,9 @@ public function testSupportsWithCustomParameter(): void $this->assertNull($this->authenticator->supports($request)); } - public function testAuthenticate(): void + public function testAuthenticate() { - $this->accessTokenHandler->add('VALID_ACCESS_TOKEN', 'foo'); + $this->accessTokenHandler->add('VALID_ACCESS_TOKEN', new UserBadge('foo')); $this->setUpAuthenticator(); $request = new Request([], [], [], [], [], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded'], 'access_token=VALID_ACCESS_TOKEN'); $request->request->set('access_token', 'VALID_ACCESS_TOKEN'); @@ -66,9 +67,9 @@ public function testAuthenticate(): void $this->assertInstanceOf(SelfValidatingPassport::class, $passport); } - public function testAuthenticateWithCustomParameter(): void + public function testAuthenticateWithCustomParameter() { - $this->accessTokenHandler->add('VALID_ACCESS_TOKEN', 'foo'); + $this->accessTokenHandler->add('VALID_ACCESS_TOKEN', new UserBadge('foo')); $this->setUpAuthenticator('protection-token'); $request = new Request([], [], [], [], [], ['CONTENT_TYPE' => 'application/x-www-form-urlencoded']); $request->request->set('protection-token', 'VALID_ACCESS_TOKEN'); @@ -81,7 +82,7 @@ public function testAuthenticateWithCustomParameter(): void /** * @dataProvider provideInvalidAuthenticateData */ - public function testAuthenticateInvalid($request, $errorMessage, $exceptionType = BadRequestHttpException::class): void + public function testAuthenticateInvalid($request, $errorMessage, $exceptionType = BadRequestHttpException::class) { $this->expectException($exceptionType); $this->expectExceptionMessage($errorMessage); @@ -119,9 +120,9 @@ public function provideInvalidAuthenticateData(): iterable private function setUpAuthenticator(string $parameter = 'access_token'): void { $this->authenticator = new AccessTokenAuthenticator( - $this->userProvider, $this->accessTokenHandler, - new FormEncodedBodyExtractor($parameter) + new FormEncodedBodyExtractor($parameter), + $this->userProvider ); } } diff --git a/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/HeaderAccessTokenAuthenticatorTest.php b/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/HeaderAccessTokenAuthenticatorTest.php index 89e91e34feecf..a4e7758bffed5 100644 --- a/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/HeaderAccessTokenAuthenticatorTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/HeaderAccessTokenAuthenticatorTest.php @@ -19,6 +19,7 @@ use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface; use Symfony\Component\Security\Http\AccessToken\HeaderAccessTokenExtractor; use Symfony\Component\Security\Http\Authenticator\AccessTokenAuthenticator; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; use Symfony\Component\Security\Http\Tests\Authenticator\InMemoryAccessTokenHandler; @@ -37,7 +38,7 @@ protected function setUp(): void /** * @dataProvider provideSupportData */ - public function testSupport($request): void + public function testSupport($request) { $this->setUpAuthenticator(); @@ -53,7 +54,7 @@ public function provideSupportData(): iterable /** * @dataProvider provideSupportsWithCustomTokenTypeData */ - public function testSupportsWithCustomTokenType($request, $result): void + public function testSupportsWithCustomTokenType($request, $result) { $this->setUpAuthenticator('Authorization', 'JWT'); @@ -71,7 +72,7 @@ public function provideSupportsWithCustomTokenTypeData(): iterable /** * @dataProvider provideSupportsWithCustomHeaderParameter */ - public function testSupportsWithCustomHeaderParameter($request, $result): void + public function testSupportsWithCustomHeaderParameter($request, $result) { $this->setUpAuthenticator('X-FOO'); @@ -86,9 +87,9 @@ public function provideSupportsWithCustomHeaderParameter(): iterable yield [new Request([], [], [], [], [], ['HTTP_AUTHORIZATION' => 'Bearer INVALID_ACCESS_TOKEN']), false]; } - public function testAuthenticate(): void + public function testAuthenticate() { - $this->accessTokenHandler->add('VALID_ACCESS_TOKEN', 'foo'); + $this->accessTokenHandler->add('VALID_ACCESS_TOKEN', new UserBadge('foo')); $this->setUpAuthenticator(); $request = new Request([], [], [], [], [], ['HTTP_AUTHORIZATION' => 'Bearer VALID_ACCESS_TOKEN']); @@ -96,9 +97,9 @@ public function testAuthenticate(): void $this->assertInstanceOf(SelfValidatingPassport::class, $passport); } - public function testAuthenticateWithCustomTokenType(): void + public function testAuthenticateWithCustomTokenType() { - $this->accessTokenHandler->add('VALID_ACCESS_TOKEN', 'foo'); + $this->accessTokenHandler->add('VALID_ACCESS_TOKEN', new UserBadge('foo')); $this->setUpAuthenticator('Authorization', 'JWT'); $request = new Request([], [], [], [], [], ['HTTP_AUTHORIZATION' => 'JWT VALID_ACCESS_TOKEN']); @@ -109,7 +110,7 @@ public function testAuthenticateWithCustomTokenType(): void /** * @dataProvider provideInvalidAuthenticateData */ - public function testAuthenticateInvalid($request, $errorMessage, $exceptionType = BadRequestHttpException::class): void + public function testAuthenticateInvalid($request, $errorMessage, $exceptionType = BadRequestHttpException::class) { $this->expectException($exceptionType); $this->expectExceptionMessage($errorMessage); @@ -143,9 +144,9 @@ public function provideInvalidAuthenticateData(): iterable private function setUpAuthenticator(string $headerParameter = 'Authorization', string $tokenType = 'Bearer'): void { $this->authenticator = new AccessTokenAuthenticator( - $this->userProvider, $this->accessTokenHandler, - new HeaderAccessTokenExtractor($headerParameter, $tokenType) + new HeaderAccessTokenExtractor($headerParameter, $tokenType), + $this->userProvider ); } } diff --git a/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/QueryAccessTokenAuthenticatorTest.php b/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/QueryAccessTokenAuthenticatorTest.php index c1a8206115452..73f8392a9fa94 100644 --- a/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/QueryAccessTokenAuthenticatorTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Authenticator/AccessToken/QueryAccessTokenAuthenticatorTest.php @@ -19,6 +19,7 @@ use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface; use Symfony\Component\Security\Http\AccessToken\QueryAccessTokenExtractor; use Symfony\Component\Security\Http\Authenticator\AccessTokenAuthenticator; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport; use Symfony\Component\Security\Http\Tests\Authenticator\InMemoryAccessTokenHandler; @@ -34,7 +35,7 @@ protected function setUp(): void $this->accessTokenHandler = new InMemoryAccessTokenHandler(); } - public function testSupport(): void + public function testSupport() { $this->setUpAuthenticator(); $request = new Request(); @@ -43,7 +44,7 @@ public function testSupport(): void $this->assertNull($this->authenticator->supports($request)); } - public function testSupportsWithCustomParameter(): void + public function testSupportsWithCustomParameter() { $this->setUpAuthenticator('protection-token'); $request = new Request(); @@ -52,9 +53,9 @@ public function testSupportsWithCustomParameter(): void $this->assertNull($this->authenticator->supports($request)); } - public function testAuthenticate(): void + public function testAuthenticate() { - $this->accessTokenHandler->add('VALID_ACCESS_TOKEN', 'foo'); + $this->accessTokenHandler->add('VALID_ACCESS_TOKEN', new UserBadge('foo')); $this->setUpAuthenticator(); $request = new Request(); $request->query->set('access_token', 'VALID_ACCESS_TOKEN'); @@ -63,9 +64,9 @@ public function testAuthenticate(): void $this->assertInstanceOf(SelfValidatingPassport::class, $passport); } - public function testAuthenticateWithCustomParameter(): void + public function testAuthenticateWithCustomParameter() { - $this->accessTokenHandler->add('VALID_ACCESS_TOKEN', 'foo'); + $this->accessTokenHandler->add('VALID_ACCESS_TOKEN', new UserBadge('foo')); $this->setUpAuthenticator('protection-token'); $request = new Request(); $request->query->set('protection-token', 'VALID_ACCESS_TOKEN'); @@ -77,7 +78,7 @@ public function testAuthenticateWithCustomParameter(): void /** * @dataProvider provideInvalidAuthenticateData */ - public function testAuthenticateInvalid($request, $errorMessage, $exceptionType = BadRequestHttpException::class): void + public function testAuthenticateInvalid($request, $errorMessage, $exceptionType = BadRequestHttpException::class) { $this->expectException($exceptionType); $this->expectExceptionMessage($errorMessage); @@ -111,9 +112,9 @@ public function provideInvalidAuthenticateData(): iterable private function setUpAuthenticator(string $parameter = 'access_token'): void { $this->authenticator = new AccessTokenAuthenticator( - $this->userProvider, $this->accessTokenHandler, - new QueryAccessTokenExtractor($parameter) + new QueryAccessTokenExtractor($parameter), + $this->userProvider ); } } diff --git a/src/Symfony/Component/Security/Http/Tests/Authenticator/InMemoryAccessTokenHandler.php b/src/Symfony/Component/Security/Http/Tests/Authenticator/InMemoryAccessTokenHandler.php index 9ace3ba8324a7..3fe4c850736ce 100644 --- a/src/Symfony/Component/Security/Http/Tests/Authenticator/InMemoryAccessTokenHandler.php +++ b/src/Symfony/Component/Security/Http/Tests/Authenticator/InMemoryAccessTokenHandler.php @@ -13,15 +13,16 @@ use Symfony\Component\Security\Core\Exception\BadCredentialsException; use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface; +use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; class InMemoryAccessTokenHandler implements AccessTokenHandlerInterface { /** - * @var array + * @var array */ private $accessTokens = []; - public function getUserIdentifierFrom(string $accessToken): string + public function getUserBadgeFrom(string $accessToken): UserBadge { if (!\array_key_exists($accessToken, $this->accessTokens)) { throw new BadCredentialsException('Invalid access token or invalid user.'); @@ -37,7 +38,7 @@ public function remove(string $accessToken): self return $this; } - public function add(string $accessToken, string $user): self + public function add(string $accessToken, UserBadge $user): self { $this->accessTokens[$accessToken] = $user;