Completed
Pull Request — master (#790)
by
unknown
30:53
created

AuthCodeGrant::validateAuthorizationRequest()   C

Complexity

Conditions 12
Paths 14

Size

Total Lines 78
Code Lines 51

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 78
rs 5.1746
cc 12
eloc 51
nc 14
nop 1

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
 * @author      Alex Bilbie <[email protected]>
4
 * @copyright   Copyright (c) Alex Bilbie
5
 * @license     http://mit-license.org/
6
 *
7
 * @link        https://github.com/thephpleague/oauth2-server
8
 */
9
10
namespace League\OAuth2\Server\Grant;
11
12
use League\OAuth2\Server\Entities\ClientEntityInterface;
13
use League\OAuth2\Server\Entities\ScopeEntityInterface;
14
use League\OAuth2\Server\Entities\UserEntityInterface;
15
use League\OAuth2\Server\Exception\OAuthServerException;
16
use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface;
17
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
18
use League\OAuth2\Server\RequestEvent;
19
use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
20
use League\OAuth2\Server\ResponseTypes\RedirectResponse;
21
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
22
use Psr\Http\Message\ServerRequestInterface;
23
24
class AuthCodeGrant extends AbstractAuthorizeGrant
25
{
26
    /**
27
     * @var \DateInterval
28
     */
29
    private $authCodeTTL;
30
31
    /**
32
     * @var bool
33
     */
34
    private $enableCodeExchangeProof = false;
35
36
    /**
37
     * @param AuthCodeRepositoryInterface     $authCodeRepository
38
     * @param RefreshTokenRepositoryInterface $refreshTokenRepository
39
     * @param \DateInterval                   $authCodeTTL
40
     */
41
    public function __construct(
42
        AuthCodeRepositoryInterface $authCodeRepository,
43
        RefreshTokenRepositoryInterface $refreshTokenRepository,
44
        \DateInterval $authCodeTTL
45
    ) {
46
        $this->setAuthCodeRepository($authCodeRepository);
47
        $this->setRefreshTokenRepository($refreshTokenRepository);
48
        $this->authCodeTTL = $authCodeTTL;
49
        $this->refreshTokenTTL = new \DateInterval('P1M');
50
    }
51
52
    public function enableCodeExchangeProof()
53
    {
54
        $this->enableCodeExchangeProof = true;
55
    }
56
57
    /**
58
     * Respond to an access token request.
59
     *
60
     * @param ServerRequestInterface $request
61
     * @param ResponseTypeInterface  $responseType
62
     * @param \DateInterval          $accessTokenTTL
63
     *
64
     * @throws OAuthServerException
65
     *
66
     * @return ResponseTypeInterface
67
     */
68
    public function respondToAccessTokenRequest(
69
        ServerRequestInterface $request,
70
        ResponseTypeInterface $responseType,
71
        \DateInterval $accessTokenTTL
72
    ) {
73
        // Validate request
74
        $client = $this->validateClient($request);
75
        $encryptedAuthCode = $this->getRequestParameter('code', $request, null);
76
77
        if ($encryptedAuthCode === null) {
78
            throw OAuthServerException::invalidRequest('code');
79
        }
80
81
        // Validate the authorization code
82
        try {
83
            $authCodePayload = json_decode($this->decrypt($encryptedAuthCode));
84
            if (time() > $authCodePayload->expire_time) {
85
                throw OAuthServerException::invalidRequest('code', 'Authorization code has expired');
86
            }
87
88
            if ($this->authCodeRepository->isAuthCodeRevoked($authCodePayload->auth_code_id) === true) {
89
                throw OAuthServerException::invalidRequest('code', 'Authorization code has been revoked');
90
            }
91
92
            if ($authCodePayload->client_id !== $client->getIdentifier()) {
93
                throw OAuthServerException::invalidRequest('code', 'Authorization code was not issued to this client');
94
            }
95
96
            // The redirect URI is required in this request
97
            $redirectUri = $this->getRequestParameter('redirect_uri', $request, null);
98
            if (empty($authCodePayload->redirect_uri) === false && $redirectUri === null) {
99
                throw OAuthServerException::invalidRequest('redirect_uri');
100
            }
101
102
            if ($authCodePayload->redirect_uri !== $redirectUri) {
103
                throw OAuthServerException::invalidRequest('redirect_uri', 'Invalid redirect URI');
104
            }
105
106
            $scopes = [];
107
            foreach ($authCodePayload->scopes as $scopeId) {
108
                $scope = $this->scopeRepository->getScopeEntityByIdentifier($scopeId);
109
110
                if ($scope instanceof ScopeEntityInterface === false) {
111
                    // @codeCoverageIgnoreStart
112
                    throw OAuthServerException::invalidScope($scopeId);
113
                    // @codeCoverageIgnoreEnd
114
                }
115
116
                $scopes[] = $scope;
117
            }
118
119
            // Finalize the requested scopes
120
            $scopes = $this->scopeRepository->finalizeScopes(
121
                $scopes,
122
                $this->getIdentifier(),
123
                $client,
124
                $authCodePayload->user_id
125
            );
126
        } catch (\LogicException  $e) {
127
            throw OAuthServerException::invalidRequest('code', 'Cannot decrypt the authorization code');
128
        }
129
130
        // Validate code challenge
131
        if ($this->enableCodeExchangeProof === true) {
132
            $codeVerifier = $this->getRequestParameter('code_verifier', $request, null);
133
            if ($codeVerifier === null) {
134
                throw OAuthServerException::invalidRequest('code_verifier');
135
            }
136
137
            switch ($authCodePayload->code_challenge_method) {
138
                case 'plain':
139
                    if (hash_equals($codeVerifier, $authCodePayload->code_challenge) === false) {
140
                        throw OAuthServerException::invalidGrant('Failed to verify `code_verifier`.');
141
                    }
142
143
                    break;
144
                case 'S256':
145
                    if (
146
                        hash_equals(
147
                            urlencode(base64_encode(hash('sha256', $codeVerifier))),
148
                            $authCodePayload->code_challenge
149
                        ) === false
150
                    ) {
151
                        throw OAuthServerException::invalidGrant('Failed to verify `code_verifier`.');
152
                    }
153
                    // @codeCoverageIgnoreStart
154
                    break;
155
                default:
156
                    throw OAuthServerException::serverError(
157
                        sprintf(
158
                            'Unsupported code challenge method `%s`',
159
                            $authCodePayload->code_challenge_method
160
                        )
161
                    );
162
                // @codeCoverageIgnoreEnd
163
            }
164
        }
165
166
        // Issue and persist access + refresh tokens
167
        $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $authCodePayload->user_id, $scopes);
168
        $refreshToken = $this->issueRefreshToken($accessToken);
0 ignored issues
show
Bug introduced by
It seems like $accessToken defined by $this->issueAccessToken(...load->user_id, $scopes) on line 167 can be null; however, League\OAuth2\Server\Gra...nt::issueRefreshToken() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
169
170
        // Inject tokens into response type
171
        $responseType->setAccessToken($accessToken);
0 ignored issues
show
Bug introduced by
It seems like $accessToken defined by $this->issueAccessToken(...load->user_id, $scopes) on line 167 can be null; however, League\OAuth2\Server\Res...rface::setAccessToken() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
172
        $responseType->setRefreshToken($refreshToken);
0 ignored issues
show
Bug introduced by
It seems like $refreshToken defined by $this->issueRefreshToken($accessToken) on line 168 can be null; however, League\OAuth2\Server\Res...face::setRefreshToken() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
173
174
        // Revoke used auth code
175
        $this->authCodeRepository->revokeAuthCode($authCodePayload->auth_code_id);
176
177
        return $responseType;
178
    }
179
180
    /**
181
     * Return the grant identifier that can be used in matching up requests.
182
     *
183
     * @return string
184
     */
185
    public function getIdentifier()
186
    {
187
        return 'authorization_code';
188
    }
189
190
    /**
191
     * {@inheritdoc}
192
     */
193
    public function canRespondToAuthorizationRequest(ServerRequestInterface $request)
194
    {
195
        return (
196
            array_key_exists('response_type', $request->getQueryParams())
197
            && $request->getQueryParams()['response_type'] === 'code'
198
            && isset($request->getQueryParams()['client_id'])
199
        );
200
    }
201
202
    /**
203
     * {@inheritdoc}
204
     */
205
    public function validateAuthorizationRequest(ServerRequestInterface $request)
206
    {
207
        $clientId = $this->getQueryStringParameter(
208
            'client_id',
209
            $request,
210
            $this->getServerParameter('PHP_AUTH_USER', $request)
211
        );
212
        if (is_null($clientId)) {
213
            throw OAuthServerException::invalidRequest('client_id');
214
        }
215
216
        $client = $this->clientRepository->getClientEntity(
217
            $clientId,
218
            $this->getIdentifier(),
219
            null,
220
            false
221
        );
222
223
        if ($client instanceof ClientEntityInterface === false) {
224
            $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
225
            throw OAuthServerException::invalidClient();
226
        }
227
228
        $redirectUri = $this->getQueryStringParameter('redirect_uri', $request);
229
        if ($redirectUri !== null) {
230
            if (
231
                is_string($client->getRedirectUri())
232
                && (strcmp($client->getRedirectUri(), $redirectUri) !== 0)
233
            ) {
234
                $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
235
                throw OAuthServerException::invalidClient();
236
            } elseif (
237
                is_array($client->getRedirectUri())
238
                && in_array($redirectUri, $client->getRedirectUri()) === false
239
            ) {
240
                $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
241
                throw OAuthServerException::invalidClient();
242
            }
243
        }
244
245
        $scopes = $this->validateScopes($this->getQueryStringParameter('scope', $request), $redirectUri);
246
247
        $stateParameter = $this->getQueryStringParameter('state', $request);
248
249
        $authorizationRequest = new AuthorizationRequest();
250
        $authorizationRequest->setGrantTypeId($this->getIdentifier());
251
        $authorizationRequest->setClient($client);
252
        $authorizationRequest->setRedirectUri($redirectUri);
253
        $authorizationRequest->setState($stateParameter);
254
        $authorizationRequest->setScopes($scopes);
255
256
        if ($this->enableCodeExchangeProof === true) {
257
            $codeChallenge = $this->getQueryStringParameter('code_challenge', $request);
258
            if ($codeChallenge === null) {
259
                throw OAuthServerException::invalidRequest('code_challenge');
260
            }
261
262
            if (preg_match('/^[A-Za-z0-9-._~]{43,128}$/', $codeChallenge) !== 1) {
263
                throw OAuthServerException::invalidRequest(
264
                    'code_challenge',
265
                    'The code_challenge must be between 43 and 128 characters'
266
                );
267
            }
268
269
            $codeChallengeMethod = $this->getQueryStringParameter('code_challenge_method', $request, 'plain');
270
            if (in_array($codeChallengeMethod, ['plain', 'S256']) === false) {
271
                throw OAuthServerException::invalidRequest(
272
                    'code_challenge_method',
273
                    'Code challenge method must be `plain` or `S256`'
274
                );
275
            }
276
277
            $authorizationRequest->setCodeChallenge($codeChallenge);
278
            $authorizationRequest->setCodeChallengeMethod($codeChallengeMethod);
279
        }
280
281
        return $authorizationRequest;
282
    }
283
284
    /**
285
     * {@inheritdoc}
286
     */
287
    public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest)
288
    {
289
        if ($authorizationRequest->getUser() instanceof UserEntityInterface === false) {
290
            throw new \LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest');
291
        }
292
293
        $finalRedirectUri = ($authorizationRequest->getRedirectUri() === null)
294
            ? is_array($authorizationRequest->getClient()->getRedirectUri())
295
                ? $authorizationRequest->getClient()->getRedirectUri()[0]
296
                : $authorizationRequest->getClient()->getRedirectUri()
297
            : $authorizationRequest->getRedirectUri();
298
299
        // The user approved the client, redirect them back with an auth code
300
        if ($authorizationRequest->isAuthorizationApproved() === true) {
301
            $authCode = $this->issueAuthCode(
302
                $this->authCodeTTL,
303
                $authorizationRequest->getClient(),
304
                $authorizationRequest->getUser()->getIdentifier(),
305
                $authorizationRequest->getRedirectUri(),
306
                $authorizationRequest->getScopes()
307
            );
308
309
            $payload = [
310
                'client_id'             => $authCode->getClient()->getIdentifier(),
311
                'redirect_uri'          => $authCode->getRedirectUri(),
312
                'auth_code_id'          => $authCode->getIdentifier(),
313
                'scopes'                => $authCode->getScopes(),
314
                'user_id'               => $authCode->getUserIdentifier(),
315
                'expire_time'           => (new \DateTime())->add($this->authCodeTTL)->format('U'),
316
                'code_challenge'        => $authorizationRequest->getCodeChallenge(),
317
                'code_challenge_method' => $authorizationRequest->getCodeChallengeMethod(),
318
            ];
319
320
            $response = new RedirectResponse();
321
            $response->setRedirectUri(
322
                $this->makeRedirectUri(
323
                    $finalRedirectUri,
324
                    [
325
                        'code'  => $this->encrypt(
326
                            json_encode(
327
                                $payload
328
                            )
329
                        ),
330
                        'state' => $authorizationRequest->getState(),
331
                    ]
332
                )
333
            );
334
335
            return $response;
336
        }
337
338
        // The user denied the client, redirect them back with an error
339
        throw OAuthServerException::accessDenied(
340
            'The user denied the request',
341
            $this->makeRedirectUri(
342
                $finalRedirectUri,
343
                [
344
                    'state' => $authorizationRequest->getState(),
345
                ]
346
            )
347
        );
348
    }
349
}
350