IdTokenVerifier::validate()   F
last analyzed

Complexity

Conditions 15
Paths 390

Size

Total Lines 78
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 36
CRAP Score 15.4085

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 15
eloc 41
nc 390
nop 5
dl 0
loc 78
ccs 36
cts 41
cp 0.878
crap 15.4085
rs 2.7083
c 5
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\AuthTimeChecker;
25
use TMV\OpenIdClient\ClaimChecker\AzpChecker;
26
use TMV\OpenIdClient\ClaimChecker\NonceChecker;
27
use TMV\OpenIdClient\Client\ClientInterface;
28
use TMV\OpenIdClient\Exception\InvalidArgumentException;
29
use TMV\OpenIdClient\Exception\RuntimeException;
30
use TMV\OpenIdClient\Session\AuthSessionInterface;
31
32
class IdTokenVerifier extends AbstractTokenVerifier implements IdTokenVerifierInterface
33
{
34
    /** @var JWSVerifier */
35
    private $jwsVerifier;
36
37
    /** @var bool */
38
    private $aadIssValidation;
39
40
    /** @var int */
41
    private $clockTolerance;
42
43 23
    public function __construct(
44
        ?JWSVerifier $jwsVerifier = null,
45
        bool $aadIssValidation = false,
46
        int $clockTolerance = 0
47
    ) {
48 23
        $this->jwsVerifier = $jwsVerifier ?: new JWSVerifier(new AlgorithmManager([new RS256()]));
49 23
        $this->aadIssValidation = $aadIssValidation;
50 23
        $this->clockTolerance = $clockTolerance;
51 23
    }
52
53 10
    public function validateUserinfoToken(
54
        ClientInterface $client,
55
        string $idToken,
56
        ?AuthSessionInterface $authSession = null,
57
        ?int $maxAge = null
58
    ): array {
59 10
        return $this->validate($client, $idToken, $authSession, true, $maxAge);
60
    }
61
62 13
    public function validateIdToken(
63
        ClientInterface $client,
64
        string $idToken,
65
        ?AuthSessionInterface $authSession = null,
66
        ?int $maxAge = null
67
    ): array {
68 13
        return $this->validate($client, $idToken, $authSession, false, $maxAge);
69
    }
70
71 23
    private function validate(
72
        ClientInterface $client,
73
        string $idToken,
74
        ?AuthSessionInterface $authSession = null,
75
        bool $fromUserInfo = false,
76
        ?int $maxAge = null
77
    ): array {
78 23
        $metadata = $client->getMetadata();
79 23
        $expectedAlg = $fromUserInfo
80 10
            ? $metadata->getUserinfoSignedResponseAlg()
81 23
            : $metadata->getIdTokenSignedResponseAlg();
82
83 23
        if (null === $expectedAlg) {
84
            throw new RuntimeException('Unable to verify id_token without an alg value');
85
        }
86
87 23
        $header = json_decode(base64url_decode(explode('.', $idToken)[0] ?? '{}'), true);
88
89 23
        if ($expectedAlg !== ($header['alg'] ?? '')) {
90
            throw new RuntimeException(sprintf('Unexpected JWS alg received, expected %s, got: %s', $expectedAlg, $header['alg'] ?? ''));
91
        }
92
93 23
        $payload = json_decode(base64url_decode(explode('.', $idToken)[1] ?? '{}'), true);
94
95 23
        if (! is_array($payload)) {
96
            throw new InvalidArgumentException('Unable to decode token payload');
97
        }
98
99 23
        $expectedIssuer = $client->getIssuer()->getMetadata()->getIssuer();
100
101 23
        if ($this->aadIssValidation) {
102
            $expectedIssuer = str_replace('{tenantid}', $payload['tid'] ?? '', $expectedIssuer);
103
        }
104
105 23
        $nonce = null !== $authSession ? $authSession->getNonce() : null;
106
107
        $claimCheckers = [
108 23
            new IssuerChecker([$expectedIssuer]),
109 23
            new IssuedAtChecker($this->clockTolerance),
110 23
            new AudienceChecker($metadata->getClientId()),
111 23
            new ExpirationTimeChecker($this->clockTolerance),
112 23
            new NotBeforeChecker($this->clockTolerance),
113 23
            new AzpChecker($metadata->getClientId()),
114 23
            null !== $maxAge ? new AuthTimeChecker($maxAge, $this->clockTolerance) : null,
115 23
            null !== $nonce ? new NonceChecker($nonce) : null,
116
        ];
117
118 23
        $requiredClaims = [];
119
120 23
        if (! $fromUserInfo) {
121 13
            $requiredClaims = ['iss', 'sub', 'aud', 'exp', 'iat'];
122
        }
123
124 23
        if ((int) $maxAge > 0 || (null !== $maxAge && null !== $metadata->get('require_auth_time'))) {
125 23
            $requiredClaims[] = 'auth_time';
126
        }
127
128 23
        $claimCheckerManager = new ClaimCheckerManager(array_filter($claimCheckers));
129
130 23
        $claimCheckerManager->check($payload, array_filter($requiredClaims));
131
132 10
        if ('none' === $expectedAlg) {
133 1
            return $payload;
134
        }
135
136 9
        $serializer = new CompactSerializer();
137 9
        $jws = $serializer->unserialize($idToken);
138
139
        /** @var string|null $kid */
140 9
        $kid = $header['kid'] ?? null;
141
142 9
        $jwks = $this->getSigningJWKSet($client, $expectedAlg, $kid);
143
144 9
        if (! $this->jwsVerifier->verifyWithKeySet($jws, $jwks, 0)) {
145
            throw new InvalidArgumentException('Failed to validate JWT signature');
146
        }
147
148 9
        return $payload;
149
    }
150
}
151