diff --git a/CHANGELOG.md b/CHANGELOG.md index 91ab6b30..7362fc74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added - Added a ScopeTrait to provide an implementation for jsonSerialize (PR #952) +- Ability to nest exceptions (PR #965) ### Fixed - Fix issue where AuthorizationServer is not stateless as ResponseType could store state of a previous request (PR #960) diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index 924c2fb5..b2035ccc 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -73,7 +73,7 @@ class BearerTokenValidator implements AuthorizationValidatorInterface throw OAuthServerException::accessDenied('Access token could not be verified'); } } catch (BadMethodCallException $exception) { - throw OAuthServerException::accessDenied('Access token is not signed'); + throw OAuthServerException::accessDenied('Access token is not signed', null, $exception); } // Ensure access token hasn't expired @@ -97,10 +97,10 @@ class BearerTokenValidator implements AuthorizationValidatorInterface ->withAttribute('oauth_scopes', $token->getClaim('scopes')); } catch (InvalidArgumentException $exception) { // JWT couldn't be parsed so return the request as is - throw OAuthServerException::accessDenied($exception->getMessage()); + 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); } } } diff --git a/src/CryptTrait.php b/src/CryptTrait.php index 79b8dc55..1196e9dc 100644 --- a/src/CryptTrait.php +++ b/src/CryptTrait.php @@ -41,7 +41,7 @@ trait CryptTrait return Crypto::encryptWithPassword($unencryptedData, $this->encryptionKey); } catch (Exception $e) { - throw new LogicException($e->getMessage()); + throw new LogicException($e->getMessage(), null, $e); } } @@ -63,7 +63,7 @@ trait CryptTrait return Crypto::decryptWithPassword($encryptedData, $this->encryptionKey); } catch (Exception $e) { - throw new LogicException($e->getMessage()); + throw new LogicException($e->getMessage(), null, $e); } } diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index 41bb8975..ed5f9c78 100644 --- a/src/Exception/OAuthServerException.php +++ b/src/Exception/OAuthServerException.php @@ -11,6 +11,7 @@ namespace League\OAuth2\Server\Exception; use Exception; use Psr\Http\Message\ResponseInterface; +use Throwable; class OAuthServerException extends Exception { @@ -48,10 +49,11 @@ 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; @@ -103,16 +105,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); } /** @@ -164,20 +167,22 @@ 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, + $previous ); } @@ -185,12 +190,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); } /** @@ -198,10 +204,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.', @@ -209,7 +216,8 @@ class OAuthServerException extends Exception 'access_denied', 401, $hint, - $redirectUri + $redirectUri, + $previous ); } diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 603c18d0..49213331 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -511,12 +511,12 @@ abstract class AbstractGrant implements GrantTypeInterface return bin2hex(random_bytes($length)); // @codeCoverageIgnoreStart } catch (TypeError $e) { - throw OAuthServerException::serverError('An unexpected error has occurred'); + throw OAuthServerException::serverError('An unexpected error has occurred', $e); } catch (Error $e) { - throw OAuthServerException::serverError('An unexpected error has occurred'); + 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 } diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index 2dc3c6d7..235a6a96 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -96,7 +96,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant $authCodePayload->user_id ); } catch (LogicException $e) { - throw OAuthServerException::invalidRequest('code', 'Cannot decrypt the authorization code'); + throw OAuthServerException::invalidRequest('code', 'Cannot decrypt the authorization code', $e); } // Validate code challenge diff --git a/src/Grant/RefreshTokenGrant.php b/src/Grant/RefreshTokenGrant.php index 2732e5b8..2181af88 100644 --- a/src/Grant/RefreshTokenGrant.php +++ b/src/Grant/RefreshTokenGrant.php @@ -97,7 +97,7 @@ class RefreshTokenGrant extends AbstractGrant try { $refreshToken = $this->decrypt($encryptedRefreshToken); } catch (Exception $e) { - throw OAuthServerException::invalidRefreshToken('Cannot decrypt the refresh token'); + throw OAuthServerException::invalidRefreshToken('Cannot decrypt the refresh token', $e); } $refreshTokenData = json_decode($refreshToken, true); diff --git a/tests/Exception/OAuthServerExceptionTest.php b/tests/Exception/OAuthServerExceptionTest.php index 976362e3..201fb5dd 100644 --- a/tests/Exception/OAuthServerExceptionTest.php +++ b/tests/Exception/OAuthServerExceptionTest.php @@ -2,6 +2,7 @@ namespace LeagueTests\Exception; +use Exception; use League\OAuth2\Server\Exception\OAuthServerException; use PHPUnit\Framework\TestCase; @@ -20,4 +21,19 @@ class OAuthServerExceptionTest extends TestCase $this->assertFalse($exceptionWithoutRedirect->hasRedirect()); } + + public function testHasPrevious() + { + $previous = new Exception('This is the previous'); + $exceptionWithPrevious = OAuthServerException::accessDenied(null, null, $previous); + + $this->assertSame('This is the previous', $exceptionWithPrevious->getPrevious()->getMessage()); + } + + public function testDoesNotHavePrevious() + { + $exceptionWithoutPrevious = OAuthServerException::accessDenied(); + + $this->assertNull($exceptionWithoutPrevious->getPrevious()); + } }