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

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