Completed
Pull Request — master (#691)
by
unknown
34:49
created

AbstractGrant::validateAuthorizationRequest()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
c 0
b 0
f 0
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
/**
3
 * OAuth 2.0 Abstract grant.
4
 *
5
 * @author      Alex Bilbie <[email protected]>
6
 * @copyright   Copyright (c) Alex Bilbie
7
 * @license     http://mit-license.org/
8
 *
9
 * @link        https://github.com/thephpleague/oauth2-server
10
 */
11
namespace League\OAuth2\Server\Grant;
12
13
use League\Event\EmitterAwareTrait;
14
use League\OAuth2\Server\CryptTrait;
15
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
16
use League\OAuth2\Server\Entities\AuthCodeEntityInterface;
17
use League\OAuth2\Server\Entities\ClientEntityInterface;
18
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
19
use League\OAuth2\Server\Entities\ScopeEntityInterface;
20
use League\OAuth2\Server\Exception\OAuthServerException;
21
use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException;
22
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
23
use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface;
24
use League\OAuth2\Server\Repositories\ClientRepositoryInterface;
25
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
26
use League\OAuth2\Server\Repositories\ScopeRepositoryInterface;
27
use League\OAuth2\Server\Repositories\UserRepositoryInterface;
28
use League\OAuth2\Server\RequestEvent;
29
use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
30
use Psr\Http\Message\ServerRequestInterface;
31
32
/**
33
 * Abstract grant class.
34
 */
35
abstract class AbstractGrant implements GrantTypeInterface
36
{
37
    use EmitterAwareTrait, CryptTrait;
38
39
    const SCOPE_DELIMITER_STRING = ' ';
40
41
    const MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS = 10;
42
43
    /**
44
     * @var ClientRepositoryInterface
45
     */
46
    protected $clientRepository;
47
48
    /**
49
     * @var AccessTokenRepositoryInterface
50
     */
51
    protected $accessTokenRepository;
52
53
    /**
54
     * @var ScopeRepositoryInterface
55
     */
56
    protected $scopeRepository;
57
58
    /**
59
     * @var AuthCodeRepositoryInterface
60
     */
61
    protected $authCodeRepository;
62
63
    /**
64
     * @var RefreshTokenRepositoryInterface
65
     */
66
    protected $refreshTokenRepository;
67
68
    /**
69
     * @var UserRepositoryInterface
70
     */
71
    protected $userRepository;
72
73
    /**
74
     * @var \DateInterval
75
     */
76
    protected $refreshTokenTTL;
77
78
    /**
79
     * @param ClientRepositoryInterface $clientRepository
80
     */
81
    public function setClientRepository(ClientRepositoryInterface $clientRepository)
82
    {
83
        $this->clientRepository = $clientRepository;
84
    }
85
86
    /**
87
     * @param AccessTokenRepositoryInterface $accessTokenRepository
88
     */
89
    public function setAccessTokenRepository(AccessTokenRepositoryInterface $accessTokenRepository)
90
    {
91
        $this->accessTokenRepository = $accessTokenRepository;
92
    }
93
94
    /**
95
     * @param ScopeRepositoryInterface $scopeRepository
96
     */
97
    public function setScopeRepository(ScopeRepositoryInterface $scopeRepository)
98
    {
99
        $this->scopeRepository = $scopeRepository;
100
    }
101
102
    /**
103
     * @param RefreshTokenRepositoryInterface $refreshTokenRepository
104
     */
105
    public function setRefreshTokenRepository(RefreshTokenRepositoryInterface $refreshTokenRepository)
106
    {
107
        $this->refreshTokenRepository = $refreshTokenRepository;
108
    }
109
110
    /**
111
     * @param AuthCodeRepositoryInterface $authCodeRepository
112
     */
113
    public function setAuthCodeRepository(AuthCodeRepositoryInterface $authCodeRepository)
114
    {
115
        $this->authCodeRepository = $authCodeRepository;
116
    }
117
118
    /**
119
     * @param UserRepositoryInterface $userRepository
120
     */
121
    public function setUserRepository(UserRepositoryInterface $userRepository)
122
    {
123
        $this->userRepository = $userRepository;
124
    }
125
126
    /**
127
     * {@inheritdoc}
128
     */
129
    public function setRefreshTokenTTL(\DateInterval $refreshTokenTTL)
130
    {
131
        $this->refreshTokenTTL = $refreshTokenTTL;
132
    }
133
134
    /**
135
     * Validate the client.
136
     *
137
     * @param ServerRequestInterface $request
138
     *
139
     * @throws OAuthServerException
140
     *
141
     * @return ClientEntityInterface
142
     */
143
    protected function validateClient(ServerRequestInterface $request)
144
    {
145
        list($basicAuthUser, $basicAuthPassword) = $this->getBasicAuthCredentials($request);
146
147
        $clientId = $this->getRequestParameter('client_id', $request, $basicAuthUser);
148
        if (is_null($clientId)) {
149
            throw OAuthServerException::invalidRequest('client_id');
150
        }
151
152
        // If the client is confidential require the client secret
153
        $clientSecret = $this->getRequestParameter('client_secret', $request, $basicAuthPassword);
154
155
        $client = $this->clientRepository->getClientEntity(
156
            $clientId,
157
            $this->getIdentifier(),
158
            $clientSecret,
159
            true
160
        );
161
162
        if ($client instanceof ClientEntityInterface === false) {
163
            $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
164
            throw OAuthServerException::invalidClient();
165
        }
166
167
        // If a redirect URI is provided ensure it matches what is pre-registered
168
        $redirectUri = $this->getRequestParameter('redirect_uri', $request, null);
169
        if ($redirectUri !== null) {
170
            if (
171
                is_string($client->getRedirectUri())
172
                && (strcmp($client->getRedirectUri(), $redirectUri) !== 0)
173
            ) {
174
                $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
175
                throw OAuthServerException::invalidClient();
176
            } elseif (
177
                is_array($client->getRedirectUri())
178
                && in_array($redirectUri, $client->getRedirectUri()) === false
179
            ) {
180
                $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
181
                throw OAuthServerException::invalidClient();
182
            }
183
        }
184
185
        return $client;
186
    }
187
188
    /**
189
     * Validate scopes in the request.
190
     *
191
     * @param string $scopes
192
     * @param string $redirectUri
193
     *
194
     * @throws OAuthServerException
195
     *
196
     * @return ScopeEntityInterface[]
197
     */
198
    public function validateScopes(
199
        $scopes,
200
        $redirectUri = null
201
    ) {
202
        $scopesList = array_filter(
203
            explode(self::SCOPE_DELIMITER_STRING, trim($scopes)),
204
            function ($scope) {
205
                return !empty($scope);
206
            }
207
        );
208
209
        $scopes = [];
210
        foreach ($scopesList as $scopeItem) {
211
            $scope = $this->scopeRepository->getScopeEntityByIdentifier($scopeItem);
212
213
            if ($scope instanceof ScopeEntityInterface === false) {
214
                throw OAuthServerException::invalidScope($scopeItem, $redirectUri);
215
            }
216
217
            $scopes[] = $scope;
218
        }
219
220
        return $scopes;
221
    }
222
223
    /**
224
     * Retrieve request parameter.
225
     *
226
     * @param string                 $parameter
227
     * @param ServerRequestInterface $request
228
     * @param mixed                  $default
229
     *
230
     * @return null|string
231
     */
232
    protected function getRequestParameter($parameter, ServerRequestInterface $request, $default = null)
233
    {
234
        $requestParameters = (array) ($request->getParsedBody()) ? $request->getParsedBody() : $request->getQueryParams();
235
236
        return isset($requestParameters[$parameter]) ? $requestParameters[$parameter] : $default;
237
    }
238
239
    /**
240
     * Retrieve HTTP Basic Auth credentials with the Authorization header
241
     * of a request. First index of the returned array is the username,
242
     * second is the password (so list() will work). If the header does
243
     * not exist, or is otherwise an invalid HTTP Basic header, return
244
     * [null, null].
245
     *
246
     * @param ServerRequestInterface $request
247
     *
248
     * @return string[]|null[]
249
     */
250
    protected function getBasicAuthCredentials(ServerRequestInterface $request)
251
    {
252
        if (!$request->hasHeader('Authorization')) {
253
            return [null, null];
254
        }
255
256
        $header = $request->getHeader('Authorization')[0];
257
        if (strpos($header, 'Basic ') !== 0) {
258
            return [null, null];
259
        }
260
261
        if (!($decoded = base64_decode(substr($header, 6)))) {
262
            return [null, null];
263
        }
264
265
        if (strpos($decoded, ':') === false) {
266
            return [null, null]; // HTTP Basic header without colon isn't valid
267
        }
268
269
        return explode(':', $decoded, 2);
270
    }
271
272
    /**
273
     * Retrieve query string parameter.
274
     *
275
     * @param string                 $parameter
276
     * @param ServerRequestInterface $request
277
     * @param mixed                  $default
278
     *
279
     * @return null|string
280
     */
281
    protected function getQueryStringParameter($parameter, ServerRequestInterface $request, $default = null)
282
    {
283
        return isset($request->getQueryParams()[$parameter]) ? $request->getQueryParams()[$parameter] : $default;
284
    }
285
286
    /**
287
     * Retrieve cookie parameter.
288
     *
289
     * @param string                 $parameter
290
     * @param ServerRequestInterface $request
291
     * @param mixed                  $default
292
     *
293
     * @return null|string
294
     */
295
    protected function getCookieParameter($parameter, ServerRequestInterface $request, $default = null)
296
    {
297
        return isset($request->getCookieParams()[$parameter]) ? $request->getCookieParams()[$parameter] : $default;
298
    }
299
300
    /**
301
     * Retrieve server parameter.
302
     *
303
     * @param string                 $parameter
304
     * @param ServerRequestInterface $request
305
     * @param mixed                  $default
306
     *
307
     * @return null|string
308
     */
309
    protected function getServerParameter($parameter, ServerRequestInterface $request, $default = null)
310
    {
311
        return isset($request->getServerParams()[$parameter]) ? $request->getServerParams()[$parameter] : $default;
312
    }
313
314
    /**
315
     * Issue an access token.
316
     *
317
     * @param \DateInterval          $accessTokenTTL
318
     * @param ClientEntityInterface  $client
319
     * @param string                 $userIdentifier
320
     * @param ScopeEntityInterface[] $scopes
321
     *
322
     * @throws OAuthServerException
323
     * @throws UniqueTokenIdentifierConstraintViolationException
324
     *
325
     * @return AccessTokenEntityInterface
326
     */
327
    protected function issueAccessToken(
328
        \DateInterval $accessTokenTTL,
329
        ClientEntityInterface $client,
330
        $userIdentifier,
331
        array $scopes = []
332
    ) {
333
        $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
334
335
        $accessToken = $this->accessTokenRepository->getNewToken($client, $scopes, $userIdentifier);
336
        $accessToken->setClient($client);
337
        $accessToken->setUserIdentifier($userIdentifier);
338
        $accessToken->setExpiryDateTime((new \DateTime())->add($accessTokenTTL));
339
340
        foreach ($scopes as $scope) {
341
            $accessToken->addScope($scope);
342
        }
343
344
        while ($maxGenerationAttempts-- > 0) {
345
            $accessToken->setIdentifier($this->generateUniqueIdentifier());
346
            try {
347
                $this->accessTokenRepository->persistNewAccessToken($accessToken);
348
349
                return $accessToken;
350
            } catch (UniqueTokenIdentifierConstraintViolationException $e) {
351
                if ($maxGenerationAttempts === 0) {
352
                    throw $e;
353
                }
354
            }
355
        }
356
    }
357
358
    /**
359
     * Issue an auth code.
360
     *
361
     * @param \DateInterval          $authCodeTTL
362
     * @param ClientEntityInterface  $client
363
     * @param string                 $userIdentifier
364
     * @param string                 $redirectUri
365
     * @param ScopeEntityInterface[] $scopes
366
     *
367
     * @throws OAuthServerException
368
     * @throws UniqueTokenIdentifierConstraintViolationException
369
     *
370
     * @return AuthCodeEntityInterface
371
     */
372
    protected function issueAuthCode(
373
        \DateInterval $authCodeTTL,
374
        ClientEntityInterface $client,
375
        $userIdentifier,
376
        $redirectUri,
377
        array $scopes = []
378
    ) {
379
        $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
380
381
        $authCode = $this->authCodeRepository->getNewAuthCode();
382
        $authCode->setExpiryDateTime((new \DateTime())->add($authCodeTTL));
383
        $authCode->setClient($client);
384
        $authCode->setUserIdentifier($userIdentifier);
385
        $authCode->setRedirectUri($redirectUri);
386
387
        foreach ($scopes as $scope) {
388
            $authCode->addScope($scope);
389
        }
390
391
        while ($maxGenerationAttempts-- > 0) {
392
            $authCode->setIdentifier($this->generateUniqueIdentifier());
393
            try {
394
                $this->authCodeRepository->persistNewAuthCode($authCode);
395
396
                return $authCode;
397
            } catch (UniqueTokenIdentifierConstraintViolationException $e) {
398
                if ($maxGenerationAttempts === 0) {
399
                    throw $e;
400
                }
401
            }
402
        }
403
    }
404
405
    /**
406
     * @param AccessTokenEntityInterface $accessToken
407
     *
408
     * @throws OAuthServerException
409
     * @throws UniqueTokenIdentifierConstraintViolationException
410
     *
411
     * @return RefreshTokenEntityInterface
412
     */
413
    protected function issueRefreshToken(AccessTokenEntityInterface $accessToken)
414
    {
415
        $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
416
417
        $refreshToken = $this->refreshTokenRepository->getNewRefreshToken();
418
        $refreshToken->setExpiryDateTime((new \DateTime())->add($this->refreshTokenTTL));
419
        $refreshToken->setAccessToken($accessToken);
420
421
        while ($maxGenerationAttempts-- > 0) {
422
            $refreshToken->setIdentifier($this->generateUniqueIdentifier());
423
            try {
424
                $this->refreshTokenRepository->persistNewRefreshToken($refreshToken);
425
426
                return $refreshToken;
427
            } catch (UniqueTokenIdentifierConstraintViolationException $e) {
428
                if ($maxGenerationAttempts === 0) {
429
                    throw $e;
430
                }
431
            }
432
        }
433
    }
434
435
    /**
436
     * Generate a new unique identifier.
437
     *
438
     * @param int $length
439
     *
440
     * @throws OAuthServerException
441
     *
442
     * @return string
443
     */
444
    protected function generateUniqueIdentifier($length = 40)
445
    {
446
        try {
447
            return bin2hex(random_bytes($length));
448
            // @codeCoverageIgnoreStart
449
        } catch (\TypeError $e) {
450
            throw OAuthServerException::serverError('An unexpected error has occurred');
451
        } catch (\Error $e) {
452
            throw OAuthServerException::serverError('An unexpected error has occurred');
453
        } catch (\Exception $e) {
454
            // If you get this message, the CSPRNG failed hard.
455
            throw OAuthServerException::serverError('Could not generate a random string');
456
        }
457
        // @codeCoverageIgnoreEnd
458
    }
459
460
    /**
461
     * {@inheritdoc}
462
     */
463
    public function canRespondToAccessTokenRequest(ServerRequestInterface $request)
464
    {
465
        $requestParameters = (array) $request->getParsedBody();
466
467
        return (
468
            array_key_exists('grant_type', $requestParameters)
469
            && $requestParameters['grant_type'] === $this->getIdentifier()
470
        );
471
    }
472
473
    /**
474
     * {@inheritdoc}
475
     */
476
    public function canRespondToAuthorizationRequest(ServerRequestInterface $request)
477
    {
478
        return false;
479
    }
480
481
    /**
482
     * {@inheritdoc}
483
     */
484
    public function validateAuthorizationRequest(ServerRequestInterface $request)
485
    {
486
        throw new \LogicException('This grant cannot validate an authorization request');
487
    }
488
489
    /**
490
     * {@inheritdoc}
491
     */
492
    public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest)
493
    {
494
        throw new \LogicException('This grant cannot complete an authorization request');
495
    }
496
}
497