diff --git a/src/Controller/TokenController.php b/src/Controller/TokenController.php index 276a3d6c..1a803464 100644 --- a/src/Controller/TokenController.php +++ b/src/Controller/TokenController.php @@ -4,6 +4,8 @@ namespace League\Bundle\OAuth2ServerBundle\Controller; +use League\Bundle\OAuth2ServerBundle\Event\TokenRequestResolveEvent; +use League\Bundle\OAuth2ServerBundle\OAuth2Events; use League\OAuth2\Server\AuthorizationServer; use League\OAuth2\Server\Exception\OAuthServerException; use Psr\Http\Message\ResponseFactoryInterface; @@ -11,6 +13,7 @@ use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; final class TokenController { @@ -34,16 +37,23 @@ final class TokenController */ private $responseFactory; + /** + * @var EventDispatcherInterface + */ + private $eventDispatcher; + public function __construct( AuthorizationServer $server, HttpMessageFactoryInterface $httpMessageFactory, HttpFoundationFactoryInterface $httpFoundationFactory, - ResponseFactoryInterface $responseFactory + ResponseFactoryInterface $responseFactory, + EventDispatcherInterface $eventDispatcher ) { $this->server = $server; $this->httpMessageFactory = $httpMessageFactory; $this->httpFoundationFactory = $httpFoundationFactory; $this->responseFactory = $responseFactory; + $this->eventDispatcher = $eventDispatcher; } public function indexAction(Request $request): Response @@ -57,6 +67,14 @@ public function indexAction(Request $request): Response $response = $e->generateHttpResponse($serverResponse); } - return $this->httpFoundationFactory->createResponse($response); + $renderedResponse = $this->httpFoundationFactory->createResponse($response); + + /** @var TokenRequestResolveEvent $event */ + $this->eventDispatcher->dispatch( + new TokenRequestResolveEvent($renderedResponse), + OAuth2Events::TOKEN_REQUEST_RESOLVE + ); + + return $renderedResponse; } } diff --git a/src/Event/TokenRequestResolveEvent.php b/src/Event/TokenRequestResolveEvent.php new file mode 100755 index 00000000..62e80b39 --- /dev/null +++ b/src/Event/TokenRequestResolveEvent.php @@ -0,0 +1,33 @@ +response = $response; + } + + public function getResponse(): Response + { + return $this->response; + } + + public function setResponse(Response $response): self + { + $this->response = $response; + + return $this; + } +} diff --git a/src/OAuth2Events.php b/src/OAuth2Events.php index 34924c6e..0f2077e8 100644 --- a/src/OAuth2Events.php +++ b/src/OAuth2Events.php @@ -31,6 +31,14 @@ final class OAuth2Events */ public const AUTHORIZATION_REQUEST_RESOLVE = 'league.oauth2_server.event.authorization_request_resolve'; + /** + * The REQUEST_TOKEN_RESOLVE event occurs right before the system + * complete token request. + * + * You could manipulate the response. + */ + public const TOKEN_REQUEST_RESOLVE = 'league.oauth2_server.event.token_request_resolve'; + /** * The PRE_SAVE_CLIENT event occurs right before the client is saved * by a ClientManager. diff --git a/src/Resources/config/services.php b/src/Resources/config/services.php index efabfc6d..b77cc77f 100644 --- a/src/Resources/config/services.php +++ b/src/Resources/config/services.php @@ -221,6 +221,7 @@ service('league.oauth2_server.factory.psr_http'), service('league.oauth2_server.factory.http_foundation'), service('league.oauth2_server.factory.psr17'), + service('event_dispatcher'), ]) ->tag('controller.service_arguments') ->alias(TokenController::class, 'league.oauth2_server.controller.token') diff --git a/tests/Acceptance/TokenEndpointTest.php b/tests/Acceptance/TokenEndpointTest.php index c603843a..26685670 100644 --- a/tests/Acceptance/TokenEndpointTest.php +++ b/tests/Acceptance/TokenEndpointTest.php @@ -4,6 +4,7 @@ namespace League\Bundle\OAuth2ServerBundle\Tests\Acceptance; +use League\Bundle\OAuth2ServerBundle\Event\TokenRequestResolveEvent; use League\Bundle\OAuth2ServerBundle\Event\UserResolveEvent; use League\Bundle\OAuth2ServerBundle\Manager\AccessTokenManagerInterface; use League\Bundle\OAuth2ServerBundle\Manager\AuthorizationCodeManagerInterface; @@ -37,6 +38,13 @@ public function testSuccessfulClientCredentialsRequest(): void 'grant_type' => 'client_credentials', ]); + $this->client + ->getContainer() + ->get('event_dispatcher') + ->addListener(OAuth2Events::TOKEN_REQUEST_RESOLVE, static function (TokenRequestResolveEvent $event): void { + $event->getResponse()->headers->set('foo', 'bar'); + }); + $response = $this->client->getResponse(); $this->assertSame(200, $response->getStatusCode()); @@ -48,6 +56,7 @@ public function testSuccessfulClientCredentialsRequest(): void $this->assertLessThanOrEqual(3600, $jsonResponse['expires_in']); $this->assertGreaterThan(0, $jsonResponse['expires_in']); $this->assertNotEmpty($jsonResponse['access_token']); + $this->assertEmpty($response->headers->get('foo'), 'bar'); } public function testSuccessfulPasswordRequest(): void @@ -59,6 +68,13 @@ public function testSuccessfulPasswordRequest(): void $event->setUser(FixtureFactory::createUser()); }); + $this->client + ->getContainer() + ->get('event_dispatcher') + ->addListener(OAuth2Events::TOKEN_REQUEST_RESOLVE, static function (TokenRequestResolveEvent $event): void { + $event->getResponse()->headers->set('foo', 'bar'); + }); + $this->client->request('POST', '/token', [ 'client_id' => 'foo', 'client_secret' => 'secret', @@ -79,6 +95,7 @@ public function testSuccessfulPasswordRequest(): void $this->assertGreaterThan(0, $jsonResponse['expires_in']); $this->assertNotEmpty($jsonResponse['access_token']); $this->assertNotEmpty($jsonResponse['refresh_token']); + $this->assertSame($response->headers->get('foo'), 'bar'); } public function testSuccessfulRefreshTokenRequest(): void @@ -95,6 +112,13 @@ public function testSuccessfulRefreshTokenRequest(): void 'refresh_token' => TestHelper::generateEncryptedPayload($refreshToken), ]); + $this->client + ->getContainer() + ->get('event_dispatcher') + ->addListener(OAuth2Events::TOKEN_REQUEST_RESOLVE, static function (TokenRequestResolveEvent $event): void { + $event->getResponse()->headers->set('foo', 'bar'); + }); + $response = $this->client->getResponse(); $this->assertSame(200, $response->getStatusCode()); @@ -107,6 +131,7 @@ public function testSuccessfulRefreshTokenRequest(): void $this->assertGreaterThan(0, $jsonResponse['expires_in']); $this->assertNotEmpty($jsonResponse['access_token']); $this->assertNotEmpty($jsonResponse['refresh_token']); + $this->assertEmpty($response->headers->get('foo'), 'bar'); } public function testSuccessfulAuthorizationCodeRequest(): void @@ -124,6 +149,13 @@ public function testSuccessfulAuthorizationCodeRequest(): void 'code' => TestHelper::generateEncryptedAuthCodePayload($authCode), ]); + $this->client + ->getContainer() + ->get('event_dispatcher') + ->addListener(OAuth2Events::TOKEN_REQUEST_RESOLVE, static function (TokenRequestResolveEvent $event): void { + $event->getResponse()->headers->set('foo', 'bar'); + }); + $response = $this->client->getResponse(); $this->assertSame(200, $response->getStatusCode()); @@ -135,6 +167,7 @@ public function testSuccessfulAuthorizationCodeRequest(): void $this->assertLessThanOrEqual(3600, $jsonResponse['expires_in']); $this->assertGreaterThan(0, $jsonResponse['expires_in']); $this->assertNotEmpty($jsonResponse['access_token']); + $this->assertEmpty($response->headers->get('foo'), 'bar'); } public function testSuccessfulAuthorizationCodeRequestWithPublicClient(): void @@ -144,6 +177,13 @@ public function testSuccessfulAuthorizationCodeRequestWithPublicClient(): void ->get(AuthorizationCodeManagerInterface::class) ->find(FixtureFactory::FIXTURE_AUTH_CODE_PUBLIC_CLIENT); + $this->client + ->getContainer() + ->get('event_dispatcher') + ->addListener(OAuth2Events::TOKEN_REQUEST_RESOLVE, static function (TokenRequestResolveEvent $event): void { + $event->getResponse()->headers->set('foo', 'bar'); + }); + $this->client->request('POST', '/token', [ 'client_id' => FixtureFactory::FIXTURE_PUBLIC_CLIENT, 'grant_type' => 'authorization_code', @@ -162,6 +202,7 @@ public function testSuccessfulAuthorizationCodeRequestWithPublicClient(): void $this->assertLessThanOrEqual(3600, $jsonResponse['expires_in']); $this->assertGreaterThan(0, $jsonResponse['expires_in']); $this->assertNotEmpty($jsonResponse['access_token']); + $this->assertSame($response->headers->get('foo'), 'bar'); } public function testFailedTokenRequest(): void @@ -188,6 +229,13 @@ public function testFailedClientCredentialsTokenRequest(): void 'grant_type' => 'client_credentials', ]); + $this->client + ->getContainer() + ->get('event_dispatcher') + ->addListener(OAuth2Events::TOKEN_REQUEST_RESOLVE, static function (TokenRequestResolveEvent $event): void { + $event->getResponse()->headers->set('foo', 'bar'); + }); + $response = $this->client->getResponse(); $this->assertSame(401, $response->getStatusCode()); @@ -197,5 +245,6 @@ public function testFailedClientCredentialsTokenRequest(): void $this->assertSame('invalid_client', $jsonResponse['error']); $this->assertSame('Client authentication failed', $jsonResponse['message']); + $this->assertEmpty($response->headers->get('foo'), 'bar'); } }