From 6af4d048f1bc3b3de1a44a7f4cf43deb247cd9ea Mon Sep 17 00:00:00 2001 From: Jacob Weber Date: Thu, 7 Feb 2019 17:04:11 -0800 Subject: [PATCH 01/16] move validation methods into trait --- src/Grant/AbstractGrant.php | 172 ++--------------------------- src/RequestValidatorTrait.php | 198 ++++++++++++++++++++++++++++++++++ 2 files changed, 208 insertions(+), 162 deletions(-) create mode 100644 src/RequestValidatorTrait.php diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 492133317..8e0dddc0c 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -15,6 +15,7 @@ use Error; use Exception; use League\Event\EmitterAwareTrait; +use League\OAuth2\Server\RequestValidatorTrait; use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\CryptTrait; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; @@ -41,7 +42,7 @@ */ abstract class AbstractGrant implements GrantTypeInterface { - use EmitterAwareTrait, CryptTrait; + use EmitterAwareTrait, CryptTrait, RequestValidatorTrait; const SCOPE_DELIMITER_STRING = ' '; @@ -92,6 +93,14 @@ abstract class AbstractGrant implements GrantTypeInterface */ protected $defaultScope; + /** + * @return ClientRepositoryInterface + */ + public function getClientRepository() + { + return $this->clientRepository; + } + /** * @param ClientRepositoryInterface $clientRepository */ @@ -166,76 +175,6 @@ public function setDefaultScope($scope) $this->defaultScope = $scope; } - /** - * Validate the client. - * - * @param ServerRequestInterface $request - * - * @throws OAuthServerException - * - * @return ClientEntityInterface - */ - protected function validateClient(ServerRequestInterface $request) - { - list($basicAuthUser, $basicAuthPassword) = $this->getBasicAuthCredentials($request); - - $clientId = $this->getRequestParameter('client_id', $request, $basicAuthUser); - if ($clientId === null) { - throw OAuthServerException::invalidRequest('client_id'); - } - - // If the client is confidential require the client secret - $clientSecret = $this->getRequestParameter('client_secret', $request, $basicAuthPassword); - - $client = $this->clientRepository->getClientEntity( - $clientId, - $this->getIdentifier(), - $clientSecret, - true - ); - - if ($client instanceof ClientEntityInterface === false) { - $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); - throw OAuthServerException::invalidClient(); - } - - $redirectUri = $this->getRequestParameter('redirect_uri', $request, null); - - if ($redirectUri !== null) { - $this->validateRedirectUri($redirectUri, $client, $request); - } - - return $client; - } - - /** - * Validate redirectUri from the request. - * If a redirect URI is provided ensure it matches what is pre-registered - * - * @param string $redirectUri - * @param ClientEntityInterface $client - * @param ServerRequestInterface $request - * - * @throws OAuthServerException - */ - protected function validateRedirectUri( - string $redirectUri, - ClientEntityInterface $client, - ServerRequestInterface $request - ) { - if (\is_string($client->getRedirectUri()) - && (strcmp($client->getRedirectUri(), $redirectUri) !== 0) - ) { - $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); - throw OAuthServerException::invalidClient(); - } elseif (\is_array($client->getRedirectUri()) - && \in_array($redirectUri, $client->getRedirectUri(), true) === false - ) { - $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); - throw OAuthServerException::invalidClient(); - } - } - /** * Validate scopes in the request. * @@ -281,97 +220,6 @@ private function convertScopesQueryStringToArray($scopes) }); } - /** - * Retrieve request parameter. - * - * @param string $parameter - * @param ServerRequestInterface $request - * @param mixed $default - * - * @return null|string - */ - protected function getRequestParameter($parameter, ServerRequestInterface $request, $default = null) - { - $requestParameters = (array) $request->getParsedBody(); - - return $requestParameters[$parameter] ?? $default; - } - - /** - * Retrieve HTTP Basic Auth credentials with the Authorization header - * of a request. First index of the returned array is the username, - * second is the password (so list() will work). If the header does - * not exist, or is otherwise an invalid HTTP Basic header, return - * [null, null]. - * - * @param ServerRequestInterface $request - * - * @return string[]|null[] - */ - protected function getBasicAuthCredentials(ServerRequestInterface $request) - { - if (!$request->hasHeader('Authorization')) { - return [null, null]; - } - - $header = $request->getHeader('Authorization')[0]; - if (strpos($header, 'Basic ') !== 0) { - return [null, null]; - } - - if (!($decoded = base64_decode(substr($header, 6)))) { - return [null, null]; - } - - if (strpos($decoded, ':') === false) { - return [null, null]; // HTTP Basic header without colon isn't valid - } - - return explode(':', $decoded, 2); - } - - /** - * Retrieve query string parameter. - * - * @param string $parameter - * @param ServerRequestInterface $request - * @param mixed $default - * - * @return null|string - */ - protected function getQueryStringParameter($parameter, ServerRequestInterface $request, $default = null) - { - return isset($request->getQueryParams()[$parameter]) ? $request->getQueryParams()[$parameter] : $default; - } - - /** - * Retrieve cookie parameter. - * - * @param string $parameter - * @param ServerRequestInterface $request - * @param mixed $default - * - * @return null|string - */ - protected function getCookieParameter($parameter, ServerRequestInterface $request, $default = null) - { - return isset($request->getCookieParams()[$parameter]) ? $request->getCookieParams()[$parameter] : $default; - } - - /** - * Retrieve server parameter. - * - * @param string $parameter - * @param ServerRequestInterface $request - * @param mixed $default - * - * @return null|string - */ - protected function getServerParameter($parameter, ServerRequestInterface $request, $default = null) - { - return isset($request->getServerParams()[$parameter]) ? $request->getServerParams()[$parameter] : $default; - } - /** * Issue an access token. * diff --git a/src/RequestValidatorTrait.php b/src/RequestValidatorTrait.php new file mode 100644 index 000000000..25ab95526 --- /dev/null +++ b/src/RequestValidatorTrait.php @@ -0,0 +1,198 @@ + + * @copyright Copyright (c) Alex Bilbie + * @license http://mit-license.org/ + * + * @link https://github.com/thephpleague/oauth2-server + */ + +namespace League\OAuth2\Server; + +use League\OAuth2\Server\RequestEvent; +use League\OAuth2\Server\Entities\ClientEntityInterface; +use League\OAuth2\Server\Exception\OAuthServerException; +use Psr\Http\Message\ServerRequestInterface; + +trait RequestValidatorTrait +{ + /** + * Get the Emitter. + * + * @return EmitterInterface + */ + abstract public function getEmitter(); + + /** + * @return ClientRepositoryInterface + */ + abstract public function getClientRepository(); + + /** + * Return the grant identifier that can be used in matching up requests. + * + * @return string + */ + abstract public function getIdentifier(); + + /** + * Validate the client. + * + * @param ServerRequestInterface $request + * + * @throws OAuthServerException + * + * @return ClientEntityInterface + */ + public function validateClient(ServerRequestInterface $request) + { + list($basicAuthUser, $basicAuthPassword) = $this->getBasicAuthCredentials($request); + + $clientId = $this->getRequestParameter('client_id', $request, $basicAuthUser); + if ($clientId === null) { + throw OAuthServerException::invalidRequest('client_id'); + } + + // If the client is confidential require the client secret + $clientSecret = $this->getRequestParameter('client_secret', $request, $basicAuthPassword); + + $client = $this->getClientRepository()->getClientEntity( + $clientId, + $this->getIdentifier(), + $clientSecret, + true + ); + + if ($client instanceof ClientEntityInterface === false) { + $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); + throw OAuthServerException::invalidClient(); + } + + $redirectUri = $this->getRequestParameter('redirect_uri', $request, null); + + if ($redirectUri !== null) { + $this->validateRedirectUri($redirectUri, $client, $request); + } + + return $client; + } + + /** + * Validate redirectUri from the request. + * If a redirect URI is provided ensure it matches what is pre-registered + * + * @param string $redirectUri + * @param ClientEntityInterface $client + * @param ServerRequestInterface $request + * + * @throws OAuthServerException + */ + public function validateRedirectUri( + $redirectUri, + ClientEntityInterface $client, + ServerRequestInterface $request + ) { + if (\is_string($client->getRedirectUri()) + && (strcmp($client->getRedirectUri(), $redirectUri) !== 0) + ) { + $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); + throw OAuthServerException::invalidClient(); + } elseif (\is_array($client->getRedirectUri()) + && \in_array($redirectUri, $client->getRedirectUri(), true) === false + ) { + $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); + throw OAuthServerException::invalidClient(); + } + } + + /** + * Retrieve request parameter. + * + * @param string $parameter + * @param ServerRequestInterface $request + * @param mixed $default + * + * @return null|string + */ + protected function getRequestParameter($parameter, ServerRequestInterface $request, $default = null) + { + $requestParameters = (array) $request->getParsedBody(); + + return isset($requestParameters[$parameter]) ? $requestParameters[$parameter] : $default; + } + + /** + * Retrieve HTTP Basic Auth credentials with the Authorization header + * of a request. First index of the returned array is the username, + * second is the password (so list() will work). If the header does + * not exist, or is otherwise an invalid HTTP Basic header, return + * [null, null]. + * + * @param ServerRequestInterface $request + * + * @return string[]|null[] + */ + public function getBasicAuthCredentials(ServerRequestInterface $request) + { + if (!$request->hasHeader('Authorization')) { + return [null, null]; + } + + $header = $request->getHeader('Authorization')[0]; + if (strpos($header, 'Basic ') !== 0) { + return [null, null]; + } + + if (!($decoded = base64_decode(substr($header, 6)))) { + return [null, null]; + } + + if (strpos($decoded, ':') === false) { + return [null, null]; // HTTP Basic header without colon isn't valid + } + + return explode(':', $decoded, 2); + } + + /** + * Retrieve query string parameter. + * + * @param string $parameter + * @param ServerRequestInterface $request + * @param mixed $default + * + * @return null|string + */ + protected function getQueryStringParameter($parameter, ServerRequestInterface $request, $default = null) + { + return isset($request->getQueryParams()[$parameter]) ? $request->getQueryParams()[$parameter] : $default; + } + + /** + * Retrieve cookie parameter. + * + * @param string $parameter + * @param ServerRequestInterface $request + * @param mixed $default + * + * @return null|string + */ + protected function getCookieParameter($parameter, ServerRequestInterface $request, $default = null) + { + return isset($request->getCookieParams()[$parameter]) ? $request->getCookieParams()[$parameter] : $default; + } + + /** + * Retrieve server parameter. + * + * @param string $parameter + * @param ServerRequestInterface $request + * @param mixed $default + * + * @return null|string + */ + protected function getServerParameter($parameter, ServerRequestInterface $request, $default = null) + { + return isset($request->getServerParams()[$parameter]) ? $request->getServerParams()[$parameter] : $default; + } +} From 8862eb1478a70929af416efc095f1ac629295f53 Mon Sep 17 00:00:00 2001 From: Jacob Weber Date: Thu, 7 Feb 2019 17:12:33 -0800 Subject: [PATCH 02/16] add revoke token handler --- src/AuthorizationServer.php | 39 +++++++ src/RevokeTokenHandler.php | 212 ++++++++++++++++++++++++++++++++++++ 2 files changed, 251 insertions(+) create mode 100644 src/RevokeTokenHandler.php diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index bde97d6eb..2ff76073e 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -13,6 +13,7 @@ use Defuse\Crypto\Key; use League\Event\EmitterAwareInterface; use League\Event\EmitterAwareTrait; +use League\OAuth2\Server\RevokeTokenHandler; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Grant\GrantTypeInterface; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; @@ -34,6 +35,11 @@ class AuthorizationServer implements EmitterAwareInterface */ protected $enabledGrantTypes = []; + /** + * @var RevokeTokenHandler + */ + protected $revokeTokenHandler = null; + /** * @var DateInterval[] */ @@ -205,6 +211,39 @@ public function respondToAccessTokenRequest(ServerRequestInterface $request, Res throw OAuthServerException::unsupportedGrantType(); } + /** + * Enable the revoke token handler on the server. + * + * @param RevokeTokenHandler $handler + */ + public function enableRevokeTokenHandler(RevokeTokenHandler $handler) + { + $handler->setAccessTokenRepository($this->accessTokenRepository); + $handler->setClientRepository($this->clientRepository); + $handler->setEncryptionKey($this->encryptionKey); + $handler->setEmitter($this->getEmitter()); + + $this->revokeTokenHandler = $handler; + } + + /** + * Return an revoke token response. + * + * @param ServerRequestInterface $request + * @param ResponseInterface $response + * + * @throws OAuthServerException + * + * @return ResponseInterface + */ + public function respondToRevokeTokenRequest(ServerRequestInterface $request, ResponseInterface $response) + { + if ($this->revokeTokenHandler !== null) { + return $this->revokeTokenHandler->respondToRevokeTokenRequest($request, $response); + } + + throw OAuthServerException::invalidRequest('token'); + } /** * Get the token type that grants will return in the HTTP response. diff --git a/src/RevokeTokenHandler.php b/src/RevokeTokenHandler.php new file mode 100644 index 000000000..be1df07ab --- /dev/null +++ b/src/RevokeTokenHandler.php @@ -0,0 +1,212 @@ + + * @copyright Copyright (c) Alex Bilbie + * @license http://mit-license.org/ + * + * @link https://github.com/thephpleague/oauth2-server + */ + +namespace League\OAuth2\Server; + +use Exception; +use InvalidArgumentException; +use Lcobucci\JWT\Parser; +use Lcobucci\JWT\Token; +use League\Event\EmitterAwareInterface; +use League\Event\EmitterAwareTrait; +use League\OAuth2\Server\ClientValidator; +use League\OAuth2\Server\CryptTrait; +use League\OAuth2\Server\RequestValidatorTrait; +use League\OAuth2\Server\Exception\OAuthServerException; +use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; +use League\OAuth2\Server\Repositories\ClientRepositoryInterface; +use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use RuntimeException; + +class RevokeTokenHandler implements EmitterAwareInterface +{ + use EmitterAwareTrait, CryptTrait, RequestValidatorTrait; + + /** + * @var ClientValidator + */ + protected $clientValidator; + + /** + * @var ClientRepositoryInterface + */ + protected $clientRepository; + + /** + * @var AccessTokenRepositoryInterface + */ + private $accessTokenRepository; + + /** + * @var RefreshTokenRepositoryInterface + */ + private $refreshTokenRepository; + + /** + * @var bool + */ + private $canRevokeAccessTokens; + + /** + * New handler instance. + * + * @param RefreshTokenRepositoryInterface $refreshTokenRepository + * @param bool $canRevokeAccessTokens + */ + public function __construct( + RefreshTokenRepositoryInterface $refreshTokenRepository, + $canRevokeAccessTokens = true + ) + { + $this->setRefreshTokenRepository($refreshTokenRepository); + $this->canRevokeAccessTokens = $canRevokeAccessTokens; + } + + /** + * @return ClientRepositoryInterface + */ + public function getClientRepository() + { + return $this->clientRepository; + } + + /** + * @param ClientRepositoryInterface $clientRepository + */ + public function setClientRepository(ClientRepositoryInterface $clientRepository) + { + $this->clientRepository = $clientRepository; + } + + /** + * @param AccessTokenRepositoryInterface $accessTokenRepository + */ + public function setAccessTokenRepository(AccessTokenRepositoryInterface $accessTokenRepository) + { + $this->accessTokenRepository = $accessTokenRepository; + } + + /** + * @param RefreshTokenRepositoryInterface $refreshTokenRepository + */ + public function setRefreshTokenRepository(RefreshTokenRepositoryInterface $refreshTokenRepository) + { + $this->refreshTokenRepository = $refreshTokenRepository; + } + + /** + * Return the grant identifier that can be used in matching up requests. + * + * @return string + */ + public function getIdentifier() { + return null; + } + + /** + * Return an revoke token response. + * https://tools.ietf.org/html/rfc7009 + * + * @param ServerRequestInterface $request + * @param ResponseInterface $response + * + * @throws OAuthServerException + * + * @return ResponseInterface + */ + public function respondToRevokeTokenRequest(ServerRequestInterface $request, ResponseInterface $response) + { + if ($request->getMethod() !== 'POST') { + throw OAuthServerException::invalidRequest('method'); + } + + $token = $this->getRequestParameter('token', $request); + $hint = $this->getRequestParameter('token_type_hint', $request); + + // Validate request + $client = $this->validateClient($request); + $clientId = $client->getIdentifier(); + + if (is_null($token)) { + return $response; + } + + // Attempt to read token + $accessToken = null; + $refreshToken = null; + if ($hint === 'access_token') { + $accessToken = $this->readAsAccessToken($token, $clientId); + } else if ($hint === 'refresh_token') { + $refreshToken = $this->readAsRefreshToken($token, $clientId); + } else { + $accessToken = $this->readAsAccessToken($token, $clientId); + if ($accessToken === null) { + $refreshToken = $this->readAsRefreshToken($token, $clientId); + } + } + + // Revoke tokens + if ($accessToken !== null) { + if (!$this->canRevokeAccessTokens) { + $errorMessage = 'The authorization server does not support the revocation of the presented token type'; + throw new OAuthServerException($errorMessage, 2, 'unsupported_token_type', 400); + } + $this->accessTokenRepository->revokeAccessToken($accessToken->getClaim('jti')); + } else if ($refreshToken !== null) { + $this->refreshTokenRepository->revokeRefreshToken($refreshToken['refresh_token_id']); + if ($this->canRevokeAccessTokens) { + $this->accessTokenRepository->revokeAccessToken($refreshToken['access_token_id']); + } + } + + return $response; + } + + /** + * @param string $tokenParam + * @param string $clientId + * + * @return null|Token + */ + protected function readAsAccessToken($tokenParam, $clientId) { + try { + $token = (new Parser())->parse($tokenParam); + $clientId = $token->getClaim('aud'); + if ($clientId !== $clientId) { + return null; + } + + return $token; + } catch (Exception $exception) { + // JWT couldn't be parsed so ignore + return null; + } + } + + /** + * @param string $tokenParam + * @param string $clientId + * + * @return null|array + */ + protected function readAsRefreshToken($tokenParam, $clientId) { + try { + $refreshToken = $this->decrypt($tokenParam); + $refreshTokenData = json_decode($refreshToken, true); + if ($refreshTokenData['client_id'] !== $clientId) { + return null; + } + return $refreshTokenData; + } catch (Exception $e) { + // token couldn't be decrypted so ignore + } + } +} From 73321324a239299aa9d5d825b74c093621263ce1 Mon Sep 17 00:00:00 2001 From: Jacob Weber Date: Fri, 8 Feb 2019 11:18:38 -0800 Subject: [PATCH 03/16] use ResponseTypeInterface --- src/AuthorizationServer.php | 7 ++++++- src/RevokeTokenHandler.php | 8 ++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index 2ff76073e..6dae231ee 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -211,6 +211,7 @@ public function respondToAccessTokenRequest(ServerRequestInterface $request, Res throw OAuthServerException::unsupportedGrantType(); } + /** * Enable the revoke token handler on the server. * @@ -239,7 +240,11 @@ public function enableRevokeTokenHandler(RevokeTokenHandler $handler) public function respondToRevokeTokenRequest(ServerRequestInterface $request, ResponseInterface $response) { if ($this->revokeTokenHandler !== null) { - return $this->revokeTokenHandler->respondToRevokeTokenRequest($request, $response); + $revokeResponse = $this->revokeTokenHandler->respondToRevokeTokenRequest($request, $this->getResponseType()); + + if ($revokeResponse instanceof ResponseTypeInterface) { + return $revokeResponse->generateHttpResponse($response); + } } throw OAuthServerException::invalidRequest('token'); diff --git a/src/RevokeTokenHandler.php b/src/RevokeTokenHandler.php index be1df07ab..0808e3220 100644 --- a/src/RevokeTokenHandler.php +++ b/src/RevokeTokenHandler.php @@ -22,7 +22,7 @@ use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; -use Psr\Http\Message\ResponseInterface; +use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use Psr\Http\Message\ServerRequestInterface; use RuntimeException; @@ -116,13 +116,13 @@ public function getIdentifier() { * https://tools.ietf.org/html/rfc7009 * * @param ServerRequestInterface $request - * @param ResponseInterface $response + * @param ResponseTypeInterface $response * * @throws OAuthServerException * - * @return ResponseInterface + * @return ResponseTypeInterface */ - public function respondToRevokeTokenRequest(ServerRequestInterface $request, ResponseInterface $response) + public function respondToRevokeTokenRequest(ServerRequestInterface $request, ResponseTypeInterface $response) { if ($request->getMethod() !== 'POST') { throw OAuthServerException::invalidRequest('method'); From ddef1d4e42826a36c3bc168239056a4a566ef9b6 Mon Sep 17 00:00:00 2001 From: Jacob Weber Date: Fri, 8 Feb 2019 11:21:25 -0800 Subject: [PATCH 04/16] update error types --- src/AuthorizationServer.php | 3 ++- src/RevokeTokenHandler.php | 27 ++++++++++++++++----------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index 6dae231ee..ae93de7cb 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -247,7 +247,8 @@ public function respondToRevokeTokenRequest(ServerRequestInterface $request, Res } } - throw OAuthServerException::invalidRequest('token'); + $errorMessage = 'Token revocation not supported.'; + throw new OAuthServerException($errorMessage, 3, 'invalid_request', 400); } /** diff --git a/src/RevokeTokenHandler.php b/src/RevokeTokenHandler.php index 0808e3220..12afe1f53 100644 --- a/src/RevokeTokenHandler.php +++ b/src/RevokeTokenHandler.php @@ -125,7 +125,8 @@ public function getIdentifier() { public function respondToRevokeTokenRequest(ServerRequestInterface $request, ResponseTypeInterface $response) { if ($request->getMethod() !== 'POST') { - throw OAuthServerException::invalidRequest('method'); + $errorMessage = 'POST method required.'; + throw new OAuthServerException($errorMessage, 3, 'invalid_request', 400); } $token = $this->getRequestParameter('token', $request); @@ -177,18 +178,19 @@ public function respondToRevokeTokenRequest(ServerRequestInterface $request, Res * @return null|Token */ protected function readAsAccessToken($tokenParam, $clientId) { + $token = null; try { $token = (new Parser())->parse($tokenParam); - $clientId = $token->getClaim('aud'); - if ($clientId !== $clientId) { - return null; - } - - return $token; } catch (Exception $exception) { // JWT couldn't be parsed so ignore return null; } + + $clientId = $token->getClaim('aud'); + if ($clientId !== $clientId) { + throw OAuthServerException::invalidClient(); + } + return $token; } /** @@ -198,15 +200,18 @@ protected function readAsAccessToken($tokenParam, $clientId) { * @return null|array */ protected function readAsRefreshToken($tokenParam, $clientId) { + $refreshTokenData = null; try { $refreshToken = $this->decrypt($tokenParam); $refreshTokenData = json_decode($refreshToken, true); - if ($refreshTokenData['client_id'] !== $clientId) { - return null; - } - return $refreshTokenData; } catch (Exception $e) { + return null; // token couldn't be decrypted so ignore } + + if ($refreshTokenData['client_id'] !== $clientId) { + throw OAuthServerException::invalidClient(); + } + return $refreshTokenData; } } From 78649571a9a9b1277c29f00e899fcd59cdb8d31e Mon Sep 17 00:00:00 2001 From: Jacob Weber Date: Fri, 8 Feb 2019 11:36:10 -0800 Subject: [PATCH 05/16] add validation with public key --- src/RevokeTokenHandler.php | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/RevokeTokenHandler.php b/src/RevokeTokenHandler.php index 12afe1f53..bf8db7a4e 100644 --- a/src/RevokeTokenHandler.php +++ b/src/RevokeTokenHandler.php @@ -12,6 +12,7 @@ use Exception; use InvalidArgumentException; use Lcobucci\JWT\Parser; +use Lcobucci\JWT\Signer\Rsa\Sha256; use Lcobucci\JWT\Token; use League\Event\EmitterAwareInterface; use League\Event\EmitterAwareTrait; @@ -55,18 +56,31 @@ class RevokeTokenHandler implements EmitterAwareInterface */ private $canRevokeAccessTokens; + /** + * @var CryptKey + */ + protected $publicKey; + /** * New handler instance. * * @param RefreshTokenRepositoryInterface $refreshTokenRepository + * @param CryptKey|string $publicKey * @param bool $canRevokeAccessTokens */ public function __construct( RefreshTokenRepositoryInterface $refreshTokenRepository, + $publicKey, $canRevokeAccessTokens = true ) { $this->setRefreshTokenRepository($refreshTokenRepository); + + if ($publicKey instanceof CryptKey === false) { + $publicKey = new CryptKey($publicKey); + } + $this->publicKey = $publicKey; + $this->canRevokeAccessTokens = $canRevokeAccessTokens; } @@ -102,6 +116,16 @@ public function setRefreshTokenRepository(RefreshTokenRepositoryInterface $refre $this->refreshTokenRepository = $refreshTokenRepository; } + /** + * Set the public key + * + * @param CryptKey $key + */ + public function setPublicKey(CryptKey $key) + { + $this->publicKey = $key; + } + /** * Return the grant identifier that can be used in matching up requests. * @@ -181,6 +205,10 @@ protected function readAsAccessToken($tokenParam, $clientId) { $token = null; try { $token = (new Parser())->parse($tokenParam); + + if ($token->verify(new Sha256(), $this->publicKey->getKeyPath()) === false) { + return null; + } } catch (Exception $exception) { // JWT couldn't be parsed so ignore return null; From 240d52dadc0e5fb58ff0a4778b2967adcf9694e1 Mon Sep 17 00:00:00 2001 From: Jacob Weber Date: Fri, 8 Feb 2019 11:36:31 -0800 Subject: [PATCH 06/16] remove dead code --- src/RevokeTokenHandler.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/RevokeTokenHandler.php b/src/RevokeTokenHandler.php index bf8db7a4e..a98831f87 100644 --- a/src/RevokeTokenHandler.php +++ b/src/RevokeTokenHandler.php @@ -16,7 +16,6 @@ use Lcobucci\JWT\Token; use League\Event\EmitterAwareInterface; use League\Event\EmitterAwareTrait; -use League\OAuth2\Server\ClientValidator; use League\OAuth2\Server\CryptTrait; use League\OAuth2\Server\RequestValidatorTrait; use League\OAuth2\Server\Exception\OAuthServerException; @@ -31,11 +30,6 @@ class RevokeTokenHandler implements EmitterAwareInterface { use EmitterAwareTrait, CryptTrait, RequestValidatorTrait; - /** - * @var ClientValidator - */ - protected $clientValidator; - /** * @var ClientRepositoryInterface */ From 2e802157599589c523e3fa8b78a8198eb20c9b3f Mon Sep 17 00:00:00 2001 From: Jacob Weber Date: Fri, 8 Feb 2019 11:36:46 -0800 Subject: [PATCH 07/16] use string for identifier --- src/RevokeTokenHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RevokeTokenHandler.php b/src/RevokeTokenHandler.php index a98831f87..240e1e4d2 100644 --- a/src/RevokeTokenHandler.php +++ b/src/RevokeTokenHandler.php @@ -126,7 +126,7 @@ public function setPublicKey(CryptKey $key) * @return string */ public function getIdentifier() { - return null; + return ''; } /** From 9a40529b2511e344cf2b12dcaafe0bf68c0caada Mon Sep 17 00:00:00 2001 From: Jacob Weber Date: Fri, 8 Feb 2019 11:36:58 -0800 Subject: [PATCH 08/16] add tests --- tests/AuthorizationServerTest.php | 69 +++++ tests/RevokeTokenHandlerTest.php | 490 ++++++++++++++++++++++++++++++ 2 files changed, 559 insertions(+) create mode 100644 tests/RevokeTokenHandlerTest.php diff --git a/tests/AuthorizationServerTest.php b/tests/AuthorizationServerTest.php index 0a8bf6d1b..9054b799e 100644 --- a/tests/AuthorizationServerTest.php +++ b/tests/AuthorizationServerTest.php @@ -4,6 +4,7 @@ use League\OAuth2\Server\AuthorizationServer; use League\OAuth2\Server\CryptKey; +use League\OAuth2\Server\RevokeTokenHandler; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Grant\AuthCodeGrant; use League\OAuth2\Server\Grant\ClientCredentialsGrant; @@ -354,4 +355,72 @@ public function testValidateAuthorizationRequestUnregistered() $server->validateAuthorizationRequest($request); } + + public function testRespondToRevokeRequestUnregistered() + { + $server = new AuthorizationServer( + $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(), + $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), + $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(), + 'file://' . __DIR__ . '/Stubs/private.key', + base64_encode(random_bytes(36)), + new StubResponseType() + ); + + try { + $server->respondToRevokeTokenRequest(ServerRequestFactory::fromGlobals(), new Response); + } catch (OAuthServerException $e) { + $this->assertEquals('invalid_request', $e->getErrorType()); + $this->assertEquals(400, $e->getHttpStatusCode()); + } + } + + public function testRespondToRevokeRequest() + { + $clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepository->method('getClientEntity')->willReturn(new ClientEntity()); + + $scope = new ScopeEntity(); + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope); + $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); + + $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); + $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); + + $server = new AuthorizationServer( + $clientRepository, + $accessTokenRepositoryMock, + $scopeRepositoryMock, + 'file://' . __DIR__ . '/Stubs/private.key', + base64_encode(random_bytes(36)), + new StubResponseType() + ); + + $server->setDefaultScope(self::DEFAULT_SCOPE); + $server->enableRevokeTokenHandler(new RevokeTokenHandler( + $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), + 'file://' . __DIR__ . '/Stubs/public.key' + )); + + $request = new ServerRequest( + [], + [], + null, + 'POST', + 'php://input', + [], + [], + [], + [ + 'token' => 'abcdef', + 'token_type_hint' => 'access_token', + 'client_id' => 'foo', + 'client_secret' => 'bar' + ] + ); + + $response = $server->respondToRevokeTokenRequest($request, new Response); + $this->assertEquals(200, $response->getStatusCode()); + } } diff --git a/tests/RevokeTokenHandlerTest.php b/tests/RevokeTokenHandlerTest.php new file mode 100644 index 000000000..4bd635f43 --- /dev/null +++ b/tests/RevokeTokenHandlerTest.php @@ -0,0 +1,490 @@ +cryptStub = new CryptTraitStub(); + } + + public function testRespondToRequestValidAccessTokenWithHint() + { + $client = new ClientEntity(); + $client->setIdentifier('foo'); + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); + $accessTokenRepositoryMock->expects($this->once())->method('revokeAccessToken'); + $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + $refreshTokenRepositoryMock->expects($this->never())->method('revokeRefreshToken'); + + $publicKey = new CryptKey('file://' . __DIR__ . '/Stubs/public.key'); + $handler = new RevokeTokenHandler($refreshTokenRepositoryMock, $publicKey); + $handler->setClientRepository($clientRepositoryMock); + $handler->setAccessTokenRepository($accessTokenRepositoryMock); + $handler->setEncryptionKey($this->cryptStub->getKey()); + + $accessToken = new AccessTokenEntity(); + $accessToken->setIdentifier('test'); + $accessToken->setUserIdentifier(123); + $accessToken->setExpiryDateTime((new \DateTime())->sub(new \DateInterval('PT1H'))); + $accessToken->setClient($client); + $accessTokenJWT = $accessToken->convertToJWT(new CryptKey('file://' . __DIR__ . '/Stubs/private.key')); + + $serverRequest = new ServerRequest(); + $serverRequest = $serverRequest->withMethod('POST')->withParsedBody( + [ + 'client_id' => 'foo', + 'client_secret' => 'bar', + 'token' => (string) $accessTokenJWT, + 'token_type_hint' => 'access_token' + ] + ); + + $responseType = new StubResponseType(); + $handler->respondToRevokeTokenRequest($serverRequest, $responseType); + } + + public function testRespondToRequestValidAccessTokenWithoutHint() + { + $client = new ClientEntity(); + $client->setIdentifier('foo'); + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); + $accessTokenRepositoryMock->expects($this->once())->method('revokeAccessToken'); + $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + $refreshTokenRepositoryMock->expects($this->never())->method('revokeRefreshToken'); + + $publicKey = new CryptKey('file://' . __DIR__ . '/Stubs/public.key'); + $handler = new RevokeTokenHandler($refreshTokenRepositoryMock, $publicKey); + $handler->setClientRepository($clientRepositoryMock); + $handler->setAccessTokenRepository($accessTokenRepositoryMock); + $handler->setEncryptionKey($this->cryptStub->getKey()); + + $accessToken = new AccessTokenEntity(); + $accessToken->setIdentifier('test'); + $accessToken->setUserIdentifier(123); + $accessToken->setExpiryDateTime((new \DateTime())->sub(new \DateInterval('PT1H'))); + $accessToken->setClient($client); + $accessTokenJWT = $accessToken->convertToJWT(new CryptKey('file://' . __DIR__ . '/Stubs/private.key')); + + $serverRequest = new ServerRequest(); + $serverRequest = $serverRequest->withMethod('POST')->withParsedBody( + [ + 'client_id' => 'foo', + 'client_secret' => 'bar', + 'token' => (string) $accessTokenJWT + ] + ); + + $responseType = new StubResponseType(); + $handler->respondToRevokeTokenRequest($serverRequest, $responseType); + } + + /** + * @expectedException \League\OAuth2\Server\Exception\OAuthServerException + * @expectedExceptionCode 2 + */ + public function testRespondToRequestValidAccessTokenButCannotRevokeAccessTokens() + { + $client = new ClientEntity(); + $client->setIdentifier('foo'); + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); + $accessTokenRepositoryMock->expects($this->never())->method('revokeAccessToken'); + $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + $refreshTokenRepositoryMock->expects($this->never())->method('revokeRefreshToken'); + + $publicKey = new CryptKey('file://' . __DIR__ . '/Stubs/public.key'); + $handler = new RevokeTokenHandler($refreshTokenRepositoryMock, $publicKey, false); + $handler->setClientRepository($clientRepositoryMock); + $handler->setAccessTokenRepository($accessTokenRepositoryMock); + $handler->setEncryptionKey($this->cryptStub->getKey()); + + $accessToken = new AccessTokenEntity(); + $accessToken->setIdentifier('test'); + $accessToken->setUserIdentifier(123); + $accessToken->setExpiryDateTime((new \DateTime())->sub(new \DateInterval('PT1H'))); + $accessToken->setClient($client); + $accessTokenJWT = $accessToken->convertToJWT(new CryptKey('file://' . __DIR__ . '/Stubs/private.key')); + + $serverRequest = new ServerRequest(); + $serverRequest = $serverRequest->withMethod('POST')->withParsedBody( + [ + 'client_id' => 'foo', + 'client_secret' => 'bar', + 'token' => (string) $accessTokenJWT, + 'token_type_hint' => 'access_token' + ] + ); + + $responseType = new StubResponseType(); + $handler->respondToRevokeTokenRequest($serverRequest, $responseType); + } + + public function testRespondToRequestValidRefreshTokenWithHint() + { + $client = new ClientEntity(); + $client->setIdentifier('foo'); + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); + $accessTokenRepositoryMock->expects($this->once())->method('revokeAccessToken'); + $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + $refreshTokenRepositoryMock->expects($this->once())->method('revokeRefreshToken'); + + $publicKey = new CryptKey('file://' . __DIR__ . '/Stubs/public.key'); + $handler = new RevokeTokenHandler($refreshTokenRepositoryMock, $publicKey); + $handler->setClientRepository($clientRepositoryMock); + $handler->setAccessTokenRepository($accessTokenRepositoryMock); + $handler->setEncryptionKey($this->cryptStub->getKey()); + + $refreshToken = $this->cryptStub->doEncrypt( + json_encode( + [ + 'client_id' => 'foo', + 'refresh_token_id' => 'zyxwvu', + 'access_token_id' => 'abcdef', + 'scopes' => ['foo'], + 'user_id' => 123, + 'expire_time' => time() + 3600, + ] + ) + ); + + $serverRequest = new ServerRequest(); + $serverRequest = $serverRequest->withMethod('POST')->withParsedBody( + [ + 'client_id' => 'foo', + 'client_secret' => 'bar', + 'token' => $refreshToken, + 'token_type_hint' => 'refresh_token' + ] + ); + + $responseType = new StubResponseType(); + $handler->respondToRevokeTokenRequest($serverRequest, $responseType); + } + + public function testRespondToRequestValidRefreshTokenWithoutHint() + { + $client = new ClientEntity(); + $client->setIdentifier('foo'); + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); + $accessTokenRepositoryMock->expects($this->once())->method('revokeAccessToken'); + $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + $refreshTokenRepositoryMock->expects($this->once())->method('revokeRefreshToken'); + + $publicKey = new CryptKey('file://' . __DIR__ . '/Stubs/public.key'); + $handler = new RevokeTokenHandler($refreshTokenRepositoryMock, $publicKey); + $handler->setClientRepository($clientRepositoryMock); + $handler->setAccessTokenRepository($accessTokenRepositoryMock); + $handler->setEncryptionKey($this->cryptStub->getKey()); + + $refreshToken = $this->cryptStub->doEncrypt( + json_encode( + [ + 'client_id' => 'foo', + 'refresh_token_id' => 'zyxwvu', + 'access_token_id' => 'abcdef', + 'scopes' => ['foo'], + 'user_id' => 123, + 'expire_time' => time() + 3600, + ] + ) + ); + + $serverRequest = new ServerRequest(); + $serverRequest = $serverRequest->withMethod('POST')->withParsedBody( + [ + 'client_id' => 'foo', + 'client_secret' => 'bar', + 'token' => $refreshToken + ] + ); + + $responseType = new StubResponseType(); + $handler->respondToRevokeTokenRequest($serverRequest, $responseType); + } + + public function testRespondToRequestValidRefreshTokenButCannotRevokeAccessTokens() + { + $client = new ClientEntity(); + $client->setIdentifier('foo'); + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); + $accessTokenRepositoryMock->expects($this->never())->method('revokeAccessToken'); + $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + $refreshTokenRepositoryMock->expects($this->once())->method('revokeRefreshToken'); + + $publicKey = new CryptKey('file://' . __DIR__ . '/Stubs/public.key'); + $handler = new RevokeTokenHandler($refreshTokenRepositoryMock, $publicKey, false); + $handler->setClientRepository($clientRepositoryMock); + $handler->setAccessTokenRepository($accessTokenRepositoryMock); + $handler->setEncryptionKey($this->cryptStub->getKey()); + + $refreshToken = $this->cryptStub->doEncrypt( + json_encode( + [ + 'client_id' => 'foo', + 'refresh_token_id' => 'zyxwvu', + 'access_token_id' => 'abcdef', + 'scopes' => ['foo'], + 'user_id' => 123, + 'expire_time' => time() + 3600, + ] + ) + ); + + $serverRequest = new ServerRequest(); + $serverRequest = $serverRequest->withMethod('POST')->withParsedBody( + [ + 'client_id' => 'foo', + 'client_secret' => 'bar', + 'token' => $refreshToken, + 'token_type_hint' => 'refresh_token' + ] + ); + + $responseType = new StubResponseType(); + $handler->respondToRevokeTokenRequest($serverRequest, $responseType); + } + + public function testRespondToRequestMissingToken() + { + $client = new ClientEntity(); + $client->setIdentifier('foo'); + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); + $accessTokenRepositoryMock->expects($this->never())->method('revokeAccessToken'); + $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + $refreshTokenRepositoryMock->expects($this->never())->method('revokeRefreshToken'); + + $publicKey = new CryptKey('file://' . __DIR__ . '/Stubs/public.key'); + $handler = new RevokeTokenHandler($refreshTokenRepositoryMock, $publicKey); + $handler->setClientRepository($clientRepositoryMock); + $handler->setAccessTokenRepository($accessTokenRepositoryMock); + $handler->setEncryptionKey($this->cryptStub->getKey()); + + $serverRequest = new ServerRequest(); + $serverRequest = $serverRequest->withMethod('POST')->withParsedBody( + [ + 'client_id' => 'foo', + 'client_secret' => 'bar' + ] + ); + + $responseType = new StubResponseType(); + $handler->respondToRevokeTokenRequest($serverRequest, $responseType); + } + + public function testRespondToRequestInvalidRefreshToken() + { + $client = new ClientEntity(); + $client->setIdentifier('foo'); + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); + $accessTokenRepositoryMock->expects($this->never())->method('revokeAccessToken'); + $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + $refreshTokenRepositoryMock->expects($this->never())->method('revokeRefreshToken'); + + $publicKey = new CryptKey('file://' . __DIR__ . '/Stubs/public.key'); + $handler = new RevokeTokenHandler($refreshTokenRepositoryMock, $publicKey); + $handler->setClientRepository($clientRepositoryMock); + $handler->setAccessTokenRepository($accessTokenRepositoryMock); + $handler->setEncryptionKey($this->cryptStub->getKey()); + + $refreshToken = 'foobar'; + + $serverRequest = new ServerRequest(); + $serverRequest = $serverRequest->withMethod('POST')->withParsedBody( + [ + 'client_id' => 'foo', + 'client_secret' => 'bar', + 'token' => $refreshToken, + 'token_type_hint' => 'refresh_token' + ] + ); + + $responseType = new StubResponseType(); + $handler->respondToRevokeTokenRequest($serverRequest, $responseType); + } + + /** + * @expectedException \League\OAuth2\Server\Exception\OAuthServerException + * @expectedExceptionCode 4 + */ + public function testRespondToRequestClientMismatch() + { + $client = new ClientEntity(); + $client->setIdentifier('foo'); + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); + $accessTokenRepositoryMock->expects($this->never())->method('revokeAccessToken'); + $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + $refreshTokenRepositoryMock->expects($this->never())->method('revokeRefreshToken'); + + $publicKey = new CryptKey('file://' . __DIR__ . '/Stubs/public.key'); + $handler = new RevokeTokenHandler($refreshTokenRepositoryMock, $publicKey); + $handler->setClientRepository($clientRepositoryMock); + $handler->setAccessTokenRepository($accessTokenRepositoryMock); + $handler->setEncryptionKey($this->cryptStub->getKey()); + + $refreshToken = $this->cryptStub->doEncrypt( + json_encode( + [ + 'client_id' => 'bar', + 'refresh_token_id' => 'zyxwvu', + 'access_token_id' => 'abcdef', + 'scopes' => ['foo'], + 'user_id' => 123, + 'expire_time' => time() + 3600, + ] + ) + ); + + $serverRequest = new ServerRequest(); + $serverRequest = $serverRequest->withMethod('POST')->withParsedBody( + [ + 'client_id' => 'foo', + 'client_secret' => 'bar', + 'token' => $refreshToken, + 'token_type_hint' => 'refresh_token' + ] + ); + + $responseType = new StubResponseType(); + $handler->respondToRevokeTokenRequest($serverRequest, $responseType); + } + + public function testRespondToRequestExpiredRefreshToken() + { + $client = new ClientEntity(); + $client->setIdentifier('foo'); + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); + $accessTokenRepositoryMock->expects($this->once())->method('revokeAccessToken'); + $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + $refreshTokenRepositoryMock->expects($this->once())->method('revokeRefreshToken'); + + $publicKey = new CryptKey('file://' . __DIR__ . '/Stubs/public.key'); + $handler = new RevokeTokenHandler($refreshTokenRepositoryMock, $publicKey); + $handler->setClientRepository($clientRepositoryMock); + $handler->setAccessTokenRepository($accessTokenRepositoryMock); + $handler->setEncryptionKey($this->cryptStub->getKey()); + + $refreshToken = $this->cryptStub->doEncrypt( + json_encode( + [ + 'client_id' => 'foo', + 'refresh_token_id' => 'zyxwvu', + 'access_token_id' => 'abcdef', + 'scopes' => ['foo'], + 'user_id' => 123, + 'expire_time' => time() - 3600, + ] + ) + ); + + $serverRequest = new ServerRequest(); + $serverRequest = $serverRequest->withMethod('POST')->withParsedBody( + [ + 'client_id' => 'foo', + 'client_secret' => 'bar', + 'token' => $refreshToken, + 'token_type_hint' => 'refresh_token' + ] + ); + + $responseType = new StubResponseType(); + $handler->respondToRevokeTokenRequest($serverRequest, $responseType); + } + + public function testRespondToRequestRevokedRefreshToken() + { + $client = new ClientEntity(); + $client->setIdentifier('foo'); + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); + $accessTokenRepositoryMock->expects($this->once())->method('revokeAccessToken'); + $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + $refreshTokenRepositoryMock->expects($this->once())->method('revokeRefreshToken'); + + $publicKey = new CryptKey('file://' . __DIR__ . '/Stubs/public.key'); + $handler = new RevokeTokenHandler($refreshTokenRepositoryMock, $publicKey); + $handler->setClientRepository($clientRepositoryMock); + $handler->setAccessTokenRepository($accessTokenRepositoryMock); + $handler->setEncryptionKey($this->cryptStub->getKey()); + + $refreshToken = $this->cryptStub->doEncrypt( + json_encode( + [ + 'client_id' => 'foo', + 'refresh_token_id' => 'zyxwvu', + 'access_token_id' => 'abcdef', + 'scopes' => ['foo'], + 'user_id' => 123, + 'expire_time' => time() + 3600, + ] + ) + ); + + $serverRequest = new ServerRequest(); + $serverRequest = $serverRequest->withMethod('POST')->withParsedBody( + [ + 'client_id' => 'foo', + 'client_secret' => 'bar', + 'token' => $refreshToken, + 'token_type_hint' => 'refresh_token' + ] + ); + + $responseType = new StubResponseType(); + $handler->respondToRevokeTokenRequest($serverRequest, $responseType); + } +} From 1e8b936f452520033f7bf808145e7a8edc95bc54 Mon Sep 17 00:00:00 2001 From: Jacob Weber Date: Fri, 8 Feb 2019 11:42:01 -0800 Subject: [PATCH 09/16] if we can't read token using hint, try other token type --- src/RevokeTokenHandler.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/RevokeTokenHandler.php b/src/RevokeTokenHandler.php index 240e1e4d2..e3f10515f 100644 --- a/src/RevokeTokenHandler.php +++ b/src/RevokeTokenHandler.php @@ -161,10 +161,11 @@ public function respondToRevokeTokenRequest(ServerRequestInterface $request, Res // Attempt to read token $accessToken = null; $refreshToken = null; - if ($hint === 'access_token') { - $accessToken = $this->readAsAccessToken($token, $clientId); - } else if ($hint === 'refresh_token') { + if ($hint === 'refresh_token') { $refreshToken = $this->readAsRefreshToken($token, $clientId); + if ($refreshToken === null) { + $accessToken = $this->readAsAccessToken($token, $clientId); + } } else { $accessToken = $this->readAsAccessToken($token, $clientId); if ($accessToken === null) { From a72240c50f3e59a00a0f2dadbe554541c7a24c4f Mon Sep 17 00:00:00 2001 From: Jacob Weber Date: Fri, 8 Feb 2019 12:14:14 -0800 Subject: [PATCH 10/16] fix styles --- src/AuthorizationServer.php | 1 - src/Grant/AbstractGrant.php | 3 +-- src/RequestValidatorTrait.php | 3 +-- src/RevokeTokenHandler.php | 30 +++++++++++++++--------------- tests/AuthorizationServerTest.php | 4 ++-- tests/RevokeTokenHandlerTest.php | 27 +++++++++++---------------- 6 files changed, 30 insertions(+), 38 deletions(-) diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index ae93de7cb..91b0b121d 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -13,7 +13,6 @@ use Defuse\Crypto\Key; use League\Event\EmitterAwareInterface; use League\Event\EmitterAwareTrait; -use League\OAuth2\Server\RevokeTokenHandler; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Grant\GrantTypeInterface; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 8e0dddc0c..0f7995ffa 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -15,7 +15,6 @@ use Error; use Exception; use League\Event\EmitterAwareTrait; -use League\OAuth2\Server\RequestValidatorTrait; use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\CryptTrait; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; @@ -31,8 +30,8 @@ use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; use League\OAuth2\Server\Repositories\UserRepositoryInterface; -use League\OAuth2\Server\RequestEvent; use League\OAuth2\Server\RequestTypes\AuthorizationRequest; +use League\OAuth2\Server\RequestValidatorTrait; use LogicException; use Psr\Http\Message\ServerRequestInterface; use TypeError; diff --git a/src/RequestValidatorTrait.php b/src/RequestValidatorTrait.php index 25ab95526..dc676a43f 100644 --- a/src/RequestValidatorTrait.php +++ b/src/RequestValidatorTrait.php @@ -9,7 +9,6 @@ namespace League\OAuth2\Server; -use League\OAuth2\Server\RequestEvent; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Exception\OAuthServerException; use Psr\Http\Message\ServerRequestInterface; @@ -38,7 +37,7 @@ abstract public function getIdentifier(); /** * Validate the client. * - * @param ServerRequestInterface $request + * @param ServerRequestInterface $request * * @throws OAuthServerException * diff --git a/src/RevokeTokenHandler.php b/src/RevokeTokenHandler.php index e3f10515f..01fb743b7 100644 --- a/src/RevokeTokenHandler.php +++ b/src/RevokeTokenHandler.php @@ -10,21 +10,17 @@ namespace League\OAuth2\Server; use Exception; -use InvalidArgumentException; use Lcobucci\JWT\Parser; use Lcobucci\JWT\Signer\Rsa\Sha256; use Lcobucci\JWT\Token; use League\Event\EmitterAwareInterface; use League\Event\EmitterAwareTrait; -use League\OAuth2\Server\CryptTrait; -use League\OAuth2\Server\RequestValidatorTrait; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use Psr\Http\Message\ServerRequestInterface; -use RuntimeException; class RevokeTokenHandler implements EmitterAwareInterface { @@ -66,8 +62,7 @@ public function __construct( RefreshTokenRepositoryInterface $refreshTokenRepository, $publicKey, $canRevokeAccessTokens = true - ) - { + ) { $this->setRefreshTokenRepository($refreshTokenRepository); if ($publicKey instanceof CryptKey === false) { @@ -125,12 +120,13 @@ public function setPublicKey(CryptKey $key) * * @return string */ - public function getIdentifier() { + public function getIdentifier() + { return ''; } - /** - * Return an revoke token response. + /** + * Return a revoke token response. * https://tools.ietf.org/html/rfc7009 * * @param ServerRequestInterface $request @@ -180,7 +176,7 @@ public function respondToRevokeTokenRequest(ServerRequestInterface $request, Res throw new OAuthServerException($errorMessage, 2, 'unsupported_token_type', 400); } $this->accessTokenRepository->revokeAccessToken($accessToken->getClaim('jti')); - } else if ($refreshToken !== null) { + } elseif ($refreshToken !== null) { $this->refreshTokenRepository->revokeRefreshToken($refreshToken['refresh_token_id']); if ($this->canRevokeAccessTokens) { $this->accessTokenRepository->revokeAccessToken($refreshToken['access_token_id']); @@ -196,23 +192,25 @@ public function respondToRevokeTokenRequest(ServerRequestInterface $request, Res * * @return null|Token */ - protected function readAsAccessToken($tokenParam, $clientId) { + protected function readAsAccessToken($tokenParam, $clientId) + { $token = null; try { $token = (new Parser())->parse($tokenParam); if ($token->verify(new Sha256(), $this->publicKey->getKeyPath()) === false) { - return null; + return; } } catch (Exception $exception) { // JWT couldn't be parsed so ignore - return null; + return; } $clientId = $token->getClaim('aud'); if ($clientId !== $clientId) { throw OAuthServerException::invalidClient(); } + return $token; } @@ -222,19 +220,21 @@ protected function readAsAccessToken($tokenParam, $clientId) { * * @return null|array */ - protected function readAsRefreshToken($tokenParam, $clientId) { + protected function readAsRefreshToken($tokenParam, $clientId) + { $refreshTokenData = null; try { $refreshToken = $this->decrypt($tokenParam); $refreshTokenData = json_decode($refreshToken, true); } catch (Exception $e) { - return null; + return; // token couldn't be decrypted so ignore } if ($refreshTokenData['client_id'] !== $clientId) { throw OAuthServerException::invalidClient(); } + return $refreshTokenData; } } diff --git a/tests/AuthorizationServerTest.php b/tests/AuthorizationServerTest.php index 9054b799e..ca87c20f0 100644 --- a/tests/AuthorizationServerTest.php +++ b/tests/AuthorizationServerTest.php @@ -4,7 +4,6 @@ use League\OAuth2\Server\AuthorizationServer; use League\OAuth2\Server\CryptKey; -use League\OAuth2\Server\RevokeTokenHandler; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Grant\AuthCodeGrant; use League\OAuth2\Server\Grant\ClientCredentialsGrant; @@ -15,6 +14,7 @@ use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; use League\OAuth2\Server\RequestTypes\AuthorizationRequest; use League\OAuth2\Server\ResponseTypes\BearerTokenResponse; +use League\OAuth2\Server\RevokeTokenHandler; use LeagueTests\Stubs\AccessTokenEntity; use LeagueTests\Stubs\AuthCodeEntity; use LeagueTests\Stubs\ClientEntity; @@ -416,7 +416,7 @@ public function testRespondToRevokeRequest() 'token' => 'abcdef', 'token_type_hint' => 'access_token', 'client_id' => 'foo', - 'client_secret' => 'bar' + 'client_secret' => 'bar', ] ); diff --git a/tests/RevokeTokenHandlerTest.php b/tests/RevokeTokenHandlerTest.php index 4bd635f43..611538369 100644 --- a/tests/RevokeTokenHandlerTest.php +++ b/tests/RevokeTokenHandlerTest.php @@ -3,18 +3,13 @@ namespace LeagueTests; use League\OAuth2\Server\CryptKey; -use League\OAuth2\Server\RevokeTokenHandler; -use League\OAuth2\Server\Entities\AccessTokenEntityInterface; -use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; -use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; +use League\OAuth2\Server\RevokeTokenHandler; use LeagueTests\Stubs\AccessTokenEntity; use LeagueTests\Stubs\ClientEntity; use LeagueTests\Stubs\CryptTraitStub; -use LeagueTests\Stubs\RefreshTokenEntity; -use LeagueTests\Stubs\ScopeEntity; use LeagueTests\Stubs\StubResponseType; use PHPUnit\Framework\TestCase; use Zend\Diactoros\ServerRequest; @@ -62,7 +57,7 @@ public function testRespondToRequestValidAccessTokenWithHint() 'client_id' => 'foo', 'client_secret' => 'bar', 'token' => (string) $accessTokenJWT, - 'token_type_hint' => 'access_token' + 'token_type_hint' => 'access_token', ] ); @@ -100,7 +95,7 @@ public function testRespondToRequestValidAccessTokenWithoutHint() [ 'client_id' => 'foo', 'client_secret' => 'bar', - 'token' => (string) $accessTokenJWT + 'token' => (string) $accessTokenJWT, ] ); @@ -143,7 +138,7 @@ public function testRespondToRequestValidAccessTokenButCannotRevokeAccessTokens( 'client_id' => 'foo', 'client_secret' => 'bar', 'token' => (string) $accessTokenJWT, - 'token_type_hint' => 'access_token' + 'token_type_hint' => 'access_token', ] ); @@ -188,7 +183,7 @@ public function testRespondToRequestValidRefreshTokenWithHint() 'client_id' => 'foo', 'client_secret' => 'bar', 'token' => $refreshToken, - 'token_type_hint' => 'refresh_token' + 'token_type_hint' => 'refresh_token', ] ); @@ -277,7 +272,7 @@ public function testRespondToRequestValidRefreshTokenButCannotRevokeAccessTokens 'client_id' => 'foo', 'client_secret' => 'bar', 'token' => $refreshToken, - 'token_type_hint' => 'refresh_token' + 'token_type_hint' => 'refresh_token', ] ); @@ -307,7 +302,7 @@ public function testRespondToRequestMissingToken() $serverRequest = $serverRequest->withMethod('POST')->withParsedBody( [ 'client_id' => 'foo', - 'client_secret' => 'bar' + 'client_secret' => 'bar', ] ); @@ -341,7 +336,7 @@ public function testRespondToRequestInvalidRefreshToken() 'client_id' => 'foo', 'client_secret' => 'bar', 'token' => $refreshToken, - 'token_type_hint' => 'refresh_token' + 'token_type_hint' => 'refresh_token', ] ); @@ -390,7 +385,7 @@ public function testRespondToRequestClientMismatch() 'client_id' => 'foo', 'client_secret' => 'bar', 'token' => $refreshToken, - 'token_type_hint' => 'refresh_token' + 'token_type_hint' => 'refresh_token', ] ); @@ -435,7 +430,7 @@ public function testRespondToRequestExpiredRefreshToken() 'client_id' => 'foo', 'client_secret' => 'bar', 'token' => $refreshToken, - 'token_type_hint' => 'refresh_token' + 'token_type_hint' => 'refresh_token', ] ); @@ -480,7 +475,7 @@ public function testRespondToRequestRevokedRefreshToken() 'client_id' => 'foo', 'client_secret' => 'bar', 'token' => $refreshToken, - 'token_type_hint' => 'refresh_token' + 'token_type_hint' => 'refresh_token', ] ); From ff794eb7338884eeae5cdb30ce447080ca58d0b1 Mon Sep 17 00:00:00 2001 From: Jacob Weber Date: Fri, 8 Feb 2019 12:16:43 -0800 Subject: [PATCH 11/16] fix styles --- src/RevokeTokenHandler.php | 4 ++-- tests/RevokeTokenHandlerTest.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/RevokeTokenHandler.php b/src/RevokeTokenHandler.php index 01fb743b7..ee95c8aa8 100644 --- a/src/RevokeTokenHandler.php +++ b/src/RevokeTokenHandler.php @@ -122,8 +122,8 @@ public function setPublicKey(CryptKey $key) */ public function getIdentifier() { - return ''; - } + return ''; + } /** * Return a revoke token response. diff --git a/tests/RevokeTokenHandlerTest.php b/tests/RevokeTokenHandlerTest.php index 611538369..bd8819219 100644 --- a/tests/RevokeTokenHandlerTest.php +++ b/tests/RevokeTokenHandlerTest.php @@ -227,7 +227,7 @@ public function testRespondToRequestValidRefreshTokenWithoutHint() [ 'client_id' => 'foo', 'client_secret' => 'bar', - 'token' => $refreshToken + 'token' => $refreshToken, ] ); From 5d78435e932c2eded21115c12262ce6e8f825ad6 Mon Sep 17 00:00:00 2001 From: Jacob Weber Date: Fri, 8 Feb 2019 12:40:38 -0800 Subject: [PATCH 12/16] undo style change --- src/RevokeTokenHandler.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/RevokeTokenHandler.php b/src/RevokeTokenHandler.php index ee95c8aa8..1bc2eab3f 100644 --- a/src/RevokeTokenHandler.php +++ b/src/RevokeTokenHandler.php @@ -199,11 +199,11 @@ protected function readAsAccessToken($tokenParam, $clientId) $token = (new Parser())->parse($tokenParam); if ($token->verify(new Sha256(), $this->publicKey->getKeyPath()) === false) { - return; + return null; } } catch (Exception $exception) { // JWT couldn't be parsed so ignore - return; + return null; } $clientId = $token->getClaim('aud'); @@ -227,8 +227,8 @@ protected function readAsRefreshToken($tokenParam, $clientId) $refreshToken = $this->decrypt($tokenParam); $refreshTokenData = json_decode($refreshToken, true); } catch (Exception $e) { - return; // token couldn't be decrypted so ignore + return null; } if ($refreshTokenData['client_id'] !== $clientId) { From 42a9597d683e1a0420e9dc9de99bb7a6c2d68384 Mon Sep 17 00:00:00 2001 From: Jacob Weber Date: Wed, 13 Feb 2019 09:57:33 -0800 Subject: [PATCH 13/16] refactor to avoid returning null --- src/RevokeTokenHandler.php | 60 +++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 34 deletions(-) diff --git a/src/RevokeTokenHandler.php b/src/RevokeTokenHandler.php index 1bc2eab3f..20dbf2f6e 100644 --- a/src/RevokeTokenHandler.php +++ b/src/RevokeTokenHandler.php @@ -154,32 +154,14 @@ public function respondToRevokeTokenRequest(ServerRequestInterface $request, Res return $response; } - // Attempt to read token - $accessToken = null; - $refreshToken = null; + // Attempt to revoke tokens if ($hint === 'refresh_token') { - $refreshToken = $this->readAsRefreshToken($token, $clientId); - if ($refreshToken === null) { - $accessToken = $this->readAsAccessToken($token, $clientId); + if (!$this->revokeRefreshToken($token, $clientId)) { + $this->revokeAccessToken($token, $clientId); } } else { - $accessToken = $this->readAsAccessToken($token, $clientId); - if ($accessToken === null) { - $refreshToken = $this->readAsRefreshToken($token, $clientId); - } - } - - // Revoke tokens - if ($accessToken !== null) { - if (!$this->canRevokeAccessTokens) { - $errorMessage = 'The authorization server does not support the revocation of the presented token type'; - throw new OAuthServerException($errorMessage, 2, 'unsupported_token_type', 400); - } - $this->accessTokenRepository->revokeAccessToken($accessToken->getClaim('jti')); - } elseif ($refreshToken !== null) { - $this->refreshTokenRepository->revokeRefreshToken($refreshToken['refresh_token_id']); - if ($this->canRevokeAccessTokens) { - $this->accessTokenRepository->revokeAccessToken($refreshToken['access_token_id']); + if (!$this->revokeAccessToken($token, $clientId)) { + $this->revokeRefreshToken($token, $clientId); } } @@ -190,20 +172,20 @@ public function respondToRevokeTokenRequest(ServerRequestInterface $request, Res * @param string $tokenParam * @param string $clientId * - * @return null|Token + * @return bool true if token was a refresh token */ - protected function readAsAccessToken($tokenParam, $clientId) + protected function revokeAccessToken($tokenParam, $clientId) { $token = null; try { $token = (new Parser())->parse($tokenParam); if ($token->verify(new Sha256(), $this->publicKey->getKeyPath()) === false) { - return null; + return false; } } catch (Exception $exception) { - // JWT couldn't be parsed so ignore - return null; + // JWT couldn't be parsed as access token + return false; } $clientId = $token->getClaim('aud'); @@ -211,30 +193,40 @@ protected function readAsAccessToken($tokenParam, $clientId) throw OAuthServerException::invalidClient(); } - return $token; + if (!$this->canRevokeAccessTokens) { + $errorMessage = 'The authorization server does not support the revocation of the presented token type'; + throw new OAuthServerException($errorMessage, 2, 'unsupported_token_type', 400); + } + $this->accessTokenRepository->revokeAccessToken($token->getClaim('jti')); + + return true; } /** * @param string $tokenParam * @param string $clientId * - * @return null|array + * @return bool true if token was a refresh token */ - protected function readAsRefreshToken($tokenParam, $clientId) + protected function revokeRefreshToken($tokenParam, $clientId) { $refreshTokenData = null; try { $refreshToken = $this->decrypt($tokenParam); $refreshTokenData = json_decode($refreshToken, true); } catch (Exception $e) { - // token couldn't be decrypted so ignore - return null; + // token couldn't be decrypted as refresh token + return false; } if ($refreshTokenData['client_id'] !== $clientId) { throw OAuthServerException::invalidClient(); } - return $refreshTokenData; + $this->refreshTokenRepository->revokeRefreshToken($refreshTokenData['refresh_token_id']); + if ($this->canRevokeAccessTokens) { + $this->accessTokenRepository->revokeAccessToken($refreshTokenData['access_token_id']); + } + return true; } } From 2f7f1abe81eb030911d0210b9c7b4304374342f9 Mon Sep 17 00:00:00 2001 From: Jacob Weber Date: Wed, 13 Feb 2019 10:00:15 -0800 Subject: [PATCH 14/16] don't check for POST, since respondToAccessTokenRequest doesn't --- src/RevokeTokenHandler.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/RevokeTokenHandler.php b/src/RevokeTokenHandler.php index 20dbf2f6e..2413aff7d 100644 --- a/src/RevokeTokenHandler.php +++ b/src/RevokeTokenHandler.php @@ -138,11 +138,6 @@ public function getIdentifier() */ public function respondToRevokeTokenRequest(ServerRequestInterface $request, ResponseTypeInterface $response) { - if ($request->getMethod() !== 'POST') { - $errorMessage = 'POST method required.'; - throw new OAuthServerException($errorMessage, 3, 'invalid_request', 400); - } - $token = $this->getRequestParameter('token', $request); $hint = $this->getRequestParameter('token_type_hint', $request); From 668d14c448f06b2596ecfd9f67b1b539a26a5246 Mon Sep 17 00:00:00 2001 From: Jacob Weber Date: Wed, 13 Feb 2019 10:02:34 -0800 Subject: [PATCH 15/16] add @throws --- src/RevokeTokenHandler.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/RevokeTokenHandler.php b/src/RevokeTokenHandler.php index 2413aff7d..fa38b8a15 100644 --- a/src/RevokeTokenHandler.php +++ b/src/RevokeTokenHandler.php @@ -167,6 +167,8 @@ public function respondToRevokeTokenRequest(ServerRequestInterface $request, Res * @param string $tokenParam * @param string $clientId * + * @throws OAuthServerException + * * @return bool true if token was a refresh token */ protected function revokeAccessToken($tokenParam, $clientId) @@ -201,6 +203,8 @@ protected function revokeAccessToken($tokenParam, $clientId) * @param string $tokenParam * @param string $clientId * + * @throws OAuthServerException + * * @return bool true if token was a refresh token */ protected function revokeRefreshToken($tokenParam, $clientId) From e4b216f22151d16d145b353b70cdba5a5a9a8569 Mon Sep 17 00:00:00 2001 From: Jacob Weber Date: Wed, 13 Feb 2019 10:03:37 -0800 Subject: [PATCH 16/16] fix styles --- src/RevokeTokenHandler.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/RevokeTokenHandler.php b/src/RevokeTokenHandler.php index fa38b8a15..d50df5069 100644 --- a/src/RevokeTokenHandler.php +++ b/src/RevokeTokenHandler.php @@ -226,6 +226,7 @@ protected function revokeRefreshToken($tokenParam, $clientId) if ($this->canRevokeAccessTokens) { $this->accessTokenRepository->revokeAccessToken($refreshTokenData['access_token_id']); } + return true; } }