Completed
Push — master ( 6e52f0...d9a404 )
by Alexandre
02:29
created

AuthorizationEndpoint::beforeConsent()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 24
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 2
dl 0
loc 24
rs 8.9713
c 0
b 0
f 0
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: GCC-MED
5
 * Date: 19/01/2018
6
 * Time: 16:02
7
 */
8
9
namespace OAuth2OLD\OpenID\Endpoints;
10
11
12
use GuzzleHttp\Psr7\Response;
13
use GuzzleHttp\Psr7\Uri;
14
use OAuth2OLD\Config;
15
use OAuth2OLD\EndpointMessages\Authorization\ErrorResponse;
16
use OAuth2OLD\Exceptions\OAuthException;
17
use OAuth2OLD\OpenID\ResponseModes\ResponseModeInterface;
18
use OAuth2OLD\OpenID\ResponseTypes\ResponseTypeInterface as OpenIDResponseTypeInterface;
19
use OAuth2OLD\ResponseTypes\ResponseTypeInterface;
20
use OAuth2OLD\Roles\ClientInterface;
21
use OAuth2OLD\Roles\Clients\RegisteredClient;
22
use OAuth2OLD\Roles\ResourceOwnerInterface;
23
use OAuth2OLD\Storages\ClientStorageInterface;
24
use Psr\Http\Message\ResponseInterface;
25
use Psr\Http\Message\ServerRequestInterface;
26
27
class AuthorizationEndpoint extends \OAuth2OLD\Endpoints\AuthorizationEndpoint
28
{
29
    /**
30
     * @param ServerRequestInterface $request
31
     * @param ResourceOwnerInterface $resourceOwner
32
     * @return ResponseInterface
33
     * @throws \Exception
34
     */
35
    public function handle(ServerRequestInterface $request, ResourceOwnerInterface $resourceOwner): ResponseInterface
36
    {
37
        /**
38
         * @var RegisteredClient $client
39
         */
40
        if($res = $this->verify($request, $result)) {
41
            return $res;
42
        }
43
44
        /**
45
         * @var RegisteredClient $client
46
         * @var Uri $redirectUri
47
         * @var array $responseTypes
48
         * @var ResponseModeInterface $responseMode
49
         * @var array|null $scope
50
         * @var bool $isSecure
51
         * @var array $data
52
         */
53
        extract($result);
54
55
        $result = [];
56
57
        try {
58
            if(!$resourceOwner->isConsentGivenForClient($client)) {
59
                throw new OAuthException('access_denied',
60
                    'The resource owner server denied the request',
61
                    'https://tools.ietf.org/html/rfc6749#section-4.1.1');
62
            }
63
64
            $extendedResponseTypes = [];
65
66
            /**
67
             * @var ResponseTypeInterface $responseType
68
             */
69
            foreach ($responseTypes as $responseType) {
70
                if($responseType->getExtendedResponseTypes()) {
71
                    $extendedResponseTypes = array_merge($extendedResponseTypes, $responseType->getExtendedResponseTypes());
72
                }
73
                $responseTypeNames[] = $responseType->getResponseType();
74
            }
75
76
            /**
77
             * @var ResponseTypeInterface $responseType
78
             */
79
            foreach ($responseTypes as $responseType) {
80
                if(!in_array($responseType->getResponseType(), $extendedResponseTypes)) {
81
                    $extendedResponseTypes = null;
82
                    if ($responseType->getExtendedResponseTypes()) {
83
                        $extendedResponseTypeNames = array_intersect($responseType->getExtendedResponseTypes(), array_keys($responseTypes));
84
                        foreach ($extendedResponseTypeNames as $name) {
85
                            $extendedResponseTypes[$name] = $responseTypes[$name];
86
                        }
87
                    }
88
                    $result = array_merge($result, $responseType->handle($request, $resourceOwner, $client, $scope, $extendedResponseTypes));
89
                }
90
            }
91
//            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...
92
93
        } catch (OAuthException $e) {
94
            return new ErrorResponse($redirectUri, $e->getError(),
95
                $e->getErrorDescription(),
96
                $e->getErrorUri(),
97
                $data['state'] ?? null);
98
        }
99
100
        if(isset($data['state'])) {
101
            $result['state'] = $data['state'];
102
        }
103
104
        return $responseMode->handle($redirectUri, $result);
105
106
107
        // todo, repository for response mode
108
        // https://developer.okta.com/docs/api/resources/oidc#parameter-details
109
       /*
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...
110
        if (isset($data['response_mode']) && $data['response_mode'] == 'post_message') {
111
            return $this->popupResponse(['access_token' => 'a'], $redirectUri);
112
        } else {
113
            return new AuthorizationResponse($redirectUri);
114
        }
115
        */
116
    }
117
118
    /**
119
     * @param ServerRequestInterface $request
120
     * @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...
121
     * @return Response
122
     * @throws \Exception
123
     */
124
    public function beforeConsent(ServerRequestInterface $request, &$result = null): ?Response
125
    {
126
        /**
127
         * @var RegisteredClient $client
128
         */
129
        if ($res = $this->verify($request, $result)) {
130
            return $res;
131
        }
132
133
        /**
134
         * @var RegisteredClient $client
135
         * @var Uri $redirectUri
136
         * @var array $responseTypes
137
         * @var ResponseModeInterface $responseMode
138
         * @var array|null $scope
139
         * @var bool $isSecure
140
         * @var array $data
141
         */
142
        extract($result);
143
144
        // Si le consentement est requis, retourné array sauf si prompt=none
145
        // l'affichage du consentement peut utiliser le paramètre 'display'
146
147
        return null;
148
149
//        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...
150
    }
151
152
    /**
153
     * @param ServerRequestInterface $request
154
     * @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...
155
     * @return array|Response
156
     * @throws \Exception
157
     */
158
    protected function verify(ServerRequestInterface $request, &$result = null) : ?Response
159
    {
160
        try {
161
            // Validate all oauth2 parameters
162
            // Verify that scope parameter is present and contains openid scope. If not, use basic oauth2 authorization endpoint
163
            // Verify all open id parameters
164
            // check sub (subject) claim that is the current user authenticated
165
            // check user authenticated or re-authenticate him if prompt=login
166
            // no interaction if prompt=none => no consent required, check client has consent
167
168
            if ($request->getMethod() == 'GET') {
169
                $data = $request->getQueryParams();
170
            } else if ($request->getMethod() == 'POST') {
171
                $data = $request->getParsedBody();
172
            } else {
173
                throw new OAuthException('invalid_request', 'Support only HTTP GET and POST methods');
174
            }
175
176
            $scopes = isset($data['scope']) ? explode(' ', trim($data['scope'])) : null;
177
            $client = $this->checkClient($data);
178
            $redirectUri = $this->checkRedirectUri($client, $data, $scopes);
179
        } catch (OAuthException $e) {
180
            return new \OAuth2OLD\EndpointMessages\Token\ErrorResponse($e->getError(), $e->getErrorDescription(), $e->getErrorUri());
181
        }
182
183
        try {
184
            // Oauth2
185
            $responseTypes = $this->checkResponseTypes($request, $data);
186
            $this->checkAuthorizedClient($client, $responseTypes);
187
            $isSecure = $this->checkRedirectionEndpointConfidentiality($client, $responseTypes, $redirectUri);
188
189
            $scope = $this->checkScope($client, $data);
190
191
//            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...
192
//                throw new OAuthException('invalid_request', 'Missing a required parameter : scope');
193
//            }
194
195
196
            $isStateRequired = $this->server->getConfigurationRepository()->getConfig(Config::ENFORCE_STATE);
197
            if ($isStateRequired && (!isset($data['state']) || !$data['state'])) {
198
                throw new OAuthException('invalid_request', 'Missing a required parameter : state');
199
            }
200
201
            $responseMode = $this->checkResponseMode($responseTypes, $data);
202
203
            $result = compact('client', 'redirectUri', 'responseTypes', 'data', 'scope', 'isSecure',
204
                'responseMode');
205
206
        } catch (OAuthException $e) {
207
            return new ErrorResponse($redirectUri,
208
                $e->getError(), $e->getErrorDescription(), $e->getErrorUri(),
209
                $data['state'] ?? null);
210
        }
211
212
        return null;
213
214
//        $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...
215
    }
216
217
    /**
218
     * @param ClientInterface $client
219
     * @param array $responseTypes
220
     * @throws OAuthException
221
     */
222
    protected function checkAuthorizedClient(ClientInterface $client, array $responseTypes)
223
    {
224
        $isImplicit = false;
225
        /**
226
         * @var OpenIDResponseTypeInterface $responseType
227
         */
228
        foreach ($responseTypes as $responseType) {
229
            if ($responseType->isImplicit()) {
230
                $isImplicit = true;
231
            }
232
        }
233
234
        if ($isImplicit && !$client->isImplicitAllowed()) {
235
            throw new OAuthException('unauthorized_client',
236
                'Client is not allowed to use implicit grant',
237
                'https://tools.ietf.org/html/rfc6749#section-3.1.1');
238
        }
239
    }
240
241
    /**
242
     * @param ClientInterface $client
243
     * @param array $data
244
     * @return array|null
245
     * @throws OAuthException
246
     */
247
    protected function checkScope(ClientInterface $client, array $data): ?array
248
    {
249
        $scopePolicyManager = $this->server->getScopePolicyManager();
250
251
        if (isset($data['scope'])) {
252
            $scope = explode(' ', $data['scope']);
253
        } else {
254
            $scope = $scopePolicyManager->getDefaultScopes($client);
255
        }
256
257
        if (!$scopePolicyManager->checkScope($client, $scope)) {
258
            $supportedScopes = implode(', ', $scopePolicyManager->getSupportedScopes($client));
259
            throw new OAuthException('invalid_scope',
260
                'Some of requested scopes are not supported. Scope supported : ' . $supportedScopes,
261
                'https://tools.ietf.org/html/rfc6749#section-4.1');
262
        }
263
264
        return $scope;
265
    }
266
267
    /**
268
     * @param ClientInterface $client
269
     * @param array $responseTypes
270
     * @param Uri $redirectUri
271
     * @return bool
272
     * @throws OAuthException
273
     * @throws \Exception
274
     */
275
    protected function checkRedirectionEndpointConfidentiality(ClientInterface $client, array $responseTypes, Uri $redirectUri): bool
276
    {
277
        $enforceTls = $this->server->getConfigurationRepository()->getConfig(Config::ENFORCE_TLS);
278
        if ($redirectUri->getScheme() === 'https') {
279
            return true;
280
        }
281
282
        foreach ($responseTypes as $responseType) {
283
            if ($responseType->requireTLS()) {
284
                if ($enforceTls === true || (is_null($enforceTls) && $client->isTLSSupported())) {
285
                    throw new OAuthException('access_denied',
286
                        'Require the use of TLS for the redirect URI',
287
                        'https://tools.ietf.org/html/rfc6749#section-3.1.2.1');
288
                }
289
                return false;
290
            }
291
        }
292
        return true;
293
    }
294
295
    /**
296
     * @param array $data
297
     * @return ClientInterface
298
     * @throws OAuthException
299
     * @throws \Exception
300
     */
301
    protected function checkClient(array $data): ClientInterface
302
    {
303
        if (!isset($data['client_id']) || !$data['client_id']) {
304
            throw new OAuthException('invalid_request', 'Missing a required parameter : client_id',
305
                'https://tools.ietf.org/html/rfc6749#section-4.1');
306
        }
307
308
        /**
309
         * @var ClientStorageInterface $clientStorage
310
         */
311
        $clientStorage = $this->server->getStorageRepository()->getStorage('client');
312
313
        $client = $clientStorage->get($data['client_id']);
314
        if (!$client) {
315
            throw new OAuthException('invalid_request', 'Invalid parameter : client_id',
316
                'https://tools.ietf.org/html/rfc6749#section-4.1');
317
        }
318
319
        if (!$client instanceof RegisteredClient) {
0 ignored issues
show
introduced by
The condition ! $client instanceof OAu...lients\RegisteredClient can never be true.
Loading history...
320
            throw new OAuthException('invalid_request', 'Client type is not supported',
321
                'https://tools.ietf.org/html/rfc6749#section-4.1');
322
        }
323
324
        return $client;
325
    }
326
327
    /**
328
     * @param ClientInterface $client
329
     * @param array $data
330
     * @param array|null $scopes
331
     * @return Uri
332
     * @throws OAuthException
333
     * @throws \Exception
334
     */
335
    protected function checkRedirectUri(ClientInterface $client, array $data, ?array $scopes): Uri
336
    {
337
        if (!isset($data['redirect_uri']) || !$data['redirect_uri']) {
338
            if (is_array($scopes) && in_array('openid', $scopes)) {
339
                throw new OAuthException('invalid_request', 'Missing a required parameter : redirect_uri');
340
            }
341
342
            if ($this->server->getConfigurationRepository()->getConfig(Config::ENFORCE_REDIRECT_URI)) {
343
                throw new OAuthException('invalid_request', 'A redirect URI must be supplied');
344
            }
345
            if (empty($client->getRedirectUris())) {
346
                throw new OAuthException('invalid_request', 'No redirect URI was supplied or stored');
347
            }
348
            if (count($client->getRedirectUris()) > 1) {
349
                throw new OAuthException('invalid_request', 'A redirect URI must be supplied when multiple redirect URIs are registered');
350
            }
351
352
            $redirectUri = new Uri($client->getRedirectUris()[0]);
353
        } else {
354
355
            $redirectUri = new Uri($data['redirect_uri']);
356
            if ($redirectUri->getFragment()) {
357
                throw new OAuthException('invalid_request', 'The redirect URI must not contain a fragment');
358
            }
359
360
            if (empty($client->getRedirectUris())) {
361
                if ($client->requireRedirectUri()) {
362
                    throw new OAuthException('invalid_request', 'The client must register a redirect URI');
363
                } else {
364
                    return $redirectUri;
365
                }
366
            }
367
368
            $redirectUriWithoutQuery = Uri::composeComponents(
369
                $redirectUri->getScheme(), $redirectUri->getAuthority(), $redirectUri->getPath(), '', '');
370
371
            $match = false;
372
            foreach ($client->getRedirectUris() as $registeredUri) {
373
                $registeredUri = new Uri($registeredUri);
374
                if ($registeredUri->getQuery()) {
375
                    if ($registeredUri->__toString() === $redirectUri->__toString()) {
376
                        $match = true;
377
                        break;
378
                    }
379
                } else {
380
                    if ($this->server->getConfigurationRepository()->getConfig(Config::STRICT_REDIRECT_URI_COMPARISON)) {
381
                        if ($registeredUri->__toString() === $redirectUri->__toString()) {
382
                            $match = true;
383
                            break;
384
                        }
385
                    } else if ($registeredUri->__toString() === $redirectUriWithoutQuery) {
386
                        $match = true;
387
                        break;
388
                    }
389
                }
390
            }
391
392
            if (!$match) {
393
                throw new OAuthException('invalid_request', 'The redirect URI provided is missing or does not match');
394
            }
395
        }
396
397
        return $redirectUri;
398
    }
399
400
    /**
401
     * @param ServerRequestInterface $request
402
     * @param array $data
403
     * @return array
404
     * @throws OAuthException
405
     */
406
    protected function checkResponseTypes(ServerRequestInterface $request, array $data): array
407
    {
408
        if (!isset($data['response_type']) || !$data['response_type']) {
409
            throw new OAuthException('invalid_request', 'Missing a required parameter : response_type');
410
        }
411
412
        $responseTypeNames = explode(' ', $data['response_type']);
413
        $responseTypes = [];
414
415
        foreach ($responseTypeNames as $responseTypeName) {
416
            $responseType = $this->server->getResponseTypeRepository()->getResponseType($responseTypeName);
417
            if (!$responseType) {
418
                throw new OAuthException('invalid_request',
419
                    'Unknown response_type : ' . $responseTypeName);
420
            }
421
422
            if (!$responseType->isMultiValuedResponseTypeSupported()) {
423
                throw new OAuthException('invalid_request',
424
                    'Multi-valued response_type not supported with response_type : ' . $responseTypeName);
425
            }
426
427
            $responseTypes[$responseTypeName] = $responseType;
428
        }
429
430
        if (empty($responseTypes)) {
431
            throw new OAuthException('invalid_request', 'Invalid response_type parameter');
432
        }
433
434
        foreach ($responseTypes as $responseType) {
435
            $responseType->verifyRequest($request);
436
        }
437
438
        return $responseTypes;
439
    }
440
441
    /**
442
     * @param array $responseTypes
443
     * @param array $data
444
     * @return ResponseModeInterface
445
     * @throws OAuthException
446
     * @throws \Exception
447
     */
448
    protected function checkResponseMode(array $responseTypes, array $data): ResponseModeInterface
449
    {
450
        $isQueryResponseModeSupported = true;
451
        /**
452
         * @var ResponseTypeInterface $responseType
453
         */
454
        foreach ($responseTypes as $responseType) {
455
            if (!$responseType->isQueryResponseModeSupported()) {
456
                $isQueryResponseModeSupported = false;
457
            }
458
        }
459
460
        $responseModeRepository = $this->server->getResponseModeRepository();
461
462
        if (count($responseTypes) == 1) {
463
//            $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...
464
            $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 454. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
465
        } else {
466
            $defaultResponseMode = $responseModeRepository->getDefaultResponseModeForMultipleValuedResponseType();
467
        }
468
469
        $responseMode = $data['response_mode'] ?? $defaultResponseMode;
470
        if ($responseMode === ResponseModeInterface::RESPONSE_MODE_QUERY && !$isQueryResponseModeSupported) {
471
            throw new OAuthException('invalid_request',
472
                'Invalid response_mode parameter : query mode is not supported for this response_type');
473
        }
474
475
        if (!$responseMode = $responseModeRepository->getResponseMode($responseMode)) {
476
            throw new OAuthException('invalid_request', 'Unsupported response_mode.');
477
        }
478
479
        return $responseMode;
480
    }
481
482
    protected function popupResponse(array $data, Uri $redirectUri)
483
    {
484
        $message = json_encode($data);
485
        $targetOrigin = Uri::composeComponents(
486
            $redirectUri->getScheme(),
487
            $redirectUri->getAuthority(),
488
            $redirectUri->getPath(), '', '');
489
490
        $popup = <<<HTML
491
<!doctype html>
492
<html>
493
<head><title>Fermer cette fenêtre</title></head>
494
<body><pre>
495
Cette fenêtre devrait ce fermer automatiquement. 
496
Si ce n'est pas le cas cliquez sur ce bouton
497
</pre>
498
<button type="button" onclick="window.close();">Fermer cette fenêtre</button>
499
<script type="text/javascript">
500
  window.opener.postMessage({$message}, '{$targetOrigin}');
501
</script>
502
</html>
503
HTML;
504
        return new Response(200, [], $popup);
505
    }
506
}