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

AbstractGrant::setClientRepository()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
cc 1
nc 1
nop 1
crap 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 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
    /**
173
     * @param IdentifierGeneratorInterface $identifierGenerator
174
     */
175 28
    public function setIdentifierGenerator(IdentifierGeneratorInterface $identifierGenerator)
176
    {
177 28
        $this->identifierGenerator = $identifierGenerator;
178 28
    }
179
180
    /**
181
     * Validate the client.
182
     *
183
     * @param ServerRequestInterface $request
184
     *
185
     * @throws OAuthServerException
186
     *
187
     * @return ClientEntityInterface
188
     */
189 42
    protected function validateClient(ServerRequestInterface $request)
190
    {
191 42
        list($basicAuthUser, $basicAuthPassword) = $this->getBasicAuthCredentials($request);
192
193 42
        $clientId = $this->getRequestParameter('client_id', $request, $basicAuthUser);
194 42
        if ($clientId === null) {
195 1
            throw OAuthServerException::invalidRequest('client_id');
196
        }
197
198
        // If the client is confidential require the client secret
199 41
        $clientSecret = $this->getRequestParameter('client_secret', $request, $basicAuthPassword);
200
201 41
        $client = $this->clientRepository->getClientEntity(
202 41
            $clientId,
203 41
            $this->getIdentifier(),
204 41
            $clientSecret,
205 41
            true
206
        );
207
208 41
        if ($client instanceof ClientEntityInterface === false) {
209 4
            $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
210 4
            throw OAuthServerException::invalidClient();
211
        }
212
213 37
        $redirectUri = $this->getRequestParameter('redirect_uri', $request, null);
214
215 37
        if ($redirectUri !== null) {
216 20
            $this->validateRedirectUri($redirectUri, $client, $request);
217
        }
218
219 35
        return $client;
220
    }
221
222
    /**
223
     * Validate redirectUri from the request.
224
     * If a redirect URI is provided ensure it matches what is pre-registered
225
     *
226
     * @param string                 $redirectUri
227
     * @param ClientEntityInterface  $client
228
     * @param ServerRequestInterface $request
229
     *
230
     * @throws OAuthServerException
231
     */
232 34
    protected function validateRedirectUri(
233
        string $redirectUri,
234
        ClientEntityInterface $client,
235
        ServerRequestInterface $request
236
    ) {
237 34
        if (\is_string($client->getRedirectUri())
238 34
            && (strcmp($client->getRedirectUri(), $redirectUri) !== 0)
239
        ) {
240 3
            $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
241 3
            throw OAuthServerException::invalidClient();
242 31
        } elseif (\is_array($client->getRedirectUri())
243 31
            && \in_array($redirectUri, $client->getRedirectUri(), true) === false
244
        ) {
245 3
            $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
246 3
            throw OAuthServerException::invalidClient();
247
        }
248 28
    }
249
250
    /**
251
     * Validate scopes in the request.
252
     *
253
     * @param string|array $scopes
254
     * @param string       $redirectUri
255
     *
256
     * @throws OAuthServerException
257
     *
258
     * @return ScopeEntityInterface[]
259
     */
260 34
    public function validateScopes($scopes, $redirectUri = null)
261
    {
262 34
        if (!\is_array($scopes)) {
263 23
            $scopes = $this->convertScopesQueryStringToArray($scopes);
264
        }
265
266 34
        $validScopes = [];
267
268 34
        foreach ($scopes as $scopeItem) {
269 28
            $scope = $this->scopeRepository->getScopeEntityByIdentifier($scopeItem);
270
271 28
            if ($scope instanceof ScopeEntityInterface === false) {
272 1
                throw OAuthServerException::invalidScope($scopeItem, $redirectUri);
273
            }
274
275 27
            $validScopes[] = $scope;
276
        }
277
278 33
        return $validScopes;
279
    }
280
281
    /**
282
     * Converts a scopes query string to an array to easily iterate for validation.
283
     *
284
     * @param string $scopes
285
     *
286
     * @return array
287
     */
288
    private function convertScopesQueryStringToArray($scopes)
289
    {
290 23
        return array_filter(explode(self::SCOPE_DELIMITER_STRING, trim($scopes)), function ($scope) {
291 23
            return !empty($scope);
292 23
        });
293
    }
294
295
    /**
296
     * Retrieve request parameter.
297
     *
298
     * @param string                 $parameter
299
     * @param ServerRequestInterface $request
300
     * @param mixed                  $default
301
     *
302
     * @return null|string
303
     */
304 42
    protected function getRequestParameter($parameter, ServerRequestInterface $request, $default = null)
305
    {
306 42
        $requestParameters = (array) $request->getParsedBody();
307
308 42
        return $requestParameters[$parameter] ?? $default;
309
    }
310
311
    /**
312
     * Retrieve HTTP Basic Auth credentials with the Authorization header
313
     * of a request. First index of the returned array is the username,
314
     * second is the password (so list() will work). If the header does
315
     * not exist, or is otherwise an invalid HTTP Basic header, return
316
     * [null, null].
317
     *
318
     * @param ServerRequestInterface $request
319
     *
320
     * @return string[]|null[]
321
     */
322 47
    protected function getBasicAuthCredentials(ServerRequestInterface $request)
323
    {
324 47
        if (!$request->hasHeader('Authorization')) {
325 42
            return [null, null];
326
        }
327
328 5
        $header = $request->getHeader('Authorization')[0];
329 5
        if (strpos($header, 'Basic ') !== 0) {
330 1
            return [null, null];
331
        }
332
333 4
        if (!($decoded = base64_decode(substr($header, 6)))) {
334 1
            return [null, null];
335
        }
336
337 3
        if (strpos($decoded, ':') === false) {
338 1
            return [null, null]; // HTTP Basic header without colon isn't valid
339
        }
340
341 2
        return explode(':', $decoded, 2);
342
    }
343
344
    /**
345
     * Retrieve query string parameter.
346
     *
347
     * @param string                 $parameter
348
     * @param ServerRequestInterface $request
349
     * @param mixed                  $default
350
     *
351
     * @return null|string
352
     */
353 21
    protected function getQueryStringParameter($parameter, ServerRequestInterface $request, $default = null)
354
    {
355 21
        return isset($request->getQueryParams()[$parameter]) ? $request->getQueryParams()[$parameter] : $default;
356
    }
357
358
    /**
359
     * Retrieve cookie parameter.
360
     *
361
     * @param string                 $parameter
362
     * @param ServerRequestInterface $request
363
     * @param mixed                  $default
364
     *
365
     * @return null|string
366
     */
367 1
    protected function getCookieParameter($parameter, ServerRequestInterface $request, $default = null)
368
    {
369 1
        return isset($request->getCookieParams()[$parameter]) ? $request->getCookieParams()[$parameter] : $default;
370
    }
371
372
    /**
373
     * Retrieve server parameter.
374
     *
375
     * @param string                 $parameter
376
     * @param ServerRequestInterface $request
377
     * @param mixed                  $default
378
     *
379
     * @return null|string
380
     */
381 20
    protected function getServerParameter($parameter, ServerRequestInterface $request, $default = null)
382
    {
383 20
        return isset($request->getServerParams()[$parameter]) ? $request->getServerParams()[$parameter] : $default;
384
    }
385
386
    /**
387
     * Issue an access token.
388
     *
389
     * @param DateInterval           $accessTokenTTL
390
     * @param ClientEntityInterface  $client
391
     * @param string|null            $userIdentifier
392
     * @param ScopeEntityInterface[] $scopes
393
     *
394
     * @throws OAuthServerException
395
     * @throws UniqueTokenIdentifierConstraintViolationException
396
     *
397
     * @return AccessTokenEntityInterface
398
     */
399 17
    protected function issueAccessToken(
400
        DateInterval $accessTokenTTL,
401
        ClientEntityInterface $client,
402
        $userIdentifier,
403
        array $scopes = []
404
    ) {
405 17
        $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
406
407 17
        $accessToken = $this->accessTokenRepository->getNewToken($client, $scopes, $userIdentifier);
408 17
        $accessToken->setClient($client);
409 17
        $accessToken->setUserIdentifier($userIdentifier);
410 17
        $accessToken->setExpiryDateTime((new DateTime())->add($accessTokenTTL));
411
412 17
        foreach ($scopes as $scope) {
413 13
            $accessToken->addScope($scope);
414
        }
415
416 17
        while ($maxGenerationAttempts-- > 0) {
417 17
            $accessToken->setIdentifier($this->identifierGenerator->generateUniqueIdentifier());
418
            try {
419 17
                $this->accessTokenRepository->persistNewAccessToken($accessToken);
420
421 15
                return $accessToken;
422 2
            } catch (UniqueTokenIdentifierConstraintViolationException $e) {
423 1
                if ($maxGenerationAttempts === 0) {
424 1
                    throw $e;
425
                }
426
            }
427
        }
428
    }
429
430
    /**
431
     * Issue an auth code.
432
     *
433
     * @param DateInterval           $authCodeTTL
434
     * @param ClientEntityInterface  $client
435
     * @param string                 $userIdentifier
436
     * @param string|null            $redirectUri
437
     * @param ScopeEntityInterface[] $scopes
438
     *
439
     * @throws OAuthServerException
440
     * @throws UniqueTokenIdentifierConstraintViolationException
441
     *
442
     * @return AuthCodeEntityInterface
443
     */
444 6
    protected function issueAuthCode(
445
        DateInterval $authCodeTTL,
446
        ClientEntityInterface $client,
447
        $userIdentifier,
448
        $redirectUri,
449
        array $scopes = []
450
    ) {
451 6
        $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
452
453 6
        $authCode = $this->authCodeRepository->getNewAuthCode();
454 6
        $authCode->setExpiryDateTime((new DateTime())->add($authCodeTTL));
455 6
        $authCode->setClient($client);
456 6
        $authCode->setUserIdentifier($userIdentifier);
457
458 6
        if ($redirectUri !== null) {
459 1
            $authCode->setRedirectUri($redirectUri);
460
        }
461
462 6
        foreach ($scopes as $scope) {
463 1
            $authCode->addScope($scope);
464
        }
465
466 6
        while ($maxGenerationAttempts-- > 0) {
467 6
            $authCode->setIdentifier($this->identifierGenerator->generateUniqueIdentifier());
468
            try {
469 6
                $this->authCodeRepository->persistNewAuthCode($authCode);
470
471 4
                return $authCode;
472 2
            } catch (UniqueTokenIdentifierConstraintViolationException $e) {
473 1
                if ($maxGenerationAttempts === 0) {
474 1
                    throw $e;
475
                }
476
            }
477
        }
478
    }
479
480
    /**
481
     * @param AccessTokenEntityInterface $accessToken
482
     *
483
     * @throws OAuthServerException
484
     * @throws UniqueTokenIdentifierConstraintViolationException
485
     *
486
     * @return RefreshTokenEntityInterface
487
     */
488 10
    protected function issueRefreshToken(AccessTokenEntityInterface $accessToken)
489
    {
490 10
        $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
491
492 10
        $refreshToken = $this->refreshTokenRepository->getNewRefreshToken();
493 10
        $refreshToken->setExpiryDateTime((new DateTime())->add($this->refreshTokenTTL));
494 10
        $refreshToken->setAccessToken($accessToken);
495
496 10
        while ($maxGenerationAttempts-- > 0) {
497 10
            $refreshToken->setIdentifier($this->identifierGenerator->generateUniqueIdentifier());
498
            try {
499 10
                $this->refreshTokenRepository->persistNewRefreshToken($refreshToken);
500
501 8
                return $refreshToken;
502 2
            } catch (UniqueTokenIdentifierConstraintViolationException $e) {
503 1
                if ($maxGenerationAttempts === 0) {
504 1
                    throw $e;
505
                }
506
            }
507
        }
508
    }
509
510
    /**
511
     * {@inheritdoc}
512
     */
513 5
    public function canRespondToAccessTokenRequest(ServerRequestInterface $request)
514
    {
515 5
        $requestParameters = (array) $request->getParsedBody();
516
517
        return (
518 5
            array_key_exists('grant_type', $requestParameters)
519 5
            && $requestParameters['grant_type'] === $this->getIdentifier()
520
        );
521
    }
522
523
    /**
524
     * {@inheritdoc}
525
     */
526 1
    public function canRespondToAuthorizationRequest(ServerRequestInterface $request)
527
    {
528 1
        return false;
529
    }
530
531
    /**
532
     * {@inheritdoc}
533
     */
534 1
    public function validateAuthorizationRequest(ServerRequestInterface $request)
535
    {
536 1
        throw new LogicException('This grant cannot validate an authorization request');
537
    }
538
539
    /**
540
     * {@inheritdoc}
541
     */
542 1
    public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest)
543
    {
544 1
        throw new LogicException('This grant cannot complete an authorization request');
545
    }
546
}
547