Passed
Push — master ( 106006...c1ddb5 )
by Thomas Mauro
06:38 queued 11s
created

ResponseTokenVerifier   A

Complexity

Total Complexity 9

Size/Duplication

Total Lines 87
Duplicated Lines 0 %

Test Coverage

Coverage 83.78%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 38
dl 0
loc 87
ccs 31
cts 37
cp 0.8378
rs 10
c 1
b 0
f 0
wmc 9

2 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 2
B validate() 0 59 7
1
<?php
2
3
declare(strict_types=1);
4
5
namespace TMV\OpenIdClient\Token;
6
7
use function array_filter;
8
use function explode;
9
use function is_array;
10
use Jose\Component\Checker\AudienceChecker;
11
use Jose\Component\Checker\ClaimCheckerManager;
12
use Jose\Component\Checker\ExpirationTimeChecker;
13
use Jose\Component\Checker\IssuedAtChecker;
14
use Jose\Component\Checker\IssuerChecker;
15
use Jose\Component\Checker\NotBeforeChecker;
16
use Jose\Component\Core\AlgorithmManager;
17
use Jose\Component\Signature\Algorithm\RS256;
18
use Jose\Component\Signature\JWSVerifier;
19
use Jose\Component\Signature\Serializer\CompactSerializer;
20
use function json_decode;
21
use function sprintf;
22
use function str_replace;
23
use function TMV\OpenIdClient\base64url_decode;
24
use TMV\OpenIdClient\ClaimChecker\AzpChecker;
25
use TMV\OpenIdClient\Client\ClientInterface;
26
use TMV\OpenIdClient\Exception\InvalidArgumentException;
27
use TMV\OpenIdClient\Exception\RuntimeException;
28
29
class ResponseTokenVerifier extends AbstractTokenVerifier implements ResponseTokenVerifierInterface
30
{
31
    /** @var JWSVerifier */
32
    private $jwsVerifier;
33
34
    /** @var bool */
35
    private $aadIssValidation;
36
37
    /** @var int */
38
    private $clockTolerance;
39
40
    /**
41
     * IdTokenVerifier constructor.
42
     *
43
     * @param null|JWSVerifier $jwsVerifier
44
     * @param bool $aadIssValidation
45
     * @param int $clockTolerance
46
     */
47 10
    public function __construct(
48
        ?JWSVerifier $jwsVerifier = null,
49
        bool $aadIssValidation = false,
50
        int $clockTolerance = 0
51
    ) {
52 10
        $this->jwsVerifier = $jwsVerifier ?: new JWSVerifier(new AlgorithmManager([new RS256()]));
53 10
        $this->aadIssValidation = $aadIssValidation;
54 10
        $this->clockTolerance = $clockTolerance;
55 10
    }
56
57 10
    public function validate(ClientInterface $client, string $token): array
58
    {
59 10
        $metadata = $client->getMetadata();
60 10
        $expectedAlg = $metadata->getAuthorizationSignedResponseAlg();
61
62 10
        if (null === $expectedAlg) {
63
            throw new RuntimeException('No authorization_signed_response_alg defined');
64
        }
65
66 10
        $header = json_decode(base64url_decode(explode('.', $token)[0] ?? '{}'), true);
67
68 10
        if ($expectedAlg !== ($header['alg'] ?? '')) {
69
            throw new RuntimeException(sprintf('Unexpected JWE alg received, expected %s, got: %s', $expectedAlg, $header['alg'] ?? ''));
70
        }
71
72 10
        $payload = json_decode(base64url_decode(explode('.', $token)[1] ?? '{}'), true);
73
74 10
        if (! is_array($payload)) {
75
            throw new InvalidArgumentException('Unable to decode token payload');
76
        }
77
78 10
        $expectedIssuer = $client->getIssuer()->getMetadata()->getIssuer();
79
80 10
        if ($this->aadIssValidation) {
81
            $expectedIssuer = str_replace('{tenantid}', $payload['tid'] ?? '', $expectedIssuer);
82
        }
83
84
        $claimCheckers = [
85 10
            new IssuerChecker([$expectedIssuer]),
86 10
            new IssuedAtChecker($this->clockTolerance),
87 10
            new AudienceChecker($metadata->getClientId()),
88 10
            new ExpirationTimeChecker($this->clockTolerance),
89 10
            new NotBeforeChecker($this->clockTolerance),
90 10
            new AzpChecker($metadata->getClientId()),
91
        ];
92
93 10
        $requiredClaims = ['iss', 'sub', 'aud', 'exp', 'iat'];
94
95 10
        $claimCheckerManager = new ClaimCheckerManager(array_filter($claimCheckers));
96
97 10
        $claimCheckerManager->check($payload, array_filter($requiredClaims));
98
99 1
        if ('none' === $expectedAlg) {
100
            return $payload;
101
        }
102
103 1
        $serializer = new CompactSerializer();
104 1
        $jws = $serializer->unserialize($token);
105
106
        /** @var string|null $kid */
107 1
        $kid = $header['kid'] ?? null;
108
109 1
        $jwks = $this->getSigningJWKSet($client, $expectedAlg, $kid);
110
111 1
        if (! $this->jwsVerifier->verifyWithKeySet($jws, $jwks, 0)) {
112
            throw new InvalidArgumentException('Failed to validate JWT signature');
113
        }
114
115 1
        return $payload;
116
    }
117
}
118