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

AuthorizationService::fetchToken()   A

Complexity

Conditions 5
Paths 7

Size

Total Lines 37
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
cc 5
eloc 17
nc 7
nop 5
dl 0
loc 37
ccs 0
cts 18
cp 0
crap 30
rs 9.3888
c 0
b 0
f 0
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_filter(\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 1
        foreach ($params as $key => $value) {
97 1
            if (null === $value) {
98
                unset($params[$key]);
99 1
            } elseif ('claims' === $key && (\is_array($value) || $value instanceof JsonSerializable)) {
100
                $params['claims'] = \json_encode($value);
101 1
            } elseif (! \is_string($value)) {
102
                $params[$key] = (string) $value;
103
            }
104
        }
105
106 1
        if (empty($params['nonce']) && \in_array('id_token', \explode(' ', $params['response_type'] ?? ''), true)) {
107
            throw new InvalidArgumentException('nonce MUST be provided for implicit and hybrid flows');
108
        }
109
110 1
        return (string) $this->uriFactory->createUri($endpointUri)
111 1
            ->withQuery(\http_build_query($params));
112
    }
113
114
    public function getCallbackParams(ServerRequestInterface $serverRequest, OpenIDClient $client): array
115
    {
116
        return $this->processResponseParams($client, parse_callback_params($serverRequest));
117
    }
118
119
    public function callback(
120
        OpenIDClient $client,
121
        array $params,
122
        ?string $redirectUri = null,
123
        ?AuthSessionInterface $authSession = null,
124
        ?int $maxAge = null
125
    ): TokenSetInterface {
126
        $tokenSet = TokenSet::fromParams($params);
127
128
        if ($params['id_token'] ?? null) {
129
            $params['id_token'] = $this->idTokenDecrypter->decryptToken($client, $params['id_token']);
130
131
            $tokenSet = TokenSet::fromParams($params);
132
            $this->tokenSetVerifier->validate($tokenSet, $client, $authSession, true, $maxAge);
133
        }
134
135
        if (! $tokenSet->getCode()) {
136
            return $tokenSet;
137
        }
138
139
        // get token
140
        return $this->fetchToken($client, $tokenSet, $redirectUri, $authSession, $maxAge);
141
    }
142
143
    public function fetchToken(
144
        OpenIDClient $client,
145
        TokenSetInterface $tokenSet,
146
        ?string $redirectUri = null,
147
        ?AuthSessionInterface $authSession = null,
148
        ?int $maxAge = null
149
    ): TokenSetInterface {
150
        $code = $tokenSet->getCode();
151
152
        if (! $code) {
153
            throw new RuntimeException('Unable to fetch token without a code');
154
        }
155
156
        if (! $redirectUri) {
157
            $redirectUri = $client->getMetadata()->getRedirectUris()[0] ?? null;
158
        }
159
160
        if (! $redirectUri) {
161
            throw new InvalidArgumentException('A redirect_uri should be provided');
162
        }
163
164
        $params = $this->grant($client, [
165
            'grant_type' => 'authorization_code',
166
            'code' => $code,
167
            'redirect_uri' => $redirectUri,
168
        ]);
169
170
        if (! ($params['id_token'] ?? null)) {
171
            return TokenSet::fromParams($params);
172
        }
173
174
        $params['id_token'] = $this->idTokenDecrypter->decryptToken($client, $params['id_token']);
175
176
        $authResponse = TokenSet::fromParams($params);
177
        $this->tokenSetVerifier->validate($authResponse, $client, $authSession, false, $maxAge);
178
179
        return $authResponse;
180
    }
181
182 1
    public function grant(OpenIDClient $client, array $params = []): array
183
    {
184 1
        $authMethod = $client->getAuthMethodFactory()
185 1
            ->create($client->getMetadata()->getTokenEndpointAuthMethod());
186
187 1
        $tokenRequest = $this->requestFactory->createRequest('POST', $client->getTokenEndpoint())
188 1
            ->withHeader('content-type', 'application/x-www-form-urlencoded');
189
190 1
        $tokenRequest = $authMethod->createRequest($tokenRequest, $client, $params);
191
192
        try {
193 1
            $response = $this->client->sendRequest($tokenRequest);
194
        } catch (ClientExceptionInterface $e) {
195
            throw new RuntimeException('Unable to get token response', 0, $e);
196
        }
197
198 1
        return $this->processResponseParams($client, parse_metadata_response($response));
199
    }
200
201 1
    private function processResponseParams(OpenIDClient $client, array $params): array
202
    {
203 1
        if (\array_key_exists('error', $params)) {
204
            throw OAuth2Exception::fromParameters($params);
205
        }
206
207 1
        if (\array_key_exists('response', $params)) {
208
            $decrypted = $this->idTokenDecrypter->decryptToken($client, $params['response']);
209
            $params = $this->responseTokenVerifier->validate($client, $decrypted);
210
        }
211
212 1
        if (\array_key_exists('error', $params)) {
213
            throw OAuth2Exception::fromParameters($params);
214
        }
215
216 1
        return $params;
217
    }
218
}
219