Passed
Push — master ( d8eef7...20c7aa )
by Thomas Mauro
02:52
created

AuthorizationService   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 190
Duplicated Lines 0 %

Test Coverage

Coverage 50.63%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 78
c 4
b 0
f 0
dl 0
loc 190
ccs 40
cts 79
cp 0.5063
rs 9.92
wmc 31

7 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 14 7
A grant() 0 17 2
A callback() 0 22 3
B getAuthorizationUri() 0 33 9
A getCallbackParams() 0 3 1
A processResponseParams() 0 16 4
A fetchToken() 0 37 5
1
<?php
2
3
declare(strict_types=1);
4
5
namespace TMV\OpenIdClient\Service;
6
7
use Http\Discovery\Psr17FactoryDiscovery;
8
use Http\Discovery\Psr18ClientDiscovery;
9
use JsonSerializable;
10
use Psr\Http\Client\ClientExceptionInterface;
11
use Psr\Http\Client\ClientInterface;
12
use Psr\Http\Message\RequestFactoryInterface;
13
use Psr\Http\Message\ServerRequestInterface;
14
use Psr\Http\Message\UriFactoryInterface;
15
use TMV\OpenIdClient\ClientInterface as OpenIDClient;
16
use TMV\OpenIdClient\Exception\InvalidArgumentException;
17
use TMV\OpenIdClient\Exception\OAuth2Exception;
18
use TMV\OpenIdClient\Exception\RuntimeException;
19
use TMV\OpenIdClient\Model\AuthSessionInterface;
20
use function TMV\OpenIdClient\parse_callback_params;
21
use function TMV\OpenIdClient\parse_metadata_response;
22
use TMV\OpenIdClient\Token\ResponseTokenVerifier;
23
use TMV\OpenIdClient\Token\ResponseTokenVerifierInterface;
24
use TMV\OpenIdClient\Token\TokenDecrypter;
25
use TMV\OpenIdClient\Token\TokenDecrypterInterface;
26
use TMV\OpenIdClient\Token\TokenSet;
27
use TMV\OpenIdClient\Token\TokenSetInterface;
28
use TMV\OpenIdClient\Token\TokenSetVerifier;
29
use TMV\OpenIdClient\Token\TokenSetVerifierInterface;
30
31
class AuthorizationService
32
{
33
    /** @var TokenSetVerifierInterface */
34
    private $tokenSetVerifier;
35
36
    /** @var ResponseTokenVerifierInterface */
37
    private $responseTokenVerifier;
38
39
    /** @var TokenDecrypterInterface */
40
    private $idTokenDecrypter;
41
42
    /** @var ClientInterface */
43
    private $client;
44
45
    /** @var RequestFactoryInterface */
46
    private $requestFactory;
47
48
    /** @var UriFactoryInterface */
49
    private $uriFactory;
50
51
    /**
52
     * AuthorizationService constructor.
53
     *
54
     * @param TokenSetVerifierInterface|null $tokenSetVerifier
55
     * @param ResponseTokenVerifierInterface|null $responseTokenVerifier
56
     * @param TokenDecrypterInterface|null $idTokenDecrypter
57
     * @param null|ClientInterface $client
58
     * @param null|RequestFactoryInterface $requestFactory
59
     * @param null|UriFactoryInterface $uriFactory
60
     */
61 2
    public function __construct(
62
        ?TokenSetVerifierInterface $tokenSetVerifier = null,
63
        ?ResponseTokenVerifierInterface $responseTokenVerifier = null,
64
        ?TokenDecrypterInterface $idTokenDecrypter = null,
65
        ?ClientInterface $client = null,
66
        ?RequestFactoryInterface $requestFactory = null,
67
        ?UriFactoryInterface $uriFactory = null
68
    ) {
69 2
        $this->tokenSetVerifier = $tokenSetVerifier ?: new TokenSetVerifier();
70 2
        $this->responseTokenVerifier = $responseTokenVerifier ?: new ResponseTokenVerifier();
71 2
        $this->idTokenDecrypter = $idTokenDecrypter ?: new TokenDecrypter();
72 2
        $this->client = $client ?: Psr18ClientDiscovery::find();
73 2
        $this->requestFactory = $requestFactory ?: Psr17FactoryDiscovery::findRequestFactory();
74 2
        $this->uriFactory = $uriFactory ?: Psr17FactoryDiscovery::findUrlFactory();
75 2
    }
76
77
    /**
78
     * @param OpenIDClient $client
79
     * @param array<string, mixed> $params
80
     *
81
     * @return string
82
     */
83 1
    public function getAuthorizationUri(OpenIDClient $client, array $params = []): string
84
    {
85 1
        $clientMetadata = $client->getMetadata();
86 1
        $issuerMetadata = $client->getIssuer()->getMetadata();
87 1
        $endpointUri = $issuerMetadata->getAuthorizationEndpoint();
88
89 1
        $params = \array_merge([
90 1
            'client_id' => $clientMetadata->getClientId(),
91 1
            'scope' => 'openid',
92 1
            'response_type' => $clientMetadata->getResponseTypes()[0] ?? 'code',
93 1
            'redirect_uri' => $clientMetadata->getRedirectUris()[0] ?? null,
94 1
        ], $params);
95
96
        $params = \array_filter($params, static function ($value) {
97 1
            return null !== $value;
98 1
        });
99
100 1
        foreach ($params as $key => $value) {
101 1
            if (null === $value) {
102
                unset($params[$key]);
103 1
            } elseif ('claims' === $key && (\is_array($value) || $value instanceof JsonSerializable)) {
104
                $params['claims'] = \json_encode($value);
105 1
            } elseif (! \is_string($value)) {
106
                $params[$key] = (string) $value;
107
            }
108
        }
109
110 1
        if (empty($params['nonce']) && \in_array('id_token', \explode(' ', $params['response_type'] ?? ''), true)) {
111
            throw new InvalidArgumentException('nonce MUST be provided for implicit and hybrid flows');
112
        }
113
114 1
        return (string) $this->uriFactory->createUri($endpointUri)
115 1
            ->withQuery(\http_build_query($params));
116
    }
117
118
    public function getCallbackParams(ServerRequestInterface $serverRequest, OpenIDClient $client): array
119
    {
120
        return $this->processResponseParams($client, parse_callback_params($serverRequest));
121
    }
122
123
    public function callback(
124
        OpenIDClient $client,
125
        array $params,
126
        ?string $redirectUri = null,
127
        ?AuthSessionInterface $authSession = null,
128
        ?int $maxAge = null
129
    ): TokenSetInterface {
130
        $tokenSet = TokenSet::fromParams($params);
131
132
        if ($params['id_token'] ?? null) {
133
            $params['id_token'] = $this->idTokenDecrypter->decryptToken($client, $params['id_token']);
134
135
            $tokenSet = TokenSet::fromParams($params);
136
            $this->tokenSetVerifier->validate($tokenSet, $client, $authSession, true, $maxAge);
137
        }
138
139
        if (! $tokenSet->getCode()) {
140
            return $tokenSet;
141
        }
142
143
        // get token
144
        return $this->fetchToken($client, $tokenSet, $redirectUri, $authSession, $maxAge);
145
    }
146
147
    public function fetchToken(
148
        OpenIDClient $client,
149
        TokenSetInterface $tokenSet,
150
        ?string $redirectUri = null,
151
        ?AuthSessionInterface $authSession = null,
152
        ?int $maxAge = null
153
    ): TokenSetInterface {
154
        $code = $tokenSet->getCode();
155
156
        if (! $code) {
157
            throw new RuntimeException('Unable to fetch token without a code');
158
        }
159
160
        if (! $redirectUri) {
161
            $redirectUri = $client->getMetadata()->getRedirectUris()[0] ?? null;
162
        }
163
164
        if (! $redirectUri) {
165
            throw new InvalidArgumentException('A redirect_uri should be provided');
166
        }
167
168
        $params = $this->grant($client, [
169
            'grant_type' => 'authorization_code',
170
            'code' => $code,
171
            'redirect_uri' => $redirectUri,
172
        ]);
173
174
        if (! ($params['id_token'] ?? null)) {
175
            return TokenSet::fromParams($params);
176
        }
177
178
        $params['id_token'] = $this->idTokenDecrypter->decryptToken($client, $params['id_token']);
179
180
        $authResponse = TokenSet::fromParams($params);
181
        $this->tokenSetVerifier->validate($authResponse, $client, $authSession, false, $maxAge);
182
183
        return $authResponse;
184
    }
185
186 1
    public function grant(OpenIDClient $client, array $params = []): array
187
    {
188 1
        $authMethod = $client->getAuthMethodFactory()
189 1
            ->create($client->getMetadata()->getTokenEndpointAuthMethod());
190
191 1
        $tokenRequest = $this->requestFactory->createRequest('POST', $client->getTokenEndpoint())
192 1
            ->withHeader('content-type', 'application/x-www-form-urlencoded');
193
194 1
        $tokenRequest = $authMethod->createRequest($tokenRequest, $client, $params);
195
196
        try {
197 1
            $response = $this->client->sendRequest($tokenRequest);
198
        } catch (ClientExceptionInterface $e) {
199
            throw new RuntimeException('Unable to get token response', 0, $e);
200
        }
201
202 1
        return $this->processResponseParams($client, parse_metadata_response($response));
203
    }
204
205 1
    private function processResponseParams(OpenIDClient $client, array $params): array
206
    {
207 1
        if (\array_key_exists('error', $params)) {
208
            throw OAuth2Exception::fromParameters($params);
209
        }
210
211 1
        if (\array_key_exists('response', $params)) {
212
            $decrypted = $this->idTokenDecrypter->decryptToken($client, $params['response']);
213
            $params = $this->responseTokenVerifier->validate($client, $decrypted);
214
        }
215
216 1
        if (\array_key_exists('error', $params)) {
217
            throw OAuth2Exception::fromParameters($params);
218
        }
219
220 1
        return $params;
221
    }
222
}
223