Completed
Pull Request — master (#987)
by
unknown
02:38
created

AbstractGrant::issueRefreshToken()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4.0072

Importance

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