More tests and more fixes in accordance with them.

This commit is contained in:
flash 2025-05-14 23:43:00 +02:00
parent 3fbbdd5b9a
commit 46ec5b9d88
Signed by: flash
GPG key ID: 6D833BE0D210AC02
15 changed files with 250 additions and 57 deletions

View file

@ -14,9 +14,25 @@ DEV_DIRS := $(wildcard ${DEV_DIR}/*)
VENDOR := $(realpath vendor)
VENDOR_BIN := ${VENDOR}/bin
# We'll use the lowest common denominator to maintain the root packages
PHP_702 := $(shell which php7.2)
export PHP_702
PHP_703 := $(shell which php7.3)
export PHP_703
PHP_704 := $(shell which php7.4)
export PHP_704
PHP_800 := $(shell which php8.0)
export PHP_800
PHP_801 := $(shell which php8.1)
export PHP_801
PHP_802 := $(shell which php8.2)
export PHP_802
PHP_803 := $(shell which php8.3)
export PHP_803
PHP_804 := $(shell which php8.4)
export PHP_804
COMPOSER := $(shell which composer)
export COMPOSER
SUDO = $(shell which sudo)
@ -33,6 +49,13 @@ update:
tests:
${PHP_702} ${VENDOR_BIN}/phpunit
${PHP_703} ${VENDOR_BIN}/phpunit
${PHP_704} ${VENDOR_BIN}/phpunit
${PHP_800} ${VENDOR_BIN}/phpunit
${PHP_801} ${VENDOR_BIN}/phpunit
${PHP_802} ${VENDOR_BIN}/phpunit
${PHP_803} ${VENDOR_BIN}/phpunit
${PHP_804} ${VENDOR_BIN}/phpunit
fix-perms: # WSL skill issues
${SUDO} chown -R ${USER_NAME}:${GROUP_NAME} .

View file

@ -1 +1 @@
v0.3.2
v0.3.3

View file

@ -1,14 +1,8 @@
PHP_702 := $(shell which php7.2)
COMPOSER := $(shell which composer)
VENDOR_BIN := $(realpath vendor/bin)
PHPSTAN = "${VENDOR_BIN}/phpstan"
PHPUNIT = "${VENDOR_BIN}/phpunit"
ifeq ($(origin PHPSTAN_CONFIG),undefined)
$(error PHPSTAN_CONFIG is not defined)
endif
install:
${PHP_702} ${COMPOSER} install

View file

@ -1,14 +1,8 @@
PHP_703 := $(shell which php7.3)
COMPOSER := $(shell which composer)
VENDOR_BIN := $(realpath vendor/bin)
PHPSTAN = "${VENDOR_BIN}/phpstan"
PHPUNIT = "${VENDOR_BIN}/phpunit"
ifeq ($(origin PHPSTAN_CONFIG),undefined)
$(error PHPSTAN_CONFIG is not defined)
endif
install:
${PHP_703} ${COMPOSER} install

View file

@ -1,14 +1,8 @@
PHP_704 := $(shell which php7.4)
COMPOSER := $(shell which composer)
VENDOR_BIN := $(realpath vendor/bin)
PHPSTAN = "${VENDOR_BIN}/phpstan"
PHPUNIT = "${VENDOR_BIN}/phpunit"
ifeq ($(origin PHPSTAN_CONFIG),undefined)
$(error PHPSTAN_CONFIG is not defined)
endif
install:
${PHP_704} ${COMPOSER} install

View file

@ -1,14 +1,8 @@
PHP_800 := $(shell which php8.0)
COMPOSER := $(shell which composer)
VENDOR_BIN := $(realpath vendor/bin)
PHPSTAN = "${VENDOR_BIN}/phpstan"
PHPUNIT = "${VENDOR_BIN}/phpunit"
ifeq ($(origin PHPSTAN_CONFIG),undefined)
$(error PHPSTAN_CONFIG is not defined)
endif
install:
${PHP_800} ${COMPOSER} install

View file

@ -1,14 +1,8 @@
PHP_801 := $(shell which php8.1)
COMPOSER := $(shell which composer)
VENDOR_BIN := $(realpath vendor/bin)
PHPSTAN = "${VENDOR_BIN}/phpstan"
PHPUNIT = "${VENDOR_BIN}/phpunit"
ifeq ($(origin PHPSTAN_CONFIG),undefined)
$(error PHPSTAN_CONFIG is not defined)
endif
install:
${PHP_801} ${COMPOSER} install

View file

@ -1,14 +1,8 @@
PHP_802 := $(shell which php8.2)
COMPOSER := $(shell which composer)
VENDOR_BIN := $(realpath vendor/bin)
PHPSTAN = "${VENDOR_BIN}/phpstan"
PHPUNIT = "${VENDOR_BIN}/phpunit"
ifeq ($(origin PHPSTAN_CONFIG),undefined)
$(error PHPSTAN_CONFIG is not defined)
endif
install:
${PHP_802} ${COMPOSER} install

View file

@ -1,14 +1,8 @@
PHP_803 := $(shell which php8.3)
COMPOSER := $(shell which composer)
VENDOR_BIN := $(realpath vendor/bin)
PHPSTAN = "${VENDOR_BIN}/phpstan"
PHPUNIT = "${VENDOR_BIN}/phpunit"
ifeq ($(origin PHPSTAN_CONFIG),undefined)
$(error PHPSTAN_CONFIG is not defined)
endif
install:
${PHP_803} ${COMPOSER} install

View file

@ -1,14 +1,8 @@
PHP_804 := $(shell which php8.4)
COMPOSER := $(shell which composer)
VENDOR_BIN := $(realpath vendor/bin)
PHPSTAN = "${VENDOR_BIN}/phpstan"
PHPUNIT = "${VENDOR_BIN}/phpunit"
ifeq ($(origin PHPSTAN_CONFIG),undefined)
$(error PHPSTAN_CONFIG is not defined)
endif
install:
${PHP_804} ${COMPOSER} install

View file

@ -32,6 +32,9 @@ class HmacJwk implements Jwk {
?string $algo = null,
?string $id = null
) {
if($algo !== null && !array_key_exists($algo, self::$algos))
throw new InvalidArgumentException('$algo is not a supported algorithm');
$this->key = $key;
$this->algo = $algo;
$this->id = $id;
@ -73,12 +76,16 @@ class HmacJwk implements Jwk {
#[\ReturnTypeWillChange]
public function jsonSerialize() {
return [
'kty' => 'oct',
'alg' => $this->algo,
'use' => 'sig',
'k' => UriBase64::encode($this->key),
];
$json = [];
$json['kty'] = 'oct';
if($this->algo !== null)
$json['alg'] = $this->algo;
if($this->id !== null)
$json['kid'] = $this->id;
$json['use'] = 'sig';
$json['k'] = UriBase64::encode($this->key);
return $json;
}
/**

117
tests/HmacJwkTest.php Normal file
View file

@ -0,0 +1,117 @@
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
use Railgun\Jwt\HmacJwk;
use Railgun\Jwt\Utility\Json;
final class HmacJwkTest extends TestCase {
/**
* @return array{string, ?string, ?string, string, string, array<string, string>}[]
*/
public function hmacJwkGeneralDataProvider(): array {
return [
[
'14pDJ4ScThxtDkubL7X7',
null,
null,
'{"kty":"oct","use":"sig","k":"MTRwREo0U2NUaHh0RGt1Ykw3WDc"}',
'FqS2nJj4gOf78JuKdtv8wtyx78QirkXuwcil3gvUXaDTEBSNlFHZKjnfiAe1',
[
'HS256' => 'a37a05bed788ae05cd756c376596aa5bd8bfe352494cddc7a95a857fbd1b6d7c',
'HS384' => '51e6e7aac281e0c491dcac3ed3aa46295c7b168a31c347e3a23961cbbf6b92268c82b510bec011f08ee8ef5e6630e96f',
'HS512' => 'fd1f1709b695c7e0adef34a5565a0b96d4d90441215b5d11ad14af66390d37c0d3bc9cf9e4c081c7ee213f27ceea337619117c96660f8794ad44394bfd73a5ac',
],
],
[
'chXEGnMXVPhIwZIzCnjbUOnuFHMIlSpblAdMBCA9',
null,
'soup',
'{"kty":"oct","kid":"soup","use":"sig","k":"Y2hYRUduTVhWUGhJd1pJekNuamJVT251RkhNSWxTcGJsQWRNQkNBOQ"}',
'nw7fS7arkmcKjYsXnrHpwt0cG2jBqvb2xIqBNgcbsKCaLWnP4vbDWTPF4p0sjxD38jHgDzsgi1fOQvdK',
[
'HS256' => '08ce793f6f542b0e0d54dfd6d13c28ee5ab664be1de1327a52f49881763fe6f6',
'HS384' => '8381a30356c797acf2e52e41a5391a96c4909606d7780bb964a1725dc680f22cc005973d3fb6de522ec64e0c0ab6cb65',
'HS512' => '989b78db27a04f723fcd8245a2122ec78d2b66262d472c5277cf255c30c17ab4f7c5f398a0b95abc27a9ab310da3ed3cccfa021d3284123da59f10f222fe5a7d',
],
],
[
'9MgUWsfYaYhngVMHZebpAsePHW2CKy',
'HS384',
null,
'{"kty":"oct","alg":"HS384","use":"sig","k":"OU1nVVdzZllhWWhuZ1ZNSFplYnBBc2VQSFcyQ0t5"}',
'pp8tNF9ZDZk0CGOzqNkeuaONYlO5tV10Tm64cjVjwC5',
[
'' => 'f660d719307a42b74e2e344454746b742234ac7580b40aa3abbd7e0ed82c1a61561c9540c9e8c61ce6966cf7abcffc04',
'HS256' => '3035cf9e5f683357e07fce493e847800ea5e3584973f00ec814a40deee2cbf88',
'HS512' => 'a3d3cdb61fd5003ebcc4b5b6ccd5d254d8e3e255b3b1749d7629a49ea78b88f8b4dac13547d21fcc68756a2d6fca756424d1e9de26cec7389573912af81eeb0c',
],
],
[
'tIyoGOvEpxRXD8bubowJgsm9kMtdyLpRSWibcYl1K9MZLvJ0XZ',
'HS256',
'beans-key',
'{"kty":"oct","alg":"HS256","kid":"beans-key","use":"sig","k":"dEl5b0dPdkVweFJYRDhidWJvd0pnc205a010ZHlMcFJTV2liY1lsMUs5TVpMdkowWFo"}',
'6ZlMT201j8bkTBhGdnhVauj',
[
'' => 'f33e4829d49e4045788590324f731fb65ddcfb961b92034b4466813336bd9ad8',
'HS384' => '309509fcb736370dec267cc758113a88365a0c01f13bd2af4e1419d7bc8865f924a5c8f05c811f33bcd25fe9a2c84e95',
'HS512' => 'bb1da7fec614b1ee03422d439475f0c25370ea453d770e2c198d22ed15ee6c880915a5120d3c7ad5fd998d15fefaa2827515d68ea8deec2c2c4587c4c817b29a',
]
],
];
}
/**
* @param array<string, string> $results
* @dataProvider hmacJwkGeneralDataProvider
*/
public function testHmacJwkGeneral(string $key, ?string $algo, ?string $id, string $json, string $subject, array $results): void {
$jwk = new HmacJwk($key, $algo, $id);
$this->assertSame($id, $jwk->getId());
$this->assertSame($algo, $jwk->getAlgorithm());
$this->assertNull($jwk->getPublicKey()); // HMAC is not asymmetric so this will always be null
$this->assertSame($json, Json::encode($jwk));
foreach($results as $algo => $expect) {
if(is_int($algo))
continue;
if($algo === '')
$algo = null;
$signature = $jwk->sign($subject, $algo);
$this->assertSame($expect, bin2hex($signature));
$this->assertTrue($jwk->verify($subject, $signature, $algo));
}
}
public function testEnsureConstructorThrowsWhenUnsupportedAlgo(): void {
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('$algo is not a supported algorithm');
new HmacJwk('a', 'WXP2600');
}
public function testEnsureSignAlgoNullWhenNoDefaultThrows(): void {
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('this key does not specify a default algorithm, you must specify $algo');
(new HmacJwk('b'))->sign('meow');
}
public function testEnsureVerifyAlgoNullWhenNoDefaultThrows(): void {
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('this key does not specify a default algorithm, you must specify $algo');
(new HmacJwk('c'))->verify('meow', 'mewow');
}
public function testEnsureSignUnsupportedAlgoThrowsEvenWhenSupportedDefault(): void {
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('$algo is not a supported algorithm');
(new HmacJwk('d', 'HS256'))->sign('hello', 'SPC700');
}
public function testEnsureVerifyUnsupportedAlgoThrowsEvenWhenSupportedDefault(): void {
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('$algo is not a supported algorithm');
(new HmacJwk('e', 'HS384'))->verify('hello', 'world', 'VR4300');
}
}

View file

@ -18,7 +18,7 @@ final class ClaimsUtilsTest extends TestCase {
}
/**
* @return array{object, }[]
* @return array{object, string[], callable(mixed, string): mixed, mixed}[]
*/
public function ensureIdenticalAndRetrieveDataProvider(): array {
return [

View file

@ -0,0 +1,48 @@
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
use Railgun\Jwt\UnexpectedValueExceptionWithPayload;
use Railgun\Jwt\Validators\{
NotValidBeforeValidator,
NotValidYetException
};
final class NotValidBeforeValidatorTest extends TestCase {
/**
* @return array{int, int, string[]|null, object, ?class-string<Throwable>}[]
*/
public function notValidBeforeDataProvider(): array {
return [
[0, 1000, null, (object)[], null],
[0, 1000, null, (object)['nbf' => 999], null],
[0, 1000, null, (object)['nbf' => 1001], NotValidYetException::class],
[30, 1000, null, (object)['nbf' => 1030], null],
[30, 1000, null, (object)['nbf' => 1031], NotValidYetException::class],
[0, 1000, null, (object)['nbf' => 1000, 'iat' => 1000], null],
[0, 900, null, (object)['nbf' => 1000, 'iat' => 1000], NotValidYetException::class],
[0, 900, null, (object)['nbf' => 1000, 'iat' => 1200], UnexpectedValueExceptionWithPayload::class],
[0, 900, ['nbf', 'iat', 'beans'], (object)['nbf' => 800, 'iat' => 800, 'beans' => 800], null],
[0, 900, ['beans'], (object)['nbf' => 9001, 'beans' => 800], null],
];
}
/**
* @dataProvider notValidBeforeDataProvider
*/
public function testNotValidBefore(int $leeway, int $time, ?array $claims, object $payload, ?string $throwable): void {
if($throwable !== null)
$this->expectException($throwable);
$time = (function(int $time) {
return function() use ($time) {
return $time;
};
})($time);
$validator = $claims === null
? new NotValidBeforeValidator($leeway, $time)
: new NotValidBeforeValidator($leeway, $time, $claims);
$this->assertNull($validator->validate((object)[], $payload));
}
}

View file

@ -0,0 +1,46 @@
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
use Railgun\Jwt\UnexpectedValueExceptionWithPayload;
use Railgun\Jwt\Validators\{
TokenExpirationValidator,
TokenExpiredException
};
final class TokenExpirationValidatorTest extends TestCase {
/**
* @return array{int, int, string[]|null, object, ?class-string<Throwable>}[]
*/
public function tokenExpirationProvider(): array {
return [
[0, 1000, null, (object)[], null],
[0, 1000, null, (object)['exp' => 999], TokenExpiredException::class],
[0, 1000, null, (object)['exp' => 1001], null],
[30, 1000, null, (object)['exp' => 970], null],
[30, 1000, null, (object)['exp' => 969], TokenExpiredException::class],
[0, 700, ['exp', 'beans'], (object)['exp' => 800, 'beans' => 800], null],
[0, 900, ['exp', 'beans'], (object)['exp' => 800, 'beans' => 900], UnexpectedValueExceptionWithPayload::class],
[0, 900, ['beans'], (object)['exp' => 334, 'beans' => 80004], null],
];
}
/**
* @dataProvider tokenExpirationProvider
*/
public function testTokenExpiration(int $leeway, int $time, ?array $claims, object $payload, ?string $throwable): void {
if($throwable !== null)
$this->expectException($throwable);
$time = (function(int $time) {
return function() use ($time) {
return $time;
};
})($time);
$validator = $claims === null
? new TokenExpirationValidator($leeway, $time)
: new TokenExpirationValidator($leeway, $time, $claims);
$this->assertNull($validator->validate((object)[], $payload));
}
}