Passed
Pull Request — master (#1135)
by
unknown
05:41 queued 04:09
created

AbstractGrant   A

Complexity

Total Complexity 39

Size/Duplication

Total Lines 364
Duplicated Lines 0 %

Test Coverage

Coverage 95%

Importance

Changes 9
Bugs 0 Features 0
Metric Value
eloc 92
c 9
b 0
f 0
dl 0
loc 364
ccs 95
cts 100
cp 0.95
rs 9.28
wmc 39

20 Methods

Rating   Name   Duplication   Size   Complexity  
A setScopeRepository() 0 3 1
A setAccessTokenRepository() 0 3 1
A setClientRepository() 0 3 1
A canRespondToAuthorizationRequest() 0 3 1
A setPrivateKey() 0 3 1
A canRespondToAccessTokenRequest() 0 7 2
A setRefreshTokenRepository() 0 3 1
A getClientRepository() 0 3 1
A issueRefreshToken() 0 22 5
A issueAuthCode() 0 31 6
A completeAuthorizationRequest() 0 3 1
A issueAccessToken() 0 21 4
A setUserRepository() 0 3 1
A setRefreshTokenTTL() 0 3 1
A setDefaultScope() 0 3 1
A generateUniqueIdentifier() 0 12 4
A setAuthCodeRepository() 0 3 1
A validateScopes() 0 19 4
A convertScopesQueryStringToArray() 0 4 1
A validateAuthorizationRequest() 0 3 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 DateTimeImmutable;
15
use Error;
16
use Exception;
17
use League\Event\EmitterAwareTrait;
18
use League\OAuth2\Server\CryptKey;
19
use League\OAuth2\Server\CryptTrait;
20
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
21
use League\OAuth2\Server\Entities\AuthCodeEntityInterface;
22
use League\OAuth2\Server\Entities\ClientEntityInterface;
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\RequestTypes\AuthorizationRequest;
34
use League\OAuth2\Server\RequestValidatorTrait;
35
use LogicException;
36
use Psr\Http\Message\ServerRequestInterface;
37
use TypeError;
38
39
/**
40
 * Abstract grant class.
41
 */
42
abstract class AbstractGrant implements GrantTypeInterface
43
{
44
    use EmitterAwareTrait, CryptTrait, RequestValidatorTrait;
45
46
    const SCOPE_DELIMITER_STRING = ' ';
47
48
    const MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS = 10;
49
50
    /**
51
     * @var ClientRepositoryInterface
52
     */
53
    protected $clientRepository;
54
55
    /**
56
     * @var AccessTokenRepositoryInterface
57
     */
58
    protected $accessTokenRepository;
59
60
    /**
61
     * @var ScopeRepositoryInterface
62
     */
63
    protected $scopeRepository;
64
65
    /**
66
     * @var AuthCodeRepositoryInterface
67
     */
68
    protected $authCodeRepository;
69
70
    /**
71
     * @var RefreshTokenRepositoryInterface
72
     */
73
    protected $refreshTokenRepository;
74
75
    /**
76
     * @var UserRepositoryInterface
77
     */
78
    protected $userRepository;
79
80
    /**
81
     * @var DateInterval
82
     */
83
    protected $refreshTokenTTL;
84
85
    /**
86
     * @var CryptKey
87
     */
88
    protected $privateKey;
89
90
    /**
91
     * @var string
92
     */
93
    protected $defaultScope;
94
95
    /**
96
     * @return ClientRepositoryInterface
97
     */
98
    public function getClientRepository()
99
    {
100
        return $this->clientRepository;
101
    }
102
103
    /**
104
     * @param ClientRepositoryInterface $clientRepository
105
     */
106 75
    public function setClientRepository(ClientRepositoryInterface $clientRepository)
107
    {
108 75
        $this->clientRepository = $clientRepository;
109 75
    }
110
111
    /**
112
     * @param AccessTokenRepositoryInterface $accessTokenRepository
113
     */
114 46
    public function setAccessTokenRepository(AccessTokenRepositoryInterface $accessTokenRepository)
115
    {
116 46
        $this->accessTokenRepository = $accessTokenRepository;
117 46
    }
118
119
    /**
120
     * @param ScopeRepositoryInterface $scopeRepository
121
     */
122 45
    public function setScopeRepository(ScopeRepositoryInterface $scopeRepository)
123
    {
124 45
        $this->scopeRepository = $scopeRepository;
125 45
    }
126
127
    /**
128
     * @param RefreshTokenRepositoryInterface $refreshTokenRepository
129
     */
130 66
    public function setRefreshTokenRepository(RefreshTokenRepositoryInterface $refreshTokenRepository)
131
    {
132 66
        $this->refreshTokenRepository = $refreshTokenRepository;
133 66
    }
134
135
    /**
136
     * @param AuthCodeRepositoryInterface $authCodeRepository
137
     */
138 49
    public function setAuthCodeRepository(AuthCodeRepositoryInterface $authCodeRepository)
139
    {
140 49
        $this->authCodeRepository = $authCodeRepository;
141 49
    }
142
143
    /**
144
     * @param UserRepositoryInterface $userRepository
145
     */
146 6
    public function setUserRepository(UserRepositoryInterface $userRepository)
147
    {
148 6
        $this->userRepository = $userRepository;
149 6
    }
150
151
    /**
152
     * {@inheritdoc}
153
     */
154 2
    public function setRefreshTokenTTL(DateInterval $refreshTokenTTL)
155
    {
156 2
        $this->refreshTokenTTL = $refreshTokenTTL;
157 2
    }
158
159
    /**
160
     * Set the private key
161
     *
162
     * @param CryptKey $key
163
     */
164 35
    public function setPrivateKey(CryptKey $key)
165
    {
166 35
        $this->privateKey = $key;
167 35
    }
168
169
    /**
170
     * @param string $scope
171
     */
172 20
    public function setDefaultScope($scope)
173
    {
174 20
        $this->defaultScope = $scope;
175 20
    }
176
177
    /**
178
     * Validate scopes in the request.
179
     *
180
     * @param string|array $scopes
181
     * @param string       $redirectUri
182
     *
183
     * @throws OAuthServerException
184
     *
185
     * @return ScopeEntityInterface[]
186
     */
187 39
    public function validateScopes($scopes, $redirectUri = null)
188
    {
189 39
        if (!\is_array($scopes)) {
190 25
            $scopes = $this->convertScopesQueryStringToArray($scopes);
191
        }
192
193 39
        $validScopes = [];
194
195 39
        foreach ($scopes as $scopeItem) {
196 35
            $scope = $this->scopeRepository->getScopeEntityByIdentifier($scopeItem);
197
198 35
            if ($scope instanceof ScopeEntityInterface === false) {
199 2
                throw OAuthServerException::invalidScope($scopeItem, $redirectUri);
200
            }
201
202 33
            $validScopes[] = $scope;
203
        }
204
205 37
        return $validScopes;
206
    }
207
208
    /**
209
     * Converts a scopes query string to an array to easily iterate for validation.
210
     *
211
     * @param string $scopes
212
     *
213
     * @return array
214
     */
215 25
    private function convertScopesQueryStringToArray($scopes)
216
    {
217
        return \array_filter(\explode(self::SCOPE_DELIMITER_STRING, \trim($scopes)), function ($scope) {
218 25
            return !empty($scope);
219 25
        });
220
    }
221
222
    /**
223
     * Issue an access token.
224
     *
225
     * @param DateInterval           $accessTokenTTL
226
     * @param ClientEntityInterface  $client
227
     * @param string|null            $userIdentifier
228
     * @param ScopeEntityInterface[] $scopes
229
     *
230
     * @throws OAuthServerException
231
     * @throws UniqueTokenIdentifierConstraintViolationException
232
     *
233
     * @return AccessTokenEntityInterface
234
     */
235 22
    protected function issueAccessToken(
236
        DateInterval $accessTokenTTL,
237
        ClientEntityInterface $client,
238
        $userIdentifier,
239
        array $scopes = []
240
    ) {
241 22
        $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
242
243 22
        $accessToken = $this->accessTokenRepository->getNewToken($client, $scopes, $userIdentifier);
244 22
        $accessToken->setExpiryDateTime((new DateTimeImmutable())->add($accessTokenTTL));
245 22
        $accessToken->setPrivateKey($this->privateKey);
246
247 22
        while ($maxGenerationAttempts-- > 0) {
248 22
            $accessToken->setIdentifier($this->generateUniqueIdentifier());
249
            try {
250 22
                $this->accessTokenRepository->persistNewAccessToken($accessToken);
251
252 20
                return $accessToken;
253 2
            } catch (UniqueTokenIdentifierConstraintViolationException $e) {
254 1
                if ($maxGenerationAttempts === 0) {
255 1
                    throw $e;
256
                }
257
            }
258
        }
259
    }
260
261
    /**
262
     * Issue an auth code.
263
     *
264
     * @param DateInterval           $authCodeTTL
265
     * @param ClientEntityInterface  $client
266
     * @param string                 $userIdentifier
267
     * @param string|null            $redirectUri
268
     * @param ScopeEntityInterface[] $scopes
269
     *
270
     * @throws OAuthServerException
271
     * @throws UniqueTokenIdentifierConstraintViolationException
272
     *
273
     * @return AuthCodeEntityInterface
274
     */
275 6
    protected function issueAuthCode(
276
        DateInterval $authCodeTTL,
277
        ClientEntityInterface $client,
278
        $userIdentifier,
279
        $redirectUri,
280
        array $scopes = []
281
    ) {
282 6
        $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
283
284 6
        $authCode = $this->authCodeRepository->getNewAuthCode();
285 6
        $authCode->setExpiryDateTime((new DateTimeImmutable())->add($authCodeTTL));
286 6
        $authCode->setClient($client);
287 6
        $authCode->setUserIdentifier($userIdentifier);
288
289 6
        if ($redirectUri !== null) {
290 1
            $authCode->setRedirectUri($redirectUri);
291
        }
292
293 6
        foreach ($scopes as $scope) {
294 1
            $authCode->addScope($scope);
295
        }
296
297 6
        while ($maxGenerationAttempts-- > 0) {
298 6
            $authCode->setIdentifier($this->generateUniqueIdentifier());
299
            try {
300 6
                $this->authCodeRepository->persistNewAuthCode($authCode);
301
302 4
                return $authCode;
303 2
            } catch (UniqueTokenIdentifierConstraintViolationException $e) {
304 1
                if ($maxGenerationAttempts === 0) {
305 1
                    throw $e;
306
                }
307
            }
308
        }
309
    }
310
311
    /**
312
     * @param AccessTokenEntityInterface $accessToken
313
     *
314
     * @throws OAuthServerException
315
     * @throws UniqueTokenIdentifierConstraintViolationException
316
     *
317
     * @return RefreshTokenEntityInterface|null
318
     */
319 16
    protected function issueRefreshToken(AccessTokenEntityInterface $accessToken)
320
    {
321 16
        $refreshToken = $this->refreshTokenRepository->getNewRefreshToken();
322
323 16
        if ($refreshToken === null) {
324 4
            return null;
325
        }
326
327 12
        $refreshToken->setExpiryDateTime((new DateTimeImmutable())->add($this->refreshTokenTTL));
328 12
        $refreshToken->setAccessToken($accessToken);
329
330 12
        $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
331
332 12
        while ($maxGenerationAttempts-- > 0) {
333 12
            $refreshToken->setIdentifier($this->generateUniqueIdentifier());
334
            try {
335 12
                $this->refreshTokenRepository->persistNewRefreshToken($refreshToken);
336
337 10
                return $refreshToken;
338 2
            } catch (UniqueTokenIdentifierConstraintViolationException $e) {
339 1
                if ($maxGenerationAttempts === 0) {
340 1
                    throw $e;
341
                }
342
            }
343
        }
344
    }
345
346
    /**
347
     * Generate a new unique identifier.
348
     *
349
     * @param int $length
350
     *
351
     * @throws OAuthServerException
352
     *
353
     * @return string
354
     */
355 30
    protected function generateUniqueIdentifier($length = 40)
356
    {
357
        try {
358 30
            return \bin2hex(\random_bytes($length));
359
            // @codeCoverageIgnoreStart
360
        } catch (TypeError $e) {
361
            throw OAuthServerException::serverError('An unexpected error has occurred', $e);
362
        } catch (Error $e) {
363
            throw OAuthServerException::serverError('An unexpected error has occurred', $e);
364
        } catch (Exception $e) {
365
            // If you get this message, the CSPRNG failed hard.
366
            throw OAuthServerException::serverError('Could not generate a random string', $e);
367
        }
368
        // @codeCoverageIgnoreEnd
369
    }
370
371
    /**
372
     * {@inheritdoc}
373
     */
374 5
    public function canRespondToAccessTokenRequest(ServerRequestInterface $request)
375
    {
376 5
        $requestParameters = (array) $request->getParsedBody();
377
378
        return (
379 5
            \array_key_exists('grant_type', $requestParameters)
380 5
            && $requestParameters['grant_type'] === $this->getIdentifier()
381
        );
382
    }
383
384
    /**
385
     * {@inheritdoc}
386
     */
387 1
    public function canRespondToAuthorizationRequest(ServerRequestInterface $request)
388
    {
389 1
        return false;
390
    }
391
392
    /**
393
     * {@inheritdoc}
394
     */
395 1
    public function validateAuthorizationRequest(ServerRequestInterface $request)
396
    {
397 1
        throw new LogicException('This grant cannot validate an authorization request');
398
    }
399
400
    /**
401
     * {@inheritdoc}
402
     */
403 1
    public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest)
404
    {
405 1
        throw new LogicException('This grant cannot complete an authorization request');
406
    }
407
}
408