From 92a101f263537196b1b3a13c370ed8ab5cb5e71a Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Thu, 11 Feb 2016 17:30:01 +0000 Subject: [PATCH 01/30] First commit of AuthCode rewrite --- examples/public/auth_code.php | 60 +++ .../AuthorizationCodeRequestEntity.php | 79 ++++ src/Grant/AuthCodeGrant.php | 361 +++++------------- 3 files changed, 236 insertions(+), 264 deletions(-) create mode 100644 examples/public/auth_code.php create mode 100644 src/Entities/AuthorizationCodeRequestEntity.php diff --git a/examples/public/auth_code.php b/examples/public/auth_code.php new file mode 100644 index 00000000..d0e76989 --- /dev/null +++ b/examples/public/auth_code.php @@ -0,0 +1,60 @@ +enableGrantType($passwordGrant); + +// App +$app = new App([Server::class => $server]); + +$app->any('/authorise', function (Request $request, Response $response) { + if (strtoupper($request->getMethod()) === 'GET') { + $response = $response->withHeader('Set-Cookie', $authCodeGrant->storeOriginalRequestParams) + } +}); + +$app->post('/access_token', function (Request $request, Response $response) { + /** @var Server $server */ + $server = $this->get(Server::class); + try { + return $server->respondToRequest($request); + } catch (OAuthServerException $e) { + return $e->generateHttpResponse(); + } catch (\Exception $e) { + return $response->withStatus(500)->write($e->getMessage()); + } +}); + +$app->run(); diff --git a/src/Entities/AuthorizationCodeRequestEntity.php b/src/Entities/AuthorizationCodeRequestEntity.php new file mode 100644 index 00000000..bd401b24 --- /dev/null +++ b/src/Entities/AuthorizationCodeRequestEntity.php @@ -0,0 +1,79 @@ +clientId; + } + + /** + * @return null|string + */ + public function getRedirectUri() + { + return $this->redirectUri; + } + + /** + * @return null|string + */ + public function getScope() + { + return $this->scope; + } + + /** + * @return null|string + */ + public function getState() + { + return $this->state; + } + + /** + * AuthorizationCodeRequestEntity constructor. + * + * @param string $clientId + * @param string|null $redirectUri + * @param string|null $scope + * @param string|null $state + */ + public function __construct($clientId, $redirectUri = null, $scope = null, $state = null) + { + $this->clientId = $clientId; + $this->redirectUri = $redirectUri; + $this->scope = $scope; + $this->state = $state; + } + + public function __sleep() + { + return ['clientId', 'redirectUri', 'scope', 'state']; + } +} diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index a7ae36bb..c51954e3 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -1,303 +1,136 @@ - * @copyright Copyright (c) Alex Bilbie - * @license http://mit-license.org/ - * @link https://github.com/thephpleague/oauth2-server - */ namespace League\OAuth2\Server\Grant; -use League\Event\Emitter; -use League\Event\Event; -use League\OAuth2\Server\Entities\AccessTokenEntity; -use League\OAuth2\Server\Entities\Interfaces\AuthCodeEntityInterface; -use League\OAuth2\Server\Entities\Interfaces\ClientEntityInterface; -use League\OAuth2\Server\Exception\InvalidClientException; -use League\OAuth2\Server\Exception\InvalidRequestException; -use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; -use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface; -use League\OAuth2\Server\Repositories\ClientRepositoryInterface; -use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; -use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; -use League\OAuth2\Server\TokenTypes\TokenTypeInterface; -use League\OAuth2\Server\Utils\SecureKey; -use Symfony\Component\HttpFoundation\Request; use DateInterval; +use DateTime; +use League\OAuth2\Server\Exception\OAuthServerException; +use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; +use Psr\Http\Message\ServerRequestInterface; -/** - * Auth code grant class - */ class AuthCodeGrant extends AbstractGrant { /** - * @var \League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface + * @inheritdoc */ - protected $authCodeRepository; - - /** - * @param \League\Event\Emitter $emitter - * @param \League\OAuth2\Server\Repositories\ClientRepositoryInterface $clientRepository - * @param \League\OAuth2\Server\Repositories\ScopeRepositoryInterface $scopeRepository - * @param \League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface $accessTokenRepository - * @param \League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface $authCodeRepository - * @param \League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface $refreshTokenRepository - */ - public function __construct( - Emitter $emitter, - ClientRepositoryInterface $clientRepository, - ScopeRepositoryInterface $scopeRepository, - AccessTokenRepositoryInterface $accessTokenRepository, - AuthCodeRepositoryInterface $authCodeRepository, - RefreshTokenRepositoryInterface $refreshTokenRepository = null + public function respondToRequest( + ServerRequestInterface $request, + ResponseTypeInterface $responseType, + DateInterval $tokenTTL, + $scopeDelimiter = ' ' ) { - $this->authCodeRepository = $authCodeRepository; - $this->refreshTokenRepository = $refreshTokenRepository; - parent::__construct($emitter, $clientRepository, $scopeRepository, $accessTokenRepository); + if ( + isset($request->getQueryParams()['response_type']) + && $request->getQueryParams()['response_type'] === 'code' + && isset($request->getQueryParams()['client_id']) + ) { + return $this->respondToAuthorizationRequest($request, $scopeDelimiter, $tokenTTL); + } elseif ( + isset($request->getParsedBody()['grant_type']) + && $request->getParsedBody()['grant_type'] === 'authorization_code' + ) { + return $this->respondToAccessTokenRequest($request, $responseType, $tokenTTL); + } else { + throw OAuthServerException::serverError('respondToRequest() should not have been called'); + } } /** - * Grant identifier + * Respond to an authorization request * - * @var string + * @param \Psr\Http\Message\ServerRequestInterface $request + * @param string $scopeDelimiter + * @param DateTime $tokenTTL */ - protected $identifier = 'authorization_code'; - - /** - * Response type - * - * @var string - */ - protected $responseType = 'code'; - - /** - * AuthServer instance - * - * @var \League\OAuth2\Server\AuthorizationServer - */ - protected $server = null; - - /** - * Access token expires in override - * - * @var int - */ - protected $accessTokenTTL = null; - - /** - * The TTL of the auth token - * - * @var integer - */ - protected $authTokenTTL = 600; - - /** - * Override the default access token expire time - * - * @param int $authTokenTTL - * - * @return void - */ - public function setAuthTokenTTL($authTokenTTL) - { - $this->authTokenTTL = $authTokenTTL; - } - - /** - * Check authorize parameters - * - * @return array Authorize request parameters - * - * @throws - */ - /*public function checkAuthorizeParams() - { + protected function respondToAuthorizationRequest( + ServerRequestInterface $request, + $scopeDelimiter = ' ', + DateTime $tokenTTL + ) { // Get required params - $clientId = $request->query->get('client_id', null); + /*$clientId = array_key_exists('client_id', $request->getQueryParams()) + ? $request->getQueryParams()['client_id'] // $_GET['client_id'] + : null; + if (is_null($clientId)) { - throw new InvalidRequestException('client_id'); + throw OAuthServerException::invalidRequest('client_id', null, '`%s` parameter is missing'); } - $redirectUri = $request->query->get('redirect_uri', null); + $redirectUri = array_key_exists('redirect_uri', $request->getQueryParams()) + ? $request->getQueryParams()['redirect_uri'] // $_GET['redirect_uri'] + : null; + if (is_null($redirectUri)) { - throw new InvalidRequestException('redirect_uri'); + throw OAuthServerException::invalidRequest('redirect_uri', null, '`%s` parameter is missing'); } // Validate client ID and redirect URI - $client = $this->server->getClientStorage()->get( + $client = $this->clientRepository->getClientEntity( $clientId, + $this->getIdentifier(), null, - $redirectUri, - $this->getIdentifier() - ); - - if (($client instanceof ClientEntity) === false) { - $this->server->getEventEmitter()->emit(new Event\ClientAuthenticationFailedEvent($request)); - throw new Exception\InvalidClientException(); - } - - $state = $request->query->get('state', null); - if ($this->server->stateParamRequired() === true && is_null($state)) { - throw new InvalidRequestException('state', $redirectUri); - } - - $responseType = $request->query->get('response_type', null); - if (is_null($responseType)) { - throw new InvalidRequestException('response_type', $redirectUri); - } - - // Ensure response type is one that is recognised - if (!in_array($responseType, $this->server->getResponseTypes())) { - throw new Exception\UnsupportedResponseTypeException($responseType, $redirectUri); - } - - // Validate any scopes that are in the request - $scopeParam = $request->query->get('scope', ''); - $scopes = $this->validateScopes($scopeParam, $client, $redirectUri); - - return [ - 'client' => $client, - 'redirect_uri' => $redirectUri, - 'state' => $state, - 'response_type' => $responseType, - 'scopes' => $scopes - ]; - }*/ - - /** - * Parse a new authorize request - * - * @param string $type The session owner's type - * @param string $typeId The session owner's ID - * @param array $authParams The authorize request $_GET parameters - * - * @return string An authorisation code - */ - /*public function newAuthorizeRequest($type, $typeId, $authParams = []) - { - // Create a new session - $session = new SessionEntity($this->server); - $session->setOwner($type, $typeId); - $session->associateClient($authParams['client']); - $session->save(); - - // Create a new auth code - $authCode = new AuthCodeEntity($this->server); - $authCode->setId(SecureKey::generate()); - $authCode->setRedirectUri($authParams['redirect_uri']); - $authCode->setExpireTime(time() + $this->authTokenTTL); - - foreach ($authParams['scopes'] as $scope) { - $authCode->associateScope($scope); - } - - $authCode->setSession($session); - $authCode->save(); - - return $authCode->generateRedirectUri($authParams['state']); - }*/ - - /** - * Return an access token - * - * @param \Symfony\Component\HttpFoundation\Request $request - * @param \League\OAuth2\Server\TokenTypes\TokenTypeInterface $tokenType - * @param \DateInterval $accessTokenTTL - * @param string $scopeDelimiter - * - * @return \League\OAuth2\Server\TokenTypes\TokenTypeInterface - * @throws \League\OAuth2\Server\Exception\InvalidClientException - * @throws \League\OAuth2\Server\Exception\InvalidGrantException - * @throws \League\OAuth2\Server\Exception\InvalidRequestException - */ - public function getAccessTokenAsType( - Request $request, - TokenTypeInterface $tokenType, - DateInterval $accessTokenTTL, - $scopeDelimiter = ' ' - ) { - // Get the required params - $clientId = $request->request->get('client_id', $request->getUser()); - if (is_null($clientId)) { - throw new InvalidRequestException('client_id', ''); - } - - $clientSecret = $request->request->get('client_secret', - $request->getPassword()); - if (is_null($clientSecret)) { - throw new InvalidRequestException('client_secret'); - } - - $redirectUri = $request->request->get('redirect_uri', null); - if (is_null($redirectUri)) { - throw new InvalidRequestException('redirect_uri'); - } - - // Validate client ID and client secret - $client = $this->clientRepository->get( - $clientId, - $clientSecret, - $redirectUri, - $this->getIdentifier() + $redirectUri ); if (($client instanceof ClientEntityInterface) === false) { - $this->emitter->emit(new Event('client.authentication.failed', $request)); - throw new InvalidClientException(); + throw OAuthServerException::invalidClient(); } - // Validate the auth code - $authCode = $request->request->get('code', null); - if (is_null($authCode)) { - throw new InvalidRequestException('code'); - } + $state = array_key_exists('state', $request->getQueryParams()) + ? $request->getQueryParams()['state'] // $_GET['state'] + : null; - $code = $this->authCodeRepository->get($authCode); - if (($code instanceof AuthCodeEntityInterface) === false) { - throw new InvalidRequestException('code'); - } + // Validate any scopes that are in the request + $scopeParam = array_key_exists('scope', $request->getQueryParams()) + ? $request->getQueryParams()['scope'] // $_GET['scope'] + : ''; + $scopes = $this->validateScopes($scopeParam, $scopeDelimiter, $client); - // Ensure the auth code hasn't expired - if ($code->isExpired() === true) { - throw new InvalidRequestException('code'); - } + // Create a new authorization code + $authCode = new AuthCodeEntity(); + $authCode->setIdentifier(SecureKey::generate()); + $authCode->setExpiryDateTime((new \DateTime())->add($authCodeTTL)); + $authCode->setClient($client); + $authCode->setUserIdentifier($userEntity->getIdentifier()); - // Check redirect URI presented matches redirect URI originally used in authorize request - if ($code->getRedirectUri() !== $redirectUri) { - throw new InvalidRequestException('redirect_uri'); - } + // Associate scopes with the session and access token + foreach ($scopes as $scope) { + $authCode->addScope($scope); + }*/ + } - // Generate the access token - $accessToken = new AccessTokenEntity($this->server); - $accessToken->setIdentifier(SecureKey::generate()); - $expirationDateTime = (new \DateTime())->add($accessTokenTTL); - $accessToken->setExpiryDateTime($expirationDateTime); - $accessToken->setClient($client); + /** + * Respond to an access token request + * + * @param \Psr\Http\Message\ServerRequestInterface $request + * @param \League\OAuth2\Server\ResponseTypes\ResponseTypeInterface $responseType + * @param \DateInterval $accessTokenTTL + * + * @return \League\OAuth2\Server\ResponseTypes\ResponseTypeInterface + */ + protected function respondToAccessTokenRequest( + ServerRequestInterface $request, + ResponseTypeInterface $responseType, + DateInterval $accessTokenTTL + ) { - foreach ($code->getScopes() as $scope) { - $accessToken->addScope($scope); - } + } - $tokenType->setAccessToken($accessToken); - - // Associate a refresh token if set - if ($this->refreshTokenRepository instanceof RefreshTokenRepositoryInterface) { -// $refreshToken = new RefreshTokenEntity($this->server); -// $refreshToken->setId(SecureKey::generate()); -// $refreshToken->setExpireTime($this->server->getGrantType('refresh_token')->getRefreshTokenTTL() + time()); -// $tokenType->setParam('refresh_token', $refreshToken->getId()); -// $refreshToken->setAccessToken($accessToken); - } - - // Expire the auth code - $this->authCodeRepository->delete($code); - - // Save the access token - $this->accessTokenRepository->create($accessToken); - - return $tokenType; + /** + * @inheritdoc + */ + public function canRespondToRequest(ServerRequestInterface $request) + { + return ( + ( + strtoupper($request->getMethod()) === 'GET' + && isset($request->getQueryParams()['response_type']) + && $request->getQueryParams()['response_type'] === 'code' + && isset($request->getQueryParams()['client_id']) + ) || ( + isset($request->getParsedBody()['grant_type']) + && $request->getParsedBody()['grant_type'] === 'authorization_code' + ) + ); } } From ca776e83a22d9d547731cb7e1ca6df8f493075d0 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Thu, 11 Feb 2016 17:58:35 +0000 Subject: [PATCH 02/30] Fix for header writing --- src/ResponseTypes/BearerTokenResponse.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ResponseTypes/BearerTokenResponse.php b/src/ResponseTypes/BearerTokenResponse.php index c67bc990..1056a087 100644 --- a/src/ResponseTypes/BearerTokenResponse.php +++ b/src/ResponseTypes/BearerTokenResponse.php @@ -63,12 +63,13 @@ class BearerTokenResponse extends AbstractResponseType $responseParams['refresh_token'] = $refreshToken; } - $response + $response = $response ->withStatus(200) ->withHeader('pragma', 'no-cache') ->withHeader('cache-control', 'no-store') - ->withHeader('content-type', 'application/json;charset=UTF-8') - ->getBody()->write(json_encode($responseParams)); + ->withHeader('content-type', 'application/json; charset=UTF-8'); + + $response->getBody()->write(json_encode($responseParams)); return $response; } From d96f57d27f157919d6cd46c52ca07f8ff197f8a8 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 12 Feb 2016 08:33:59 +0000 Subject: [PATCH 03/30] Got rid of mystery $identifier class property, moved it to the getIdentifier method --- src/Grant/AbstractGrant.php | 17 +---------------- src/Grant/ClientCredentialsGrant.php | 15 ++++++++------- src/Grant/GrantTypeInterface.php | 2 +- src/Grant/PasswordGrant.php | 15 ++++++++------- src/Grant/RefreshTokenGrant.php | 15 ++++++++------- 5 files changed, 26 insertions(+), 38 deletions(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index c6ffc760..42c1644e 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -31,13 +31,6 @@ abstract class AbstractGrant implements GrantTypeInterface { const SCOPE_DELIMITER_STRING = ' '; - /** - * Grant identifier - * - * @var string - */ - protected $identifier = ''; - /** * Grant responds with * @@ -141,14 +134,6 @@ abstract class AbstractGrant implements GrantTypeInterface $this->refreshTokenTTL = $refreshTokenTTL; } - /** - * {@inheritdoc} - */ - public function getIdentifier() - { - return $this->identifier; - } - /** * {@inheritdoc} */ @@ -317,7 +302,7 @@ abstract class AbstractGrant implements GrantTypeInterface { return ( isset($request->getParsedBody()['grant_type']) - && $request->getParsedBody()['grant_type'] === $this->identifier + && $request->getParsedBody()['grant_type'] === $this->getIdentifier() ); } } diff --git a/src/Grant/ClientCredentialsGrant.php b/src/Grant/ClientCredentialsGrant.php index 918586f9..cf6ce268 100644 --- a/src/Grant/ClientCredentialsGrant.php +++ b/src/Grant/ClientCredentialsGrant.php @@ -19,13 +19,6 @@ use Psr\Http\Message\ServerRequestInterface; */ class ClientCredentialsGrant extends AbstractGrant { - /** - * Grant identifier - * - * @var string - */ - protected $identifier = 'client_credentials'; - /** * @inheritdoc */ @@ -47,4 +40,12 @@ class ClientCredentialsGrant extends AbstractGrant return $responseType; } + + /** + * @inheritdoc + */ + public function getIdentifier() + { + return 'client_credentials'; + } } diff --git a/src/Grant/GrantTypeInterface.php b/src/Grant/GrantTypeInterface.php index a6a5c63a..eed44f18 100644 --- a/src/Grant/GrantTypeInterface.php +++ b/src/Grant/GrantTypeInterface.php @@ -31,7 +31,7 @@ interface GrantTypeInterface public function setRefreshTokenTTL(\DateInterval $refreshTokenTTL); /** - * Return the identifier + * Return the grant identifier that can be used in matching up requests * * @return string */ diff --git a/src/Grant/PasswordGrant.php b/src/Grant/PasswordGrant.php index 9f4f41e8..0e2a1085 100644 --- a/src/Grant/PasswordGrant.php +++ b/src/Grant/PasswordGrant.php @@ -24,13 +24,6 @@ use Psr\Http\Message\ServerRequestInterface; */ class PasswordGrant extends AbstractGrant { - /** - * Grant identifier - * - * @var string - */ - protected $identifier = 'password'; - /** * @var \League\OAuth2\Server\Repositories\UserRepositoryInterface */ @@ -109,4 +102,12 @@ class PasswordGrant extends AbstractGrant return $user; } + + /** + * @inheritdoc + */ + public function getIdentifier() + { + return 'password'; + } } diff --git a/src/Grant/RefreshTokenGrant.php b/src/Grant/RefreshTokenGrant.php index cf3286c8..dc7d35b1 100644 --- a/src/Grant/RefreshTokenGrant.php +++ b/src/Grant/RefreshTokenGrant.php @@ -23,13 +23,6 @@ use Psr\Http\Message\ServerRequestInterface; */ class RefreshTokenGrant extends AbstractGrant { - /** - * Grant identifier - * - * @var string - */ - protected $identifier = 'refresh_token'; - /** * @var \League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface */ @@ -134,4 +127,12 @@ class RefreshTokenGrant extends AbstractGrant return $refreshTokenData; } + + /** + * @inheritdoc + */ + public function getIdentifier() + { + return 'refresh_token'; + } } From bfcf7af4d85e6cd7becdeafa341e5b59b4798897 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 12 Feb 2016 09:02:17 +0000 Subject: [PATCH 04/30] Added getQueryStringParameter method --- src/Grant/AbstractGrant.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 42c1644e..bf526b8c 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -239,6 +239,20 @@ abstract class AbstractGrant implements GrantTypeInterface return (isset($request->getParsedBody()[$parameter])) ? $request->getParsedBody()[$parameter] : $default; } + /** + * Retrieve query string parameter. + * + * @param string $parameter + * @param \Psr\Http\Message\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 server parameter. * From c6d806d3f7e60f7aebfe324a9fb638ad8bbc0bb0 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 12 Feb 2016 09:02:33 +0000 Subject: [PATCH 05/30] Docblock updates --- src/Grant/AbstractGrant.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index bf526b8c..208e5fbc 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -186,6 +186,8 @@ abstract class AbstractGrant implements GrantTypeInterface } /** + * Validate scopes in the request + * * @param \Psr\Http\Message\ServerRequestInterface $request * @param \League\OAuth2\Server\Entities\Interfaces\ClientEntityInterface $client * @param string $redirectUri @@ -268,6 +270,8 @@ abstract class AbstractGrant implements GrantTypeInterface } /** + * Issue an access token + * * @param \DateInterval $tokenTTL * @param \League\OAuth2\Server\Entities\Interfaces\ClientEntityInterface $client * @param string $userIdentifier From 7a628409db3b67431cfecc69b8031ffa4abddd48 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 12 Feb 2016 09:03:35 +0000 Subject: [PATCH 06/30] Validate client can now optionally validate secret + redirectUri, and actually validate the redirectUri --- src/Grant/AbstractGrant.php | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 208e5fbc..4b589f4e 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -143,14 +143,20 @@ abstract class AbstractGrant implements GrantTypeInterface } /** + * Validate the client + * * @param \Psr\Http\Message\ServerRequestInterface $request + * @param bool $validateSecret + * @param bool $validateRedirectUri * * @return \League\OAuth2\Server\Entities\Interfaces\ClientEntityInterface - * * @throws \League\OAuth2\Server\Exception\OAuthServerException */ - protected function validateClient(ServerRequestInterface $request) - { + protected function validateClient( + ServerRequestInterface $request, + $validateSecret = true, + $validateRedirectUri = false + ) { $clientId = $this->getRequestParameter( 'client_id', $request, @@ -165,14 +171,19 @@ abstract class AbstractGrant implements GrantTypeInterface $request, $this->getServerParameter('PHP_AUTH_PW', $request) ); - if (is_null($clientSecret)) { + if (is_null($clientSecret) && $validateSecret === true) { throw OAuthServerException::invalidRequest('client_secret', null, '`%s` parameter is missing'); } + $redirectUri = $this->getRequestParameter('redirect_uri', $request, null); + if (is_null($redirectUri) && $validateRedirectUri === true) { + throw OAuthServerException::invalidRequest('redirect_uri', null, '`%s` parameter is missing'); + } + $client = $this->clientRepository->getClientEntity( $clientId, $clientSecret, - null, + $redirectUri, $this->getIdentifier() ); From f4b83baf740963e9ed0a71f6a8b2755d284b1c75 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 12 Feb 2016 09:09:39 +0000 Subject: [PATCH 07/30] Fix getClientEntity method call --- src/Grant/AbstractGrant.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 4aa45126..656affd0 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -179,10 +179,10 @@ abstract class AbstractGrant implements GrantTypeInterface } $client = $this->clientRepository->getClientEntity( - $this->getIdentifier(), $clientId, $clientSecret, - $redirectUri + $redirectUri, + $this->getIdentifier() ); if (!$client instanceof ClientEntityInterface) { From 38a7e53cb5feed5a1249c819a64d578a5f31e1ef Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 12 Feb 2016 09:59:47 +0000 Subject: [PATCH 08/30] Added optional redirectUri parameter to accessDenied method --- src/Exception/OAuthServerException.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index 35c38dd2..bd386a28 100644 --- a/src/Exception/OAuthServerException.php +++ b/src/Exception/OAuthServerException.php @@ -197,13 +197,20 @@ class OAuthServerException extends \Exception /** * Access denied * - * @param null|string $hint + * @param string|null $hint + * @param string|null $redirectUri * * @return static */ - public static function accessDenied($hint = null) + public static function accessDenied($hint = null, $redirectUri = null) { - return new static('The server denied the request.', 'access_denied', 401, $hint); + return new static( + 'The resource owner or authorization server denied the request.', + 'access_denied', + 401, + $hint, + $redirectUri + ); } /** From 0b6bcad9fbbdc1158d0f4e20366cf322ab4917fa Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 12 Feb 2016 09:59:59 +0000 Subject: [PATCH 09/30] Added getCookieParameter method --- src/Grant/AbstractGrant.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 656affd0..5be39338 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -264,6 +264,20 @@ abstract class AbstractGrant implements GrantTypeInterface return (isset($request->getQueryParams()[$parameter])) ? $request->getQueryParams()[$parameter] : $default; } + /** + * Retrieve cookie parameter. + * + * @param string $parameter + * @param \Psr\Http\Message\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. * From c2c199cf981eb1b8192ab55e74e7134a3d5a6aee Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 12 Feb 2016 10:00:10 +0000 Subject: [PATCH 10/30] Added issueAuthCode method --- src/Grant/AbstractGrant.php | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 5be39338..39fbf543 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -15,6 +15,7 @@ use League\Event\EmitterAwareTrait; use League\Event\EmitterInterface; use League\Event\Event; use League\OAuth2\Server\Entities\AccessTokenEntity; +use League\OAuth2\Server\Entities\AuthCodeEntity; use League\OAuth2\Server\Entities\Interfaces\ClientEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntity; use League\OAuth2\Server\Entities\ScopeEntity; @@ -321,6 +322,39 @@ abstract class AbstractGrant implements GrantTypeInterface return $accessToken; } + /** + * Issue an auth code + * + * @param \DateInterval $tokenTTL + * @param \League\OAuth2\Server\Entities\Interfaces\ClientEntityInterface $client + * @param string $userIdentifier + * @param string $redirectUri + * @param array $scopes + * + * @return \League\OAuth2\Server\Entities\AuthCodeEntity + * @throws \League\OAuth2\Server\Exception\OAuthServerException + */ + protected function issueAuthCode( + \DateInterval $tokenTTL, + ClientEntityInterface $client, + $userIdentifier, + $redirectUri, + array $scopes = [] + ) { + $authCode = new AuthCodeEntity(); + $authCode->setIdentifier(SecureKey::generate()); + $authCode->setExpiryDateTime((new \DateTime())->add($tokenTTL)); + $authCode->setClient($client); + $authCode->setUserIdentifier($userIdentifier); + $authCode->setRedirectUri($redirectUri); + + foreach ($scopes as $scope) { + $authCode->addScope($scope); + } + + return $authCode; + } + /** * @param \League\OAuth2\Server\Entities\AccessTokenEntity $accessToken * From 264eba9f20d5ae2fa21f98513be6ee545754715e Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 12 Feb 2016 10:00:22 +0000 Subject: [PATCH 11/30] Updated AuthCodeRepositoryInterface --- .../AuthCodeRepositoryInterface.php | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Repositories/AuthCodeRepositoryInterface.php b/src/Repositories/AuthCodeRepositoryInterface.php index 481106d3..be341ca0 100644 --- a/src/Repositories/AuthCodeRepositoryInterface.php +++ b/src/Repositories/AuthCodeRepositoryInterface.php @@ -19,27 +19,27 @@ use League\OAuth2\Server\Entities\Interfaces\AuthCodeEntityInterface; interface AuthCodeRepositoryInterface extends RepositoryInterface { /** - * Get the auth code + * Persists a new auth code to permanent storage * - * @param string $code + * @param \League\OAuth2\Server\Entities\Interfaces\AuthCodeEntityInterface $authCodeEntityInterface * - * @return \League\OAuth2\Server\Entities\Interfaces\AuthCodeEntityInterface + * @return */ - public function getAuthCodeEntityByCodeString($code); + public function persistNewAuthCode(AuthCodeEntityInterface $authCodeEntityInterface); /** - * Persist a new authorization code + * Revoke an auth code * - * @param string $code The authorization code string - * @param integer $expireTime Token expire time - * @param string $redirectUri Client redirect uri + * @param string $codeId */ - public function persistNewAuthCode($code, $expireTime, $redirectUri); + public function revokeAuthCode($codeId); /** - * Delete an access token + * Check if the auth code has been revoked * - * @param \League\OAuth2\Server\Entities\Interfaces\AuthCodeEntityInterface $token The access token to delete + * @param string $codeId + * + * @return bool Return true if this code has been revoked */ - public function deleteAuthCodeEntity(AuthCodeEntityInterface $token); + public function isAuthCodeRevoked($codeId); } From dcc3f5d856f2eef0633736a2181041963145712a Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 12 Feb 2016 10:00:32 +0000 Subject: [PATCH 12/30] First commit of new ResponseTypes --- .../AuthorizeClientResponseTypeInterface.php | 22 +++++++++++++++++++ .../LoginUserResponseTypeInterface.php | 22 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 src/ResponseTypes/AuthorizeClientResponseTypeInterface.php create mode 100644 src/ResponseTypes/LoginUserResponseTypeInterface.php diff --git a/src/ResponseTypes/AuthorizeClientResponseTypeInterface.php b/src/ResponseTypes/AuthorizeClientResponseTypeInterface.php new file mode 100644 index 00000000..188a0332 --- /dev/null +++ b/src/ResponseTypes/AuthorizeClientResponseTypeInterface.php @@ -0,0 +1,22 @@ + Date: Fri, 12 Feb 2016 10:00:41 +0000 Subject: [PATCH 13/30] Updated Docblock --- src/Utils/KeyCrypt.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Utils/KeyCrypt.php b/src/Utils/KeyCrypt.php index d5e5e2c4..634cd80b 100644 --- a/src/Utils/KeyCrypt.php +++ b/src/Utils/KeyCrypt.php @@ -51,6 +51,8 @@ class KeyCrypt * @param string $encryptedData * @param string $pathToPublicKey * + * @throws \LogicException + * * @return string */ public static function decrypt($encryptedData, $pathToPublicKey) From fccb06ed6753610c7b13ab3783375fd0b5688c9f Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 12 Feb 2016 10:01:15 +0000 Subject: [PATCH 14/30] First commit of updated AuthCodeGrant with respondToAuthorizationRequest method completed --- src/Grant/AuthCodeGrant.php | 204 +++++++++++++++++++++++++----------- 1 file changed, 143 insertions(+), 61 deletions(-) diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index c51954e3..1e967437 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -3,100 +3,151 @@ namespace League\OAuth2\Server\Grant; use DateInterval; -use DateTime; use League\OAuth2\Server\Exception\OAuthServerException; +use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface; +use League\OAuth2\Server\ResponseTypes\AuthorizeClientResponseTypeInterface; +use League\OAuth2\Server\ResponseTypes\LoginUserResponseTypeInterface; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; +use League\OAuth2\Server\Utils\KeyCrypt; use Psr\Http\Message\ServerRequestInterface; +use Zend\Diactoros\Response; +use Zend\Diactoros\Uri; class AuthCodeGrant extends AbstractGrant { /** - * @inheritdoc + * @var \League\OAuth2\Server\ResponseTypes\LoginUserResponseTypeInterface */ - public function respondToRequest( - ServerRequestInterface $request, - ResponseTypeInterface $responseType, - DateInterval $tokenTTL, - $scopeDelimiter = ' ' + private $loginUserResponseType; + + /** + * @var \DateInterval + */ + private $authCodeTTL; + /** + * @var \League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface + */ + private $authCodeRepository; + /** + * @var \League\OAuth2\Server\ResponseTypes\AuthorizeClientResponseTypeInterface + */ + private $authorizeClientResponseType; + + /** + * @param \League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface $authCodeRepository + * @param \DateInterval $authCodeTTL + * @param \League\OAuth2\Server\ResponseTypes\LoginUserResponseTypeInterface $loginUserResponseType + * @param \League\OAuth2\Server\ResponseTypes\AuthorizeClientResponseTypeInterface $authorizeClientResponseType + */ + public function __construct( + AuthCodeRepositoryInterface $authCodeRepository, + \DateInterval $authCodeTTL, + LoginUserResponseTypeInterface $loginUserResponseType, + AuthorizeClientResponseTypeInterface $authorizeClientResponseType ) { - if ( - isset($request->getQueryParams()['response_type']) - && $request->getQueryParams()['response_type'] === 'code' - && isset($request->getQueryParams()['client_id']) - ) { - return $this->respondToAuthorizationRequest($request, $scopeDelimiter, $tokenTTL); - } elseif ( - isset($request->getParsedBody()['grant_type']) - && $request->getParsedBody()['grant_type'] === 'authorization_code' - ) { - return $this->respondToAccessTokenRequest($request, $responseType, $tokenTTL); - } else { - throw OAuthServerException::serverError('respondToRequest() should not have been called'); - } + $this->authCodeRepository = $authCodeRepository; + $this->authCodeTTL = $authCodeTTL; + $this->loginUserResponseType = $loginUserResponseType; + $this->authorizeClientResponseType = $authorizeClientResponseType; } + /** * Respond to an authorization request * * @param \Psr\Http\Message\ServerRequestInterface $request - * @param string $scopeDelimiter - * @param DateTime $tokenTTL + * + * @return \Psr\Http\Message\ResponseInterface + * @throws \League\OAuth2\Server\Exception\OAuthServerException */ protected function respondToAuthorizationRequest( - ServerRequestInterface $request, - $scopeDelimiter = ' ', - DateTime $tokenTTL + ServerRequestInterface $request ) { - // Get required params - /*$clientId = array_key_exists('client_id', $request->getQueryParams()) - ? $request->getQueryParams()['client_id'] // $_GET['client_id'] - : null; - + $clientId = $this->getQueryStringParameter( + 'client_id', + $request, + $this->getServerParameter('PHP_AUTH_USER', $request) + ); if (is_null($clientId)) { throw OAuthServerException::invalidRequest('client_id', null, '`%s` parameter is missing'); } - $redirectUri = array_key_exists('redirect_uri', $request->getQueryParams()) - ? $request->getQueryParams()['redirect_uri'] // $_GET['redirect_uri'] - : null; - + $redirectUri = $this->getQueryStringParameter('redirect_uri', $request, null); if (is_null($redirectUri)) { throw OAuthServerException::invalidRequest('redirect_uri', null, '`%s` parameter is missing'); } - // Validate client ID and redirect URI $client = $this->clientRepository->getClientEntity( $clientId, - $this->getIdentifier(), null, - $redirectUri + $redirectUri, + $this->getIdentifier() ); - if (($client instanceof ClientEntityInterface) === false) { + if (!$client instanceof ClientEntityInterface) { + $this->emitter->emit(new Event('client.authentication.failed', $request)); + throw OAuthServerException::invalidClient(); } - $state = array_key_exists('state', $request->getQueryParams()) - ? $request->getQueryParams()['state'] // $_GET['state'] - : null; + $scopes = $this->validateScopes($request, $client, $redirectUri); + $queryString = http_build_query($request->getQueryParams()); - // Validate any scopes that are in the request - $scopeParam = array_key_exists('scope', $request->getQueryParams()) - ? $request->getQueryParams()['scope'] // $_GET['scope'] - : ''; - $scopes = $this->validateScopes($scopeParam, $scopeDelimiter, $client); + // Check if the user has been validated + $userIdCookieParam = $this->getCookieParameter('oauth_user_id', $request, null); + if ($userIdCookieParam === null) { + return $this->loginUserResponseType->handle($client, $scopes, $queryString, $this->pathToPrivateKey); + } else { + try { + $userId = KeyCrypt::decrypt($userIdCookieParam, $this->pathToPublicKey); + } catch (\LogicException $e) { + throw OAuthServerException::serverError($e->getMessage()); + } + } - // Create a new authorization code - $authCode = new AuthCodeEntity(); - $authCode->setIdentifier(SecureKey::generate()); - $authCode->setExpiryDateTime((new \DateTime())->add($authCodeTTL)); - $authCode->setClient($client); - $authCode->setUserIdentifier($userEntity->getIdentifier()); + // Check the user has approved the request + $userApprovedCookieParam = $this->getCookieParameter('oauth_user_approved_client', $request, null); + if ($userApprovedCookieParam === null) { + return $this->authorizeClientResponseType->handle($client, $scopes, $queryString, $this->pathToPrivateKey); + } else { + try { + $userApprovedClient = KeyCrypt::decrypt($userApprovedCookieParam, $this->pathToPublicKey); + } catch (\LogicException $e) { + throw OAuthServerException::serverError($e->getMessage()); + } + } - // Associate scopes with the session and access token - foreach ($scopes as $scope) { - $authCode->addScope($scope); - }*/ + $stateParameter = $this->getQueryStringParameter('state', $request); + + $redirectUri = new Uri($redirectUri); + parse_str($redirectUri->getQuery(), $redirectPayload); + if ($stateParameter !== null) { + $redirectPayload['state'] = $stateParameter; + } + + if ($userApprovedClient === 1) { + $authCode = $this->issueAuthCode( + $this->authCodeTTL, + $client, + $userId, + $redirectUri, + $scopes + ); + $this->authCodeRepository->persistNewAuthCode($authCode); + + $redirectPayload['code'] = $authCode->getIdentifier(); + + return new Response( + 'php://memory', + 302, + [ + 'Location' => $redirectUri->withQuery(http_build_query($redirectPayload)), + ] + ); + } + + $exception = OAuthServerException::accessDenied('The user denied the request', (string) $redirectUri); + return $exception->generateHttpResponse(); } /** @@ -127,10 +178,41 @@ class AuthCodeGrant extends AbstractGrant && isset($request->getQueryParams()['response_type']) && $request->getQueryParams()['response_type'] === 'code' && isset($request->getQueryParams()['client_id']) - ) || ( - isset($request->getParsedBody()['grant_type']) - && $request->getParsedBody()['grant_type'] === 'authorization_code' - ) + ) || (parent::canRespondToRequest($request)) ); } + + /** + * Return the grant identifier that can be used in matching up requests + * + * @return string + */ + public function getIdentifier() + { + return 'authorization_code'; + } + + /** + * @inheritdoc + */ + public function respondToRequest( + ServerRequestInterface $request, + ResponseTypeInterface $responseType, + \DateInterval $accessTokenTTL + ) { + if ( + isset($request->getQueryParams()['response_type']) + && $request->getQueryParams()['response_type'] === 'code' + && isset($request->getQueryParams()['client_id']) + ) { + return $this->respondToAuthorizationRequest($request); + } elseif ( + isset($request->getParsedBody()['grant_type']) + && $request->getParsedBody()['grant_type'] === 'authorization_code' + ) { + return $this->respondToAccessTokenRequest($request, $responseType, $accessTokenTTL); + } else { + throw OAuthServerException::serverError('respondToRequest() should not have been called'); + } + } } From ac9955b393725ca7d361765e6438c2f28503fced Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 12 Feb 2016 11:30:59 +0000 Subject: [PATCH 15/30] Removed response type interfaces for auth code login + authorize because they were a stupid idea --- .../AuthorizeClientResponseTypeInterface.php | 22 ------------------- .../LoginUserResponseTypeInterface.php | 22 ------------------- 2 files changed, 44 deletions(-) delete mode 100644 src/ResponseTypes/AuthorizeClientResponseTypeInterface.php delete mode 100644 src/ResponseTypes/LoginUserResponseTypeInterface.php diff --git a/src/ResponseTypes/AuthorizeClientResponseTypeInterface.php b/src/ResponseTypes/AuthorizeClientResponseTypeInterface.php deleted file mode 100644 index 188a0332..00000000 --- a/src/ResponseTypes/AuthorizeClientResponseTypeInterface.php +++ /dev/null @@ -1,22 +0,0 @@ - Date: Fri, 12 Feb 2016 11:31:19 +0000 Subject: [PATCH 16/30] Suggest league/plates --- composer.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/composer.json b/composer.json index 89cabf93..21ee5356 100644 --- a/composer.json +++ b/composer.json @@ -65,5 +65,8 @@ "branch-alias": { "dev-V5-WIP": "5.0-dev" } + }, + "suggest": { + "league/plates": "Required for parsing authorization code templates" } } From 556c9fa782e1e309a7e464446e36bb481c365e42 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 12 Feb 2016 11:31:46 +0000 Subject: [PATCH 17/30] Require league/plates in the examples composer.json --- examples/composer.json | 3 +- examples/composer.lock | 63 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/examples/composer.json b/examples/composer.json index 0b85ac70..f0bf2564 100644 --- a/examples/composer.json +++ b/examples/composer.json @@ -7,7 +7,8 @@ ], "require": { "slim/slim": "3.0.*", - "league/oauth2-server": "dev-V5-WIP" + "league/oauth2-server": "dev-V5-WIP", + "league/plates": "^3.1" }, "autoload": { "psr-4": { diff --git a/examples/composer.lock b/examples/composer.lock index 251ab498..776fb37a 100644 --- a/examples/composer.lock +++ b/examples/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "ff6f832d21c141662627622e68079ca5", - "content-hash": "f08d5c7c3ede910150d75ad3c56d1b46", + "hash": "143453cc35e7f499b130b6460222dc5a", + "content-hash": "1ea46581fb6db25f323a37a45ef74f95", "packages": [ { "name": "container-interop/container-interop", @@ -145,9 +145,14 @@ { "name": "league/oauth2-server", "version": "dev-V5-WIP", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-server.git", + "reference": "95919a688e29c911d1e4e83112cacd18f719700f" + }, "dist": { "type": "path", - "url": "../", + "url": "https://api.github.com/repos/thephpleague/oauth2-server/zipball/95919a688e29c911d1e4e83112cacd18f719700f", "reference": "168e7640c6e8217b7e961151de522810b3edce6e", "shasum": null }, @@ -214,6 +219,58 @@ "server" ] }, + { + "name": "league/plates", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/plates.git", + "reference": "2d8569e9f140a70d6a05db38006926f7547cb802" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/plates/zipball/2d8569e9f140a70d6a05db38006926f7547cb802", + "reference": "2d8569e9f140a70d6a05db38006926f7547cb802", + "shasum": "" + }, + "require-dev": { + "mikey179/vfsstream": "~1.4.0", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~1.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Plates\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jonathan Reinink", + "email": "jonathan@reinink.ca", + "role": "Developer" + } + ], + "description": "Plates, the native PHP template system that's fast, easy to use and easy to extend.", + "homepage": "http://platesphp.com", + "keywords": [ + "league", + "package", + "templates", + "templating", + "views" + ], + "time": "2015-07-09 02:14:40" + }, { "name": "namshi/jose", "version": "6.1.0", From 1c913fe75e147fcb19ce5565e4df701436602a7c Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 12 Feb 2016 11:32:09 +0000 Subject: [PATCH 18/30] Added default basic HTML login + authorise templates --- .../DefaultTemplates/authorize_client.php | 35 +++++++++++++++++++ .../DefaultTemplates/login_user.php | 35 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 src/ResponseTypes/DefaultTemplates/authorize_client.php create mode 100644 src/ResponseTypes/DefaultTemplates/login_user.php diff --git a/src/ResponseTypes/DefaultTemplates/authorize_client.php b/src/ResponseTypes/DefaultTemplates/authorize_client.php new file mode 100644 index 00000000..8ef9dddd --- /dev/null +++ b/src/ResponseTypes/DefaultTemplates/authorize_client.php @@ -0,0 +1,35 @@ + + + + + Authorize <?=$this->e($client->getName)?> + + + + +

+ Authorize e($client->getName)?> +

+ +

+ Do you want to authorize e($client->getName)?> to access the following data? +

+ +
    + +
  • getIdentifier?>
  • + +
+ +
+ + +
+ +
+ + +
+ + + \ No newline at end of file diff --git a/src/ResponseTypes/DefaultTemplates/login_user.php b/src/ResponseTypes/DefaultTemplates/login_user.php new file mode 100644 index 00000000..75b6b529 --- /dev/null +++ b/src/ResponseTypes/DefaultTemplates/login_user.php @@ -0,0 +1,35 @@ + + + + + Login + + + + +

Login

+ + +
+ e($error)?> +
+ + +
+ + + + +
+ + + + +
+ + + +
+ + + \ No newline at end of file From 2025749fa4d874a60ae1b714d7fe44a0e18e022f Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 12 Feb 2016 11:55:41 +0000 Subject: [PATCH 19/30] Updated `respondToAuthorizationRequest` to use Plates templates instead of custom ResponseType --- src/Grant/AuthCodeGrant.php | 154 ++++++++++++++++++++++++++---------- 1 file changed, 113 insertions(+), 41 deletions(-) diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index 1e967437..b5cfd85d 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -3,23 +3,19 @@ namespace League\OAuth2\Server\Grant; use DateInterval; +use League\OAuth2\Server\Entities\Interfaces\UserEntityInterface; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface; -use League\OAuth2\Server\ResponseTypes\AuthorizeClientResponseTypeInterface; -use League\OAuth2\Server\ResponseTypes\LoginUserResponseTypeInterface; +use League\OAuth2\Server\Repositories\UserRepositoryInterface; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use League\OAuth2\Server\Utils\KeyCrypt; +use League\Plates\Engine; use Psr\Http\Message\ServerRequestInterface; use Zend\Diactoros\Response; use Zend\Diactoros\Uri; class AuthCodeGrant extends AbstractGrant { - /** - * @var \League\OAuth2\Server\ResponseTypes\LoginUserResponseTypeInterface - */ - private $loginUserResponseType; - /** * @var \DateInterval */ @@ -28,27 +24,41 @@ class AuthCodeGrant extends AbstractGrant * @var \League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface */ private $authCodeRepository; - /** - * @var \League\OAuth2\Server\ResponseTypes\AuthorizeClientResponseTypeInterface - */ - private $authorizeClientResponseType; /** - * @param \League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface $authCodeRepository - * @param \DateInterval $authCodeTTL - * @param \League\OAuth2\Server\ResponseTypes\LoginUserResponseTypeInterface $loginUserResponseType - * @param \League\OAuth2\Server\ResponseTypes\AuthorizeClientResponseTypeInterface $authorizeClientResponseType + * @var \League\OAuth2\Server\Repositories\UserRepositoryInterface + */ + private $userRepository; + + /** + * @var null|string + */ + private $pathToLoginTemplate; + + /** + * @var null|string + */ + private $pathToAuthorizeTemplate; + + /** + * @param \League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface $authCodeRepository + * @param \League\OAuth2\Server\Repositories\UserRepositoryInterface $userRepository + * @param \DateInterval $authCodeTTL + * @param string|null $pathToLoginTemplate + * @param string|null $pathToAuthorizeTemplate */ public function __construct( AuthCodeRepositoryInterface $authCodeRepository, + UserRepositoryInterface $userRepository, \DateInterval $authCodeTTL, - LoginUserResponseTypeInterface $loginUserResponseType, - AuthorizeClientResponseTypeInterface $authorizeClientResponseType + $pathToLoginTemplate = null, + $pathToAuthorizeTemplate = null ) { $this->authCodeRepository = $authCodeRepository; + $this->userRepository = $userRepository; $this->authCodeTTL = $authCodeTTL; - $this->loginUserResponseType = $loginUserResponseType; - $this->authorizeClientResponseType = $authorizeClientResponseType; + $this->pathToLoginTemplate = $pathToLoginTemplate; + $this->pathToAuthorizeTemplate = $pathToAuthorizeTemplate; } @@ -92,31 +102,99 @@ class AuthCodeGrant extends AbstractGrant $scopes = $this->validateScopes($request, $client, $redirectUri); $queryString = http_build_query($request->getQueryParams()); + $postbackUri = new Uri( + sprintf( + '//%s%s', + $request->getServerParams()['HTTP_HOST'], + $request->getServerParams()['REQUEST_URI'] + ) + ); + + $userId = null; + $userHasApprovedClient = $userHasApprovedClient = $this->getRequestParameter('action', null); // Check if the user has been validated - $userIdCookieParam = $this->getCookieParameter('oauth_user_id', $request, null); - if ($userIdCookieParam === null) { - return $this->loginUserResponseType->handle($client, $scopes, $queryString, $this->pathToPrivateKey); - } else { + $oauthCookie = $this->getCookieParameter('oauth_authorize_request', $request, null); + if ($oauthCookie !== null) { try { - $userId = KeyCrypt::decrypt($userIdCookieParam, $this->pathToPublicKey); + $oauthCookiePayload = json_decode(KeyCrypt::decrypt($oauthCookie, $this->pathToPublicKey)); + $userId = $oauthCookiePayload->user_id; + $userHasApprovedClient = $oauthCookiePayload->client_is_authorized; } catch (\LogicException $e) { throw OAuthServerException::serverError($e->getMessage()); } } - // Check the user has approved the request - $userApprovedCookieParam = $this->getCookieParameter('oauth_user_approved_client', $request, null); - if ($userApprovedCookieParam === null) { - return $this->authorizeClientResponseType->handle($client, $scopes, $queryString, $this->pathToPrivateKey); - } else { - try { - $userApprovedClient = KeyCrypt::decrypt($userApprovedCookieParam, $this->pathToPublicKey); - } catch (\LogicException $e) { - throw OAuthServerException::serverError($e->getMessage()); + // The username + password might be available in $_POST + $usernameParameter = $this->getRequestParameter('username', null); + $passwordParameter = $this->getRequestParameter('password', null); + + $loginError = null; + + // Assert if the user has logged in already + if ($userId === null && $usernameParameter !== null && $passwordParameter !== null) { + $userEntity = $this->userRepository->getUserEntityByUserCredentials( + $usernameParameter, + $passwordParameter + ); + + if ($userEntity instanceof UserEntityInterface) { + $userId = $userEntity->getIdentifier(); + } else { + $loginError = 'Incorrect username or password'; } } + // The user hasn't logged in yet so show a login form + if ($userId === null) { + $engine = new Engine(); + $html = $engine->render( + ($this->pathToLoginTemplate === null) + ? __DIR__ . '/../ResponseTypes/DefaultTemplates/login_user.php' + : $this->pathToLoginTemplate, + [ + 'error' => $loginError, + 'postback_uri' => (string) $postbackUri->withQuery($queryString), + ] + ); + + return new Response\HtmlResponse($html); + } + + + // The user hasn't approved the client yet so show an authorize form + if ($userId !== null && $userHasApprovedClient === null) { + $engine = new Engine(); + $html = $engine->render( + ($this->pathToLoginTemplate === null) + ? __DIR__ . '/../ResponseTypes/DefaultTemplates/authorize_client.php' + : $this->pathToAuthorizeTemplate, + [ + 'client' => $client, + 'scopes' => $scopes, + 'postback_uri' => (string) $postbackUri->withQuery($queryString), + ] + ); + + return new Response\HtmlResponse( + $html, + 200, + [ + 'Set-Cookie' => sprintf( + 'oauth_authorize_request=%s; Expires=%s', + KeyCrypt::encrypt( + json_encode([ + 'user_id' => $userId, + 'client_is_authorized' => null, + ]), + $this->pathToPrivateKey + ), + (new \DateTime())->add(new \DateInterval('PT5M'))->format('D, d M Y H:i:s e') + ), + ] + ); + } + $stateParameter = $this->getQueryStringParameter('state', $request); $redirectUri = new Uri($redirectUri); @@ -125,7 +203,7 @@ class AuthCodeGrant extends AbstractGrant $redirectPayload['state'] = $stateParameter; } - if ($userApprovedClient === 1) { + if ($userHasApprovedClient === true) { $authCode = $this->issueAuthCode( $this->authCodeTTL, $client, @@ -137,13 +215,7 @@ class AuthCodeGrant extends AbstractGrant $redirectPayload['code'] = $authCode->getIdentifier(); - return new Response( - 'php://memory', - 302, - [ - 'Location' => $redirectUri->withQuery(http_build_query($redirectPayload)), - ] - ); + return new Response\RedirectResponse($redirectUri->withQuery(http_build_query($redirectPayload))); } $exception = OAuthServerException::accessDenied('The user denied the request', (string) $redirectUri); From 5e326d9e4544f69ed09ef751180030ead7675776 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 12 Feb 2016 13:01:25 +0000 Subject: [PATCH 20/30] First commit of respondToAccessTokenRequest --- src/Grant/AuthCodeGrant.php | 48 ++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index b5cfd85d..763cfbdf 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -3,6 +3,7 @@ namespace League\OAuth2\Server\Grant; use DateInterval; +use League\OAuth2\Server\Entities\ClientEntity; use League\OAuth2\Server\Entities\Interfaces\UserEntityInterface; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface; @@ -213,7 +214,18 @@ class AuthCodeGrant extends AbstractGrant ); $this->authCodeRepository->persistNewAuthCode($authCode); - $redirectPayload['code'] = $authCode->getIdentifier(); + $redirectPayload['code'] = KeyCrypt::encrypt( + json_encode( + [ + 'client_id' => $authCode->getClient()->getIdentifier(), + 'auth_code_id' => $authCode->getIdentifier(), + 'scopes' => $authCode->getScopes(), + 'user_id' => $authCode->getUserIdentifier(), + 'expire_time' => $this->authCodeTTL->format('U'), + ] + ), + $this->pathToPrivateKey + ); return new Response\RedirectResponse($redirectUri->withQuery(http_build_query($redirectPayload))); } @@ -236,7 +248,41 @@ class AuthCodeGrant extends AbstractGrant ResponseTypeInterface $responseType, DateInterval $accessTokenTTL ) { + // Validate request + $client = $this->validateClient($request); + $scopes = $this->validateScopes($request, $client); + $encryptedAuthcode = $this->getRequestParameter('code', $request, null); + if ($encryptedAuthcode === null) { + throw OAuthServerException::invalidRequest('code'); + } + + // Validate the authorization code + try { + $authCodePayload = json_decode(KeyCrypt::decrypt($encryptedAuthcode, $this->pathToPrivateKey)); + if (time() > $authCodePayload->expire_time) { + throw OAuthServerException::invalidRequest('code', 'Authorization code has expired'); + } + } catch (\LogicException $e) { + throw OAuthServerException::invalidRequest('code'); + } + + $client = new ClientEntity(); + $client->setIdentifier($authCodePayload->client_id); + + // Issue and persist access token + $accessToken = $this->issueAccessToken( + $accessTokenTTL, + $client, + $authCodePayload->user_id, + $authCodePayload->scopes + ); + $this->accessTokenRepository->persistNewAccessToken($accessToken); + + // Inject access token into response type + $responseType->setAccessToken($accessToken); + + return $responseType; } /** From 9b97778618ac551866f53dd182f0baa6c67a95bd Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 12 Feb 2016 13:02:26 +0000 Subject: [PATCH 21/30] Removed unused dependency --- composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json index 21ee5356..55357050 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,6 @@ "php": ">=5.5.9", "league/event": "~2.1", "zendframework/zend-diactoros": "~1.1", - "namshi/jose": "^6.0", "lcobucci/jwt": "^3.1", "paragonie/random_compat": "^1.1" }, From f314154216f82b30a172f93a771f2583aff58f96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juli=C3=A1n=20Guti=C3=A9rrez?= Date: Fri, 12 Feb 2016 14:19:47 +0100 Subject: [PATCH 22/30] abstract access token validation --- examples/public/middleware_authentication.php | 4 ++-- .../AuthenticationServerMiddleware.php | 6 ++++-- src/Middleware/ResourceServerMiddleware.php | 8 +++++--- src/Server.php | 20 ++++++++++++++++--- 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/examples/public/middleware_authentication.php b/examples/public/middleware_authentication.php index f9b525dc..d928e19d 100644 --- a/examples/public/middleware_authentication.php +++ b/examples/public/middleware_authentication.php @@ -1,5 +1,7 @@ generateHttpResponse($response); } catch (\Exception $exception) { - $response->getBody()->write($exception->getMessage()); + $body = new Stream('php://temp', 'r+'); + $body->write($exception->getMessage()); - return $response->withStatus(500); + return $response->withStatus(500)->withBody($body); } if (in_array($response->getStatusCode(), [400, 401, 500])) { diff --git a/src/Middleware/ResourceServerMiddleware.php b/src/Middleware/ResourceServerMiddleware.php index 1794cdce..0f0b20ae 100644 --- a/src/Middleware/ResourceServerMiddleware.php +++ b/src/Middleware/ResourceServerMiddleware.php @@ -6,6 +6,7 @@ use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Server; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use Zend\Diactoros\Stream; class ResourceServerMiddleware { @@ -34,13 +35,14 @@ class ResourceServerMiddleware public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next) { try { - $request = $this->server->getResponseType()->determineAccessTokenInHeader($request); + $request = $this->server->validateRequest($request); } catch (OAuthServerException $exception) { return $exception->generateHttpResponse($response); } catch (\Exception $exception) { - $response->getBody()->write($exception->getMessage()); + $body = new Stream('php://temp', 'r+'); + $body->write($exception->getMessage()); - return $response->withStatus(500); + return $response->withStatus(500)->withBody($body); } // Pass the request and response on to the next responder in the chain diff --git a/src/Server.php b/src/Server.php index b63ad6ed..d8e420c1 100644 --- a/src/Server.php +++ b/src/Server.php @@ -26,7 +26,7 @@ class Server implements EmitterAwareInterface protected $enabledGrantTypes = []; /** - * @var DateInterval[] + * @var \DateInterval[] */ protected $grantTypeAccessTokenTTL = []; @@ -90,7 +90,7 @@ class Server implements EmitterAwareInterface * Enable a grant type on the server * * @param \League\OAuth2\Server\Grant\GrantTypeInterface $grantType - * @param DateInterval $accessTokenTTL + * @param \DateInterval $accessTokenTTL */ public function enableGrantType(GrantTypeInterface $grantType, \DateInterval $accessTokenTTL) { @@ -143,12 +143,26 @@ class Server implements EmitterAwareInterface return $tokenResponse->generateHttpResponse($response); } + /** + * Determine the access token validity + * + * @param \Psr\Http\Message\ServerRequestInterface $request + * + * @return \Psr\Http\Message\ServerRequestInterface + * + * @throws \League\OAuth2\Server\Exception\OAuthServerException + */ + public function validateRequest(ServerRequestInterface $request) + { + return $this->getResponseType()->determineAccessTokenInHeader($request); + } + /** * Get the token type that grants will return in the HTTP response * * @return ResponseTypeInterface */ - public function getResponseType() + protected function getResponseType() { if (!$this->responseType instanceof ResponseTypeInterface) { $this->responseType = new BearerTokenResponse( From 0115c41eeabff40d6981f179c99dadc1715d2daf Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 12 Feb 2016 13:32:58 +0000 Subject: [PATCH 23/30] Numerous bug fixes --- examples/public/auth_code.php | 78 ++++++++++++------- .../src/Repositories/AuthCodeRepository.php | 42 ++++++++++ src/Grant/AuthCodeGrant.php | 46 +++++++---- .../AuthCodeRepositoryInterface.php | 6 +- 4 files changed, 125 insertions(+), 47 deletions(-) create mode 100644 examples/src/Repositories/AuthCodeRepository.php diff --git a/examples/public/auth_code.php b/examples/public/auth_code.php index d0e76989..3b2642c0 100644 --- a/examples/public/auth_code.php +++ b/examples/public/auth_code.php @@ -1,10 +1,10 @@ enableGrantType($passwordGrant); - // App -$app = new App([Server::class => $server]); +$app = new App([ + Server::class => function () { -$app->any('/authorise', function (Request $request, Response $response) { - if (strtoupper($request->getMethod()) === 'GET') { - $response = $response->withHeader('Set-Cookie', $authCodeGrant->storeOriginalRequestParams) - } + // Init our repositories + $clientRepository = new ClientRepository(); + $scopeRepository = new ScopeRepository(); + $accessTokenRepository = new AccessTokenRepository(); + $userRepository = new UserRepository(); + $refreshTokenRepository = new RefreshTokenRepository(); + $authCodeRepository = new AuthCodeRepository(); + + $privateKeyPath = 'file://' . __DIR__ . '/../private.key'; + $publicKeyPath = 'file://' . __DIR__ . '/../public.key'; + + // Setup the authorization server + $server = new Server( + $clientRepository, + $accessTokenRepository, + $scopeRepository, + $privateKeyPath, + $publicKeyPath + ); + + // Enable the password grant on the server with a token TTL of 1 hour + $server->enableGrantType( + new \League\OAuth2\Server\Grant\AuthCodeGrant( + $authCodeRepository, + $refreshTokenRepository, + $userRepository, + new \DateInterval('PT10M') + ), + new \DateInterval('PT1H') + ); + + return $server; + }, +]); + +$app->any('/authorize', function (Request $request, Response $response) { + /** @var Server $server */ + $server = $this->get(Server::class); + try { + return $server->respondToRequest($request, $response); + } catch (OAuthServerException $e) { + return $e->generateHttpResponse($response); + } catch (\Exception $e) { + return $response->withStatus(500)->write($e->getMessage()); + } }); $app->post('/access_token', function (Request $request, Response $response) { /** @var Server $server */ $server = $this->get(Server::class); try { - return $server->respondToRequest($request); + return $server->respondToRequest($request, $response); } catch (OAuthServerException $e) { - return $e->generateHttpResponse(); + return $e->generateHttpResponse($response); } catch (\Exception $e) { return $response->withStatus(500)->write($e->getMessage()); } diff --git a/examples/src/Repositories/AuthCodeRepository.php b/examples/src/Repositories/AuthCodeRepository.php new file mode 100644 index 00000000..dd91c749 --- /dev/null +++ b/examples/src/Repositories/AuthCodeRepository.php @@ -0,0 +1,42 @@ +authCodeRepository = $authCodeRepository; + $this->refreshTokenRepository = $refreshTokenRepository; $this->userRepository = $userRepository; $this->authCodeTTL = $authCodeTTL; $this->pathToLoginTemplate = $pathToLoginTemplate; @@ -242,6 +250,7 @@ class AuthCodeGrant extends AbstractGrant * @param \DateInterval $accessTokenTTL * * @return \League\OAuth2\Server\ResponseTypes\ResponseTypeInterface + * @throws \League\OAuth2\Server\Exception\OAuthServerException */ protected function respondToAccessTokenRequest( ServerRequestInterface $request, @@ -250,37 +259,44 @@ class AuthCodeGrant extends AbstractGrant ) { // Validate request $client = $this->validateClient($request); - $scopes = $this->validateScopes($request, $client); - $encryptedAuthcode = $this->getRequestParameter('code', $request, null); + $encryptedAuthCode = $this->getRequestParameter('code', $request, null); - if ($encryptedAuthcode === null) { + if ($encryptedAuthCode === null) { throw OAuthServerException::invalidRequest('code'); } // Validate the authorization code try { - $authCodePayload = json_decode(KeyCrypt::decrypt($encryptedAuthcode, $this->pathToPrivateKey)); + $authCodePayload = json_decode(KeyCrypt::decrypt($encryptedAuthCode, $this->pathToPrivateKey)); if (time() > $authCodePayload->expire_time) { throw OAuthServerException::invalidRequest('code', 'Authorization code has expired'); } + + if ($this->authCodeRepository->isAuthCodeRevoked($authCodePayload->auth_code_id) === true) { + throw OAuthServerException::invalidRequest('code', 'Authorization code has been revoked'); + } + + if ($authCodePayload->client_id !== $client->getIdentifier()) { + throw OAuthServerException::invalidRequest('code', 'Authorization code was not issued to this client'); + } } catch (\LogicException $e) { throw OAuthServerException::invalidRequest('code'); } - $client = new ClientEntity(); - $client->setIdentifier($authCodePayload->client_id); - - // Issue and persist access token + // Issue and persist access + refresh tokens $accessToken = $this->issueAccessToken( $accessTokenTTL, $client, $authCodePayload->user_id, $authCodePayload->scopes ); + $refreshToken = $this->issueRefreshToken($accessToken); $this->accessTokenRepository->persistNewAccessToken($accessToken); + $this->refreshTokenRepository->persistNewRefreshToken($refreshToken); - // Inject access token into response type + // Inject tokens into response type $responseType->setAccessToken($accessToken); + $responseType->setRefreshToken($refreshToken); return $responseType; } diff --git a/src/Repositories/AuthCodeRepositoryInterface.php b/src/Repositories/AuthCodeRepositoryInterface.php index be341ca0..a6742092 100644 --- a/src/Repositories/AuthCodeRepositoryInterface.php +++ b/src/Repositories/AuthCodeRepositoryInterface.php @@ -21,11 +21,9 @@ interface AuthCodeRepositoryInterface extends RepositoryInterface /** * Persists a new auth code to permanent storage * - * @param \League\OAuth2\Server\Entities\Interfaces\AuthCodeEntityInterface $authCodeEntityInterface - * - * @return + * @param \League\OAuth2\Server\Entities\Interfaces\AuthCodeEntityInterface $authCodeEntity */ - public function persistNewAuthCode(AuthCodeEntityInterface $authCodeEntityInterface); + public function persistNewAuthCode(AuthCodeEntityInterface $authCodeEntity); /** * Revoke an auth code From f6cc8bbb42050769a9f3b427ba5fc679076bf11d Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 12 Feb 2016 14:17:49 +0000 Subject: [PATCH 24/30] Import namespace --- examples/public/auth_code.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/public/auth_code.php b/examples/public/auth_code.php index 3b2642c0..761f7ae3 100644 --- a/examples/public/auth_code.php +++ b/examples/public/auth_code.php @@ -1,6 +1,7 @@ enableGrantType( - new \League\OAuth2\Server\Grant\AuthCodeGrant( + new AuthCodeGrant( $authCodeRepository, $refreshTokenRepository, $userRepository, From 6dd4caf0560ae2c8f6fe7dc951a00eb0c5079ac6 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 12 Feb 2016 14:17:58 +0000 Subject: [PATCH 25/30] Fix for redirect_uri --- examples/src/Repositories/ClientRepository.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/src/Repositories/ClientRepository.php b/examples/src/Repositories/ClientRepository.php index 1130121e..d708a4a7 100644 --- a/examples/src/Repositories/ClientRepository.php +++ b/examples/src/Repositories/ClientRepository.php @@ -15,7 +15,7 @@ class ClientRepository implements ClientRepositoryInterface 'myawesomeapp' => [ 'secret' => password_hash('abc123', PASSWORD_BCRYPT), 'name' => 'My Awesome App', - 'redirect_uri' => '' + 'redirect_uri' => 'http://foo/bar' ] ]; @@ -30,7 +30,7 @@ class ClientRepository implements ClientRepositoryInterface } // Check if redirect URI is valid - if ($redirectUri !== null && $redirectUri !== $clients[$clientIdentifier]['redirectUri']) { + if ($redirectUri !== null && $redirectUri !== $clients[$clientIdentifier]['redirect_uri']) { return null; } From 4234b69f3a4ee4c6647d5a105340a69854592adf Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 12 Feb 2016 14:18:10 +0000 Subject: [PATCH 26/30] Fix for method calls --- src/ResponseTypes/DefaultTemplates/authorize_client.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ResponseTypes/DefaultTemplates/authorize_client.php b/src/ResponseTypes/DefaultTemplates/authorize_client.php index 8ef9dddd..81587174 100644 --- a/src/ResponseTypes/DefaultTemplates/authorize_client.php +++ b/src/ResponseTypes/DefaultTemplates/authorize_client.php @@ -2,22 +2,22 @@ - Authorize <?=$this->e($client->getName)?> + Authorize <?=$this->e($client->getName())?>

- Authorize e($client->getName)?> + Authorize e($client->getName())?>

- Do you want to authorize e($client->getName)?> to access the following data? + Do you want to authorize e($client->getName())?> to access the following data?

    -
  • getIdentifier?>
  • +
  • getIdentifier()?>
From 796106b6c1ca397ccc99857ff0764c2264485c04 Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 12 Feb 2016 14:18:34 +0000 Subject: [PATCH 27/30] Fix for non-imported namespace --- src/Server.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Server.php b/src/Server.php index b63ad6ed..52605297 100644 --- a/src/Server.php +++ b/src/Server.php @@ -2,6 +2,7 @@ namespace League\OAuth2\Server; +use DateInterval; use League\Event\EmitterAwareInterface; use League\Event\EmitterAwareTrait; use League\OAuth2\Server\Exception\OAuthServerException; @@ -92,7 +93,7 @@ class Server implements EmitterAwareInterface * @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); From 1a5030200ad190809955d8bfc9006699f9165c5b Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 12 Feb 2016 14:18:45 +0000 Subject: [PATCH 28/30] The response may be a PSR response which is valid --- src/Server.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Server.php b/src/Server.php index 52605297..c7639db3 100644 --- a/src/Server.php +++ b/src/Server.php @@ -137,7 +137,11 @@ class Server implements EmitterAwareInterface } } - if (!$tokenResponse instanceof ResponseTypeInterface) { + if ($tokenResponse instanceof ResponseInterface) { + return $tokenResponse; + } + + if ($tokenResponse instanceof ResponseTypeInterface === false) { return OAuthServerException::unsupportedGrantType()->generateHttpResponse($response); } From 85b9412813edbc779f116a8406c63a9779f6c2ec Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 12 Feb 2016 14:18:52 +0000 Subject: [PATCH 29/30] Multiple fixes --- src/Grant/AuthCodeGrant.php | 50 ++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index 67c55db2..0a26d423 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -3,6 +3,7 @@ namespace League\OAuth2\Server\Grant; use DateInterval; +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\AuthCodeRepositoryInterface; @@ -11,6 +12,7 @@ use League\OAuth2\Server\Repositories\UserRepositoryInterface; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use League\OAuth2\Server\Utils\KeyCrypt; use League\Plates\Engine; +use League\Event\Event; use Psr\Http\Message\ServerRequestInterface; use Zend\Diactoros\Response; use Zend\Diactoros\Uri; @@ -66,8 +68,12 @@ class AuthCodeGrant extends AbstractGrant $this->refreshTokenRepository = $refreshTokenRepository; $this->userRepository = $userRepository; $this->authCodeTTL = $authCodeTTL; - $this->pathToLoginTemplate = $pathToLoginTemplate; - $this->pathToAuthorizeTemplate = $pathToAuthorizeTemplate; + $this->pathToLoginTemplate = ($pathToLoginTemplate === null) + ? __DIR__ . '/../ResponseTypes/DefaultTemplates/login_user.php' + : $this->pathToLoginTemplate; + $this->pathToAuthorizeTemplate = ($pathToLoginTemplate === null) + ? __DIR__ . '/../ResponseTypes/DefaultTemplates/authorize_client.php' + : $this->pathToAuthorizeTemplate; } @@ -103,7 +109,7 @@ class AuthCodeGrant extends AbstractGrant $this->getIdentifier() ); - if (!$client instanceof ClientEntityInterface) { + if ($client instanceof ClientEntityInterface === false) { $this->emitter->emit(new Event('client.authentication.failed', $request)); throw OAuthServerException::invalidClient(); @@ -120,23 +126,27 @@ class AuthCodeGrant extends AbstractGrant ); $userId = null; - $userHasApprovedClient = $userHasApprovedClient = $this->getRequestParameter('action', null); + $userHasApprovedClient = null; + if ($this->getRequestParameter('action', $request, null) !== null) { + $userHasApprovedClient = ($this->getRequestParameter('action', $request) === 'approve'); + } - // Check if the user has been validated + // Check if the user has been authenticated $oauthCookie = $this->getCookieParameter('oauth_authorize_request', $request, null); if ($oauthCookie !== null) { try { $oauthCookiePayload = json_decode(KeyCrypt::decrypt($oauthCookie, $this->pathToPublicKey)); - $userId = $oauthCookiePayload->user_id; - $userHasApprovedClient = $oauthCookiePayload->client_is_authorized; + if (is_object($oauthCookiePayload)) { + $userId = $oauthCookiePayload->user_id; + } } catch (\LogicException $e) { throw OAuthServerException::serverError($e->getMessage()); } } // The username + password might be available in $_POST - $usernameParameter = $this->getRequestParameter('username', null); - $passwordParameter = $this->getRequestParameter('password', null); + $usernameParameter = $this->getRequestParameter('username', $request, null); + $passwordParameter = $this->getRequestParameter('password', $request, null); $loginError = null; @@ -156,11 +166,9 @@ class AuthCodeGrant extends AbstractGrant // The user hasn't logged in yet so show a login form if ($userId === null) { - $engine = new Engine(); + $engine = new Engine(dirname($this->pathToLoginTemplate)); $html = $engine->render( - ($this->pathToLoginTemplate === null) - ? __DIR__ . '/../ResponseTypes/DefaultTemplates/login_user.php' - : $this->pathToLoginTemplate, + 'login_user', [ 'error' => $loginError, 'postback_uri' => (string) $postbackUri->withQuery($queryString), @@ -173,11 +181,9 @@ class AuthCodeGrant extends AbstractGrant // The user hasn't approved the client yet so show an authorize form if ($userId !== null && $userHasApprovedClient === null) { - $engine = new Engine(); + $engine = new Engine(dirname($this->pathToAuthorizeTemplate)); $html = $engine->render( - ($this->pathToLoginTemplate === null) - ? __DIR__ . '/../ResponseTypes/DefaultTemplates/authorize_client.php' - : $this->pathToAuthorizeTemplate, + 'authorize_client', [ 'client' => $client, 'scopes' => $scopes, @@ -191,13 +197,12 @@ class AuthCodeGrant extends AbstractGrant [ 'Set-Cookie' => sprintf( 'oauth_authorize_request=%s; Expires=%s', - KeyCrypt::encrypt( + urlencode(KeyCrypt::encrypt( json_encode([ - 'user_id' => $userId, - 'client_is_authorized' => null, + 'user_id' => $userId, ]), $this->pathToPrivateKey - ), + )), (new \DateTime())->add(new \DateInterval('PT5M'))->format('D, d M Y H:i:s e') ), ] @@ -308,8 +313,7 @@ class AuthCodeGrant extends AbstractGrant { return ( ( - strtoupper($request->getMethod()) === 'GET' - && isset($request->getQueryParams()['response_type']) + isset($request->getQueryParams()['response_type']) && $request->getQueryParams()['response_type'] === 'code' && isset($request->getQueryParams()['client_id']) ) || (parent::canRespondToRequest($request)) From d95958bae4f296c1e3f4a1ae0877f7f8a1338b9b Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Fri, 12 Feb 2016 14:28:24 +0000 Subject: [PATCH 30/30] Small fixes --- src/Grant/AuthCodeGrant.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index 0a26d423..560b8ad1 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -74,6 +74,7 @@ class AuthCodeGrant extends AbstractGrant $this->pathToAuthorizeTemplate = ($pathToLoginTemplate === null) ? __DIR__ . '/../ResponseTypes/DefaultTemplates/authorize_client.php' : $this->pathToAuthorizeTemplate; + $this->refreshTokenTTL = new \DateInterval('P1M'); } @@ -234,7 +235,7 @@ class AuthCodeGrant extends AbstractGrant 'auth_code_id' => $authCode->getIdentifier(), 'scopes' => $authCode->getScopes(), 'user_id' => $authCode->getUserIdentifier(), - 'expire_time' => $this->authCodeTTL->format('U'), + 'expire_time' => (new \DateTime())->add($this->authCodeTTL)->format('U'), ] ), $this->pathToPrivateKey @@ -272,7 +273,7 @@ class AuthCodeGrant extends AbstractGrant // Validate the authorization code try { - $authCodePayload = json_decode(KeyCrypt::decrypt($encryptedAuthCode, $this->pathToPrivateKey)); + $authCodePayload = json_decode(KeyCrypt::decrypt($encryptedAuthCode, $this->pathToPublicKey)); if (time() > $authCodePayload->expire_time) { throw OAuthServerException::invalidRequest('code', 'Authorization code has expired'); } @@ -285,7 +286,7 @@ class AuthCodeGrant extends AbstractGrant throw OAuthServerException::invalidRequest('code', 'Authorization code was not issued to this client'); } } catch (\LogicException $e) { - throw OAuthServerException::invalidRequest('code'); + throw OAuthServerException::invalidRequest('code', null, 'Cannot decrypt the authorization code'); } // Issue and persist access + refresh tokens