Passed
Push — master ( 0c0a9d...5256fe )
by Alexandre
01:49
created

AuthorizationEndpoint::verify()   B

Complexity

Conditions 9
Paths 37

Size

Total Lines 55
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 90

Importance

Changes 0
Metric Value
cc 9
eloc 28
nc 37
nop 2
dl 0
loc 55
ccs 0
cts 0
cp 0
crap 90
rs 7.2446
c 0
b 0
f 0

How to fix   Long Method   

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
 * Created by PhpStorm.
4
 * User: GCC-MED
5
 * Date: 19/01/2018
6
 * Time: 16:02
7
 */
8
9
namespace OAuth2\OpenID\Endpoints;
10
11
12
use GuzzleHttp\Psr7\Response;
13
use GuzzleHttp\Psr7\Uri;
14
use OAuth2\Config;
15
use OAuth2\EndpointMessages\Authorization\AuthorizationResponse;
16
use OAuth2\EndpointMessages\Authorization\ErrorResponse;
17
use OAuth2\Exceptions\OAuthException;
18
use OAuth2\OpenID\ResponseModes\ResponseModeInterface;
19
use OAuth2\OpenID\ResponseTypes\ResponseTypeInterface as OpenIDResponseTypeInterface;
20
use OAuth2\ResponseTypes\ResponseTypeInterface;
21
use OAuth2\Roles\ClientInterface;
22
use OAuth2\Roles\Clients\RegisteredClient;
23
use OAuth2\Roles\ResourceOwnerInterface;
24
use OAuth2\Storages\ClientStorageInterface;
25
use Psr\Http\Message\ResponseInterface;
26
use Psr\Http\Message\ServerRequestInterface;
27
28
class AuthorizationEndpoint extends \OAuth2\Endpoints\AuthorizationEndpoint
29
{
30
    /**
31
     * @param ServerRequestInterface $request
32
     * @param ResourceOwnerInterface $resourceOwner
33
     * @return ResponseInterface
34
     * @throws \Exception
35
     */
36
    public function handle(ServerRequestInterface $request, ResourceOwnerInterface $resourceOwner): ResponseInterface
37
    {
38
        /**
39
         * @var RegisteredClient $client
40
         */
41
        if($res = $this->verify($request, $result)) {
42
            return $res;
43
        }
44
45
        /**
46
         * @var RegisteredClient $client
47
         * @var Uri $redirectUri
48
         * @var array $responseTypes
49
         * @var ResponseModeInterface $responseMode
50
         * @var array|null $scope
51
         * @var bool $isSecure
52
         * @var array $data
53
         */
54
        extract($result);
55
56
        $result = [];
57
58
        try {
59
            if(!$resourceOwner->isConsentGivenForClient($client)) {
60
                throw new OAuthException('access_denied',
61
                    'The resource owner server denied the request',
62
                    'https://tools.ietf.org/html/rfc6749#section-4.1.1');
63
            }
64
65
            $extendedResponseTypes = [];
66
67
            /**
68
             * @var ResponseTypeInterface $responseType
69
             */
70
            foreach ($responseTypes as $responseType) {
71
                if($responseType->getExtendedResponseTypes()) {
72
                    $extendedResponseTypes = array_merge($extendedResponseTypes, $responseType->getExtendedResponseTypes());
73
                }
74
                $responseTypeNames[] = $responseType->getResponseType();
75
            }
76
77
            /**
78
             * @var ResponseTypeInterface $responseType
79
             */
80
            foreach ($responseTypes as $responseType) {
81
                if(!in_array($responseType->getResponseType(), $extendedResponseTypes)) {
82
                    $extendedResponseTypes = null;
83
                    if ($responseType->getExtendedResponseTypes()) {
84
                        $extendedResponseTypeNames = array_intersect($responseType->getExtendedResponseTypes(), array_keys($responseTypes));
85
                        foreach ($extendedResponseTypeNames as $name) {
86
                            $extendedResponseTypes[$name] = $responseTypes[$name];
87
                        }
88
                    }
89
                    $result = array_merge($result, $responseType->handle($request, $resourceOwner, $client, $scope, $extendedResponseTypes));
90
                }
91
            }
92
//            var_dump($result);die;
0 ignored issues
show
Unused Code Comprehensibility introduced by
75% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
93
94
        } catch (OAuthException $e) {
95
            return new ErrorResponse($redirectUri, $e->getError(),
96
                $e->getErrorDescription(),
97
                $e->getErrorUri(),
98
                $data['state'] ?? null);
99
        }
100
101
        if(isset($data['state'])) {
102
            $result['state'] = $data['state'];
103
        }
104
105
        return $responseMode->handle($redirectUri, $result);
106
107
108
        // todo, repository for response mode
109
        // https://developer.okta.com/docs/api/resources/oidc#parameter-details
110
       /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
111
        if (isset($data['response_mode']) && $data['response_mode'] == 'post_message') {
112
            return $this->popupResponse(['access_token' => 'a'], $redirectUri);
113
        } else {
114
            return new AuthorizationResponse($redirectUri);
115
        }
116
        */
117
    }
118
119
    /**
120
     * @param ServerRequestInterface $request
121
     * @param null $result
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $result is correct as it would always require null to be passed?
Loading history...
122
     * @return Response
123
     * @throws \Exception
124
     */
125
    public function beforeConsent(ServerRequestInterface $request, &$result = null): ?Response
126
    {
127
        /**
128
         * @var RegisteredClient $client
129
         */
130
        if ($res = $this->verify($request, $result)) {
131
            return $res;
132
        }
133
134
        /**
135
         * @var RegisteredClient $client
136
         * @var Uri $redirectUri
137
         * @var array $responseTypes
138
         * @var ResponseModeInterface $responseMode
139
         * @var array|null $scope
140
         * @var bool $isSecure
141
         * @var array $data
142
         */
143
        extract($result);
144
145
        // Si le consentement est requis, retourné array sauf si prompt=none
146
        // l'affichage du consentement peut utiliser le paramètre 'display'
147
148
        return null;
149
150
//        return new AuthorizationResponse($redirectUri);
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
151
    }
152
153
    /**
154
     * @param ServerRequestInterface $request
155
     * @param null $result
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $result is correct as it would always require null to be passed?
Loading history...
156
     * @return array|Response
157
     * @throws \Exception
158
     */
159
    protected function verify(ServerRequestInterface $request, &$result = null) : ?Response
160
    {
161
        try {
162
            // Validate all oauth2 parameters
163
            // Verify that scope parameter is present and contains openid scope. If not, use basic oauth2 authorization endpoint
164
            // Verify all open id parameters
165
            // check sub (subject) claim that is the current user authenticated
166
            // check user authenticated or re-authenticate him if prompt=login
167
            // no interaction if prompt=none => no consent required, check client has consent
168
169
            if ($request->getMethod() == 'GET') {
170
                $data = $request->getQueryParams();
171
            } else if ($request->getMethod() == 'POST') {
172
                $data = $request->getParsedBody();
173
            } else {
174
                throw new OAuthException('invalid_request', 'Support only HTTP GET and POST methods');
175
            }
176
177
            $scopes = isset($data['scope']) ? explode(' ', trim($data['scope'])) : null;
178
            $client = $this->checkClient($data);
179
            $redirectUri = $this->checkRedirectUri($client, $data, $scopes);
180
        } catch (OAuthException $e) {
181
            return new \OAuth2\EndpointMessages\Token\ErrorResponse($e->getError(), $e->getErrorDescription(), $e->getErrorUri());
182
        }
183
184
        try {
185
            // Oauth2
186
            $responseTypes = $this->checkResponseTypes($request, $data);
187
            $this->checkAuthorizedClient($client, $responseTypes);
188
            $isSecure = $this->checkRedirectionEndpointConfidentiality($client, $responseTypes, $redirectUri);
189
190
            $scope = $this->checkScope($client, $data);
191
192
//            if (!isset($data['scope']) || !$data['scope']) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
68% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
193
//                throw new OAuthException('invalid_request', 'Missing a required parameter : scope');
194
//            }
195
196
197
            $isStateRequired = $this->server->getConfigurationRepository()->getConfig(Config::ENFORCE_STATE);
198
            if ($isStateRequired && (!isset($data['state']) || !$data['state'])) {
199
                throw new OAuthException('invalid_request', 'Missing a required parameter : state');
200
            }
201
202
            $responseMode = $this->checkResponseMode($responseTypes, $data);
203
204
            $result = compact('client', 'redirectUri', 'responseTypes', 'data', 'scope', 'isSecure',
205
                'responseMode');
206
207
        } catch (OAuthException $e) {
208
            return new ErrorResponse($redirectUri,
209
                $e->getError(), $e->getErrorDescription(), $e->getErrorUri(),
210
                $data['state'] ?? null);
211
        }
212
213
        return null;
214
215
//        $requiredParameters = ['scope', 'response_type', 'client_id']
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
216
    }
217
218
    /**
219
     * @param ClientInterface $client
220
     * @param array $responseTypes
221
     * @throws OAuthException
222
     */
223
    protected function checkAuthorizedClient(ClientInterface $client, array $responseTypes)
224
    {
225
        $isImplicit = false;
226
        /**
227
         * @var OpenIDResponseTypeInterface $responseType
228
         */
229
        foreach ($responseTypes as $responseType) {
230
            if ($responseType->isImplicit()) {
231
                $isImplicit = true;
232
            }
233
        }
234
235
        if ($isImplicit && !$client->isImplicitAllowed()) {
236
            throw new OAuthException('unauthorized_client',
237
                'Client is not allowed to use implicit grant',
238
                'https://tools.ietf.org/html/rfc6749#section-3.1.1');
239
        }
240
    }
241
242
    /**
243
     * @param ClientInterface $client
244
     * @param array $data
245
     * @return array|null
246
     * @throws OAuthException
247
     */
248
    protected function checkScope(ClientInterface $client, array $data): ?array
249
    {
250
        $scopePolicyManager = $this->server->getScopePolicyManager();
251
252
        if (isset($data['scope'])) {
253
            $scope = explode(' ', $data['scope']);
254
        } else {
255
            $scope = $scopePolicyManager->getDefaultScopes($client);
256
        }
257
258
        if (!$scopePolicyManager->checkScope($client, $scope)) {
259
            $supportedScopes = implode(', ', $scopePolicyManager->getSupportedScopes($client));
260
            throw new OAuthException('invalid_scope',
261
                'Some of requested scopes are not supported. Scope supported : ' . $supportedScopes,
262
                'https://tools.ietf.org/html/rfc6749#section-4.1');
263
        }
264
265
        return $scope;
266
    }
267
268
    /**
269
     * @param ClientInterface $client
270
     * @param array $responseTypes
271
     * @param Uri $redirectUri
272
     * @return bool
273
     * @throws OAuthException
274
     * @throws \Exception
275
     */
276
    protected function checkRedirectionEndpointConfidentiality(ClientInterface $client, array $responseTypes, Uri $redirectUri): bool
277
    {
278
        $enforceTls = $this->server->getConfigurationRepository()->getConfig(Config::ENFORCE_TLS);
279
        if ($redirectUri->getScheme() === 'https') {
280
            return true;
281
        }
282
283
        foreach ($responseTypes as $responseType) {
284
            if ($responseType->requireTLS()) {
285
                if ($enforceTls === true || (is_null($enforceTls) && $client->isTLSSupported())) {
286
                    throw new OAuthException('access_denied',
287
                        'Require the use of TLS for the redirect URI',
288
                        'https://tools.ietf.org/html/rfc6749#section-3.1.2.1');
289
                }
290
                return false;
291
            }
292
        }
293
        return true;
294
    }
295
296
    /**
297
     * @param array $data
298
     * @return ClientInterface
299
     * @throws OAuthException
300
     * @throws \Exception
301
     */
302
    protected function checkClient(array $data): ClientInterface
303
    {
304
        if (!isset($data['client_id']) || !$data['client_id']) {
305
            throw new OAuthException('invalid_request', 'Missing a required parameter : client_id',
306
                'https://tools.ietf.org/html/rfc6749#section-4.1');
307
        }
308
309
        /**
310
         * @var ClientStorageInterface $clientStorage
311
         */
312
        $clientStorage = $this->server->getStorageRepository()->getStorage('client');
313
314
        $client = $clientStorage->get($data['client_id']);
315
        if (!$client) {
316
            throw new OAuthException('invalid_request', 'Invalid parameter : client_id',
317
                'https://tools.ietf.org/html/rfc6749#section-4.1');
318
        }
319
320
        if (!$client instanceof RegisteredClient) {
0 ignored issues
show
introduced by
The condition ! $client instanceof OAu...lients\RegisteredClient can never be true.
Loading history...
321
            throw new OAuthException('invalid_request', 'Client type is not supported',
322
                'https://tools.ietf.org/html/rfc6749#section-4.1');
323
        }
324
325
        return $client;
326
    }
327
328
    /**
329
     * @param ClientInterface $client
330
     * @param array $data
331
     * @param array|null $scopes
332
     * @return Uri
333
     * @throws OAuthException
334
     * @throws \Exception
335
     */
336
    protected function checkRedirectUri(ClientInterface $client, array $data, ?array $scopes): Uri
337
    {
338
        if (!isset($data['redirect_uri']) || !$data['redirect_uri']) {
339
            if (is_array($scopes) && in_array('openid', $scopes)) {
340
                throw new OAuthException('invalid_request', 'Missing a required parameter : redirect_uri');
341
            }
342
343
            if ($this->server->getConfigurationRepository()->getConfig(Config::ENFORCE_REDIRECT_URI)) {
344
                throw new OAuthException('invalid_request', 'A redirect URI must be supplied');
345
            }
346
            if (empty($client->getRedirectUris())) {
347
                throw new OAuthException('invalid_request', 'No redirect URI was supplied or stored');
348
            }
349
            if (count($client->getRedirectUris()) > 1) {
350
                throw new OAuthException('invalid_request', 'A redirect URI must be supplied when multiple redirect URIs are registered');
351
            }
352
353
            $redirectUri = new Uri($client->getRedirectUris()[0]);
354
        } else {
355
356
            $redirectUri = new Uri($data['redirect_uri']);
357
            if ($redirectUri->getFragment()) {
358
                throw new OAuthException('invalid_request', 'The redirect URI must not contain a fragment');
359
            }
360
361
            if (empty($client->getRedirectUris())) {
362
                if ($client->requireRedirectUri()) {
363
                    throw new OAuthException('invalid_request', 'The client must register a redirect URI');
364
                } else {
365
                    return $redirectUri;
366
                }
367
            }
368
369
            $redirectUriWithoutQuery = Uri::composeComponents(
370
                $redirectUri->getScheme(), $redirectUri->getAuthority(), $redirectUri->getPath(), '', '');
371
372
            $match = false;
373
            foreach ($client->getRedirectUris() as $registeredUri) {
374
                $registeredUri = new Uri($registeredUri);
375
                if ($registeredUri->getQuery()) {
376
                    if ($registeredUri->__toString() === $redirectUri->__toString()) {
377
                        $match = true;
378
                        break;
379
                    }
380
                } else {
381
                    if ($this->server->getConfigurationRepository()->getConfig(Config::STRICT_REDIRECT_URI_COMPARISON)) {
382
                        if ($registeredUri->__toString() === $redirectUri->__toString()) {
383
                            $match = true;
384
                            break;
385
                        }
386
                    } else if ($registeredUri->__toString() === $redirectUriWithoutQuery) {
387
                        $match = true;
388
                        break;
389
                    }
390
                }
391
            }
392
393
            if (!$match) {
394
                throw new OAuthException('invalid_request', 'The redirect URI provided is missing or does not match');
395
            }
396
        }
397
398
        return $redirectUri;
399
    }
400
401
    /**
402
     * @param ServerRequestInterface $request
403
     * @param array $data
404
     * @return array
405
     * @throws OAuthException
406
     */
407
    protected function checkResponseTypes(ServerRequestInterface $request, array $data): array
408
    {
409
        if (!isset($data['response_type']) || !$data['response_type']) {
410
            throw new OAuthException('invalid_request', 'Missing a required parameter : response_type');
411
        }
412
413
        $responseTypeNames = explode(' ', $data['response_type']);
414
        $responseTypes = [];
415
416
        foreach ($responseTypeNames as $responseTypeName) {
417
            $responseType = $this->server->getResponseTypeRepository()->getResponseType($responseTypeName);
418
            if (!$responseType) {
419
                throw new OAuthException('invalid_request',
420
                    'Unknown response_type : ' . $responseTypeName);
421
            }
422
423
            if (!$responseType->isMultiValuedResponseTypeSupported()) {
424
                throw new OAuthException('invalid_request',
425
                    'Multi-valued response_type not supported with response_type : ' . $responseTypeName);
426
            }
427
428
            $responseTypes[$responseTypeName] = $responseType;
429
        }
430
431
        if (empty($responseTypes)) {
432
            throw new OAuthException('invalid_request', 'Invalid response_type parameter');
433
        }
434
435
        foreach ($responseTypes as $responseType) {
436
            $responseType->verifyRequest($request);
437
        }
438
439
        return $responseTypes;
440
    }
441
442
    /**
443
     * @param array $responseTypes
444
     * @param array $data
445
     * @return ResponseModeInterface
446
     * @throws OAuthException
447
     * @throws \Exception
448
     */
449
    protected function checkResponseMode(array $responseTypes, array $data): ResponseModeInterface
450
    {
451
        $isQueryResponseModeSupported = true;
452
        /**
453
         * @var ResponseTypeInterface $responseType
454
         */
455
        foreach ($responseTypes as $responseType) {
456
            if (!$responseType->isQueryResponseModeSupported()) {
457
                $isQueryResponseModeSupported = false;
458
            }
459
        }
460
461
        $responseModeRepository = $this->server->getResponseModeRepository();
462
463
        if (count($responseTypes) == 1) {
464
//            $responseType = array_values($responseTypes)[0];
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
465
            $defaultResponseMode = $responseType->getDefaultResponseMode();
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $responseType seems to be defined by a foreach iteration on line 455. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
466
        } else {
467
            $defaultResponseMode = $responseModeRepository->getDefaultResponseModeForMultipleValuedResponseType();
468
        }
469
470
        $responseMode = $data['response_mode'] ?? $defaultResponseMode;
471
        if ($responseMode === ResponseModeInterface::RESPONSE_MODE_QUERY && !$isQueryResponseModeSupported) {
472
            throw new OAuthException('invalid_request',
473
                'Invalid response_mode parameter : query mode is not supported for this response_type');
474
        }
475
476
        if (!$responseMode = $responseModeRepository->getResponseMode($responseMode)) {
477
            throw new OAuthException('invalid_request', 'Unsupported response_mode.');
478
        }
479
480
        return $responseMode;
481
    }
482
483
    protected function popupResponse(array $data, Uri $redirectUri)
484
    {
485
        $message = json_encode($data);
486
        $targetOrigin = Uri::composeComponents(
487
            $redirectUri->getScheme(),
488
            $redirectUri->getAuthority(),
489
            $redirectUri->getPath(), '', '');
490
491
        $popup = <<<HTML
492
<!doctype html>
493
<html>
494
<head><title>Fermer cette fenêtre</title></head>
495
<body><pre>
496
Cette fenêtre devrait ce fermer automatiquement. 
497
Si ce n'est pas le cas cliquez sur ce bouton
498
</pre>
499
<button type="button" onclick="window.close();">Fermer cette fenêtre</button>
500
<script type="text/javascript">
501
  window.opener.postMessage({$message}, '{$targetOrigin}');
502
</script>
503
</html>
504
HTML;
505
        return new Response(200, [], $popup);
506
    }
507
}