Completed
Pull Request — master (#995)
by
unknown
02:16
created

AbstractGrant   A

Complexity

Total Complexity 39

Size/Duplication

Total Lines 366
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 10

Test Coverage

Coverage 97.03%

Importance

Changes 0
Metric Value
wmc 39
lcom 2
cbo 10
dl 0
loc 366
ccs 98
cts 101
cp 0.9703
rs 9.28
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A getClientRepository() 0 4 1
A setClientRepository() 0 4 1
A setAccessTokenRepository() 0 4 1
A setScopeRepository() 0 4 1
A setRefreshTokenRepository() 0 4 1
A setAuthCodeRepository() 0 4 1
A setUserRepository() 0 4 1
A setRefreshTokenTTL() 0 4 1
A setPrivateKey() 0 4 1
A setDefaultScope() 0 4 1
A validateScopes() 0 20 4
A convertScopesQueryStringToArray() 0 6 1
A issueAccessToken() 0 30 5
B issueAuthCode() 0 35 6
A issueRefreshToken() 0 21 4
A generateUniqueIdentifier() 0 15 4
A canRespondToAccessTokenRequest() 0 9 2
A canRespondToAuthorizationRequest() 0 4 1
A validateAuthorizationRequest() 0 4 1
A completeAuthorizationRequest() 0 4 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 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
     * @string
92
     */
93
    protected $defaultScope;
94
95
    /**
96
     * @return ClientRepositoryInterface
97
     */
98 41
    public function getClientRepository()
99
    {
100 41
        return $this->clientRepository;
101
    }
102
103
    /**
104
     * @param ClientRepositoryInterface $clientRepository
105
     */
106 64
    public function setClientRepository(ClientRepositoryInterface $clientRepository)
107
    {
108 64
        $this->clientRepository = $clientRepository;
109 64
    }
110
111
    /**
112
     * @param AccessTokenRepositoryInterface $accessTokenRepository
113
     */
114 42
    public function setAccessTokenRepository(AccessTokenRepositoryInterface $accessTokenRepository)
115
    {
116 42
        $this->accessTokenRepository = $accessTokenRepository;
117 42
    }
118
119
    /**
120
     * @param ScopeRepositoryInterface $scopeRepository
121
     */
122 37
    public function setScopeRepository(ScopeRepositoryInterface $scopeRepository)
123
    {
124 37
        $this->scopeRepository = $scopeRepository;
125 37
    }
126
127
    /**
128
     * @param RefreshTokenRepositoryInterface $refreshTokenRepository
129
     */
130 56
    public function setRefreshTokenRepository(RefreshTokenRepositoryInterface $refreshTokenRepository)
131
    {
132 56
        $this->refreshTokenRepository = $refreshTokenRepository;
133 56
    }
134
135
    /**
136
     * @param AuthCodeRepositoryInterface $authCodeRepository
137
     */
138 42
    public function setAuthCodeRepository(AuthCodeRepositoryInterface $authCodeRepository)
139
    {
140 42
        $this->authCodeRepository = $authCodeRepository;
141 42
    }
142
143
    /**
144
     * @param UserRepositoryInterface $userRepository
145
     */
146 5
    public function setUserRepository(UserRepositoryInterface $userRepository)
147
    {
148 5
        $this->userRepository = $userRepository;
149 5
    }
150
151
    /**
152
     * {@inheritdoc}
153
     */
154 1
    public function setRefreshTokenTTL(DateInterval $refreshTokenTTL)
155
    {
156 1
        $this->refreshTokenTTL = $refreshTokenTTL;
157 1
    }
158
159
    /**
160
     * Set the private key
161
     *
162
     * @param CryptKey $key
163
     */
164 20
    public function setPrivateKey(CryptKey $key)
165
    {
166 20
        $this->privateKey = $key;
167 20
    }
168
169
    /**
170
     * @param string $scope
171
     */
172 16
    public function setDefaultScope($scope)
173
    {
174 16
        $this->defaultScope = $scope;
175 16
    }
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 34
    public function validateScopes($scopes, $redirectUri = null)
188
    {
189 34
        if (!\is_array($scopes)) {
190 23
            $scopes = $this->convertScopesQueryStringToArray($scopes);
191
        }
192
193 34
        $validScopes = [];
194
195 34
        foreach ($scopes as $scopeItem) {
196 28
            $scope = $this->scopeRepository->getScopeEntityByIdentifier($scopeItem);
197
198 28
            if ($scope instanceof ScopeEntityInterface === false) {
199 1
                throw OAuthServerException::invalidScope($scopeItem, $redirectUri);
200
            }
201
202 27
            $validScopes[] = $scope;
203
        }
204
205 33
        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
    private function convertScopesQueryStringToArray($scopes)
216
    {
217 23
        return array_filter(explode(self::SCOPE_DELIMITER_STRING, trim($scopes)), function ($scope) {
218 23
            return !empty($scope);
219 23
        });
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 17
    protected function issueAccessToken(
236
        DateInterval $accessTokenTTL,
237
        ClientEntityInterface $client,
238
        $userIdentifier,
239
        array $scopes = []
240
    ) {
241 17
        $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
242
243 17
        $accessToken = $this->accessTokenRepository->getNewToken($client, $scopes, $userIdentifier);
244 17
        $accessToken->setClient($client);
245 17
        $accessToken->setUserIdentifier($userIdentifier);
246 17
        $accessToken->setExpiryDateTime((new DateTime())->add($accessTokenTTL));
247
248 17
        foreach ($scopes as $scope) {
249 13
            $accessToken->addScope($scope);
250
        }
251
252 17
        while ($maxGenerationAttempts-- > 0) {
253 17
            $accessToken->setIdentifier($this->generateUniqueIdentifier());
254
            try {
255 17
                $this->accessTokenRepository->persistNewAccessToken($accessToken);
256
257 15
                return $accessToken;
258 2
            } catch (UniqueTokenIdentifierConstraintViolationException $e) {
259 1
                if ($maxGenerationAttempts === 0) {
260 1
                    throw $e;
261
                }
262
            }
263
        }
264
    }
265
266
    /**
267
     * Issue an auth code.
268
     *
269
     * @param DateInterval           $authCodeTTL
270
     * @param ClientEntityInterface  $client
271
     * @param string                 $userIdentifier
272
     * @param string|null            $redirectUri
273
     * @param ScopeEntityInterface[] $scopes
274
     *
275
     * @throws OAuthServerException
276
     * @throws UniqueTokenIdentifierConstraintViolationException
277
     *
278
     * @return AuthCodeEntityInterface
279
     */
280 6
    protected function issueAuthCode(
281
        DateInterval $authCodeTTL,
282
        ClientEntityInterface $client,
283
        $userIdentifier,
284
        $redirectUri,
285
        array $scopes = []
286
    ) {
287 6
        $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
288
289 6
        $authCode = $this->authCodeRepository->getNewAuthCode();
290 6
        $authCode->setExpiryDateTime((new DateTime())->add($authCodeTTL));
291 6
        $authCode->setClient($client);
292 6
        $authCode->setUserIdentifier($userIdentifier);
293
294 6
        if ($redirectUri !== null) {
295 1
            $authCode->setRedirectUri($redirectUri);
296
        }
297
298 6
        foreach ($scopes as $scope) {
299 1
            $authCode->addScope($scope);
300
        }
301
302 6
        while ($maxGenerationAttempts-- > 0) {
303 6
            $authCode->setIdentifier($this->generateUniqueIdentifier());
304
            try {
305 6
                $this->authCodeRepository->persistNewAuthCode($authCode);
306
307 4
                return $authCode;
308 2
            } catch (UniqueTokenIdentifierConstraintViolationException $e) {
309 1
                if ($maxGenerationAttempts === 0) {
310 1
                    throw $e;
311
                }
312
            }
313
        }
314
    }
315
316
    /**
317
     * @param AccessTokenEntityInterface $accessToken
318
     *
319
     * @throws OAuthServerException
320
     * @throws UniqueTokenIdentifierConstraintViolationException
321
     *
322
     * @return RefreshTokenEntityInterface
323
     */
324 10
    protected function issueRefreshToken(AccessTokenEntityInterface $accessToken)
325
    {
326 10
        $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
327
328 10
        $refreshToken = $this->refreshTokenRepository->getNewRefreshToken();
329 10
        $refreshToken->setExpiryDateTime((new DateTime())->add($this->refreshTokenTTL));
330 10
        $refreshToken->setAccessToken($accessToken);
331
332 10
        while ($maxGenerationAttempts-- > 0) {
333 10
            $refreshToken->setIdentifier($this->generateUniqueIdentifier());
334
            try {
335 10
                $this->refreshTokenRepository->persistNewRefreshToken($refreshToken);
336
337 8
                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 25
    protected function generateUniqueIdentifier($length = 40)
356
    {
357
        try {
358 25
            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