Removed support for PHP versions older than 7.2 to add PSR-7 dependency.

This commit is contained in:
flash 2025-05-12 14:56:01 +02:00
parent da5ee82055
commit 8a179c62a4
Signed by: flash
GPG key ID: 6D833BE0D210AC02
40 changed files with 233 additions and 5446 deletions

View file

@ -4,9 +4,6 @@ export ROOT
PHPSTAN_CONFIG := $(realpath phpstan.neon)
export PHPSTAN_CONFIG
PHPSTAN_LEGACY_CONFIG := $(realpath phpstan-legacy.neon)
export PHPSTAN_LEGACY_CONFIG
SRC_DIR := $(realpath src)
export SRC_DIR
@ -15,10 +12,8 @@ DEV_DIR := $(realpath dev)
DEV_DIRS := $(wildcard ${DEV_DIR}/*)
# We'll use the lowest common denominator to maintain the root packages
PHP_506 := $(shell which php5.6)
export PHP_506
COMPOSER_LTS := $(shell which composer-lts)
export COMPOSER_LTS
PHP_702 := $(shell which php7.2)
COMPOSER := $(shell which composer)
SUDO = $(shell which sudo)
@ -28,10 +23,10 @@ GROUP_NAME = $(shell id -gn)
${DEV_TARGETS}: ${DEV_DIRS}
install:
${PHP_506} ${COMPOSER_LTS} install
${PHP_702} ${COMPOSER} install
update:
${PHP_506} ${COMPOSER_LTS} update
${PHP_702} ${COMPOSER} update
fix-perms: # WSL skill issues
${SUDO} chown -R ${USER_NAME}:${GROUP_NAME} .

View file

@ -4,8 +4,3 @@ This is a JWT library that exists because I ran into issues with the more common
It is very, very loosely based on it in but you'll probably not run into many familiar things.
I'll write a better readme Someday...
## Why does this target PHP 5.6?????
The only core dependency this library has is PHP Sec Lib, which also targets PHP 5.6.
Although I'm also doing it as a personal challenge to see if I can support this insane setup.

View file

@ -5,8 +5,9 @@
"homepage": "https://railgun.sh/libs/jwt",
"license": "bsd-3-clause-clear",
"require": {
"php": ">=5.6",
"phpseclib/phpseclib": "~3.0"
"php": "^7.2 || ^8.0",
"phpseclib/phpseclib": "~3.0",
"psr/http-message": "^2.0"
},
"authors": [
{

99
composer.lock generated
View file

@ -4,28 +4,28 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "69fe58fe65d92e48451e103ec91a85a2",
"content-hash": "d7ada60109a3a64c5981029ad8ab90bc",
"packages": [
{
"name": "paragonie/constant_time_encoding",
"version": "v1.1.0",
"version": "v2.7.0",
"source": {
"type": "git",
"url": "https://github.com/paragonie/constant_time_encoding.git",
"reference": "317718fb438e60151f72b20404f040cb5ae1d494"
"reference": "52a0d99e69f56b9ec27ace92ba56897fe6993105"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/317718fb438e60151f72b20404f040cb5ae1d494",
"reference": "317718fb438e60151f72b20404f040cb5ae1d494",
"url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/52a0d99e69f56b9ec27ace92ba56897fe6993105",
"reference": "52a0d99e69f56b9ec27ace92ba56897fe6993105",
"shasum": ""
},
"require": {
"php": "^5.3|^7|^8"
"php": "^7|^8"
},
"require-dev": {
"paragonie/random_compat": "^1.4|^2",
"phpunit/phpunit": ">= 4"
"phpunit/phpunit": "^6|^7|^8|^9",
"vimeo/psalm": "^1|^2|^3|^4"
},
"type": "library",
"autoload": {
@ -71,37 +71,33 @@
"issues": "https://github.com/paragonie/constant_time_encoding/issues",
"source": "https://github.com/paragonie/constant_time_encoding"
},
"time": "2022-01-17T05:23:46+00:00"
"time": "2024-05-08T12:18:48+00:00"
},
{
"name": "paragonie/random_compat",
"version": "v2.0.21",
"version": "v9.99.100",
"source": {
"type": "git",
"url": "https://github.com/paragonie/random_compat.git",
"reference": "96c132c7f2f7bc3230723b66e89f8f150b29d5ae"
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/96c132c7f2f7bc3230723b66e89f8f150b29d5ae",
"reference": "96c132c7f2f7bc3230723b66e89f8f150b29d5ae",
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a",
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a",
"shasum": ""
},
"require": {
"php": ">=5.2.0"
"php": ">= 7"
},
"require-dev": {
"phpunit/phpunit": "*"
"phpunit/phpunit": "4.*|5.*",
"vimeo/psalm": "^1"
},
"suggest": {
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
},
"type": "library",
"autoload": {
"files": [
"lib/random.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
@ -125,7 +121,7 @@
"issues": "https://github.com/paragonie/random_compat/issues",
"source": "https://github.com/paragonie/random_compat"
},
"time": "2022-02-16T17:07:03+00:00"
"time": "2020-10-15T08:29:30+00:00"
},
{
"name": "phpseclib/phpseclib",
@ -236,17 +232,70 @@
}
],
"time": "2024-12-14T21:12:59+00:00"
},
{
"name": "psr/http-message",
"version": "2.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-message.git",
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71",
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for HTTP messages",
"homepage": "https://github.com/php-fig/http-message",
"keywords": [
"http",
"http-message",
"psr",
"psr-7",
"request",
"response"
],
"support": {
"source": "https://github.com/php-fig/http-message/tree/2.0"
},
"time": "2023-04-04T09:54:51+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=5.6"
"php": "^7.2 || ^8.0"
},
"platform-dev": [],
"plugin-api-version": "2.2.0"
"platform-dev": {},
"plugin-api-version": "2.6.0"
}

View file

@ -1,17 +0,0 @@
PHP_506 := $(shell which php5.6)
COMPOSER_LTS := $(shell which composer-lts)
VENDOR_BIN := $(realpath vendor/bin)
PHPUNIT = "${VENDOR_BIN}/phpunit"
install:
${PHP_506} ${COMPOSER_LTS} install
update:
${PHP_506} ${COMPOSER_LTS} update
analyze: analyse
analyse:
echo No static analysis available for PHP 5.6.
.PHONY: install update analyze analyse

View file

@ -1,5 +0,0 @@
{
"require-dev": {
"phpunit/phpunit": "~5.7"
}
}

1550
dev/php506/composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,17 +0,0 @@
PHP_700 := $(shell which php7.0)
COMPOSER_LTS := $(shell which composer-lts)
VENDOR_BIN := $(realpath vendor/bin)
PHPUNIT = "${VENDOR_BIN}/phpunit"
install:
${PHP_700} ${COMPOSER_LTS} install
update:
${PHP_700} ${COMPOSER_LTS} update
analyze: analyse
analyse:
echo No static analysis available for PHP 7.0.
.PHONY: install update analyze analyse

View file

@ -1,5 +0,0 @@
{
"require-dev": {
"phpunit/phpunit": "~6.5"
}
}

1712
dev/php700/composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,22 +0,0 @@
PHP_701 := $(shell which php7.1)
COMPOSER_LTS := $(shell which composer-lts)
VENDOR_BIN := $(realpath vendor/bin)
PHPSTAN = "${VENDOR_BIN}/phpstan"
PHPUNIT = "${VENDOR_BIN}/phpunit"
ifeq ($(origin PHPSTAN_LEGACY_CONFIG),undefined)
$(error PHPSTAN_LEGACY_CONFIG is not defined)
endif
install:
${PHP_701} ${COMPOSER_LTS} install
update:
${PHP_701} ${COMPOSER_LTS} update
analyze: analyse
analyse:
${PHP_701} ${PHPSTAN} analyze -c ${PHPSTAN_LEGACY_CONFIG}
.PHONY: install update analyze analyse

View file

@ -1,6 +0,0 @@
{
"require-dev": {
"phpstan/phpstan": "^1.4",
"phpunit/phpunit": "~7.5"
}
}

1763
dev/php701/composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -626,16 +626,16 @@
},
{
"name": "phpunit/phpunit",
"version": "11.5.19",
"version": "11.5.20",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "0da1ebcdbc4d5bd2d189cfe02846a89936d8dda5"
"reference": "e6bdea63ecb7a8287d2cdab25bdde3126e0cfe6f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0da1ebcdbc4d5bd2d189cfe02846a89936d8dda5",
"reference": "0da1ebcdbc4d5bd2d189cfe02846a89936d8dda5",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e6bdea63ecb7a8287d2cdab25bdde3126e0cfe6f",
"reference": "e6bdea63ecb7a8287d2cdab25bdde3126e0cfe6f",
"shasum": ""
},
"require": {
@ -707,7 +707,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.19"
"source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.20"
},
"funding": [
{
@ -731,7 +731,7 @@
"type": "tidelift"
}
],
"time": "2025-05-02T06:56:52+00:00"
"time": "2025-05-11T06:39:52+00:00"
},
{
"name": "sebastian/cli-parser",

View file

@ -637,16 +637,16 @@
},
{
"name": "phpunit/phpunit",
"version": "12.1.4",
"version": "12.1.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "5ee57ad690bda2c487594577600931a99053436c"
"reference": "f93ef2198df8d54b3195bcee381a33be51d8705e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5ee57ad690bda2c487594577600931a99053436c",
"reference": "5ee57ad690bda2c487594577600931a99053436c",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f93ef2198df8d54b3195bcee381a33be51d8705e",
"reference": "f93ef2198df8d54b3195bcee381a33be51d8705e",
"shasum": ""
},
"require": {
@ -660,7 +660,7 @@
"phar-io/manifest": "^2.0.4",
"phar-io/version": "^3.2.1",
"php": ">=8.3",
"phpunit/php-code-coverage": "^12.1.2",
"phpunit/php-code-coverage": "^12.2.1",
"phpunit/php-file-iterator": "^6.0.0",
"phpunit/php-invoker": "^6.0.0",
"phpunit/php-text-template": "^5.0.0",
@ -714,7 +714,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/12.1.4"
"source": "https://github.com/sebastianbergmann/phpunit/tree/12.1.5"
},
"funding": [
{
@ -738,7 +738,7 @@
"type": "tidelift"
}
],
"time": "2025-05-02T07:01:56+00:00"
"time": "2025-05-11T06:44:52+00:00"
},
{
"name": "sebastian/cli-parser",

View file

@ -637,16 +637,16 @@
},
{
"name": "phpunit/phpunit",
"version": "12.1.4",
"version": "12.1.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "5ee57ad690bda2c487594577600931a99053436c"
"reference": "f93ef2198df8d54b3195bcee381a33be51d8705e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/5ee57ad690bda2c487594577600931a99053436c",
"reference": "5ee57ad690bda2c487594577600931a99053436c",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f93ef2198df8d54b3195bcee381a33be51d8705e",
"reference": "f93ef2198df8d54b3195bcee381a33be51d8705e",
"shasum": ""
},
"require": {
@ -660,7 +660,7 @@
"phar-io/manifest": "^2.0.4",
"phar-io/version": "^3.2.1",
"php": ">=8.3",
"phpunit/php-code-coverage": "^12.1.2",
"phpunit/php-code-coverage": "^12.2.1",
"phpunit/php-file-iterator": "^6.0.0",
"phpunit/php-invoker": "^6.0.0",
"phpunit/php-text-template": "^5.0.0",
@ -714,7 +714,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/12.1.4"
"source": "https://github.com/sebastianbergmann/phpunit/tree/12.1.5"
},
"funding": [
{
@ -738,7 +738,7 @@
"type": "tidelift"
}
],
"time": "2025-05-02T07:01:56+00:00"
"time": "2025-05-11T06:44:52+00:00"
},
{
"name": "sebastian/cli-parser",

View file

@ -1,9 +0,0 @@
parameters:
level: 9
checkUninitializedProperties: true
treatPhpDocTypesAsCertain: false
reportUnmatchedIgnoredErrors: false
paths:
- src
bootstrapFiles:
- vendor/autoload.php

View file

@ -1,8 +1,8 @@
<?php
namespace Railgun\Jwt;
use RuntimeException;
use InvalidArgumentException;
use RuntimeException;
/**
* Represents a JSON Web Key Set backed by an array.
@ -15,9 +15,7 @@ class ArrayJwkSet implements JwkSet {
* @param array<string, Jwk> $keys
* @throws InvalidArgumentException
*/
public function __construct($keys) {
if(!is_array($keys))
throw new InvalidArgumentException('$keys must be an array');
public function __construct(array $keys) {
if(!array_all($keys, [self::class, 'ensureArrayType']))
throw new InvalidArgumentException('$keys does not conform array<string, Jwk>');
@ -29,20 +27,15 @@ class ArrayJwkSet implements JwkSet {
* @param mixed $key
* @return bool
*/
private static function ensureArrayType($value, $key) {
private static function ensureArrayType($value, $key): bool {
return !is_string($key) || !($value instanceof Jwk);
}
public function getKeys() {
public function getKeys(): array {
return $this->keys;
}
public function getKey($keyId = null, $algo = null) {
if($keyId !== null && !is_string($keyId))
throw new InvalidArgumentException('$keyId must be a string or null');
if($algo !== null && !is_string($algo))
throw new InvalidArgumentException('$algo must be a string or null');
public function getKey(?string $keyId = null, ?string $algo = null): Jwk {
if($keyId === null) {
if($algo === null)
return $this->keys[array_key_first($this->keys)];

View file

@ -1,8 +1,8 @@
<?php
namespace Railgun\Jwt;
use RuntimeException;
use InvalidArgumentException;
use RuntimeException;
/**
* Represents a HMAC JSON Web Key.
@ -27,36 +27,24 @@ class HmacJwk implements Jwk {
*/
public function __construct(
#[\SensitiveParameter]
$key,
$algo = null,
$id = null
string $key,
?string $algo = null,
?string $id = null
) {
if(!is_string($key))
throw new InvalidArgumentException('$key must be a string');
if($algo !== null && !is_string($algo))
throw new InvalidArgumentException('$algo must be a string or null');
if($id !== null && !is_string($id))
throw new InvalidArgumentException('$id must be a string or null');
$this->key = $key;
$this->algo = $algo;
$this->id = $id;
}
public function getId() {
public function getId(): ?string {
return $this->id;
}
public function getAlgorithm() {
public function getAlgorithm(): ?string {
return $this->algo;
}
public function sign($data, $algo = null) {
if(!is_string($data))
throw new InvalidArgumentException('$data must be a string');
if($algo !== null && !is_string($algo))
throw new InvalidArgumentException('$algo must be a string or null');
public function sign(string $data, ?string $algo = null): string {
if($algo === null) {
if($this->algo === null)
throw new InvalidArgumentException('this key does not specify a default algorithm, you must specify $algo');
@ -74,10 +62,7 @@ class HmacJwk implements Jwk {
return $result;
}
public function verify($data, $signature, $algo = null) {
if(!is_string($signature))
throw new InvalidArgumentException('$signature must be a string');
public function verify(string $data, string $signature, ?string $algo = null): bool {
return hash_equals($this->sign($data, $algo), $signature);
}
@ -86,7 +71,7 @@ class HmacJwk implements Jwk {
*
* @return string[]
*/
public static function algorithms() {
public static function algorithms(): array {
return array_keys(self::$algos);
}
@ -97,11 +82,7 @@ class HmacJwk implements Jwk {
* @param string $algo Native PHP algorithm.
* @return void
*/
public static function defineAlgorithm($alias, $algo) {
if(!is_string($alias))
throw new InvalidArgumentException('$alias must be a string');
if(!is_string($algo))
throw new InvalidArgumentException('$algo must be a string');
public static function defineAlgorithm(string $alias, string $algo): void {
if(array_key_exists($alias, self::$algos))
throw new InvalidArgumentException('$alias has already been defined');
if(!in_array($algo, hash_hmac_algos()))

View file

@ -1,8 +1,8 @@
<?php
namespace Railgun\Jwt;
use Exception;
use InvalidArgumentException;
use Throwable;
/**
* Provides an InvalidArgumentException with access to a JWT's payload.
@ -15,18 +15,19 @@ class InvalidArgumentExceptionWithPayload extends InvalidArgumentException imple
* @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 ?Exception $previous The previous exception used for the exception chaining.
* Yes, this should be Throwable, but that doesn't exist in PHP 5.6.
* @param ?Throwable $previous The previous exception used for the exception chaining.
*/
public function __construct($payload, $message = '', $code = 0, $previous = null) {
if(!is_object($payload))
throw new InvalidArgumentException('$payload must be an object');
public function __construct(
object $payload,
string $message = '',
int $code = 0,
?Throwable $previous = null
) {
$this->payload = $payload;
parent::__construct($message, $code, $previous);
}
public function getPayload() {
public function getPayload(): object {
return $this->payload;
}
@ -35,15 +36,9 @@ class InvalidArgumentExceptionWithPayload extends InvalidArgumentException imple
*
* @param object $payload Data that was provided for the payload section.
* @param InvalidArgumentException $previous Exception to convert.
* @throws InvalidArgumentException If any of the argument types were not as expected.
* @return InvalidArgumentExceptionWithPayload
*/
public static function convert($payload, $previous) {
if(!is_object($payload))
throw new InvalidArgumentException('$payload must be an object');
if(!($previous instanceof InvalidArgumentException))
throw new InvalidArgumentExceptionWithPayload($payload, '$previous must be InvalidArgumentException');
public static function convert(object $payload, InvalidArgumentException $previous) {
return new InvalidArgumentExceptionWithPayload($payload, $previous->getMessage(), $previous->getCode(), $previous);
}
}

View file

@ -11,12 +11,12 @@ interface Jwk {
/**
* @return ?string
*/
public function getId();
public function getId(): ?string;
/**
* @return ?string
*/
public function getAlgorithm();
public function getAlgorithm(): ?string;
/**
* @param string $data
@ -25,7 +25,7 @@ interface Jwk {
* @throws InvalidArgumentException If any of the argument types were not as expected.
* @return string
*/
public function sign($data, $algo = null);
public function sign(string $data, ?string $algo = null): string;
/**
* @param string $data
@ -34,5 +34,5 @@ interface Jwk {
* @throws InvalidArgumentException If any of the argument types were not as expected.
* @return bool
*/
public function verify($data, $signature, $algo = null);
public function verify(string $data, string $signature, ?string $algo = null): bool;
}

View file

@ -11,7 +11,7 @@ interface JwkSet {
/**
* @return array<string, Jwk>
*/
public function getKeys();
public function getKeys(): array;
/**
* @param ?string $keyId
@ -20,5 +20,5 @@ interface JwkSet {
* @throws InvalidArgumentException If any of the argument types were not as expected.
* @return Jwk
*/
public function getKey($keyId = null, $algo = null);
public function getKey(?string $keyId = null, ?string $algo = null): Jwk;
}

View file

@ -22,7 +22,6 @@ class Jwt {
* @param ?string $alg Algorithm to sign the token with, must be supported by $key. If null, $key is expected to return a default value.
* @param array<string, mixed>|object|null $headers Additional JWT header data, may not contain typ, alg or kid.
* @throws InvalidArgumentException If any of the argument types were not as expected.
* @throws UnexpectedValueException If any of the methods in $key returned unexpected values.
* @return string
*/
public static function encode(
@ -53,8 +52,6 @@ class Jwt {
$algo = $key->getAlgorithm();
if($algo === null)
throw new InvalidArgumentException('$key did not provide a default algorithm, $alg must be specified');
if(!is_string($algo))
throw new UnexpectedValueException('$key->getAlgorithm() must return a string or null');
$headers['alg'] = $algo;
} else {
@ -65,12 +62,8 @@ class Jwt {
}
$keyId = $key->getId();
if($keyId !== null) {
if(!is_string($keyId))
throw new UnexpectedValueException('$key->getId() must return a string or null');
if($keyId !== null)
$headers['kid'] = $keyId;
}
$headers = Json::encode(array_merge($headers, $tmp));
$payload = Json::encode($payload);
@ -78,10 +71,7 @@ class Jwt {
$encoded = sprintf('%s.%s', UriBase64::encode($headers), UriBase64::encode($payload));
$signature = $key->sign($encoded, $alg);
if(!is_string($signature))
throw new UnexpectedValueException('$key->sign() did not return a string');
if(!empty($signature))
$encoded = sprintf('%s.%s', $encoded, UriBase64::encode($signature));
$encoded = sprintf('%s.%s', $encoded, UriBase64::encode($signature));
return $encoded;
}
@ -139,16 +129,10 @@ class Jwt {
throw new UnexpectedValueException('$token payload part must be a JSON object');
$key = $keys->getKey($kid, $alg);
if(!($key instanceof Jwk))
throw new UnexpectedValueException('$keys->getKey() did not return an instance of Jwk');
$sig = UriBase64::decode($sigEnc);
$enc = sprintf('%s.%s', $headerEnc, $payloadEnc);
$verify = $key->verify($enc, $sig, $alg);
if(!is_bool($verify))
throw new UnexpectedValueException('$key->verify() did not return a boolean');
if(!$verify)
if(!$key->verify($enc, $sig, $alg))
throw new RuntimeException('could not verify signature of $token');
// move these to a chaining system

View file

@ -4,7 +4,6 @@ namespace Railgun\Jwt;
use stdClass;
use InvalidArgumentException;
use RuntimeException;
use UnexpectedValueException;
use Railgun\Jwt\Utility\{UriBase64,Json};
/**
@ -23,10 +22,8 @@ class JwtEncoder {
*/
public function __construct(
JwkSet $keys,
$validators = []
array $validators = []
) {
if(!is_array($validators))
throw new InvalidArgumentException('$validators must be an array');
if(!array_all($validators, function($item) { return $item instanceof JwtValidator; })) // @phpstan-ignore instanceof.alwaysTrue
throw new InvalidArgumentException('all items of $validators must implement JwtValidator');
@ -41,18 +38,9 @@ class JwtEncoder {
* @param array<string, mixed>|object|null $headers JWT header section override.
* @param bool $mergeHeaders Whether $headers should be merged with the actual provided headers in the token (true), or replace them entirely (false).
* @throws InvalidArgumentException If any of the argument types were not as expected.
* @throws UnexpectedValueException If any underlying object returns unexpected data.
* @return object Payload of the JWT.
*/
public function decode($token, $headers = null, $mergeHeaders = true) {
// verify type of $token
if(!is_string($token))
throw new InvalidArgumentException('$token must be a string');
// verify type of $mergeHeaders
if(!is_bool($mergeHeaders))
throw new InvalidArgumentException('$mergeHeaders must be a bool');
public function decode(string $token, $headers = null, bool $mergeHeaders = true) {
// verify type of $headers
if($headers !== null) {
if(is_object($headers))
@ -93,23 +81,17 @@ class JwtEncoder {
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');
// attempt to select the specified key
$alg = $headers->alg;
$kid = property_exists($headers, 'kid') && is_string($headers->kid) ? $headers->kid : null;
// attempt to select the specified key
$jwk = $this->keys->getKey($kid, $alg);
if(!($jwk instanceof Jwk))
throw new UnexpectedValueExceptionWithPayload($payload, 'JwkSet::getKey() did not return an instance of Jwk');
// decode signature and reconstruct the value input
$sig = UriBase64::decode($sigEnc);
$enc = sprintf('%s.%s', $headersEnc, $payloadEnc);
// verify the signature section of $token
$verify = $jwk->verify($enc, $sig, $alg);
if(!is_bool($verify))
throw new UnexpectedValueExceptionWithPayload($payload, 'Jwk::verify() did not return a boolean');
if(!$verify)
if(!$jwk->verify($enc, $sig, $alg))
throw new InvalidArgumentExceptionWithPayload($payload, '$token could not be verified');
// Run validators over the headers and payload sections
@ -126,7 +108,6 @@ class JwtEncoder {
* @param array<string, mixed>|object $payload JWT payload section.
* @param array<string, mixed>|object|null $headers JWT header section.
* @throws InvalidArgumentException If any of the argument types were not as expected.
* @throws UnexpectedValueException If any underlying object returns unexpected data.
* @return string
*/
public function encode($payload, $headers = null) {
@ -168,14 +149,10 @@ class JwtEncoder {
// attempt to select the requested key
$jwk = $this->keys->getKey($kid, $alg);
if(!($jwk instanceof Jwk))
throw new UnexpectedValueException('JwkSet::getKey() did not return an instance of Jwk');
// check if we need to set the alg claim
if($alg === null) {
$alg = $jwk->getAlgorithm();
if($alg !== null && !is_string($alg)) // @phpstan-ignore-line
throw new UnexpectedValueException('Jwk::getAlgorithm() did not return a string or null');
if($alg === null)
throw new RuntimeException('Selected key does not provide a default algorithm, $headers->alg must be specified');
@ -185,8 +162,6 @@ class JwtEncoder {
// check if we need to set the kid claim
if($kid === null) {
$kid = $jwk->getId();
if($kid !== null && !is_string($kid)) // @phpstan-ignore-line
throw new UnexpectedValueException('Jwk::getId() did not return a string or null');
if($kid !== null)
$headers->kid = $kid; // @phpstan-ignore-line
}
@ -204,8 +179,6 @@ class JwtEncoder {
// sign the token and ensure we got an expected return value
$signature = $jwk->sign($encoded, $alg);
if(!is_string($signature))
throw new UnexpectedValueException('Jwk::sign() did not return a string');
if($signature !== '')
$signature = UriBase64::encode($signature);

View file

@ -1,7 +1,7 @@
<?php
namespace Railgun\Jwt;
use Exception;
use Throwable;
/**
* Defines a validator for JWT decoding.
@ -13,8 +13,8 @@ interface JwtValidator {
*
* @param object $headers Headers section from the provided JWT.
* @param object $payload Payload section from the provided JWT.
* @throws Exception If claims could not be validated.
* @throws Throwable If claims could not be validated.
* @return void
*/
public function validate($headers, $payload);
public function validate(object $headers, object $payload): void;
}

View file

@ -1,8 +1,6 @@
<?php
namespace Railgun\Jwt;
use Exception;
use InvalidArgumentException;
use RuntimeException;
use Throwable;
@ -17,18 +15,19 @@ class RuntimeExceptionWithPayload extends RuntimeException implements WithPayloa
* @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 ?Exception $previous The previous exception used for the exception chaining.
* Yes, this should be Throwable, but that doesn't exist in PHP 5.6.
* @param ?Throwable $previous The previous exception used for the exception chaining.
*/
public function __construct($payload, $message = '', $code = 0, $previous = null) {
if(!is_object($payload))
throw new InvalidArgumentException('$payload must be an object');
public function __construct(
object $payload,
string $message = '',
int $code = 0,
?Throwable $previous = null
) {
$this->payload = $payload;
parent::__construct($message, $code, $previous);
}
public function getPayload() {
public function getPayload(): object {
return $this->payload;
}
@ -36,16 +35,10 @@ class RuntimeExceptionWithPayload extends RuntimeException implements WithPayloa
* Converts a normal RuntimeException to one with a payload.
*
* @param object $payload Data that was provided for the payload section.
* @param Exception $previous Exception to convert.
* @throws InvalidArgumentException If any of the argument types were not as expected.
* @param Throwable $previous Exception to convert.
* @return RuntimeExceptionWithPayload
*/
public static function convert($payload, $previous) {
if(!is_object($payload))
throw new InvalidArgumentException('$payload must be an object');
if(interface_exists(Throwable::class) ? !($previous instanceof Throwable) : !($previous instanceof Exception))
throw new InvalidArgumentExceptionWithPayload($payload, '$previous must be a subclass of Throwable or Exception');
public static function convert(object $payload, Throwable $previous) {
return new RuntimeExceptionWithPayload($payload, $previous->getMessage(), (int)$previous->getCode(), $previous);
}
}

View file

@ -29,14 +29,12 @@ class SecLibJwk implements Jwk {
*/
public function __construct(
#[\SensitiveParameter]
$key,
AsymmetricKey $key,
$algo = null,
$id = null
?string $id = null
) {
if(!($key instanceof AsymmetricKey) || (!($key instanceof PublicKey) && !($key instanceof PrivateKey)))
if((!($key instanceof PublicKey) && !($key instanceof PrivateKey)))
throw new InvalidArgumentException('$key is not a supported type combination');
if($id !== null && !is_string($id))
throw new InvalidArgumentException('$id must be a string or null');
if($algo !== null) {
if(is_string($algo))
$algo = SecLibJwkAlgorithm::create($algo);
@ -49,11 +47,11 @@ class SecLibJwk implements Jwk {
$this->id = $id;
}
public function getId() {
public function getId(): ?string {
return $this->id;
}
public function getAlgorithm() {
public function getAlgorithm(): ?string {
return $this->algoInfo instanceof SecLibJwkAlgorithm
? $this->algoInfo->getAlgorithm()
: null;
@ -64,7 +62,7 @@ class SecLibJwk implements Jwk {
* @throws InvalidArgumentException If any of the argument types were not as expected.
* @return SecLibJwkAlgorithm
*/
private function resolveAlgo($algo) {
private function resolveAlgo($algo): SecLibJwkAlgorithm {
if($algo === null) {
if($this->algoInfo === null)
throw new InvalidArgumentException('this key does not specify a default algorithm, you must specify $algo');
@ -79,15 +77,7 @@ class SecLibJwk implements Jwk {
throw new InvalidArgumentException('$algo could not be resolved');
}
/**
* {@inheritdoc}
* @param SecLibJwkAlgorithm|string|null $algo
* @throws UnexpectedValueException
*/
public function sign($data, $algo = null) {
if(!is_string($data))
throw new InvalidArgumentException('$data must be a string');
public function sign(string $data, ?string $algo = null): string {
$key = $this->resolveAlgo($algo)->transform($this->key);
if(!($key instanceof PrivateKey))
throw new RuntimeException('this instance does not have a private key, it can only be used to verify');
@ -99,16 +89,7 @@ class SecLibJwk implements Jwk {
throw new UnexpectedValueException('unexpected return value for signature');
}
/**
* {@inheritdoc}
* @param SecLibJwkAlgorithm|string|null $algo
*/
public function verify($data, $signature, $algo = null) {
if(!is_string($data))
throw new InvalidArgumentException('$data must be a string');
if(!is_string($signature))
throw new InvalidArgumentException('$signature must be a string');
public function verify(string $data, string $signature, ?string $algo = null): bool {
$key = $this->resolveAlgo($algo)->transform($this->key);
if($key instanceof PrivateKey)
$key = $key->getPublicKey();

View file

@ -2,7 +2,6 @@
namespace Railgun\Jwt\SecLib;
use InvalidArgumentException;
use RuntimeException;
use UnexpectedValueException;
use phpseclib3\Crypt\{EC,RSA};
use phpseclib3\Crypt\Common\AsymmetricKey;
@ -24,9 +23,7 @@ class SecLibJwkAlgorithm {
* @param string $algo
* @param callable(AsymmetricKey): AsymmetricKey $transformFunc
*/
public function __construct($algo, $transformFunc) {
if(!is_string($algo))
throw new InvalidArgumentException('$algo must be a string');
public function __construct(string $algo, $transformFunc) {
if(!is_callable($transformFunc))
throw new InvalidArgumentException('$transformFunc must be callable');
@ -37,7 +34,7 @@ class SecLibJwkAlgorithm {
/**
* @return string
*/
public function getAlgorithm() {
public function getAlgorithm(): string {
return $this->algo;
}
@ -47,10 +44,7 @@ class SecLibJwkAlgorithm {
* @throws UnexpectedValueException
* @return AsymmetricKey
*/
public function transform($key) {
if(!($key instanceof AsymmetricKey))
throw new InvalidArgumentException('$key must be an instance of AsymmetricKey');
public function transform(AsymmetricKey $key): AsymmetricKey {
$key = ($this->transformFunc)($key);
if(!($key instanceof AsymmetricKey))
throw new UnexpectedValueException('result of the transformer was not an AsymmetricKey');
@ -63,9 +57,7 @@ class SecLibJwkAlgorithm {
* @throws InvalidArgumentException If any of the argument types were not as expected.
* @return SecLibJwkAlgorithm
*/
public static function create($algo) {
if(!is_string($algo))
throw new InvalidArgumentException('$algo must be a string');
public static function create(string $algo): SecLibJwkAlgorithm {
if(!array_key_exists($algo, self::$algos))
throw new InvalidArgumentException(sprintf('$algo is not a supported algorithm: "%s"', $algo));
@ -77,7 +69,7 @@ class SecLibJwkAlgorithm {
* @throws InvalidArgumentException If any of the argument types were not as expected.
* @return RSA
*/
public static function ensureRsaKey($key) {
public static function ensureRsaKey(AsymmetricKey $key): RSA {
if($key instanceof RSA)
return $key;
@ -89,7 +81,7 @@ class SecLibJwkAlgorithm {
* @throws UnexpectedValueException
* @return RSA
*/
public static function applyPkcs1Padding($key) {
public static function applyPkcs1Padding(RSA $key): RSA {
$key = $key->withPadding(RSA::SIGNATURE_PKCS1);
if(!($key instanceof RSA))
throw new UnexpectedValueException('return type changed unexpectedly');
@ -102,7 +94,7 @@ class SecLibJwkAlgorithm {
* @throws UnexpectedValueException
* @return RSA
*/
public static function applyPssPadding($key) {
public static function applyPssPadding(RSA $key): RSA {
$key = $key->withPadding(RSA::SIGNATURE_PSS);
if(!($key instanceof RSA))
throw new UnexpectedValueException('return type changed unexpectedly');
@ -115,7 +107,7 @@ class SecLibJwkAlgorithm {
* @throws InvalidArgumentException If any of the argument types were not as expected.
* @return EC
*/
public static function ensureEcKey($key) {
public static function ensureEcKey(AsymmetricKey $key): EC {
if($key instanceof EC)
return $key;
@ -127,7 +119,7 @@ class SecLibJwkAlgorithm {
* @throws UnexpectedValueException
* @return EC
*/
public static function applyIeeeSignatureFormat($key) {
public static function applyIeeeSignatureFormat(EC $key): EC {
$key = $key->withSignatureFormat('IEEE');
if(!($key instanceof EC))
throw new UnexpectedValueException('return type changed unexpectedly');
@ -138,7 +130,7 @@ class SecLibJwkAlgorithm {
/**
* @return string[]
*/
public static function algorithms() {
public static function algorithms(): array {
return array_keys(self::$algos);
}
@ -147,7 +139,7 @@ class SecLibJwkAlgorithm {
* @throws UnexpectedValueException
* @return RSA
*/
public static function transformRs256($key) {
public static function transformRs256(AsymmetricKey $key): RSA {
$key = self::applyPkcs1Padding(self::ensureRsaKey($key))->withHash('sha256');
if(!($key instanceof RSA))
throw new UnexpectedValueException('return type changed unexpectedly');
@ -160,7 +152,7 @@ class SecLibJwkAlgorithm {
* @throws UnexpectedValueException
* @return RSA
*/
public static function transformRs384($key) {
public static function transformRs384(AsymmetricKey $key): RSA {
$key = self::applyPkcs1Padding(self::ensureRsaKey($key))->withHash('sha384');
if(!($key instanceof RSA))
throw new UnexpectedValueException('return type changed unexpectedly');
@ -173,7 +165,7 @@ class SecLibJwkAlgorithm {
* @throws UnexpectedValueException
* @return RSA
*/
public static function transformRs512($key) {
public static function transformRs512(AsymmetricKey $key): RSA {
$key = self::applyPkcs1Padding(self::ensureRsaKey($key))->withHash('sha512');
if(!($key instanceof RSA))
throw new UnexpectedValueException('return type changed unexpectedly');
@ -186,7 +178,7 @@ class SecLibJwkAlgorithm {
* @throws UnexpectedValueException
* @return RSA
*/
public static function transformPs256($key) {
public static function transformPs256(AsymmetricKey $key): RSA {
$key = self::applyPssPadding(self::ensureRsaKey($key))->withHash('sha256');
if(!($key instanceof RSA))
throw new UnexpectedValueException('return type changed unexpectedly');
@ -199,7 +191,7 @@ class SecLibJwkAlgorithm {
* @throws UnexpectedValueException
* @return RSA
*/
public static function transformPs384($key) {
public static function transformPs384(AsymmetricKey $key): RSA {
$key = self::applyPssPadding(self::ensureRsaKey($key))->withHash('sha384');
if(!($key instanceof RSA))
throw new UnexpectedValueException('return type changed unexpectedly');
@ -212,7 +204,7 @@ class SecLibJwkAlgorithm {
* @throws UnexpectedValueException
* @return RSA
*/
public static function transformPs512($key) {
public static function transformPs512(AsymmetricKey $key): RSA {
$key = self::applyPssPadding(self::ensureRsaKey($key))->withHash('sha512');
if(!($key instanceof RSA))
throw new UnexpectedValueException('return type changed unexpectedly');
@ -226,7 +218,7 @@ class SecLibJwkAlgorithm {
* @throws UnexpectedValueException
* @return EC
*/
public static function transformEs256($key) {
public static function transformEs256(AsymmetricKey $key): EC {
$key = self::ensureEcKey($key);
if($key->getCurve() !== 'secp256r1')
throw new InvalidArgumentException('curve must be secp256r1');
@ -244,7 +236,7 @@ class SecLibJwkAlgorithm {
* @throws UnexpectedValueException
* @return EC
*/
public static function transformEs256K($key) {
public static function transformEs256K(AsymmetricKey $key): EC {
$key = self::ensureEcKey($key);
if($key->getCurve() !== 'secp256k1')
throw new InvalidArgumentException('curve must be secp256k1');
@ -262,7 +254,7 @@ class SecLibJwkAlgorithm {
* @throws UnexpectedValueException
* @return EC
*/
public static function transformEs384($key) {
public static function transformEs384(AsymmetricKey $key): EC {
$key = self::ensureEcKey($key);
if($key->getCurve() !== 'secp384r1')
throw new InvalidArgumentException('curve must be secp384r1');
@ -280,7 +272,7 @@ class SecLibJwkAlgorithm {
* @throws UnexpectedValueException
* @return EC
*/
public static function transformEs512($key) {
public static function transformEs512(AsymmetricKey $key): EC {
$key = self::ensureEcKey($key);
if($key->getCurve() !== 'secp521r1')
throw new InvalidArgumentException('curve must be secp521r1');
@ -298,15 +290,11 @@ class SecLibJwkAlgorithm {
* @throws UnexpectedValueException
* @return EC
*/
public static function transformEdDsa($key) {
public static function transformEdDsa(AsymmetricKey $key): EC {
$key = self::ensureEcKey($key);
if($key->getCurve() !== 'Ed25519' && $key->getCurve() !== 'Ed448')
throw new InvalidArgumentException('curve must be Ed25519 or Ed448');
$key = $key;
if(!($key instanceof EC))
throw new UnexpectedValueException('return type changed unexpectedly');
return $key;
}
@ -316,9 +304,7 @@ class SecLibJwkAlgorithm {
* @throws InvalidArgumentException If any of the argument types were not as expected.
* @return void
*/
public static function defineAlgorithm($algo, $transformFunc) {
if(!is_string($algo))
throw new InvalidArgumentException('$algo must be a string');
public static function defineAlgorithm(string $algo, $transformFunc): void {
if(!is_callable($transformFunc))
throw new InvalidArgumentException('$transformFunc must be a callable');
if(array_key_exists($algo, self::$algos))

View file

@ -1,8 +1,7 @@
<?php
namespace Railgun\Jwt;
use Exception;
use InvalidArgumentException;
use Throwable;
use UnexpectedValueException;
/**
@ -16,18 +15,19 @@ class UnexpectedValueExceptionWithPayload extends UnexpectedValueException imple
* @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 ?Exception $previous The previous exception used for the exception chaining.
* Yes, this should be Throwable, but that doesn't exist in PHP 5.6.
* @param ?Throwable $previous The previous exception used for the exception chaining.
*/
public function __construct($payload, $message = '', $code = 0, $previous = null) {
if(!is_object($payload))
throw new InvalidArgumentException('$payload must be an object');
public function __construct(
object $payload,
string $message = '',
int $code = 0,
?Throwable $previous = null
) {
$this->payload = $payload;
parent::__construct($message, $code, $previous);
}
public function getPayload() {
public function getPayload(): object {
return $this->payload;
}
@ -36,15 +36,9 @@ class UnexpectedValueExceptionWithPayload extends UnexpectedValueException imple
*
* @param object $payload Data that was provided for the payload section.
* @param UnexpectedValueException $previous Exception to convert.
* @throws InvalidArgumentException If any of the argument types were not as expected.
* @return UnexpectedValueExceptionWithPayload
*/
public static function convert($payload, $previous) {
if(!is_object($payload))
throw new InvalidArgumentException('$payload must be an object');
if(!($previous instanceof UnexpectedValueException))
throw new InvalidArgumentExceptionWithPayload($payload, '$previous must be UnexpectedValueException');
public static function convert(object $payload, UnexpectedValueException $previous): self {
return new UnexpectedValueExceptionWithPayload($payload, $previous->getMessage(), $previous->getCode(), $previous);
}
}

View file

@ -1,8 +1,6 @@
<?php
namespace Railgun\Jwt\Utility;
use Exception;
use InvalidArgumentException;
use Throwable;
use UnexpectedValueException;
use Railgun\Jwt\{InvalidArgumentExceptionWithPayload,UnexpectedValueExceptionWithPayload};
@ -22,7 +20,7 @@ final class ClaimsUtils {
* @throws UnexpectedValueException If $value could not be converted.
* @return int
*/
public static function filterIntOrFloatToInt($value) {
public static function filterIntOrFloatToInt($value): int {
if(is_float($value))
$value = (int)floor($value);
elseif(!is_int($value))
@ -37,15 +35,11 @@ final class ClaimsUtils {
* @param object $payload Payload object to consider.
* @param string[] $claims Names of the claims to check.
* @param callable(mixed, string): mixed $filter Filter, first argument is the current value, second argument is the name of the claim, should return a cleaned version of the value or throw an exception if unacceptable.
* @throws InvalidArgumentException If any of the argument types were not as expected.
* @throws InvalidArgumentExceptionWithPayload If any of the argument types were not as expected.
* @throws UnexpectedValueExceptionWithPayload If validation failed or any of the values do not match.
* @return mixed
*/
public static function ensureIdenticalAndRetrieve($payload, $claims, $filter) {
if(!is_object($payload))
throw new InvalidArgumentException('$payload must be an object');
if(!is_array($claims))
throw new InvalidArgumentExceptionWithPayload($payload, '$claims must be an array of strings');
public static function ensureIdenticalAndRetrieve(object $payload, array $claims, $filter) {
if(!is_callable($filter))
throw new InvalidArgumentExceptionWithPayload($payload, '$filter must be a callable');
@ -60,7 +54,7 @@ final class ClaimsUtils {
// filter the value
try {
$value = $filter($payload->{$claim}, $claim);
} catch(Exception $ex) {
} catch(Throwable $ex) {
throw ExceptionUtils::ensureWithPayload($payload, $ex);
}

View file

@ -21,10 +21,7 @@ final class DateTimeUtils {
* @throws UnexpectedValueException If date/time conversion fails.
* @return DateTimeImmutable
*/
public static function ensureImmutable($dt, $dtArg = 'dt') {
if(!is_string($dtArg))
throw new InvalidArgumentException('$dtArg must be a string');
public static function ensureImmutable($dt, string $dtArg = 'dt'): DateTimeImmutable {
if($dt instanceof DateTimeImmutable)
return $dt;
@ -38,8 +35,11 @@ final class DateTimeUtils {
if($dt instanceof DateTimeInterface) {
// why did this not exist until PHP 8.0...
if(method_exists(DateTimeImmutable::class, 'createFromInterface')) // @phpstan-ignore function.alreadyNarrowedType
return DateTimeImmutable::createFromInterface($dt); // @phpstan-ignore-line
if(method_exists(DateTimeImmutable::class, 'createFromInterface')) { // @phpstan-ignore function.alreadyNarrowedType
$dti = DateTimeImmutable::createFromInterface($dt);
if($dti instanceof DateTimeImmutable) // @phpstan-ignore instanceof.alwaysTrue
return $dti;
}
$dt = DateTimeImmutable::createFromFormat('Uv', $dt->format('Uv'));
if($dt === false)

View file

@ -1,7 +1,6 @@
<?php
namespace Railgun\Jwt\Utility;
use Exception;
use InvalidArgumentException;
use Throwable;
use UnexpectedValueException;
@ -22,16 +21,10 @@ final class ExceptionUtils {
* Ensures an exception includes payload data.
*
* @param object $payload JWT payload section.
* @param Exception $exception Exception to potentially convert.
* @throws InvalidArgumentException If any of the argument types were not as expected.
* @return Exception&WithPayload
* @param Throwable $exception Exception to potentially convert.
* @return Throwable&WithPayload
*/
public static function ensureWithPayload($payload, $exception) {
if(!is_object($payload))
throw new InvalidArgumentException('$payload must be an object');
if(interface_exists(Throwable::class) ? !($exception instanceof Throwable) : !($exception instanceof Exception))
throw new InvalidArgumentExceptionWithPayload($payload, '$exception must be a subclass of Throwable or Exception');
public static function ensureWithPayload(object $payload, Throwable $exception): Throwable {
// If we already have a payload, just return immediately.
if($exception instanceof WithPayload)
return $exception;

View file

@ -16,7 +16,7 @@ final class Json {
* @throws RuntimeException If there was a JSON error.
* @return void
*/
private static function throwIfError() {
private static function throwIfError(): void {
$error = json_last_error();
if($error !== JSON_ERROR_NONE)
throw new RuntimeException(json_last_error_msg(), $error);
@ -28,9 +28,7 @@ final class Json {
* @param int $flags
* @return int
*/
private static function applyFlags($flags) {
if(!is_int($flags))
$flags = 0;
private static function applyFlags(int $flags = 0): int {
if(defined('JSON_INVALID_UTF8_SUBSTITUTE'))
$flags |= JSON_INVALID_UTF8_SUBSTITUTE;
if(defined('JSON_THROW_ON_ERROR'))
@ -42,16 +40,12 @@ final class Json {
* Decodes a JSON string.
*
* @param string $json The json string to be decoded.
* @throws InvalidArgumentException If any of the arguments were not of an expected type.
* @throws RuntimeException If json value failed to decode.
* @return mixed Returns the value encoded in json as an appropriate PHP type.
*/
public static function decode($json) {
if(!is_string($json))
throw new InvalidArgumentException('$json must be a string');
public static function decode(string $json) {
try {
$value = json_decode($json, false, 512, self::applyFlags(0));
$value = json_decode($json, false, 512, self::applyFlags());
} catch(\Exception $ex) {
throw new RuntimeException($ex->getMessage(), (int)$ex->getCode(), $ex);
}
@ -70,7 +64,7 @@ final class Json {
* @throws RuntimeException If json value failed to encode.
* @return string Returns a JSON encoded string.
*/
public static function encode($value) {
public static function encode($value): string {
if(is_resource($value))
throw new InvalidArgumentException('$value may not be resource');

View file

@ -1,7 +1,6 @@
<?php
namespace Railgun\Jwt\Utility;
use InvalidArgumentException;
use UnexpectedValueException;
/**
@ -14,14 +13,10 @@ final class UriBase64 {
* Decodes a URI base64 encoded string.
*
* @param string $string The encoded data.
* @throws InvalidArgumentException If any of the argument types were not as expected.
* @throws UnexpectedValueException If the data could not be decoded.
* @return string Returns the decoded data. The returned data may be binary.
*/
public static function decode($string) {
if(!is_string($string))
throw new InvalidArgumentException('$string must be a string');
public static function decode(string $string): string {
$string = base64_decode(str_pad(strtr($string, '-_', '+/'), strlen($string) % 4, '=', STR_PAD_RIGHT), true);
if($string === false)
throw new UnexpectedValueException('$string could not be decoded');
@ -33,13 +28,9 @@ final class UriBase64 {
* Encodes the given string with URI base64.
*
* @param string $string The data to encode.
* @throws InvalidArgumentException If any of the argument types were not as expected.
* @return string The encoded data, as a string.
*/
public static function encode($string) {
if(!is_string($string))
throw new InvalidArgumentException('$string must be a string');
public static function encode(string $string): string {
return rtrim(strtr(base64_encode($string), '+/', '-_'), '=');
}
}

View file

@ -1,7 +1,6 @@
<?php
namespace Railgun\Jwt\Validators;
use DateTimeImmutable;
use Railgun\Jwt\Utility\ClaimsUtils;
/**
@ -32,7 +31,11 @@ class NotValidBeforeValidator extends TimeBasedValidator {
/**
* {@inheritdoc}
*/
public function __construct($leeway = 0, $time = null, $claims = self::CLAIMS) {
public function __construct(
int $leeway = 0,
$time = null,
array $claims = self::CLAIMS
) {
parent::__construct($leeway, $time, $claims);
}
@ -40,7 +43,7 @@ class NotValidBeforeValidator extends TimeBasedValidator {
* {@inheritdoc}
* @throws NotValidYetException If a token is not yet valid.
*/
public function validate($headers, $payload) {
public function validate(object $headers, object $payload): void {
// retrieve the not valid before value
$value = ClaimsUtils::ensureIdenticalAndRetrieve($payload, $this->getClaims(), [ClaimsUtils::class, 'filterIntOrFloatToInt']);

View file

@ -36,7 +36,7 @@ class NotValidYetException extends RuntimeExceptionWithPayload {
*
* @return DateTimeImmutable
*/
public function getValidAfter() {
public function getValidAfter(): DateTimeImmutable {
return $this->validAfter;
}
}

View file

@ -23,12 +23,10 @@ abstract class TimeBasedValidator implements JwtValidator {
* @param (callable(): int)|(Closure(): int)|null $time Callable that return the current timestamp, will use PHP's time() if null.
* @param non-empty-array<string> $claims Claim names to consider as possible expiration claims, all will be ensured to be set to the same value if they are present.
*/
public function __construct($leeway, $time, $claims) {
if(!is_int($leeway))
throw new InvalidArgumentException('$leeway must be an integer');
public function __construct(int $leeway, $time, array $claims) {
if($time !== null && !is_callable($time))
throw new InvalidArgumentException('$time must be null, a callable or an instance of Closure');
if(!is_array($claims) || !array_all($claims, function($value) { return is_string($value); })) // @phpstan-ignore function.alreadyNarrowedType
if(!array_all($claims, function($value) { return is_string($value); })) // @phpstan-ignore function.alreadyNarrowedType
throw new InvalidArgumentException('$claims must be a non-empty array of strings');
$this->leeway = $leeway;
@ -41,7 +39,7 @@ abstract class TimeBasedValidator implements JwtValidator {
*
* @return int
*/
protected function getLeeway() {
protected function getLeeway(): int {
return $this->leeway;
}
@ -50,7 +48,7 @@ abstract class TimeBasedValidator implements JwtValidator {
*
* @return int
*/
protected function getTime() {
protected function getTime(): int {
return ($this->time)();
}
@ -59,9 +57,9 @@ abstract class TimeBasedValidator implements JwtValidator {
*
* @return string[]
*/
protected function getClaims() {
protected function getClaims(): array {
return $this->claims;
}
abstract public function validate($headers, $payload);
abstract public function validate(object $headers, object $payload): void;
}

View file

@ -1,7 +1,6 @@
<?php
namespace Railgun\Jwt\Validators;
use DateTimeImmutable;
use Railgun\Jwt\Utility\ClaimsUtils;
/**
@ -25,7 +24,11 @@ class TokenExpirationValidator extends TimeBasedValidator {
/**
* {@inheritdoc}
*/
public function __construct($leeway = 0, $time = null, $claims = self::CLAIMS) {
public function __construct(
int $leeway = 0,
$time = null,
array $claims = self::CLAIMS
) {
parent::__construct($leeway, $time, $claims);
}
@ -33,7 +36,7 @@ class TokenExpirationValidator extends TimeBasedValidator {
* {@inheritdoc}
* @throws TokenExpiredException If a token has expired.
*/
public function validate($headers, $payload) {
public function validate(object $headers, object $payload): void {
// retrieve the expiration value
$value = ClaimsUtils::ensureIdenticalAndRetrieve($payload, $this->getClaims(), [ClaimsUtils::class, 'filterIntOrFloatToInt']);

View file

@ -19,9 +19,6 @@ class TokenExpiredException extends RuntimeExceptionWithPayload {
* @param DateTimeInterface|int $expiredAt Expiration time.
*/
public function __construct($payload, $expiredAt) {
if(!is_object($payload))
throw new InvalidArgumentException('$payload must be an object');
try {
$this->expiredAt = DateTimeUtils::ensureImmutable($expiredAt, 'expiredAt');
} catch(InvalidArgumentException $ex) {
@ -36,7 +33,7 @@ class TokenExpiredException extends RuntimeExceptionWithPayload {
*
* @return DateTimeImmutable
*/
public function getExpiredAt() {
public function getExpiredAt(): DateTimeImmutable {
return $this->expiredAt;
}
}

View file

@ -10,5 +10,5 @@ interface WithPayload {
*
* @return object
*/
public function getPayload();
public function getPayload(): object;
}