Completed
Push — master ( 732162...df20da )
by Alex
32:14
created

AbstractGrant::issueAuthCode()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 31
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 31
rs 8.439
cc 5
eloc 22
nc 8
nop 5
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 League\Event\EmitterAwareTrait;
14
use League\OAuth2\Server\CryptTrait;
15
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
16
use League\OAuth2\Server\Entities\ClientEntityInterface;
17
use League\OAuth2\Server\Entities\ScopeEntityInterface;
18
use League\OAuth2\Server\Exception\OAuthServerException;
19
use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException;
20
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
21
use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface;
22
use League\OAuth2\Server\Repositories\ClientRepositoryInterface;
23
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
24
use League\OAuth2\Server\Repositories\ScopeRepositoryInterface;
25
use League\OAuth2\Server\Repositories\UserRepositoryInterface;
26
use League\OAuth2\Server\RequestEvent;
27
use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
28
use Psr\Http\Message\ServerRequestInterface;
29
30
/**
31
 * Abstract grant class.
32
 */
33
abstract class AbstractGrant implements GrantTypeInterface
34
{
35
    use EmitterAwareTrait, CryptTrait;
36
37
    const SCOPE_DELIMITER_STRING = ' ';
38
39
    const MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS = 10;
40
41
    /**
42
     * @var ClientRepositoryInterface
43
     */
44
    protected $clientRepository;
45
46
    /**
47
     * @var AccessTokenRepositoryInterface
48
     */
49
    protected $accessTokenRepository;
50
51
    /**
52
     * @var ScopeRepositoryInterface
53
     */
54
    protected $scopeRepository;
55
56
    /**
57
     * @var \League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface
58
     */
59
    protected $authCodeRepository;
60
61
    /**
62
     * @var \League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface
63
     */
64
    protected $refreshTokenRepository;
65
66
    /**
67
     * @var \League\OAuth2\Server\Repositories\UserRepositoryInterface
68
     */
69
    protected $userRepository;
70
71
    /**
72
     * @var \DateInterval
73
     */
74
    protected $refreshTokenTTL;
75
76
    /**
77
     * @param ClientRepositoryInterface $clientRepository
78
     */
79
    public function setClientRepository(ClientRepositoryInterface $clientRepository)
80
    {
81
        $this->clientRepository = $clientRepository;
82
    }
83
84
    /**
85
     * @param AccessTokenRepositoryInterface $accessTokenRepository
86
     */
87
    public function setAccessTokenRepository(AccessTokenRepositoryInterface $accessTokenRepository)
88
    {
89
        $this->accessTokenRepository = $accessTokenRepository;
90
    }
91
92
    /**
93
     * @param ScopeRepositoryInterface $scopeRepository
94
     */
95
    public function setScopeRepository(ScopeRepositoryInterface $scopeRepository)
96
    {
97
        $this->scopeRepository = $scopeRepository;
98
    }
99
100
    /**
101
     * @param \League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface $refreshTokenRepository
102
     */
103
    public function setRefreshTokenRepository(RefreshTokenRepositoryInterface $refreshTokenRepository)
104
    {
105
        $this->refreshTokenRepository = $refreshTokenRepository;
106
    }
107
108
    /**
109
     * @param \League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface $authCodeRepository
110
     */
111
    public function setAuthCodeRepository(AuthCodeRepositoryInterface $authCodeRepository)
112
    {
113
        $this->authCodeRepository = $authCodeRepository;
114
    }
115
116
    /**
117
     * @param \League\OAuth2\Server\Repositories\UserRepositoryInterface $userRepository
118
     */
119
    public function setUserRepository(UserRepositoryInterface $userRepository)
120
    {
121
        $this->userRepository = $userRepository;
122
    }
123
124
    /**
125
     * {@inheritdoc}
126
     */
127
    public function setRefreshTokenTTL(\DateInterval $refreshTokenTTL)
128
    {
129
        $this->refreshTokenTTL = $refreshTokenTTL;
130
    }
131
132
    /**
133
     * Validate the client.
134
     *
135
     * @param \Psr\Http\Message\ServerRequestInterface $request
136
     *
137
     * @throws \League\OAuth2\Server\Exception\OAuthServerException
138
     *
139
     * @return \League\OAuth2\Server\Entities\ClientEntityInterface
140
     */
141
    protected function validateClient(ServerRequestInterface $request)
142
    {
143
        list($basicAuthUser, $basicAuthPassword) = $this->getBasicAuthCredentials($request);
144
145
        $clientId = $this->getRequestParameter('client_id', $request, $basicAuthUser);
146
        if (is_null($clientId)) {
147
            throw OAuthServerException::invalidRequest('client_id');
148
        }
149
150
        // If the client is confidential require the client secret
151
        $clientSecret = $this->getRequestParameter('client_secret', $request, $basicAuthPassword);
152
153
        $client = $this->clientRepository->getClientEntity(
154
            $clientId,
155
            $this->getIdentifier(),
156
            $clientSecret,
157
            true
158
        );
159
160
        if (!$client instanceof ClientEntityInterface) {
161
            $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
162
            throw OAuthServerException::invalidClient();
163
        }
164
165
        // If a redirect URI is provided ensure it matches what is pre-registered
166
        $redirectUri = $this->getRequestParameter('redirect_uri', $request, null);
167
        if ($redirectUri !== null) {
168
            if (
169
                is_string($client->getRedirectUri())
170
                && (strcmp($client->getRedirectUri(), $redirectUri) !== 0)
171
            ) {
172
                $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
173
                throw OAuthServerException::invalidClient();
174
            } elseif (
175
                is_array($client->getRedirectUri())
176
                && in_array($redirectUri, $client->getRedirectUri()) === false
177
            ) {
178
                $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
179
                throw OAuthServerException::invalidClient();
180
            }
181
        }
182
183
        return $client;
184
    }
185
186
    /**
187
     * Validate scopes in the request.
188
     *
189
     * @param string $scopes
190
     * @param string $redirectUri
191
     *
192
     * @throws \League\OAuth2\Server\Exception\OAuthServerException
193
     *
194
     * @return \League\OAuth2\Server\Entities\ScopeEntityInterface[]
195
     */
196
    public function validateScopes(
197
        $scopes,
198
        $redirectUri = null
199
    ) {
200
        $scopesList = array_filter(
201
            explode(self::SCOPE_DELIMITER_STRING, trim($scopes)),
202
            function ($scope) {
203
                return !empty($scope);
204
            }
205
        );
206
207
        $scopes = [];
208
        foreach ($scopesList as $scopeItem) {
209
            $scope = $this->scopeRepository->getScopeEntityByIdentifier($scopeItem);
210
211
            if (!$scope instanceof ScopeEntityInterface) {
212
                throw OAuthServerException::invalidScope($scopeItem, $redirectUri);
213
            }
214
215
            $scopes[] = $scope;
216
        }
217
218
        return $scopes;
219
    }
220
221
    /**
222
     * Retrieve request parameter.
223
     *
224
     * @param string                                   $parameter
225
     * @param \Psr\Http\Message\ServerRequestInterface $request
226
     * @param mixed                                    $default
227
     *
228
     * @return null|string
229
     */
230
    protected function getRequestParameter($parameter, ServerRequestInterface $request, $default = null)
231
    {
232
        $requestParameters = (array) $request->getParsedBody();
233
234
        return isset($requestParameters[$parameter]) ? $requestParameters[$parameter] : $default;
235
    }
236
237
    /**
238
     * Retrieve HTTP Basic Auth credentials with the Authorization header
239
     * of a request. First index of the returned array is the username,
240
     * second is the password (so list() will work). If the header does
241
     * not exist, or is otherwise an invalid HTTP Basic header, return
242
     * [null, null].
243
     *
244
     * @param \Psr\Http\Message\ServerRequestInterface $request
245
     * @return string[]|null[]
246
     */
247
    protected function getBasicAuthCredentials(ServerRequestInterface $request)
248
    {
249
        if (!$request->hasHeader('Authorization')) {
250
            return [null, null];
251
        }
252
253
        $header = $request->getHeader('Authorization')[0];
254
        if (strpos($header, 'Basic ') !== 0) {
255
            return [null, null];
256
        }
257
258
        if (!($decoded = base64_decode(substr($header, 6)))) {
259
            return [null, null];
260
        }
261
262
        if (strpos($decoded, ':') === false) {
263
            return [null, null]; // HTTP Basic header without colon isn't valid
264
        }
265
266
        return explode(':', $decoded, 2);
267
    }
268
269
    /**
270
     * Retrieve query string parameter.
271
     *
272
     * @param string                                   $parameter
273
     * @param \Psr\Http\Message\ServerRequestInterface $request
274
     * @param mixed                                    $default
275
     *
276
     * @return null|string
277
     */
278
    protected function getQueryStringParameter($parameter, ServerRequestInterface $request, $default = null)
279
    {
280
        return isset($request->getQueryParams()[$parameter]) ? $request->getQueryParams()[$parameter] : $default;
281
    }
282
283
    /**
284
     * Retrieve cookie parameter.
285
     *
286
     * @param string                                   $parameter
287
     * @param \Psr\Http\Message\ServerRequestInterface $request
288
     * @param mixed                                    $default
289
     *
290
     * @return null|string
291
     */
292
    protected function getCookieParameter($parameter, ServerRequestInterface $request, $default = null)
293
    {
294
        return isset($request->getCookieParams()[$parameter]) ? $request->getCookieParams()[$parameter] : $default;
295
    }
296
297
    /**
298
     * Retrieve server parameter.
299
     *
300
     * @param string                                   $parameter
301
     * @param \Psr\Http\Message\ServerRequestInterface $request
302
     * @param mixed                                    $default
303
     *
304
     * @return null|string
305
     */
306
    protected function getServerParameter($parameter, ServerRequestInterface $request, $default = null)
307
    {
308
        return isset($request->getServerParams()[$parameter]) ? $request->getServerParams()[$parameter] : $default;
309
    }
310
311
    /**
312
     * Issue an access token.
313
     *
314
     * @param \DateInterval                                         $accessTokenTTL
315
     * @param \League\OAuth2\Server\Entities\ClientEntityInterface  $client
316
     * @param string                                                $userIdentifier
317
     * @param \League\OAuth2\Server\Entities\ScopeEntityInterface[] $scopes
318
     *
319
     * @return \League\OAuth2\Server\Entities\AccessTokenEntityInterface
320
     */
321
    protected function issueAccessToken(
322
        \DateInterval $accessTokenTTL,
323
        ClientEntityInterface $client,
324
        $userIdentifier,
325
        array $scopes = []
326
    ) {
327
        $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
328
329
        $accessToken = $this->accessTokenRepository->getNewToken($client, $scopes, $userIdentifier);
330
        $accessToken->setClient($client);
331
        $accessToken->setUserIdentifier($userIdentifier);
332
        $accessToken->setExpiryDateTime((new \DateTime())->add($accessTokenTTL));
333
334
        foreach ($scopes as $scope) {
335
            $accessToken->addScope($scope);
336
        }
337
338
        while ($maxGenerationAttempts-- > 0) {
339
            $accessToken->setIdentifier($this->generateUniqueIdentifier());
340
            try {
341
                $this->accessTokenRepository->persistNewAccessToken($accessToken);
342
                return $accessToken;
343
            } catch (UniqueTokenIdentifierConstraintViolationException $e) {
344
                if ($maxGenerationAttempts === 0) {
345
                    throw $e;
346
                }
347
            }
348
        }
349
    }
350
351
    /**
352
     * Issue an auth code.
353
     *
354
     * @param \DateInterval                                         $authCodeTTL
355
     * @param \League\OAuth2\Server\Entities\ClientEntityInterface  $client
356
     * @param string                                                $userIdentifier
357
     * @param string                                                $redirectUri
358
     * @param \League\OAuth2\Server\Entities\ScopeEntityInterface[] $scopes
359
     *
360
     * @return \League\OAuth2\Server\Entities\AuthCodeEntityInterface
361
     */
362
    protected function issueAuthCode(
363
        \DateInterval $authCodeTTL,
364
        ClientEntityInterface $client,
365
        $userIdentifier,
366
        $redirectUri,
367
        array $scopes = []
368
    ) {
369
        $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
370
371
        $authCode = $this->authCodeRepository->getNewAuthCode();
372
        $authCode->setExpiryDateTime((new \DateTime())->add($authCodeTTL));
373
        $authCode->setClient($client);
374
        $authCode->setUserIdentifier($userIdentifier);
375
        $authCode->setRedirectUri($redirectUri);
376
377
        foreach ($scopes as $scope) {
378
            $authCode->addScope($scope);
379
        }
380
381
        while ($maxGenerationAttempts-- > 0) {
382
            $authCode->setIdentifier($this->generateUniqueIdentifier());
383
            try {
384
                $this->authCodeRepository->persistNewAuthCode($authCode);
385
                return $authCode;
386
            } catch (UniqueTokenIdentifierConstraintViolationException $e) {
387
                if ($maxGenerationAttempts === 0) {
388
                    throw $e;
389
                }
390
            }
391
        }
392
    }
393
394
    /**
395
     * @param \League\OAuth2\Server\Entities\AccessTokenEntityInterface $accessToken
396
     *
397
     * @return \League\OAuth2\Server\Entities\RefreshTokenEntityInterface
398
     */
399
    protected function issueRefreshToken(AccessTokenEntityInterface $accessToken)
400
    {
401
        $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
402
403
        $refreshToken = $this->refreshTokenRepository->getNewRefreshToken();
404
        $refreshToken->setExpiryDateTime((new \DateTime())->add($this->refreshTokenTTL));
405
        $refreshToken->setAccessToken($accessToken);
406
407
        while ($maxGenerationAttempts-- > 0) {
408
            $refreshToken->setIdentifier($this->generateUniqueIdentifier());
409
            try {
410
                $this->refreshTokenRepository->persistNewRefreshToken($refreshToken);
411
                return $refreshToken;
412
            } catch (UniqueTokenIdentifierConstraintViolationException $e) {
413
                if ($maxGenerationAttempts === 0) {
414
                    throw $e;
415
                }
416
            }
417
        }
418
    }
419
420
    /**
421
     * Generate a new unique identifier.
422
     *
423
     * @param int $length
424
     *
425
     * @throws \League\OAuth2\Server\Exception\OAuthServerException
426
     *
427
     * @return string
428
     */
429
    protected function generateUniqueIdentifier($length = 40)
430
    {
431
        try {
432
            return bin2hex(random_bytes($length));
433
            // @codeCoverageIgnoreStart
434
        } catch (\TypeError $e) {
435
            throw OAuthServerException::serverError('An unexpected error has occurred');
436
        } catch (\Error $e) {
437
            throw OAuthServerException::serverError('An unexpected error has occurred');
438
        } catch (\Exception $e) {
439
            // If you get this message, the CSPRNG failed hard.
440
            throw OAuthServerException::serverError('Could not generate a random string');
441
        }
442
        // @codeCoverageIgnoreEnd
443
    }
444
445
    /**
446
     * {@inheritdoc}
447
     */
448
    public function canRespondToAccessTokenRequest(ServerRequestInterface $request)
449
    {
450
        $requestParameters = (array) $request->getParsedBody();
451
452
        return (
453
            array_key_exists('grant_type', $requestParameters)
454
            && $requestParameters['grant_type'] === $this->getIdentifier()
455
        );
456
    }
457
458
    /**
459
     * {@inheritdoc}
460
     */
461
    public function canRespondToAuthorizationRequest(ServerRequestInterface $request)
462
    {
463
        return false;
464
    }
465
466
    /**
467
     * {@inheritdoc}
468
     */
469
    public function validateAuthorizationRequest(ServerRequestInterface $request)
470
    {
471
        throw new \LogicException('This grant cannot validate an authorization request');
472
    }
473
474
    /**
475
     * {@inheritdoc}
476
     */
477
    public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest)
478
    {
479
        throw new \LogicException('This grant cannot complete an authorization request');
480
    }
481
}
482