Completed
Pull Request — master (#823)
by Martin
59:44 queued 24:43
created

AbstractGrant::getServerParameter()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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