Revising how/what exceptions are thrown (unfinished).

This commit is contained in:
flash 2025-05-15 22:19:59 +02:00
parent 46ec5b9d88
commit dc38d6e423
Signed by: flash
GPG key ID: 6D833BE0D210AC02
21 changed files with 319 additions and 87 deletions

View file

@ -1 +1 @@
v0.3.3 v0.4.0

View file

@ -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
View 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);
}
}

View 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);
}
}

View file

@ -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;
} }

View file

@ -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);
} }
} }

View 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);
}
}

View 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;
}
}

View 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);
}
}

View 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;
}
}

View file

@ -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;

View 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);
}
}

View file

@ -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;
} }

View file

@ -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);
} }
} }

View file

@ -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.
* *

View 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);
}

View file

@ -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);
} }
} }

View file

@ -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);
} }
} }

View file

@ -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],
]; ];
} }

View file

@ -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);
} }
} }

View file

@ -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);
} }
} }