Completed
Pull Request — master (#987)
by
unknown
04:24
created

AbstractGrant::getCookieParameter()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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