Passed
Push — master ( 19fc27...8d3ce8 )
by Alexandre
02:16
created

AuthorizationEndpoint::getState()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: Alexandre
5
 * Date: 18/02/2018
6
 * Time: 18:14
7
 */
8
9
namespace OAuth2\Endpoints;
10
11
12
use GuzzleHttp\Psr7\Response;
13
use GuzzleHttp\Psr7\Uri;
14
use OAuth2\Exceptions\OAuthException;
15
use OAuth2\ResponseModes\ResponseModeInterface;
16
use OAuth2\ResponseModes\ResponseModeManager;
17
use OAuth2\ResponseTypes\ResponseTypeInterface;
18
use OAuth2\ResponseTypes\ResponseTypeManager;
19
use OAuth2\Roles\ClientInterface;
20
use OAuth2\Roles\Clients\RegisteredClient;
21
use OAuth2\Roles\ResourceOwnerInterface;
22
use OAuth2\ScopePolicy\ScopePolicyManager;
23
use OAuth2\Storages\ClientStorageInterface;
24
use Psr\Http\Message\ResponseInterface;
25
use Psr\Http\Message\ServerRequestInterface;
26
27
class AuthorizationEndpoint implements EndpointInterface
28
{
29
    /**
30
     * @var ResponseTypeManager
31
     */
32
    protected $responseTypeManager;
33
    /**
34
     * @var ResponseTypeInterface|null
35
     */
36
    private $responseType;
37
    /**
38
     * @var ResponseModeManager
39
     */
40
    protected $responseModeManager;
41
    /**
42
     * @var ScopePolicyManager
43
     */
44
    private $scopePolicyManager;
45
    /**
46
     * @var ResponseModeInterface|null
47
     */
48
    private $responseMode;
49
    /**
50
     * @var ResourceOwnerInterface
51
     */
52
    private $resourceOwner;
53
    /**
54
     * @var ClientStorageInterface
55
     */
56
    private $clientStorage;
57
    /**
58
     * @var RegisteredClient|null
59
     */
60
    private $client;
61
    /**
62
     * @var Uri|null
63
     */
64
    private $redirectUri;
65
    /**
66
     * @var string|null
67
     */
68
    private $state;
69
    /**
70
     * @var array|null
71
     */
72
    private $scopes;
73
    /**
74
     * @var array|null
75
     */
76
    private $requestData;
77
78
    public function __construct(ResponseTypeManager $responseTypeManager,
79
                                ResponseModeManager $responseModeManager,
80
                                ScopePolicyManager $scopePolicyManager,
81
                                ResourceOwnerInterface $resourceOwner,
82
                                ClientStorageInterface $clientStorage)
83
    {
84
        $this->responseTypeManager = $responseTypeManager;
85
        $this->responseModeManager = $responseModeManager;
86
        $this->scopePolicyManager = $scopePolicyManager;
87
        $this->resourceOwner = $resourceOwner;
88
        $this->clientStorage = $clientStorage;
89
    }
90
91
    public function verifyRequest(ServerRequestInterface $request): ?ResponseInterface {
92
93
        if($response = $this->parseRequestData($request)) {
94
            return $response;
95
        }
96
97
        try {
98
            $this->verifyClient($this->requestData['client_id'] ?? null);
99
            $this->verifyRedirectUri($this->requestData['redirect_uri'] ?? null);
100
101
        } catch (OAuthException $e) {
102
            return new Response(400, ['content-type' => 'application/json'], $e->jsonSerialize());
103
        }
104
105
        try {
106
            $this->verifyRequestData($this->requestData);
0 ignored issues
show
Bug introduced by
It seems like $this->requestData can also be of type null; however, parameter $requestData of OAuth2\Endpoints\Authori...nt::verifyRequestData() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

106
            $this->verifyRequestData(/** @scrutinizer ignore-type */ $this->requestData);
Loading history...
107
            $this->responseType->verifyAuthorizationRequest($this, $this->requestData);
0 ignored issues
show
Bug introduced by
The method verifyAuthorizationRequest() does not exist on null. ( Ignorable by Annotation )

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

107
            $this->responseType->/** @scrutinizer ignore-call */ 
108
                                 verifyAuthorizationRequest($this, $this->requestData);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
108
109
            // Authorization Server Authenticates End-User
110
            if($response = $this->verifyResourceOwner()) {
111
                return $response;
112
            }
113
        } catch (OAuthException $e) {
114
            /**
115
             * If the Authorization Server encounters any error, it MUST return an error response, per Section 3.1.2.6.
116
             */
117
            $responseData = [
118
                'error' => $e->getError()
119
            ];
120
            if ($e->getErrorDescription()) {
121
                $responseData['error_description'] = $e->getErrorDescription();
122
            }
123
            if ($e->getErrorUri()) {
124
                $responseData['error_uri'] = $e->getErrorUri();
125
            }
126
127
            if (!empty($this->state)) {
128
                $responseData['state'] = $this->state;
129
            }
130
131
            return $this->getResponseMode()->buildResponse($this, $this->requestData, $responseData);
132
        }
133
134
        return null;
135
    }
136
137
    public function handleRequest(ServerRequestInterface $request): ResponseInterface
138
    {
139
       if($response = $this->verifyRequest($request)) {
140
           return $response;
141
       }
142
143
        try {
144
            if($response = $this->verifyConsent($this->requestData)) {
0 ignored issues
show
Bug introduced by
It seems like $this->requestData can also be of type null; however, parameter $requestData of OAuth2\Endpoints\Authori...dpoint::verifyConsent() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

144
            if($response = $this->verifyConsent(/** @scrutinizer ignore-type */ $this->requestData)) {
Loading history...
145
                return $response;
146
            }
147
148
            $responseData = $this->getResponseType()->handleAuthorizationRequest($this, $this->requestData);
149
        } catch (OAuthException $e) {
150
            /**
151
             * If the Authorization Server encounters any error, it MUST return an error response, per Section 3.1.2.6.
152
             */
153
            $responseData = [
154
                'error' => $e->getError()
155
            ];
156
            if ($e->getErrorDescription()) {
157
                $responseData['error_description'] = $e->getErrorDescription();
158
            }
159
            if ($e->getErrorUri()) {
160
                $responseData['error_uri'] = $e->getErrorUri();
161
            }
162
        }
163
164
        if (!empty($this->state)) {
165
            $responseData['state'] = $this->state;
166
        }
167
168
        return $this->getResponseMode()->buildResponse($this, $this->requestData, $responseData);
169
    }
170
171
    protected function parseRequestData(ServerRequestInterface $request): ?Response {
172
        if ($request->getMethod() === 'GET') {
173
            $this->requestData = $request->getQueryParams();
174
        } else if ($request->getMethod() === 'POST') {
175
            $this->requestData = is_array($request->getParsedBody()) ? $request->getParsedBody() : [];
176
        } else {
177
            return new Response(404);
178
        }
179
        return null;
180
    }
181
182
    protected function verifyResourceOwner(): ?ResponseInterface {
183
        if (!$this->resourceOwner->isAuthenticated()) {
184
            return $this->resourceOwner->authenticate();
185
        }
186
        return null;
187
    }
188
189
    /**
190
     * @param array $requestData
191
     * @return null|ResponseInterface
192
     * @throws OAuthException
193
     */
194
    protected function verifyConsent(array $requestData): ?ResponseInterface {
195
        $consentGiven = $this->resourceOwner->hasGivenConsent($this->getClient(), $this->getScopes());
196
        if (is_null($consentGiven)) {
197
            return $this->resourceOwner->obtainConsent($this, $requestData);
198
        }
199
200
        if (empty($consentGiven)) {
201
            throw new OAuthException('access_denied', 'The resource owner denied the request.',
202
                'https://tools.ietf.org/html/rfc6749#section-4.1');
203
        }
204
205
        return null;
206
    }
207
208
    /**
209
     * @param null|string $clientId
210
     * @throws OAuthException
211
     */
212
    protected function verifyClient(?string $clientId = null) {
213
        if (empty($clientId)) {
214
            throw new OAuthException('invalid_request', 'The request is missing the required parameter client_id.',
215
                'https://tools.ietf.org/html/rfc6749#section-4.1');
216
        }
217
218
        if (!($client = $this->clientStorage->get($clientId))) {
219
            throw new OAuthException('invalid_request', 'The request includes the invalid parameter client_id.',
220
                'https://tools.ietf.org/html/rfc6749#section-4.1');
221
        }
222
        $this->client = $client;
223
    }
224
225
    /**
226
     * @param array $requestData
227
     * @throws OAuthException
228
     */
229
    protected function verifyRequestData(array $requestData)
230
    {
231
        // set the default response in case of invalid response type
232
233
        $this->responseMode = $this->responseModeManager->getDefaultResponseMode();
234
235
        // response_type required
236
        if (empty($requestData['response_type'])) {
237
            throw new OAuthException('invalid_request', 'The request is missing the required parameter response_type.',
238
                'https://tools.ietf.org/html/rfc6749#section-4.1');
239
        }
240
241
        if (!($responseType = $this->responseTypeManager->getResponseType($requestData['response_type']))) {
242
            throw new OAuthException('invalid_request', 'The request includes the invalid parameter response_type.',
243
                'https://tools.ietf.org/html/rfc6749#section-4.1');
244
        }
245
        $this->responseType = $responseType;
246
247
        $supportedResponseTypes = $this->client->getMetadata()->getResponseTypes() ?: ['code'];
0 ignored issues
show
Bug introduced by
The method getMetadata() does not exist on null. ( Ignorable by Annotation )

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

247
        $supportedResponseTypes = $this->client->/** @scrutinizer ignore-call */ getMetadata()->getResponseTypes() ?: ['code'];

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
248
        foreach (explode(' ', $requestData['response_type']) as $responseType) {
249
            if (!in_array($responseType, $supportedResponseTypes)) {
250
                throw new OAuthException('unsupported_response_type',
251
                    'The authorization server does not support obtaining an authorization code using this method.',
252
                    'https://tools.ietf.org/html/rfc6749#section-4.1');
253
            }
254
        }
255
256
        $this->verifyScope($requestData['scope'] ?? null);
257
258
        $this->state = $requestData['state'] ?? null;
259
260
        $responseModeIdentifier = $requestData['response_mode'] ?? $this->getResponseType()->getDefaultResponseMode();
261
        if (!($responseMode = $this->responseModeManager->getResponseMode($responseModeIdentifier))) {
262
            throw new OAuthException('invalid_request', 'response_mode invalid');
263
        }
264
265
        if (in_array($responseModeIdentifier, $this->getResponseType()->getUnsupportedResponseModes())) {
266
            throw new OAuthException('invalid_request', 'response_mode unsupported');
267
        }
268
269
        $this->responseMode = $responseMode;
270
    }
271
272
    /**
273
     * @param null|string $redirectUri
274
     * @throws OAuthException
275
     */
276
    protected function verifyRedirectUri(?string $redirectUri = null)
277
    {
278
        $redirectUris = $this->getClient()->getMetadata()->getRedirectUris();
279
        if(empty($redirectUris)) {
280
            throw new OAuthException('invalid_request',
281
                'Clients using flows with redirection MUST register their redirection URI values',
282
                'https://tools.ietf.org/html/rfc7591#section-2.1');
283
        }
284
285
        if ($redirectUri) {
286
            if (!in_array($redirectUri, $redirectUris)) {
287
                throw new OAuthException('invalid_request', 'The request includes the invalid parameter redirect_uri.',
288
                    'https://tools.ietf.org/html/rfc6749#section-4.1');
289
            }
290
        } else {
291
            if (count($redirectUris) == 1) {
292
                $redirectUri = $redirectUris[0];
293
            } else {
294
                throw new OAuthException('invalid_request', 'The request is missing the required parameter redirect_uri.',
295
                    'https://tools.ietf.org/html/rfc6749#section-4.1');
296
            }
297
        }
298
299
        try {
300
            $redirectUri = new Uri($redirectUri);
301
            if ($redirectUri->getFragment()) {
302
                throw new \InvalidArgumentException('The endpoint URI must not include a fragment component.');
303
            }
304
305
            $this->redirectUri = $redirectUri;
306
        } catch (\InvalidArgumentException $e) {
307
            throw new OAuthException('invalid_request', 'The request includes the malformed parameter redirect_uri. ' . $e->getMessage(),
308
                'https://tools.ietf.org/html/rfc6749#section-4.1');
309
        }
310
    }
311
312
    /**
313
     * @param null|string $scope
314
     * @throws OAuthException
315
     */
316
    protected function verifyScope(?string $scope = null)
317
    {
318
        $scopes = $this->scopePolicyManager->getScopes($this->getClient(), $scope);
319
        $this->scopePolicyManager->verifyScopes($this->getClient(), $scopes);
320
        $this->scopes = $scopes;
321
    }
322
323
    /**
324
     * @return null|ResponseTypeInterface
325
     */
326
    public function getResponseType(): ?ResponseTypeInterface
327
    {
328
        return $this->responseType;
329
    }
330
331
    /**
332
     * @return null|ResponseModeInterface
333
     */
334
    public function getResponseMode(): ?ResponseModeInterface
335
    {
336
        return $this->responseMode;
337
    }
338
339
    /**
340
     * @return null|RegisteredClient
341
     */
342
    public function getClient(): ?RegisteredClient
343
    {
344
        return $this->client;
345
    }
346
347
    /**
348
     * @return Uri|null
349
     */
350
    public function getRedirectUri(): ?Uri
351
    {
352
        return $this->redirectUri;
353
    }
354
355
    /**
356
     * @return null|string
357
     */
358
    public function getState(): ?string
359
    {
360
        return $this->state;
361
    }
362
363
    /**
364
     * @return null|array
365
     */
366
    public function getScopes(): ?array
367
    {
368
        return $this->scopes;
369
    }
370
371
    /**
372
     * @return null|array
373
     */
374
    public function getRequestData(): ?array
375
    {
376
        return $this->requestData;
377
    }
378
379
    /**
380
     * @return ResourceOwnerInterface
381
     */
382
    public function getResourceOwner(): ResourceOwnerInterface
383
    {
384
        return $this->resourceOwner;
385
    }
386
}