RequestObjectFactory::create()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 2
dl 0
loc 6
ccs 4
cts 4
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace TMV\OpenIdClient\RequestObject;
6
7
use function array_filter;
8
use function array_merge;
9
use function implode;
10
use Jose\Component\Core\AlgorithmManager;
11
use Jose\Component\Encryption\Compression\CompressionMethodManager;
12
use Jose\Component\Encryption\Compression\Deflate;
13
use Jose\Component\Encryption\JWEBuilder;
14
use Jose\Component\Encryption\Serializer\CompactSerializer as EncryptionCompactSerializer;
15
use Jose\Component\Encryption\Serializer\JWESerializer;
16
use Jose\Component\Signature\Algorithm\None;
17
use Jose\Component\Signature\Algorithm\RS256;
18
use Jose\Component\Signature\JWSBuilder;
19
use Jose\Component\Signature\Serializer\CompactSerializer as SignatureCompactSerializer;
20
use Jose\Component\Signature\Serializer\JWSSerializer;
21
use function json_encode;
22
use function preg_match;
23
use function random_bytes;
24
use function strpos;
25
use function time;
26
use function TMV\OpenIdClient\base64url_encode;
27
use TMV\OpenIdClient\Client\ClientInterface;
28
use TMV\OpenIdClient\Exception\RuntimeException;
29
use function TMV\OpenIdClient\jose_secret_key;
30
31
class RequestObjectFactory
32
{
33
    /** @var AlgorithmManager */
34
    private $algorithmManager;
35
36
    /** @var JWSBuilder */
37
    private $jwsBuilder;
38
39
    /** @var JWEBuilder */
40
    private $jweBuilder;
41
42
    /** @var JWSSerializer */
43
    private $signatureSerializer;
44
45
    /** @var JWESerializer */
46
    private $encryptionSerializer;
47
48 6
    public function __construct(
49
        ?AlgorithmManager $algorithmManager = null,
50
        ?JWSBuilder $jwsBuilder = null,
51
        ?JWEBuilder $jweBuilder = null,
52
        ?JWSSerializer $signatureSerializer = null,
53
        ?JWESerializer $encryptionSerializer = null
54
    ) {
55 6
        $this->algorithmManager = $algorithmManager ?: new AlgorithmManager([new None(), new RS256()]);
56 6
        $this->jwsBuilder = $jwsBuilder ?: new JWSBuilder($this->algorithmManager);
57 6
        $this->jweBuilder = $jweBuilder ?: new JWEBuilder(
58 1
            $this->algorithmManager,
59 1
            $this->algorithmManager,
60 1
            new CompressionMethodManager([new Deflate()])
61
        );
62 6
        $this->signatureSerializer = $signatureSerializer ?: new SignatureCompactSerializer();
63 6
        $this->encryptionSerializer = $encryptionSerializer ?: new EncryptionCompactSerializer();
64 6
    }
65
66 5
    public function create(ClientInterface $client, array $params = []): string
67
    {
68 5
        $payload = $this->createPayload($client, $params);
69 5
        $signedToken = $this->createSignedToken($client, $payload);
70
71 5
        return $this->createEncryptedToken($client, $signedToken);
72
    }
73
74 5
    private function createPayload(ClientInterface $client, array $params = []): string
75
    {
76 5
        $metadata = $client->getMetadata();
77 5
        $issuer = $client->getIssuer();
78
79 5
        $payload = json_encode(array_merge($params, [
80 5
            'iss' => $metadata->getClientId(),
81 5
            'aud' => $issuer->getMetadata()->getIssuer(),
82 5
            'client_id' => $metadata->getClientId(),
83 5
            'jti' => base64url_encode(random_bytes(32)),
84 5
            'iat' => time(),
85 5
            'exp' => time() + 300,
86
        ]));
87
88 5
        if (false === $payload) {
89
            throw new RuntimeException('Unable to encode payload');
90
        }
91
92 5
        return $payload;
93
    }
94
95 5
    private function createSignedToken(ClientInterface $client, string $payload): string
96
    {
97 5
        $metadata = $client->getMetadata();
98
99
        /** @var string $alg */
100 5
        $alg = $metadata->get('request_object_signing_alg') ?: 'none';
101
102 5
        if ('none' === $alg) {
103 3
            return implode('.', [
104 3
                base64url_encode((string) json_encode(['alg' => $alg])),
105 3
                base64url_encode($payload),
106 3
                '',
107
            ]);
108
        }
109
110 2
        if (0 === strpos($alg, 'HS')) {
111 1
            $jwk = jose_secret_key($metadata->getClientSecret() ?: '');
112
        } else {
113 1
            $jwk = $client->getJwks()->selectKey('sig', $this->algorithmManager->get($alg));
114
        }
115
116 2
        if (null === $jwk) {
117
            throw new RuntimeException('No key to sign with alg ' . $alg);
118
        }
119
120 2
        $ktyIsOct = $jwk->has('kty') && $jwk->get('kty') === 'oct';
121
122 2
        $header = array_filter([
123 2
            'alg' => $alg,
124 2
            'typ' => 'JWT',
125 2
            'kid' => ! $ktyIsOct && $jwk->has('kid') ? $jwk->get('kid') : null,
126
        ]);
127
128 2
        $jws = $this->jwsBuilder->create()
129 2
            ->withPayload($payload)
130 2
            ->addSignature($jwk, $header)
131 2
            ->build();
132
133 2
        return $this->signatureSerializer->serialize($jws, 0);
134
    }
135
136 5
    private function createEncryptedToken(ClientInterface $client, string $payload): string
137
    {
138 5
        $metadata = $client->getMetadata();
139
140
        /** @var null|string $alg */
141 5
        $alg = $metadata->get('request_object_encryption_alg');
142
143 5
        if (null === $alg) {
144 3
            return $payload;
145
        }
146
147
        /** @var null|string $enc */
148 2
        $enc = $metadata->get('request_object_encryption_enc');
149
150 2
        if ((bool) preg_match('/^(RSA|ECDH)/', $alg)) {
151 1
            $jwk = $client->getIssuer()
152 1
                ->getJwks()
153 1
                ->selectKey('enc', $this->algorithmManager->get($alg));
154
        } else {
155 1
            $jwk = jose_secret_key(
156 1
                $metadata->getClientSecret() ?: '',
157 1
                'dir' === $alg ? $enc : $alg
158
            );
159
        }
160
161 2
        if (null === $jwk) {
162
            throw new RuntimeException('No key to sign with alg ' . $alg);
163
        }
164
165 2
        $ktyIsOct = $jwk->has('kty') && $jwk->get('kty') === 'oct';
166
167 2
        $header = array_filter([
168 2
            'alg' => $alg,
169 2
            'enc' => $enc,
170 2
            'cty' => 'JWT',
171 2
            'kid' => ! $ktyIsOct && $jwk->has('kid') ? $jwk->get('kid') : null,
172
        ]);
173
174 2
        $jwe = $this->jweBuilder->create()
175 2
            ->withPayload($payload)
176 2
            ->withSharedProtectedHeader($header)
177 2
            ->addRecipient($jwk)
178 2
            ->build();
179
180 2
        return $this->encryptionSerializer->serialize($jwe, 0);
181
    }
182
}
183