Merge remote-tracking branch 'upstream/8.0.0' into protect-client-entity-gets

This commit is contained in:
sephster
2019-06-23 17:23:40 +01:00
35 changed files with 780 additions and 249 deletions

View File

@@ -50,7 +50,7 @@ class AuthorizationServer implements EmitterAwareInterface
protected $publicKey;
/**
* @var null|ResponseTypeInterface
* @var ResponseTypeInterface
*/
protected $responseType;
@@ -104,8 +104,16 @@ class AuthorizationServer implements EmitterAwareInterface
if ($privateKey instanceof CryptKey === false) {
$privateKey = new CryptKey($privateKey);
}
$this->privateKey = $privateKey;
$this->encryptionKey = $encryptionKey;
if ($responseType === null) {
$responseType = new BearerTokenResponse();
} else {
$responseType = clone $responseType;
}
$this->responseType = $responseType;
}
@@ -205,16 +213,15 @@ class AuthorizationServer implements EmitterAwareInterface
*/
protected function getResponseType()
{
if ($this->responseType instanceof ResponseTypeInterface === false) {
$this->responseType = new BearerTokenResponse();
$responseType = clone $this->responseType;
if ($responseType instanceof AbstractResponseType) {
$responseType->setPrivateKey($this->privateKey);
}
if ($this->responseType instanceof AbstractResponseType === true) {
$this->responseType->setPrivateKey($this->privateKey);
}
$this->responseType->setEncryptionKey($this->encryptionKey);
$responseType->setEncryptionKey($this->encryptionKey);
return $this->responseType;
return $responseType;
}
/**

View File

@@ -9,6 +9,8 @@
namespace League\OAuth2\Server\AuthorizationValidators;
use BadMethodCallException;
use InvalidArgumentException;
use Lcobucci\JWT\Parser;
use Lcobucci\JWT\Signer\Rsa\Sha256;
use Lcobucci\JWT\ValidationData;
@@ -17,6 +19,7 @@ use League\OAuth2\Server\CryptTrait;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
use Psr\Http\Message\ServerRequestInterface;
use RuntimeException;
class BearerTokenValidator implements AuthorizationValidatorInterface
{
@@ -28,7 +31,7 @@ class BearerTokenValidator implements AuthorizationValidatorInterface
private $accessTokenRepository;
/**
* @var \League\OAuth2\Server\CryptKey
* @var CryptKey
*/
protected $publicKey;
@@ -43,7 +46,7 @@ class BearerTokenValidator implements AuthorizationValidatorInterface
/**
* Set the public key
*
* @param \League\OAuth2\Server\CryptKey $key
* @param CryptKey $key
*/
public function setPublicKey(CryptKey $key)
{
@@ -69,8 +72,8 @@ class BearerTokenValidator implements AuthorizationValidatorInterface
if ($token->verify(new Sha256(), $this->publicKey->getKeyPath()) === false) {
throw OAuthServerException::accessDenied('Access token could not be verified');
}
} catch (\BadMethodCallException $exception) {
throw OAuthServerException::accessDenied('Access token is not signed');
} catch (BadMethodCallException $exception) {
throw OAuthServerException::accessDenied('Access token is not signed', null, $exception);
}
// Ensure access token hasn't expired
@@ -92,12 +95,12 @@ class BearerTokenValidator implements AuthorizationValidatorInterface
->withAttribute('oauth_client_id', $token->getClaim('aud'))
->withAttribute('oauth_user_id', $token->getClaim('sub'))
->withAttribute('oauth_scopes', $token->getClaim('scopes'));
} catch (\InvalidArgumentException $exception) {
} catch (InvalidArgumentException $exception) {
// JWT couldn't be parsed so return the request as is
throw OAuthServerException::accessDenied($exception->getMessage());
} catch (\RuntimeException $exception) {
throw OAuthServerException::accessDenied($exception->getMessage(), null, $exception);
} catch (RuntimeException $exception) {
//JWR couldn't be parsed so return the request as is
throw OAuthServerException::accessDenied('Error while decoding to JSON');
throw OAuthServerException::accessDenied('Error while decoding to JSON', null, $exception);
}
}
}

View File

@@ -11,6 +11,9 @@
namespace League\OAuth2\Server;
use LogicException;
use RuntimeException;
class CryptKey
{
const RSA_KEY_PATTERN =
@@ -42,7 +45,7 @@ class CryptKey
}
if (!file_exists($keyPath) || !is_readable($keyPath)) {
throw new \LogicException(sprintf('Key path "%s" does not exist or is not readable', $keyPath));
throw new LogicException(sprintf('Key path "%s" does not exist or is not readable', $keyPath));
}
if ($keyPermissionsCheck === true) {
@@ -64,7 +67,7 @@ class CryptKey
/**
* @param string $key
*
* @throws \RuntimeException
* @throws RuntimeException
*
* @return string
*/
@@ -79,19 +82,19 @@ class CryptKey
if (!touch($keyPath)) {
// @codeCoverageIgnoreStart
throw new \RuntimeException(sprintf('"%s" key file could not be created', $keyPath));
throw new RuntimeException(sprintf('"%s" key file could not be created', $keyPath));
// @codeCoverageIgnoreEnd
}
if (file_put_contents($keyPath, $key) === false) {
// @codeCoverageIgnoreStart
throw new \RuntimeException(sprintf('Unable to write key file to temporary directory "%s"', $tmpDir));
throw new RuntimeException(sprintf('Unable to write key file to temporary directory "%s"', $tmpDir));
// @codeCoverageIgnoreEnd
}
if (chmod($keyPath, 0600) === false) {
// @codeCoverageIgnoreStart
throw new \RuntimeException(sprintf('The key file "%s" file mode could not be changed with chmod to 600', $keyPath));
throw new RuntimeException(sprintf('The key file "%s" file mode could not be changed with chmod to 600', $keyPath));
// @codeCoverageIgnoreEnd
}

View File

@@ -13,6 +13,8 @@ namespace League\OAuth2\Server;
use Defuse\Crypto\Crypto;
use Defuse\Crypto\Key;
use Exception;
use LogicException;
trait CryptTrait
{
@@ -26,7 +28,7 @@ trait CryptTrait
*
* @param string $unencryptedData
*
* @throws \LogicException
* @throws LogicException
*
* @return string
*/
@@ -38,8 +40,8 @@ trait CryptTrait
}
return Crypto::encryptWithPassword($unencryptedData, $this->encryptionKey);
} catch (\Exception $e) {
throw new \LogicException($e->getMessage());
} catch (Exception $e) {
throw new LogicException($e->getMessage(), null, $e);
}
}
@@ -48,7 +50,7 @@ trait CryptTrait
*
* @param string $encryptedData
*
* @throws \LogicException
* @throws LogicException
*
* @return string
*/
@@ -60,8 +62,8 @@ trait CryptTrait
}
return Crypto::decryptWithPassword($encryptedData, $this->encryptionKey);
} catch (\Exception $e) {
throw new \LogicException($e->getMessage());
} catch (Exception $e) {
throw new LogicException($e->getMessage(), null, $e);
}
}

View File

@@ -9,7 +9,9 @@
namespace League\OAuth2\Server\Entities;
interface ScopeEntityInterface extends \JsonSerializable
use JsonSerializable;
interface ScopeEntityInterface extends JsonSerializable
{
/**
* Get the scope's identifier.

View File

@@ -0,0 +1,28 @@
<?php
/**
* @author Andrew Millington <andrew@noexceptions.io>
* @copyright Copyright (c) Andrew Millington
* @license http://mit-license.org
*
* @link https://github.com/thephpleague/oauth2-server
*/
namespace League\OAuth2\Server\Entities\Traits;
trait ScopeTrait
{
/**
* Serialize the object to the scopes string identifier when using json_encode().
*
* @return string
*/
public function jsonSerialize()
{
return $this->getIdentifier();
}
/**
* @return string
*/
abstract public function getIdentifier();
}

View File

@@ -9,10 +9,12 @@
namespace League\OAuth2\Server\Exception;
use Exception;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Throwable;
class OAuthServerException extends \Exception
class OAuthServerException extends Exception
{
/**
* @var int
@@ -53,17 +55,18 @@ class OAuthServerException extends \Exception
* @param int $httpStatusCode HTTP status code to send (default = 400)
* @param null|string $hint A helper hint
* @param null|string $redirectUri A HTTP URI to redirect the user back to
* @param Throwable $previous Previous exception
*/
public function __construct($message, $code, $errorType, $httpStatusCode = 400, $hint = null, $redirectUri = null)
public function __construct($message, $code, $errorType, $httpStatusCode = 400, $hint = null, $redirectUri = null, Throwable $previous = null)
{
parent::__construct($message, $code);
parent::__construct($message, $code, $previous);
$this->httpStatusCode = $httpStatusCode;
$this->errorType = $errorType;
$this->hint = $hint;
$this->redirectUri = $redirectUri;
$this->payload = [
'error' => $errorType,
'message' => $message,
'error' => $errorType,
'error_description' => $message,
];
if ($hint !== null) {
$this->payload['hint'] = $hint;
@@ -77,7 +80,15 @@ class OAuthServerException extends \Exception
*/
public function getPayload()
{
return $this->payload;
$payload = $this->payload;
// The "message" property is deprecated and replaced by "error_description"
// TODO: remove "message" property
if (isset($payload['error_description']) && !isset($payload['message'])) {
$payload['message'] = $payload['error_description'];
}
return $payload;
}
/**
@@ -95,7 +106,7 @@ class OAuthServerException extends \Exception
*
* @param ServerRequestInterface $serverRequest
*/
public function setServerRequest($serverRequest)
public function setServerRequest(ServerRequestInterface $serverRequest)
{
$this->serverRequest = $serverRequest;
}
@@ -118,16 +129,17 @@ class OAuthServerException extends \Exception
*
* @param string $parameter The invalid parameter
* @param null|string $hint
* @param Throwable $previous Previous exception
*
* @return static
*/
public static function invalidRequest($parameter, $hint = null)
public static function invalidRequest($parameter, $hint = null, Throwable $previous = null)
{
$errorMessage = 'The request is missing a required parameter, includes an invalid parameter value, ' .
'includes a parameter more than once, or is otherwise malformed.';
$hint = ($hint === null) ? sprintf('Check the `%s` parameter', $parameter) : $hint;
return new static($errorMessage, 3, 'invalid_request', 400, $hint);
return new static($errorMessage, 3, 'invalid_request', 400, $hint, null, $previous);
}
/**
@@ -137,7 +149,7 @@ class OAuthServerException extends \Exception
*
* @return static
*/
public static function invalidClient($serverRequest)
public static function invalidClient(ServerRequestInterface $serverRequest)
{
$exception = new static('Client authentication failed', 4, 'invalid_client', 401);
@@ -183,20 +195,24 @@ class OAuthServerException extends \Exception
/**
* Server error.
*
* @param string $hint
* @param string $hint
* @param Throwable $previous
*
* @return static
*
* @codeCoverageIgnore
*/
public static function serverError($hint)
public static function serverError($hint, Throwable $previous = null)
{
return new static(
'The authorization server encountered an unexpected condition which prevented it from fulfilling'
. ' the request: ' . $hint,
7,
'server_error',
500
500,
null,
null,
$previous
);
}
@@ -204,12 +220,13 @@ class OAuthServerException extends \Exception
* Invalid refresh token.
*
* @param null|string $hint
* @param Throwable $previous
*
* @return static
*/
public static function invalidRefreshToken($hint = null)
public static function invalidRefreshToken($hint = null, Throwable $previous = null)
{
return new static('The refresh token is invalid.', 8, 'invalid_request', 401, $hint);
return new static('The refresh token is invalid.', 8, 'invalid_request', 401, $hint, null, $previous);
}
/**
@@ -217,10 +234,11 @@ class OAuthServerException extends \Exception
*
* @param null|string $hint
* @param null|string $redirectUri
* @param Throwable $previous
*
* @return static
*/
public static function accessDenied($hint = null, $redirectUri = null)
public static function accessDenied($hint = null, $redirectUri = null, Throwable $previous = null)
{
return new static(
'The resource owner or authorization server denied the request.',
@@ -228,7 +246,8 @@ class OAuthServerException extends \Exception
'access_denied',
401,
$hint,
$redirectUri
$redirectUri,
$previous
);
}

View File

@@ -11,6 +11,9 @@ namespace League\OAuth2\Server\Exception;
class UniqueTokenIdentifierConstraintViolationException extends OAuthServerException
{
/**
* @return UniqueTokenIdentifierConstraintViolationException
*/
public static function create()
{
$errorMessage = 'Could not create unique access token identifier';

View File

@@ -12,6 +12,8 @@ namespace League\OAuth2\Server\Grant;
use DateInterval;
use DateTimeImmutable;
use Error;
use Exception;
use League\Event\EmitterAwareTrait;
use League\OAuth2\Server\CryptKey;
use League\OAuth2\Server\CryptTrait;
@@ -30,7 +32,9 @@ use League\OAuth2\Server\Repositories\ScopeRepositoryInterface;
use League\OAuth2\Server\Repositories\UserRepositoryInterface;
use League\OAuth2\Server\RequestEvent;
use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
use LogicException;
use Psr\Http\Message\ServerRequestInterface;
use TypeError;
/**
* Abstract grant class.
@@ -79,7 +83,7 @@ abstract class AbstractGrant implements GrantTypeInterface
protected $refreshTokenTTL;
/**
* @var \League\OAuth2\Server\CryptKey
* @var CryptKey
*/
protected $privateKey;
@@ -147,7 +151,7 @@ abstract class AbstractGrant implements GrantTypeInterface
/**
* Set the private key
*
* @param \League\OAuth2\Server\CryptKey $key
* @param CryptKey $key
*/
public function setPrivateKey(CryptKey $key)
{
@@ -258,13 +262,13 @@ abstract class AbstractGrant implements GrantTypeInterface
ClientEntityInterface $client,
ServerRequestInterface $request
) {
if (is_string($client->getRedirectUri())
if (\is_string($client->getRedirectUri())
&& (strcmp($client->getRedirectUri(), $redirectUri) !== 0)
) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidClient($request);
} elseif (is_array($client->getRedirectUri())
&& in_array($redirectUri, $client->getRedirectUri(), true) === false
} elseif (\is_array($client->getRedirectUri())
&& \in_array($redirectUri, $client->getRedirectUri(), true) === false
) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidClient($request);
@@ -274,8 +278,8 @@ abstract class AbstractGrant implements GrantTypeInterface
/**
* Validate scopes in the request.
*
* @param string $scopes
* @param string $redirectUri
* @param string|array $scopes
* @param string $redirectUri
*
* @throws OAuthServerException
*
@@ -283,13 +287,13 @@ abstract class AbstractGrant implements GrantTypeInterface
*/
public function validateScopes($scopes, $redirectUri = null)
{
$scopesList = array_filter(explode(self::SCOPE_DELIMITER_STRING, trim($scopes)), function ($scope) {
return !empty($scope);
});
if (!\is_array($scopes)) {
$scopes = $this->convertScopesQueryStringToArray($scopes);
}
$validScopes = [];
foreach ($scopesList as $scopeItem) {
foreach ($scopes as $scopeItem) {
$scope = $this->scopeRepository->getScopeEntityByIdentifier($scopeItem);
if ($scope instanceof ScopeEntityInterface === false) {
@@ -302,6 +306,20 @@ abstract class AbstractGrant implements GrantTypeInterface
return $validScopes;
}
/**
* Converts a scopes query string to an array to easily iterate for validation.
*
* @param string $scopes
*
* @return array
*/
private function convertScopesQueryStringToArray($scopes)
{
return array_filter(explode(self::SCOPE_DELIMITER_STRING, trim($scopes)), function ($scope) {
return !empty($scope);
});
}
/**
* Retrieve request parameter.
*
@@ -315,7 +333,7 @@ abstract class AbstractGrant implements GrantTypeInterface
{
$requestParameters = (array) $request->getParsedBody();
return isset($requestParameters[$parameter]) ? $requestParameters[$parameter] : $default;
return $requestParameters[$parameter] ?? $default;
}
/**
@@ -488,16 +506,21 @@ abstract class AbstractGrant implements GrantTypeInterface
* @throws OAuthServerException
* @throws UniqueTokenIdentifierConstraintViolationException
*
* @return RefreshTokenEntityInterface
* @return RefreshTokenEntityInterface|null
*/
protected function issueRefreshToken(AccessTokenEntityInterface $accessToken)
{
$maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
$refreshToken = $this->refreshTokenRepository->getNewRefreshToken();
if ($refreshToken === null) {
return null;
}
$refreshToken->setExpiryDateTime((new DateTimeImmutable())->add($this->refreshTokenTTL));
$refreshToken->setAccessToken($accessToken);
$maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
while ($maxGenerationAttempts-- > 0) {
$refreshToken->setIdentifier($this->generateUniqueIdentifier());
try {
@@ -526,13 +549,13 @@ abstract class AbstractGrant implements GrantTypeInterface
try {
return bin2hex(random_bytes($length));
// @codeCoverageIgnoreStart
} catch (\TypeError $e) {
throw OAuthServerException::serverError('An unexpected error has occurred');
} catch (\Error $e) {
throw OAuthServerException::serverError('An unexpected error has occurred');
} catch (\Exception $e) {
} catch (TypeError $e) {
throw OAuthServerException::serverError('An unexpected error has occurred', $e);
} catch (Error $e) {
throw OAuthServerException::serverError('An unexpected error has occurred', $e);
} catch (Exception $e) {
// If you get this message, the CSPRNG failed hard.
throw OAuthServerException::serverError('Could not generate a random string');
throw OAuthServerException::serverError('Could not generate a random string', $e);
}
// @codeCoverageIgnoreEnd
}
@@ -563,7 +586,7 @@ abstract class AbstractGrant implements GrantTypeInterface
*/
public function validateAuthorizationRequest(ServerRequestInterface $request)
{
throw new \LogicException('This grant cannot validate an authorization request');
throw new LogicException('This grant cannot validate an authorization request');
}
/**
@@ -571,6 +594,6 @@ abstract class AbstractGrant implements GrantTypeInterface
*/
public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest)
{
throw new \LogicException('This grant cannot complete an authorization request');
throw new LogicException('This grant cannot complete an authorization request');
}
}

View File

@@ -11,10 +11,10 @@ namespace League\OAuth2\Server\Grant;
use DateInterval;
use DateTimeImmutable;
use Exception;
use League\OAuth2\Server\CodeChallengeVerifiers\CodeChallengeVerifierInterface;
use League\OAuth2\Server\CodeChallengeVerifiers\PlainVerifier;
use League\OAuth2\Server\CodeChallengeVerifiers\S256Verifier;
use League\OAuth2\Server\Entities\ScopeEntityInterface;
use League\OAuth2\Server\Entities\UserEntityInterface;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface;
@@ -23,7 +23,9 @@ use League\OAuth2\Server\RequestEvent;
use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
use League\OAuth2\Server\ResponseTypes\RedirectResponse;
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
use LogicException;
use Psr\Http\Message\ServerRequestInterface;
use stdClass;
class AuthCodeGrant extends AbstractAuthorizeGrant
{
@@ -46,6 +48,8 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
* @param AuthCodeRepositoryInterface $authCodeRepository
* @param RefreshTokenRepositoryInterface $refreshTokenRepository
* @param DateInterval $authCodeTTL
*
* @throws Exception
*/
public function __construct(
AuthCodeRepositoryInterface $authCodeRepository,
@@ -57,14 +61,13 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
$this->authCodeTTL = $authCodeTTL;
$this->refreshTokenTTL = new DateInterval('P1M');
// SHOULD ONLY DO THIS IS SHA256 is supported
$s256Verifier = new S256Verifier();
$plainVerifier = new PlainVerifier();
if (in_array('sha256', hash_algos(), true)) {
$s256Verifier = new S256Verifier();
$this->codeChallengeVerifiers[$s256Verifier->getMethod()] = $s256Verifier;
}
$this->codeChallengeVerifiers = [
$s256Verifier->getMethod() => $s256Verifier,
$plainVerifier->getMethod() => $plainVerifier,
];
$plainVerifier = new PlainVerifier();
$this->codeChallengeVerifiers[$plainVerifier->getMethod()] = $plainVerifier;
}
/**
@@ -106,56 +109,22 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
throw OAuthServerException::invalidRequest('code');
}
// Validate the authorization code
try {
$authCodePayload = json_decode($this->decrypt($encryptedAuthCode));
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');
}
$this->validateAuthorizationCode($authCodePayload, $client, $request);
if ($authCodePayload->client_id !== $client->getIdentifier()) {
throw OAuthServerException::invalidRequest('code', 'Authorization code was not issued to this client');
}
// The redirect URI is required in this request
$redirectUri = $this->getRequestParameter('redirect_uri', $request, null);
if (empty($authCodePayload->redirect_uri) === false && $redirectUri === null) {
throw OAuthServerException::invalidRequest('redirect_uri');
}
if ($authCodePayload->redirect_uri !== $redirectUri) {
throw OAuthServerException::invalidRequest('redirect_uri', 'Invalid redirect URI');
}
$scopes = [];
foreach ($authCodePayload->scopes as $scopeId) {
$scope = $this->scopeRepository->getScopeEntityByIdentifier($scopeId);
if ($scope instanceof ScopeEntityInterface === false) {
// @codeCoverageIgnoreStart
throw OAuthServerException::invalidScope($scopeId);
// @codeCoverageIgnoreEnd
}
$scopes[] = $scope;
}
// Finalize the requested scopes
$scopes = $this->scopeRepository->finalizeScopes(
$scopes,
$this->validateScopes($authCodePayload->scopes),
$this->getIdentifier(),
$client,
$authCodePayload->user_id
);
} catch (\LogicException $e) {
throw OAuthServerException::invalidRequest('code', 'Cannot decrypt the authorization code');
} catch (LogicException $e) {
throw OAuthServerException::invalidRequest('code', 'Cannot decrypt the authorization code', $e);
}
// Validate code challenge
@@ -191,17 +160,18 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
}
}
// Issue and persist access + refresh tokens
// Issue and persist new access token
$accessToken = $this->issueAccessToken($accessTokenTTL, $client, $authCodePayload->user_id, $scopes);
$this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request));
$responseType->setAccessToken($accessToken);
// Issue and persist new refresh token if given
$refreshToken = $this->issueRefreshToken($accessToken);
// Send events to emitter
$this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request));
$this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request));
// Inject tokens into response type
$responseType->setAccessToken($accessToken);
$responseType->setRefreshToken($refreshToken);
if ($refreshToken !== null) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request));
$responseType->setRefreshToken($refreshToken);
}
// Revoke used auth code
$this->authCodeRepository->revokeAuthCode($authCodePayload->auth_code_id);
@@ -209,6 +179,41 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
return $responseType;
}
/**
* Validate the authorization code.
*
* @param stdClass $authCodePayload
* @param ClientEntityInterface $client
* @param ServerRequestInterface $request
*/
private function validateAuthorizationCode(
$authCodePayload,
ClientEntityInterface $client,
ServerRequestInterface $request
) {
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');
}
// The redirect URI is required in this request
$redirectUri = $this->getRequestParameter('redirect_uri', $request, null);
if (empty($authCodePayload->redirect_uri) === false && $redirectUri === null) {
throw OAuthServerException::invalidRequest('redirect_uri');
}
if ($authCodePayload->redirect_uri !== $redirectUri) {
throw OAuthServerException::invalidRequest('redirect_uri', 'Invalid redirect URI');
}
}
/**
* Return the grant identifier that can be used in matching up requests.
*
@@ -242,7 +247,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
$this->getServerParameter('PHP_AUTH_USER', $request)
);
if (is_null($clientId)) {
if ($clientId === null) {
throw OAuthServerException::invalidRequest('client_id');
}
@@ -252,12 +257,12 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
if ($redirectUri !== null) {
$this->validateRedirectUri($redirectUri, $client, $request);
} elseif (is_array($client->getRedirectUri()) && count($client->getRedirectUri()) !== 1
|| empty($client->getRedirectUri())) {
} elseif (empty($client->getRedirectUri()) ||
(\is_array($client->getRedirectUri()) && \count($client->getRedirectUri()) !== 1)) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidClient($request);
} else {
$redirectUri = is_array($client->getRedirectUri())
$redirectUri = \is_array($client->getRedirectUri())
? $client->getRedirectUri()[0]
: $client->getRedirectUri();
}
@@ -321,14 +326,11 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest)
{
if ($authorizationRequest->getUser() instanceof UserEntityInterface === false) {
throw new \LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest');
throw new LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest');
}
$finalRedirectUri = ($authorizationRequest->getRedirectUri() === null)
? is_array($authorizationRequest->getClient()->getRedirectUri())
? $authorizationRequest->getClient()->getRedirectUri()[0]
: $authorizationRequest->getClient()->getRedirectUri()
: $authorizationRequest->getRedirectUri();
$finalRedirectUri = $authorizationRequest->getRedirectUri()
?? $this->getClientRedirectUri($authorizationRequest);
// The user approved the client, redirect them back with an auth code
if ($authorizationRequest->isAuthorizationApproved() === true) {
@@ -376,4 +378,18 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
)
);
}
/**
* Get the client redirect URI if not set in the request.
*
* @param AuthorizationRequest $authorizationRequest
*
* @return string
*/
private function getClientRedirectUri(AuthorizationRequest $authorizationRequest)
{
return \is_array($authorizationRequest->getClient()->getRedirectUri())
? $authorizationRequest->getClient()->getRedirectUri()[0]
: $authorizationRequest->getClient()->getRedirectUri();
}
}

View File

@@ -17,6 +17,7 @@ use League\OAuth2\Server\RequestEvent;
use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
use League\OAuth2\Server\ResponseTypes\RedirectResponse;
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
use LogicException;
use Psr\Http\Message\ServerRequestInterface;
class ImplicitGrant extends AbstractAuthorizeGrant
@@ -44,21 +45,21 @@ class ImplicitGrant extends AbstractAuthorizeGrant
/**
* @param DateInterval $refreshTokenTTL
*
* @throw \LogicException
* @throw LogicException
*/
public function setRefreshTokenTTL(DateInterval $refreshTokenTTL)
{
throw new \LogicException('The Implicit Grant does not return refresh tokens');
throw new LogicException('The Implicit Grant does not return refresh tokens');
}
/**
* @param RefreshTokenRepositoryInterface $refreshTokenRepository
*
* @throw \LogicException
* @throw LogicException
*/
public function setRefreshTokenRepository(RefreshTokenRepositoryInterface $refreshTokenRepository)
{
throw new \LogicException('The Implicit Grant does not return refresh tokens');
throw new LogicException('The Implicit Grant does not return refresh tokens');
}
/**
@@ -93,7 +94,7 @@ class ImplicitGrant extends AbstractAuthorizeGrant
ResponseTypeInterface $responseType,
DateInterval $accessTokenTTL
) {
throw new \LogicException('This grant does not used this method');
throw new LogicException('This grant does not used this method');
}
/**
@@ -144,13 +145,6 @@ class ImplicitGrant extends AbstractAuthorizeGrant
$redirectUri
);
// Finalize the requested scopes
$finalizedScopes = $this->scopeRepository->finalizeScopes(
$scopes,
$this->getIdentifier(),
$client
);
$stateParameter = $this->getQueryStringParameter('state', $request);
$authorizationRequest = new AuthorizationRequest();
@@ -162,7 +156,7 @@ class ImplicitGrant extends AbstractAuthorizeGrant
$authorizationRequest->setState($stateParameter);
}
$authorizationRequest->setScopes($finalizedScopes);
$authorizationRequest->setScopes($scopes);
return $authorizationRequest;
}
@@ -173,7 +167,7 @@ class ImplicitGrant extends AbstractAuthorizeGrant
public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest)
{
if ($authorizationRequest->getUser() instanceof UserEntityInterface === false) {
throw new \LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest');
throw new LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest');
}
$finalRedirectUri = ($authorizationRequest->getRedirectUri() === null)
@@ -184,11 +178,19 @@ class ImplicitGrant extends AbstractAuthorizeGrant
// The user approved the client, redirect them back with an access token
if ($authorizationRequest->isAuthorizationApproved() === true) {
// Finalize the requested scopes
$finalizedScopes = $this->scopeRepository->finalizeScopes(
$authorizationRequest->getScopes(),
$this->getIdentifier(),
$authorizationRequest->getClient(),
$authorizationRequest->getUser()->getIdentifier()
);
$accessToken = $this->issueAccessToken(
$this->accessTokenTTL,
$authorizationRequest->getClient(),
$authorizationRequest->getUser()->getIdentifier(),
$authorizationRequest->getScopes()
$finalizedScopes
);
$response = new RedirectResponse();

View File

@@ -56,17 +56,18 @@ class PasswordGrant extends AbstractGrant
// Finalize the requested scopes
$finalizedScopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client, $user->getIdentifier());
// Issue and persist new tokens
// Issue and persist new access token
$accessToken = $this->issueAccessToken($accessTokenTTL, $client, $user->getIdentifier(), $finalizedScopes);
$this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request));
$responseType->setAccessToken($accessToken);
// Issue and persist new refresh token if given
$refreshToken = $this->issueRefreshToken($accessToken);
// Send events to emitter
$this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request));
$this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request));
// Inject tokens into response
$responseType->setAccessToken($accessToken);
$responseType->setRefreshToken($refreshToken);
if ($refreshToken !== null) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request));
$responseType->setRefreshToken($refreshToken);
}
return $responseType;
}

View File

@@ -12,6 +12,7 @@
namespace League\OAuth2\Server\Grant;
use DateInterval;
use Exception;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
use League\OAuth2\Server\RequestEvent;
@@ -62,17 +63,18 @@ class RefreshTokenGrant extends AbstractGrant
$this->accessTokenRepository->revokeAccessToken($oldRefreshToken['access_token_id']);
$this->refreshTokenRepository->revokeRefreshToken($oldRefreshToken['refresh_token_id']);
// Issue and persist new tokens
// Issue and persist new access token
$accessToken = $this->issueAccessToken($accessTokenTTL, $client, $oldRefreshToken['user_id'], $scopes);
$this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request));
$responseType->setAccessToken($accessToken);
// Issue and persist new refresh token if given
$refreshToken = $this->issueRefreshToken($accessToken);
// Send events to emitter
$this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request));
$this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request));
// Inject tokens into response
$responseType->setAccessToken($accessToken);
$responseType->setRefreshToken($refreshToken);
if ($refreshToken !== null) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request));
$responseType->setRefreshToken($refreshToken);
}
return $responseType;
}
@@ -95,8 +97,8 @@ class RefreshTokenGrant extends AbstractGrant
// Validate refresh token
try {
$refreshToken = $this->decrypt($encryptedRefreshToken);
} catch (\Exception $e) {
throw OAuthServerException::invalidRefreshToken('Cannot decrypt the refresh token');
} catch (Exception $e) {
throw OAuthServerException::invalidRefreshToken('Cannot decrypt the refresh token', $e);
}
$refreshTokenData = json_decode($refreshToken, true);

View File

@@ -9,6 +9,7 @@
namespace League\OAuth2\Server\Middleware;
use Exception;
use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\Exception\OAuthServerException;
use Psr\Http\Message\ResponseInterface;
@@ -43,7 +44,7 @@ class AuthorizationServerMiddleware
} catch (OAuthServerException $exception) {
return $exception->generateHttpResponse($response);
// @codeCoverageIgnoreStart
} catch (\Exception $exception) {
} catch (Exception $exception) {
return (new OAuthServerException($exception->getMessage(), 0, 'unknown_error', 500))
->generateHttpResponse($response);
// @codeCoverageIgnoreEnd

View File

@@ -9,6 +9,7 @@
namespace League\OAuth2\Server\Middleware;
use Exception;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\ResourceServer;
use Psr\Http\Message\ResponseInterface;
@@ -34,7 +35,7 @@ class ResourceServerMiddleware
* @param ResponseInterface $response
* @param callable $next
*
* @return \Psr\Http\Message\ResponseInterface
* @return ResponseInterface
*/
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
{
@@ -43,7 +44,7 @@ class ResourceServerMiddleware
} catch (OAuthServerException $exception) {
return $exception->generateHttpResponse($response);
// @codeCoverageIgnoreStart
} catch (\Exception $exception) {
} catch (Exception $exception) {
return (new OAuthServerException($exception->getMessage(), 0, 'unknown_error', 500))
->generateHttpResponse($response);
// @codeCoverageIgnoreEnd

View File

@@ -20,7 +20,7 @@ interface RefreshTokenRepositoryInterface extends RepositoryInterface
/**
* Creates a new refresh token
*
* @return RefreshTokenEntityInterface
* @return RefreshTokenEntityInterface|null
*/
public function getNewRefreshToken();

View File

@@ -54,7 +54,7 @@ abstract class AbstractResponseType implements ResponseTypeInterface
/**
* Set the private key
*
* @param \League\OAuth2\Server\CryptKey $key
* @param CryptKey $key
*/
public function setPrivateKey(CryptKey $key)
{