Passed
Push — develop ( 2c382f...68966f )
by nguereza
01:54
created

handleTokenRevocationRequest()   C

Complexity

Conditions 12
Paths 58

Size

Total Lines 57
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
eloc 32
c 1
b 0
f 0
nc 58
nop 1
dl 0
loc 57
rs 6.9666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Platine OAuth2
5
 *
6
 * Platine OAuth2 is a library that implements the OAuth2 specification
7
 *
8
 * This content is released under the MIT License (MIT)
9
 *
10
 * Copyright (c) 2020 Platine OAuth2
11
 *
12
 * Permission is hereby granted, free of charge, to any person obtaining a copy
13
 * of this software and associated documentation files (the "Software"), to deal
14
 * in the Software without restriction, including without limitation the rights
15
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
 * copies of the Software, and to permit persons to whom the Software is
17
 * furnished to do so, subject to the following conditions:
18
 *
19
 * The above copyright notice and this permission notice shall be included in all
20
 * copies or substantial portions of the Software.
21
 *
22
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
 * SOFTWARE.
29
 */
30
31
declare(strict_types=1);
32
33
namespace Platine\OAuth2;
34
35
use Platine\Http\Response;
36
use Platine\Http\ResponseInterface;
37
use Platine\Http\ServerRequestInterface;
38
use Platine\OAuth2\AuthorizationServerInterface;
39
use Platine\OAuth2\Entity\Client;
40
use Platine\OAuth2\Entity\TokenOwnerInterface;
41
use Platine\OAuth2\Exception\OAuth2Exception;
42
use Platine\OAuth2\Grant\AuthorizationServerAwareInterface;
43
use Platine\OAuth2\Grant\GrantInterface;
44
use Platine\OAuth2\Response\JsonResponse;
45
use Platine\OAuth2\Service\AccessTokenService;
46
use Platine\OAuth2\Service\ClientService;
47
use Platine\OAuth2\Service\RefreshTokenService;
48
use Throwable;
49
50
/**
51
 * @class AuthorizationServer
52
 * @package Platine\OAuth2
53
 */
54
class AuthorizationServer implements AuthorizationServerInterface
55
{
56
    /**
57
     * The ClientService
58
     * @var ClientService
59
     */
60
    protected ClientService $clientService;
61
62
    /**
63
     * The AccessTokenService
64
     * @var AccessTokenService
65
     */
66
    protected AccessTokenService $accessTokenService;
67
68
    /**
69
     * The RefreshTokenService
70
     * @var RefreshTokenService
71
     */
72
    protected RefreshTokenService $refreshTokenService;
73
74
75
    /**
76
     * The grant list
77
     * @var array<string, GrantInterface>
78
     */
79
    protected array $grants = [];
80
81
    /**
82
     * A list of grant that can answer to an authorization request
83
     * @var array<string, GrantInterface>
84
     */
85
    protected array $responseTypes = [];
86
87
    /**
88
     * Create new instance
89
     * @param ClientService $clientService
90
     * @param AccessTokenService $accessTokenService
91
     * @param RefreshTokenService $refreshTokenService
92
     * @param array<GrantInterface> $grants
93
     */
94
    public function __construct(
95
        ClientService $clientService,
96
        AccessTokenService $accessTokenService,
97
        RefreshTokenService $refreshTokenService,
98
        array $grants = []
99
    ) {
100
        $this->clientService = $clientService;
101
        $this->accessTokenService = $accessTokenService;
102
        $this->refreshTokenService = $refreshTokenService;
103
104
        foreach ($grants as /** @var GrantInterface $grant */ $grant) {
105
            if ($grant instanceof AuthorizationServerAwareInterface) {
106
                $grant->setAuthorizationServer($this);
107
            }
108
109
            $this->grants[$grant->getType()] = $grant;
0 ignored issues
show
Bug introduced by
The method getType() does not exist on Platine\OAuth2\Grant\Aut...ionServerAwareInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Platine\OAuth2\Grant\Aut...ionServerAwareInterface. ( Ignorable by Annotation )

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

109
            $this->grants[$grant->/** @scrutinizer ignore-call */ getType()] = $grant;
Loading history...
110
111
            $responseType = $grant->getResponseType();
0 ignored issues
show
Bug introduced by
The method getResponseType() does not exist on Platine\OAuth2\Grant\Aut...ionServerAwareInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Platine\OAuth2\Grant\Aut...ionServerAwareInterface. ( Ignorable by Annotation )

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

111
            /** @scrutinizer ignore-call */ 
112
            $responseType = $grant->getResponseType();
Loading history...
112
            if (!empty($responseType)) {
113
                $this->responseTypes[$responseType] = $grant;
114
            }
115
        }
116
    }
117
118
    /**
119
     * {@inheritdoc}
120
     */
121
    public function handleAuthorizationRequest(
122
        ServerRequestInterface $request,
123
        ?TokenOwnerInterface $owner = null
124
    ): ResponseInterface {
125
        try {
126
            $queryParams = $request->getQueryParams();
127
            $responseTypeParam = $queryParams['response_type'] ?? null;
128
            if ($responseTypeParam === null) {
129
                throw OAuth2Exception::invalidRequest('No grant response type was found in the request');
130
            }
131
132
            $responseType = $this->getResponseType((string) $responseTypeParam);
133
            $client = $this->getClient($request, $responseType->allowPublicClients());
134
135
            if ($client === null) {
136
                throw OAuth2Exception::invalidClient('No client could be authenticated');
137
            }
138
            $response = $responseType->createAuthorizationResponse($request, $client, $owner);
139
        } catch (OAuth2Exception $ex) {
140
            $response = $this->createResponsFromException($ex);
141
        }
142
143
        return $response->withHeader('Content-Type', 'application/json');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $response->withHe...e', 'application/json') could return the type Platine\Http\MessageInterface which includes types incompatible with the type-hinted return Platine\Http\ResponseInterface. Consider adding an additional type-check to rule them out.
Loading history...
144
    }
145
146
    /**
147
     * {@inheritdoc}
148
     */
149
    public function handleTokenRequest(
150
        ServerRequestInterface $request,
151
        ?TokenOwnerInterface $owner = null
152
    ): ResponseInterface {
153
        $postParams = (array) $request->getParsedBody();
154
155
        try {
156
            $grantParam = $postParams['grant_type'] ?? null;
157
            if ($grantParam === null) {
158
                throw OAuth2Exception::invalidRequest('No grant type was found in the request');
159
            }
160
161
            $grant = $this->getGrant((string) $grantParam);
162
            $client = $this->getClient($request, $grant->allowPublicClients());
163
164
            if ($client === null) {
165
                throw OAuth2Exception::invalidClient('No client could be authenticated');
166
            }
167
168
            $response = $grant->createTokenResponse($request, $client, $owner);
169
        } catch (OAuth2Exception $ex) {
170
            $response = $this->createResponsFromException($ex);
171
        }
172
173
        // According to the spec, we must set those headers
174
        // (http://tools.ietf.org/html/rfc6749#section-5.1)
175
        return $response->withHeader('Content-Type', 'application/json')
0 ignored issues
show
Bug Best Practice introduced by
The expression return $response->withHe...r('Pragma', 'no-cache') could return the type Platine\Http\MessageInterface which includes types incompatible with the type-hinted return Platine\Http\ResponseInterface. Consider adding an additional type-check to rule them out.
Loading history...
176
                        ->withHeader('Cache-Control', 'no-store')
177
                        ->withHeader('Pragma', 'no-cache');
178
    }
179
180
    /**
181
     * {@inheritdoc}
182
     */
183
    public function handleTokenRevocationRequest(ServerRequestInterface $request): ResponseInterface
184
    {
185
        $response = new Response();
186
        try {
187
            $postParams = (array) $request->getParsedBody();
188
            $tokenParam = $postParams['token'] ?? null;
189
            $tokenHint = $postParams['token_type_hint'] ?? null;
190
            if ($tokenParam === null || $tokenHint === null) {
191
                throw OAuth2Exception::invalidRequest(
192
                    'Cannot revoke a token as the "token" and/or "token_type_hint" parameters are missing'
193
                );
194
            }
195
196
            if (in_array($tokenHint, ['access_token', 'refresh_token']) === false) {
197
                throw OAuth2Exception::unsupportedTokenType(sprintf(
198
                    'Authorization server does not support revocation of token of type "%s"',
199
                    $tokenHint
200
                ));
201
            }
202
203
            if ($tokenHint === 'access_token') {
204
                $token = $this->accessTokenService->getToken((string) $tokenParam);
205
            } else {
206
                $token = $this->refreshTokenService->getToken((string) $tokenParam);
207
            }
208
209
            // According to spec, we should return 200 if token is invalid
210
            if ($token === null) {
211
                return $response;
212
            }
213
214
            // Now, we must validate the client if the token was generated against a non-public client
215
            if ($token->getClient() !== null && $token->getClient()->isPublic() === false) {
216
                $requestClient = $this->getClient($request, false);
217
218
                if ($requestClient !== $token->getClient()) {
219
                    throw OAuth2Exception::invalidClient(
220
                        'Token was issued for another client and cannot be revoked'
221
                    );
222
                }
223
            }
224
225
            if ($tokenHint === 'access_token') {
226
                $this->accessTokenService->delete($token);
227
            } else {
228
                $this->refreshTokenService->delete($token);
229
            }
230
        } catch (OAuth2Exception $ex) {
231
            $response = $this->createResponsFromException($ex);
232
        } catch (Throwable $ex) {
233
            // According to spec (https://tools.ietf.org/html/rfc7009#section-2.2.1),
234
            // we should return a server 503
235
            // error if we cannot delete the token for any reason
236
            $response = $response->withStatus(503, 'An error occurred while trying to delete the token');
237
        }
238
239
        return $response;
240
    }
241
242
    /**
243
     * {@inheritdoc}
244
     */
245
    public function hasGrant(string $grant): bool
246
    {
247
        return array_key_exists($grant, $this->grants);
248
    }
249
250
    /**
251
     * {@inheritdoc}
252
     */
253
    public function hasResponseType(string $responseType): bool
254
    {
255
        return array_key_exists($responseType, $this->responseTypes);
256
    }
257
258
    /**
259
     * Return the grant
260
     * @param string $grantType
261
     * @return GrantInterface
262
     */
263
    public function getGrant(string $grantType): GrantInterface
264
    {
265
        if ($this->hasGrant($grantType)) {
266
            return $this->grants[$grantType];
267
        }
268
269
        throw OAuth2Exception::unsupportedGrantType(sprintf(
270
            'Grant type "%s" is not supported by this server',
271
            $grantType
272
        ));
273
    }
274
275
    /**
276
     * Return the grant response type
277
     * @param string $responseType
278
     * @return GrantInterface
279
     */
280
    public function getResponseType(string $responseType): GrantInterface
281
    {
282
        if ($this->hasResponseType($responseType)) {
283
            return $this->responseTypes[$responseType];
284
        }
285
286
        throw OAuth2Exception::unsupportedResponseType(sprintf(
287
            'Response type "%s" is not supported by this server',
288
            $responseType
289
        ));
290
    }
291
292
    /**
293
     * Get the client (after authenticating it)
294
     *
295
     * According to the spec (http://tools.ietf.org/html/rfc6749#section-2.3), for public clients we do
296
     * not need to authenticate them
297
     *
298
     * @param ServerRequestInterface $request
299
     * @param bool $allowPublicClients
300
     * @return Client|null
301
     */
302
    protected function getClient(ServerRequestInterface $request, bool $allowPublicClients): ?Client
303
    {
304
        [$id, $secret] = $this->getClientCredentialsFromRequest($request);
305
306
        // If the grant type we are issuing does not allow public clients, and that the secret is
307
        // missing, then we have an error...
308
        if ($allowPublicClients === false && empty($secret)) {
309
            throw OAuth2Exception::invalidClient('Client secret is missing');
310
        }
311
312
        // If we allow public clients and no client id was set, we can return null
313
        if ($allowPublicClients && empty($id)) {
314
            return null;
315
        }
316
317
        $client = $this->clientService->find($id);
318
        // We delegate all the checks to the client service
319
        if ($client === null || ($allowPublicClients === false && $client->authenticate($secret) === false)) {
320
            throw OAuth2Exception::invalidClient('Client authentication failed');
321
        }
322
323
        return $client;
324
    }
325
326
    /**
327
     * Create a response from the exception, using the format of the spec
328
     * @link   http://tools.ietf.org/html/rfc6749#section-5.2
329
     *
330
     * @param OAuth2Exception $exception
331
     * @return ResponseInterface
332
     */
333
    protected function createResponsFromException(OAuth2Exception $exception): ResponseInterface
334
    {
335
        $data = [
336
            'error' => $exception->getCode(),
337
            'error_description' => $exception->getMessage(),
338
        ];
339
340
        return new JsonResponse($data, 400);
341
    }
342
343
    /**
344
     * Return the client id and secret from request data
345
     * @param ServerRequestInterface $request
346
     * @return array<string>
347
     */
348
    protected function getClientCredentialsFromRequest(ServerRequestInterface $request): array
349
    {
350
        // We first try to get the Authorization header, as this is
351
        // the recommended way according to the spec
352
        if ($request->hasHeader('Authorization')) {
353
            // Header value is expected to be "Bearer xxx"
354
            $parts = explode(' ', $request->getHeaderLine('Authorization'));
355
            $value = base64_decode(end($parts));
356
357
            [$id, $secret] = explode(':', $value);
358
        } else {
359
            $postParams = (array) $request->getParsedBody();
360
            $id = $postParams['client_id'] ?? null;
361
            $secret = $postParams['client_secret'] ?? null;
362
        }
363
364
        return [$id, $secret];
365
    }
366
}
367