From b7064befe4f02b6e5b82d2860cc93d619a46457e Mon Sep 17 00:00:00 2001 From: Alex Bilbie Date: Sun, 10 Apr 2016 10:07:08 +0100 Subject: [PATCH] Checkin --- src/Grant/AbstractGrant.php | 27 ++ src/Grant/AuthCodeGrant.php | 328 +++++++++------------- src/Grant/GrantTypeInterface.php | 37 ++- src/Grant/ImplicitGrant.php | 2 +- src/RequestTypes/AuthorizationRequest.php | 165 +++++++++++ src/Server.php | 22 ++ 6 files changed, 378 insertions(+), 203 deletions(-) create mode 100644 src/RequestTypes/AuthorizationRequest.php diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index b7fcda88..739fb1c5 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -23,6 +23,7 @@ use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; use League\OAuth2\Server\Repositories\UserRepositoryInterface; use League\OAuth2\Server\RequestEvent; +use League\OAuth2\Server\RequestTypes\AuthorizationRequest; use Psr\Http\Message\ServerRequestInterface; /** @@ -175,11 +176,13 @@ abstract class AbstractGrant implements GrantTypeInterface is_string($client->getRedirectUri()) && (strcmp($client->getRedirectUri(), $redirectUri) !== 0) ) { + $this->getEmitter()->emit(new RequestEvent('client.authentication.failed', $request)); throw OAuthServerException::invalidClient(); } elseif ( is_array($client->getRedirectUri()) && in_array($redirectUri, $client->getRedirectUri()) === false ) { + $this->getEmitter()->emit(new RequestEvent('client.authentication.failed', $request)); throw OAuthServerException::invalidClient(); } } @@ -401,4 +404,28 @@ abstract class AbstractGrant implements GrantTypeInterface && $requestParameters['grant_type'] === $this->getIdentifier() ); } + + /** + * @inheritdoc + */ + public function canRespondToAuthorizationRequest(ServerRequestInterface $request) + { + return false; + } + + /** + * @inheritdoc + */ + public function validateAuthorizationRequest(ServerRequestInterface $request) + { + throw new \LogicException('This grant cannot validate an authorization request'); + } + + /** + * @inheritdoc + */ + public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest) + { + throw new \LogicException('This grant cannot complete an authorization request'); + } } diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index 210dc9a1..5178ed5a 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -10,6 +10,7 @@ use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\Repositories\UserRepositoryInterface; use League\OAuth2\Server\RequestEvent; +use League\OAuth2\Server\RequestTypes\AuthorizationRequest; use League\OAuth2\Server\ResponseTypes\HtmlResponse; use League\OAuth2\Server\ResponseTypes\RedirectResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; @@ -45,191 +46,6 @@ class AuthCodeGrant extends AbstractAuthorizeGrant $this->templateRenderer = $templateRenderer; } - /** - * Respond to an authorization request. - * - * @param \Psr\Http\Message\ServerRequestInterface $request - * - * @throws \League\OAuth2\Server\Exception\OAuthServerException - * - * @return \Psr\Http\Message\ResponseInterface - */ - protected function respondToAuthorizationRequest( - ServerRequestInterface $request - ) { - $clientId = $this->getQueryStringParameter( - 'client_id', - $request, - $this->getServerParameter('PHP_AUTH_USER', $request) - ); - if (is_null($clientId)) { - throw OAuthServerException::invalidRequest('client_id'); - } - - $client = $this->clientRepository->getClientEntity( - $clientId, - $this->getIdentifier() - ); - - if ($client instanceof ClientEntityInterface === false) { - $this->getEmitter()->emit(new RequestEvent('client.authentication.failed', $request)); - throw OAuthServerException::invalidClient(); - } - - $redirectUriParameter = $this->getQueryStringParameter('redirect_uri', $request, $client->getRedirectUri()); - if ($redirectUriParameter !== $client->getRedirectUri()) { - $this->getEmitter()->emit(new RequestEvent('client.authentication.failed', $request)); - throw OAuthServerException::invalidClient(); - } - - $scopes = $this->validateScopes( - $this->getQueryStringParameter('scope', $request), - $client, - $client->getRedirectUri() - ); - - $postbackUri = sprintf( - '//%s%s', - $request->getServerParams()['HTTP_HOST'], - $request->getServerParams()['REQUEST_URI'] - ); - - $userId = null; - $userHasApprovedClient = null; - if ($this->getRequestParameter('action', $request, null) !== null) { - $userHasApprovedClient = ($this->getRequestParameter('action', $request) === 'approve'); - } - - // Check if the user has been authenticated - $oauthCookie = $this->getCookieParameter('oauth_authorize_request', $request, null); - if ($oauthCookie !== null) { - try { - $oauthCookiePayload = json_decode($this->decrypt($oauthCookie)); - 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', $request, null); - $passwordParameter = $this->getRequestParameter('password', $request, null); - - $loginError = null; - - // Assert if the user has logged in already - if ($userId === null && $usernameParameter !== null && $passwordParameter !== null) { - $userEntity = $this->userRepository->getUserEntityByUserCredentials( - $usernameParameter, - $passwordParameter, - $this->getIdentifier(), - $client - ); - - 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) { - $html = $this->getTemplateRenderer()->renderLogin([ - 'error' => $loginError, - 'postback_uri' => $this->makeRedirectUri( - $postbackUri, - $request->getQueryParams() - ), - ]); - - $htmlResponse = new HtmlResponse(); - $htmlResponse->setStatusCode(403); - $htmlResponse->setHtml($html); - - return $htmlResponse; - } - - // The user hasn't approved the client yet so show an authorize form - if ($userId !== null && $userHasApprovedClient === null) { - $html = $this->getTemplateRenderer()->renderAuthorize([ - 'client' => $client, - 'scopes' => $scopes, - 'postback_uri' => $this->makeRedirectUri( - $postbackUri, - $request->getQueryParams() - ), - ]); - - $htmlResponse = new HtmlResponse(); - $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 = $client->getRedirectUri(); - $redirectPayload = []; - - $stateParameter = $this->getQueryStringParameter('state', $request); - if ($stateParameter !== null) { - $redirectPayload['state'] = $stateParameter; - } - - // THe user approved the client, redirect them back with an auth code - if ($userHasApprovedClient === true) { - - // Finalize the requested scopes - $scopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client, $userId); - - $authCode = $this->issueAuthCode( - $this->authCodeTTL, - $client, - $userId, - $redirectUri, - $scopes - ); - - $redirectPayload['code'] = $this->encrypt( - json_encode( - [ - 'client_id' => $authCode->getClient()->getIdentifier(), - 'redirect_uri' => $authCode->getRedirectUri(), - 'auth_code_id' => $authCode->getIdentifier(), - 'scopes' => $authCode->getScopes(), - 'user_id' => $authCode->getUserIdentifier(), - 'expire_time' => (new \DateTime())->add($this->authCodeTTL)->format('U'), - ] - ) - ); - - $response = new RedirectResponse(); - $response->setRedirectUri( - $this->makeRedirectUri( - $redirectUri, - $redirectPayload - ) - ); - - return $response; - } - - // The user denied the client, redirect them back with an error - throw OAuthServerException::accessDenied('The user denied the request', (string) $redirectUri); - } - /** * Respond to an access token request. * @@ -324,20 +140,6 @@ class AuthCodeGrant extends AbstractAuthorizeGrant return $this->respondToAccessTokenRequest($request, $responseType, $accessTokenTTL); } - /** - * {@inheritdoc} - */ - public function canRespondToRequest(ServerRequestInterface $request) - { - return - ( - array_key_exists('response_type', $request->getQueryParams()) - && $request->getQueryParams()['response_type'] === 'code' - && isset($request->getQueryParams()['client_id']) - ) - || parent::canRespondToRequest($request); - } - /** * Return the grant identifier that can be used in matching up requests. * @@ -347,4 +149,132 @@ class AuthCodeGrant extends AbstractAuthorizeGrant { return 'authorization_code'; } + + /** + * @inheritdoc + */ + public function canRespondToAuthorizationRequest(ServerRequestInterface $request) + { + return ( + array_key_exists('response_type', $request->getQueryParams()) + && $request->getQueryParams()['response_type'] === 'code' + && isset($request->getQueryParams()['client_id']) + ); + } + + /** + * @inheritdoc + */ + public function validateAuthorizationRequest(ServerRequestInterface $request) + { + $clientId = $this->getQueryStringParameter( + 'client_id', + $request, + $this->getServerParameter('PHP_AUTH_USER', $request) + ); + if (is_null($clientId)) { + throw OAuthServerException::invalidRequest('client_id'); + } + + $client = $this->clientRepository->getClientEntity( + $clientId, + $this->getIdentifier() + ); + + if ($client instanceof ClientEntityInterface === false) { + $this->getEmitter()->emit(new RequestEvent('client.authentication.failed', $request)); + throw OAuthServerException::invalidClient(); + } + + $redirectUri = $this->getQueryStringParameter('redirect_uri', $request); + if ($redirectUri !== null) { + if ( + is_string($client->getRedirectUri()) + && (strcmp($client->getRedirectUri(), $redirectUri) !== 0) + ) { + $this->getEmitter()->emit(new RequestEvent('client.authentication.failed', $request)); + throw OAuthServerException::invalidClient(); + } elseif ( + is_array($client->getRedirectUri()) + && in_array($redirectUri, $client->getRedirectUri()) === false + ) { + $this->getEmitter()->emit(new RequestEvent('client.authentication.failed', $request)); + throw OAuthServerException::invalidClient(); + } + } + + $scopes = $this->validateScopes( + $this->getQueryStringParameter('scope', $request), + $client, + $client->getRedirectUri() + ); + + $stateParameter = $this->getQueryStringParameter('state', $request); + + $authorizationRequest = new AuthorizationRequest(); + $authorizationRequest->setGrantTypeId($this->getIdentifier()); + $authorizationRequest->setClient($client); + $authorizationRequest->setRedirectUri($redirectUri); + $authorizationRequest->setState($stateParameter); + $authorizationRequest->setScopes($scopes); + + return $authorizationRequest; + } + + /** + * @inheritdoc + */ + public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest) + { + if ($authorizationRequest->getUser() instanceof UserEntityInterface === false) { + throw new \LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest'); + } + + // The user approved the client, redirect them back with an auth code + if ($authorizationRequest->isAuthorizationApproved() === true) { + + $authCode = $this->issueAuthCode( + $this->authCodeTTL, + $authorizationRequest->getClient(), + $authorizationRequest->getUser()->getIdentifier(), + $authorizationRequest->getRedirectUri(), + $authorizationRequest->getScopes() + ); + + $redirectPayload['code'] = $this->encrypt( + json_encode( + [ + 'client_id' => $authCode->getClient()->getIdentifier(), + 'redirect_uri' => $authCode->getRedirectUri(), + 'auth_code_id' => $authCode->getIdentifier(), + 'scopes' => $authCode->getScopes(), + 'user_id' => $authCode->getUserIdentifier(), + 'expire_time' => (new \DateTime())->add($this->authCodeTTL)->format('U'), + ] + ) + ); + + $finalRedirectUri = ($authorizationRequest->getRedirectUri() === null) + ? is_array($authorizationRequest->getClient()->getRedirectUri()) + ? $authorizationRequest->getClient()->getRedirectUri()[0] + : $authorizationRequest->getClient()->getRedirectUri() + : $authorizationRequest->getRedirectUri(); + + $response = new RedirectResponse(); + $response->setRedirectUri( + $this->makeRedirectUri( + $finalRedirectUri, + $redirectPayload + ) + ); + + return $response; + } + + // The user denied the client, redirect them back with an error + throw OAuthServerException::accessDenied( + 'The user denied the request', + (string) $authorizationRequest->getRedirectUri() + ); + } } diff --git a/src/Grant/GrantTypeInterface.php b/src/Grant/GrantTypeInterface.php index dd35c6ce..fc26d925 100644 --- a/src/Grant/GrantTypeInterface.php +++ b/src/Grant/GrantTypeInterface.php @@ -15,6 +15,7 @@ use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; +use League\OAuth2\Server\RequestTypes\AuthorizationRequest; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use Psr\Http\Message\ServerRequestInterface; @@ -52,14 +53,44 @@ interface GrantTypeInterface extends EmitterAwareInterface \DateInterval $accessTokenTTL ); + /** + * The grant type should return true if it is able to response to an authorization request + * + * @param \Psr\Http\Message\ServerRequestInterface $request + * + * @return bool + */ + public function canRespondToAuthorizationRequest(ServerRequestInterface $request); + + /** + * If the grant can respond to an authorization request this method should be called to validate the parameters of + * the request. + * + * If the validation is successful an AuthorizationRequest object will be returned. This object can be safely + * serialized in a user's session, and can be used during user authentication and authorization. + * + * @param \Psr\Http\Message\ServerRequestInterface $request + * + * @return AuthorizationRequest + */ + public function validateAuthorizationRequest(ServerRequestInterface $request); + + /** + * Once a user has authenticated and authorized the client the grant can complete the authorization request. + * The AuthorizationRequest object's $userId property must be set to the authenticated user and the + * $authorizationApproved property must reflect their desire to authorize or deny the client. + * + * @param \League\OAuth2\Server\RequestTypes\AuthorizationRequest $authorizationRequest + * + * @return \League\OAuth2\Server\ResponseTypes\ResponseTypeInterface + */ + public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest); + /** * The grant type should return true if it is able to respond to this request. * * For example most grant types will check that the $_POST['grant_type'] property matches it's identifier property. * - * Some grants, such as the authorization code grant can respond to multiple requests - * - i.e. a client requesting an authorization code and requesting an access token - * * @param \Psr\Http\Message\ServerRequestInterface $request * * @return bool diff --git a/src/Grant/ImplicitGrant.php b/src/Grant/ImplicitGrant.php index fefc7189..497542d5 100644 --- a/src/Grant/ImplicitGrant.php +++ b/src/Grant/ImplicitGrant.php @@ -29,7 +29,7 @@ class ImplicitGrant extends AbstractAuthorizeGrant /** * {@inheritdoc} */ - public function canRespondToRequest(ServerRequestInterface $request) + public function canRespondToAccessTokenRequest(ServerRequestInterface $request) { return (array_key_exists('response_type', $request->getQueryParams()) && $request->getQueryParams()['response_type'] === 'token'); diff --git a/src/RequestTypes/AuthorizationRequest.php b/src/RequestTypes/AuthorizationRequest.php new file mode 100644 index 00000000..06a06672 --- /dev/null +++ b/src/RequestTypes/AuthorizationRequest.php @@ -0,0 +1,165 @@ +grantTypeId; + } + + /** + * @param string $grantTypeId + */ + public function setGrantTypeId($grantTypeId) + { + $this->grantTypeId = $grantTypeId; + } + + /** + * @return ClientEntityInterface + */ + public function getClient() + { + return $this->client; + } + + /** + * @param ClientEntityInterface $client + */ + public function setClient(ClientEntityInterface $client) + { + $this->client = $client; + } + + /** + * @return UserEntityInterface + */ + public function getUser() + { + return $this->user; + } + + /** + * @param UserEntityInterface $user + */ + public function setUser(UserEntityInterface $user) + { + $this->user = $user; + } + + /** + * @return \League\OAuth2\Server\Entities\ScopeEntityInterface[] + */ + public function getScopes() + { + return $this->scopes; + } + + /** + * @param \League\OAuth2\Server\Entities\ScopeEntityInterface[] $scopes + */ + public function setScopes($scopes) + { + $this->scopes = $scopes; + } + + /** + * @return boolean + */ + public function isAuthorizationApproved() + { + return $this->authorizationApproved; + } + + /** + * @param boolean $authorizationApproved + */ + public function setAuthorizationApproved($authorizationApproved) + { + $this->authorizationApproved = $authorizationApproved; + } + + /** + * @return string + */ + public function getRedirectUri() + { + return $this->redirectUri; + } + + /** + * @param string $redirectUri + */ + public function setRedirectUri($redirectUri) + { + $this->redirectUri = $redirectUri; + } + + /** + * @return string + */ + public function getState() + { + return $this->state; + } + + /** + * @param string $state + */ + public function setState($state) + { + $this->state = $state; + } + +} diff --git a/src/Server.php b/src/Server.php index ba18ce91..1dfc1312 100644 --- a/src/Server.php +++ b/src/Server.php @@ -124,6 +124,28 @@ class Server implements EmitterAwareInterface $this->grantTypeAccessTokenTTL[$grantType->getIdentifier()] = $accessTokenTTL; } + /** + * @param \Psr\Http\Message\ServerRequestInterface $request + * + * @throws \League\OAuth2\Server\Exception\OAuthServerException + */ + public function respondToAuthorizationRequest(ServerRequestInterface $request) + { + $authRequest = null; + while ($authRequest === null && $grantType = array_shift($this->enabledGrantTypes)) { + /** @var \League\OAuth2\Server\Grant\GrantTypeInterface $grantType */ + if ($grantType->canRespondToAccessTokenRequest($request)) { + $authRequest = $grantType->respondToRequest( + $request, + $this->getResponseType(), + $this->grantTypeAccessTokenTTL[$grantType->getIdentifier()] + ); + } + } + + throw OAuthServerException::unsupportedGrantType(); + } + /** * Return an access token response. *