Passed
Pull Request — master (#1135)
by
unknown
03:32
created

AbstractGrant::getClientRepository()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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