Revising how/what exceptions are thrown (unfinished).
This commit is contained in:
parent
46ec5b9d88
commit
dc38d6e423
21 changed files with 319 additions and 87 deletions
VERSION
src
ArrayJwkSet.phpErrorWithPayload.phpExceptionWithPayload.phpHmacJwk.phpInvalidArgumentExceptionWithPayload.phpJwkAlgorithmMismatchException.phpJwkAlgorithmMismatchExceptionWithPayload.phpJwkNotFoundException.phpJwkNotFoundExceptionWithPayload.phpJwtEncoder.phpLogicExceptionWithPayload.phpNoneJwkSet.phpRuntimeExceptionWithPayload.phpThrowableWithPayload.phpThrowableWithPayloadEquivalent.phpUnexpectedValueExceptionWithPayload.php
Utility
tests
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
v0.3.3
|
v0.4.0
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
namespace Railgun\Jwt;
|
namespace Railgun\Jwt;
|
||||||
|
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use RuntimeException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a JSON Web Key Set backed by an array.
|
* Represents a JSON Web Key Set backed by an array.
|
||||||
|
@ -55,21 +54,21 @@ class ArrayJwkSet implements JwkSet {
|
||||||
|
|
||||||
foreach($this->keys as $key) {
|
foreach($this->keys as $key) {
|
||||||
$keyAlgo = $key->getAlgorithm();
|
$keyAlgo = $key->getAlgorithm();
|
||||||
if($keyAlgo !== null && hash_equals($keyAlgo, $algo))
|
if($keyAlgo !== null && $keyAlgo === $algo)
|
||||||
return $key;
|
return $key;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new RuntimeException('could not find a key that matched the requested algorithm');
|
throw new JwkNotFoundException('could not find a key that matched the requested algorithm');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if(!array_key_exists($keyId, $this->keys))
|
if(!array_key_exists($keyId, $this->keys))
|
||||||
throw new RuntimeException('could not find a key with that id');
|
throw new JwkNotFoundException('could not find a key with that id');
|
||||||
|
|
||||||
$key = $this->keys[$keyId];
|
$key = $this->keys[$keyId];
|
||||||
$keyAlgo = $key->getAlgorithm();
|
$keyAlgo = $key->getAlgorithm();
|
||||||
if($algo !== null && $keyAlgo !== null && !hash_equals($keyAlgo, $algo))
|
if($algo !== null && $keyAlgo !== null && $keyAlgo !== $algo)
|
||||||
throw new RuntimeException('requested algorithm does not match');
|
throw new JwkAlgorithmMismatchException('requested algorithm does not match');
|
||||||
|
|
||||||
return $key;
|
return $key;
|
||||||
}
|
}
|
||||||
|
|
37
src/ErrorWithPayload.php
Normal file
37
src/ErrorWithPayload.php
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
namespace Railgun\Jwt;
|
||||||
|
|
||||||
|
use Error;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides an Error with access to a JWT's payload.
|
||||||
|
*/
|
||||||
|
class ErrorWithPayload extends Error implements ThrowableWithPayload {
|
||||||
|
/** @var object */
|
||||||
|
private $payload;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param object $payload Data that was provided for the payload section.
|
||||||
|
* @param string $message The Exception message to throw.
|
||||||
|
* @param int $code The Exception code.
|
||||||
|
* @param ?Throwable $previous The previous exception used for the exception chaining.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
object $payload,
|
||||||
|
string $message = '',
|
||||||
|
int $code = 0,
|
||||||
|
?Throwable $previous = null
|
||||||
|
) {
|
||||||
|
$this->payload = $payload;
|
||||||
|
parent::__construct($message, $code, $previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPayload(): object {
|
||||||
|
return $this->payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withPayload(object $payload) {
|
||||||
|
return new ErrorWithPayload($payload, $this->getMessage(), $this->getCode(), $this);
|
||||||
|
}
|
||||||
|
}
|
37
src/ExceptionWithPayload.php
Normal file
37
src/ExceptionWithPayload.php
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
namespace Railgun\Jwt;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides an Exception with access to a JWT's payload.
|
||||||
|
*/
|
||||||
|
class ExceptionWithPayload extends Exception implements ThrowableWithPayload {
|
||||||
|
/** @var object */
|
||||||
|
private $payload;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param object $payload Data that was provided for the payload section.
|
||||||
|
* @param string $message The Exception message to throw.
|
||||||
|
* @param int $code The Exception code.
|
||||||
|
* @param ?Throwable $previous The previous exception used for the exception chaining.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
object $payload,
|
||||||
|
string $message = '',
|
||||||
|
int $code = 0,
|
||||||
|
?Throwable $previous = null
|
||||||
|
) {
|
||||||
|
$this->payload = $payload;
|
||||||
|
parent::__construct($message, $code, $previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPayload(): object {
|
||||||
|
return $this->payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withPayload(object $payload) {
|
||||||
|
return new ExceptionWithPayload($payload, $this->getMessage(), $this->getCode(), $this);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
namespace Railgun\Jwt;
|
namespace Railgun\Jwt;
|
||||||
|
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use RuntimeException;
|
use UnexpectedValueException;
|
||||||
use Railgun\Jwt\Utility\UriBase64;
|
use Railgun\Jwt\Utility\UriBase64;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -65,7 +65,7 @@ class HmacJwk implements Jwk {
|
||||||
|
|
||||||
$result = hash_hmac(self::$algos[$algo], $data, $this->key, true);
|
$result = hash_hmac(self::$algos[$algo], $data, $this->key, true);
|
||||||
if($result === false) // @phpstan-ignore identical.alwaysFalse
|
if($result === false) // @phpstan-ignore identical.alwaysFalse
|
||||||
throw new RuntimeException('failed to sign $data');
|
throw new UnexpectedValueException('failed to sign $data');
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ use Throwable;
|
||||||
/**
|
/**
|
||||||
* Provides an InvalidArgumentException with access to a JWT's payload.
|
* Provides an InvalidArgumentException with access to a JWT's payload.
|
||||||
*/
|
*/
|
||||||
class InvalidArgumentExceptionWithPayload extends InvalidArgumentException implements WithPayload {
|
class InvalidArgumentExceptionWithPayload extends InvalidArgumentException implements ThrowableWithPayload {
|
||||||
/** @var object */
|
/** @var object */
|
||||||
private $payload;
|
private $payload;
|
||||||
|
|
||||||
|
@ -31,14 +31,7 @@ class InvalidArgumentExceptionWithPayload extends InvalidArgumentException imple
|
||||||
return $this->payload;
|
return $this->payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function withPayload(object $payload) {
|
||||||
* Converts a normal InvalidArgumentException to one with a payload.
|
return new InvalidArgumentExceptionWithPayload($payload, $this->getMessage(), $this->getCode(), $this);
|
||||||
*
|
|
||||||
* @param object $payload Data that was provided for the payload section.
|
|
||||||
* @param InvalidArgumentException $previous Exception to convert.
|
|
||||||
* @return InvalidArgumentExceptionWithPayload
|
|
||||||
*/
|
|
||||||
public static function convert(object $payload, InvalidArgumentException $previous) {
|
|
||||||
return new InvalidArgumentExceptionWithPayload($payload, $previous->getMessage(), $previous->getCode(), $previous);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
11
src/JwkAlgorithmMismatchException.php
Normal file
11
src/JwkAlgorithmMismatchException.php
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<?php
|
||||||
|
namespace Railgun\Jwt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when JwkSet::getKey() can find an algorithm with the provided key id, but the algorithm is a mismatch.
|
||||||
|
*/
|
||||||
|
class JwkAlgorithmMismatchException extends JwkNotFoundException implements ThrowableWithPayloadEquivalent {
|
||||||
|
public function withPayload(object $payload) {
|
||||||
|
return new JwkAlgorithmMismatchExceptionWithPayload($payload, $this->getMessage(), $this->getCode(), $this);
|
||||||
|
}
|
||||||
|
}
|
32
src/JwkAlgorithmMismatchExceptionWithPayload.php
Normal file
32
src/JwkAlgorithmMismatchExceptionWithPayload.php
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
namespace Railgun\Jwt;
|
||||||
|
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when JwkSet::getKey() can find an algorithm with the provided key id, but the algorithm is a mismatch.
|
||||||
|
*/
|
||||||
|
class JwkAlgorithmMismatchExceptionWithPayload extends JwkAlgorithmMismatchException implements ThrowableWithPayload {
|
||||||
|
/** @var object */
|
||||||
|
private $payload;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param object $payload Data that was provided for the payload section.
|
||||||
|
* @param string $message The Exception message to throw.
|
||||||
|
* @param int $code The Exception code.
|
||||||
|
* @param ?Throwable $previous The previous exception used for the exception chaining.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
object $payload,
|
||||||
|
string $message = '',
|
||||||
|
int $code = 0,
|
||||||
|
?Throwable $previous = null
|
||||||
|
) {
|
||||||
|
$this->payload = $payload;
|
||||||
|
parent::__construct($message, $code, $previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPayload(): object {
|
||||||
|
return $this->payload;
|
||||||
|
}
|
||||||
|
}
|
13
src/JwkNotFoundException.php
Normal file
13
src/JwkNotFoundException.php
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
namespace Railgun\Jwt;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when JwkSet::getKey() cannot find a key that matches the provided arguments.
|
||||||
|
*/
|
||||||
|
class JwkNotFoundException extends RuntimeException implements ThrowableWithPayloadEquivalent {
|
||||||
|
public function withPayload(object $payload) {
|
||||||
|
return new JwkNotFoundExceptionWithPayload($payload, $this->getMessage(), $this->getCode(), $this);
|
||||||
|
}
|
||||||
|
}
|
32
src/JwkNotFoundExceptionWithPayload.php
Normal file
32
src/JwkNotFoundExceptionWithPayload.php
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
namespace Railgun\Jwt;
|
||||||
|
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when JwkSet::getKey() cannot find a key that matches the provided arguments.
|
||||||
|
*/
|
||||||
|
class JwkNotFoundExceptionWithPayload extends JwkNotFoundException implements ThrowableWithPayload {
|
||||||
|
/** @var object */
|
||||||
|
private $payload;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param object $payload Data that was provided for the payload section.
|
||||||
|
* @param string $message The Exception message to throw.
|
||||||
|
* @param int $code The Exception code.
|
||||||
|
* @param ?Throwable $previous The previous exception used for the exception chaining.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
object $payload,
|
||||||
|
string $message = '',
|
||||||
|
int $code = 0,
|
||||||
|
?Throwable $previous = null
|
||||||
|
) {
|
||||||
|
$this->payload = $payload;
|
||||||
|
parent::__construct($message, $code, $previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPayload(): object {
|
||||||
|
return $this->payload;
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,8 @@ namespace Railgun\Jwt;
|
||||||
use stdClass;
|
use stdClass;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Railgun\Jwt\Utility\{UriBase64,Json};
|
use Throwable;
|
||||||
|
use Railgun\Jwt\Utility\{ExceptionUtils,UriBase64,Json};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements the JWT decoder and encoder.
|
* Implements the JWT decoder and encoder.
|
||||||
|
@ -70,39 +71,44 @@ class JwtEncoder {
|
||||||
if(!is_object($payload))
|
if(!is_object($payload))
|
||||||
throw new InvalidArgumentException('$token payload part must be a JSON object');
|
throw new InvalidArgumentException('$token payload part must be a JSON object');
|
||||||
|
|
||||||
// decode headers section and ensure its type
|
try {
|
||||||
$headersRaw = UriBase64::decode($headersEnc);
|
// decode headers section and ensure its type
|
||||||
$headersDec = Json::decode($headersRaw);
|
$headersRaw = UriBase64::decode($headersEnc);
|
||||||
if(!is_object($headersDec))
|
$headersDec = Json::decode($headersRaw);
|
||||||
throw new InvalidArgumentExceptionWithPayload($payload, '$token header part must be a JSON object');
|
if(!is_object($headersDec))
|
||||||
|
throw new InvalidArgumentExceptionWithPayload($payload, '$token header part must be a JSON object');
|
||||||
|
|
||||||
if($headers === null) // assign to $headers if null
|
if($headers === null) // assign to $headers if null
|
||||||
$headers = $headersDec;
|
$headers = $headersDec;
|
||||||
elseif($mergeHeaders) // merge if needed
|
elseif($mergeHeaders) // merge if needed
|
||||||
$headers = (object)array_merge((array)$headersDec, $headers);
|
$headers = (object)array_merge((array)$headersDec, $headers);
|
||||||
else // fix type of $headers
|
else // fix type of $headers
|
||||||
$headers = (object)$headers;
|
$headers = (object)$headers;
|
||||||
|
|
||||||
// verify alg and kid header claims
|
// verify alg and kid header claims
|
||||||
if(!property_exists($headers, 'alg') || !is_string($headers->alg))
|
if(!property_exists($headers, 'alg') || !is_string($headers->alg))
|
||||||
throw new InvalidArgumentExceptionWithPayload($payload, '$token does not contain a valid alg claim in its headers section');
|
throw new InvalidArgumentExceptionWithPayload($payload, '$token does not contain a valid alg claim in its headers section');
|
||||||
|
|
||||||
// attempt to select the specified key
|
// attempt to select the specified key
|
||||||
$alg = $headers->alg;
|
$alg = $headers->alg;
|
||||||
$kid = property_exists($headers, 'kid') && is_string($headers->kid) ? $headers->kid : null;
|
$kid = property_exists($headers, 'kid') && is_string($headers->kid) ? $headers->kid : null;
|
||||||
$jwk = $this->keys->getKey($kid, $alg);
|
$jwk = $this->keys->getKey($kid, $alg);
|
||||||
|
|
||||||
// decode signature and reconstruct the value input
|
// decode signature and reconstruct the value input
|
||||||
$sig = UriBase64::decode($sigEnc);
|
$sig = UriBase64::decode($sigEnc);
|
||||||
$enc = sprintf('%s.%s', $headersEnc, $payloadEnc);
|
$enc = sprintf('%s.%s', $headersEnc, $payloadEnc);
|
||||||
|
|
||||||
// verify the signature section of $token
|
// verify the signature section of $token
|
||||||
if(!$jwk->verify($enc, $sig, $alg))
|
if(!$jwk->verify($enc, $sig, $alg))
|
||||||
throw new InvalidArgumentExceptionWithPayload($payload, '$token could not be verified');
|
throw new InvalidArgumentExceptionWithPayload($payload, '$token could not be verified');
|
||||||
|
|
||||||
// Run validators over the headers and payload sections
|
// Run validators over the headers and payload sections
|
||||||
foreach($this->validators as $validator)
|
foreach($this->validators as $validator)
|
||||||
$validator->validate($headers, $payload);
|
$validator->validate($headers, $payload);
|
||||||
|
} catch(Throwable $ex) {
|
||||||
|
// bundle our payload
|
||||||
|
throw ExceptionUtils::ensureWithPayload($payload, $ex);
|
||||||
|
}
|
||||||
|
|
||||||
// enjoy your payload!
|
// enjoy your payload!
|
||||||
return $payload;
|
return $payload;
|
||||||
|
|
37
src/LogicExceptionWithPayload.php
Normal file
37
src/LogicExceptionWithPayload.php
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
namespace Railgun\Jwt;
|
||||||
|
|
||||||
|
use LogicException;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a LogicException with access to a JWT's payload.
|
||||||
|
*/
|
||||||
|
class LogicExceptionWithPayload extends LogicException implements ThrowableWithPayload {
|
||||||
|
/** @var object */
|
||||||
|
private $payload;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param object $payload Data that was provided for the payload section.
|
||||||
|
* @param string $message The Exception message to throw.
|
||||||
|
* @param int $code The Exception code.
|
||||||
|
* @param ?Throwable $previous The previous exception used for the exception chaining.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
object $payload,
|
||||||
|
string $message = '',
|
||||||
|
int $code = 0,
|
||||||
|
?Throwable $previous = null
|
||||||
|
) {
|
||||||
|
$this->payload = $payload;
|
||||||
|
parent::__construct($message, $code, $previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPayload(): object {
|
||||||
|
return $this->payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withPayload(object $payload) {
|
||||||
|
return new LogicExceptionWithPayload($payload, $this->getMessage(), $this->getCode(), $this);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Railgun\Jwt;
|
namespace Railgun\Jwt;
|
||||||
|
|
||||||
use RuntimeException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a JwkSet containing only a NoneJwk.
|
* Represents a JwkSet containing only a NoneJwk.
|
||||||
*/
|
*/
|
||||||
|
@ -24,7 +22,7 @@ class NoneJwkSet implements JwkSet {
|
||||||
|
|
||||||
public function getKey(?string $keyId = null, ?string $algo = null): Jwk {
|
public function getKey(?string $keyId = null, ?string $algo = null): Jwk {
|
||||||
if($algo !== null && $algo !== 'none')
|
if($algo !== null && $algo !== 'none')
|
||||||
throw new RuntimeException('could not find a key that matched the requested algorithm');
|
throw new JwkAlgorithmMismatchException('could not find a key that matched the requested algorithm');
|
||||||
|
|
||||||
return $this->jwk;
|
return $this->jwk;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ use Throwable;
|
||||||
/**
|
/**
|
||||||
* Provides an RuntimeException with access to a JWT's payload.
|
* Provides an RuntimeException with access to a JWT's payload.
|
||||||
*/
|
*/
|
||||||
class RuntimeExceptionWithPayload extends RuntimeException implements WithPayload {
|
class RuntimeExceptionWithPayload extends RuntimeException implements ThrowableWithPayload {
|
||||||
/** @var object */
|
/** @var object */
|
||||||
private $payload;
|
private $payload;
|
||||||
|
|
||||||
|
@ -31,14 +31,7 @@ class RuntimeExceptionWithPayload extends RuntimeException implements WithPayloa
|
||||||
return $this->payload;
|
return $this->payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function withPayload(object $payload) {
|
||||||
* Converts a normal RuntimeException to one with a payload.
|
return new RuntimeExceptionWithPayload($payload, $this->getMessage(), $this->getCode(), $this);
|
||||||
*
|
|
||||||
* @param object $payload Data that was provided for the payload section.
|
|
||||||
* @param Throwable $previous Exception to convert.
|
|
||||||
* @return RuntimeExceptionWithPayload
|
|
||||||
*/
|
|
||||||
public static function convert(object $payload, Throwable $previous) {
|
|
||||||
return new RuntimeExceptionWithPayload($payload, $previous->getMessage(), (int)$previous->getCode(), $previous);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
namespace Railgun\Jwt;
|
namespace Railgun\Jwt;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a common interface to check for exception types that contain ::getPayload().
|
* Provides a common interface to check for Throwable types that contain ::getPayload().
|
||||||
*/
|
*/
|
||||||
interface WithPayload {
|
interface ThrowableWithPayload extends ThrowableWithPayloadEquivalent {
|
||||||
/**
|
/**
|
||||||
* Gets the JWT payload value.
|
* Gets the JWT payload value.
|
||||||
*
|
*
|
17
src/ThrowableWithPayloadEquivalent.php
Normal file
17
src/ThrowableWithPayloadEquivalent.php
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
namespace Railgun\Jwt;
|
||||||
|
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complements the WithPayload interface.
|
||||||
|
*/
|
||||||
|
interface ThrowableWithPayloadEquivalent extends Throwable {
|
||||||
|
/**
|
||||||
|
* Creates a clone of the current object using its equivalent that can contain JWT payload data.
|
||||||
|
*
|
||||||
|
* @param object $payload JWT payload data.
|
||||||
|
* @return ThrowableWithPayload
|
||||||
|
*/
|
||||||
|
public function withPayload(object $payload);
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ use UnexpectedValueException;
|
||||||
/**
|
/**
|
||||||
* Provides an UnexpectedValueException with access to a JWT's payload.
|
* Provides an UnexpectedValueException with access to a JWT's payload.
|
||||||
*/
|
*/
|
||||||
class UnexpectedValueExceptionWithPayload extends UnexpectedValueException implements WithPayload {
|
class UnexpectedValueExceptionWithPayload extends UnexpectedValueException implements ThrowableWithPayload {
|
||||||
/** @var object */
|
/** @var object */
|
||||||
private $payload;
|
private $payload;
|
||||||
|
|
||||||
|
@ -31,14 +31,7 @@ class UnexpectedValueExceptionWithPayload extends UnexpectedValueException imple
|
||||||
return $this->payload;
|
return $this->payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function withPayload(object $payload) {
|
||||||
* Converts a normal UnexpectedValueException to one with a payload.
|
return new UnexpectedValueExceptionWithPayload($payload, $this->getMessage(), $this->getCode(), $this);
|
||||||
*
|
|
||||||
* @param object $payload Data that was provided for the payload section.
|
|
||||||
* @param UnexpectedValueException $previous Exception to convert.
|
|
||||||
* @return UnexpectedValueExceptionWithPayload
|
|
||||||
*/
|
|
||||||
public static function convert(object $payload, UnexpectedValueException $previous): self {
|
|
||||||
return new UnexpectedValueExceptionWithPayload($payload, $previous->getMessage(), $previous->getCode(), $previous);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,21 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Railgun\Jwt\Utility;
|
namespace Railgun\Jwt\Utility;
|
||||||
|
|
||||||
|
use Error;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
|
use LogicException;
|
||||||
|
use RuntimeException;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
use UnexpectedValueException;
|
use UnexpectedValueException;
|
||||||
use Railgun\Jwt\{
|
use Railgun\Jwt\{
|
||||||
|
ErrorWithPayload,
|
||||||
|
ExceptionWithPayload,
|
||||||
InvalidArgumentExceptionWithPayload,
|
InvalidArgumentExceptionWithPayload,
|
||||||
|
LogicExceptionWithPayload,
|
||||||
RuntimeExceptionWithPayload,
|
RuntimeExceptionWithPayload,
|
||||||
|
ThrowableWithPayload,
|
||||||
|
ThrowableWithPayloadEquivalent,
|
||||||
UnexpectedValueExceptionWithPayload,
|
UnexpectedValueExceptionWithPayload,
|
||||||
WithPayload
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -22,19 +29,33 @@ final class ExceptionUtils {
|
||||||
*
|
*
|
||||||
* @param object $payload JWT payload section.
|
* @param object $payload JWT payload section.
|
||||||
* @param Throwable $exception Exception to potentially convert.
|
* @param Throwable $exception Exception to potentially convert.
|
||||||
* @return Throwable&WithPayload
|
* @return ThrowableWithPayload
|
||||||
*/
|
*/
|
||||||
public static function ensureWithPayload(object $payload, Throwable $exception): Throwable {
|
public static function ensureWithPayload(object $payload, Throwable $exception): ThrowableWithPayload {
|
||||||
// If we already have a payload, just return immediately.
|
// If we already have a payload, just return immediately.
|
||||||
if($exception instanceof WithPayload && $exception->getPayload() === $payload)
|
if($exception instanceof ThrowableWithPayload && $exception->getPayload() === $payload)
|
||||||
return $exception;
|
return $exception;
|
||||||
|
|
||||||
if($exception instanceof InvalidArgumentException)
|
if($exception instanceof ThrowableWithPayloadEquivalent)
|
||||||
return InvalidArgumentExceptionWithPayload::convert($payload, $exception);
|
return $exception->withPayload($payload);
|
||||||
|
|
||||||
if($exception instanceof UnexpectedValueException)
|
if($exception instanceof LogicException) {
|
||||||
return UnexpectedValueExceptionWithPayload::convert($payload, $exception);
|
if($exception instanceof InvalidArgumentException)
|
||||||
|
return new InvalidArgumentExceptionWithPayload($payload, $exception->getMessage(), $exception->getCode(), $exception);
|
||||||
|
|
||||||
return RuntimeExceptionWithPayload::convert($payload, $exception);
|
return new LogicExceptionWithPayload($payload, $exception->getMessage(), $exception->getCode(), $exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($exception instanceof RuntimeException) {
|
||||||
|
if($exception instanceof UnexpectedValueException)
|
||||||
|
return new UnexpectedValueExceptionWithPayload($payload, $exception->getMessage(), $exception->getCode(), $exception);
|
||||||
|
|
||||||
|
return new RuntimeExceptionWithPayload($payload, $exception->getMessage(), (int)$exception->getCode(), $exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($exception instanceof Error)
|
||||||
|
return new ErrorWithPayload($payload, $exception->getMessage(), $exception->getCode(), $exception);
|
||||||
|
|
||||||
|
return new ExceptionWithPayload($payload, $exception->getMessage(), (int)$exception->getCode(), $exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,10 @@
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
use Railgun\Jwt\{
|
use Railgun\Jwt\{
|
||||||
|
ErrorWithPayload,
|
||||||
|
ExceptionWithPayload,
|
||||||
InvalidArgumentExceptionWithPayload,
|
InvalidArgumentExceptionWithPayload,
|
||||||
|
LogicExceptionWithPayload,
|
||||||
RuntimeExceptionWithPayload,
|
RuntimeExceptionWithPayload,
|
||||||
UnexpectedValueExceptionWithPayload,
|
UnexpectedValueExceptionWithPayload,
|
||||||
WithPayload
|
WithPayload
|
||||||
|
@ -24,12 +27,16 @@ final class ExceptionUtilsTest extends TestCase {
|
||||||
[new InvalidArgumentException('test', 1234), InvalidArgumentExceptionWithPayload::class, $payload, false],
|
[new InvalidArgumentException('test', 1234), InvalidArgumentExceptionWithPayload::class, $payload, false],
|
||||||
[new UnexpectedValueException('test', 5678), UnexpectedValueExceptionWithPayload::class, $payload, false],
|
[new UnexpectedValueException('test', 5678), UnexpectedValueExceptionWithPayload::class, $payload, false],
|
||||||
[new RuntimeException('test', 0xF00F), RuntimeExceptionWithPayload::class, $payload, false],
|
[new RuntimeException('test', 0xF00F), RuntimeExceptionWithPayload::class, $payload, false],
|
||||||
[new LogicException('test', 0, new InvalidArgumentException('inner')), RuntimeExceptionWithPayload::class, $payload, false],
|
[new LogicException('test', 0, new InvalidArgumentException('inner')), LogicExceptionWithPayload::class, $payload, false],
|
||||||
[new Exception('test'), RuntimeExceptionWithPayload::class, $payload, false],
|
[new Exception('test'), ExceptionWithPayload::class, $payload, false],
|
||||||
[new InvalidArgumentExceptionWithPayload($payload, 'test'), InvalidArgumentExceptionWithPayload::class, $payload, true],
|
[new InvalidArgumentExceptionWithPayload($payload, 'test'), InvalidArgumentExceptionWithPayload::class, $payload, true],
|
||||||
[new UnexpectedValueExceptionWithPayload($payload, 'test'), UnexpectedValueExceptionWithPayload::class, $payload, true],
|
[new UnexpectedValueExceptionWithPayload($payload, 'test'), UnexpectedValueExceptionWithPayload::class, $payload, true],
|
||||||
[new RuntimeExceptionWithPayload($payload, 'test'), RuntimeExceptionWithPayload::class, $payload, true],
|
[new RuntimeExceptionWithPayload($payload, 'test'), RuntimeExceptionWithPayload::class, $payload, true],
|
||||||
[new RuntimeExceptionWithPayload($other, 'test'), RuntimeExceptionWithPayload::class, $payload, false],
|
[new RuntimeExceptionWithPayload($other, 'test'), RuntimeExceptionWithPayload::class, $payload, false],
|
||||||
|
[new TypeError('test'), ErrorWithPayload::class, $payload, false],
|
||||||
|
[new LengthException('test'), LogicExceptionWithPayload::class, $payload, false],
|
||||||
|
[new IntlException('test'), ExceptionWithPayload::class, $payload, false],
|
||||||
|
[new PDOException('test'), RuntimeExceptionWithPayload::class, $payload, false],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ use Railgun\Jwt\Validators\{
|
||||||
|
|
||||||
final class NotValidBeforeValidatorTest extends TestCase {
|
final class NotValidBeforeValidatorTest extends TestCase {
|
||||||
/**
|
/**
|
||||||
* @return array{int, int, string[]|null, object, ?class-string<Throwable>}[]
|
* @return array{int, int, ?non-empty-array<string>, object, ?class-string<Throwable>}[]
|
||||||
*/
|
*/
|
||||||
public function notValidBeforeDataProvider(): array {
|
public function notValidBeforeDataProvider(): array {
|
||||||
return [
|
return [
|
||||||
|
@ -27,6 +27,8 @@ final class NotValidBeforeValidatorTest extends TestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param ?non-empty-array<string> $claims
|
||||||
|
* @param ?class-string<Throwable> $throwable
|
||||||
* @dataProvider notValidBeforeDataProvider
|
* @dataProvider notValidBeforeDataProvider
|
||||||
*/
|
*/
|
||||||
public function testNotValidBefore(int $leeway, int $time, ?array $claims, object $payload, ?string $throwable): void {
|
public function testNotValidBefore(int $leeway, int $time, ?array $claims, object $payload, ?string $throwable): void {
|
||||||
|
@ -43,6 +45,7 @@ final class NotValidBeforeValidatorTest extends TestCase {
|
||||||
? new NotValidBeforeValidator($leeway, $time)
|
? new NotValidBeforeValidator($leeway, $time)
|
||||||
: new NotValidBeforeValidator($leeway, $time, $claims);
|
: new NotValidBeforeValidator($leeway, $time, $claims);
|
||||||
|
|
||||||
$this->assertNull($validator->validate((object)[], $payload));
|
$validator->validate((object)[], $payload);
|
||||||
|
$this->addToAssertionCount(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ use Railgun\Jwt\Validators\{
|
||||||
|
|
||||||
final class TokenExpirationValidatorTest extends TestCase {
|
final class TokenExpirationValidatorTest extends TestCase {
|
||||||
/**
|
/**
|
||||||
* @return array{int, int, string[]|null, object, ?class-string<Throwable>}[]
|
* @return array{int, int, ?non-empty-array<string>, object, ?class-string<Throwable>}[]
|
||||||
*/
|
*/
|
||||||
public function tokenExpirationProvider(): array {
|
public function tokenExpirationProvider(): array {
|
||||||
return [
|
return [
|
||||||
|
@ -25,6 +25,8 @@ final class TokenExpirationValidatorTest extends TestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param ?non-empty-array<string> $claims
|
||||||
|
* @param ?class-string<Throwable> $throwable
|
||||||
* @dataProvider tokenExpirationProvider
|
* @dataProvider tokenExpirationProvider
|
||||||
*/
|
*/
|
||||||
public function testTokenExpiration(int $leeway, int $time, ?array $claims, object $payload, ?string $throwable): void {
|
public function testTokenExpiration(int $leeway, int $time, ?array $claims, object $payload, ?string $throwable): void {
|
||||||
|
@ -41,6 +43,7 @@ final class TokenExpirationValidatorTest extends TestCase {
|
||||||
? new TokenExpirationValidator($leeway, $time)
|
? new TokenExpirationValidator($leeway, $time)
|
||||||
: new TokenExpirationValidator($leeway, $time, $claims);
|
: new TokenExpirationValidator($leeway, $time, $claims);
|
||||||
|
|
||||||
$this->assertNull($validator->validate((object)[], $payload));
|
$validator->validate((object)[], $payload);
|
||||||
|
$this->addToAssertionCount(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue