RegistrationService::read()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 11
nc 3
nop 2
dl 0
loc 19
ccs 0
cts 10
cp 0
crap 12
rs 9.9
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Facile\OpenIDClient\Service;
6
7
use function array_diff_key;
8
use function array_flip;
9
use function array_intersect_key;
10
use function array_key_exists;
11
use function array_merge;
12
use function Facile\OpenIDClient\check_server_response;
13
use Facile\OpenIDClient\Exception\InvalidArgumentException;
14
use Facile\OpenIDClient\Exception\RuntimeException;
15
use Facile\OpenIDClient\Issuer\IssuerInterface;
16
use function Facile\OpenIDClient\parse_metadata_response;
17
use function json_encode;
18
use Psr\Http\Client\ClientExceptionInterface;
19
use Psr\Http\Client\ClientInterface;
20
use Psr\Http\Message\RequestFactoryInterface;
21
22
/**
23
 * Dynamic Client Registration Protocol
24
 *
25
 * @link https://tools.ietf.org/html/rfc7591
26
 * @link https://openid.net/specs/openid-connect-registration-1_0.html
27
 */
28
final class RegistrationService
29
{
30
    /** @var ClientInterface */
31
    private $client;
32
33
    /** @var RequestFactoryInterface */
34
    private $requestFactory;
35
36
    /** @var string[] */
37
    private static $registrationClaims = [
38
        'registration_access_token',
39
        'registration_client_uri',
40
        'client_secret_expires_at',
41
        'client_id_issued_at',
42
    ];
43
44
    public function __construct(
45
        ClientInterface $client,
46
        RequestFactoryInterface $requestFactory
47
    ) {
48
        $this->client = $client;
49
        $this->requestFactory = $requestFactory;
50
    }
51
52
    /**
53
     * @param IssuerInterface $issuer
54
     * @param array<string, mixed> $metadata
55
     * @param string|null $initialToken
56
     *
57
     * @return array<string, mixed>
58
     */
59
    public function register(
60
        IssuerInterface $issuer,
61
        array $metadata,
62
        ?string $initialToken = null
63
    ): array {
64
        $registrationEndpoint = $issuer->getMetadata()->getRegistrationEndpoint();
65
66
        if (null === $registrationEndpoint) {
67
            throw new InvalidArgumentException('Issuer does not support dynamic client registration');
68
        }
69
70
        $encodedMetadata = json_encode($metadata);
71
72
        if (false === $encodedMetadata) {
73
            throw new RuntimeException('Unable to encode client metadata');
74
        }
75
76
        $request = $this->requestFactory->createRequest('POST', $registrationEndpoint)
77
            ->withHeader('content-type', 'application/json')
78
            ->withHeader('accept', 'application/json');
79
80
        $request->getBody()->write($encodedMetadata);
81
82
        if (null !== $initialToken) {
83
            $request = $request->withHeader('authorization', 'Bearer ' . $initialToken);
84
        }
85
86
        try {
87
            $response = $this->client->sendRequest($request);
88
        } catch (ClientExceptionInterface $e) {
89
            throw new RuntimeException('Unable to register OpenID client', 0, $e);
90
        }
91
92
        $data = parse_metadata_response($response, 201);
93
94
        if (! array_key_exists('client_id', $data)) {
95
            throw new RuntimeException('Registration response did not return a client_id field');
96
        }
97
98
        return $data;
99
    }
100
101
    /**
102
     * @param string $clientUri
103
     * @param string $accessToken
104
     *
105
     * @return array<string, mixed>
106
     */
107
    public function read(string $clientUri, string $accessToken): array
108
    {
109
        $request = $this->requestFactory->createRequest('GET', $clientUri)
110
            ->withHeader('accept', 'application/json')
111
            ->withHeader('authorization', 'Bearer ' . $accessToken);
112
113
        try {
114
            $response = $this->client->sendRequest($request);
115
        } catch (ClientExceptionInterface $e) {
116
            throw new RuntimeException('Unable to read OpenID client', 0, $e);
117
        }
118
119
        $claims = parse_metadata_response($response, 200);
120
121
        if (! array_key_exists('client_id', $claims)) {
122
            throw new RuntimeException('Registration response did not return a client_id field');
123
        }
124
125
        return $claims;
126
    }
127
128
    /**
129
     * @param string $clientUri
130
     * @param string $accessToken
131
     * @param array<string, mixed> $metadata
132
     *
133
     * @return array<string, mixed>
134
     */
135
    public function update(
136
        string $clientUri,
137
        string $accessToken,
138
        array $metadata
139
    ): array {
140
        /** @var array<string, mixed> $clientRegistrationMetadata */
141
        $clientRegistrationMetadata = array_intersect_key($metadata, array_flip(self::$registrationClaims));
142
        /** @var array<string, mixed> $metadata */
143
        $metadata = array_diff_key($metadata, $clientRegistrationMetadata);
144
145
        $encodedMetadata = json_encode($metadata);
146
147
        if (false === $encodedMetadata) {
148
            throw new RuntimeException('Unable to encode client metadata');
149
        }
150
151
        $request = $this->requestFactory->createRequest('PUT', $clientUri)
152
            ->withHeader('accept', 'application/json')
153
            ->withHeader('content-type', 'application/json')
154
            ->withHeader('authorization', 'Bearer ' . $accessToken);
155
156
        $request->getBody()->write($encodedMetadata);
157
158
        try {
159
            $response = $this->client->sendRequest($request);
160
        } catch (ClientExceptionInterface $e) {
161
            throw new RuntimeException('Unable to update OpenID client', 0, $e);
162
        }
163
164
        $data = parse_metadata_response($response, 200);
165
166
        if (! array_key_exists('client_id', $data)) {
167
            throw new RuntimeException('Registration response did not return a client_id field');
168
        }
169
170
        /** @var array<string, mixed> $merged */
171
        $merged = array_merge($clientRegistrationMetadata, $data);
172
173
        return $merged;
174
    }
175
176
    public function delete(string $clientUri, string $accessToken): void
177
    {
178
        $request = $this->requestFactory->createRequest('DELETE', $clientUri)
179
            ->withHeader('authorization', 'Bearer ' . $accessToken);
180
181
        try {
182
            $response = $this->client->sendRequest($request);
183
        } catch (ClientExceptionInterface $e) {
184
            throw new RuntimeException('Unable to delete OpenID client', 0, $e);
185
        }
186
187
        check_server_response($response, 204);
188
    }
189
}
190