AbstractClaims::claimJWT()   B
last analyzed

Complexity

Conditions 8
Paths 8

Size

Total Lines 45
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 8.7515

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 22
nc 8
nop 2
dl 0
loc 45
ccs 17
cts 22
cp 0.7727
crap 8.7515
rs 8.4444
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Facile\OpenIDClient\Claims;
6
7
use function array_diff_key;
8
use function array_flip;
9
use function array_key_exists;
10
use function count;
11
use function explode;
12
use Facile\OpenIDClient\AlgorithmManagerBuilder;
13
use function Facile\OpenIDClient\base64url_decode;
14
use Facile\OpenIDClient\Client\ClientInterface as OpenIDClient;
15
use Facile\OpenIDClient\Exception\InvalidArgumentException;
16
use Facile\OpenIDClient\Exception\RuntimeException;
17
use Facile\OpenIDClient\Issuer\IssuerBuilder;
18
use Facile\OpenIDClient\Issuer\IssuerBuilderInterface;
19
use Jose\Component\Core\AlgorithmManager;
20
use Jose\Component\Core\JWKSet;
21
use Jose\Component\Signature\JWSVerifier;
22
use Jose\Component\Signature\Serializer\CompactSerializer;
23
use Jose\Component\Signature\Serializer\JWSSerializer;
24
use function json_decode;
25
use function sprintf;
26
27
/**
28
 * @psalm-import-type TokenSetClaimsType from \Facile\OpenIDClient\Token\TokenSetInterface
29
 */
30
abstract class AbstractClaims
31
{
32
    /** @var IssuerBuilderInterface */
33
    protected $issuerBuilder;
34
35
    /** @var AlgorithmManager */
36
    protected $algorithmManager;
37
38
    /** @var JWSVerifier */
39
    protected $JWSVerifier;
40
41
    /** @var JWSSerializer */
42
    protected $serializer;
43
44 8
    public function __construct(
45
        ?IssuerBuilderInterface $issuerBuilder = null,
46
        ?AlgorithmManager $algorithmManager = null,
47
        ?JWSVerifier $JWSVerifier = null,
48
        ?JWSSerializer $serializer = null
49
    ) {
50 8
        $this->issuerBuilder = $issuerBuilder ?? new IssuerBuilder();
51 8
        $this->algorithmManager = $algorithmManager ?? (new AlgorithmManagerBuilder())->build();
52 8
        $this->JWSVerifier = $JWSVerifier ?? new JWSVerifier($this->algorithmManager);
53 8
        $this->serializer = $serializer ?? new CompactSerializer();
54 8
    }
55
56
    /**
57
     * @param OpenIDClient $client
58
     * @param string $jwt
59
     *
60
     * @return array<string, mixed>
61
     */
62 3
    protected function claimJWT(OpenIDClient $client, string $jwt): array
63
    {
64 3
        $issuer = $client->getIssuer();
65
66
        /** @var null|array<string, mixed> $header */
67 3
        $header = json_decode(base64url_decode(explode('.', $jwt)[0] ?? '{}'), true);
68
        /** @var array<string, mixed> $payload */
69 3
        $payload = json_decode(base64url_decode(explode('.', $jwt)[1] ?? '{}'), true);
70
71
        /** @var null|string $alg */
72 3
        $alg = $header['alg'] ?? null;
73
        /** @var null|string $kid */
74 3
        $kid = $header['kid'] ?? null;
75
76 3
        if (null === $alg) {
77
            throw new InvalidArgumentException('Claim source is missing JWT header alg property');
78
        }
79
80 3
        if ('none' === $alg) {
81 2
            return $payload;
82
        }
83
84
        /** @var null|string $iss */
85 1
        $iss = $payload['iss'] ?? null;
86
87 1
        if (null === $iss || $iss === $issuer->getMetadata()->getIssuer()) {
88 1
            $jwks = JWKSet::createFromKeyData($issuer->getJwksProvider()->getJwks());
89
        } else {
90
            $discovered = $this->issuerBuilder->build($iss);
91
            $jwks = JWKSet::createFromKeyData($discovered->getJwksProvider()->getJwks());
92
        }
93
94 1
        $jws = $this->serializer->unserialize($jwt);
95
96 1
        $jwk = $jwks->selectKey('sig', $this->algorithmManager->get($alg), null !== $kid ? ['kid' => $kid] : []);
97
98 1
        if (null === $jwk) {
99
            throw new RuntimeException('Unable to get a key to verify claim source JWT');
100
        }
101
102 1
        if (false === $this->JWSVerifier->verifyWithKey($jws, $jwk, 0)) {
103
            throw new InvalidArgumentException('Invalid claim source JWT signature');
104
        }
105
106 1
        return $payload;
107
    }
108
109
    /**
110
     * @param array<string, mixed> $claims
111
     * @param array<string, string> $sourceNames
112
     * @param array<string, array<string, mixed>> $sources
113
     *
114
     * @return array<string, mixed>
115
     *
116
     * @psalm-param TokenSetClaimsType $claims
117
     * @psalm-return TokenSetClaimsType
118
     */
119 4
    protected function assignClaims(array $claims, array $sourceNames, array $sources): array
120
    {
121 4
        foreach ($sourceNames as $claim => $inSource) {
122 4
            if (! array_key_exists($inSource, $sources)) {
123 1
                continue;
124
            }
125
126 3
            if (! array_key_exists($claim, $sources[$inSource])) {
127
                throw new RuntimeException(sprintf('Unable to find claim "%s" in source "%s"', $claim, $inSource));
128
            }
129
130
            /** @var scalar $value */
131 3
            $value = $sources[$inSource][$claim];
132 3
            $claims[$claim] = $value;
133
            /** @var TokenSetClaimsType $claims */
134 3
            $claims['_claim_names'] = array_diff_key($claims['_claim_names'] ?? [], array_flip([$claim]));
135
        }
136
137 4
        return $claims;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $claims could return the type Facile\OpenIDClient\Claims\TokenSetClaimsType which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
138
    }
139
140
    /**
141
     * @param array<string, mixed> $claims
142
     *
143
     * @return array<string, mixed>
144
     *
145
     * @psalm-param TokenSetClaimsType $claims
146
     * @psalm-return TokenSetClaimsType
147
     */
148 4
    protected function cleanClaims(array $claims): array
149
    {
150 4
        if (array_key_exists('_claim_names', $claims) && 0 === count($claims['_claim_names'] ?? [])) {
151
            /** @var TokenSetClaimsType $claims */
152 3
            $claims = array_diff_key($claims, array_flip(['_claim_names']));
0 ignored issues
show
Bug introduced by
$claims of type Facile\OpenIDClient\Claims\TokenSetClaimsType is incompatible with the type array expected by parameter $array1 of array_diff_key(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

152
            $claims = array_diff_key(/** @scrutinizer ignore-type */ $claims, array_flip(['_claim_names']));
Loading history...
153
        }
154
155 4
        if (array_key_exists('_claim_sources', $claims) && 0 === count($claims['_claim_sources'] ?? [])) {
156
            /** @var TokenSetClaimsType $claims */
157 3
            $claims = array_diff_key($claims, array_flip(['_claim_sources']));
158
        }
159
160 4
        return $claims;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $claims could return the type Facile\OpenIDClient\Claims\TokenSetClaimsType which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
161
    }
162
}
163