diff --git a/composer.json b/composer.json index 1834d999..c5a6cbbd 100644 --- a/composer.json +++ b/composer.json @@ -6,9 +6,9 @@ "require": { "php": ">=5.5.9", "league/event": "^2.1", - "zendframework/zend-diactoros": "^1.1", "lcobucci/jwt": "^3.1", - "paragonie/random_compat": "^1.1" + "paragonie/random_compat": "^1.1", + "psr/http-message": "^1.0" }, "require-dev": { "phpunit/phpunit": "^4.8", diff --git a/src/AuthorizationValidators/AuthorizationValidatorInterface.php b/src/AuthorizationValidators/AuthorizationValidatorInterface.php new file mode 100644 index 00000000..2ed2a1ee --- /dev/null +++ b/src/AuthorizationValidators/AuthorizationValidatorInterface.php @@ -0,0 +1,18 @@ +accessTokenRepository = $accessTokenRepository; + } + + /** + * {@inheritdoc} + */ + public function validateAuthorization(ServerRequestInterface $request) + { + if ($request->hasHeader('authorization') === false) { + throw OAuthServerException::accessDenied('Missing "Authorization" header'); + } + + $header = $request->getHeader('authorization'); + $jwt = trim(preg_replace('/^(?:\s+)?Bearer\s/', '', $header[0])); + + try { + // Attempt to parse and validate the JWT + $token = (new Parser())->parse($jwt); + if ($token->verify(new Sha256(), $this->publicKeyPath) === false) { + throw OAuthServerException::accessDenied('Access token could not be verified'); + } + + // Check if token has been revoked + if ($this->accessTokenRepository->isAccessTokenRevoked($token->getClaim('jti'))) { + throw OAuthServerException::accessDenied('Access token has been revoked'); + } + + // Return the request with additional attributes + return $request + ->withAttribute('oauth_access_token_id', $token->getClaim('jti')) + ->withAttribute('oauth_client_id', $token->getClaim('aud')) + ->withAttribute('oauth_user_id', $token->getClaim('sub')) + ->withAttribute('oauth_scopes', $token->getClaim('scopes')); + } catch (\InvalidArgumentException $exception) { + // JWT couldn't be parsed so return the request as is + throw OAuthServerException::accessDenied($exception->getMessage()); + } + } +} \ No newline at end of file diff --git a/src/Utils/KeyCrypt.php b/src/CryptTrait.php similarity index 64% rename from src/Utils/KeyCrypt.php rename to src/CryptTrait.php index 48659bde..3c649762 100644 --- a/src/Utils/KeyCrypt.php +++ b/src/CryptTrait.php @@ -8,24 +8,61 @@ * * @link https://github.com/thephpleague/oauth2-server */ -namespace League\OAuth2\Server\Utils; +namespace League\OAuth2\Server; -class KeyCrypt +trait CryptTrait { + /** + * @var string + */ + protected $privateKeyPath; + + /** + * @var string + */ + protected $publicKeyPath; + + /** + * Set path to private key. + * + * @param string $privateKeyPath + */ + public function setPrivateKeyPath($privateKeyPath) + { + if (strpos($privateKeyPath, 'file://') !== 0) { + $privateKeyPath = 'file://' . $privateKeyPath; + } + + $this->privateKeyPath = $privateKeyPath; + } + + /** + * Set path to public key. + * + * @param string $publicKeyPath + */ + public function setPublicKeyPath($publicKeyPath) + { + if (strpos($publicKeyPath, 'file://') !== 0) { + $publicKeyPath = 'file://' . $publicKeyPath; + } + + $this->publicKeyPath = $publicKeyPath; + } + /** * Encrypt data with a private key. * * @param string $unencryptedData - * @param string $pathToPrivateKey * * @return string */ - public static function encrypt($unencryptedData, $pathToPrivateKey) + protected function encrypt($unencryptedData) { - $privateKey = openssl_pkey_get_private($pathToPrivateKey); + $privateKey = openssl_pkey_get_private($this->privateKeyPath); $privateKeyDetails = @openssl_pkey_get_details($privateKey); if ($privateKeyDetails === null) { - throw new \LogicException(sprintf('Could not get details of private key: %s', $pathToPrivateKey)); + throw new \LogicException(sprintf('Could not get details of private key: %s', $this->privateKeyPath)); } $chunkSize = ceil($privateKeyDetails['bits'] / 8) - 11; @@ -50,18 +87,17 @@ class KeyCrypt * Decrypt data with a public key. * * @param string $encryptedData - * @param string $pathToPublicKey * * @throws \LogicException * * @return string */ - public static function decrypt($encryptedData, $pathToPublicKey) + protected function decrypt($encryptedData) { - $publicKey = openssl_pkey_get_public($pathToPublicKey); + $publicKey = openssl_pkey_get_public($this->publicKeyPath); $publicKeyDetails = @openssl_pkey_get_details($publicKey); if ($publicKeyDetails === null) { - throw new \LogicException(sprintf('Could not get details of public key: %s', $pathToPublicKey)); + throw new \LogicException(sprintf('Could not get details of public key: %s', $this->publicKeyPath)); } $chunkSize = ceil($publicKeyDetails['bits'] / 8); @@ -72,7 +108,7 @@ class KeyCrypt while ($encryptedData) { $chunk = substr($encryptedData, 0, $chunkSize); $encryptedData = substr($encryptedData, $chunkSize); - if (openssl_public_decrypt($chunk, $decrypted, $publicKey) === false) { + if (openssl_public_decrypt($chunk, $decrypted, $publicKey, OPENSSL_PKCS1_OAEP_PADDING) === false) { // @codeCoverageIgnoreStart throw new \LogicException('Failed to decrypt data'); // @codeCoverageIgnoreEnd @@ -83,4 +119,4 @@ class KeyCrypt return $output; } -} +} \ No newline at end of file diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index b61d1183..1f08919c 100644 --- a/src/Exception/OAuthServerException.php +++ b/src/Exception/OAuthServerException.php @@ -3,9 +3,6 @@ namespace League\OAuth2\Server\Exception; use Psr\Http\Message\ResponseInterface; -use Zend\Diactoros\Response; -use Zend\Diactoros\ServerRequest; -use Zend\Diactoros\Uri; class OAuthServerException extends \Exception { @@ -183,12 +180,8 @@ class OAuthServerException extends \Exception * * @return \Psr\Http\Message\ResponseInterface */ - public function generateHttpResponse(ResponseInterface $response = null, $useFragment = false) + public function generateHttpResponse(ResponseInterface $response, $useFragment = false) { - if (!$response instanceof ResponseInterface) { - $response = new Response(); - } - $headers = $this->getHttpHeaders(); $payload = [ @@ -201,18 +194,13 @@ class OAuthServerException extends \Exception } if ($this->redirectUri !== null) { - $redirectUri = new Uri($this->redirectUri); - parse_str($redirectUri->getQuery(), $redirectPayload); - if ($useFragment === true) { - $headers['Location'] = (string) $redirectUri->withFragment(http_build_query( - array_merge($redirectPayload, $payload) - )); + $this->redirectUri .= (strstr($this->redirectUri, '#') === false) ? '#' : '&'; } else { - $headers['Location'] = (string) $redirectUri->withQuery(http_build_query( - array_merge($redirectPayload, $payload) - )); + $this->redirectUri .= (strstr($this->redirectUri, '?') === false) ? '?' : '&'; } + + return $response->withStatus(302)->withHeader('Location', $this->redirectUri . http_build_query($payload)); } foreach ($headers as $header => $content) { @@ -245,29 +233,14 @@ class OAuthServerException extends \Exception // matching the authentication scheme used by the client. // @codeCoverageIgnoreStart if ($this->errorType === 'invalid_client') { - $authScheme = null; - $request = new ServerRequest(); - if (isset($request->getServerParams()['PHP_AUTH_USER']) && - $request->getServerParams()['PHP_AUTH_USER'] !== null + $authScheme = 'Basic'; + if (array_key_exists('HTTP_AUTHORIZATION', $_SERVER) !== false + && strpos($_SERVER['HTTP_AUTHORIZATION'], 'Bearer') === 0 ) { - $authScheme = 'Basic'; - } else { - $authHeader = $request->getHeader('authorization'); - if ($authHeader !== []) { - if (strpos($authHeader[0], 'Bearer') === 0) { - $authScheme = 'Bearer'; - } elseif (strpos($authHeader[0], 'MAC') === 0) { - $authScheme = 'MAC'; - } elseif (strpos($authHeader[0], 'Basic') === 0) { - $authScheme = 'Basic'; - } - } - } - if ($authScheme !== null) { - $headers[] = 'WWW-Authenticate: ' . $authScheme . ' realm="OAuth"'; + $authScheme = 'Bearer'; } + $headers[] = 'WWW-Authenticate: ' . $authScheme . ' realm="OAuth"'; } - // @codeCoverageIgnoreEnd return $headers; } diff --git a/src/Grant/AbstractAuthorizeGrant.php b/src/Grant/AbstractAuthorizeGrant.php index bab3ee01..7c08dccf 100644 --- a/src/Grant/AbstractAuthorizeGrant.php +++ b/src/Grant/AbstractAuthorizeGrant.php @@ -39,4 +39,17 @@ abstract class AbstractAuthorizeGrant extends AbstractGrant return $this->templateRenderer; } + + /** + * @param string $uri + * @param array $params + * @param string $queryDelimiter + * + * @return string + */ + public function makeRedirectUri($uri, $params = [], $queryDelimiter = '?') + { + $uri .= (strstr($uri, $queryDelimiter) === false) ? $queryDelimiter : '&'; + return $uri . http_build_query($params); + } } diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 6b3f21ba..49b03741 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -12,6 +12,7 @@ namespace League\OAuth2\Server\Grant; use League\Event\EmitterAwareTrait; use League\Event\Event; +use League\OAuth2\Server\CryptTrait; use League\OAuth2\Server\Entities\AccessTokenEntity; use League\OAuth2\Server\Entities\AuthCodeEntity; use League\OAuth2\Server\Entities\Interfaces\AccessTokenEntityInterface; @@ -32,7 +33,7 @@ use Psr\Http\Message\ServerRequestInterface; */ abstract class AbstractGrant implements GrantTypeInterface { - use EmitterAwareTrait; + use EmitterAwareTrait, CryptTrait; const SCOPE_DELIMITER_STRING = ' '; @@ -71,16 +72,6 @@ abstract class AbstractGrant implements GrantTypeInterface */ protected $userRepository; - /** - * @var string - */ - protected $pathToPrivateKey; - - /** - * @var string - */ - protected $pathToPublicKey; - /** * @var \DateInterval */ @@ -134,22 +125,6 @@ abstract class AbstractGrant implements GrantTypeInterface $this->userRepository = $userRepository; } - /** - * @param string $pathToPrivateKey - */ - public function setPathToPrivateKey($pathToPrivateKey) - { - $this->pathToPrivateKey = $pathToPrivateKey; - } - - /** - * @param string $pathToPublicKey - */ - public function setPathToPublicKey($pathToPublicKey) - { - $this->pathToPublicKey = $pathToPublicKey; - } - /** * {@inheritdoc} */ diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index a3fb2e34..10ccb1e8 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -10,12 +10,11 @@ use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\Repositories\UserRepositoryInterface; +use League\OAuth2\Server\ResponseTypes\HtmlResponse; +use League\OAuth2\Server\ResponseTypes\RedirectResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use League\OAuth2\Server\TemplateRenderer\AbstractRenderer; -use League\OAuth2\Server\Utils\KeyCrypt; use Psr\Http\Message\ServerRequestInterface; -use Zend\Diactoros\Response; -use Zend\Diactoros\Uri; class AuthCodeGrant extends AbstractAuthorizeGrant { @@ -88,13 +87,11 @@ class AuthCodeGrant extends AbstractAuthorizeGrant $client, $client->getRedirectUri() ); - $queryString = http_build_query($request->getQueryParams()); - $postbackUri = new Uri( - sprintf( - '//%s%s', - $request->getServerParams()['HTTP_HOST'], - $request->getServerParams()['REQUEST_URI'] - ) + + $postbackUri = sprintf( + '//%s%s', + $request->getServerParams()['HTTP_HOST'], + $request->getServerParams()['REQUEST_URI'] ); $userId = null; @@ -107,7 +104,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant $oauthCookie = $this->getCookieParameter('oauth_authorize_request', $request, null); if ($oauthCookie !== null) { try { - $oauthCookiePayload = json_decode(KeyCrypt::decrypt($oauthCookie, $this->pathToPublicKey)); + $oauthCookiePayload = json_decode($this->decrypt($oauthCookie)); if (is_object($oauthCookiePayload)) { $userId = $oauthCookiePayload->user_id; } @@ -140,10 +137,16 @@ class AuthCodeGrant extends AbstractAuthorizeGrant if ($userId === null) { $html = $this->getTemplateRenderer()->renderLogin([ 'error' => $loginError, - 'postback_uri' => (string) $postbackUri->withQuery($queryString), + 'postback_uri' => $this->makeRedirectUri( + $postbackUri, + $request->getQueryParams() + ), ]); - return new Response\HtmlResponse($html); + $htmlResponse = new HtmlResponse($this->accessTokenRepository); + $htmlResponse->setStatusCode(403); + $htmlResponse->setHtml($html); + return $htmlResponse; } // The user hasn't approved the client yet so show an authorize form @@ -151,30 +154,31 @@ class AuthCodeGrant extends AbstractAuthorizeGrant $html = $this->getTemplateRenderer()->renderAuthorize([ 'client' => $client, 'scopes' => $scopes, - 'postback_uri' => (string) $postbackUri->withQuery($queryString), + 'postback_uri' => $this->makeRedirectUri( + $postbackUri, + $request->getQueryParams() + ), ]); - return new Response\HtmlResponse( - $html, - 200, - [ - 'Set-Cookie' => sprintf( - 'oauth_authorize_request=%s; Expires=%s', - urlencode(KeyCrypt::encrypt( - json_encode([ - 'user_id' => $userId, - ]), - $this->pathToPrivateKey - )), - (new \DateTime())->add(new \DateInterval('PT5M'))->format('D, d M Y H:i:s e') - ), - ] - ); + $htmlResponse = new HtmlResponse($this->accessTokenRepository); + $htmlResponse->setStatusCode(200); + $htmlResponse->setHtml($html); + $htmlResponse->setHeader('set-cookie', sprintf( + 'oauth_authorize_request=%s; Expires=%s', + urlencode($this->encrypt( + json_encode([ + 'user_id' => $userId, + ]) + )), + (new \DateTime())->add(new \DateInterval('PT5M'))->format('D, d M Y H:i:s e') + )); + + return $htmlResponse; } // The user has either approved or denied the client, so redirect them back - $redirectUri = new Uri($client->getRedirectUri()); - parse_str($redirectUri->getQuery(), $redirectPayload); + $redirectUri = $client->getRedirectUri(); + $redirectPayload = []; $stateParameter = $this->getQueryStringParameter('state', $request); if ($stateParameter !== null) { @@ -191,7 +195,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant $scopes ); - $redirectPayload['code'] = KeyCrypt::encrypt( + $redirectPayload['code'] = $this->encrypt( json_encode( [ 'client_id' => $authCode->getClient()->getIdentifier(), @@ -201,17 +205,22 @@ class AuthCodeGrant extends AbstractAuthorizeGrant 'user_id' => $authCode->getUserIdentifier(), 'expire_time' => (new \DateTime())->add($this->authCodeTTL)->format('U'), ] - ), - $this->pathToPrivateKey + ) ); - return new Response\RedirectResponse($redirectUri->withQuery(http_build_query($redirectPayload))); + $response = new RedirectResponse($this->accessTokenRepository); + $response->setRedirectUri( + $this->makeRedirectUri( + $redirectUri, + $redirectPayload + ) + ); + + return $response; } // The user denied the client, redirect them back with an error - $exception = OAuthServerException::accessDenied('The user denied the request', (string) $redirectUri); - - return $exception->generateHttpResponse(); + throw OAuthServerException::accessDenied('The user denied the request', (string) $redirectUri); } /** @@ -246,7 +255,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant // Validate the authorization code try { - $authCodePayload = json_decode(KeyCrypt::decrypt($encryptedAuthCode, $this->pathToPublicKey)); + $authCodePayload = json_decode($this->decrypt($encryptedAuthCode)); if (time() > $authCodePayload->expire_time) { throw OAuthServerException::invalidRequest('code', 'Authorization code has expired'); } diff --git a/src/Grant/GrantTypeInterface.php b/src/Grant/GrantTypeInterface.php index 5ddd81b8..8e7572c2 100644 --- a/src/Grant/GrantTypeInterface.php +++ b/src/Grant/GrantTypeInterface.php @@ -91,12 +91,12 @@ interface GrantTypeInterface extends EmitterAwareInterface * * @param string $pathToPrivateKey */ - public function setPathToPrivateKey($pathToPrivateKey); + public function setPrivateKeyPath($pathToPrivateKey); /** * Set the path to the public key. * * @param string $pathToPublicKey */ - public function setPathToPublicKey($pathToPublicKey); + public function setPublicKeyPath($pathToPublicKey); } diff --git a/src/Grant/ImplicitGrant.php b/src/Grant/ImplicitGrant.php index 89a3c2e6..3fd567d4 100644 --- a/src/Grant/ImplicitGrant.php +++ b/src/Grant/ImplicitGrant.php @@ -7,12 +7,11 @@ use League\OAuth2\Server\Entities\Interfaces\ClientEntityInterface; use League\OAuth2\Server\Entities\Interfaces\UserEntityInterface; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Repositories\UserRepositoryInterface; +use League\OAuth2\Server\ResponseTypes\HtmlResponse; +use League\OAuth2\Server\ResponseTypes\RedirectResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use League\OAuth2\Server\TemplateRenderer\AbstractRenderer; -use League\OAuth2\Server\Utils\KeyCrypt; use Psr\Http\Message\ServerRequestInterface; -use Zend\Diactoros\Response; -use Zend\Diactoros\Uri; class ImplicitGrant extends AbstractAuthorizeGrant { @@ -86,13 +85,11 @@ class ImplicitGrant extends AbstractAuthorizeGrant $client, $client->getRedirectUri() ); - $queryString = http_build_query($request->getQueryParams()); - $postbackUri = new Uri( - sprintf( - '//%s%s', - $request->getServerParams()['HTTP_HOST'], - $request->getServerParams()['REQUEST_URI'] - ) + + $postbackUri = sprintf( + '//%s%s', + $request->getServerParams()['HTTP_HOST'], + $request->getServerParams()['REQUEST_URI'] ); $userId = null; @@ -105,7 +102,7 @@ class ImplicitGrant extends AbstractAuthorizeGrant $oauthCookie = $this->getCookieParameter('oauth_authorize_request', $request, null); if ($oauthCookie !== null) { try { - $oauthCookiePayload = json_decode(KeyCrypt::decrypt($oauthCookie, $this->pathToPublicKey)); + $oauthCookiePayload = json_decode($this->decrypt($oauthCookie)); if (is_object($oauthCookiePayload)) { $userId = $oauthCookiePayload->user_id; } @@ -138,10 +135,16 @@ class ImplicitGrant extends AbstractAuthorizeGrant if ($userId === null) { $html = $this->getTemplateRenderer()->renderLogin([ 'error' => $loginError, - 'postback_uri' => (string) $postbackUri->withQuery($queryString), + 'postback_uri' => $this->makeRedirectUri( + $postbackUri, + $request->getQueryParams() + ), ]); - return new Response\HtmlResponse($html); + $htmlResponse = new HtmlResponse($this->accessTokenRepository); + $htmlResponse->setStatusCode(403); + $htmlResponse->setHtml($html); + return $htmlResponse; } // The user hasn't approved the client yet so show an authorize form @@ -149,25 +152,26 @@ class ImplicitGrant extends AbstractAuthorizeGrant $html = $this->getTemplateRenderer()->renderAuthorize([ 'client' => $client, 'scopes' => $scopes, - 'postback_uri' => (string) $postbackUri->withQuery($queryString), + 'postback_uri' => $this->makeRedirectUri( + $postbackUri, + $request->getQueryParams() + ) ]); - return new Response\HtmlResponse( - $html, - 200, - [ - 'Set-Cookie' => sprintf( - 'oauth_authorize_request=%s; Expires=%s', - urlencode(KeyCrypt::encrypt( - json_encode([ - 'user_id' => $userId, - ]), - $this->pathToPrivateKey - )), - (new \DateTime())->add(new \DateInterval('PT5M'))->format('D, d M Y H:i:s e') - ), - ] - ); + $htmlResponse = new HtmlResponse($this->accessTokenRepository); + $htmlResponse->setStatusCode(200); + $htmlResponse->setHtml($html); + $htmlResponse->setHeader('set-cookie', sprintf( + 'oauth_authorize_request=%s; Expires=%s', + urlencode($this->encrypt( + json_encode([ + 'user_id' => $userId, + ]) + )), + (new \DateTime())->add(new \DateInterval('PT5M'))->format('D, d M Y H:i:s e') + )); + + return $htmlResponse; } // The user has either approved or denied the client, so redirect them back @@ -188,11 +192,18 @@ class ImplicitGrant extends AbstractAuthorizeGrant $scopes ); - $redirectPayload['access_token'] = (string) $accessToken->convertToJWT($this->pathToPrivateKey); + $redirectPayload['access_token'] = (string) $accessToken->convertToJWT($this->privateKeyPath); $redirectPayload['token_type'] = 'bearer'; $redirectPayload['expires_in'] = time() - $accessToken->getExpiryDateTime()->getTimestamp(); - return new Response\RedirectResponse($redirectUri->withFragment(http_build_query($redirectPayload))); + $response = new RedirectResponse($this->accessTokenRepository); + $response->setRedirectUri( + $this->makeRedirectUri( + $redirectUri, + $redirectPayload, + '#' + ) + ); } // The user denied the client, redirect them back with an error diff --git a/src/Grant/RefreshTokenGrant.php b/src/Grant/RefreshTokenGrant.php index a03bdc90..be0d803e 100644 --- a/src/Grant/RefreshTokenGrant.php +++ b/src/Grant/RefreshTokenGrant.php @@ -14,7 +14,6 @@ use League\Event\Event; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; -use League\OAuth2\Server\Utils\KeyCrypt; use Psr\Http\Message\ServerRequestInterface; /** @@ -106,7 +105,7 @@ class RefreshTokenGrant extends AbstractGrant // Validate refresh token try { - $refreshToken = KeyCrypt::decrypt($encryptedRefreshToken, $this->pathToPublicKey); + $refreshToken = $this->decrypt($encryptedRefreshToken); } catch (\LogicException $e) { throw OAuthServerException::invalidRefreshToken('Cannot parse refresh token: ' . $e->getMessage()); } diff --git a/src/ResponseTypes/AbstractResponseType.php b/src/ResponseTypes/AbstractResponseType.php index a64bfded..e693d85b 100644 --- a/src/ResponseTypes/AbstractResponseType.php +++ b/src/ResponseTypes/AbstractResponseType.php @@ -10,21 +10,14 @@ */ namespace League\OAuth2\Server\ResponseTypes; +use League\OAuth2\Server\CryptTrait; use League\OAuth2\Server\Entities\Interfaces\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\Interfaces\RefreshTokenEntityInterface; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; abstract class AbstractResponseType implements ResponseTypeInterface { - /** - * @var string - */ - protected $pathToPrivateKey; - - /** - * @var string - */ - protected $pathToPublicKey; + use CryptTrait; /** * @var \League\OAuth2\Server\Entities\Interfaces\AccessTokenEntityInterface @@ -42,17 +35,10 @@ abstract class AbstractResponseType implements ResponseTypeInterface protected $accessTokenRepository; /** - * @param string $pathToPrivateKey - * @param string $pathToPublicKey * @param \League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface $accessTokenRepository */ - public function __construct( - $pathToPrivateKey, - $pathToPublicKey, - AccessTokenRepositoryInterface $accessTokenRepository - ) { - $this->pathToPrivateKey = $pathToPrivateKey; - $this->pathToPublicKey = $pathToPublicKey; + public function __construct(AccessTokenRepositoryInterface $accessTokenRepository) + { $this->accessTokenRepository = $accessTokenRepository; } diff --git a/src/ResponseTypes/BearerTokenResponse.php b/src/ResponseTypes/BearerTokenResponse.php index 04295895..17675b88 100644 --- a/src/ResponseTypes/BearerTokenResponse.php +++ b/src/ResponseTypes/BearerTokenResponse.php @@ -10,13 +10,8 @@ */ namespace League\OAuth2\Server\ResponseTypes; -use Lcobucci\JWT\Parser; -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; class BearerTokenResponse extends AbstractResponseType { @@ -27,7 +22,7 @@ class BearerTokenResponse extends AbstractResponseType { $expireDateTime = $this->accessToken->getExpiryDateTime()->getTimestamp(); - $jwtAccessToken = $this->accessToken->convertToJWT($this->pathToPrivateKey); + $jwtAccessToken = $this->accessToken->convertToJWT($this->privateKeyPath); $responseParams = [ 'token_type' => 'Bearer', @@ -36,7 +31,7 @@ class BearerTokenResponse extends AbstractResponseType ]; if ($this->refreshToken instanceof RefreshTokenEntityInterface) { - $refreshToken = KeyCrypt::encrypt( + $refreshToken = $this->encrypt( json_encode( [ 'client_id' => $this->accessToken->getClient()->getIdentifier(), @@ -46,8 +41,7 @@ class BearerTokenResponse extends AbstractResponseType 'user_id' => $this->accessToken->getUserIdentifier(), 'expire_time' => $expireDateTime, ] - ), - $this->pathToPrivateKey + ) ); $responseParams['refresh_token'] = $refreshToken; @@ -63,40 +57,4 @@ class BearerTokenResponse extends AbstractResponseType return $response; } - - /** - * {@inheritdoc} - */ - public function validateAccessToken(ServerRequestInterface $request) - { - if ($request->hasHeader('authorization') === false) { - throw OAuthServerException::accessDenied('Missing "Authorization" header'); - } - - $header = $request->getHeader('authorization'); - $jwt = trim(preg_replace('/^(?:\s+)?Bearer\s/', '', $header[0])); - - try { - // Attempt to parse and validate the JWT - $token = (new Parser())->parse($jwt); - if ($token->verify(new Sha256(), $this->pathToPublicKey) === false) { - throw OAuthServerException::accessDenied('Access token could not be verified'); - } - - // Check if token has been revoked - if ($this->accessTokenRepository->isAccessTokenRevoked($token->getClaim('jti'))) { - throw OAuthServerException::accessDenied('Access token has been revoked'); - } - - // Return the request with additional attributes - return $request - ->withAttribute('oauth_access_token_id', $token->getClaim('jti')) - ->withAttribute('oauth_client_id', $token->getClaim('aud')) - ->withAttribute('oauth_user_id', $token->getClaim('sub')) - ->withAttribute('oauth_scopes', $token->getClaim('scopes')); - } catch (\InvalidArgumentException $exception) { - // JWT couldn't be parsed so return the request as is - throw OAuthServerException::accessDenied($exception->getMessage()); - } - } } diff --git a/src/ResponseTypes/HtmlResponse.php b/src/ResponseTypes/HtmlResponse.php new file mode 100644 index 00000000..ad05f53e --- /dev/null +++ b/src/ResponseTypes/HtmlResponse.php @@ -0,0 +1,66 @@ +html = $html; + } + + /** + * @param int $statusCode + */ + public function setStatusCode($statusCode = 200) + { + $this->statusCode = $statusCode; + } + + /** + * @param ResponseInterface $response + * + * @return ResponseInterface + */ + public function generateHttpResponse(ResponseInterface $response) + { + $response->getBody()->write($this->html); + + foreach ($this->headers as $key => $value) { + $response = $response->withHeader($key, $value); + } + + return $response + ->withStatus($this->statusCode) + ->withHeader('content-type', 'text/html'); + } + + /** + * @param string $key + * @param string $value + */ + public function setHeader($key, $value) + { + $this->headers[$key] = $value; + } +} \ No newline at end of file diff --git a/src/ResponseTypes/RedirectResponse.php b/src/ResponseTypes/RedirectResponse.php new file mode 100644 index 00000000..d171a12d --- /dev/null +++ b/src/ResponseTypes/RedirectResponse.php @@ -0,0 +1,31 @@ +redirectUri = $redirectUri; + } + + /** + * @param ResponseInterface $response + * + * @return ResponseInterface + */ + public function generateHttpResponse(ResponseInterface $response) + { + return $response->withStatus(302)->withHeader('location', $this->redirectUri); + } +} \ No newline at end of file diff --git a/src/ResponseTypes/ResponseTypeInterface.php b/src/ResponseTypes/ResponseTypeInterface.php index 982ef2b1..100e8cda 100644 --- a/src/ResponseTypes/ResponseTypeInterface.php +++ b/src/ResponseTypes/ResponseTypeInterface.php @@ -13,7 +13,6 @@ namespace League\OAuth2\Server\ResponseTypes; use League\OAuth2\Server\Entities\Interfaces\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\Interfaces\RefreshTokenEntityInterface; use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; interface ResponseTypeInterface { @@ -27,16 +26,6 @@ interface ResponseTypeInterface */ public function setRefreshToken(RefreshTokenEntityInterface $refreshToken); - /** - * Determine the access token in the authorization header and append OAUth properties to the request - * as attributes. - * - * @param ServerRequestInterface $request - * - * @return ServerRequestInterface - */ - public function validateAccessToken(ServerRequestInterface $request); - /** * @param ResponseInterface $response * diff --git a/src/Server.php b/src/Server.php index 13765727..30bfd194 100644 --- a/src/Server.php +++ b/src/Server.php @@ -5,6 +5,8 @@ namespace League\OAuth2\Server; use DateInterval; use League\Event\EmitterAwareInterface; use League\Event\EmitterAwareTrait; +use League\OAuth2\Server\AuthorizationValidators\AuthorizationValidatorInterface; +use League\OAuth2\Server\AuthorizationValidators\BearerTokenValidator; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Grant\GrantTypeInterface; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; @@ -14,8 +16,6 @@ use League\OAuth2\Server\ResponseTypes\BearerTokenResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use Zend\Diactoros\Response; -use Zend\Diactoros\ServerRequestFactory; class Server implements EmitterAwareInterface { @@ -61,15 +61,21 @@ class Server implements EmitterAwareInterface */ private $scopeRepository; + /** + * @var \League\OAuth2\Server\AuthorizationValidators\AuthorizationValidatorInterface + */ + private $authorizationValidator; + /** * New server instance. * - * @param \League\OAuth2\Server\Repositories\ClientRepositoryInterface $clientRepository - * @param \League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface $accessTokenRepository - * @param \League\OAuth2\Server\Repositories\ScopeRepositoryInterface $scopeRepository - * @param string $privateKeyPath - * @param string $publicKeyPath - * @param null|\League\OAuth2\Server\ResponseTypes\ResponseTypeInterface $responseType + * @param \League\OAuth2\Server\Repositories\ClientRepositoryInterface $clientRepository + * @param \League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface $accessTokenRepository + * @param \League\OAuth2\Server\Repositories\ScopeRepositoryInterface $scopeRepository + * @param string $privateKeyPath + * @param string $publicKeyPath + * @param null|\League\OAuth2\Server\ResponseTypes\ResponseTypeInterface $responseType + * @param null|\League\OAuth2\Server\AuthorizationValidators\AuthorizationValidatorInterface $authorizationValidator */ public function __construct( ClientRepositoryInterface $clientRepository, @@ -77,7 +83,8 @@ class Server implements EmitterAwareInterface ScopeRepositoryInterface $scopeRepository, $privateKeyPath, $publicKeyPath, - ResponseTypeInterface $responseType = null + ResponseTypeInterface $responseType = null, + AuthorizationValidatorInterface $authorizationValidator = null ) { $this->clientRepository = $clientRepository; $this->accessTokenRepository = $accessTokenRepository; @@ -85,6 +92,7 @@ class Server implements EmitterAwareInterface $this->privateKeyPath = $privateKeyPath; $this->publicKeyPath = $publicKeyPath; $this->responseType = $responseType; + $this->authorizationValidator = $authorizationValidator; } /** @@ -98,8 +106,8 @@ class Server implements EmitterAwareInterface $grantType->setAccessTokenRepository($this->accessTokenRepository); $grantType->setClientRepository($this->clientRepository); $grantType->setScopeRepository($this->scopeRepository); - $grantType->setPathToPrivateKey($this->privateKeyPath); - $grantType->setPathToPublicKey($this->publicKeyPath); + $grantType->setPrivateKeyPath($this->privateKeyPath); + $grantType->setPublicKeyPath($this->publicKeyPath); $grantType->setEmitter($this->getEmitter()); $this->enabledGrantTypes[$grantType->getIdentifier()] = $grantType; @@ -117,37 +125,29 @@ class Server implements EmitterAwareInterface * * @return \Psr\Http\Message\ResponseInterface */ - public function respondToRequest(ServerRequestInterface $request = null, ResponseInterface $response = null) + public function respondToRequest(ServerRequestInterface $request, ResponseInterface $response) { - if (!$request instanceof ServerRequestInterface) { - $request = ServerRequestFactory::fromGlobals(); - } - - if (!$response instanceof ResponseInterface) { - $response = new Response(); - } - - $tokenResponse = null; - while ($tokenResponse === null && $grantType = array_shift($this->enabledGrantTypes)) { - /** @var \League\OAuth2\Server\Grant\GrantTypeInterface $grantType */ - if ($grantType->canRespondToRequest($request)) { - $tokenResponse = $grantType->respondToRequest( - $request, - $this->getResponseType(), - $this->grantTypeAccessTokenTTL[$grantType->getIdentifier()] - ); + try { + $tokenResponse = null; + while ($tokenResponse === null && $grantType = array_shift($this->enabledGrantTypes)) { + /** @var \League\OAuth2\Server\Grant\GrantTypeInterface $grantType */ + if ($grantType->canRespondToRequest($request)) { + $tokenResponse = $grantType->respondToRequest( + $request, + $this->getResponseType(), + $this->grantTypeAccessTokenTTL[$grantType->getIdentifier()] + ); + } } - } - if ($tokenResponse instanceof ResponseInterface) { - return $tokenResponse; - } + if ($tokenResponse instanceof ResponseTypeInterface) { + return $tokenResponse->generateHttpResponse($response); + } - if ($tokenResponse instanceof ResponseTypeInterface) { - return $tokenResponse->generateHttpResponse($response); + throw OAuthServerException::unsupportedGrantType(); + } catch (OAuthServerException $e) { + return $e->generateHttpResponse($response); } - - throw OAuthServerException::unsupportedGrantType(); } /** @@ -161,7 +161,7 @@ class Server implements EmitterAwareInterface */ public function validateAuthenticatedRequest(ServerRequestInterface $request) { - return $this->getResponseType()->validateAccessToken($request); + return $this->getAuthorizationValidator()->validateAuthorization($request); } /** @@ -172,13 +172,27 @@ class Server implements EmitterAwareInterface protected function getResponseType() { if (!$this->responseType instanceof ResponseTypeInterface) { - $this->responseType = new BearerTokenResponse( - $this->privateKeyPath, - $this->publicKeyPath, - $this->accessTokenRepository - ); + $this->responseType = new BearerTokenResponse($this->accessTokenRepository); } + $this->responseType->setPublicKeyPath($this->publicKeyPath); + $this->responseType->setPrivateKeyPath($this->privateKeyPath); + return $this->responseType; } + + /** + * @return \League\OAuth2\Server\AuthorizationValidators\AuthorizationValidatorInterface + */ + protected function getAuthorizationValidator() + { + if (!$this->authorizationValidator instanceof AuthorizationValidatorInterface) { + $this->authorizationValidator = new BearerTokenValidator($this->accessTokenRepository); + } + + $this->authorizationValidator->setPublicKeyPath($this->publicKeyPath); + $this->authorizationValidator->setPrivateKeyPath($this->privateKeyPath); + + return $this->authorizationValidator; + } }