Completed
Push — master ( 06424f...aac467 )
by Alex
86:38 queued 51:34
created

AbstractGrant::setPrivateKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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