Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions src/Library/Checker/ClaimCheckerManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
/**
* This class manages claim checkers and performs claim checks.
*
* The set of checkers is fixed at construction time: a manager expresses a policy that no consumer is allowed to widen
* afterwards.
*
* @final The class will be final in 5.0.0: implement ClaimCheckerManagerInterface and decorate the service instead of
* extending it.
*
Expand All @@ -22,17 +25,19 @@ class ClaimCheckerManager implements ClaimCheckerManagerInterface
/**
* @var ClaimChecker[]
*/
private array $checkers = [];
private readonly array $checkers;

/**
* @param ClaimChecker[] $checkers
*/
public function __construct(iterable $checkers)
{
InheritanceChecker::warnIfExtended(static::class, self::class, ClaimCheckerManagerInterface::class);
$indexedCheckers = [];
foreach ($checkers as $checker) {
$this->add($checker);
$indexedCheckers[$checker->supportedClaim()] = $checker;
}
$this->checkers = $indexedCheckers;
}

/**
Expand Down Expand Up @@ -68,12 +73,6 @@ public function check(array $claims, array $mandatoryClaims = []): array
return $checkedClaims;
}

private function add(ClaimChecker $checker): void
{
$claim = $checker->supportedClaim();
$this->checkers[$claim] = $checker;
}

/**
* @param string[] $mandatoryClaims
*/
Expand Down
27 changes: 12 additions & 15 deletions src/Library/Checker/HeaderCheckerManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
* It allows to add header parameter checkers and token type supports.
* The factory is responsible to create a Header Checker Manager with the header parameter checkers found based
*
* The checkers and the token type supports are fixed at construction time: a manager expresses a policy that no
* consumer is allowed to widen afterwards.
*
* @final The class will be final in 5.0.0: implement HeaderCheckerManagerInterface and decorate the service instead
* of extending it.
*/
Expand All @@ -28,12 +31,12 @@ class HeaderCheckerManager implements HeaderCheckerManagerInterface
/**
* @var array<string, HeaderChecker>
*/
private array $checkers = [];
private readonly array $checkers;

/**
* @var TokenTypeSupport[]
*/
private array $tokenTypes = [];
private readonly array $tokenTypes;

/**
* @param HeaderChecker[] $checkers
Expand All @@ -42,12 +45,17 @@ class HeaderCheckerManager implements HeaderCheckerManagerInterface
public function __construct(iterable $checkers, iterable $tokenTypes)
{
InheritanceChecker::warnIfExtended(static::class, self::class, HeaderCheckerManagerInterface::class);
$indexedCheckers = [];
foreach ($checkers as $checker) {
$this->add($checker);
$indexedCheckers[$checker->supportedHeader()] = $checker;
}
$this->checkers = $indexedCheckers;

$supportedTokenTypes = [];
foreach ($tokenTypes as $tokenType) {
$this->addTokenTypeSupport($tokenType);
$supportedTokenTypes[] = $tokenType;
}
$this->tokenTypes = $supportedTokenTypes;
}

/**
Expand Down Expand Up @@ -84,17 +92,6 @@ public function check(JWT $jwt, int $index, array $mandatoryHeaderParameters = [
throw new InvalidArgumentException('Unsupported token type.');
}

private function addTokenTypeSupport(TokenTypeSupport $tokenType): void
{
$this->tokenTypes[] = $tokenType;
}

private function add(HeaderChecker $checker): void
{
$header = $checker->supportedHeader();
$this->checkers[$header] = $checker;
}

private function checkDuplicatedHeaderParameters(array $header1, array $header2): void
{
$inter = array_intersect_key($header1, $header2);
Expand Down
35 changes: 34 additions & 1 deletion src/Library/Core/AlgorithmManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Jose\Component\Core\Exception\UnsupportedAlgorithmException;
use function array_key_exists;
use function sprintf;
use function trigger_deprecation;

final class AlgorithmManager
{
Expand All @@ -21,7 +22,7 @@ final class AlgorithmManager
public function __construct(iterable $algorithms)
{
foreach ($algorithms as $algorithm) {
$this->add($algorithm);
$this->register($algorithm);
}
}

Expand Down Expand Up @@ -67,10 +68,42 @@ public function get(string $algorithm): Algorithm
return $this->algorithms[$algorithm];
}

/**
* Returns a new manager that supports the algorithms of the current one plus the given ones.
*
* This method is immutable: the current manager is left untouched, so that a manager shared as a service keeps
* expressing the policy it was built with. An algorithm whose name is already supported replaces the previous one
* in the returned manager.
*/
public function with(Algorithm ...$algorithms): self
{
$clone = clone $this;
foreach ($algorithms as $algorithm) {
$clone->register($algorithm);
}

return $clone;
}

/**
* Adds an algorithm to the manager.
*
* @deprecated since 4.3.0, will be removed in 5.0.0. Use {@see self::with()} instead.
*/
public function add(Algorithm $algorithm): void
{
trigger_deprecation(
'web-token/jwt-framework',
'4.3.0',
'The method "%s::add()" is deprecated and will be removed in 5.0.0. It widens the policy of a manager that is usually a shared service: use "%s::with()" instead, which returns a new manager and leaves the current one untouched.',
self::class,
self::class
);

$this->register($algorithm);
}

private function register(Algorithm $algorithm): void
{
$name = $algorithm->name();
$this->algorithms[$name] = $algorithm;
Expand Down
20 changes: 9 additions & 11 deletions src/Library/Encryption/Serializer/JWESerializerManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,27 @@
use Jose\Component\Encryption\JWE;
use function sprintf;

final class JWESerializerManager
/**
* The set of serializers is fixed at construction time: a manager shared as a service cannot be silently extended by
* one of its consumers.
*/
final readonly class JWESerializerManager
{
/**
* @var JWESerializer[]
*/
private array $serializers = [];
private array $serializers;

/**
* @param JWESerializer[] $serializers
*/
public function __construct(iterable $serializers)
{
$indexedSerializers = [];
foreach ($serializers as $serializer) {
$this->add($serializer);
$indexedSerializers[$serializer->name()] = $serializer;
}
$this->serializers = $indexedSerializers;
}

/**
Expand Down Expand Up @@ -76,12 +82,4 @@ public function unserialize(string $input, ?string &$name = null): JWE

throw new InvalidSerializationException('Unsupported input.', 0, $lastError);
}

/**
* Adds a serializer to the manager.
*/
private function add(JWESerializer $serializer): void
{
$this->serializers[$serializer->name()] = $serializer;
}
}
17 changes: 9 additions & 8 deletions src/Library/Signature/Serializer/JWSSerializerManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,27 @@
use Jose\Component\Signature\JWS;
use function sprintf;

final class JWSSerializerManager
/**
* The set of serializers is fixed at construction time: a manager shared as a service cannot be silently extended by
* one of its consumers.
*/
final readonly class JWSSerializerManager
{
/**
* @var JWSSerializer[]
*/
private array $serializers = [];
private array $serializers;

/**
* @param JWSSerializer[] $serializers
*/
public function __construct(iterable $serializers)
{
$indexedSerializers = [];
foreach ($serializers as $serializer) {
$this->add($serializer);
$indexedSerializers[$serializer->name()] = $serializer;
}
$this->serializers = $indexedSerializers;
}

/**
Expand Down Expand Up @@ -74,9 +80,4 @@ public function unserialize(string $input, ?string &$name = null): JWS

throw new InvalidSerializationException('Unsupported input.', 0, $lastError);
}

private function add(JWSSerializer $serializer): void
{
$this->serializers[$serializer->name()] = $serializer;
}
}
128 changes: 128 additions & 0 deletions tests/Component/Core/AlgorithmManagerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<?php

declare(strict_types=1);

namespace Jose\Tests\Component\Core;

use Jose\Component\Core\AlgorithmManager;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use const E_USER_DEPRECATED;

/**
* @internal
*/
final class AlgorithmManagerTest extends TestCase
{
#[Test]
public function theManagerIsBuiltFromTheAlgorithmsPassedToTheConstructor(): void
{
$algorithm = new FooAlgorithm();

$sut = new AlgorithmManager([$algorithm]);

static::assertTrue($sut->has('foo'));
static::assertSame(['foo'], $sut->list());
static::assertSame([
'foo' => $algorithm,
], $sut->all());
static::assertSame($algorithm, $sut->get('foo'));
}

#[Test]
public function theConstructorDoesNotTriggerTheDeprecationOfTheAddMethod(): void
{
$deprecations = $this->collectDeprecations(static function (): void {
new AlgorithmManager([new FooAlgorithm()]);
});

static::assertSame([], $deprecations);
}

#[Test]
public function withReturnsANewManagerAndLeavesTheCurrentOneUntouched(): void
{
$sut = new AlgorithmManager([new FooAlgorithm()]);

$new = $sut->with(new BarAlgorithm());

static::assertNotSame($sut, $new);
static::assertSame(['foo'], $sut->list());
static::assertSame(['foo', 'bar'], $new->list());
}

#[Test]
public function withAcceptsSeveralAlgorithmsAtOnce(): void
{
$sut = new AlgorithmManager([]);

$new = $sut->with(new FooAlgorithm(), new BarAlgorithm());

static::assertSame(['foo', 'bar'], $new->list());
}

#[Test]
public function withReplacesAnAlgorithmThatHasTheSameName(): void
{
$replacement = new FooAlgorithm();
$sut = new AlgorithmManager([new FooAlgorithm()]);

$new = $sut->with($replacement);

static::assertSame(['foo'], $new->list());
static::assertSame($replacement, $new->get('foo'));
}

#[Test]
public function withDoesNotTriggerAnyDeprecation(): void
{
$sut = new AlgorithmManager([new FooAlgorithm()]);

$deprecations = $this->collectDeprecations(static function () use ($sut): void {
$sut->with(new BarAlgorithm());
});

static::assertSame([], $deprecations);
}

#[Test]
public function theAddMethodIsDeprecatedButStillMutatesTheManager(): void
{
$sut = new AlgorithmManager([new FooAlgorithm()]);
$algorithm = new BarAlgorithm();

$deprecations = $this->collectDeprecations(static function () use ($sut, $algorithm): void {
$sut->add($algorithm);
});

static::assertCount(1, $deprecations);
static::assertStringContainsString(
'The method "Jose\Component\Core\AlgorithmManager::add()" is deprecated and will be removed in 5.0.0.',
$deprecations[0]
);
static::assertSame($algorithm, $sut->get('bar'));
}

/**
* @param callable(): void $callback
*
* @return list<string>
*/
private function collectDeprecations(callable $callback): array
{
$deprecations = [];
set_error_handler(static function (int $errno, string $errstr) use (&$deprecations): bool {
$deprecations[] = $errstr;

return true;
}, E_USER_DEPRECATED);

try {
$callback();
} finally {
restore_error_handler();
}

return $deprecations;
}
}
Loading
Loading