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

AuthorizationEndpoint::checkClientRedirectUri()   C

Complexity

Conditions 15
Paths 27

Size

Total Lines 52
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
eloc 37
nc 27
nop 2
dl 0
loc 52
rs 5.9385
c 0
b 0
f 0

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
 * Created by PhpStorm.
4
 * User: Alexandre
5
 * Date: 07/01/2018
6
 * Time: 13:57
7
 */
8
9
namespace OAuth2OLD\Endpoints;
10
11
12
use GuzzleHttp\Psr7\Request;
13
use GuzzleHttp\Psr7\Response;
14
use GuzzleHttp\Psr7\Uri;
15
use OAuth2OLD\Config;
16
use OAuth2OLD\EndpointMessages\Authorization\AuthorizationRequest;
17
use OAuth2OLD\EndpointMessages\Authorization\AuthorizationResponse;
18
use OAuth2OLD\EndpointMessages\Authorization\ErrorResponse;
19
use OAuth2OLD\Exceptions\OAuthException;
20
use OAuth2OLD\OpenID\ResponseModes\ResponseModeInterface;
21
use OAuth2OLD\ResponseTypes\ResponseTypeInterface;
22
use OAuth2OLD\Roles\ClientInterface;
23
use OAuth2OLD\Roles\Clients\RegisteredClient;
24
use OAuth2OLD\Roles\ResourceOwnerInterface;
25
use OAuth2OLD\Storages\ClientStorageInterface;
26
use Psr\Http\Message\ResponseInterface;
27
use Psr\Http\Message\ServerRequestInterface;
28
use Psr\Http\Message\UriInterface;
29
30
class AuthorizationEndpoint extends AbstractEndpoint
31
{
32
    /**
33
     * https://github.com/authbucket/oauth2-php/blob/master/src/GrantType/AuthorizationCodeGrantTypeHandler.php
34
     *
35
     *
36
     * @param ServerRequestInterface $request
37
     * @param ResourceOwnerInterface $resourceOwner
38
     * @return ResponseInterface
39
     * @throws \Exception
40
     */
41
    function handle(ServerRequestInterface $request, ResourceOwnerInterface $resourceOwner): ResponseInterface
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
42
    {
43
        /**
44
         * @var RegisteredClient $client
45
         */
46
        $result = $this->verify($request);
47
        if ($result instanceof Response) {
48
            return $result;
49
        }
50
51
        /**
52
         * @var RegisteredClient $client
53
         * @var array $responseTypes
54
         * @var Uri $redirectUri
55
         * @var array|null $scope
56
         * @var string|null $responseMode
57
         * @var bool $isInsecure
58
         */
59
        extract($result);
60
61
        $authorizationRequest = AuthorizationRequest::createFromServerRequest($request);
62
63
        try {
64
/*
0 ignored issues
show
Unused Code Comprehensibility introduced by
63% 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...
65
            if (!$authorizationDecision) {
66
                throw new OAuthException('access_denied',
67
                    'The resource owner server denied the request',
68
                    'https://tools.ietf.org/html/rfc6749#section-4.1.1');
69
            }
70
*/
71
//            $scope = $scopeRestrictedByResourceOwner ? $scopeRestrictedByResourceOwner : $scope;
0 ignored issues
show
Unused Code Comprehensibility introduced by
47% 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...
72
            $data = [];
73
74
            /**
75
             * @var ResponseTypeInterface $responseType
76
             * @var Uri $redirectUri
77
             */
78
            foreach ($responseTypes as $responseType) {
79
                $result = $responseType->handle($request, $resourceOwner, $client, $scope);
80
81
                $data = array_merge($data, $result);
82
            }
83
        } catch (OAuthException $e) {
84
            return new ErrorResponse($redirectUri, $e->getError(),
85
                $e->getErrorDescription(),
86
                $e->getErrorUri(),
87
                $authorizationRequest->getState());
88
        }
89
90
        if ($authorizationRequest->getState()) {
91
            $data['state'] = $authorizationRequest->getState();
92
        }
93
94
        if ($responseMode == ResponseModeInterface::RESPONSE_MODE_QUERY) {
95
            foreach ($data as $k => $v) {
96
                $redirectUri = Uri::withQueryValue($redirectUri, $k, $v);
97
            }
98
        } else {
99
            $redirectUri = $redirectUri->withFragment(http_build_query($data));
100
        }
101
102
        return new AuthorizationResponse($redirectUri);
103
    }
104
105
    public function beforeConsent(ServerRequestInterface $request, &$result = null): ?Response {
0 ignored issues
show
Unused Code introduced by
The parameter $result is not used and could be removed. ( Ignorable by Annotation )

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

105
    public function beforeConsent(ServerRequestInterface $request, /** @scrutinizer ignore-unused */ &$result = null): ?Response {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
106
        return null;
107
    }
108
109
    /**
110
     * Renvoie une réponse si une erreur survient, null sinon,  le client et la redirect_uri sont des parametres int/out
111
     * @param ServerRequestInterface $request
112
     * @return Response|array
113
     * @throws \Exception
114
     */
115
    protected function verify(ServerRequestInterface $request)
116
    {
117
        $authorizationRequest = AuthorizationRequest::createFromServerRequest($request);
118
119
        if (!$authorizationRequest->getClientId()) {
120
            return new Response(400, [], json_encode([
121
                'error' => 'invalid_request',
122
                'error_description' => 'Missing a required parameter : client_id',
123
                'error_uri' => 'https://tools.ietf.org/html/rfc6749#section-4.1',
124
            ]));
125
        }
126
127
        /**
128
         * @var ClientStorageInterface $clientStorage
129
         */
130
        $clientStorage = $this->server->getStorageRepository()->getStorage('client');
131
132
        $client = $clientStorage->get($authorizationRequest->getClientId());
133
        if (!$client) {
134
            return new Response(400, [], json_encode([
135
                'error' => 'invalid_request',
136
                'error_description' => 'Invalid parameter : client_id',
137
                'error_uri' => 'https://tools.ietf.org/html/rfc6749#section-4.1',
138
            ]));
139
        }
140
        if (!$client instanceof RegisteredClient) {
0 ignored issues
show
introduced by
The condition ! $client instanceof OAu...lients\RegisteredClient can never be true.
Loading history...
141
            return new Response(400, [], json_encode([
142
                'error' => 'invalid_request',
143
                'error_description' => 'Client type is not supported',
144
                'error_uri' => 'https://tools.ietf.org/html/rfc6749#section-4.1',
145
            ]));
146
        }
147
148
        try {
149
            $redirectUri = $this->checkClientRedirectUri($client, $authorizationRequest->getRedirectUri());
150
        } catch (\Exception $e) {
151
            return new Response(400, [], json_encode([
152
                'error' => 'invalid_request',
153
                'error_description' => $e->getMessage(),
154
                'error_uri' => 'https://tools.ietf.org/html/rfc6749#section-4.1',
155
            ]));
156
        }
157
158
        try {
159
            if (!$authorizationRequest->getResponseType()) {
160
                throw new OAuthException('invalid_request',
161
                    'Missing a required parameter : response_type',
162
                    'https://tools.ietf.org/html/rfc6749#section-4.1'
163
                );
164
            }
165
166
            if ($this->server->getConfigurationRepository()->getConfig(Config::ENFORCE_STATE) &&
167
                !$authorizationRequest->getState()) {
168
                throw new OAuthException('invalid_request',
169
                    'Missing a required parameter : state',
170
                    'https://tools.ietf.org/html/rfc6749#section-4.1'
171
                );
172
            }
173
174
            $enforceTls = $this->server->getConfigurationRepository()->getConfig(Config::ENFORCE_TLS);
175
            $responseMode = ResponseTypeInterface::RESPONSE_MODE_QUERY;
0 ignored issues
show
Bug introduced by
The constant OAuth2OLD\ResponseTypes\...ce::RESPONSE_MODE_QUERY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
176
            $isImplicit = false;
177
            $isInsecure = false;
178
            $responseTypes = [];
179
            foreach (explode(' ', $authorizationRequest->getResponseType()) as $responseTypeName) {
180
                $responseType = $this->server->getResponseTypeRepository()->getResponseType($responseTypeName);
181
                if (!$responseType) {
182
                    throw new OAuthException('unsupported_response_type',
183
                        'Invalid response_type parameter',
184
                        'https://tools.ietf.org/html/rfc6749#section-3.1.1');
185
                }
186
187
                if (($enforceTls == true && !$redirectUri->getScheme() != 'https') ||
188
                    (is_null($enforceTls) && $responseType->requireTLS() && !$redirectUri->getScheme() != 'https')) {
189
                    if (is_null($enforceTls) && !$client->isTLSSupported()) {
190
                        $isInsecure = true;
191
                    } else {
192
                        throw new OAuthException('access_denied',
193
                            'Require the use of TLS for the redirect URI',
194
                            'https://tools.ietf.org/html/rfc6749#section-3.1.2.1');
195
                    }
196
                }
197
198
                $responseType->verifyRequest($request);
199
                $responseTypes[] = $responseType;
200
201
                if ($responseType->getDefaultResponseMode() == ResponseTypeInterface::RESPONSE_MODE_FRAGMENT) {
0 ignored issues
show
Bug introduced by
The constant OAuth2OLD\ResponseTypes\...:RESPONSE_MODE_FRAGMENT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
202
                    $responseMode = $responseType->getDefaultResponseMode();
203
                }
204
205
                if ($responseType->isImplicit()) {
206
                    $isImplicit = true;
207
                }
208
            }
209
210
            if ($isImplicit && !$client->isImplicitAllowed()) {
211
                throw new OAuthException('unauthorized_client',
212
                    'Client is not allowed to use implicit grant',
213
                    'https://tools.ietf.org/html/rfc6749#section-3.1.1');
214
            }
215
216
            $scopePolicyManager = $this->server->getScopePolicyManager();
217
218
            $scope = $scopePolicyManager->getScopeArray($client, $authorizationRequest->getScope());
219
            if (!$scopePolicyManager->checkScope($client, $scope)) {
220
                $supportedScopes = implode(', ', $scopePolicyManager->getSupportedScopes($client));
221
                throw new OAuthException('invalid_scope',
222
                    'Some of requested scopes are not supported. Scope supported : ' . $supportedScopes,
223
                    'https://tools.ietf.org/html/rfc6749#section-4.1');
224
            }
225
226
        } catch (OAuthException $e) {
227
            return new ErrorResponse($redirectUri,
228
                $e->getError(), $e->getErrorDescription(), $e->getErrorUri(),
229
                $authorizationRequest->getState());
230
        }
231
232
        return compact('client', 'responseTypes', 'redirectUri', 'scope', 'responseMode', 'isInsecure');
233
    }
234
235
    /**
236
     * @see https://tools.ietf.org/html/rfc6749#section-3.1.2.3
237
     *
238
     * Dynamic Configuration
239
     *
240
     *     If multiple redirection URIs have been registered, if only part of
241
     * the redirection URI has been registered, or if no redirection URI has
242
     * been registered, the client MUST include a redirection URI with the
243
     * authorization request using the "redirect_uri" request parameter.
244
     *
245
     * When a redirection URI is included in an authorization request, the
246
     * authorization server MUST compare and match the value received
247
     * against at least one of the registered redirection URIs (or URI
248
     * components) as defined in [RFC3986] Section 6, if any redirection
249
     * URIs were registered.  If the client registration included the full
250
     * redirection URI, the authorization server MUST compare the two URIs
251
     * using simple string comparison as defined in [RFC3986] Section 6.2.1
252
     *
253
     * @param ClientInterface|RegisteredClient $client
254
     * @param null|string $redirectUri
255
     * @return UriInterface
256
     * @throws \Exception
257
     */
258
    private function checkClientRedirectUri(ClientInterface $client, ?string $redirectUri) : UriInterface
259
    {
260
        if (!$redirectUri) {
261
            if ($this->server->getConfigurationRepository()->getConfig(Config::ENFORCE_REDIRECT_URI)) {
262
                throw new \Exception('A redirect URI must be supplied');
263
            }
264
            if (empty($client->getRedirectUris())) {
265
                throw new \Exception('No redirect URI was supplied or stored');
266
            }
267
            if (count($client->getRedirectUris()) > 1) {
268
                throw new \Exception('A redirect URI must be supplied when multiple redirect URIs are registered');
269
            }
270
            $redirectUri = new Uri($client->getRedirectUris()[0]);
271
        } else {
272
            $redirectUri = new Uri($redirectUri);
273
            if ($redirectUri->getFragment()) {
274
                throw new \Exception('The redirect URI must not contain a fragment');
275
            }
276
            if (empty($client->getRedirectUris())) {
277
                if ($client->requireRedirectUri()) {
278
                    throw new \Exception('A redirect URI must be stored');
279
                } else {
280
                    return $redirectUri;
281
                }
282
            }
283
            $redirectUriWithoutQuery = Uri::composeComponents(
284
                $redirectUri->getScheme(), $redirectUri->getAuthority(), $redirectUri->getPath(), '', '');
285
            $match = false;
286
            foreach ($client->getRedirectUris() as $registeredUri) {
287
                $registeredUri = new Uri($registeredUri);
288
                if ($registeredUri->getQuery()) {
289
                    if ($registeredUri->__toString() === $redirectUri->__toString()) {
290
                        $match = true;
291
                        break;
292
                    }
293
                } else {
294
                    if ($this->server->getConfigurationRepository()->getConfig(Config::STRICT_REDIRECT_URI_COMPARISON)) {
295
                        if ($registeredUri->__toString() === $redirectUri->__toString()) {
296
                            $match = true;
297
                            break;
298
                        }
299
                    } else if ($registeredUri->__toString() === $redirectUriWithoutQuery) {
300
                        $match = true;
301
                        break;
302
                    }
303
                }
304
            }
305
            if (!$match) {
306
                throw new \Exception('The redirect URI provided is missing or does not match');
307
            }
308
        }
309
        return $redirectUri;
310
    }
311
}