Passed
Push — master ( 898cc7...450add )
by Thomas Mauro
03:07
created

ResponseTokenVerifier::validate()   B

Complexity

Conditions 8
Paths 13

Size

Total Lines 66
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 37
nc 13
nop 2
dl 0
loc 66
ccs 0
cts 48
cp 0
crap 72
rs 8.0835
c 1
b 0
f 0

How to fix   Long Method   

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