diff --git a/src/Event/AfterCreateAccessTokenEvent.php b/src/Event/AfterCreateAccessTokenEvent.php new file mode 100644 index 0000000..ba7e059 --- /dev/null +++ b/src/Event/AfterCreateAccessTokenEvent.php @@ -0,0 +1,39 @@ +client; + } + + public function getUser(): ?UserInterface + { + return $this->user; + } + + public function getAccessToken(): string + { + return $this->accessToken; + } + + public function getRefreshToken(): ?string + { + return $this->refreshToken; + } +} diff --git a/src/Event/VerifyTokenEvent.php b/src/Event/VerifyTokenEvent.php new file mode 100644 index 0000000..aa5dbd7 --- /dev/null +++ b/src/Event/VerifyTokenEvent.php @@ -0,0 +1,20 @@ +token; + } +} diff --git a/src/Resources/config/grant_extension.php b/src/Resources/config/grant_extension.php index 92f7c12..905d63b 100644 --- a/src/Resources/config/grant_extension.php +++ b/src/Resources/config/grant_extension.php @@ -30,6 +30,7 @@ $services ->set('oauth_server.grant_extension.refresh_token', RefreshTokenGrantExtension::class) ->args([ + service('event_dispatcher'), service('oauth_server.doctrine_storage.refresh_token'), ]) ->alias(RefreshTokenGrantExtension::class, 'oauth_server.grant_extension.refresh_token') diff --git a/src/Server/GrantExtension/RefreshTokenGrantExtension.php b/src/Server/GrantExtension/RefreshTokenGrantExtension.php index 9bd3899..a409810 100644 --- a/src/Server/GrantExtension/RefreshTokenGrantExtension.php +++ b/src/Server/GrantExtension/RefreshTokenGrantExtension.php @@ -5,16 +5,20 @@ namespace OAuth\Server\GrantExtension; use OAuth\Enum\ErrorCode; +use OAuth\Event\VerifyTokenEvent; use OAuth\Exception\OAuthServerException; use OAuth\Model\ClientInterface; use OAuth\Server\Config; use OAuth\Server\Storage\RefreshTokenStorageInterface; use Symfony\Component\HttpFoundation\Response; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; class RefreshTokenGrantExtension implements GrantExtensionInterface { - public function __construct(private readonly RefreshTokenStorageInterface $storage) - { + public function __construct( + private readonly EventDispatcherInterface $dispatcher, + private readonly RefreshTokenStorageInterface $storage, + ) { } public function checkGrantExtension(ClientInterface $client, Config $config, string $grantType, array $input): Grant @@ -33,6 +37,8 @@ public function checkGrantExtension(ClientInterface $client, Config $config, str throw new OAuthServerException(Response::HTTP_BAD_REQUEST, ErrorCode::ERROR_INVALID_GRANT, 'Refresh token has expired'); } + $this->dispatcher->dispatch(new VerifyTokenEvent($token)); + $this->storage->unsetRefreshToken($token->getToken()); return new Grant($token->getUser(), $token->getScope()); diff --git a/src/Server/Handler.php b/src/Server/Handler.php index 76cb4aa..4763489 100644 --- a/src/Server/Handler.php +++ b/src/Server/Handler.php @@ -6,7 +6,9 @@ use OAuth\Enum\ErrorCode; use OAuth\Enum\TransportMethod; +use OAuth\Event\AfterCreateAccessTokenEvent; use OAuth\Event\AfterGrantAccessEvent; +use OAuth\Event\VerifyTokenEvent; use OAuth\Exception\OAuthAuthenticateException; use OAuth\Exception\OAuthRedirectException; use OAuth\Exception\OAuthServerException; @@ -115,6 +117,8 @@ public function verifyAccessToken(string $tokenParam, ?string $scope = null): Ac throw new OAuthAuthenticateException(Response::HTTP_FORBIDDEN, $tokenType, $realm, ErrorCode::ERROR_INSUFFICIENT_SCOPE, 'The request requires higher privileges than provided by the access token.', $scope); } + $this->eventDispatcher->dispatch(new VerifyTokenEvent($token)); + return $token; } @@ -393,6 +397,13 @@ private function createAccessToken( ); } + $this->eventDispatcher->dispatch(new AfterCreateAccessTokenEvent( + $client, + $user, + $token['access_token'], + $token['refresh_token'] ?? null, + )); + return $token; } diff --git a/tests/Server/HandlerTest.php b/tests/Server/HandlerTest.php index 6cf9540..8b5f940 100644 --- a/tests/Server/HandlerTest.php +++ b/tests/Server/HandlerTest.php @@ -4,7 +4,9 @@ namespace OAuth\Tests\Server; +use OAuth\Event\AfterCreateAccessTokenEvent; use OAuth\Event\AfterGrantAccessEvent; +use OAuth\Event\VerifyTokenEvent; use OAuth\Exception\OAuthAuthenticateException; use OAuth\Exception\OAuthServerException; use OAuth\Model\AccessTokenInterface; @@ -81,7 +83,7 @@ protected function setUp(): void $this->authCodeStorage, new AuthCodeGrantExtension($this->authCodeStorage), new ClientCredentialsGrantExtension(), - new RefreshTokenGrantExtension($this->refreshTokenStorage), + new RefreshTokenGrantExtension($this->eventDispatcher, $this->refreshTokenStorage), new UserCredentialsGrantExtension($this->userProviderInterface, $this->passwordHasherFactory), $this->customGrantExtension, ); @@ -105,6 +107,8 @@ public function testVerifyAccessToken(): void $token = $this->manager->verifyAccessToken('my_token'); $this->assertNotNull($token); $this->assertEquals('my_token', $token->getToken()); + + $this->assertEquals([VerifyTokenEvent::class], $this->eventDispatcher->getOrphanedEvents()); } public static function provideVerifyAccessTokenException(): iterable @@ -173,6 +177,8 @@ public function testVerifyAccessTokenException( $this->expectException(OAuthAuthenticateException::class); $this->manager->verifyAccessToken($tokenParam, $scope); + + $this->assertEquals([], $this->eventDispatcher->getOrphanedEvents()); } public static function provideGetBearerToken(): iterable @@ -380,7 +386,7 @@ public function testGrantAccessTokenAuthCode(Request $request, array $expectedRe $response = $this->manager->grantAccessToken($request); $this->assertEquals($expectedResponse, json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR)); - $this->assertEquals([AfterGrantAccessEvent::class], $this->eventDispatcher->getOrphanedEvents()); + $this->assertEquals([AfterGrantAccessEvent::class, AfterCreateAccessTokenEvent::class], $this->eventDispatcher->getOrphanedEvents()); } public static function provideGrantAccessTokenUserCredentials(): iterable @@ -433,7 +439,7 @@ public function testGrantAccessTokenUserCredentials(Request $request): void 'scope' => null, 'refresh_token' => 'refresh_token', ], json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR)); - $this->assertEquals([AfterGrantAccessEvent::class], $this->eventDispatcher->getOrphanedEvents()); + $this->assertEquals([AfterGrantAccessEvent::class, AfterCreateAccessTokenEvent::class], $this->eventDispatcher->getOrphanedEvents()); } public static function provideGrantAccessTokenClientCredentials(): iterable @@ -478,7 +484,7 @@ public function testGrantAccessTokenClientCredentials(Request $request): void 'token_type' => 'bearer', 'scope' => null, ], json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR)); - $this->assertEquals([AfterGrantAccessEvent::class], $this->eventDispatcher->getOrphanedEvents()); + $this->assertEquals([AfterGrantAccessEvent::class, AfterCreateAccessTokenEvent::class], $this->eventDispatcher->getOrphanedEvents()); } public static function provideGrantAccessTokenRefreshToken(): iterable @@ -537,7 +543,7 @@ public function testGrantAccessTokenRefreshToken(Request $request): void 'scope' => 'read', 'refresh_token' => 'refresh_token', ], json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR)); - $this->assertEquals([AfterGrantAccessEvent::class], $this->eventDispatcher->getOrphanedEvents()); + $this->assertEquals([VerifyTokenEvent::class, AfterGrantAccessEvent::class, AfterCreateAccessTokenEvent::class], $this->eventDispatcher->getOrphanedEvents()); } public static function provideGrantAccessTokenCustom(): iterable @@ -595,7 +601,7 @@ public function checkGrantExtension(ClientInterface $client, Config $config, str 'scope' => null, 'refresh_token' => 'refresh_token', ], json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR)); - $this->assertEquals([AfterGrantAccessEvent::class], $this->eventDispatcher->getOrphanedEvents()); + $this->assertEquals([AfterGrantAccessEvent::class, AfterCreateAccessTokenEvent::class], $this->eventDispatcher->getOrphanedEvents()); } public static function provideGrantAccessTokenException(): iterable