Completed
Push — master ( d9a404...9e6750 )
by Alexandre
02:12
created

AuthorizationEndpoint::verifyScope()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
ccs 1
cts 1
cp 1
crap 2
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\Flows\AuthorizationCodeFlow;
16
use OAuth2\Flows\FlowInterface;
17
use OAuth2\Flows\FlowManager;
18
use OAuth2\ResponseModes\ResponseModeInterface;
19
use OAuth2\ResponseModes\ResponseModeManager;
20
use OAuth2\ResponseTypes\ResponseTypeInterface;
21
use OAuth2\ResponseTypes\ResponseTypeManager;
22
use OAuth2\Roles\ClientInterface;
23
use OAuth2\Roles\Clients\RegisteredClient;
24
use OAuth2\Roles\ResourceOwnerInterface;
25
use OAuth2\ScopePolicy\ScopePolicyManager;
26
use OAuth2\Storages\ClientStorageInterface;
27
use Psr\Http\Message\ResponseInterface;
28
use Psr\Http\Message\ServerRequestInterface;
29
30
class AuthorizationEndpoint implements EndpointInterface
31
{
32
    /**
33
     * @var ResponseTypeManager
34
     */
35
    protected $responseTypeManager;
36
    /**
37
     * @var ResponseTypeInterface|null
38
     */
39
    private $responseType;
40
    /**
41 4
     * @var ResponseModeManager
42
     */
43
    protected $responseModeManager;
44
    /**
45
     * @var ScopePolicyManager
46
     */
47 4
    private $scopePolicyManager;
48 4
    /**
49 1
     * @var ResponseModeInterface|null
50
     */
51
    private $responseMode;
52
    /**
53
     * @var ResourceOwnerInterface
54
     */
55
    private $resourceOwner;
56
    /**
57
     * @var ClientStorageInterface
58
     */
59
    private $clientStorage;
60 3
    /**
61
     * @var RegisteredClient|null
62 3
     */
63
    private $client;
64
    /**
65
     * @var Uri|null
66 3
     */
67
    private $redirectUri;
68
    /**
69
     * @var mixed|null
70
     */
71
    private $state;
72 3
    /**
73 3
     * @var array|null
74
     */
75
    private $scopes;
76
77
    public function __construct(ResponseTypeManager $responseTypeManager,
78
                                ResponseModeManager $responseModeManager,
79 3
                                ScopePolicyManager $scopePolicyManager,
80 3
                                ResourceOwnerInterface $resourceOwner,
81
                                ClientStorageInterface $clientStorage)
82 3
    {
83
        $this->responseTypeManager = $responseTypeManager;
84
        $this->responseModeManager = $responseModeManager;
85
        $this->scopePolicyManager = $scopePolicyManager;
86
        $this->resourceOwner = $resourceOwner;
87
        $this->clientStorage = $clientStorage;
88
    }
89
90
    function handleRequest(ServerRequestInterface $request): 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...
91 3
    {
92
        $requestData = $request->getMethod() == 'GET' ? $request->getQueryParams() : $request->getParsedBody();
93
94
        try {
95 3
            // Authentication Request Validation
96 2
            // The Authorization Server MUST validate all the OAuth 2.0 parameters according to the OAuth 2.0 specification.
97 2
            $this->verifyRequestData($requestData);
0 ignored issues
show
Bug introduced by
It seems like $requestData can also be of type object and 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

97
            $this->verifyRequestData(/** @scrutinizer ignore-type */ $requestData);
Loading history...
98
99
        } catch (OAuthException $e) {
100 1
            /**
101
             * If the Authorization Server encounters any error, it MUST return an error response, per Section 3.1.2.6.
102
             */
103 3
104
            return new Response(400, ['content-type' => 'application/json'], $e->jsonSerialize());
105
        }
106
107
        // Authorization Server Authenticates End-User
108
        if (!$this->resourceOwner->isAuthenticated()) {
109
            return $this->resourceOwner->authenticate();
110
        }
111
112 4
        try {
113
            $consentGiven = $this->resourceOwner->hasGivenConsent($this->getClient(), $this->getScopes());
114 4
            if (is_null($consentGiven)) {
115
                return $this->resourceOwner->obtainConsent($this->getClient(), $this->getScopes());
116 4
            }
117
118
            if (!$consentGiven) {
119
                throw new OAuthException('access_denied', 'The resource owner denied the request.',
120
                    'https://tools.ietf.org/html/rfc6749#section-4.1');
121
            }
122
        } catch (OAuthException $e) {
123
            /**
124 4
             * If the Authorization Server encounters any error, it MUST return an error response, per Section 3.1.2.6.
125
             */
126
            $uri = $this->getRedirectUri();
127
            if(!empty($this->state)) {
128
                $uri = Uri::withQueryValue($uri, 'state', $this->state);
129
            }
130
            $uri = Uri::withQueryValue($uri, 'error', $e->getError());
131
            if($e->getErrorDescription()) {
132
                $uri = Uri::withQueryValue($uri, 'error_description', $e->getErrorDescription());
133
            }
134
            if($e->getErrorUri()) {
135 4
                $uri = Uri::withQueryValue($uri, 'error_uri', $e->getErrorUri());
136
            }
137 4
            return new Response(302, ['Location' => $uri]);
138 4
        }
139
140
        $responseData = $this->getResponseType()->handle($requestData);
141
        return $this->getResponseMode()->buildResponse($requestData, $responseData);
142
    }
143
144
145 4
    /**
146
     * @param array $requestData
147
     * @throws OAuthException
148
     */
149
    protected function verifyRequestData(array $requestData)
150
    {
151
        // response_type required
152
        if (empty($requestData['response_type'])) {
153
            throw new OAuthException('invalid_request', 'The request is missing the required parameter response_type.',
154 4
                'https://tools.ietf.org/html/rfc6749#section-4.1');
155
        }
156
157
        if (!($responseType = $this->responseTypeManager->getResponseType($requestData['response_type']))) {
158
            throw new OAuthException('invalid_request', 'The request includes the invalid parameter response_type.',
159
                'https://tools.ietf.org/html/rfc6749#section-4.1');
160
        }
161
        $this->responseType = $responseType;
162
163
        if (empty($requestData['client_id'])) {
164 4
            throw new OAuthException('invalid_request', 'The request is missing the required parameter client_id.',
165
                'https://tools.ietf.org/html/rfc6749#section-4.1');
166
        }
167
168
        if (!($client = $this->clientStorage->get($requestData['client_id']))) {
169
            throw new OAuthException('invalid_request', 'The request includes the invalid parameter client_id.',
170
                'https://tools.ietf.org/html/rfc6749#section-4.1');
171 4
        }
172 4
        $this->client = $client;
173
174
        $supportedResponseTypes = $this->client->getMetadata()->getResponseTypes() ?: ['code'];
175
        foreach (explode(' ', $requestData['response_type']) as $responseType) {
176
            if(!in_array($responseType, $supportedResponseTypes)) {
177
            throw new OAuthException('unsupported_response_type',
178
                'The authorization server does not support obtaining an authorization code using this method.',
179 4
                'https://tools.ietf.org/html/rfc6749#section-4.1');
180 4
            }
181 4
        }
182 4
183 4
        $this->verifyRedirectUri($requestData['redirect_uri'] ?? null);
184 4
185 4
        $this->verifyScope($requestData['scope'] ?? null);
186 4
187 1
        $this->state = $requestData['state'] ?? null;
188 1
189 1
        $responseModeIdentifier = $requestData['response_mode'] ?? $this->getResponseType()->getDefaultResponseMode();
190
        if (!($responseMode = $this->responseModeManager->getResponseMode($responseModeIdentifier))) {
191
            throw new OAuthException('invalid_request', 'response_mode invalid');
192 3
        }
193 3
194 3
        if (in_array($responseModeIdentifier, $this->getResponseType()->getUnsupportedResponseModes())) {
195 3
            throw new OAuthException('invalid_request', 'response_mode unsupported');
196
        }
197
        $this->responseMode = $responseMode;
198
    }
199
200
    /**
201
     * @param null|string $redirectUri
202
     * @throws OAuthException
203 3
     */
204 3
    protected function verifyRedirectUri(?string $redirectUri = null)
205
    {
206 3
        $redirectUris = $this->getClient()->getMetadata()->getRedirectUris();
207 1
        if ($redirectUri) {
208
            if (!in_array($redirectUri, $redirectUris)) {
209
                throw new OAuthException('invalid_request', 'The request includes the invalid parameter redirect_uri.',
210 3
                    'https://tools.ietf.org/html/rfc6749#section-4.1');
211 3
            }
212
        }
213
        else {
214
            if (count($redirectUris) == 1) {
215 3
               $redirectUri = $redirectUris[0];
216
            }
217
            else {
218
                throw new OAuthException('invalid_request', 'The request is missing the required parameter redirect_uri.',
219
                    'https://tools.ietf.org/html/rfc6749#section-4.1');
220
            }
221 3
        }
222
        try {
223 3
224 3
            $this->redirectUri = new Uri($redirectUri);
225
        }
226
        catch (\InvalidArgumentException $e) {
227
            throw new OAuthException('invalid_request', 'The request includes the malformed parameter redirect_uri.',
228 3
                'https://tools.ietf.org/html/rfc6749#section-4.1');
229
        }
230
    }
231 1
232 1
    /**
233 1
     * @param null|string $scope
234 1
     * @throws OAuthException
235
     */
236
    protected function verifyScope(?string $scope = null) {
237 3
        $scopes = $scope ? explode(' ', $scope) : $this->scopePolicyManager->getDefaultScopes($this->getClient());
238
        $this->scopePolicyManager->verifyScopes($this->getClient(), $scopes);
239
        $this->scopes = $scopes;
240
    }
241
242
    /**
243
     * @return null|ResponseTypeInterface
244
     */
245
    public function getResponseType(): ?ResponseTypeInterface
246
    {
247
        return $this->responseType;
248
    }
249
250
    /**
251
     * @return null|ResponseModeInterface
252
     */
253
    public function getResponseMode(): ?ResponseModeInterface
254
    {
255
        return $this->responseMode;
256
    }
257
258
    /**
259
     * @return null|RegisteredClient
260
     */
261
    public function getClient(): ?RegisteredClient
262
    {
263 4
        return $this->client;
264
    }
265 4
266
    /**
267
     * @return Uri|null
268
     */
269
    public function getRedirectUri(): ?Uri
270
    {
271
        return $this->redirectUri;
272
    }
273
274
    /**
275
     * @return null|mixed
276
     */
277 4
    public function getState()
278 4
    {
279
        return $this->state;
280
    }
281 4
282
    /**
283
     * @return null|array
284
     */
285
    public function getScopes(): ?array
286
    {
287
        return $this->scopes;
288
    }
289
}