diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index b0309290..09ef4ec5 100644 --- a/src/Exception/OAuthServerException.php +++ b/src/Exception/OAuthServerException.php @@ -2,10 +2,10 @@ namespace League\OAuth2\Server\Exception; -use League\OAuth2\Server\Utils\RedirectUri; use Psr\Http\Message\ResponseInterface; use Zend\Diactoros\Response; use Zend\Diactoros\ServerRequest; +use Zend\Diactoros\Uri; class OAuthServerException extends \Exception { @@ -184,6 +184,7 @@ class OAuthServerException extends \Exception /** * Invalid refresh token * + * @param string|null $hint * @return static */ public static function invalidRefreshToken($hint = null) @@ -236,16 +237,21 @@ class OAuthServerException extends \Exception } if ($this->redirectUri !== null) { - $headers['Location'] = RedirectUri::make($this->redirectUri, $payload); + $redirectUri = new Uri($this->redirectUri); + parse_str($redirectUri->getQuery(), $redirectPayload); + + $headers['Location'] = (string) $redirectUri->withQuery(http_build_query( + array_merge($redirectPayload, $payload) + )); } foreach ($headers as $header => $content) { $response = $response->withHeader($header, $content); } - $response = $response->withStatus($this->getHttpStatusCode()); $response->getBody()->write(json_encode($payload)); - return $response; + + return $response->withStatus($this->getHttpStatusCode()); } /** diff --git a/src/Exception/UnauthorizedClientException.php b/src/Exception/UnauthorizedClientException.php deleted file mode 100644 index fd1f18c3..00000000 --- a/src/Exception/UnauthorizedClientException.php +++ /dev/null @@ -1,36 +0,0 @@ - - * @copyright Copyright (c) Alex Bilbie - * @license http://mit-license.org/ - * @link https://github.com/thephpleague/oauth2-server - */ - -namespace League\OAuth2\Server\Exception; - -/** - * Exception class - */ -class UnauthorizedClientException extends OAuthException -{ - /** - * {@inheritdoc} - */ - public $httpStatusCode = 400; - - /** - * {@inheritdoc} - */ - public $errorType = 'unauthorized_client'; - - /** - * {@inheritdoc} - */ - public function __construct() - { - parent::__construct('The client is not authorized to request an access token using this method.'); - } -} diff --git a/src/Exception/UnsupportedResponseTypeException.php b/src/Exception/UnsupportedResponseTypeException.php deleted file mode 100644 index 8707f0c5..00000000 --- a/src/Exception/UnsupportedResponseTypeException.php +++ /dev/null @@ -1,37 +0,0 @@ - - * @copyright Copyright (c) Alex Bilbie - * @license http://mit-license.org/ - * @link https://github.com/thephpleague/oauth2-server - */ - -namespace League\OAuth2\Server\Exception; - -/** - * Exception class - */ -class UnsupportedResponseTypeException extends OAuthException -{ - /** - * {@inheritdoc} - */ - public $httpStatusCode = 400; - - /** - * {@inheritdoc} - */ - public $errorType = 'unsupported_response_type'; - - /** - * {@inheritdoc} - */ - public function __construct($parameter, $redirectUri = null) - { - parent::__construct('The authorization server does not support obtaining an access token using this method.'); - $this->redirectUri = $redirectUri; - } -} diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 490ecf4c..c6ffc760 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -80,6 +80,11 @@ abstract class AbstractGrant implements GrantTypeInterface */ protected $pathToPublicKey; + /** + * @var \DateInterval + */ + protected $refreshTokenTTL; + /** * @param ClientRepositoryInterface $clientRepository */ @@ -128,6 +133,14 @@ abstract class AbstractGrant implements GrantTypeInterface $this->emitter = $emitter; } + /** + * @inheritdoc + */ + public function setRefreshTokenTTL(\DateInterval $refreshTokenTTL) + { + $this->refreshTokenTTL = $refreshTokenTTL; + } + /** * {@inheritdoc} */ @@ -291,7 +304,7 @@ abstract class AbstractGrant implements GrantTypeInterface { $refreshToken = new RefreshTokenEntity(); $refreshToken->setIdentifier(SecureKey::generate()); - $refreshToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('P1M'))); + $refreshToken->setExpiryDateTime((new \DateTime())->add($this->refreshTokenTTL)); $refreshToken->setAccessToken($accessToken); return $refreshToken; diff --git a/src/Grant/ClientCredentialsGrant.php b/src/Grant/ClientCredentialsGrant.php index 03c6c721..918586f9 100644 --- a/src/Grant/ClientCredentialsGrant.php +++ b/src/Grant/ClientCredentialsGrant.php @@ -32,14 +32,14 @@ class ClientCredentialsGrant extends AbstractGrant public function respondToRequest( ServerRequestInterface $request, ResponseTypeInterface $responseType, - \DateInterval $tokenTTL + \DateInterval $accessTokenTTL ) { // Validate request $client = $this->validateClient($request); $scopes = $this->validateScopes($request, $client); // Issue and persist access token - $accessToken = $this->issueAccessToken($tokenTTL, $client, $client->getIdentifier(), $scopes); + $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $client->getIdentifier(), $scopes); $this->accessTokenRepository->persistNewAccessToken($accessToken); // Inject access token into response type diff --git a/src/Grant/GrantTypeInterface.php b/src/Grant/GrantTypeInterface.php index 5bc9bf08..a6a5c63a 100644 --- a/src/Grant/GrantTypeInterface.php +++ b/src/Grant/GrantTypeInterface.php @@ -11,7 +11,6 @@ namespace League\OAuth2\Server\Grant; -use DateInterval; use League\Event\EmitterInterface; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; @@ -24,6 +23,13 @@ use Psr\Http\Message\ServerRequestInterface; */ interface GrantTypeInterface { + /** + * Set refresh token TTL + * + * @param \DateInterval $refreshTokenTTL + */ + public function setRefreshTokenTTL(\DateInterval $refreshTokenTTL); + /** * Return the identifier * @@ -43,14 +49,14 @@ interface GrantTypeInterface * * @param \Psr\Http\Message\ServerRequestInterface $request * @param \League\OAuth2\Server\ResponseTypes\ResponseTypeInterface $responseType - * @param \DateInterval $tokenTTL + * @param \DateInterval $accessTokenTTL * * @return \League\OAuth2\Server\ResponseTypes\ResponseTypeInterface */ public function respondToRequest( ServerRequestInterface $request, ResponseTypeInterface $responseType, - DateInterval $tokenTTL + \DateInterval $accessTokenTTL ); /** diff --git a/src/Grant/PasswordGrant.php b/src/Grant/PasswordGrant.php index 50593ef3..9f4f41e8 100644 --- a/src/Grant/PasswordGrant.php +++ b/src/Grant/PasswordGrant.php @@ -51,6 +51,8 @@ class PasswordGrant extends AbstractGrant ) { $this->userRepository = $userRepository; $this->refreshTokenRepository = $refreshTokenRepository; + + $this->refreshTokenTTL = new \DateInterval('P1M'); } /** @@ -59,7 +61,7 @@ class PasswordGrant extends AbstractGrant public function respondToRequest( ServerRequestInterface $request, ResponseTypeInterface $responseType, - \DateInterval $tokenTTL + \DateInterval $accessTokenTTL ) { // Validate request $client = $this->validateClient($request); @@ -67,7 +69,7 @@ class PasswordGrant extends AbstractGrant $scopes = $this->validateScopes($request, $client); // Issue and persist new tokens - $accessToken = $this->issueAccessToken($tokenTTL, $client, $user->getIdentifier(), $scopes); + $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $user->getIdentifier(), $scopes); $refreshToken = $this->issueRefreshToken($accessToken); $this->accessTokenRepository->persistNewAccessToken($accessToken); $this->refreshTokenRepository->persistNewRefreshToken($refreshToken); diff --git a/src/Grant/RefreshTokenGrant.php b/src/Grant/RefreshTokenGrant.php index d8348d25..cf3286c8 100644 --- a/src/Grant/RefreshTokenGrant.php +++ b/src/Grant/RefreshTokenGrant.php @@ -42,6 +42,8 @@ class RefreshTokenGrant extends AbstractGrant RefreshTokenRepositoryInterface $refreshTokenRepository ) { $this->refreshTokenRepository = $refreshTokenRepository; + + $this->refreshTokenTTL = new \DateInterval('P1M'); } /** @@ -50,8 +52,9 @@ class RefreshTokenGrant extends AbstractGrant public function respondToRequest( ServerRequestInterface $request, ResponseTypeInterface $responseType, - \DateInterval $tokenTTL + \DateInterval $accessTokenTTL ) { + // Validate request $client = $this->validateClient($request); $oldRefreshToken = $this->validateOldRefreshToken($request, $client->getIdentifier()); $scopes = $this->validateScopes($request, $client); @@ -75,9 +78,9 @@ class RefreshTokenGrant extends AbstractGrant $this->accessTokenRepository->revokeAccessToken($oldRefreshToken['access_token_id']); $this->refreshTokenRepository->revokeRefreshToken($oldRefreshToken['refresh_token_id']); - $accessToken = $this->issueAccessToken($tokenTTL, $client, $oldRefreshToken['user_id'], $scopes); + // Issue and persist new tokens + $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $oldRefreshToken['user_id'], $scopes); $refreshToken = $this->issueRefreshToken($accessToken); - $this->accessTokenRepository->persistNewAccessToken($accessToken); $this->refreshTokenRepository->persistNewRefreshToken($refreshToken); diff --git a/src/Middleware/AuthenticationServerMiddleware.php b/src/Middleware/AuthenticationServerMiddleware.php index 28bd39cb..14ac1c32 100644 --- a/src/Middleware/AuthenticationServerMiddleware.php +++ b/src/Middleware/AuthenticationServerMiddleware.php @@ -34,11 +34,13 @@ class AuthenticationServerMiddleware public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next) { try { - $response = $server->respondToRequest($request, $response); + $response = $this->server->respondToRequest($request, $response); } catch (OAuthServerException $exception) { return $exception->generateHttpResponse($response); } catch (\Exception $exception) { - return $response->withStatus(500)->write($exception->getMessage()); + $response->getBody()->write($exception->getMessage()); + + return $response->withStatus(500); } if (in_array($response->getStatusCode(), [400, 401, 500])) { diff --git a/src/Middleware/ResourceServerMiddleware.php b/src/Middleware/ResourceServerMiddleware.php index 874a14c6..1794cdce 100644 --- a/src/Middleware/ResourceServerMiddleware.php +++ b/src/Middleware/ResourceServerMiddleware.php @@ -33,18 +33,14 @@ class ResourceServerMiddleware */ public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next) { - if ($request->hasHeader('authorization') === false) { - $exception = OAuthServerException::accessDenied('Missing authorization header'); - + try { + $request = $this->server->getResponseType()->determineAccessTokenInHeader($request); + } catch (OAuthServerException $exception) { return $exception->generateHttpResponse($response); - } + } catch (\Exception $exception) { + $response->getBody()->write($exception->getMessage()); - $request = $this->server->getResponseType()->determineAccessTokenInHeader($request); - - if ($request->getAttribute('oauth_access_token') === null) { - $exception = OAuthServerException::accessDenied($request->getAttribute('oauth_access_token_error')); - - return $exception->generateHttpResponse($response); + return $response->withStatus(500); } // Pass the request and response on to the next responder in the chain diff --git a/src/ResponseTypes/AbstractResponseType.php b/src/ResponseTypes/AbstractResponseType.php index 2388582f..9605a463 100644 --- a/src/ResponseTypes/AbstractResponseType.php +++ b/src/ResponseTypes/AbstractResponseType.php @@ -13,7 +13,9 @@ namespace League\OAuth2\Server\ResponseTypes; use League\OAuth2\Server\Entities\Interfaces\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\Interfaces\RefreshTokenEntityInterface; +use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; +use Psr\Http\Message\ServerRequestInterface; abstract class AbstractResponseType implements ResponseTypeInterface { @@ -66,10 +68,22 @@ abstract class AbstractResponseType implements ResponseTypeInterface } /** - * @param \League\OAuth2\Server\Entities\Interfaces\RefreshTokenEntityInterface $refreshToken + * {@inheritdoc} */ public function setRefreshToken(RefreshTokenEntityInterface $refreshToken) { $this->refreshToken = $refreshToken; } + + /** + * {@inheritdoc} + */ + public function determineAccessTokenInHeader(ServerRequestInterface $request) + { + if ($request->hasHeader('authorization') === false) { + throw OAuthServerException::accessDenied('Missing "Authorization" header'); + } + + return $request; + } } diff --git a/src/ResponseTypes/BearerTokenResponse.php b/src/ResponseTypes/BearerTokenResponse.php index 278b166d..c67bc990 100644 --- a/src/ResponseTypes/BearerTokenResponse.php +++ b/src/ResponseTypes/BearerTokenResponse.php @@ -16,10 +16,10 @@ use Lcobucci\JWT\Parser; use Lcobucci\JWT\Signer\Key; use Lcobucci\JWT\Signer\Rsa\Sha256; use League\OAuth2\Server\Entities\Interfaces\RefreshTokenEntityInterface; +use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Utils\KeyCrypt; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Zend\Diactoros\Response; class BearerTokenResponse extends AbstractResponseType { @@ -78,6 +78,8 @@ class BearerTokenResponse extends AbstractResponseType */ public function determineAccessTokenInHeader(ServerRequestInterface $request) { + $request = parent::determineAccessTokenInHeader($request); + $header = $request->getHeader('authorization'); $jwt = trim(preg_replace('/^(?:\s+)?Bearer\s/', '', $header[0])); @@ -85,12 +87,12 @@ class BearerTokenResponse extends AbstractResponseType // Attempt to parse and validate the JWT $token = (new Parser())->parse($jwt); if ($token->verify(new Sha256(), $this->pathToPublicKey) === false) { - return $request->withAttribute('oauth_access_token_error', 'Access token could not be verified'); + throw OAuthServerException::accessDenied('Access token could not be verified'); } // Check if token has been revoked if ($this->accessTokenRepository->isAccessTokenRevoked($token->getClaim('jti'))) { - return $request->withAttribute('oauth_access_token_error', 'Access token has been revoked'); + throw OAuthServerException::accessDenied('Access token has been revoked'); } // Return the request with additional attributes @@ -98,9 +100,9 @@ class BearerTokenResponse extends AbstractResponseType ->withAttribute('oauth_client_id', $token->getClaim('aud')) ->withAttribute('oauth_user_id', $token->getClaim('sub')) ->withAttribute('oauth_scopes', $token->getClaim('scopes')); - } catch (\InvalidArgumentException $e) { + } catch (\InvalidArgumentException $exception) { // JWT couldn't be parsed so return the request as is - return $request->withAttribute('oauth_access_token_error', $e->getMessage()); + throw OAuthServerException::accessDenied($exception->getMessage()); } } } diff --git a/src/Server.php b/src/Server.php index a62e8b32..cc7eb320 100644 --- a/src/Server.php +++ b/src/Server.php @@ -26,11 +26,6 @@ class Server implements EmitterAwareInterface */ protected $enabledGrantTypes = []; - /** - * @var ResponseTypeInterface[] - */ - protected $grantResponseTypes = []; - /** * @var DateInterval[] */ @@ -92,47 +87,23 @@ class Server implements EmitterAwareInterface $this->responseType = $responseType; } - /** - * Get the token type that grants will return in the HTTP response - * - * @return ResponseTypeInterface - */ - public function getResponseType() - { - if (!$this->responseType instanceof ResponseTypeInterface) { - $this->responseType = new BearerTokenResponse( - $this->privateKeyPath, - $this->publicKeyPath, - $this->accessTokenRepository - ); - } - - return $this->responseType; - } - /** * Enable a grant type on the server * * @param \League\OAuth2\Server\Grant\GrantTypeInterface $grantType * @param DateInterval $accessTokenTTL */ - public function enableGrantType( - GrantTypeInterface $grantType, - \DateInterval $accessTokenTTL - ) { + public function enableGrantType(GrantTypeInterface $grantType, \DateInterval $accessTokenTTL) + { $grantType->setAccessTokenRepository($this->accessTokenRepository); $grantType->setClientRepository($this->clientRepository); $grantType->setScopeRepository($this->scopeRepository); $grantType->setPathToPrivateKey($this->privateKeyPath); $grantType->setPathToPublicKey($this->publicKeyPath); - $grantType->setEmitter($this->getEmitter()); + $this->enabledGrantTypes[$grantType->getIdentifier()] = $grantType; - // Set grant response type - $this->grantResponseTypes[$grantType->getIdentifier()] = $this->getResponseType(); - - // Set grant access token TTL $this->grantTypeAccessTokenTTL[$grantType->getIdentifier()] = $accessTokenTTL; } @@ -160,7 +131,7 @@ class Server implements EmitterAwareInterface if ($grantType->canRespondToRequest($request)) { $tokenResponse = $grantType->respondToRequest( $request, - $this->grantResponseTypes[$grantType->getIdentifier()], + $this->getResponseType(), $this->grantTypeAccessTokenTTL[$grantType->getIdentifier()] ); } @@ -172,4 +143,22 @@ class Server implements EmitterAwareInterface return $tokenResponse->generateHttpResponse($response); } + + /** + * Get the token type that grants will return in the HTTP response + * + * @return ResponseTypeInterface + */ + public function getResponseType() + { + if (!$this->responseType instanceof ResponseTypeInterface) { + $this->responseType = new BearerTokenResponse( + $this->privateKeyPath, + $this->publicKeyPath, + $this->accessTokenRepository + ); + } + + return $this->responseType; + } } diff --git a/src/Utils/RedirectUri.php b/src/Utils/RedirectUri.php deleted file mode 100644 index d00f29cc..00000000 --- a/src/Utils/RedirectUri.php +++ /dev/null @@ -1,34 +0,0 @@ - - * @copyright Copyright (c) Alex Bilbie - * @license http://mit-license.org/ - * @link https://github.com/thephpleague/oauth2-server - */ - -namespace League\OAuth2\Server\Utils; - -/** - * RedirectUri class - */ -class RedirectUri -{ - /** - * Generate a new redirect uri - * - * @param string $uri The base URI - * @param array $params The query string parameters - * @param string $queryDelimiter The query string delimiter (default: "?") - * - * @return string The updated URI - */ - public static function make($uri, $params = [], $queryDelimiter = '?') - { - $uri .= (strstr($uri, $queryDelimiter) === false) ? $queryDelimiter : '&'; - - return $uri . http_build_query($params); - } -}