Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add token revocation support (RFC 7009) #1135

Open
wants to merge 27 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6af4d04
move validation methods into trait
jacobweber Feb 8, 2019
8862eb1
add revoke token handler
jacobweber Feb 8, 2019
7332132
use ResponseTypeInterface
jacobweber Feb 8, 2019
ddef1d4
update error types
jacobweber Feb 8, 2019
7864957
add validation with public key
jacobweber Feb 8, 2019
240d52d
remove dead code
jacobweber Feb 8, 2019
2e80215
use string for identifier
jacobweber Feb 8, 2019
9a40529
add tests
jacobweber Feb 8, 2019
1e8b936
if we can't read token using hint, try other token type
jacobweber Feb 8, 2019
a72240c
fix styles
jacobweber Feb 8, 2019
ff794eb
fix styles
jacobweber Feb 8, 2019
5d78435
undo style change
jacobweber Feb 8, 2019
42a9597
refactor to avoid returning null
jacobweber Feb 13, 2019
2f7f1ab
don't check for POST, since respondToAccessTokenRequest doesn't
jacobweber Feb 13, 2019
668d14c
add @throws
jacobweber Feb 13, 2019
e4b216f
fix styles
jacobweber Feb 13, 2019
3e9c7d4
Merge remote-tracking branch 'upstream/master' into revokeToken
tomsisk Jun 19, 2020
99f4020
fix tests
tomsisk Jun 19, 2020
c843cb7
use psr response
tomsisk Jun 22, 2020
e087aaa
switch back to ResponseTypeInterface
tomsisk Aug 27, 2020
be1a0d1
update methods in trait to latest
tomsisk Aug 27, 2020
ff62db8
test fixes
tomsisk Aug 27, 2020
3d736d3
typehint fix
tomsisk Aug 27, 2020
d82dfc0
Merge remote-tracking branch 'upstream/master' into revokeToken
tomsisk Sep 30, 2020
db9d98c
update tests
tomsisk Sep 30, 2020
f4ea8e4
style fixes
tomsisk Sep 30, 2020
8f6b7a9
more style
tomsisk Sep 30, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions src/AuthorizationServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ class AuthorizationServer implements EmitterAwareInterface
*/
protected $enabledGrantTypes = [];

/**
* @var RevokeTokenHandler
*/
protected $revokeTokenHandler = null;

/**
* @var DateInterval[]
*/
Expand Down Expand Up @@ -206,6 +211,45 @@ 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) {
$revokeResponse = $this->revokeTokenHandler->respondToRevokeTokenRequest($request, $this->getResponseType());

if ($revokeResponse instanceof ResponseTypeInterface) {
return $revokeResponse->generateHttpResponse($response);
}
}

$errorMessage = 'Token revocation not supported.';
throw new OAuthServerException($errorMessage, 3, 'invalid_request', 400);
}

/**
* Get the token type that grants will return in the HTTP response.
*
Expand Down
212 changes: 10 additions & 202 deletions src/Grant/AbstractGrant.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,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;
Expand All @@ -41,7 +41,7 @@
*/
abstract class AbstractGrant implements GrantTypeInterface
{
use EmitterAwareTrait, CryptTrait;
use EmitterAwareTrait, CryptTrait, RequestValidatorTrait;

const SCOPE_DELIMITER_STRING = ' ';

Expand Down Expand Up @@ -92,6 +92,14 @@ abstract class AbstractGrant implements GrantTypeInterface
*/
protected $defaultScope;

/**
* @return ClientRepositoryInterface
*/
public function getClientRepository()
{
return $this->clientRepository;
}

/**
* @param ClientRepositoryInterface $clientRepository
*/
Expand Down Expand Up @@ -166,115 +174,6 @@ public function setDefaultScope($scope)
$this->defaultScope = $scope;
}

/**
* Validate the client.
*
* @param ServerRequestInterface $request
*
* @throws OAuthServerException
*
* @return ClientEntityInterface
*/
protected function validateClient(ServerRequestInterface $request)
{
list($clientId, $clientSecret) = $this->getClientCredentials($request);

if ($this->clientRepository->validateClient($clientId, $clientSecret, $this->getIdentifier()) === false) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));

throw OAuthServerException::invalidClient($request);
}

$client = $this->getClientEntityOrFail($clientId, $request);

// If a redirect URI is provided ensure it matches what is pre-registered
$redirectUri = $this->getRequestParameter('redirect_uri', $request, null);

if ($redirectUri !== null) {
$this->validateRedirectUri($redirectUri, $client, $request);
}

return $client;
}

/**
* Wrapper around ClientRepository::getClientEntity() that ensures we emit
* an event and throw an exception if the repo doesn't return a client
* entity.
*
* This is a bit of defensive coding because the interface contract
* doesn't actually enforce non-null returns/exception-on-no-client so
* getClientEntity might return null. By contrast, this method will
* always either return a ClientEntityInterface or throw.
*
* @param string $clientId
* @param ServerRequestInterface $request
*
* @return ClientEntityInterface
*/
protected function getClientEntityOrFail($clientId, ServerRequestInterface $request)
{
$client = $this->clientRepository->getClientEntity($clientId);

if ($client instanceof ClientEntityInterface === false || empty($client->getRedirectUri())) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidClient($request);
}

return $client;
}

/**
* Gets the client credentials from the request from the request body or
* the Http Basic Authorization header
*
* @param ServerRequestInterface $request
*
* @return array
*/
protected function getClientCredentials(ServerRequestInterface $request)
{
list($basicAuthUser, $basicAuthPassword) = $this->getBasicAuthCredentials($request);

$clientId = $this->getRequestParameter('client_id', $request, $basicAuthUser);

if (\is_null($clientId)) {
throw OAuthServerException::invalidRequest('client_id');
}

$clientSecret = $this->getRequestParameter('client_secret', $request, $basicAuthPassword);

return [$clientId, $clientSecret];
}

/**
* 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($request);
} 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($request);
}
}

/**
* Validate scopes in the request.
*
Expand Down Expand Up @@ -320,97 +219,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.
*
Expand Down
Loading