From ac7706c30fece82d87b6d6310865c409e67dd40f Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Sun, 6 Dec 2020 18:19:10 +0100 Subject: [PATCH 01/47] ADD: Create the AUTHENTICATION_FAILURE event --- Event/AuthenticationFailureEvent.php | 52 +++++++++++++++++++ OAuth2Events.php | 13 +++-- Resources/config/services.xml | 1 + .../Oauth2AuthenticationFailedException.php | 15 +++--- Security/Firewall/OAuth2Listener.php | 24 ++++++++- 5 files changed, 93 insertions(+), 12 deletions(-) create mode 100644 Event/AuthenticationFailureEvent.php diff --git a/Event/AuthenticationFailureEvent.php b/Event/AuthenticationFailureEvent.php new file mode 100644 index 00000000..3efab875 --- /dev/null +++ b/Event/AuthenticationFailureEvent.php @@ -0,0 +1,52 @@ + + */ +class AuthenticationFailureEvent extends Event { + /** + * @var AuthenticationException + */ + protected $exception; + + /** + * @var Response + */ + protected $response; + + /** + * @param AuthenticationException $exception + * @param Response $response + */ + public function __construct(AuthenticationException $exception, Response $response) { + $this->exception = $exception; + $this->response = $response; + } + + /** + * @return AuthenticationException + */ + public function getException(): AuthenticationException { + return $this->exception; + } + + /** + * @return Response + */ + public function getResponse(): Response { + return $this->response; + } + + /** + * @param Response $response + */ + public function setResponse(Response $response): void { + $this->response = $response; + } +} diff --git a/OAuth2Events.php b/OAuth2Events.php index 8894eb9d..836ffe33 100644 --- a/OAuth2Events.php +++ b/OAuth2Events.php @@ -7,7 +7,7 @@ final class OAuth2Events { /** - * The USER_RESOLVE event occurrs when the client requests a "password" + * The USER_RESOLVE event occurs when the client requests a "password" * grant type from the authorization server. * * You should set a valid user here if applicable. @@ -15,7 +15,7 @@ final class OAuth2Events public const USER_RESOLVE = 'trikoder.oauth2.user_resolve'; /** - * The SCOPE_RESOLVE event occurrs right before the user obtains their + * The SCOPE_RESOLVE event occurs right before the user obtains their * valid access token. * * You could alter the access token's scope here. @@ -23,11 +23,18 @@ final class OAuth2Events public const SCOPE_RESOLVE = 'trikoder.oauth2.scope_resolve'; /** - * The AUTHORIZATION_REQUEST_RESOLVE event occurrs right before the system + * The AUTHORIZATION_REQUEST_RESOLVE event occurs right before the system * complete the authorization request. * * You could approve or deny the authorization request, or set the uri where * must be redirected to resolve the authorization request. */ public const AUTHORIZATION_REQUEST_RESOLVE = 'trikoder.oauth2.authorization_request_resolve'; + + /** + * The AUTHENTICATION_FAILURE event occurs when the oauth token wasn't found + * + * You can set a custom error message in the response body + */ + public const AUTHENTICATION_FAILURE = 'trikoder.oauth2.autentication_failure'; } diff --git a/Resources/config/services.xml b/Resources/config/services.xml index 73734a27..dfc77ecf 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -84,6 +84,7 @@ + diff --git a/Security/Exception/Oauth2AuthenticationFailedException.php b/Security/Exception/Oauth2AuthenticationFailedException.php index f42097ed..00580998 100644 --- a/Security/Exception/Oauth2AuthenticationFailedException.php +++ b/Security/Exception/Oauth2AuthenticationFailedException.php @@ -1,6 +1,4 @@ - + * @author Benoit VIGNAL */ -class Oauth2AuthenticationFailedException extends AuthenticationException +class OAuth2AuthenticationFailedException extends AuthenticationException { - public static function create(string $message): self - { - return new self($message, 401); + /** + * {@inheritdoc} + */ + public function getMessageKey(): string { + return "OAuth Token not found"; } } diff --git a/Security/Firewall/OAuth2Listener.php b/Security/Firewall/OAuth2Listener.php index 36330fc7..e617138b 100644 --- a/Security/Firewall/OAuth2Listener.php +++ b/Security/Firewall/OAuth2Listener.php @@ -5,15 +5,19 @@ namespace Trikoder\Bundle\OAuth2Bundle\Security\Firewall; use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Trikoder\Bundle\OAuth2Bundle\Event\AuthenticationFailureEvent; +use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2Token; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2TokenFactory; use Trikoder\Bundle\OAuth2Bundle\Security\Exception\InsufficientScopesException; -use Trikoder\Bundle\OAuth2Bundle\Security\Exception\Oauth2AuthenticationFailedException; +use Trikoder\Bundle\OAuth2Bundle\Security\Exception\OAuth2AuthenticationFailedException; final class OAuth2Listener { @@ -37,6 +41,11 @@ final class OAuth2Listener */ private $oauth2TokenFactory; + /** + * @var EventDispatcherInterface + */ + private $eventDispatcher; + /** * @var string */ @@ -46,12 +55,14 @@ public function __construct( TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, HttpMessageFactoryInterface $httpMessageFactory, + EventDispatcherInterface $eventDispatcher, OAuth2TokenFactory $oauth2TokenFactory, string $providerKey ) { $this->tokenStorage = $tokenStorage; $this->authenticationManager = $authenticationManager; $this->httpMessageFactory = $httpMessageFactory; + $this->eventDispatcher = $eventDispatcher; $this->oauth2TokenFactory = $oauth2TokenFactory; $this->providerKey = $providerKey; } @@ -68,7 +79,16 @@ public function __invoke(RequestEvent $event) /** @var OAuth2Token $authenticatedToken */ $authenticatedToken = $this->authenticationManager->authenticate($this->oauth2TokenFactory->createOAuth2Token($request, null, $this->providerKey)); } catch (AuthenticationException $e) { - throw new Oauth2AuthenticationFailedException($e->getMessage(), 401, $e); + $exception = new OAuth2AuthenticationFailedException("OAuth Token not found", 0, $e); + $response = new Response($exception->getMessageKey(), Response::HTTP_UNAUTHORIZED); + + $authenticationFailureEvent = new AuthenticationFailureEvent($exception, $response); + $this->eventDispatcher->dispatch($authenticationFailureEvent, OAuth2Events::AUTHENTICATION_FAILURE); + + if ($response = $authenticationFailureEvent->getResponse()) { + $event->setResponse($response); + } + return; } if (!$this->isAccessToRouteGranted($event->getRequest(), $authenticatedToken)) { From cd5219b8b2dcd132b804eddfdd0960616c74afe1 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Sun, 6 Dec 2020 18:26:19 +0100 Subject: [PATCH 02/47] RENAME: Wrong filename --- ...ailedException.php => OAuth2AuthenticationFailedException.php} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Security/Exception/{Oauth2AuthenticationFailedException.php => OAuth2AuthenticationFailedException.php} (100%) diff --git a/Security/Exception/Oauth2AuthenticationFailedException.php b/Security/Exception/OAuth2AuthenticationFailedException.php similarity index 100% rename from Security/Exception/Oauth2AuthenticationFailedException.php rename to Security/Exception/OAuth2AuthenticationFailedException.php From bb05bd69a9b74355185c15f789b8e3e86633dc54 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Sun, 6 Dec 2020 21:06:20 +0100 Subject: [PATCH 03/47] ADD: Custom response formatter --- DependencyInjection/Configuration.php | 5 ++ .../TrikoderOAuth2Extension.php | 4 ++ README.md | 5 ++ Resources/config/services.xml | 5 ++ .../Formatter/DisabledResponseFormatter.php | 19 ++++++ Response/Formatter/JsonResponseFormatter.php | 21 ++++++ .../Formatter/ResponseFormatterInterface.php | 16 +++++ Response/ResponseFormatter.php | 66 +++++++++++++++++++ Security/Firewall/OAuth2Listener.php | 10 ++- docs/custom-response-formatter.md | 16 +++++ 10 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 Response/Formatter/DisabledResponseFormatter.php create mode 100644 Response/Formatter/JsonResponseFormatter.php create mode 100644 Response/Formatter/ResponseFormatterInterface.php create mode 100644 Response/ResponseFormatter.php create mode 100644 docs/custom-response-formatter.md diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 707b2266..86d3dc60 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -31,6 +31,11 @@ public function getConfigTreeBuilder(): TreeBuilder ->info('The priority of the event listener that converts an Exception to a Response.') ->defaultValue(10) ->end() + ->scalarNode('response_formatter') + ->info('Define a custom format in which any response will be converted.') + ->defaultValue('disabled') + ->cannotBeEmpty() + ->end() ->scalarNode('role_prefix') ->info('Set a custom prefix that replaces the default "ROLE_OAUTH2_" role prefix.') ->defaultValue('ROLE_OAUTH2_') diff --git a/DependencyInjection/TrikoderOAuth2Extension.php b/DependencyInjection/TrikoderOAuth2Extension.php index ec7a6009..eb1fc35c 100644 --- a/DependencyInjection/TrikoderOAuth2Extension.php +++ b/DependencyInjection/TrikoderOAuth2Extension.php @@ -41,6 +41,7 @@ use Trikoder\Bundle\OAuth2Bundle\Manager\Doctrine\RefreshTokenManager; use Trikoder\Bundle\OAuth2Bundle\Manager\ScopeManagerInterface; use Trikoder\Bundle\OAuth2Bundle\Model\Scope as ScopeModel; +use Trikoder\Bundle\OAuth2Bundle\Response\ResponseFormatter; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2TokenFactory; use Trikoder\Bundle\OAuth2Bundle\Service\CredentialsRevoker\DoctrineCredentialsRevoker; @@ -66,6 +67,9 @@ public function load(array $configs, ContainerBuilder $container) $container->getDefinition(OAuth2TokenFactory::class) ->setArgument(0, $config['role_prefix']); + $container->getDefinition(ResponseFormatter::class) + ->setArgument(0, $config["response_formatter"]); + $container->getDefinition(ConvertExceptionToResponseListener::class) ->addTag('kernel.event_listener', [ 'event' => KernelEvents::EXCEPTION, diff --git a/README.md b/README.md index 553a1c15..db54a009 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,10 @@ This package is currently in the active development. # The priority of the event listener that converts an Exception to a Response. exception_event_listener_priority: 10 + # Define a custom format in which any response will be converted. + # Supported by default: disabled, json + response_formatter: disabled + # Set a custom prefix that replaces the default "ROLE_OAUTH2_" role prefix. role_prefix: ROLE_OAUTH2_ ``` @@ -206,6 +210,7 @@ security: * [Controlling token scopes](docs/controlling-token-scopes.md) * [Password grant handling](docs/password-grant-handling.md) * [Implementing custom grant type](docs/implementing-custom-grant-type.md) +* [Implementing a custom response formatter](docs/custom-response-formatter.md) ## Contributing diff --git a/Resources/config/services.xml b/Resources/config/services.xml index dfc77ecf..7c091e97 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -86,6 +86,7 @@ + The "%alias_id%" service alias is deprecated and will be removed in v4. @@ -257,5 +258,9 @@ + + + + diff --git a/Response/Formatter/DisabledResponseFormatter.php b/Response/Formatter/DisabledResponseFormatter.php new file mode 100644 index 00000000..06062e4f --- /dev/null +++ b/Response/Formatter/DisabledResponseFormatter.php @@ -0,0 +1,19 @@ + + */ +class DisabledResponseFormatter implements ResponseFormatterInterface { + + public function getResponse(string $message, int $httpStatusCode): Response { + return new Response($message, $httpStatusCode); + } +} diff --git a/Response/Formatter/JsonResponseFormatter.php b/Response/Formatter/JsonResponseFormatter.php new file mode 100644 index 00000000..6b7269f5 --- /dev/null +++ b/Response/Formatter/JsonResponseFormatter.php @@ -0,0 +1,21 @@ + + */ +class JsonResponseFormatter implements ResponseFormatterInterface { + + public function getResponse(string $message, int $httpStatusCode): Response { + return new JsonResponse([ + "message" => $message + ], $httpStatusCode); + } +} diff --git a/Response/Formatter/ResponseFormatterInterface.php b/Response/Formatter/ResponseFormatterInterface.php new file mode 100644 index 00000000..7aaa6501 --- /dev/null +++ b/Response/Formatter/ResponseFormatterInterface.php @@ -0,0 +1,16 @@ + + */ +interface ResponseFormatterInterface { + + public function getResponse(string $message, int $httpStatusCode): Response; +} diff --git a/Response/ResponseFormatter.php b/Response/ResponseFormatter.php new file mode 100644 index 00000000..e04268a8 --- /dev/null +++ b/Response/ResponseFormatter.php @@ -0,0 +1,66 @@ + + */ +class ResponseFormatter { + private const DEFAULT_FORMATTER = [ + "disabled" => DisabledResponseFormatter::class, + "json" => JsonResponseFormatter::class, + ]; + + /** + * @var string + */ + private $responseFormatter; + + + public function __construct(string $responseFormatter) { + $this->responseFormatter = $responseFormatter; + } + + /** + * Format the message into an unified response type + * + * @param string $message + * @param int $httpStatusCode + * @return Response + * @throws \Exception + */ + public function format(string $message, int $httpStatusCode): Response { + $formatter = $this->getFormatter(); + return $formatter->getResponse($message, $httpStatusCode); + } + + private function getFormatter(): ResponseFormatterInterface { + + // The formatter is one supported out of the box + if (array_key_exists($this->responseFormatter, self::DEFAULT_FORMATTER)) { + $responseFormatter = self::DEFAULT_FORMATTER[$this->responseFormatter]; + /** @var ResponseFormatterInterface $responseFormatter */ + return new $responseFormatter(); + } + + // User defined formatter + if (class_exists($this->responseFormatter)) { + if (!in_array($this->responseFormatter, class_implements($this->responseFormatter))) { + return new $this->responseFormatter(); + } else { + throw new \Exception("\"{$this->responseFormatter}\" must implement \"" . ResponseFormatterInterface::class . "\"" ); + } + } + + throw new \Exception("Unsupported ResponseFormatter type."); + } +} diff --git a/Security/Firewall/OAuth2Listener.php b/Security/Firewall/OAuth2Listener.php index e617138b..0369d8cb 100644 --- a/Security/Firewall/OAuth2Listener.php +++ b/Security/Firewall/OAuth2Listener.php @@ -14,6 +14,7 @@ use Symfony\Component\Security\Core\Exception\AuthenticationException; use Trikoder\Bundle\OAuth2Bundle\Event\AuthenticationFailureEvent; use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; +use Trikoder\Bundle\OAuth2Bundle\Response\ResponseFormatter; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2Token; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2TokenFactory; use Trikoder\Bundle\OAuth2Bundle\Security\Exception\InsufficientScopesException; @@ -46,6 +47,11 @@ final class OAuth2Listener */ private $eventDispatcher; + /** + * @var ResponseFormatter + */ + private $responseFormatter; + /** * @var string */ @@ -57,6 +63,7 @@ public function __construct( HttpMessageFactoryInterface $httpMessageFactory, EventDispatcherInterface $eventDispatcher, OAuth2TokenFactory $oauth2TokenFactory, + ResponseFormatter $responseFormatter, string $providerKey ) { $this->tokenStorage = $tokenStorage; @@ -64,6 +71,7 @@ public function __construct( $this->httpMessageFactory = $httpMessageFactory; $this->eventDispatcher = $eventDispatcher; $this->oauth2TokenFactory = $oauth2TokenFactory; + $this->responseFormatter = $responseFormatter; $this->providerKey = $providerKey; } @@ -80,7 +88,7 @@ public function __invoke(RequestEvent $event) $authenticatedToken = $this->authenticationManager->authenticate($this->oauth2TokenFactory->createOAuth2Token($request, null, $this->providerKey)); } catch (AuthenticationException $e) { $exception = new OAuth2AuthenticationFailedException("OAuth Token not found", 0, $e); - $response = new Response($exception->getMessageKey(), Response::HTTP_UNAUTHORIZED); + $response = $this->responseFormatter->format($exception->getMessageKey(), Response::HTTP_UNAUTHORIZED); $authenticationFailureEvent = new AuthenticationFailureEvent($exception, $response); $this->eventDispatcher->dispatch($authenticationFailureEvent, OAuth2Events::AUTHENTICATION_FAILURE); diff --git a/docs/custom-response-formatter.md b/docs/custom-response-formatter.md new file mode 100644 index 00000000..2ae96655 --- /dev/null +++ b/docs/custom-response-formatter.md @@ -0,0 +1,16 @@ +# Creating a custom response formatter + +To create a custom response formatter, your class must implement `Trikoder\Bundle\OAuth2Bundle\Response\Formatter`. + +Once that's done, set the `response_formatter` in the configuration file to reference the new class + +Example: +```yml +config/packages/trikoder_oauth2.yaml + + + trikoder_oauth2: + + response_formatter: App\Response\CustomOAuthResponseFormatter + +``` From c0b984f9a12beb67d0aace2366f17b997a6b4967 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Sun, 6 Dec 2020 21:16:31 +0100 Subject: [PATCH 04/47] QUALITY: PHP Coding Standards Fix --- .../TrikoderOAuth2Extension.php | 2 +- Event/AuthenticationFailureEvent.php | 32 +++++++---------- .../Formatter/DisabledResponseFormatter.php | 9 +++-- Response/Formatter/JsonResponseFormatter.php | 10 +++--- .../Formatter/ResponseFormatterInterface.php | 5 ++- Response/ResponseFormatter.php | 34 +++++++++---------- .../OAuth2AuthenticationFailedException.php | 9 +++-- Security/Firewall/OAuth2Listener.php | 3 +- 8 files changed, 49 insertions(+), 55 deletions(-) diff --git a/DependencyInjection/TrikoderOAuth2Extension.php b/DependencyInjection/TrikoderOAuth2Extension.php index eb1fc35c..f470f143 100644 --- a/DependencyInjection/TrikoderOAuth2Extension.php +++ b/DependencyInjection/TrikoderOAuth2Extension.php @@ -68,7 +68,7 @@ public function load(array $configs, ContainerBuilder $container) ->setArgument(0, $config['role_prefix']); $container->getDefinition(ResponseFormatter::class) - ->setArgument(0, $config["response_formatter"]); + ->setArgument(0, $config['response_formatter']); $container->getDefinition(ConvertExceptionToResponseListener::class) ->addTag('kernel.event_listener', [ diff --git a/Event/AuthenticationFailureEvent.php b/Event/AuthenticationFailureEvent.php index 3efab875..e5243689 100644 --- a/Event/AuthenticationFailureEvent.php +++ b/Event/AuthenticationFailureEvent.php @@ -1,4 +1,6 @@ - */ -class AuthenticationFailureEvent extends Event { +class AuthenticationFailureEvent extends Event +{ /** * @var AuthenticationException */ @@ -20,33 +23,24 @@ class AuthenticationFailureEvent extends Event { */ protected $response; - /** - * @param AuthenticationException $exception - * @param Response $response - */ - public function __construct(AuthenticationException $exception, Response $response) { + public function __construct(AuthenticationException $exception, Response $response) + { $this->exception = $exception; $this->response = $response; } - /** - * @return AuthenticationException - */ - public function getException(): AuthenticationException { + public function getException(): AuthenticationException + { return $this->exception; } - /** - * @return Response - */ - public function getResponse(): Response { + public function getResponse(): Response + { return $this->response; } - /** - * @param Response $response - */ - public function setResponse(Response $response): void { + public function setResponse(Response $response): void + { $this->response = $response; } } diff --git a/Response/Formatter/DisabledResponseFormatter.php b/Response/Formatter/DisabledResponseFormatter.php index 06062e4f..5e387e29 100644 --- a/Response/Formatter/DisabledResponseFormatter.php +++ b/Response/Formatter/DisabledResponseFormatter.php @@ -4,16 +4,15 @@ namespace Trikoder\Bundle\OAuth2Bundle\Response\Formatter; - -use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; /** * @author Benoit VIGNAL */ -class DisabledResponseFormatter implements ResponseFormatterInterface { - - public function getResponse(string $message, int $httpStatusCode): Response { +class DisabledResponseFormatter implements ResponseFormatterInterface +{ + public function getResponse(string $message, int $httpStatusCode): Response + { return new Response($message, $httpStatusCode); } } diff --git a/Response/Formatter/JsonResponseFormatter.php b/Response/Formatter/JsonResponseFormatter.php index 6b7269f5..d106ef16 100644 --- a/Response/Formatter/JsonResponseFormatter.php +++ b/Response/Formatter/JsonResponseFormatter.php @@ -4,18 +4,18 @@ namespace Trikoder\Bundle\OAuth2Bundle\Response\Formatter; - use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; /** * @author Benoit VIGNAL */ -class JsonResponseFormatter implements ResponseFormatterInterface { - - public function getResponse(string $message, int $httpStatusCode): Response { +class JsonResponseFormatter implements ResponseFormatterInterface +{ + public function getResponse(string $message, int $httpStatusCode): Response + { return new JsonResponse([ - "message" => $message + 'message' => $message, ], $httpStatusCode); } } diff --git a/Response/Formatter/ResponseFormatterInterface.php b/Response/Formatter/ResponseFormatterInterface.php index 7aaa6501..c0f3658c 100644 --- a/Response/Formatter/ResponseFormatterInterface.php +++ b/Response/Formatter/ResponseFormatterInterface.php @@ -4,13 +4,12 @@ namespace Trikoder\Bundle\OAuth2Bundle\Response\Formatter; - use Symfony\Component\HttpFoundation\Response; /** * @author Benoit VIGNAL */ -interface ResponseFormatterInterface { - +interface ResponseFormatterInterface +{ public function getResponse(string $message, int $httpStatusCode): Response; } diff --git a/Response/ResponseFormatter.php b/Response/ResponseFormatter.php index e04268a8..2c451f1f 100644 --- a/Response/ResponseFormatter.php +++ b/Response/ResponseFormatter.php @@ -4,9 +4,7 @@ namespace Trikoder\Bundle\OAuth2Bundle\Response; - use Symfony\Component\HttpFoundation\Response; -use Trikoder\Bundle\OAuth2Bundle\Response\Formatter\AbstractResponseFormatter; use Trikoder\Bundle\OAuth2Bundle\Response\Formatter\DisabledResponseFormatter; use Trikoder\Bundle\OAuth2Bundle\Response\Formatter\JsonResponseFormatter; use Trikoder\Bundle\OAuth2Bundle\Response\Formatter\ResponseFormatterInterface; @@ -14,10 +12,11 @@ /** * @author Benoit VIGNAL */ -class ResponseFormatter { +class ResponseFormatter +{ private const DEFAULT_FORMATTER = [ - "disabled" => DisabledResponseFormatter::class, - "json" => JsonResponseFormatter::class, + 'disabled' => DisabledResponseFormatter::class, + 'json' => JsonResponseFormatter::class, ]; /** @@ -25,42 +24,41 @@ class ResponseFormatter { */ private $responseFormatter; - - public function __construct(string $responseFormatter) { + public function __construct(string $responseFormatter) + { $this->responseFormatter = $responseFormatter; } /** * Format the message into an unified response type * - * @param string $message - * @param int $httpStatusCode - * @return Response * @throws \Exception */ - public function format(string $message, int $httpStatusCode): Response { + public function format(string $message, int $httpStatusCode): Response + { $formatter = $this->getFormatter(); + return $formatter->getResponse($message, $httpStatusCode); } - private function getFormatter(): ResponseFormatterInterface { - + private function getFormatter(): ResponseFormatterInterface + { // The formatter is one supported out of the box - if (array_key_exists($this->responseFormatter, self::DEFAULT_FORMATTER)) { + if (\array_key_exists($this->responseFormatter, self::DEFAULT_FORMATTER)) { $responseFormatter = self::DEFAULT_FORMATTER[$this->responseFormatter]; - /** @var ResponseFormatterInterface $responseFormatter */ + /* @var ResponseFormatterInterface $responseFormatter */ return new $responseFormatter(); } // User defined formatter if (class_exists($this->responseFormatter)) { - if (!in_array($this->responseFormatter, class_implements($this->responseFormatter))) { + if (!\in_array($this->responseFormatter, class_implements($this->responseFormatter))) { return new $this->responseFormatter(); } else { - throw new \Exception("\"{$this->responseFormatter}\" must implement \"" . ResponseFormatterInterface::class . "\"" ); + throw new \Exception("\"{$this->responseFormatter}\" must implement \"" . ResponseFormatterInterface::class . '"'); } } - throw new \Exception("Unsupported ResponseFormatter type."); + throw new \Exception('Unsupported ResponseFormatter type.'); } } diff --git a/Security/Exception/OAuth2AuthenticationFailedException.php b/Security/Exception/OAuth2AuthenticationFailedException.php index 00580998..c4f9094e 100644 --- a/Security/Exception/OAuth2AuthenticationFailedException.php +++ b/Security/Exception/OAuth2AuthenticationFailedException.php @@ -1,4 +1,6 @@ -authenticationManager->authenticate($this->oauth2TokenFactory->createOAuth2Token($request, null, $this->providerKey)); } catch (AuthenticationException $e) { - $exception = new OAuth2AuthenticationFailedException("OAuth Token not found", 0, $e); + $exception = new OAuth2AuthenticationFailedException('OAuth Token not found', 0, $e); $response = $this->responseFormatter->format($exception->getMessageKey(), Response::HTTP_UNAUTHORIZED); $authenticationFailureEvent = new AuthenticationFailureEvent($exception, $response); @@ -96,6 +96,7 @@ public function __invoke(RequestEvent $event) if ($response = $authenticationFailureEvent->getResponse()) { $event->setResponse($response); } + return; } From aec0a1a1c2124d0a00c2d9bb7884ff8c8a5b7a19 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Sun, 6 Dec 2020 21:22:22 +0100 Subject: [PATCH 05/47] QUALITY: File not capitalize --- ...ailedException.php => Oauth2AuthenticationFailedException.php} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Security/Exception/{OAuth2AuthenticationFailedException.php => Oauth2AuthenticationFailedException.php} (100%) diff --git a/Security/Exception/OAuth2AuthenticationFailedException.php b/Security/Exception/Oauth2AuthenticationFailedException.php similarity index 100% rename from Security/Exception/OAuth2AuthenticationFailedException.php rename to Security/Exception/Oauth2AuthenticationFailedException.php From a7c5d1a7eda8959d414a89d3059f87cd3841ce5e Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Sun, 6 Dec 2020 21:22:37 +0100 Subject: [PATCH 06/47] QUALITY: File not capitalize --- ...ailedException.php => OAuth2AuthenticationFailedException.php} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Security/Exception/{Oauth2AuthenticationFailedException.php => OAuth2AuthenticationFailedException.php} (100%) diff --git a/Security/Exception/Oauth2AuthenticationFailedException.php b/Security/Exception/OAuth2AuthenticationFailedException.php similarity index 100% rename from Security/Exception/Oauth2AuthenticationFailedException.php rename to Security/Exception/OAuth2AuthenticationFailedException.php From 34a5dac0f162bfa2e00de37c51d3b0fe53d7797a Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Mon, 7 Dec 2020 21:18:43 +0100 Subject: [PATCH 07/47] ADD: Authentication scope failure event --- Event/AuthenticationScopeFailureEvent.php | 36 +++++++++++++++++++ OAuth2Events.php | 7 ++++ .../Exception/InsufficientScopesException.php | 10 ++++++ .../OAuth2AuthenticationFailedException.php | 2 +- Security/Firewall/OAuth2Listener.php | 19 ++++++++-- 5 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 Event/AuthenticationScopeFailureEvent.php diff --git a/Event/AuthenticationScopeFailureEvent.php b/Event/AuthenticationScopeFailureEvent.php new file mode 100644 index 00000000..f75d53ed --- /dev/null +++ b/Event/AuthenticationScopeFailureEvent.php @@ -0,0 +1,36 @@ + + */ +class AuthenticationScopeFailureEvent extends AuthenticationFailureEvent +{ + /** + * @var TokenInterface + */ + private $token; + + public function __construct(AuthenticationException $exception, Response $response, TokenInterface $token) + { + parent::__construct($exception, $response); + $this->token = $token; + } + + /** + * @return TokenInterface + */ + public function getToken(): TokenInterface + { + return $this->token; + } + +} diff --git a/OAuth2Events.php b/OAuth2Events.php index 836ffe33..91c8165f 100644 --- a/OAuth2Events.php +++ b/OAuth2Events.php @@ -37,4 +37,11 @@ final class OAuth2Events * You can set a custom error message in the response body */ public const AUTHENTICATION_FAILURE = 'trikoder.oauth2.autentication_failure'; + + /** + * The AUTHENTICATION_SCOPE_FAILURE event occurs when the scope validation for the token failed + * + * You can set a custom error message in the response body + */ + public const AUTHENTICATION_SCOPE_FAILURE = 'trikoder.oauth2.autentication_scope_failure'; } diff --git a/Security/Exception/InsufficientScopesException.php b/Security/Exception/InsufficientScopesException.php index 74b14103..5e02bc47 100644 --- a/Security/Exception/InsufficientScopesException.php +++ b/Security/Exception/InsufficientScopesException.php @@ -9,6 +9,7 @@ /** * @author Tobias Nyholm + * @author Benoit VIGNAL */ class InsufficientScopesException extends AuthenticationException { @@ -19,4 +20,13 @@ public static function create(TokenInterface $token): self return $exception; } + + /** + * {@inheritdoc} + */ + public function getMessageKey(): string + { + return "The token has insufficient scopes."; + } + } diff --git a/Security/Exception/OAuth2AuthenticationFailedException.php b/Security/Exception/OAuth2AuthenticationFailedException.php index c4f9094e..cc32d047 100644 --- a/Security/Exception/OAuth2AuthenticationFailedException.php +++ b/Security/Exception/OAuth2AuthenticationFailedException.php @@ -17,6 +17,6 @@ class OAuth2AuthenticationFailedException extends AuthenticationException */ public function getMessageKey(): string { - return 'OAuth Token not found'; + return 'OAuth Token not found.'; } } diff --git a/Security/Firewall/OAuth2Listener.php b/Security/Firewall/OAuth2Listener.php index 3e1dfcfd..b9a7afb5 100644 --- a/Security/Firewall/OAuth2Listener.php +++ b/Security/Firewall/OAuth2Listener.php @@ -13,6 +13,7 @@ use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Trikoder\Bundle\OAuth2Bundle\Event\AuthenticationFailureEvent; +use Trikoder\Bundle\OAuth2Bundle\Event\AuthenticationScopeFailureEvent; use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; use Trikoder\Bundle\OAuth2Bundle\Response\ResponseFormatter; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2Token; @@ -87,7 +88,7 @@ public function __invoke(RequestEvent $event) /** @var OAuth2Token $authenticatedToken */ $authenticatedToken = $this->authenticationManager->authenticate($this->oauth2TokenFactory->createOAuth2Token($request, null, $this->providerKey)); } catch (AuthenticationException $e) { - $exception = new OAuth2AuthenticationFailedException('OAuth Token not found', 0, $e); + $exception = new OAuth2AuthenticationFailedException(); $response = $this->responseFormatter->format($exception->getMessageKey(), Response::HTTP_UNAUTHORIZED); $authenticationFailureEvent = new AuthenticationFailureEvent($exception, $response); @@ -100,8 +101,20 @@ public function __invoke(RequestEvent $event) return; } - if (!$this->isAccessToRouteGranted($event->getRequest(), $authenticatedToken)) { - throw InsufficientScopesException::create($authenticatedToken); + if ($this->isAccessToRouteGranted($event->getRequest(), $authenticatedToken)) { + $exception = new InsufficientScopesException(); + $exception->setToken($authenticatedToken); + + $response = $this->responseFormatter->format($exception->getMessageKey(), Response::HTTP_FORBIDDEN); + + $authenticationFailureScopeEvent = new AuthenticationScopeFailureEvent($exception, $response, $authenticatedToken); + $this->eventDispatcher->dispatch($authenticationFailureScopeEvent, OAuth2Events::AUTHENTICATION_SCOPE_FAILURE); + + if ($response = $authenticationFailureScopeEvent->getResponse()) { + $event->setResponse($response); + } + + return; } $this->tokenStorage->setToken($authenticatedToken); From f20ae680473e08179efa68af24f68f0a55928f09 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Mon, 7 Dec 2020 21:24:39 +0100 Subject: [PATCH 08/47] REMOVE: exception_event_listener_priority not anymore used --- DependencyInjection/Configuration.php | 4 ---- .../TrikoderOAuth2Extension.php | 9 ------- .../ConvertExceptionToResponseListener.php | 24 ------------------- README.md | 3 --- Resources/config/services.xml | 5 ---- 5 files changed, 45 deletions(-) delete mode 100644 EventListener/ConvertExceptionToResponseListener.php diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 86d3dc60..61429f2a 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -27,10 +27,6 @@ public function getConfigTreeBuilder(): TreeBuilder $rootNode ->children() - ->scalarNode('exception_event_listener_priority') - ->info('The priority of the event listener that converts an Exception to a Response.') - ->defaultValue(10) - ->end() ->scalarNode('response_formatter') ->info('Define a custom format in which any response will be converted.') ->defaultValue('disabled') diff --git a/DependencyInjection/TrikoderOAuth2Extension.php b/DependencyInjection/TrikoderOAuth2Extension.php index f470f143..11244651 100644 --- a/DependencyInjection/TrikoderOAuth2Extension.php +++ b/DependencyInjection/TrikoderOAuth2Extension.php @@ -29,11 +29,9 @@ use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\HttpKernel\KernelEvents; use Trikoder\Bundle\OAuth2Bundle\DBAL\Type\Grant as GrantType; use Trikoder\Bundle\OAuth2Bundle\DBAL\Type\RedirectUri as RedirectUriType; use Trikoder\Bundle\OAuth2Bundle\DBAL\Type\Scope as ScopeType; -use Trikoder\Bundle\OAuth2Bundle\EventListener\ConvertExceptionToResponseListener; use Trikoder\Bundle\OAuth2Bundle\League\AuthorizationServer\GrantTypeInterface; use Trikoder\Bundle\OAuth2Bundle\Manager\Doctrine\AccessTokenManager; use Trikoder\Bundle\OAuth2Bundle\Manager\Doctrine\AuthorizationCodeManager; @@ -70,13 +68,6 @@ public function load(array $configs, ContainerBuilder $container) $container->getDefinition(ResponseFormatter::class) ->setArgument(0, $config['response_formatter']); - $container->getDefinition(ConvertExceptionToResponseListener::class) - ->addTag('kernel.event_listener', [ - 'event' => KernelEvents::EXCEPTION, - 'method' => 'onKernelException', - 'priority' => $config['exception_event_listener_priority'], - ]); - $container->registerForAutoconfiguration(GrantTypeInterface::class) ->addTag('trikoder.oauth2.authorization_server.grant'); } diff --git a/EventListener/ConvertExceptionToResponseListener.php b/EventListener/ConvertExceptionToResponseListener.php deleted file mode 100644 index b29fdffe..00000000 --- a/EventListener/ConvertExceptionToResponseListener.php +++ /dev/null @@ -1,24 +0,0 @@ - - */ -final class ConvertExceptionToResponseListener -{ - public function onKernelException(ExceptionEvent $event): void - { - $exception = $event->getThrowable(); - if ($exception instanceof InsufficientScopesException || $exception instanceof Oauth2AuthenticationFailedException) { - $event->setResponse(new Response($exception->getMessage(), $exception->getCode())); - } - } -} diff --git a/README.md b/README.md index db54a009..a1b56192 100644 --- a/README.md +++ b/README.md @@ -162,9 +162,6 @@ This package is currently in the active development. entity_manager: default in_memory: ~ - # The priority of the event listener that converts an Exception to a Response. - exception_event_listener_priority: 10 - # Define a custom format in which any response will be converted. # Supported by default: disabled, json response_formatter: disabled diff --git a/Resources/config/services.xml b/Resources/config/services.xml index 7c091e97..44facfb6 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -170,11 +170,6 @@ The "%alias_id%" service alias is deprecated and will be removed in v4. - - - The "%alias_id%" service alias is deprecated and will be removed in v4. - - From ad87d0128f8d5d5c31a8e8bfc58d33fa52ef2e73 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Mon, 7 Dec 2020 21:32:48 +0100 Subject: [PATCH 09/47] FIX: Error in condition blocking the scope verification in firewall --- Security/Firewall/OAuth2Listener.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Security/Firewall/OAuth2Listener.php b/Security/Firewall/OAuth2Listener.php index b9a7afb5..52ffb181 100644 --- a/Security/Firewall/OAuth2Listener.php +++ b/Security/Firewall/OAuth2Listener.php @@ -101,7 +101,7 @@ public function __invoke(RequestEvent $event) return; } - if ($this->isAccessToRouteGranted($event->getRequest(), $authenticatedToken)) { + if (!$this->isAccessToRouteGranted($event->getRequest(), $authenticatedToken)) { $exception = new InsufficientScopesException(); $exception->setToken($authenticatedToken); From 16651d1e9ed9720795ba514bd68c9adbdb48a438 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Mon, 7 Dec 2020 21:58:25 +0100 Subject: [PATCH 10/47] UPDATE: Remove irrelevant condition --- Security/Firewall/OAuth2Listener.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Security/Firewall/OAuth2Listener.php b/Security/Firewall/OAuth2Listener.php index 52ffb181..a4f562d6 100644 --- a/Security/Firewall/OAuth2Listener.php +++ b/Security/Firewall/OAuth2Listener.php @@ -94,9 +94,7 @@ public function __invoke(RequestEvent $event) $authenticationFailureEvent = new AuthenticationFailureEvent($exception, $response); $this->eventDispatcher->dispatch($authenticationFailureEvent, OAuth2Events::AUTHENTICATION_FAILURE); - if ($response = $authenticationFailureEvent->getResponse()) { - $event->setResponse($response); - } + $event->setResponse($authenticationFailureEvent->getResponse()); return; } @@ -110,9 +108,7 @@ public function __invoke(RequestEvent $event) $authenticationFailureScopeEvent = new AuthenticationScopeFailureEvent($exception, $response, $authenticatedToken); $this->eventDispatcher->dispatch($authenticationFailureScopeEvent, OAuth2Events::AUTHENTICATION_SCOPE_FAILURE); - if ($response = $authenticationFailureScopeEvent->getResponse()) { - $event->setResponse($response); - } + $event->setResponse($authenticationFailureScopeEvent->getResponse()); return; } From b5433c02f0c844a410041d4c72423265a16c3d9d Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Mon, 7 Dec 2020 22:04:12 +0100 Subject: [PATCH 11/47] UPDATE: Remove unused code --- Security/Exception/InsufficientScopesException.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Security/Exception/InsufficientScopesException.php b/Security/Exception/InsufficientScopesException.php index 5e02bc47..6ae84f48 100644 --- a/Security/Exception/InsufficientScopesException.php +++ b/Security/Exception/InsufficientScopesException.php @@ -4,7 +4,6 @@ namespace Trikoder\Bundle\OAuth2Bundle\Security\Exception; -use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; /** @@ -13,14 +12,6 @@ */ class InsufficientScopesException extends AuthenticationException { - public static function create(TokenInterface $token): self - { - $exception = new self('The token has insufficient scopes.', 403); - $exception->setToken($token); - - return $exception; - } - /** * {@inheritdoc} */ From 6c8a9d43da8d0102f5f3c0fc578a51fe70489461 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Mon, 7 Dec 2020 22:04:37 +0100 Subject: [PATCH 12/47] ADD: MISSING_AUTHORIZATION_HEADER event --- Event/MissingAuthorizationHeaderEvent.php | 17 +++++++++++++++ OAuth2Events.php | 7 +++++++ .../MissingAuthorizationHeaderException.php | 21 +++++++++++++++++++ Security/Firewall/OAuth2Listener.php | 10 +++++++++ 4 files changed, 55 insertions(+) create mode 100644 Event/MissingAuthorizationHeaderEvent.php create mode 100644 Security/Exception/MissingAuthorizationHeaderException.php diff --git a/Event/MissingAuthorizationHeaderEvent.php b/Event/MissingAuthorizationHeaderEvent.php new file mode 100644 index 00000000..63830844 --- /dev/null +++ b/Event/MissingAuthorizationHeaderEvent.php @@ -0,0 +1,17 @@ + + */ +class MissingAuthorizationHeaderEvent extends AuthenticationFailureEvent +{ + +} diff --git a/OAuth2Events.php b/OAuth2Events.php index 91c8165f..b6cb8f82 100644 --- a/OAuth2Events.php +++ b/OAuth2Events.php @@ -31,6 +31,13 @@ final class OAuth2Events */ public const AUTHORIZATION_REQUEST_RESOLVE = 'trikoder.oauth2.authorization_request_resolve'; + /** + * The MISSING_AUTHORIZATION_HEADER event occurs when the Authorization Bearer header was not found + * + * You can set a custom error message in the response body + */ + public const MISSING_AUTHORIZATION_HEADER = 'trikoder.oauth2.missing_authorization_header'; + /** * The AUTHENTICATION_FAILURE event occurs when the oauth token wasn't found * diff --git a/Security/Exception/MissingAuthorizationHeaderException.php b/Security/Exception/MissingAuthorizationHeaderException.php new file mode 100644 index 00000000..16f32842 --- /dev/null +++ b/Security/Exception/MissingAuthorizationHeaderException.php @@ -0,0 +1,21 @@ + + */ +class MissingAuthorizationHeaderException extends AuthenticationException +{ + /** + * {@inheritdoc} + */ + public function getMessageKey(): string + { + return 'Authorization Bearer missing..'; + } +} diff --git a/Security/Firewall/OAuth2Listener.php b/Security/Firewall/OAuth2Listener.php index a4f562d6..ea0f1f57 100644 --- a/Security/Firewall/OAuth2Listener.php +++ b/Security/Firewall/OAuth2Listener.php @@ -14,11 +14,13 @@ use Symfony\Component\Security\Core\Exception\AuthenticationException; use Trikoder\Bundle\OAuth2Bundle\Event\AuthenticationFailureEvent; use Trikoder\Bundle\OAuth2Bundle\Event\AuthenticationScopeFailureEvent; +use Trikoder\Bundle\OAuth2Bundle\Event\MissingAuthorizationHeaderEvent; use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; use Trikoder\Bundle\OAuth2Bundle\Response\ResponseFormatter; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2Token; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2TokenFactory; use Trikoder\Bundle\OAuth2Bundle\Security\Exception\InsufficientScopesException; +use Trikoder\Bundle\OAuth2Bundle\Security\Exception\MissingAuthorizationHeaderException; use Trikoder\Bundle\OAuth2Bundle\Security\Exception\OAuth2AuthenticationFailedException; final class OAuth2Listener @@ -81,6 +83,14 @@ public function __invoke(RequestEvent $event) $request = $this->httpMessageFactory->createRequest($event->getRequest()); if (!$request->hasHeader('Authorization')) { + $exception = new MissingAuthorizationHeaderException(); + $response = $this->responseFormatter->format($exception->getMessageKey(), Response::HTTP_UNAUTHORIZED); + + $missingAuthHeaderEvent = new MissingAuthorizationHeaderEvent($exception, $response); + $this->eventDispatcher->dispatch($missingAuthHeaderEvent, OAuth2Events::MISSING_AUTHORIZATION_HEADER); + + $event->setResponse($missingAuthHeaderEvent->getResponse()); + return; } From cf97ed3498ecc462c26954156a703eb8a020cbeb Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Mon, 7 Dec 2020 22:09:30 +0100 Subject: [PATCH 13/47] ADD: MISSING_AUTHORIZATION_HEADER event for Guard --- Resources/config/services.xml | 2 ++ .../Authenticator/OAuth2Authenticator.php | 31 +++++++++++++++---- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/Resources/config/services.xml b/Resources/config/services.xml index 44facfb6..b613c9a9 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -101,6 +101,8 @@ + + diff --git a/Security/Guard/Authenticator/OAuth2Authenticator.php b/Security/Guard/Authenticator/OAuth2Authenticator.php index 4fd87a81..92dbd12e 100644 --- a/Security/Guard/Authenticator/OAuth2Authenticator.php +++ b/Security/Guard/Authenticator/OAuth2Authenticator.php @@ -7,6 +7,7 @@ use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\ResourceServer; use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; @@ -15,14 +16,19 @@ use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Guard\AuthenticatorInterface; +use Trikoder\Bundle\OAuth2Bundle\Event\MissingAuthorizationHeaderEvent; +use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; +use Trikoder\Bundle\OAuth2Bundle\Response\ResponseFormatter; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2Token; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2TokenFactory; use Trikoder\Bundle\OAuth2Bundle\Security\Exception\InsufficientScopesException; +use Trikoder\Bundle\OAuth2Bundle\Security\Exception\MissingAuthorizationHeaderException; use Trikoder\Bundle\OAuth2Bundle\Security\User\NullUser; /** * @author Yonel Ceruto * @author Antonio J. GarcĂ­a Lagar + * @author Benoit VIGNAL */ final class OAuth2Authenticator implements AuthenticatorInterface { @@ -30,19 +36,33 @@ final class OAuth2Authenticator implements AuthenticatorInterface private $resourceServer; private $oauth2TokenFactory; private $psr7Request; - - public function __construct(HttpMessageFactoryInterface $httpMessageFactory, ResourceServer $resourceServer, OAuth2TokenFactory $oauth2TokenFactory) + /** + * @var EventDispatcherInterface + */ + private $eventDispatcher; + /** + * @var ResponseFormatter + */ + private $responseFormatter; + + public function __construct(HttpMessageFactoryInterface $httpMessageFactory, ResourceServer $resourceServer, OAuth2TokenFactory $oauth2TokenFactory, EventDispatcherInterface $eventDispatcher, ResponseFormatter $responseFormatter) { $this->httpMessageFactory = $httpMessageFactory; $this->resourceServer = $resourceServer; $this->oauth2TokenFactory = $oauth2TokenFactory; + $this->eventDispatcher = $eventDispatcher; + $this->responseFormatter = $responseFormatter; } public function start(Request $request, ?AuthenticationException $authException = null): Response { - $exception = new UnauthorizedHttpException('Bearer'); + $exception = new MissingAuthorizationHeaderException(); + $response = $this->responseFormatter->format($exception->getMessageKey(), Response::HTTP_UNAUTHORIZED); + + $event = new MissingAuthorizationHeaderEvent($exception, $response); + $this->eventDispatcher->dispatch($event, OAuth2Events::MISSING_AUTHORIZATION_HEADER); - return new Response('', $exception->getStatusCode(), $exception->getHeaders()); + return $event->getResponse(); } public function supports(Request $request): bool @@ -57,9 +77,8 @@ public function getCredentials(Request $request) try { $this->psr7Request = $this->resourceServer->validateAuthenticatedRequest($psr7Request); } catch (OAuthServerException $e) { - throw new AuthenticationException('The resource server rejected the request.', 0, $e); + throw new AuthenticationException($e->getMessage(), 0, $e); } - return $this->psr7Request->getAttribute('oauth_user_id'); } From 64fc4f3cf777f71d4f83c2f60c3c3e1699a69e27 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Fri, 11 Dec 2020 16:31:43 +0100 Subject: [PATCH 14/47] UPDATE: Switch to InvalidAuthorizationHeader event --- ...hp => InvalidAuthorizationHeaderEvent.php} | 2 +- OAuth2Events.php | 5 +-- .../InvalidAuthorizationHeaderException.php | 31 +++++++++++++++++++ .../MissingAuthorizationHeaderException.php | 21 ------------- Security/Firewall/OAuth2Listener.php | 10 +++--- .../Authenticator/OAuth2Authenticator.php | 12 ++++--- 6 files changed, 47 insertions(+), 34 deletions(-) rename Event/{MissingAuthorizationHeaderEvent.php => InvalidAuthorizationHeaderEvent.php} (83%) create mode 100644 Security/Exception/InvalidAuthorizationHeaderException.php delete mode 100644 Security/Exception/MissingAuthorizationHeaderException.php diff --git a/Event/MissingAuthorizationHeaderEvent.php b/Event/InvalidAuthorizationHeaderEvent.php similarity index 83% rename from Event/MissingAuthorizationHeaderEvent.php rename to Event/InvalidAuthorizationHeaderEvent.php index 63830844..9714356a 100644 --- a/Event/MissingAuthorizationHeaderEvent.php +++ b/Event/InvalidAuthorizationHeaderEvent.php @@ -11,7 +11,7 @@ /** * @author Benoit VIGNAL */ -class MissingAuthorizationHeaderEvent extends AuthenticationFailureEvent +class InvalidAuthorizationHeaderEvent extends AuthenticationFailureEvent { } diff --git a/OAuth2Events.php b/OAuth2Events.php index b6cb8f82..a95166b9 100644 --- a/OAuth2Events.php +++ b/OAuth2Events.php @@ -32,11 +32,12 @@ final class OAuth2Events public const AUTHORIZATION_REQUEST_RESOLVE = 'trikoder.oauth2.authorization_request_resolve'; /** - * The MISSING_AUTHORIZATION_HEADER event occurs when the Authorization Bearer header was not found + * The INVALID_AUTHORIZATION_HEADER event occurs when the + * Authorization Bearer header was not found, or is wrong/malformed * * You can set a custom error message in the response body */ - public const MISSING_AUTHORIZATION_HEADER = 'trikoder.oauth2.missing_authorization_header'; + public const INVALID_AUTHORIZATION_HEADER = 'trikoder.oauth2.invalid_authorization_header'; /** * The AUTHENTICATION_FAILURE event occurs when the oauth token wasn't found diff --git a/Security/Exception/InvalidAuthorizationHeaderException.php b/Security/Exception/InvalidAuthorizationHeaderException.php new file mode 100644 index 00000000..920db9ad --- /dev/null +++ b/Security/Exception/InvalidAuthorizationHeaderException.php @@ -0,0 +1,31 @@ + + */ +class InvalidAuthorizationHeaderException extends AuthenticationException +{ + private $previousException; + + /** + * {@inheritdoc} + */ + public function getMessageKey(): string + { + return 'Invalid Authorization Bearer.'; + } + + public function getPreviousException(): ?AuthenticationException { + return $this->previousException; + } + + public function setPreviousException(?AuthenticationException $previousException): void { + $this->previousException = $previousException; + } +} diff --git a/Security/Exception/MissingAuthorizationHeaderException.php b/Security/Exception/MissingAuthorizationHeaderException.php deleted file mode 100644 index 16f32842..00000000 --- a/Security/Exception/MissingAuthorizationHeaderException.php +++ /dev/null @@ -1,21 +0,0 @@ - - */ -class MissingAuthorizationHeaderException extends AuthenticationException -{ - /** - * {@inheritdoc} - */ - public function getMessageKey(): string - { - return 'Authorization Bearer missing..'; - } -} diff --git a/Security/Firewall/OAuth2Listener.php b/Security/Firewall/OAuth2Listener.php index ea0f1f57..896d3862 100644 --- a/Security/Firewall/OAuth2Listener.php +++ b/Security/Firewall/OAuth2Listener.php @@ -14,13 +14,13 @@ use Symfony\Component\Security\Core\Exception\AuthenticationException; use Trikoder\Bundle\OAuth2Bundle\Event\AuthenticationFailureEvent; use Trikoder\Bundle\OAuth2Bundle\Event\AuthenticationScopeFailureEvent; -use Trikoder\Bundle\OAuth2Bundle\Event\MissingAuthorizationHeaderEvent; +use Trikoder\Bundle\OAuth2Bundle\Event\InvalidAuthorizationHeaderEvent; use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; use Trikoder\Bundle\OAuth2Bundle\Response\ResponseFormatter; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2Token; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2TokenFactory; use Trikoder\Bundle\OAuth2Bundle\Security\Exception\InsufficientScopesException; -use Trikoder\Bundle\OAuth2Bundle\Security\Exception\MissingAuthorizationHeaderException; +use Trikoder\Bundle\OAuth2Bundle\Security\Exception\InvalidAuthorizationHeaderException; use Trikoder\Bundle\OAuth2Bundle\Security\Exception\OAuth2AuthenticationFailedException; final class OAuth2Listener @@ -83,11 +83,11 @@ public function __invoke(RequestEvent $event) $request = $this->httpMessageFactory->createRequest($event->getRequest()); if (!$request->hasHeader('Authorization')) { - $exception = new MissingAuthorizationHeaderException(); + $exception = new InvalidAuthorizationHeaderException(); $response = $this->responseFormatter->format($exception->getMessageKey(), Response::HTTP_UNAUTHORIZED); - $missingAuthHeaderEvent = new MissingAuthorizationHeaderEvent($exception, $response); - $this->eventDispatcher->dispatch($missingAuthHeaderEvent, OAuth2Events::MISSING_AUTHORIZATION_HEADER); + $missingAuthHeaderEvent = new InvalidAuthorizationHeaderEvent($exception, $response); + $this->eventDispatcher->dispatch($missingAuthHeaderEvent, OAuth2Events::INVALID_AUTHORIZATION_HEADER); $event->setResponse($missingAuthHeaderEvent->getResponse()); diff --git a/Security/Guard/Authenticator/OAuth2Authenticator.php b/Security/Guard/Authenticator/OAuth2Authenticator.php index 92dbd12e..b2e9d190 100644 --- a/Security/Guard/Authenticator/OAuth2Authenticator.php +++ b/Security/Guard/Authenticator/OAuth2Authenticator.php @@ -16,13 +16,13 @@ use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Guard\AuthenticatorInterface; -use Trikoder\Bundle\OAuth2Bundle\Event\MissingAuthorizationHeaderEvent; +use Trikoder\Bundle\OAuth2Bundle\Event\InvalidAuthorizationHeaderEvent; use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; use Trikoder\Bundle\OAuth2Bundle\Response\ResponseFormatter; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2Token; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2TokenFactory; use Trikoder\Bundle\OAuth2Bundle\Security\Exception\InsufficientScopesException; -use Trikoder\Bundle\OAuth2Bundle\Security\Exception\MissingAuthorizationHeaderException; +use Trikoder\Bundle\OAuth2Bundle\Security\Exception\InvalidAuthorizationHeaderException; use Trikoder\Bundle\OAuth2Bundle\Security\User\NullUser; /** @@ -56,11 +56,13 @@ public function __construct(HttpMessageFactoryInterface $httpMessageFactory, Res public function start(Request $request, ?AuthenticationException $authException = null): Response { - $exception = new MissingAuthorizationHeaderException(); + $exception = new InvalidAuthorizationHeaderException(); + $exception->setPreviousException($authException); + $response = $this->responseFormatter->format($exception->getMessageKey(), Response::HTTP_UNAUTHORIZED); - $event = new MissingAuthorizationHeaderEvent($exception, $response); - $this->eventDispatcher->dispatch($event, OAuth2Events::MISSING_AUTHORIZATION_HEADER); + $event = new InvalidAuthorizationHeaderEvent($exception, $response); + $this->eventDispatcher->dispatch($event, OAuth2Events::INVALID_AUTHORIZATION_HEADER); return $event->getResponse(); } From 9d8794d1eefb43a7bb487e204951c793d3872c8b Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Fri, 11 Dec 2020 17:12:09 +0100 Subject: [PATCH 15/47] UPDATE: Guard now support all exception --- .../Authenticator/OAuth2Authenticator.php | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/Security/Guard/Authenticator/OAuth2Authenticator.php b/Security/Guard/Authenticator/OAuth2Authenticator.php index b2e9d190..38954765 100644 --- a/Security/Guard/Authenticator/OAuth2Authenticator.php +++ b/Security/Guard/Authenticator/OAuth2Authenticator.php @@ -16,6 +16,8 @@ use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Guard\AuthenticatorInterface; +use Trikoder\Bundle\OAuth2Bundle\Event\AuthenticationFailureEvent; +use Trikoder\Bundle\OAuth2Bundle\Event\AuthenticationScopeFailureEvent; use Trikoder\Bundle\OAuth2Bundle\Event\InvalidAuthorizationHeaderEvent; use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; use Trikoder\Bundle\OAuth2Bundle\Response\ResponseFormatter; @@ -101,7 +103,10 @@ public function createAuthenticatedToken(UserInterface $user, $providerKey): OAu $oauth2Token = $this->oauth2TokenFactory->createOAuth2Token($this->psr7Request, $tokenUser, $providerKey); if (!$this->isAccessToRouteGranted($oauth2Token)) { - throw InsufficientScopesException::create($oauth2Token); + $exception = new InsufficientScopesException(); + $exception->setToken($oauth2Token); + + throw $exception; } $oauth2Token->setAuthenticated(true); @@ -113,7 +118,19 @@ public function onAuthenticationFailure(Request $request, AuthenticationExceptio { $this->psr7Request = null; - throw $exception; + $response = $this->responseFormatter->format($exception->getMessageKey(), Response::HTTP_FORBIDDEN); + + if ($exception instanceof InsufficientScopesException) { + $event = new AuthenticationScopeFailureEvent($exception, $response, $exception->getToken()); + $eventName = OAuth2Events::AUTHENTICATION_SCOPE_FAILURE; + } else { + $event = new AuthenticationFailureEvent($exception, $response); + $eventName = OAuth2Events::AUTHENTICATION_FAILURE; + } + + $this->eventDispatcher->dispatch($event, $eventName); + + return $event->getResponse(); } public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey): ?Response From b4836d24a1081e90237a83a62d2debb6256ba3d1 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Fri, 11 Dec 2020 17:13:09 +0100 Subject: [PATCH 16/47] QUALITY: php-cs-fixer --- Event/AuthenticationScopeFailureEvent.php | 5 ----- Event/InvalidAuthorizationHeaderEvent.php | 5 ----- Security/Exception/InsufficientScopesException.php | 3 +-- Security/Exception/InvalidAuthorizationHeaderException.php | 6 ++++-- Security/Guard/Authenticator/OAuth2Authenticator.php | 2 +- 5 files changed, 6 insertions(+), 15 deletions(-) diff --git a/Event/AuthenticationScopeFailureEvent.php b/Event/AuthenticationScopeFailureEvent.php index f75d53ed..9757c5b1 100644 --- a/Event/AuthenticationScopeFailureEvent.php +++ b/Event/AuthenticationScopeFailureEvent.php @@ -7,7 +7,6 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; -use Symfony\Contracts\EventDispatcher\Event; /** * @author Benoit VIGNAL @@ -25,12 +24,8 @@ public function __construct(AuthenticationException $exception, Response $respon $this->token = $token; } - /** - * @return TokenInterface - */ public function getToken(): TokenInterface { return $this->token; } - } diff --git a/Event/InvalidAuthorizationHeaderEvent.php b/Event/InvalidAuthorizationHeaderEvent.php index 9714356a..22af92d3 100644 --- a/Event/InvalidAuthorizationHeaderEvent.php +++ b/Event/InvalidAuthorizationHeaderEvent.php @@ -4,14 +4,9 @@ namespace Trikoder\Bundle\OAuth2Bundle\Event; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Security\Core\Exception\AuthenticationException; -use Symfony\Contracts\EventDispatcher\Event; - /** * @author Benoit VIGNAL */ class InvalidAuthorizationHeaderEvent extends AuthenticationFailureEvent { - } diff --git a/Security/Exception/InsufficientScopesException.php b/Security/Exception/InsufficientScopesException.php index 6ae84f48..c0c1a4bf 100644 --- a/Security/Exception/InsufficientScopesException.php +++ b/Security/Exception/InsufficientScopesException.php @@ -17,7 +17,6 @@ class InsufficientScopesException extends AuthenticationException */ public function getMessageKey(): string { - return "The token has insufficient scopes."; + return 'The token has insufficient scopes.'; } - } diff --git a/Security/Exception/InvalidAuthorizationHeaderException.php b/Security/Exception/InvalidAuthorizationHeaderException.php index 920db9ad..7b087d21 100644 --- a/Security/Exception/InvalidAuthorizationHeaderException.php +++ b/Security/Exception/InvalidAuthorizationHeaderException.php @@ -21,11 +21,13 @@ public function getMessageKey(): string return 'Invalid Authorization Bearer.'; } - public function getPreviousException(): ?AuthenticationException { + public function getPreviousException(): ?AuthenticationException + { return $this->previousException; } - public function setPreviousException(?AuthenticationException $previousException): void { + public function setPreviousException(?AuthenticationException $previousException): void + { $this->previousException = $previousException; } } diff --git a/Security/Guard/Authenticator/OAuth2Authenticator.php b/Security/Guard/Authenticator/OAuth2Authenticator.php index 38954765..6b39f46e 100644 --- a/Security/Guard/Authenticator/OAuth2Authenticator.php +++ b/Security/Guard/Authenticator/OAuth2Authenticator.php @@ -10,7 +10,6 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\User\UserInterface; @@ -83,6 +82,7 @@ public function getCredentials(Request $request) } catch (OAuthServerException $e) { throw new AuthenticationException($e->getMessage(), 0, $e); } + return $this->psr7Request->getAttribute('oauth_user_id'); } From 388bd3ab23e87cd01be1b74ff34c033b3b45af24 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Fri, 11 Dec 2020 18:21:12 +0100 Subject: [PATCH 17/47] FIX: Wrong response status code --- .../OAuth2AuthenticationFailedException.php | 12 ++++++++++++ Security/Firewall/OAuth2Listener.php | 1 + Security/Guard/Authenticator/OAuth2Authenticator.php | 10 +++++++--- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/Security/Exception/OAuth2AuthenticationFailedException.php b/Security/Exception/OAuth2AuthenticationFailedException.php index cc32d047..6f5248ed 100644 --- a/Security/Exception/OAuth2AuthenticationFailedException.php +++ b/Security/Exception/OAuth2AuthenticationFailedException.php @@ -12,6 +12,8 @@ */ class OAuth2AuthenticationFailedException extends AuthenticationException { + private $previousException; + /** * {@inheritdoc} */ @@ -19,4 +21,14 @@ public function getMessageKey(): string { return 'OAuth Token not found.'; } + + public function getPreviousException(): ?\Exception + { + return $this->previousException; + } + + public function setPreviousException(?\Exception $previousException): void + { + $this->previousException = $previousException; + } } diff --git a/Security/Firewall/OAuth2Listener.php b/Security/Firewall/OAuth2Listener.php index 896d3862..357e4e11 100644 --- a/Security/Firewall/OAuth2Listener.php +++ b/Security/Firewall/OAuth2Listener.php @@ -99,6 +99,7 @@ public function __invoke(RequestEvent $event) $authenticatedToken = $this->authenticationManager->authenticate($this->oauth2TokenFactory->createOAuth2Token($request, null, $this->providerKey)); } catch (AuthenticationException $e) { $exception = new OAuth2AuthenticationFailedException(); + $exception->setPreviousException($e); $response = $this->responseFormatter->format($exception->getMessageKey(), Response::HTTP_UNAUTHORIZED); $authenticationFailureEvent = new AuthenticationFailureEvent($exception, $response); diff --git a/Security/Guard/Authenticator/OAuth2Authenticator.php b/Security/Guard/Authenticator/OAuth2Authenticator.php index 6b39f46e..4ee516a1 100644 --- a/Security/Guard/Authenticator/OAuth2Authenticator.php +++ b/Security/Guard/Authenticator/OAuth2Authenticator.php @@ -24,6 +24,7 @@ use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2TokenFactory; use Trikoder\Bundle\OAuth2Bundle\Security\Exception\InsufficientScopesException; use Trikoder\Bundle\OAuth2Bundle\Security\Exception\InvalidAuthorizationHeaderException; +use Trikoder\Bundle\OAuth2Bundle\Security\Exception\OAuth2AuthenticationFailedException; use Trikoder\Bundle\OAuth2Bundle\Security\User\NullUser; /** @@ -80,7 +81,9 @@ public function getCredentials(Request $request) try { $this->psr7Request = $this->resourceServer->validateAuthenticatedRequest($psr7Request); } catch (OAuthServerException $e) { - throw new AuthenticationException($e->getMessage(), 0, $e); + $exception = new OAuth2AuthenticationFailedException(); + $exception->setPreviousException($e); + throw $exception; } return $this->psr7Request->getAttribute('oauth_user_id'); @@ -118,12 +121,13 @@ public function onAuthenticationFailure(Request $request, AuthenticationExceptio { $this->psr7Request = null; - $response = $this->responseFormatter->format($exception->getMessageKey(), Response::HTTP_FORBIDDEN); - if ($exception instanceof InsufficientScopesException) { + $response = $this->responseFormatter->format($exception->getMessageKey(), Response::HTTP_FORBIDDEN); $event = new AuthenticationScopeFailureEvent($exception, $response, $exception->getToken()); $eventName = OAuth2Events::AUTHENTICATION_SCOPE_FAILURE; } else { + $response = $this->responseFormatter->format($exception->getMessageKey(), Response::HTTP_UNAUTHORIZED); + $event = new AuthenticationFailureEvent($exception, $response); $eventName = OAuth2Events::AUTHENTICATION_FAILURE; } From 498d477dff8819679bbb5a5a37e3bd2a189a8115 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Fri, 11 Dec 2020 18:26:40 +0100 Subject: [PATCH 18/47] FIX: Missing WWW-Authenticate response --- Security/Firewall/OAuth2Listener.php | 1 + Security/Guard/Authenticator/OAuth2Authenticator.php | 1 + 2 files changed, 2 insertions(+) diff --git a/Security/Firewall/OAuth2Listener.php b/Security/Firewall/OAuth2Listener.php index 357e4e11..e5774e04 100644 --- a/Security/Firewall/OAuth2Listener.php +++ b/Security/Firewall/OAuth2Listener.php @@ -85,6 +85,7 @@ public function __invoke(RequestEvent $event) if (!$request->hasHeader('Authorization')) { $exception = new InvalidAuthorizationHeaderException(); $response = $this->responseFormatter->format($exception->getMessageKey(), Response::HTTP_UNAUTHORIZED); + $response->headers->set("WWW-Authenticate", "Bearer"); $missingAuthHeaderEvent = new InvalidAuthorizationHeaderEvent($exception, $response); $this->eventDispatcher->dispatch($missingAuthHeaderEvent, OAuth2Events::INVALID_AUTHORIZATION_HEADER); diff --git a/Security/Guard/Authenticator/OAuth2Authenticator.php b/Security/Guard/Authenticator/OAuth2Authenticator.php index 4ee516a1..fb1cccb1 100644 --- a/Security/Guard/Authenticator/OAuth2Authenticator.php +++ b/Security/Guard/Authenticator/OAuth2Authenticator.php @@ -62,6 +62,7 @@ public function start(Request $request, ?AuthenticationException $authException $exception->setPreviousException($authException); $response = $this->responseFormatter->format($exception->getMessageKey(), Response::HTTP_UNAUTHORIZED); + $response->headers->set("WWW-Authenticate", "Bearer"); $event = new InvalidAuthorizationHeaderEvent($exception, $response); $this->eventDispatcher->dispatch($event, OAuth2Events::INVALID_AUTHORIZATION_HEADER); From 334f958f4acd098f95b13dca41ade2ef89c7dfdc Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Fri, 11 Dec 2020 19:48:18 +0100 Subject: [PATCH 19/47] QUALITY: php-cs-fixer --- Security/Firewall/OAuth2Listener.php | 2 +- Security/Guard/Authenticator/OAuth2Authenticator.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Security/Firewall/OAuth2Listener.php b/Security/Firewall/OAuth2Listener.php index e5774e04..cf8afc3f 100644 --- a/Security/Firewall/OAuth2Listener.php +++ b/Security/Firewall/OAuth2Listener.php @@ -85,7 +85,7 @@ public function __invoke(RequestEvent $event) if (!$request->hasHeader('Authorization')) { $exception = new InvalidAuthorizationHeaderException(); $response = $this->responseFormatter->format($exception->getMessageKey(), Response::HTTP_UNAUTHORIZED); - $response->headers->set("WWW-Authenticate", "Bearer"); + $response->headers->set('WWW-Authenticate', 'Bearer'); $missingAuthHeaderEvent = new InvalidAuthorizationHeaderEvent($exception, $response); $this->eventDispatcher->dispatch($missingAuthHeaderEvent, OAuth2Events::INVALID_AUTHORIZATION_HEADER); diff --git a/Security/Guard/Authenticator/OAuth2Authenticator.php b/Security/Guard/Authenticator/OAuth2Authenticator.php index fb1cccb1..cd3d8ee1 100644 --- a/Security/Guard/Authenticator/OAuth2Authenticator.php +++ b/Security/Guard/Authenticator/OAuth2Authenticator.php @@ -62,7 +62,7 @@ public function start(Request $request, ?AuthenticationException $authException $exception->setPreviousException($authException); $response = $this->responseFormatter->format($exception->getMessageKey(), Response::HTTP_UNAUTHORIZED); - $response->headers->set("WWW-Authenticate", "Bearer"); + $response->headers->set('WWW-Authenticate', 'Bearer'); $event = new InvalidAuthorizationHeaderEvent($exception, $response); $this->eventDispatcher->dispatch($event, OAuth2Events::INVALID_AUTHORIZATION_HEADER); From 90ade724201ceb48a92741a45ad781a0880d8104 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Fri, 11 Dec 2020 23:48:06 +0100 Subject: [PATCH 20/47] DOC: Add doc for the new events --- README.md | 1 + docs/data-customization.md | 99 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 docs/data-customization.md diff --git a/README.md b/README.md index a1b56192..99d7fdf0 100644 --- a/README.md +++ b/README.md @@ -208,6 +208,7 @@ security: * [Password grant handling](docs/password-grant-handling.md) * [Implementing custom grant type](docs/implementing-custom-grant-type.md) * [Implementing a custom response formatter](docs/custom-response-formatter.md) +* [Data customization](docs/data-customization.md) ## Contributing diff --git a/docs/data-customization.md b/docs/data-customization.md new file mode 100644 index 00000000..1b953aa4 --- /dev/null +++ b/docs/data-customization.md @@ -0,0 +1,99 @@ +# Data customization + +## Table of contents +- [Customizing the response on invalid authorization header](#customizing-the-response-on-invalid-authorization-header) +- [Customizing the response on invalid scope](#customizing-the-response-on-invalid-scope) +- [Customizing the response on authentication failure](#customizing-the-response-on-authentication-failure) + +## Customizing the response on invalid authorization header + +Called when the `Authorization Bearer` was not found or is malformed. + +Example: + +```php + "onInvalidAuthorizationHeader", + ]; + } + + public function onInvalidAuthorizationHeader(InvalidAuthorizationHeaderEvent $event): void { + $response = new JsonResponse("Invalid header.", Response::HTTP_UNAUTHORIZED); + $event->setResponse($response); + } +} +``` + +## Customizing the response on invalid scope + +Called when the user didn't have the right scope defined. + +Example: + +```php + "onInvalidScope", + ]; + } + + public function onInvalidScope(AuthenticationScopeFailureEvent $event): void { + $response = new JsonResponse("Invalid scope.", Response::HTTP_UNAUTHORIZED); + $event->setResponse($response); + } +} +``` + +## Customizing the response on authentication failure + +Called when the authentication failed. + +Example: + +```php + "onAuthenticationFailure", + ]; + } + + public function onAuthenticationFailure(AuthenticationFailureEvent $event): void { + $response = new JsonResponse("Invalid scope.", Response::HTTP_UNAUTHORIZED); + $event->setResponse($response); + } +} +``` From ce304f6bf72e65ce2971256fa22e35950bfb3b31 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Wed, 30 Dec 2020 15:32:49 +0100 Subject: [PATCH 21/47] Revert "ADD: Custom response formatter" This reverts commit bb05bd69a9b74355185c15f789b8e3e86633dc54. --- DependencyInjection/Configuration.php | 5 ----- DependencyInjection/TrikoderOAuth2Extension.php | 4 ---- README.md | 6 ++---- Resources/config/services.xml | 5 ----- .../Formatter/ResponseFormatterInterface.php | 15 --------------- Security/Firewall/OAuth2Listener.php | 8 -------- docs/custom-response-formatter.md | 16 ---------------- 7 files changed, 2 insertions(+), 57 deletions(-) delete mode 100644 Response/Formatter/ResponseFormatterInterface.php delete mode 100644 docs/custom-response-formatter.md diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 61429f2a..547cb84e 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -27,11 +27,6 @@ public function getConfigTreeBuilder(): TreeBuilder $rootNode ->children() - ->scalarNode('response_formatter') - ->info('Define a custom format in which any response will be converted.') - ->defaultValue('disabled') - ->cannotBeEmpty() - ->end() ->scalarNode('role_prefix') ->info('Set a custom prefix that replaces the default "ROLE_OAUTH2_" role prefix.') ->defaultValue('ROLE_OAUTH2_') diff --git a/DependencyInjection/TrikoderOAuth2Extension.php b/DependencyInjection/TrikoderOAuth2Extension.php index 11244651..0afbe88c 100644 --- a/DependencyInjection/TrikoderOAuth2Extension.php +++ b/DependencyInjection/TrikoderOAuth2Extension.php @@ -39,7 +39,6 @@ use Trikoder\Bundle\OAuth2Bundle\Manager\Doctrine\RefreshTokenManager; use Trikoder\Bundle\OAuth2Bundle\Manager\ScopeManagerInterface; use Trikoder\Bundle\OAuth2Bundle\Model\Scope as ScopeModel; -use Trikoder\Bundle\OAuth2Bundle\Response\ResponseFormatter; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2TokenFactory; use Trikoder\Bundle\OAuth2Bundle\Service\CredentialsRevoker\DoctrineCredentialsRevoker; @@ -65,9 +64,6 @@ public function load(array $configs, ContainerBuilder $container) $container->getDefinition(OAuth2TokenFactory::class) ->setArgument(0, $config['role_prefix']); - $container->getDefinition(ResponseFormatter::class) - ->setArgument(0, $config['response_formatter']); - $container->registerForAutoconfiguration(GrantTypeInterface::class) ->addTag('trikoder.oauth2.authorization_server.grant'); } diff --git a/README.md b/README.md index 99d7fdf0..98af8aab 100644 --- a/README.md +++ b/README.md @@ -162,9 +162,8 @@ This package is currently in the active development. entity_manager: default in_memory: ~ - # Define a custom format in which any response will be converted. - # Supported by default: disabled, json - response_formatter: disabled + # The priority of the event listener that converts an Exception to a Response. + exception_event_listener_priority: 10 # Set a custom prefix that replaces the default "ROLE_OAUTH2_" role prefix. role_prefix: ROLE_OAUTH2_ @@ -207,7 +206,6 @@ security: * [Controlling token scopes](docs/controlling-token-scopes.md) * [Password grant handling](docs/password-grant-handling.md) * [Implementing custom grant type](docs/implementing-custom-grant-type.md) -* [Implementing a custom response formatter](docs/custom-response-formatter.md) * [Data customization](docs/data-customization.md) ## Contributing diff --git a/Resources/config/services.xml b/Resources/config/services.xml index b613c9a9..0dd27d1a 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -86,7 +86,6 @@ - The "%alias_id%" service alias is deprecated and will be removed in v4. @@ -255,9 +254,5 @@ - - - - diff --git a/Response/Formatter/ResponseFormatterInterface.php b/Response/Formatter/ResponseFormatterInterface.php deleted file mode 100644 index c0f3658c..00000000 --- a/Response/Formatter/ResponseFormatterInterface.php +++ /dev/null @@ -1,15 +0,0 @@ - - */ -interface ResponseFormatterInterface -{ - public function getResponse(string $message, int $httpStatusCode): Response; -} diff --git a/Security/Firewall/OAuth2Listener.php b/Security/Firewall/OAuth2Listener.php index cf8afc3f..68d6149c 100644 --- a/Security/Firewall/OAuth2Listener.php +++ b/Security/Firewall/OAuth2Listener.php @@ -16,7 +16,6 @@ use Trikoder\Bundle\OAuth2Bundle\Event\AuthenticationScopeFailureEvent; use Trikoder\Bundle\OAuth2Bundle\Event\InvalidAuthorizationHeaderEvent; use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; -use Trikoder\Bundle\OAuth2Bundle\Response\ResponseFormatter; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2Token; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2TokenFactory; use Trikoder\Bundle\OAuth2Bundle\Security\Exception\InsufficientScopesException; @@ -50,11 +49,6 @@ final class OAuth2Listener */ private $eventDispatcher; - /** - * @var ResponseFormatter - */ - private $responseFormatter; - /** * @var string */ @@ -66,7 +60,6 @@ public function __construct( HttpMessageFactoryInterface $httpMessageFactory, EventDispatcherInterface $eventDispatcher, OAuth2TokenFactory $oauth2TokenFactory, - ResponseFormatter $responseFormatter, string $providerKey ) { $this->tokenStorage = $tokenStorage; @@ -74,7 +67,6 @@ public function __construct( $this->httpMessageFactory = $httpMessageFactory; $this->eventDispatcher = $eventDispatcher; $this->oauth2TokenFactory = $oauth2TokenFactory; - $this->responseFormatter = $responseFormatter; $this->providerKey = $providerKey; } diff --git a/docs/custom-response-formatter.md b/docs/custom-response-formatter.md deleted file mode 100644 index 2ae96655..00000000 --- a/docs/custom-response-formatter.md +++ /dev/null @@ -1,16 +0,0 @@ -# Creating a custom response formatter - -To create a custom response formatter, your class must implement `Trikoder\Bundle\OAuth2Bundle\Response\Formatter`. - -Once that's done, set the `response_formatter` in the configuration file to reference the new class - -Example: -```yml -config/packages/trikoder_oauth2.yaml - - - trikoder_oauth2: - - response_formatter: App\Response\CustomOAuthResponseFormatter - -``` From 76180ab43a9e6be673e49630a51819f3d7ad8a71 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Wed, 30 Dec 2020 15:37:51 +0100 Subject: [PATCH 22/47] Revert "Custom response formatter" --- Resources/config/services.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/Resources/config/services.xml b/Resources/config/services.xml index 0dd27d1a..923cb00b 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -101,7 +101,6 @@ - From 69c13851e4c8b86393bd7f7a1fc134dc8681fa12 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Wed, 30 Dec 2020 15:52:35 +0100 Subject: [PATCH 23/47] UPDATE: Replace the ResponseFormatter to be always an ErrorJsonResponse --- Response/ErrorJsonResponse.php | 21 ++++++ .../Formatter/DisabledResponseFormatter.php | 18 ------ Response/Formatter/JsonResponseFormatter.php | 21 ------ Response/ResponseFormatter.php | 64 ------------------- Security/Firewall/OAuth2Listener.php | 7 +- .../Authenticator/OAuth2Authenticator.php | 14 ++-- 6 files changed, 30 insertions(+), 115 deletions(-) create mode 100644 Response/ErrorJsonResponse.php delete mode 100644 Response/Formatter/DisabledResponseFormatter.php delete mode 100644 Response/Formatter/JsonResponseFormatter.php delete mode 100644 Response/ResponseFormatter.php diff --git a/Response/ErrorJsonResponse.php b/Response/ErrorJsonResponse.php new file mode 100644 index 00000000..8e304312 --- /dev/null +++ b/Response/ErrorJsonResponse.php @@ -0,0 +1,21 @@ + + */ +class ErrorJsonResponse extends JsonResponse +{ + public function __construct(string $message, int $status = Response::HTTP_UNAUTHORIZED) + { + // We force the error body to be always the same + // In the future we could add a specific error code to help debugging + parent::__construct(["message" => $message], $status); + } +} diff --git a/Response/Formatter/DisabledResponseFormatter.php b/Response/Formatter/DisabledResponseFormatter.php deleted file mode 100644 index 5e387e29..00000000 --- a/Response/Formatter/DisabledResponseFormatter.php +++ /dev/null @@ -1,18 +0,0 @@ - - */ -class DisabledResponseFormatter implements ResponseFormatterInterface -{ - public function getResponse(string $message, int $httpStatusCode): Response - { - return new Response($message, $httpStatusCode); - } -} diff --git a/Response/Formatter/JsonResponseFormatter.php b/Response/Formatter/JsonResponseFormatter.php deleted file mode 100644 index d106ef16..00000000 --- a/Response/Formatter/JsonResponseFormatter.php +++ /dev/null @@ -1,21 +0,0 @@ - - */ -class JsonResponseFormatter implements ResponseFormatterInterface -{ - public function getResponse(string $message, int $httpStatusCode): Response - { - return new JsonResponse([ - 'message' => $message, - ], $httpStatusCode); - } -} diff --git a/Response/ResponseFormatter.php b/Response/ResponseFormatter.php deleted file mode 100644 index 2c451f1f..00000000 --- a/Response/ResponseFormatter.php +++ /dev/null @@ -1,64 +0,0 @@ - - */ -class ResponseFormatter -{ - private const DEFAULT_FORMATTER = [ - 'disabled' => DisabledResponseFormatter::class, - 'json' => JsonResponseFormatter::class, - ]; - - /** - * @var string - */ - private $responseFormatter; - - public function __construct(string $responseFormatter) - { - $this->responseFormatter = $responseFormatter; - } - - /** - * Format the message into an unified response type - * - * @throws \Exception - */ - public function format(string $message, int $httpStatusCode): Response - { - $formatter = $this->getFormatter(); - - return $formatter->getResponse($message, $httpStatusCode); - } - - private function getFormatter(): ResponseFormatterInterface - { - // The formatter is one supported out of the box - if (\array_key_exists($this->responseFormatter, self::DEFAULT_FORMATTER)) { - $responseFormatter = self::DEFAULT_FORMATTER[$this->responseFormatter]; - /* @var ResponseFormatterInterface $responseFormatter */ - return new $responseFormatter(); - } - - // User defined formatter - if (class_exists($this->responseFormatter)) { - if (!\in_array($this->responseFormatter, class_implements($this->responseFormatter))) { - return new $this->responseFormatter(); - } else { - throw new \Exception("\"{$this->responseFormatter}\" must implement \"" . ResponseFormatterInterface::class . '"'); - } - } - - throw new \Exception('Unsupported ResponseFormatter type.'); - } -} diff --git a/Security/Firewall/OAuth2Listener.php b/Security/Firewall/OAuth2Listener.php index 68d6149c..b7a691f9 100644 --- a/Security/Firewall/OAuth2Listener.php +++ b/Security/Firewall/OAuth2Listener.php @@ -16,6 +16,7 @@ use Trikoder\Bundle\OAuth2Bundle\Event\AuthenticationScopeFailureEvent; use Trikoder\Bundle\OAuth2Bundle\Event\InvalidAuthorizationHeaderEvent; use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; +use Trikoder\Bundle\OAuth2Bundle\Response\ErrorJsonResponse; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2Token; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2TokenFactory; use Trikoder\Bundle\OAuth2Bundle\Security\Exception\InsufficientScopesException; @@ -76,7 +77,7 @@ public function __invoke(RequestEvent $event) if (!$request->hasHeader('Authorization')) { $exception = new InvalidAuthorizationHeaderException(); - $response = $this->responseFormatter->format($exception->getMessageKey(), Response::HTTP_UNAUTHORIZED); + $response = new ErrorJsonResponse($exception->getMessageKey()); $response->headers->set('WWW-Authenticate', 'Bearer'); $missingAuthHeaderEvent = new InvalidAuthorizationHeaderEvent($exception, $response); @@ -93,7 +94,7 @@ public function __invoke(RequestEvent $event) } catch (AuthenticationException $e) { $exception = new OAuth2AuthenticationFailedException(); $exception->setPreviousException($e); - $response = $this->responseFormatter->format($exception->getMessageKey(), Response::HTTP_UNAUTHORIZED); + $response = new ErrorJsonResponse($exception->getMessageKey()); $authenticationFailureEvent = new AuthenticationFailureEvent($exception, $response); $this->eventDispatcher->dispatch($authenticationFailureEvent, OAuth2Events::AUTHENTICATION_FAILURE); @@ -107,7 +108,7 @@ public function __invoke(RequestEvent $event) $exception = new InsufficientScopesException(); $exception->setToken($authenticatedToken); - $response = $this->responseFormatter->format($exception->getMessageKey(), Response::HTTP_FORBIDDEN); + $response = new ErrorJsonResponse($exception->getMessageKey(), Response::HTTP_FORBIDDEN); $authenticationFailureScopeEvent = new AuthenticationScopeFailureEvent($exception, $response, $authenticatedToken); $this->eventDispatcher->dispatch($authenticationFailureScopeEvent, OAuth2Events::AUTHENTICATION_SCOPE_FAILURE); diff --git a/Security/Guard/Authenticator/OAuth2Authenticator.php b/Security/Guard/Authenticator/OAuth2Authenticator.php index cd3d8ee1..a22c8e90 100644 --- a/Security/Guard/Authenticator/OAuth2Authenticator.php +++ b/Security/Guard/Authenticator/OAuth2Authenticator.php @@ -19,6 +19,7 @@ use Trikoder\Bundle\OAuth2Bundle\Event\AuthenticationScopeFailureEvent; use Trikoder\Bundle\OAuth2Bundle\Event\InvalidAuthorizationHeaderEvent; use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; +use Trikoder\Bundle\OAuth2Bundle\Response\ErrorJsonResponse; use Trikoder\Bundle\OAuth2Bundle\Response\ResponseFormatter; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2Token; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2TokenFactory; @@ -42,18 +43,13 @@ final class OAuth2Authenticator implements AuthenticatorInterface * @var EventDispatcherInterface */ private $eventDispatcher; - /** - * @var ResponseFormatter - */ - private $responseFormatter; - public function __construct(HttpMessageFactoryInterface $httpMessageFactory, ResourceServer $resourceServer, OAuth2TokenFactory $oauth2TokenFactory, EventDispatcherInterface $eventDispatcher, ResponseFormatter $responseFormatter) + public function __construct(HttpMessageFactoryInterface $httpMessageFactory, ResourceServer $resourceServer, OAuth2TokenFactory $oauth2TokenFactory, EventDispatcherInterface $eventDispatcher) { $this->httpMessageFactory = $httpMessageFactory; $this->resourceServer = $resourceServer; $this->oauth2TokenFactory = $oauth2TokenFactory; $this->eventDispatcher = $eventDispatcher; - $this->responseFormatter = $responseFormatter; } public function start(Request $request, ?AuthenticationException $authException = null): Response @@ -61,7 +57,7 @@ public function start(Request $request, ?AuthenticationException $authException $exception = new InvalidAuthorizationHeaderException(); $exception->setPreviousException($authException); - $response = $this->responseFormatter->format($exception->getMessageKey(), Response::HTTP_UNAUTHORIZED); + $response = new ErrorJsonResponse($exception->getMessageKey()); $response->headers->set('WWW-Authenticate', 'Bearer'); $event = new InvalidAuthorizationHeaderEvent($exception, $response); @@ -123,11 +119,11 @@ public function onAuthenticationFailure(Request $request, AuthenticationExceptio $this->psr7Request = null; if ($exception instanceof InsufficientScopesException) { - $response = $this->responseFormatter->format($exception->getMessageKey(), Response::HTTP_FORBIDDEN); + $response = new ErrorJsonResponse($exception->getMessageKey(), Response::HTTP_FORBIDDEN); $event = new AuthenticationScopeFailureEvent($exception, $response, $exception->getToken()); $eventName = OAuth2Events::AUTHENTICATION_SCOPE_FAILURE; } else { - $response = $this->responseFormatter->format($exception->getMessageKey(), Response::HTTP_UNAUTHORIZED); + $response = new ErrorJsonResponse($exception->getMessageKey()); $event = new AuthenticationFailureEvent($exception, $response); $eventName = OAuth2Events::AUTHENTICATION_FAILURE; From 8498f85ca9007a18e30374a7fdfbbc8bd2f36a9e Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Wed, 30 Dec 2020 16:15:17 +0100 Subject: [PATCH 24/47] DOC: Update doc, update the UPGRADE section (in case it will be added in a major release) --- README.md | 2 +- UPGRADE.md | 18 ++++++++++++++++++ ...mization.md => event-data-customization.md} | 14 +++++++------- 3 files changed, 26 insertions(+), 8 deletions(-) rename docs/{data-customization.md => event-data-customization.md} (75%) diff --git a/README.md b/README.md index 98af8aab..ba2fe258 100644 --- a/README.md +++ b/README.md @@ -206,7 +206,7 @@ security: * [Controlling token scopes](docs/controlling-token-scopes.md) * [Password grant handling](docs/password-grant-handling.md) * [Implementing custom grant type](docs/implementing-custom-grant-type.md) -* [Data customization](docs/data-customization.md) +* [Event/Data customization](docs/event-data-customization.md) ## Contributing diff --git a/UPGRADE.md b/UPGRADE.md index 5ac62f79..97cf3e63 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,6 +1,24 @@ # Upgrade Here you will find upgrade steps between releases. +## From 3.2.0 to 4.0.0 + +### Remove `exception_event_listener_priority` +The bundle configuration `exception_event_listener_priority` has been removed in favor of [Event](docs/event-data-customization.md). + +### Error response are now formatted in json by default +All the error response are now formatted as json (no formatting before). + +They can be modified using the new event system. + +All the errors follow the same structure : +```json +{ + "message": "" +} +``` + + ## From 3.1.1 to 3.2.0 The bundle makes modifications to the existing schema. You will need to run the Doctrine schema update process to sync the changes: diff --git a/docs/data-customization.md b/docs/event-data-customization.md similarity index 75% rename from docs/data-customization.md rename to docs/event-data-customization.md index 1b953aa4..10f53b72 100644 --- a/docs/data-customization.md +++ b/docs/event-data-customization.md @@ -1,11 +1,11 @@ -# Data customization +# Event/Data customization ## Table of contents -- [Customizing the response on invalid authorization header](#customizing-the-response-on-invalid-authorization-header) -- [Customizing the response on invalid scope](#customizing-the-response-on-invalid-scope) -- [Customizing the response on authentication failure](#customizing-the-response-on-authentication-failure) +- [INVALID_AUTHORIZATION_HEADER - Customizing the response on invalid authorization header](#oauth2eventsinvalid_authorization_header---customizing-the-response-on-invalid-authorization-header) +- [AUTHENTICATION_SCOPE_FAILURE - Customizing the response on invalid scope](#oauth2eventsauthentication_scope_failure---customizing-the-response-on-invalid-scope) +- [AUTHENTICATION_FAILURE - Customizing the response on authentication failure](#oauth2eventsauthentication_failure---customizing-the-response-on-authentication-failure) -## Customizing the response on invalid authorization header +## OAuth2Events::INVALID_AUTHORIZATION_HEADER - Customizing the response on invalid authorization header Called when the `Authorization Bearer` was not found or is malformed. @@ -36,7 +36,7 @@ class OAuthListener implements EventSubscriberInterface { } ``` -## Customizing the response on invalid scope +## OAuth2Events::AUTHENTICATION_SCOPE_FAILURE - Customizing the response on invalid scope Called when the user didn't have the right scope defined. @@ -67,7 +67,7 @@ class OAuthListener implements EventSubscriberInterface { } ``` -## Customizing the response on authentication failure +## OAuth2Events::AUTHENTICATION_FAILURE - Customizing the response on authentication failure Called when the authentication failed. From 11ab5e851a5c72d2d418bba527e7db6f4a9ffbe1 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Wed, 30 Dec 2020 16:22:16 +0100 Subject: [PATCH 25/47] QUALITY: php-cs-fixer --- Response/ErrorJsonResponse.php | 2 +- Security/Guard/Authenticator/OAuth2Authenticator.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Response/ErrorJsonResponse.php b/Response/ErrorJsonResponse.php index 8e304312..5a1f0d50 100644 --- a/Response/ErrorJsonResponse.php +++ b/Response/ErrorJsonResponse.php @@ -16,6 +16,6 @@ public function __construct(string $message, int $status = Response::HTTP_UNAUTH { // We force the error body to be always the same // In the future we could add a specific error code to help debugging - parent::__construct(["message" => $message], $status); + parent::__construct(['message' => $message], $status); } } diff --git a/Security/Guard/Authenticator/OAuth2Authenticator.php b/Security/Guard/Authenticator/OAuth2Authenticator.php index a22c8e90..a1cc6a07 100644 --- a/Security/Guard/Authenticator/OAuth2Authenticator.php +++ b/Security/Guard/Authenticator/OAuth2Authenticator.php @@ -20,7 +20,6 @@ use Trikoder\Bundle\OAuth2Bundle\Event\InvalidAuthorizationHeaderEvent; use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; use Trikoder\Bundle\OAuth2Bundle\Response\ErrorJsonResponse; -use Trikoder\Bundle\OAuth2Bundle\Response\ResponseFormatter; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2Token; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2TokenFactory; use Trikoder\Bundle\OAuth2Bundle\Security\Exception\InsufficientScopesException; From 0c969fcb298078dab1ab64a357188b843a8930d5 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Fri, 22 Jan 2021 19:33:22 +0100 Subject: [PATCH 26/47] UPDATE: Rename file and default error message for exception (missing and wrong bearer) --- ...rException.php => MissingAuthorizationHeaderException.php} | 4 ++-- Security/Exception/OAuth2AuthenticationFailedException.php | 2 +- Security/Firewall/OAuth2Listener.php | 4 ++-- Security/Guard/Authenticator/OAuth2Authenticator.php | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) rename Security/Exception/{InvalidAuthorizationHeaderException.php => MissingAuthorizationHeaderException.php} (85%) diff --git a/Security/Exception/InvalidAuthorizationHeaderException.php b/Security/Exception/MissingAuthorizationHeaderException.php similarity index 85% rename from Security/Exception/InvalidAuthorizationHeaderException.php rename to Security/Exception/MissingAuthorizationHeaderException.php index 7b087d21..4a171b86 100644 --- a/Security/Exception/InvalidAuthorizationHeaderException.php +++ b/Security/Exception/MissingAuthorizationHeaderException.php @@ -9,7 +9,7 @@ /** * @author Benoit VIGNAL */ -class InvalidAuthorizationHeaderException extends AuthenticationException +class MissingAuthorizationHeaderException extends AuthenticationException { private $previousException; @@ -18,7 +18,7 @@ class InvalidAuthorizationHeaderException extends AuthenticationException */ public function getMessageKey(): string { - return 'Invalid Authorization Bearer.'; + return 'Missing Authorization Bearer.'; } public function getPreviousException(): ?AuthenticationException diff --git a/Security/Exception/OAuth2AuthenticationFailedException.php b/Security/Exception/OAuth2AuthenticationFailedException.php index 6f5248ed..c98cb222 100644 --- a/Security/Exception/OAuth2AuthenticationFailedException.php +++ b/Security/Exception/OAuth2AuthenticationFailedException.php @@ -19,7 +19,7 @@ class OAuth2AuthenticationFailedException extends AuthenticationException */ public function getMessageKey(): string { - return 'OAuth Token not found.'; + return 'Invalid OAuth Token.'; } public function getPreviousException(): ?\Exception diff --git a/Security/Firewall/OAuth2Listener.php b/Security/Firewall/OAuth2Listener.php index b7a691f9..e25ef573 100644 --- a/Security/Firewall/OAuth2Listener.php +++ b/Security/Firewall/OAuth2Listener.php @@ -20,7 +20,7 @@ use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2Token; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2TokenFactory; use Trikoder\Bundle\OAuth2Bundle\Security\Exception\InsufficientScopesException; -use Trikoder\Bundle\OAuth2Bundle\Security\Exception\InvalidAuthorizationHeaderException; +use Trikoder\Bundle\OAuth2Bundle\Security\Exception\MissingAuthorizationHeaderException; use Trikoder\Bundle\OAuth2Bundle\Security\Exception\OAuth2AuthenticationFailedException; final class OAuth2Listener @@ -76,7 +76,7 @@ public function __invoke(RequestEvent $event) $request = $this->httpMessageFactory->createRequest($event->getRequest()); if (!$request->hasHeader('Authorization')) { - $exception = new InvalidAuthorizationHeaderException(); + $exception = new MissingAuthorizationHeaderException(); $response = new ErrorJsonResponse($exception->getMessageKey()); $response->headers->set('WWW-Authenticate', 'Bearer'); diff --git a/Security/Guard/Authenticator/OAuth2Authenticator.php b/Security/Guard/Authenticator/OAuth2Authenticator.php index a1cc6a07..67d17a26 100644 --- a/Security/Guard/Authenticator/OAuth2Authenticator.php +++ b/Security/Guard/Authenticator/OAuth2Authenticator.php @@ -23,7 +23,7 @@ use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2Token; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2TokenFactory; use Trikoder\Bundle\OAuth2Bundle\Security\Exception\InsufficientScopesException; -use Trikoder\Bundle\OAuth2Bundle\Security\Exception\InvalidAuthorizationHeaderException; +use Trikoder\Bundle\OAuth2Bundle\Security\Exception\MissingAuthorizationHeaderException; use Trikoder\Bundle\OAuth2Bundle\Security\Exception\OAuth2AuthenticationFailedException; use Trikoder\Bundle\OAuth2Bundle\Security\User\NullUser; @@ -53,7 +53,7 @@ public function __construct(HttpMessageFactoryInterface $httpMessageFactory, Res public function start(Request $request, ?AuthenticationException $authException = null): Response { - $exception = new InvalidAuthorizationHeaderException(); + $exception = new MissingAuthorizationHeaderException(); $exception->setPreviousException($authException); $response = new ErrorJsonResponse($exception->getMessageKey()); From 95a690d0f3f471e7d335079b8e56edbf38233c92 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Fri, 22 Jan 2021 19:38:34 +0100 Subject: [PATCH 27/47] UPDATE: Rename file and default error message for exception (missing and wrong bearer) --- ...erEvent.php => MissingAuthorizationHeaderEvent.php} | 2 +- OAuth2Events.php | 10 +++++----- Security/Firewall/OAuth2Listener.php | 6 +++--- Security/Guard/Authenticator/OAuth2Authenticator.php | 6 +++--- docs/event-data-customization.md | 10 +++++----- 5 files changed, 17 insertions(+), 17 deletions(-) rename Event/{InvalidAuthorizationHeaderEvent.php => MissingAuthorizationHeaderEvent.php} (70%) diff --git a/Event/InvalidAuthorizationHeaderEvent.php b/Event/MissingAuthorizationHeaderEvent.php similarity index 70% rename from Event/InvalidAuthorizationHeaderEvent.php rename to Event/MissingAuthorizationHeaderEvent.php index 22af92d3..345bb24f 100644 --- a/Event/InvalidAuthorizationHeaderEvent.php +++ b/Event/MissingAuthorizationHeaderEvent.php @@ -7,6 +7,6 @@ /** * @author Benoit VIGNAL */ -class InvalidAuthorizationHeaderEvent extends AuthenticationFailureEvent +class MissingAuthorizationHeaderEvent extends AuthenticationFailureEvent { } diff --git a/OAuth2Events.php b/OAuth2Events.php index a95166b9..6c3af889 100644 --- a/OAuth2Events.php +++ b/OAuth2Events.php @@ -32,24 +32,24 @@ final class OAuth2Events public const AUTHORIZATION_REQUEST_RESOLVE = 'trikoder.oauth2.authorization_request_resolve'; /** - * The INVALID_AUTHORIZATION_HEADER event occurs when the + * The MISSING_AUTHORIZATION_HEADER event occurs when the * Authorization Bearer header was not found, or is wrong/malformed * * You can set a custom error message in the response body */ - public const INVALID_AUTHORIZATION_HEADER = 'trikoder.oauth2.invalid_authorization_header'; + public const MISSING_AUTHORIZATION_HEADER = 'trikoder.oauth2.missing_authorization_header'; /** - * The AUTHENTICATION_FAILURE event occurs when the oauth token wasn't found + * The AUTHENTICATION_FAILURE event occurs when the oauth token verification failed * * You can set a custom error message in the response body */ - public const AUTHENTICATION_FAILURE = 'trikoder.oauth2.autentication_failure'; + public const AUTHENTICATION_FAILURE = 'trikoder.oauth2.authentication_failure'; /** * The AUTHENTICATION_SCOPE_FAILURE event occurs when the scope validation for the token failed * * You can set a custom error message in the response body */ - public const AUTHENTICATION_SCOPE_FAILURE = 'trikoder.oauth2.autentication_scope_failure'; + public const AUTHENTICATION_SCOPE_FAILURE = 'trikoder.oauth2.authentication_scope_failure'; } diff --git a/Security/Firewall/OAuth2Listener.php b/Security/Firewall/OAuth2Listener.php index e25ef573..43cd8221 100644 --- a/Security/Firewall/OAuth2Listener.php +++ b/Security/Firewall/OAuth2Listener.php @@ -14,7 +14,7 @@ use Symfony\Component\Security\Core\Exception\AuthenticationException; use Trikoder\Bundle\OAuth2Bundle\Event\AuthenticationFailureEvent; use Trikoder\Bundle\OAuth2Bundle\Event\AuthenticationScopeFailureEvent; -use Trikoder\Bundle\OAuth2Bundle\Event\InvalidAuthorizationHeaderEvent; +use Trikoder\Bundle\OAuth2Bundle\Event\MissingAuthorizationHeaderEvent; use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; use Trikoder\Bundle\OAuth2Bundle\Response\ErrorJsonResponse; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2Token; @@ -80,8 +80,8 @@ public function __invoke(RequestEvent $event) $response = new ErrorJsonResponse($exception->getMessageKey()); $response->headers->set('WWW-Authenticate', 'Bearer'); - $missingAuthHeaderEvent = new InvalidAuthorizationHeaderEvent($exception, $response); - $this->eventDispatcher->dispatch($missingAuthHeaderEvent, OAuth2Events::INVALID_AUTHORIZATION_HEADER); + $missingAuthHeaderEvent = new MissingAuthorizationHeaderEvent($exception, $response); + $this->eventDispatcher->dispatch($missingAuthHeaderEvent, OAuth2Events::MISSING_AUTHORIZATION_HEADER); $event->setResponse($missingAuthHeaderEvent->getResponse()); diff --git a/Security/Guard/Authenticator/OAuth2Authenticator.php b/Security/Guard/Authenticator/OAuth2Authenticator.php index 67d17a26..d22ff951 100644 --- a/Security/Guard/Authenticator/OAuth2Authenticator.php +++ b/Security/Guard/Authenticator/OAuth2Authenticator.php @@ -17,7 +17,7 @@ use Symfony\Component\Security\Guard\AuthenticatorInterface; use Trikoder\Bundle\OAuth2Bundle\Event\AuthenticationFailureEvent; use Trikoder\Bundle\OAuth2Bundle\Event\AuthenticationScopeFailureEvent; -use Trikoder\Bundle\OAuth2Bundle\Event\InvalidAuthorizationHeaderEvent; +use Trikoder\Bundle\OAuth2Bundle\Event\MissingAuthorizationHeaderEvent; use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; use Trikoder\Bundle\OAuth2Bundle\Response\ErrorJsonResponse; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2Token; @@ -59,8 +59,8 @@ public function start(Request $request, ?AuthenticationException $authException $response = new ErrorJsonResponse($exception->getMessageKey()); $response->headers->set('WWW-Authenticate', 'Bearer'); - $event = new InvalidAuthorizationHeaderEvent($exception, $response); - $this->eventDispatcher->dispatch($event, OAuth2Events::INVALID_AUTHORIZATION_HEADER); + $event = new MissingAuthorizationHeaderEvent($exception, $response); + $this->eventDispatcher->dispatch($event, OAuth2Events::MISSING_AUTHORIZATION_HEADER); return $event->getResponse(); } diff --git a/docs/event-data-customization.md b/docs/event-data-customization.md index 10f53b72..4ae4525c 100644 --- a/docs/event-data-customization.md +++ b/docs/event-data-customization.md @@ -1,11 +1,11 @@ # Event/Data customization ## Table of contents -- [INVALID_AUTHORIZATION_HEADER - Customizing the response on invalid authorization header](#oauth2eventsinvalid_authorization_header---customizing-the-response-on-invalid-authorization-header) +- [MISSING_AUTHORIZATION_HEADER - Customizing the response on invalid authorization header](#oauth2eventsmissing_authorization_header---customizing-the-response-on-invalid-authorization-header) - [AUTHENTICATION_SCOPE_FAILURE - Customizing the response on invalid scope](#oauth2eventsauthentication_scope_failure---customizing-the-response-on-invalid-scope) - [AUTHENTICATION_FAILURE - Customizing the response on authentication failure](#oauth2eventsauthentication_failure---customizing-the-response-on-authentication-failure) -## OAuth2Events::INVALID_AUTHORIZATION_HEADER - Customizing the response on invalid authorization header +## OAuth2Events::MISSING_AUTHORIZATION_HEADER - Customizing the response on invalid authorization header Called when the `Authorization Bearer` was not found or is malformed. @@ -19,17 +19,17 @@ namespace App\EventListener\Kernel; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\JsonResponse; -use Trikoder\Bundle\OAuth2Bundle\Event\InvalidAuthorizationHeaderEvent; +use Trikoder\Bundle\OAuth2Bundle\Event\MissingAuthorizationHeaderEvent; use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; class OAuthListener implements EventSubscriberInterface { public static function getSubscribedEvents() { return [ - OAuth2Events::INVALID_AUTHORIZATION_HEADER => "onInvalidAuthorizationHeader", + OAuth2Events::MISSING_AUTHORIZATION_HEADER => "onMissingAuthorizationHeader", ]; } - public function onInvalidAuthorizationHeader(InvalidAuthorizationHeaderEvent $event): void { + public function onMissingAuthorizationHeader(MissingAuthorizationHeaderEvent $event): void { $response = new JsonResponse("Invalid header.", Response::HTTP_UNAUTHORIZED); $event->setResponse($response); } From af19bf82a26603d0abdbf22c7dea8c2d13e49425 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Sun, 24 Jan 2021 20:43:21 +0100 Subject: [PATCH 28/47] ADD: InvalidCredentials event --- Controller/AuthorizationController.php | 12 ++++++- Controller/TokenController.php | 21 ++++++++++-- Event/InvalidCredentialsEvent.php | 16 +++++++++ League/Repository/UserRepository.php | 6 +++- OAuth2Events.php | 5 +++ Resources/config/services.xml | 1 + .../Exception/InsufficientScopesException.php | 2 +- .../Exception/InvalidCredentialsException.php | 21 ++++++++++++ docs/event-data-customization.md | 34 +++++++++++++++++++ docs/password-grant-handling.md | 3 ++ 10 files changed, 116 insertions(+), 5 deletions(-) create mode 100644 Event/InvalidCredentialsEvent.php create mode 100644 Security/Exception/InvalidCredentialsException.php diff --git a/Controller/AuthorizationController.php b/Controller/AuthorizationController.php index 68253057..fddf8f54 100644 --- a/Controller/AuthorizationController.php +++ b/Controller/AuthorizationController.php @@ -13,8 +13,11 @@ use Trikoder\Bundle\OAuth2Bundle\Converter\UserConverterInterface; use Trikoder\Bundle\OAuth2Bundle\Event\AuthorizationRequestResolveEvent; use Trikoder\Bundle\OAuth2Bundle\Event\AuthorizationRequestResolveEventFactory; +use Trikoder\Bundle\OAuth2Bundle\Event\InvalidCredentialsEvent; use Trikoder\Bundle\OAuth2Bundle\Manager\ClientManagerInterface; use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; +use Trikoder\Bundle\OAuth2Bundle\Response\ErrorJsonResponse; +use Trikoder\Bundle\OAuth2Bundle\Security\Exception\InvalidCredentialsException; final class AuthorizationController { @@ -57,7 +60,7 @@ public function __construct( $this->clientManager = $clientManager; } - public function indexAction(ServerRequestInterface $serverRequest, ResponseFactoryInterface $responseFactory): ResponseInterface + public function indexAction(ServerRequestInterface $serverRequest, ResponseFactoryInterface $responseFactory) { $serverResponse = $responseFactory->createResponse(); @@ -91,6 +94,13 @@ public function indexAction(ServerRequestInterface $serverRequest, ResponseFacto return $this->server->completeAuthorizationRequest($authRequest, $serverResponse); } catch (OAuthServerException $e) { return $e->generateHttpResponse($serverResponse); + } catch (InvalidCredentialsException $e) { + $response = new ErrorJsonResponse($e->getMessageKey()); + + $event = new InvalidCredentialsEvent($e, $response); + $this->eventDispatcher->dispatch($event, OAuth2Events::INVALID_CREDENTIALS); + + return $event->getResponse(); } } } diff --git a/Controller/TokenController.php b/Controller/TokenController.php index 6d78619e..b2d07828 100644 --- a/Controller/TokenController.php +++ b/Controller/TokenController.php @@ -9,6 +9,11 @@ use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Trikoder\Bundle\OAuth2Bundle\Event\InvalidCredentialsEvent; +use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; +use Trikoder\Bundle\OAuth2Bundle\Response\ErrorJsonResponse; +use Trikoder\Bundle\OAuth2Bundle\Security\Exception\InvalidCredentialsException; final class TokenController { @@ -16,22 +21,34 @@ final class TokenController * @var AuthorizationServer */ private $server; + /** + * @var EventDispatcherInterface + */ + private $eventDispatcher; - public function __construct(AuthorizationServer $server) + public function __construct(AuthorizationServer $server, EventDispatcherInterface $eventDispatcher) { $this->server = $server; + $this->eventDispatcher = $eventDispatcher; } public function indexAction( ServerRequestInterface $serverRequest, ResponseFactoryInterface $responseFactory - ): ResponseInterface { + ) { $serverResponse = $responseFactory->createResponse(); try { return $this->server->respondToAccessTokenRequest($serverRequest, $serverResponse); } catch (OAuthServerException $e) { return $e->generateHttpResponse($serverResponse); + } catch (InvalidCredentialsException $e) { + $response = new ErrorJsonResponse($e->getMessageKey()); + + $event = new InvalidCredentialsEvent($e, $response); + $this->eventDispatcher->dispatch($event, OAuth2Events::INVALID_CREDENTIALS); + + return $response; } } } diff --git a/Event/InvalidCredentialsEvent.php b/Event/InvalidCredentialsEvent.php new file mode 100644 index 00000000..6e92d058 --- /dev/null +++ b/Event/InvalidCredentialsEvent.php @@ -0,0 +1,16 @@ + + */ +class InvalidCredentialsEvent extends AuthenticationFailureEvent +{ +} diff --git a/League/Repository/UserRepository.php b/League/Repository/UserRepository.php index bdacb26c..7d962afc 100644 --- a/League/Repository/UserRepository.php +++ b/League/Repository/UserRepository.php @@ -8,10 +8,14 @@ use League\OAuth2\Server\Repositories\UserRepositoryInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Trikoder\Bundle\OAuth2Bundle\Converter\UserConverterInterface; +use Trikoder\Bundle\OAuth2Bundle\Event\InvalidCredentialsEvent; use Trikoder\Bundle\OAuth2Bundle\Event\UserResolveEvent; use Trikoder\Bundle\OAuth2Bundle\Manager\ClientManagerInterface; use Trikoder\Bundle\OAuth2Bundle\Model\Grant as GrantModel; use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; +use Trikoder\Bundle\OAuth2Bundle\Response\ErrorJsonResponse; +use Trikoder\Bundle\OAuth2Bundle\Security\Exception\InvalidCredentialsException; +use Trikoder\Bundle\OAuth2Bundle\Security\Exception\OAuth2AuthenticationFailedException; final class UserRepository implements UserRepositoryInterface { @@ -64,7 +68,7 @@ public function getUserEntityByUserCredentials( $user = $event->getUser(); if (null === $user) { - return null; + throw new InvalidCredentialsException(); } return $this->userConverter->toLeague($user); diff --git a/OAuth2Events.php b/OAuth2Events.php index 6c3af889..4ba195ac 100644 --- a/OAuth2Events.php +++ b/OAuth2Events.php @@ -14,6 +14,11 @@ final class OAuth2Events */ public const USER_RESOLVE = 'trikoder.oauth2.user_resolve'; + /** + * The INVALID_CREDENTIALS event occurs when no user was found (invalid credentials) + */ + public const INVALID_CREDENTIALS = 'trikoder.oauth2.invalid_credentials'; + /** * The SCOPE_RESOLVE event occurs right before the user obtains their * valid access token. diff --git a/Resources/config/services.xml b/Resources/config/services.xml index 923cb00b..a502e8dc 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -173,6 +173,7 @@ + diff --git a/Security/Exception/InsufficientScopesException.php b/Security/Exception/InsufficientScopesException.php index c0c1a4bf..422ca83c 100644 --- a/Security/Exception/InsufficientScopesException.php +++ b/Security/Exception/InsufficientScopesException.php @@ -17,6 +17,6 @@ class InsufficientScopesException extends AuthenticationException */ public function getMessageKey(): string { - return 'The token has insufficient scopes.'; + return 'Insufficient scopes.'; } } diff --git a/Security/Exception/InvalidCredentialsException.php b/Security/Exception/InvalidCredentialsException.php new file mode 100644 index 00000000..77d47232 --- /dev/null +++ b/Security/Exception/InvalidCredentialsException.php @@ -0,0 +1,21 @@ + + */ +class InvalidCredentialsException extends AuthenticationException +{ + /** + * {@inheritdoc} + */ + public function getMessageKey(): string + { + return 'Invalid user credentials.'; + } +} diff --git a/docs/event-data-customization.md b/docs/event-data-customization.md index 4ae4525c..134bae47 100644 --- a/docs/event-data-customization.md +++ b/docs/event-data-customization.md @@ -3,6 +3,7 @@ ## Table of contents - [MISSING_AUTHORIZATION_HEADER - Customizing the response on invalid authorization header](#oauth2eventsmissing_authorization_header---customizing-the-response-on-invalid-authorization-header) - [AUTHENTICATION_SCOPE_FAILURE - Customizing the response on invalid scope](#oauth2eventsauthentication_scope_failure---customizing-the-response-on-invalid-scope) +- [INVALID_CREDENTIALS - Customizing the response on credentials failure](#oauth2eventsinvalid_credentials---customizing-the-response-on-credentials-failure) - [AUTHENTICATION_FAILURE - Customizing the response on authentication failure](#oauth2eventsauthentication_failure---customizing-the-response-on-authentication-failure) ## OAuth2Events::MISSING_AUTHORIZATION_HEADER - Customizing the response on invalid authorization header @@ -67,6 +68,39 @@ class OAuthListener implements EventSubscriberInterface { } ``` +## OAuth2Events::INVALID_CREDENTIALS - Customizing the response on credentials failure + +Called when the credential verification failed. + +Often when no user was return after calling `OAuth2Events::USER_RESOLVE` + +Example: + +```php + "onInvalidCredentials", + ]; + } + + public function onInvalidCredentials(InvalidCredentialsEvent $event): void { + $response = new JsonResponse("Wrong username/password.", Response::HTTP_UNAUTHORIZED); + $event->setResponse($response); + } +} +``` + ## OAuth2Events::AUTHENTICATION_FAILURE - Customizing the response on authentication failure Called when the authentication failed. diff --git a/docs/password-grant-handling.md b/docs/password-grant-handling.md index 5c58f88d..617228b0 100644 --- a/docs/password-grant-handling.md +++ b/docs/password-grant-handling.md @@ -2,6 +2,9 @@ The `password` grant issues access and refresh tokens that are bound to both a client and a user within your application. As user system implementations can differ greatly on an application basis, the `trikoder.oauth2.user_resolve` was created which allows you to decide which user you want to bind to issuing tokens. +After triggering this event, if no user was returned an [`OAuth2Events::INVALID_CREDENTIALS`](event-data-customization.md#oauth2eventsinvalid_credentials---customizing-the-response-on-credentials-failure) will be triggered. +Within this new event you'll be able to define a custom error response for example. + ## Requirements The user model should implement the `Symfony\Component\Security\Core\User\UserInterface` interface. From 573f08feb317868004f229b4b070d959e8221508 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Tue, 20 Apr 2021 22:30:24 +0200 Subject: [PATCH 29/47] update: revert to original version --- League/Repository/UserRepository.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/League/Repository/UserRepository.php b/League/Repository/UserRepository.php index 7d962afc..bdacb26c 100644 --- a/League/Repository/UserRepository.php +++ b/League/Repository/UserRepository.php @@ -8,14 +8,10 @@ use League\OAuth2\Server\Repositories\UserRepositoryInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Trikoder\Bundle\OAuth2Bundle\Converter\UserConverterInterface; -use Trikoder\Bundle\OAuth2Bundle\Event\InvalidCredentialsEvent; use Trikoder\Bundle\OAuth2Bundle\Event\UserResolveEvent; use Trikoder\Bundle\OAuth2Bundle\Manager\ClientManagerInterface; use Trikoder\Bundle\OAuth2Bundle\Model\Grant as GrantModel; use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; -use Trikoder\Bundle\OAuth2Bundle\Response\ErrorJsonResponse; -use Trikoder\Bundle\OAuth2Bundle\Security\Exception\InvalidCredentialsException; -use Trikoder\Bundle\OAuth2Bundle\Security\Exception\OAuth2AuthenticationFailedException; final class UserRepository implements UserRepositoryInterface { @@ -68,7 +64,7 @@ public function getUserEntityByUserCredentials( $user = $event->getUser(); if (null === $user) { - throw new InvalidCredentialsException(); + return null; } return $this->userConverter->toLeague($user); From 025af85061a8985ff871269e7063d82b920f2b48 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Wed, 21 Apr 2021 00:25:45 +0200 Subject: [PATCH 30/47] update: start switching to an ExceptionEventFactory --- Event/AuthenticationFailureEvent.php | 10 +-- Event/AuthenticationScopeFailureEvent.php | 4 +- Resources/config/services.xml | 6 +- Security/Exception/ExceptionEventFactory.php | 64 ++++++++++++++++++++ Security/Firewall/OAuth2Listener.php | 40 +++--------- 5 files changed, 85 insertions(+), 39 deletions(-) create mode 100644 Security/Exception/ExceptionEventFactory.php diff --git a/Event/AuthenticationFailureEvent.php b/Event/AuthenticationFailureEvent.php index e5243689..c0f66666 100644 --- a/Event/AuthenticationFailureEvent.php +++ b/Event/AuthenticationFailureEvent.php @@ -4,7 +4,7 @@ namespace Trikoder\Bundle\OAuth2Bundle\Event; -use Symfony\Component\HttpFoundation\Response; +use Psr\Http\Message\ResponseInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Contracts\EventDispatcher\Event; @@ -19,11 +19,11 @@ class AuthenticationFailureEvent extends Event protected $exception; /** - * @var Response + * @var ResponseInterface */ protected $response; - public function __construct(AuthenticationException $exception, Response $response) + public function __construct(AuthenticationException $exception, ResponseInterface $response) { $this->exception = $exception; $this->response = $response; @@ -34,12 +34,12 @@ public function getException(): AuthenticationException return $this->exception; } - public function getResponse(): Response + public function getResponse(): ResponseInterface { return $this->response; } - public function setResponse(Response $response): void + public function setResponse(ResponseInterface $response): void { $this->response = $response; } diff --git a/Event/AuthenticationScopeFailureEvent.php b/Event/AuthenticationScopeFailureEvent.php index 9757c5b1..3127513e 100644 --- a/Event/AuthenticationScopeFailureEvent.php +++ b/Event/AuthenticationScopeFailureEvent.php @@ -4,7 +4,7 @@ namespace Trikoder\Bundle\OAuth2Bundle\Event; -use Symfony\Component\HttpFoundation\Response; +use Psr\Http\Message\ResponseInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; @@ -18,7 +18,7 @@ class AuthenticationScopeFailureEvent extends AuthenticationFailureEvent */ private $token; - public function __construct(AuthenticationException $exception, Response $response, TokenInterface $token) + public function __construct(AuthenticationException $exception, ResponseInterface $response, TokenInterface $token) { parent::__construct($exception, $response); $this->token = $token; diff --git a/Resources/config/services.xml b/Resources/config/services.xml index b977f5bc..6d860c5d 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -84,7 +84,7 @@ - + @@ -243,6 +243,10 @@ + + + + diff --git a/Security/Exception/ExceptionEventFactory.php b/Security/Exception/ExceptionEventFactory.php new file mode 100644 index 00000000..1eb42f3e --- /dev/null +++ b/Security/Exception/ExceptionEventFactory.php @@ -0,0 +1,64 @@ +eventDispatcher = $eventDispatcher; + $this->responseFactory = $responseFactory; + } + + public function invalidClient(ServerRequestInterface $serverRequest): MissingAuthorizationHeaderEvent + { + $exception = OAuthServerException::invalidClient($serverRequest); + + $event = new MissingAuthorizationHeaderEvent($exception, $exception->generateHttpResponse($this->responseFactory->createResponse())); + $this->eventDispatcher->dispatch($event, OAuth2Events::MISSING_AUTHORIZATION_HEADER); + + return $event; + } + + public function accessDenied(Throwable $previous = null): AuthenticationFailureEvent + { + $exception = OAuthServerException::accessDenied(null, null, $previous); + + $event = new AuthenticationFailureEvent($exception, $exception->generateHttpResponse($this->responseFactory->createResponse())); + $this->eventDispatcher->dispatch($event, OAuth2Events::AUTHENTICATION_FAILURE); + + return $event; + } + + public function invalidScope(OAuth2Token $authenticatedToken): AuthenticationScopeFailureEvent + { + $exception = OAuthServerException::invalidScope(""); + + $event = new AuthenticationScopeFailureEvent($exception, $exception->generateHttpResponse($this->responseFactory->createResponse()), $authenticatedToken); + $this->eventDispatcher->dispatch($event, OAuth2Events::AUTHENTICATION_SCOPE_FAILURE); + + return $event; + } +} diff --git a/Security/Firewall/OAuth2Listener.php b/Security/Firewall/OAuth2Listener.php index 43cd8221..a54a2e5d 100644 --- a/Security/Firewall/OAuth2Listener.php +++ b/Security/Firewall/OAuth2Listener.php @@ -4,8 +4,8 @@ namespace Trikoder\Bundle\OAuth2Bundle\Security\Firewall; +use League\OAuth2\Server\Exception\OAuthServerException; use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\RequestEvent; @@ -14,6 +14,7 @@ use Symfony\Component\Security\Core\Exception\AuthenticationException; use Trikoder\Bundle\OAuth2Bundle\Event\AuthenticationFailureEvent; use Trikoder\Bundle\OAuth2Bundle\Event\AuthenticationScopeFailureEvent; +use Trikoder\Bundle\OAuth2Bundle\Security\Exception\ExceptionEventFactory; use Trikoder\Bundle\OAuth2Bundle\Event\MissingAuthorizationHeaderEvent; use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; use Trikoder\Bundle\OAuth2Bundle\Response\ErrorJsonResponse; @@ -21,7 +22,6 @@ use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2TokenFactory; use Trikoder\Bundle\OAuth2Bundle\Security\Exception\InsufficientScopesException; use Trikoder\Bundle\OAuth2Bundle\Security\Exception\MissingAuthorizationHeaderException; -use Trikoder\Bundle\OAuth2Bundle\Security\Exception\OAuth2AuthenticationFailedException; final class OAuth2Listener { @@ -46,9 +46,9 @@ final class OAuth2Listener private $oauth2TokenFactory; /** - * @var EventDispatcherInterface + * @var ExceptionEventFactory */ - private $eventDispatcher; + private $exceptionEventFactory; /** * @var string @@ -59,14 +59,14 @@ public function __construct( TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, HttpMessageFactoryInterface $httpMessageFactory, - EventDispatcherInterface $eventDispatcher, + ExceptionEventFactory $exceptionEventFactory, OAuth2TokenFactory $oauth2TokenFactory, string $providerKey ) { $this->tokenStorage = $tokenStorage; $this->authenticationManager = $authenticationManager; $this->httpMessageFactory = $httpMessageFactory; - $this->eventDispatcher = $eventDispatcher; + $this->exceptionEventFactory = $exceptionEventFactory; $this->oauth2TokenFactory = $oauth2TokenFactory; $this->providerKey = $providerKey; } @@ -76,15 +76,8 @@ public function __invoke(RequestEvent $event) $request = $this->httpMessageFactory->createRequest($event->getRequest()); if (!$request->hasHeader('Authorization')) { - $exception = new MissingAuthorizationHeaderException(); - $response = new ErrorJsonResponse($exception->getMessageKey()); - $response->headers->set('WWW-Authenticate', 'Bearer'); - - $missingAuthHeaderEvent = new MissingAuthorizationHeaderEvent($exception, $response); - $this->eventDispatcher->dispatch($missingAuthHeaderEvent, OAuth2Events::MISSING_AUTHORIZATION_HEADER); - + $missingAuthHeaderEvent = $this->exceptionEventFactory->invalidClient($request); $event->setResponse($missingAuthHeaderEvent->getResponse()); - return; } @@ -92,29 +85,14 @@ public function __invoke(RequestEvent $event) /** @var OAuth2Token $authenticatedToken */ $authenticatedToken = $this->authenticationManager->authenticate($this->oauth2TokenFactory->createOAuth2Token($request, null, $this->providerKey)); } catch (AuthenticationException $e) { - $exception = new OAuth2AuthenticationFailedException(); - $exception->setPreviousException($e); - $response = new ErrorJsonResponse($exception->getMessageKey()); - - $authenticationFailureEvent = new AuthenticationFailureEvent($exception, $response); - $this->eventDispatcher->dispatch($authenticationFailureEvent, OAuth2Events::AUTHENTICATION_FAILURE); - + $authenticationFailureEvent = $this->exceptionEventFactory->accessDenied($e); $event->setResponse($authenticationFailureEvent->getResponse()); - return; } if (!$this->isAccessToRouteGranted($event->getRequest(), $authenticatedToken)) { - $exception = new InsufficientScopesException(); - $exception->setToken($authenticatedToken); - - $response = new ErrorJsonResponse($exception->getMessageKey(), Response::HTTP_FORBIDDEN); - - $authenticationFailureScopeEvent = new AuthenticationScopeFailureEvent($exception, $response, $authenticatedToken); - $this->eventDispatcher->dispatch($authenticationFailureScopeEvent, OAuth2Events::AUTHENTICATION_SCOPE_FAILURE); - + $authenticationFailureScopeEvent = $this->exceptionEventFactory->invalidScope($authenticatedToken); $event->setResponse($authenticationFailureScopeEvent->getResponse()); - return; } From 8cb11b22c345ff9b13a5e85cafe6eee3b6a2d575 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Wed, 21 Apr 2021 00:40:56 +0200 Subject: [PATCH 31/47] add: invalidCredential can now use the event system --- Security/Exception/ExceptionEventFactory.php | 23 ++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/Security/Exception/ExceptionEventFactory.php b/Security/Exception/ExceptionEventFactory.php index 1eb42f3e..f1fe8b7e 100644 --- a/Security/Exception/ExceptionEventFactory.php +++ b/Security/Exception/ExceptionEventFactory.php @@ -6,9 +6,9 @@ use League\OAuth2\Server\Exception\OAuthServerException; use Psr\Http\Message\ResponseFactoryInterface; +use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Symfony\Contracts\EventDispatcher\Event; use Trikoder\Bundle\OAuth2Bundle\Event\AuthenticationFailureEvent; use Trikoder\Bundle\OAuth2Bundle\Event\AuthenticationScopeFailureEvent; use Trikoder\Bundle\OAuth2Bundle\Event\MissingAuthorizationHeaderEvent; @@ -32,21 +32,36 @@ public function __construct(EventDispatcherInterface $eventDispatcher, ResponseF $this->responseFactory = $responseFactory; } + private function generateResponse(OAuthServerException $exception): ResponseInterface + { + return $exception->generateHttpResponse($this->responseFactory->createResponse()); + } + public function invalidClient(ServerRequestInterface $serverRequest): MissingAuthorizationHeaderEvent { $exception = OAuthServerException::invalidClient($serverRequest); - $event = new MissingAuthorizationHeaderEvent($exception, $exception->generateHttpResponse($this->responseFactory->createResponse())); + $event = new MissingAuthorizationHeaderEvent($exception, $this->generateResponse($exception)); $this->eventDispatcher->dispatch($event, OAuth2Events::MISSING_AUTHORIZATION_HEADER); return $event; } + public function invalidCredentials(): InvalidCredentialsEvent + { + $exception = OAuthServerException::invalidCredentials(); + + $event = new InvalidCredentialsEvent($exception, $this->generateResponse($exception)); + $this->eventDispatcher->dispatch($event, OAuth2Events::INVALID_CREDENTIALS); + + return $event; + } + public function accessDenied(Throwable $previous = null): AuthenticationFailureEvent { $exception = OAuthServerException::accessDenied(null, null, $previous); - $event = new AuthenticationFailureEvent($exception, $exception->generateHttpResponse($this->responseFactory->createResponse())); + $event = new AuthenticationFailureEvent($exception, $this->generateResponse($exception)); $this->eventDispatcher->dispatch($event, OAuth2Events::AUTHENTICATION_FAILURE); return $event; @@ -56,7 +71,7 @@ public function invalidScope(OAuth2Token $authenticatedToken): AuthenticationSco { $exception = OAuthServerException::invalidScope(""); - $event = new AuthenticationScopeFailureEvent($exception, $exception->generateHttpResponse($this->responseFactory->createResponse()), $authenticatedToken); + $event = new AuthenticationScopeFailureEvent($exception, $this->generateResponse($exception), $authenticatedToken); $this->eventDispatcher->dispatch($event, OAuth2Events::AUTHENTICATION_SCOPE_FAILURE); return $event; From 397ec2dc7ae9dee6d3e9c1547d8150b6f2dc3866 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Thu, 22 Apr 2021 22:23:29 +0200 Subject: [PATCH 32/47] update: switch to AbstractOauthEvent --- Event/AbstractOauthEvent.php | 47 +++++++++++++++++++++++ Event/AuthenticationFailureEvent.php | 36 +---------------- Event/AuthenticationScopeFailureEvent.php | 6 +-- Event/InvalidCredentialsEvent.php | 6 +-- Event/MissingAuthorizationHeaderEvent.php | 2 +- 5 files changed, 53 insertions(+), 44 deletions(-) create mode 100644 Event/AbstractOauthEvent.php diff --git a/Event/AbstractOauthEvent.php b/Event/AbstractOauthEvent.php new file mode 100644 index 00000000..72fe0cf9 --- /dev/null +++ b/Event/AbstractOauthEvent.php @@ -0,0 +1,47 @@ + + */ +class AbstractOauthEvent extends Event +{ + /** + * @var OAuthServerException + */ + protected $exception; + + /** + * @var ResponseInterface + */ + protected $response; + + public function __construct(OAuthServerException $exception, ResponseInterface $response) + { + $this->exception = $exception; + $this->response = $response; + } + + public function getException(): OAuthServerException + { + return $this->exception; + } + + public function getResponse(): ResponseInterface + { + return $this->response; + } + + public function setResponse(ResponseInterface $response): AbstractOauthEvent + { + $this->response = $response; + return $this; + } +} diff --git a/Event/AuthenticationFailureEvent.php b/Event/AuthenticationFailureEvent.php index c0f66666..5dbbbe3e 100644 --- a/Event/AuthenticationFailureEvent.php +++ b/Event/AuthenticationFailureEvent.php @@ -4,43 +4,9 @@ namespace Trikoder\Bundle\OAuth2Bundle\Event; -use Psr\Http\Message\ResponseInterface; -use Symfony\Component\Security\Core\Exception\AuthenticationException; -use Symfony\Contracts\EventDispatcher\Event; - /** * @author Benoit VIGNAL */ -class AuthenticationFailureEvent extends Event +class AuthenticationFailureEvent extends AbstractOauthEvent { - /** - * @var AuthenticationException - */ - protected $exception; - - /** - * @var ResponseInterface - */ - protected $response; - - public function __construct(AuthenticationException $exception, ResponseInterface $response) - { - $this->exception = $exception; - $this->response = $response; - } - - public function getException(): AuthenticationException - { - return $this->exception; - } - - public function getResponse(): ResponseInterface - { - return $this->response; - } - - public function setResponse(ResponseInterface $response): void - { - $this->response = $response; - } } diff --git a/Event/AuthenticationScopeFailureEvent.php b/Event/AuthenticationScopeFailureEvent.php index 3127513e..2db1ff0d 100644 --- a/Event/AuthenticationScopeFailureEvent.php +++ b/Event/AuthenticationScopeFailureEvent.php @@ -4,21 +4,21 @@ namespace Trikoder\Bundle\OAuth2Bundle\Event; +use League\OAuth2\Server\Exception\OAuthServerException; use Psr\Http\Message\ResponseInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Core\Exception\AuthenticationException; /** * @author Benoit VIGNAL */ -class AuthenticationScopeFailureEvent extends AuthenticationFailureEvent +class AuthenticationScopeFailureEvent extends AbstractOauthEvent { /** * @var TokenInterface */ private $token; - public function __construct(AuthenticationException $exception, ResponseInterface $response, TokenInterface $token) + public function __construct(OAuthServerException $exception, ResponseInterface $response, TokenInterface $token) { parent::__construct($exception, $response); $this->token = $token; diff --git a/Event/InvalidCredentialsEvent.php b/Event/InvalidCredentialsEvent.php index 6e92d058..1f6fc478 100644 --- a/Event/InvalidCredentialsEvent.php +++ b/Event/InvalidCredentialsEvent.php @@ -4,13 +4,9 @@ namespace Trikoder\Bundle\OAuth2Bundle\Event; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Security\Core\Exception\AuthenticationException; -use Symfony\Contracts\EventDispatcher\Event; - /** * @author Benoit VIGNAL */ -class InvalidCredentialsEvent extends AuthenticationFailureEvent +class InvalidCredentialsEvent extends AbstractOauthEvent { } diff --git a/Event/MissingAuthorizationHeaderEvent.php b/Event/MissingAuthorizationHeaderEvent.php index 345bb24f..7343fe1e 100644 --- a/Event/MissingAuthorizationHeaderEvent.php +++ b/Event/MissingAuthorizationHeaderEvent.php @@ -7,6 +7,6 @@ /** * @author Benoit VIGNAL */ -class MissingAuthorizationHeaderEvent extends AuthenticationFailureEvent +class MissingAuthorizationHeaderEvent extends AbstractOauthEvent { } From c77a0715f33e30b7faa8b76b85797d1049fa2aa5 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Fri, 23 Apr 2021 00:18:02 +0200 Subject: [PATCH 33/47] update: Move OautEvent to a dedicated folder --- Event/AuthenticationFailureEvent.php | 12 ------------ Event/InvalidCredentialsEvent.php | 12 ------------ Event/MissingAuthorizationHeaderEvent.php | 12 ------------ Event/{ => OauthEvent}/AbstractOauthEvent.php | 14 ++++++++++++-- .../OauthEvent/AuthenticationFailureEvent.php | 18 ++++++++++++++++++ .../AuthenticationScopeFailureEvent.php | 14 ++++++++++---- .../AuthorizationServerErrorEvent.php | 18 ++++++++++++++++++ Event/OauthEvent/InvalidCredentialsEvent.php | 18 ++++++++++++++++++ .../MissingAuthorizationHeaderEvent.php | 18 ++++++++++++++++++ 9 files changed, 94 insertions(+), 42 deletions(-) delete mode 100644 Event/AuthenticationFailureEvent.php delete mode 100644 Event/InvalidCredentialsEvent.php delete mode 100644 Event/MissingAuthorizationHeaderEvent.php rename Event/{ => OauthEvent}/AbstractOauthEvent.php (71%) create mode 100644 Event/OauthEvent/AuthenticationFailureEvent.php rename Event/{ => OauthEvent}/AuthenticationScopeFailureEvent.php (61%) create mode 100644 Event/OauthEvent/AuthorizationServerErrorEvent.php create mode 100644 Event/OauthEvent/InvalidCredentialsEvent.php create mode 100644 Event/OauthEvent/MissingAuthorizationHeaderEvent.php diff --git a/Event/AuthenticationFailureEvent.php b/Event/AuthenticationFailureEvent.php deleted file mode 100644 index 5dbbbe3e..00000000 --- a/Event/AuthenticationFailureEvent.php +++ /dev/null @@ -1,12 +0,0 @@ - - */ -class AuthenticationFailureEvent extends AbstractOauthEvent -{ -} diff --git a/Event/InvalidCredentialsEvent.php b/Event/InvalidCredentialsEvent.php deleted file mode 100644 index 1f6fc478..00000000 --- a/Event/InvalidCredentialsEvent.php +++ /dev/null @@ -1,12 +0,0 @@ - - */ -class InvalidCredentialsEvent extends AbstractOauthEvent -{ -} diff --git a/Event/MissingAuthorizationHeaderEvent.php b/Event/MissingAuthorizationHeaderEvent.php deleted file mode 100644 index 7343fe1e..00000000 --- a/Event/MissingAuthorizationHeaderEvent.php +++ /dev/null @@ -1,12 +0,0 @@ - - */ -class MissingAuthorizationHeaderEvent extends AbstractOauthEvent -{ -} diff --git a/Event/AbstractOauthEvent.php b/Event/OauthEvent/AbstractOauthEvent.php similarity index 71% rename from Event/AbstractOauthEvent.php rename to Event/OauthEvent/AbstractOauthEvent.php index 72fe0cf9..de737755 100644 --- a/Event/AbstractOauthEvent.php +++ b/Event/OauthEvent/AbstractOauthEvent.php @@ -2,16 +2,17 @@ declare(strict_types=1); -namespace Trikoder\Bundle\OAuth2Bundle\Event; +namespace Trikoder\Bundle\OAuth2Bundle\Event\OauthEvent; use League\OAuth2\Server\Exception\OAuthServerException; use Psr\Http\Message\ResponseInterface; +use Symfony\Component\HttpFoundation\Response; use Symfony\Contracts\EventDispatcher\Event; /** * @author Benoit VIGNAL */ -class AbstractOauthEvent extends Event +abstract class AbstractOauthEvent extends Event { /** * @var OAuthServerException @@ -29,6 +30,11 @@ public function __construct(OAuthServerException $exception, ResponseInterface $ $this->response = $response; } + /** + * @return string The event name that will be use with the eventDispatcher + */ + abstract function getEventName(): string; + public function getException(): OAuthServerException { return $this->exception; @@ -39,6 +45,10 @@ public function getResponse(): ResponseInterface return $this->response; } + /** + * @param ResponseInterface $response + * @return $this + */ public function setResponse(ResponseInterface $response): AbstractOauthEvent { $this->response = $response; diff --git a/Event/OauthEvent/AuthenticationFailureEvent.php b/Event/OauthEvent/AuthenticationFailureEvent.php new file mode 100644 index 00000000..619a5ba9 --- /dev/null +++ b/Event/OauthEvent/AuthenticationFailureEvent.php @@ -0,0 +1,18 @@ + + */ +class AuthenticationFailureEvent extends AbstractOauthEvent +{ + function getEventName(): string + { + return OAuth2Events::AUTHENTICATION_FAILURE; + } +} diff --git a/Event/AuthenticationScopeFailureEvent.php b/Event/OauthEvent/AuthenticationScopeFailureEvent.php similarity index 61% rename from Event/AuthenticationScopeFailureEvent.php rename to Event/OauthEvent/AuthenticationScopeFailureEvent.php index 2db1ff0d..51a77192 100644 --- a/Event/AuthenticationScopeFailureEvent.php +++ b/Event/OauthEvent/AuthenticationScopeFailureEvent.php @@ -2,11 +2,12 @@ declare(strict_types=1); -namespace Trikoder\Bundle\OAuth2Bundle\Event; +namespace Trikoder\Bundle\OAuth2Bundle\Event\OauthEvent; use League\OAuth2\Server\Exception\OAuthServerException; use Psr\Http\Message\ResponseInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; /** * @author Benoit VIGNAL @@ -14,17 +15,22 @@ class AuthenticationScopeFailureEvent extends AbstractOauthEvent { /** - * @var TokenInterface + * @var TokenInterface|null */ private $token; - public function __construct(OAuthServerException $exception, ResponseInterface $response, TokenInterface $token) + public function __construct(OAuthServerException $exception, ResponseInterface $response, ?TokenInterface $token = null) { parent::__construct($exception, $response); $this->token = $token; } - public function getToken(): TokenInterface + function getEventName(): string + { + return OAuth2Events::AUTHENTICATION_SCOPE_FAILURE; + } + + public function getToken(): ?TokenInterface { return $this->token; } diff --git a/Event/OauthEvent/AuthorizationServerErrorEvent.php b/Event/OauthEvent/AuthorizationServerErrorEvent.php new file mode 100644 index 00000000..dff69591 --- /dev/null +++ b/Event/OauthEvent/AuthorizationServerErrorEvent.php @@ -0,0 +1,18 @@ + + */ +class AuthorizationServerErrorEvent extends AbstractOauthEvent +{ + function getEventName(): string + { + return OAuth2Events::AUTHORIZATION_SERVER_ERROR; + } +} diff --git a/Event/OauthEvent/InvalidCredentialsEvent.php b/Event/OauthEvent/InvalidCredentialsEvent.php new file mode 100644 index 00000000..82d5d4e9 --- /dev/null +++ b/Event/OauthEvent/InvalidCredentialsEvent.php @@ -0,0 +1,18 @@ + + */ +class InvalidCredentialsEvent extends AbstractOauthEvent +{ + function getEventName(): string + { + return OAuth2Events::INVALID_CREDENTIALS; + } +} diff --git a/Event/OauthEvent/MissingAuthorizationHeaderEvent.php b/Event/OauthEvent/MissingAuthorizationHeaderEvent.php new file mode 100644 index 00000000..3995b6cd --- /dev/null +++ b/Event/OauthEvent/MissingAuthorizationHeaderEvent.php @@ -0,0 +1,18 @@ + + */ +class MissingAuthorizationHeaderEvent extends AbstractOauthEvent +{ + function getEventName(): string + { + return OAuth2Events::AUTHORIZATION_HEADER_FAILURE; + } +} From de9ece029df50c3ce5cd3bfb9a1be0502eb045bf Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Fri, 23 Apr 2021 00:18:11 +0200 Subject: [PATCH 34/47] update: new events names --- OAuth2Events.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/OAuth2Events.php b/OAuth2Events.php index 4ba195ac..b956a3f5 100644 --- a/OAuth2Events.php +++ b/OAuth2Events.php @@ -37,12 +37,12 @@ final class OAuth2Events public const AUTHORIZATION_REQUEST_RESOLVE = 'trikoder.oauth2.authorization_request_resolve'; /** - * The MISSING_AUTHORIZATION_HEADER event occurs when the + * The AUTHORIZATION_HEADER_FAILURE event occurs when the * Authorization Bearer header was not found, or is wrong/malformed * * You can set a custom error message in the response body */ - public const MISSING_AUTHORIZATION_HEADER = 'trikoder.oauth2.missing_authorization_header'; + public const AUTHORIZATION_HEADER_FAILURE = 'trikoder.oauth2.authorization_header_failure'; /** * The AUTHENTICATION_FAILURE event occurs when the oauth token verification failed @@ -57,4 +57,11 @@ final class OAuth2Events * You can set a custom error message in the response body */ public const AUTHENTICATION_SCOPE_FAILURE = 'trikoder.oauth2.authentication_scope_failure'; + + /** + * The AUTHORIZATION_SERVER_ERROR event occurs when the scope validation for the token failed + * + * You can set a custom error message in the response body + */ + public const AUTHORIZATION_SERVER_ERROR = 'trikoder.oauth2.authorization_server_error'; } From c6d814dbd4d0d4739d01d8e45dd43c58545374d7 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Fri, 23 Apr 2021 00:20:07 +0200 Subject: [PATCH 35/47] _wip_: update: handling league exception and calling event --- Controller/AuthorizationController.php | 22 ++++---- Controller/TokenController.php | 26 +++------ League/Repository/UserRepository.php | 3 +- Resources/config/services.xml | 3 +- Response/ErrorJsonResponse.php | 21 -------- Security/Exception/ExceptionEventFactory.php | 55 ++++++++++++++++---- docs/event-data-customization.md | 30 +++-------- 7 files changed, 74 insertions(+), 86 deletions(-) delete mode 100644 Response/ErrorJsonResponse.php diff --git a/Controller/AuthorizationController.php b/Controller/AuthorizationController.php index fddf8f54..9d2c4ce2 100644 --- a/Controller/AuthorizationController.php +++ b/Controller/AuthorizationController.php @@ -7,17 +7,14 @@ use League\OAuth2\Server\AuthorizationServer; use League\OAuth2\Server\Exception\OAuthServerException; use Psr\Http\Message\ResponseFactoryInterface; -use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Trikoder\Bundle\OAuth2Bundle\Converter\UserConverterInterface; use Trikoder\Bundle\OAuth2Bundle\Event\AuthorizationRequestResolveEvent; use Trikoder\Bundle\OAuth2Bundle\Event\AuthorizationRequestResolveEventFactory; -use Trikoder\Bundle\OAuth2Bundle\Event\InvalidCredentialsEvent; use Trikoder\Bundle\OAuth2Bundle\Manager\ClientManagerInterface; use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; -use Trikoder\Bundle\OAuth2Bundle\Response\ErrorJsonResponse; -use Trikoder\Bundle\OAuth2Bundle\Security\Exception\InvalidCredentialsException; +use Trikoder\Bundle\OAuth2Bundle\Security\Exception\ExceptionEventFactory; final class AuthorizationController { @@ -46,18 +43,25 @@ final class AuthorizationController */ private $clientManager; + /** + * @var ExceptionEventFactory + */ + private $exceptionEventFactory; + public function __construct( AuthorizationServer $server, EventDispatcherInterface $eventDispatcher, AuthorizationRequestResolveEventFactory $eventFactory, UserConverterInterface $userConverter, - ClientManagerInterface $clientManager + ClientManagerInterface $clientManager, + ExceptionEventFactory $exceptionEventFactory ) { $this->server = $server; $this->eventDispatcher = $eventDispatcher; $this->eventFactory = $eventFactory; $this->userConverter = $userConverter; $this->clientManager = $clientManager; + $this->exceptionEventFactory = $exceptionEventFactory; } public function indexAction(ServerRequestInterface $serverRequest, ResponseFactoryInterface $responseFactory) @@ -93,13 +97,7 @@ public function indexAction(ServerRequestInterface $serverRequest, ResponseFacto return $this->server->completeAuthorizationRequest($authRequest, $serverResponse); } catch (OAuthServerException $e) { - return $e->generateHttpResponse($serverResponse); - } catch (InvalidCredentialsException $e) { - $response = new ErrorJsonResponse($e->getMessageKey()); - - $event = new InvalidCredentialsEvent($e, $response); - $this->eventDispatcher->dispatch($event, OAuth2Events::INVALID_CREDENTIALS); - + $event = $this->exceptionEventFactory->handleLeagueException($e); return $event->getResponse(); } } diff --git a/Controller/TokenController.php b/Controller/TokenController.php index b2d07828..3fe8aae1 100644 --- a/Controller/TokenController.php +++ b/Controller/TokenController.php @@ -7,13 +7,8 @@ use League\OAuth2\Server\AuthorizationServer; use League\OAuth2\Server\Exception\OAuthServerException; use Psr\Http\Message\ResponseFactoryInterface; -use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Trikoder\Bundle\OAuth2Bundle\Event\InvalidCredentialsEvent; -use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; -use Trikoder\Bundle\OAuth2Bundle\Response\ErrorJsonResponse; -use Trikoder\Bundle\OAuth2Bundle\Security\Exception\InvalidCredentialsException; +use Trikoder\Bundle\OAuth2Bundle\Security\Exception\ExceptionEventFactory; final class TokenController { @@ -21,15 +16,16 @@ final class TokenController * @var AuthorizationServer */ private $server; + /** - * @var EventDispatcherInterface + * @var ExceptionEventFactory */ - private $eventDispatcher; + private $exceptionEventFactory; - public function __construct(AuthorizationServer $server, EventDispatcherInterface $eventDispatcher) + public function __construct(AuthorizationServer $server, ExceptionEventFactory $exceptionEventFactory) { $this->server = $server; - $this->eventDispatcher = $eventDispatcher; + $this->exceptionEventFactory = $exceptionEventFactory; } public function indexAction( @@ -41,14 +37,8 @@ public function indexAction( try { return $this->server->respondToAccessTokenRequest($serverRequest, $serverResponse); } catch (OAuthServerException $e) { - return $e->generateHttpResponse($serverResponse); - } catch (InvalidCredentialsException $e) { - $response = new ErrorJsonResponse($e->getMessageKey()); - - $event = new InvalidCredentialsEvent($e, $response); - $this->eventDispatcher->dispatch($event, OAuth2Events::INVALID_CREDENTIALS); - - return $response; + $event = $this->exceptionEventFactory->handleLeagueException($e); + return $event->getResponse(); } } } diff --git a/League/Repository/UserRepository.php b/League/Repository/UserRepository.php index bdacb26c..18711187 100644 --- a/League/Repository/UserRepository.php +++ b/League/Repository/UserRepository.php @@ -5,6 +5,7 @@ namespace Trikoder\Bundle\OAuth2Bundle\League\Repository; use League\OAuth2\Server\Entities\ClientEntityInterface; +use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Repositories\UserRepositoryInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Trikoder\Bundle\OAuth2Bundle\Converter\UserConverterInterface; @@ -64,7 +65,7 @@ public function getUserEntityByUserCredentials( $user = $event->getUser(); if (null === $user) { - return null; + throw OAuthServerException::invalidCredentials(); } return $this->userConverter->toLeague($user); diff --git a/Resources/config/services.xml b/Resources/config/services.xml index 6d860c5d..5b3426d0 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -155,6 +155,7 @@ + @@ -173,7 +174,7 @@ - + diff --git a/Response/ErrorJsonResponse.php b/Response/ErrorJsonResponse.php deleted file mode 100644 index 5a1f0d50..00000000 --- a/Response/ErrorJsonResponse.php +++ /dev/null @@ -1,21 +0,0 @@ - - */ -class ErrorJsonResponse extends JsonResponse -{ - public function __construct(string $message, int $status = Response::HTTP_UNAUTHORIZED) - { - // We force the error body to be always the same - // In the future we could add a specific error code to help debugging - parent::__construct(['message' => $message], $status); - } -} diff --git a/Security/Exception/ExceptionEventFactory.php b/Security/Exception/ExceptionEventFactory.php index f1fe8b7e..b3b44fd2 100644 --- a/Security/Exception/ExceptionEventFactory.php +++ b/Security/Exception/ExceptionEventFactory.php @@ -9,18 +9,32 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Trikoder\Bundle\OAuth2Bundle\Event\AuthenticationFailureEvent; -use Trikoder\Bundle\OAuth2Bundle\Event\AuthenticationScopeFailureEvent; -use Trikoder\Bundle\OAuth2Bundle\Event\MissingAuthorizationHeaderEvent; -use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; +use Trikoder\Bundle\OAuth2Bundle\Event\OauthEvent\AbstractOauthEvent; +use Trikoder\Bundle\OAuth2Bundle\Event\OauthEvent\AuthenticationFailureEvent; +use Trikoder\Bundle\OAuth2Bundle\Event\OauthEvent\AuthenticationScopeFailureEvent; +use Trikoder\Bundle\OAuth2Bundle\Event\OauthEvent\AuthorizationServerErrorEvent; +use Trikoder\Bundle\OAuth2Bundle\Event\OauthEvent\InvalidCredentialsEvent; +use Trikoder\Bundle\OAuth2Bundle\Event\OauthEvent\MissingAuthorizationHeaderEvent; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2Token; +/** + * @author Benoit VIGNAL + */ class ExceptionEventFactory { + protected const MAPPING_LEAGUE_EVENT = [ + "invalid_client" => MissingAuthorizationHeaderEvent::class, + "invalid_scope" => AuthenticationScopeFailureEvent::class, + "invalid_credentials" => InvalidCredentialsEvent::class, + "server_error" => AuthorizationServerErrorEvent::class, + "access_denied" => AuthenticationFailureEvent::class, + ]; + /** * @var EventDispatcherInterface */ private $eventDispatcher; + /** * @var ResponseFactoryInterface */ @@ -37,12 +51,33 @@ private function generateResponse(OAuthServerException $exception): ResponseInte return $exception->generateHttpResponse($this->responseFactory->createResponse()); } + /** + * Will receive the league exception, find the right event to notify and the return it. + * Doing this give us the ability to notify other app and being able to apply their custom logic + * + * @param OAuthServerException $exception + */ + public function handleLeagueException(OAuthServerException $exception): AbstractOauthEvent + { + if (array_key_exists($exception->getErrorType(), self::MAPPING_LEAGUE_EVENT)) { + $eventClass = self::MAPPING_LEAGUE_EVENT[$exception->getErrorType()]; + /** @var AbstractOauthEvent $event */ + $event = new $eventClass($exception, $this->generateResponse($exception)); + $this->eventDispatcher->dispatch($event, $event->getEventName()); + return $event; + } else { // We fallback to a generic event + $event = new AuthorizationServerErrorEvent($exception, $this->generateResponse($exception)); + $this->eventDispatcher->dispatch($event, $event->getEventName()); + return $event; + } + } + public function invalidClient(ServerRequestInterface $serverRequest): MissingAuthorizationHeaderEvent { $exception = OAuthServerException::invalidClient($serverRequest); $event = new MissingAuthorizationHeaderEvent($exception, $this->generateResponse($exception)); - $this->eventDispatcher->dispatch($event, OAuth2Events::MISSING_AUTHORIZATION_HEADER); + $this->eventDispatcher->dispatch($event, $event->getEventName()); return $event; } @@ -52,7 +87,7 @@ public function invalidCredentials(): InvalidCredentialsEvent $exception = OAuthServerException::invalidCredentials(); $event = new InvalidCredentialsEvent($exception, $this->generateResponse($exception)); - $this->eventDispatcher->dispatch($event, OAuth2Events::INVALID_CREDENTIALS); + $this->eventDispatcher->dispatch($event, $event->getEventName()); return $event; } @@ -62,17 +97,17 @@ public function accessDenied(Throwable $previous = null): AuthenticationFailureE $exception = OAuthServerException::accessDenied(null, null, $previous); $event = new AuthenticationFailureEvent($exception, $this->generateResponse($exception)); - $this->eventDispatcher->dispatch($event, OAuth2Events::AUTHENTICATION_FAILURE); + $this->eventDispatcher->dispatch($event, $event->getEventName()); return $event; } - public function invalidScope(OAuth2Token $authenticatedToken): AuthenticationScopeFailureEvent + public function invalidScope(OAuth2Token $authenticatedToken, string $scope = ""): AuthenticationScopeFailureEvent { - $exception = OAuthServerException::invalidScope(""); + $exception = OAuthServerException::invalidScope($scope); $event = new AuthenticationScopeFailureEvent($exception, $this->generateResponse($exception), $authenticatedToken); - $this->eventDispatcher->dispatch($event, OAuth2Events::AUTHENTICATION_SCOPE_FAILURE); + $this->eventDispatcher->dispatch($event, $event->getEventName()); return $event; } diff --git a/docs/event-data-customization.md b/docs/event-data-customization.md index 134bae47..f67f336c 100644 --- a/docs/event-data-customization.md +++ b/docs/event-data-customization.md @@ -1,12 +1,12 @@ # Event/Data customization ## Table of contents -- [MISSING_AUTHORIZATION_HEADER - Customizing the response on invalid authorization header](#oauth2eventsmissing_authorization_header---customizing-the-response-on-invalid-authorization-header) +- [AUTHORIZATION_HEADER_FAILURE - Customizing the response on invalid authorization header](#oauth2eventsmissing_authorization_header---customizing-the-response-on-invalid-authorization-header) - [AUTHENTICATION_SCOPE_FAILURE - Customizing the response on invalid scope](#oauth2eventsauthentication_scope_failure---customizing-the-response-on-invalid-scope) - [INVALID_CREDENTIALS - Customizing the response on credentials failure](#oauth2eventsinvalid_credentials---customizing-the-response-on-credentials-failure) - [AUTHENTICATION_FAILURE - Customizing the response on authentication failure](#oauth2eventsauthentication_failure---customizing-the-response-on-authentication-failure) -## OAuth2Events::MISSING_AUTHORIZATION_HEADER - Customizing the response on invalid authorization header +## OAuth2Events::AUTHORIZATION_HEADER_FAILURE - Customizing the response on invalid authorization header Called when the `Authorization Bearer` was not found or is malformed. @@ -17,16 +17,12 @@ Example: namespace App\EventListener\Kernel; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpFoundation\JsonResponse; -use Trikoder\Bundle\OAuth2Bundle\Event\MissingAuthorizationHeaderEvent; -use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; +use Symfony\Component\EventDispatcher\EventSubscriberInterface;use Symfony\Component\HttpFoundation\JsonResponse;use Symfony\Component\HttpFoundation\Response;use Trikoder\Bundle\OAuth2Bundle\Event\OauthEvent\MissingAuthorizationHeaderEvent;use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; class OAuthListener implements EventSubscriberInterface { public static function getSubscribedEvents() { return [ - OAuth2Events::MISSING_AUTHORIZATION_HEADER => "onMissingAuthorizationHeader", + OAuth2Events::AUTHORIZATION_HEADER_FAILURE => "onMissingAuthorizationHeader", ]; } @@ -48,11 +44,7 @@ Example: namespace App\EventListener\Kernel; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpFoundation\JsonResponse; -use Trikoder\Bundle\OAuth2Bundle\Event\AuthenticationScopeFailureEvent; -use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; +use Symfony\Component\EventDispatcher\EventSubscriberInterface;use Symfony\Component\HttpFoundation\JsonResponse;use Symfony\Component\HttpFoundation\Response;use Trikoder\Bundle\OAuth2Bundle\Event\OauthEvent\AuthenticationScopeFailureEvent;use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; class OAuthListener implements EventSubscriberInterface { public static function getSubscribedEvents() { @@ -81,11 +73,7 @@ Example: namespace App\EventListener\Kernel; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpFoundation\JsonResponse; -use Trikoder\Bundle\OAuth2Bundle\Event\InvalidCredentialsEvent; -use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; +use Symfony\Component\EventDispatcher\EventSubscriberInterface;use Symfony\Component\HttpFoundation\JsonResponse;use Symfony\Component\HttpFoundation\Response;use Trikoder\Bundle\OAuth2Bundle\Event\OauthEvent\InvalidCredentialsEvent;use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; class OAuthListener implements EventSubscriberInterface { public static function getSubscribedEvents() { @@ -112,11 +100,7 @@ Example: namespace App\EventListener\Kernel; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpFoundation\JsonResponse; -use Trikoder\Bundle\OAuth2Bundle\Event\AuthenticationFailureEvent; -use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; +use Symfony\Component\EventDispatcher\EventSubscriberInterface;use Symfony\Component\HttpFoundation\JsonResponse;use Symfony\Component\HttpFoundation\Response;use Trikoder\Bundle\OAuth2Bundle\Event\OauthEvent\AuthenticationFailureEvent;use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; class OAuthListener implements EventSubscriberInterface { public static function getSubscribedEvents() { From 221951cc9045e5147c44b1d6958fbf4fda79963e Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Fri, 23 Apr 2021 11:09:04 +0200 Subject: [PATCH 36/47] update: OauthEvent folder change --- Security/Guard/Authenticator/OAuth2Authenticator.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Security/Guard/Authenticator/OAuth2Authenticator.php b/Security/Guard/Authenticator/OAuth2Authenticator.php index d22ff951..c9ecd109 100644 --- a/Security/Guard/Authenticator/OAuth2Authenticator.php +++ b/Security/Guard/Authenticator/OAuth2Authenticator.php @@ -15,9 +15,9 @@ use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Guard\AuthenticatorInterface; -use Trikoder\Bundle\OAuth2Bundle\Event\AuthenticationFailureEvent; -use Trikoder\Bundle\OAuth2Bundle\Event\AuthenticationScopeFailureEvent; -use Trikoder\Bundle\OAuth2Bundle\Event\MissingAuthorizationHeaderEvent; +use Trikoder\Bundle\OAuth2Bundle\Event\OauthEvent\AuthenticationFailureEvent; +use Trikoder\Bundle\OAuth2Bundle\Event\OauthEvent\AuthenticationScopeFailureEvent; +use Trikoder\Bundle\OAuth2Bundle\Event\OauthEvent\MissingAuthorizationHeaderEvent; use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; use Trikoder\Bundle\OAuth2Bundle\Response\ErrorJsonResponse; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2Token; @@ -60,7 +60,7 @@ public function start(Request $request, ?AuthenticationException $authException $response->headers->set('WWW-Authenticate', 'Bearer'); $event = new MissingAuthorizationHeaderEvent($exception, $response); - $this->eventDispatcher->dispatch($event, OAuth2Events::MISSING_AUTHORIZATION_HEADER); + $this->eventDispatcher->dispatch($event, OAuth2Events::AUTHORIZATION_HEADER_FAILURE); return $event->getResponse(); } @@ -115,6 +115,7 @@ public function createAuthenticatedToken(UserInterface $user, $providerKey): OAu public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response { + dump($exception); $this->psr7Request = null; if ($exception instanceof InsufficientScopesException) { From 6134a253893bf6678c9b77cfcb991d5f3efec147 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Sat, 15 May 2021 21:55:43 +0200 Subject: [PATCH 37/47] update: import cleanup --- Security/Firewall/OAuth2Listener.php | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/Security/Firewall/OAuth2Listener.php b/Security/Firewall/OAuth2Listener.php index a54a2e5d..70a95593 100644 --- a/Security/Firewall/OAuth2Listener.php +++ b/Security/Firewall/OAuth2Listener.php @@ -4,24 +4,15 @@ namespace Trikoder\Bundle\OAuth2Bundle\Security\Firewall; -use League\OAuth2\Server\Exception\OAuthServerException; use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; -use Trikoder\Bundle\OAuth2Bundle\Event\AuthenticationFailureEvent; -use Trikoder\Bundle\OAuth2Bundle\Event\AuthenticationScopeFailureEvent; -use Trikoder\Bundle\OAuth2Bundle\Security\Exception\ExceptionEventFactory; -use Trikoder\Bundle\OAuth2Bundle\Event\MissingAuthorizationHeaderEvent; -use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; -use Trikoder\Bundle\OAuth2Bundle\Response\ErrorJsonResponse; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2Token; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2TokenFactory; -use Trikoder\Bundle\OAuth2Bundle\Security\Exception\InsufficientScopesException; -use Trikoder\Bundle\OAuth2Bundle\Security\Exception\MissingAuthorizationHeaderException; +use Trikoder\Bundle\OAuth2Bundle\Security\Exception\ExceptionEventFactory; final class OAuth2Listener { From a922ce83bf41855f37dec113b06cae6a07dd4c56 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Sun, 16 May 2021 18:17:11 +0200 Subject: [PATCH 38/47] add: Conversion from OauthServerException to nice error Response (some wasn't catch) update: start updating Guard to new notification system --- .../ExceptionToOauthResponseListener.php | 41 +++++++++++++++++++ Resources/config/services.xml | 6 ++- .../Authenticator/OAuth2Authenticator.php | 39 +++++++++--------- 3 files changed, 65 insertions(+), 21 deletions(-) create mode 100644 EventListener/ExceptionToOauthResponseListener.php diff --git a/EventListener/ExceptionToOauthResponseListener.php b/EventListener/ExceptionToOauthResponseListener.php new file mode 100644 index 00000000..7eb0a9e0 --- /dev/null +++ b/EventListener/ExceptionToOauthResponseListener.php @@ -0,0 +1,41 @@ +exceptionEventFactory = $exceptionEventFactory; + } + + /** + * This method will catch and convert all OAuthServerException to a nice ErrorResponse + * This will also trigger the event system + * + * @param ExceptionEvent $event + */ + public function onKernelException(ExceptionEvent $event): void + { + $exception = $event->getThrowable(); + if ($exception instanceof OAuthServerException) { + $updatedEvent = $this->exceptionEventFactory->handleLeagueException($exception); + + $httpFoundationFactory = new HttpFoundationFactory(); + $event->setResponse($httpFoundationFactory->createResponse($updatedEvent->getResponse())); + } + } +} diff --git a/Resources/config/services.xml b/Resources/config/services.xml index 5b3426d0..1189b96a 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -100,7 +100,7 @@ - + @@ -171,6 +171,10 @@ The "%alias_id%" service alias is deprecated and will be removed in v4. + + + + diff --git a/Security/Guard/Authenticator/OAuth2Authenticator.php b/Security/Guard/Authenticator/OAuth2Authenticator.php index c9ecd109..5f46fd07 100644 --- a/Security/Guard/Authenticator/OAuth2Authenticator.php +++ b/Security/Guard/Authenticator/OAuth2Authenticator.php @@ -7,9 +7,9 @@ use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\ResourceServer; use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\User\UserInterface; @@ -22,6 +22,7 @@ use Trikoder\Bundle\OAuth2Bundle\Response\ErrorJsonResponse; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2Token; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2TokenFactory; +use Trikoder\Bundle\OAuth2Bundle\Security\Exception\ExceptionEventFactory; use Trikoder\Bundle\OAuth2Bundle\Security\Exception\InsufficientScopesException; use Trikoder\Bundle\OAuth2Bundle\Security\Exception\MissingAuthorizationHeaderException; use Trikoder\Bundle\OAuth2Bundle\Security\Exception\OAuth2AuthenticationFailedException; @@ -38,31 +39,25 @@ final class OAuth2Authenticator implements AuthenticatorInterface private $resourceServer; private $oauth2TokenFactory; private $psr7Request; + /** - * @var EventDispatcherInterface + * @var ExceptionEventFactory */ - private $eventDispatcher; + private $exceptionEventFactory; - public function __construct(HttpMessageFactoryInterface $httpMessageFactory, ResourceServer $resourceServer, OAuth2TokenFactory $oauth2TokenFactory, EventDispatcherInterface $eventDispatcher) + public function __construct(HttpMessageFactoryInterface $httpMessageFactory, ResourceServer $resourceServer, OAuth2TokenFactory $oauth2TokenFactory, ExceptionEventFactory $exceptionEventFactory) { $this->httpMessageFactory = $httpMessageFactory; $this->resourceServer = $resourceServer; $this->oauth2TokenFactory = $oauth2TokenFactory; - $this->eventDispatcher = $eventDispatcher; + $this->exceptionEventFactory = $exceptionEventFactory; } public function start(Request $request, ?AuthenticationException $authException = null): Response { - $exception = new MissingAuthorizationHeaderException(); - $exception->setPreviousException($authException); - - $response = new ErrorJsonResponse($exception->getMessageKey()); - $response->headers->set('WWW-Authenticate', 'Bearer'); + $missingAuthHeaderEvent = $this->exceptionEventFactory->invalidClient($request); - $event = new MissingAuthorizationHeaderEvent($exception, $response); - $this->eventDispatcher->dispatch($event, OAuth2Events::AUTHORIZATION_HEADER_FAILURE); - - return $event->getResponse(); + return $missingAuthHeaderEvent->getResponse(); } public function supports(Request $request): bool @@ -77,9 +72,10 @@ public function getCredentials(Request $request) try { $this->psr7Request = $this->resourceServer->validateAuthenticatedRequest($psr7Request); } catch (OAuthServerException $e) { - $exception = new OAuth2AuthenticationFailedException(); - $exception->setPreviousException($e); - throw $exception; + dump($e); + dump("Failed validating request"); //FIXME: Make a $event = $this->exceptionEventFactory->handleLeagueException($e); or Maybe let the error propagate and will be catch higher + // return ""; + throw $e; } return $this->psr7Request->getAttribute('oauth_user_id'); @@ -87,6 +83,7 @@ public function getCredentials(Request $request) public function getUser($userIdentifier, UserProviderInterface $userProvider): UserInterface { + dump("az"); // https://github.com/lexik/LexikJWTAuthenticationBundle/blob/master/Security/Guard/JWTTokenAuthenticator.php#L135 return '' === $userIdentifier ? new NullUser() : $userProvider->loadUserByUsername($userIdentifier); } @@ -122,15 +119,17 @@ public function onAuthenticationFailure(Request $request, AuthenticationExceptio $response = new ErrorJsonResponse($exception->getMessageKey(), Response::HTTP_FORBIDDEN); $event = new AuthenticationScopeFailureEvent($exception, $response, $exception->getToken()); $eventName = OAuth2Events::AUTHENTICATION_SCOPE_FAILURE; + $this->eventDispatcher->dispatch($event, $eventName); + } else if ($exception instanceof OAuthServerException) { + $event = $this->exceptionEventFactory->handleLeagueException($exception); } else { $response = new ErrorJsonResponse($exception->getMessageKey()); $event = new AuthenticationFailureEvent($exception, $response); $eventName = OAuth2Events::AUTHENTICATION_FAILURE; + $this->eventDispatcher->dispatch($event, $eventName); } - $this->eventDispatcher->dispatch($event, $eventName); - return $event->getResponse(); } @@ -147,7 +146,7 @@ public function supportsRememberMe(): bool private function isAccessToRouteGranted(OAuth2Token $token): bool { $routeScopes = $this->psr7Request->getAttribute('oauth2_scopes', []); - +dump($routeScopes); if ([] === $routeScopes) { return true; } From 12c8e5ea10e9c0ebdb68edb94ca8024b8252c19b Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Sun, 16 May 2021 18:17:31 +0200 Subject: [PATCH 39/47] add: Conversion from OauthServerException to nice error Response (some wasn't catch) --- DependencyInjection/TrikoderOAuth2Extension.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/DependencyInjection/TrikoderOAuth2Extension.php b/DependencyInjection/TrikoderOAuth2Extension.php index d57a377c..3910f326 100644 --- a/DependencyInjection/TrikoderOAuth2Extension.php +++ b/DependencyInjection/TrikoderOAuth2Extension.php @@ -29,9 +29,11 @@ use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\HttpKernel\KernelEvents; use Trikoder\Bundle\OAuth2Bundle\DBAL\Type\Grant as GrantType; use Trikoder\Bundle\OAuth2Bundle\DBAL\Type\RedirectUri as RedirectUriType; use Trikoder\Bundle\OAuth2Bundle\DBAL\Type\Scope as ScopeType; +use Trikoder\Bundle\OAuth2Bundle\EventListener\ExceptionToOauthResponseListener; use Trikoder\Bundle\OAuth2Bundle\League\AuthorizationServer\GrantTypeInterface; use Trikoder\Bundle\OAuth2Bundle\Manager\Doctrine\AccessTokenManager; use Trikoder\Bundle\OAuth2Bundle\Manager\Doctrine\AuthorizationCodeManager; @@ -66,6 +68,13 @@ public function load(array $configs, ContainerBuilder $container) $container->registerForAutoconfiguration(GrantTypeInterface::class) ->addTag('trikoder.oauth2.authorization_server.grant'); + + $container->getDefinition(ExceptionToOauthResponseListener::class) + ->addTag('kernel.event_listener', [ + 'event' => KernelEvents::EXCEPTION, + 'method' => 'onKernelException', + 'priority' => 10 + ]); } /** From c4856fab46ce74ee2564f71d2e6eb2b0cf8f1f11 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Sun, 16 May 2021 18:40:17 +0200 Subject: [PATCH 40/47] update: guard now works 100% with events --- Security/Exception/ExceptionEventFactory.php | 1 + .../Authenticator/OAuth2Authenticator.php | 46 ++++++++----------- 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/Security/Exception/ExceptionEventFactory.php b/Security/Exception/ExceptionEventFactory.php index b3b44fd2..1ba80d77 100644 --- a/Security/Exception/ExceptionEventFactory.php +++ b/Security/Exception/ExceptionEventFactory.php @@ -9,6 +9,7 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Throwable; use Trikoder\Bundle\OAuth2Bundle\Event\OauthEvent\AbstractOauthEvent; use Trikoder\Bundle\OAuth2Bundle\Event\OauthEvent\AuthenticationFailureEvent; use Trikoder\Bundle\OAuth2Bundle\Event\OauthEvent\AuthenticationScopeFailureEvent; diff --git a/Security/Guard/Authenticator/OAuth2Authenticator.php b/Security/Guard/Authenticator/OAuth2Authenticator.php index 5f46fd07..71802e03 100644 --- a/Security/Guard/Authenticator/OAuth2Authenticator.php +++ b/Security/Guard/Authenticator/OAuth2Authenticator.php @@ -6,6 +6,7 @@ use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\ResourceServer; +use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory; use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -38,6 +39,7 @@ final class OAuth2Authenticator implements AuthenticatorInterface private $httpMessageFactory; private $resourceServer; private $oauth2TokenFactory; + /** @var HttpMessageFactoryInterface */ private $psr7Request; /** @@ -69,21 +71,14 @@ public function getCredentials(Request $request) { $psr7Request = $this->httpMessageFactory->createRequest($request); - try { - $this->psr7Request = $this->resourceServer->validateAuthenticatedRequest($psr7Request); - } catch (OAuthServerException $e) { - dump($e); - dump("Failed validating request"); //FIXME: Make a $event = $this->exceptionEventFactory->handleLeagueException($e); or Maybe let the error propagate and will be catch higher - // return ""; - throw $e; - } + // Error will be automatically catch and converted in ExceptionToOauthResponseListener + $this->psr7Request = $this->resourceServer->validateAuthenticatedRequest($psr7Request); return $this->psr7Request->getAttribute('oauth_user_id'); } public function getUser($userIdentifier, UserProviderInterface $userProvider): UserInterface { - dump("az"); // https://github.com/lexik/LexikJWTAuthenticationBundle/blob/master/Security/Guard/JWTTokenAuthenticator.php#L135 return '' === $userIdentifier ? new NullUser() : $userProvider->loadUserByUsername($userIdentifier); } @@ -99,10 +94,8 @@ public function createAuthenticatedToken(UserInterface $user, $providerKey): OAu $oauth2Token = $this->oauth2TokenFactory->createOAuth2Token($this->psr7Request, $tokenUser, $providerKey); if (!$this->isAccessToRouteGranted($oauth2Token)) { - $exception = new InsufficientScopesException(); - $exception->setToken($oauth2Token); - - throw $exception; + // In the hint the route scope will be showed + throw OAuthServerException::invalidScope($this->getRouteScopes()); } $oauth2Token->setAuthenticated(true); @@ -112,25 +105,17 @@ public function createAuthenticatedToken(UserInterface $user, $providerKey): OAu public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response { - dump($exception); $this->psr7Request = null; - if ($exception instanceof InsufficientScopesException) { - $response = new ErrorJsonResponse($exception->getMessageKey(), Response::HTTP_FORBIDDEN); - $event = new AuthenticationScopeFailureEvent($exception, $response, $exception->getToken()); - $eventName = OAuth2Events::AUTHENTICATION_SCOPE_FAILURE; - $this->eventDispatcher->dispatch($event, $eventName); - } else if ($exception instanceof OAuthServerException) { + dump($exception); + if ($exception instanceof OAuthServerException) { $event = $this->exceptionEventFactory->handleLeagueException($exception); } else { - $response = new ErrorJsonResponse($exception->getMessageKey()); - - $event = new AuthenticationFailureEvent($exception, $response); - $eventName = OAuth2Events::AUTHENTICATION_FAILURE; - $this->eventDispatcher->dispatch($event, $eventName); + $event = $this->exceptionEventFactory->accessDenied($exception); } - return $event->getResponse(); + $httpFoundationFactory = new HttpFoundationFactory(); + return $httpFoundationFactory->createResponse($event->getResponse()); } public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey): ?Response @@ -143,10 +128,15 @@ public function supportsRememberMe(): bool return false; } + private function getRouteScopes(): array + { + return $this->psr7Request->getAttribute('oauth2_scopes', []); + } + private function isAccessToRouteGranted(OAuth2Token $token): bool { - $routeScopes = $this->psr7Request->getAttribute('oauth2_scopes', []); -dump($routeScopes); + $routeScopes = $this->getRouteScopes(); + if ([] === $routeScopes) { return true; } From 83742b70f2ec245edf23a2a4d1d3847902ebd057 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Sun, 16 May 2021 18:42:27 +0200 Subject: [PATCH 41/47] update: import cleanup --- Security/Guard/Authenticator/OAuth2Authenticator.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Security/Guard/Authenticator/OAuth2Authenticator.php b/Security/Guard/Authenticator/OAuth2Authenticator.php index 71802e03..37c32dd2 100644 --- a/Security/Guard/Authenticator/OAuth2Authenticator.php +++ b/Security/Guard/Authenticator/OAuth2Authenticator.php @@ -10,23 +10,15 @@ use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; use Symfony\Component\Security\Guard\AuthenticatorInterface; -use Trikoder\Bundle\OAuth2Bundle\Event\OauthEvent\AuthenticationFailureEvent; -use Trikoder\Bundle\OAuth2Bundle\Event\OauthEvent\AuthenticationScopeFailureEvent; -use Trikoder\Bundle\OAuth2Bundle\Event\OauthEvent\MissingAuthorizationHeaderEvent; -use Trikoder\Bundle\OAuth2Bundle\OAuth2Events; use Trikoder\Bundle\OAuth2Bundle\Response\ErrorJsonResponse; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2Token; use Trikoder\Bundle\OAuth2Bundle\Security\Authentication\Token\OAuth2TokenFactory; use Trikoder\Bundle\OAuth2Bundle\Security\Exception\ExceptionEventFactory; -use Trikoder\Bundle\OAuth2Bundle\Security\Exception\InsufficientScopesException; -use Trikoder\Bundle\OAuth2Bundle\Security\Exception\MissingAuthorizationHeaderException; -use Trikoder\Bundle\OAuth2Bundle\Security\Exception\OAuth2AuthenticationFailedException; use Trikoder\Bundle\OAuth2Bundle\Security\User\NullUser; /** From b30da91d42f5804a7ef04348bfd459cbe0bea060 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Sun, 16 May 2021 18:42:45 +0200 Subject: [PATCH 42/47] update: firewall response wasn't converted to sf response --- Security/Firewall/OAuth2Listener.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Security/Firewall/OAuth2Listener.php b/Security/Firewall/OAuth2Listener.php index 70a95593..52005b46 100644 --- a/Security/Firewall/OAuth2Listener.php +++ b/Security/Firewall/OAuth2Listener.php @@ -4,6 +4,7 @@ namespace Trikoder\Bundle\OAuth2Bundle\Security\Firewall; +use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory; use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\RequestEvent; @@ -65,10 +66,11 @@ public function __construct( public function __invoke(RequestEvent $event) { $request = $this->httpMessageFactory->createRequest($event->getRequest()); + $responseFactory = new HttpFoundationFactory(); if (!$request->hasHeader('Authorization')) { $missingAuthHeaderEvent = $this->exceptionEventFactory->invalidClient($request); - $event->setResponse($missingAuthHeaderEvent->getResponse()); + $event->setResponse($responseFactory->createResponse($missingAuthHeaderEvent->getResponse())); return; } @@ -77,13 +79,13 @@ public function __invoke(RequestEvent $event) $authenticatedToken = $this->authenticationManager->authenticate($this->oauth2TokenFactory->createOAuth2Token($request, null, $this->providerKey)); } catch (AuthenticationException $e) { $authenticationFailureEvent = $this->exceptionEventFactory->accessDenied($e); - $event->setResponse($authenticationFailureEvent->getResponse()); + $event->setResponse($responseFactory->createResponse($authenticationFailureEvent->getResponse())); return; } if (!$this->isAccessToRouteGranted($event->getRequest(), $authenticatedToken)) { $authenticationFailureScopeEvent = $this->exceptionEventFactory->invalidScope($authenticatedToken); - $event->setResponse($authenticationFailureScopeEvent->getResponse()); + $event->setResponse($responseFactory->createResponse($authenticationFailureScopeEvent->getResponse())); return; } From e0fb8b2a948f883521c169202f3061e92ff8706c Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Sun, 16 May 2021 18:59:56 +0200 Subject: [PATCH 43/47] update: remove catching to be like in the guard --- Security/Authentication/Provider/OAuth2Provider.php | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Security/Authentication/Provider/OAuth2Provider.php b/Security/Authentication/Provider/OAuth2Provider.php index 3528b2e7..7030aa9d 100644 --- a/Security/Authentication/Provider/OAuth2Provider.php +++ b/Security/Authentication/Provider/OAuth2Provider.php @@ -58,13 +58,9 @@ public function authenticate(TokenInterface $token) throw new RuntimeException(sprintf('This authentication provider can only handle tokes of type \'%s\'.', OAuth2Token::class)); } - try { - $request = $this->resourceServer->validateAuthenticatedRequest( - $token->getAttribute('server_request') - ); - } catch (OAuthServerException $e) { - throw new AuthenticationException('The resource server rejected the request.', 0, $e); - } + $request = $this->resourceServer->validateAuthenticatedRequest( + $token->getAttribute('server_request') + ); $user = $this->getAuthenticatedUser( $request->getAttribute('oauth_user_id') From 99229f332e9d84d3705ec21ef793ad17e8a3e0bd Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Sun, 16 May 2021 19:02:33 +0200 Subject: [PATCH 44/47] remove: unused Exception --- .../Exception/InsufficientScopesException.php | 22 ------------ .../Exception/InvalidCredentialsException.php | 21 ------------ .../MissingAuthorizationHeaderException.php | 33 ------------------ .../OAuth2AuthenticationFailedException.php | 34 ------------------- 4 files changed, 110 deletions(-) delete mode 100644 Security/Exception/InsufficientScopesException.php delete mode 100644 Security/Exception/InvalidCredentialsException.php delete mode 100644 Security/Exception/MissingAuthorizationHeaderException.php delete mode 100644 Security/Exception/OAuth2AuthenticationFailedException.php diff --git a/Security/Exception/InsufficientScopesException.php b/Security/Exception/InsufficientScopesException.php deleted file mode 100644 index 422ca83c..00000000 --- a/Security/Exception/InsufficientScopesException.php +++ /dev/null @@ -1,22 +0,0 @@ - - * @author Benoit VIGNAL - */ -class InsufficientScopesException extends AuthenticationException -{ - /** - * {@inheritdoc} - */ - public function getMessageKey(): string - { - return 'Insufficient scopes.'; - } -} diff --git a/Security/Exception/InvalidCredentialsException.php b/Security/Exception/InvalidCredentialsException.php deleted file mode 100644 index 77d47232..00000000 --- a/Security/Exception/InvalidCredentialsException.php +++ /dev/null @@ -1,21 +0,0 @@ - - */ -class InvalidCredentialsException extends AuthenticationException -{ - /** - * {@inheritdoc} - */ - public function getMessageKey(): string - { - return 'Invalid user credentials.'; - } -} diff --git a/Security/Exception/MissingAuthorizationHeaderException.php b/Security/Exception/MissingAuthorizationHeaderException.php deleted file mode 100644 index 4a171b86..00000000 --- a/Security/Exception/MissingAuthorizationHeaderException.php +++ /dev/null @@ -1,33 +0,0 @@ - - */ -class MissingAuthorizationHeaderException extends AuthenticationException -{ - private $previousException; - - /** - * {@inheritdoc} - */ - public function getMessageKey(): string - { - return 'Missing Authorization Bearer.'; - } - - public function getPreviousException(): ?AuthenticationException - { - return $this->previousException; - } - - public function setPreviousException(?AuthenticationException $previousException): void - { - $this->previousException = $previousException; - } -} diff --git a/Security/Exception/OAuth2AuthenticationFailedException.php b/Security/Exception/OAuth2AuthenticationFailedException.php deleted file mode 100644 index c98cb222..00000000 --- a/Security/Exception/OAuth2AuthenticationFailedException.php +++ /dev/null @@ -1,34 +0,0 @@ - - * @author Benoit VIGNAL - */ -class OAuth2AuthenticationFailedException extends AuthenticationException -{ - private $previousException; - - /** - * {@inheritdoc} - */ - public function getMessageKey(): string - { - return 'Invalid OAuth Token.'; - } - - public function getPreviousException(): ?\Exception - { - return $this->previousException; - } - - public function setPreviousException(?\Exception $previousException): void - { - $this->previousException = $previousException; - } -} From 525d3ef43062a6307e8d267d348f3e8402fc5b80 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Sun, 16 May 2021 19:05:31 +0200 Subject: [PATCH 45/47] quality: code quality --- Security/Guard/Authenticator/OAuth2Authenticator.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Security/Guard/Authenticator/OAuth2Authenticator.php b/Security/Guard/Authenticator/OAuth2Authenticator.php index 37c32dd2..32cd275b 100644 --- a/Security/Guard/Authenticator/OAuth2Authenticator.php +++ b/Security/Guard/Authenticator/OAuth2Authenticator.php @@ -99,7 +99,6 @@ public function onAuthenticationFailure(Request $request, AuthenticationExceptio { $this->psr7Request = null; - dump($exception); if ($exception instanceof OAuthServerException) { $event = $this->exceptionEventFactory->handleLeagueException($exception); } else { From c7f65ca4c89024e0d5a5c301c0f154726b538896 Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Sun, 16 May 2021 19:31:16 +0200 Subject: [PATCH 46/47] test: invalid_credentials error is now returned --- Tests/Integration/AuthorizationServerTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Integration/AuthorizationServerTest.php b/Tests/Integration/AuthorizationServerTest.php index cc1e9f40..28753f2f 100644 --- a/Tests/Integration/AuthorizationServerTest.php +++ b/Tests/Integration/AuthorizationServerTest.php @@ -339,8 +339,8 @@ public function testInvalidCredentialsPasswordGrant(): void $response = $this->handleTokenRequest($request); // Response assertions. - $this->assertSame('invalid_grant', $response['error']); - $this->assertSame('The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.', $response['message']); + $this->assertSame('invalid_credentials', $response['error']); + $this->assertSame('The user credentials were incorrect.', $response['message']); } public function testMissingUsernameFieldPasswordGrant(): void From d2a685758ade3649ed922538bd63d87d701a787c Mon Sep 17 00:00:00 2001 From: Benoit VIGNAL Date: Sun, 16 May 2021 19:31:30 +0200 Subject: [PATCH 47/47] fix: forgot to convert Request to PsrRequest --- Security/Guard/Authenticator/OAuth2Authenticator.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Security/Guard/Authenticator/OAuth2Authenticator.php b/Security/Guard/Authenticator/OAuth2Authenticator.php index 32cd275b..65141901 100644 --- a/Security/Guard/Authenticator/OAuth2Authenticator.php +++ b/Security/Guard/Authenticator/OAuth2Authenticator.php @@ -49,9 +49,10 @@ public function __construct(HttpMessageFactoryInterface $httpMessageFactory, Res public function start(Request $request, ?AuthenticationException $authException = null): Response { + $request = $this->httpMessageFactory->createRequest($request); $missingAuthHeaderEvent = $this->exceptionEventFactory->invalidClient($request); - - return $missingAuthHeaderEvent->getResponse(); + $httpFoundationFactory = new HttpFoundationFactory(); + return $httpFoundationFactory->createResponse($missingAuthHeaderEvent->getResponse()); } public function supports(Request $request): bool