Completed
Pull Request — master (#887)
by
unknown
02:07
created

AbstractGrant::getRequestParameter()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

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