Completed
Pull Request — master (#601)
by
unknown
66:39 queued 31:39
created

AbstractGrant::getQueryStringParameter()   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\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
        $clientId = $this->getRequestParameter(
144
            'client_id',
145
            $request,
146
            $this->getServerParameter('PHP_AUTH_USER', $request)
147
        );
148
        if (is_null($clientId)) {
149
            throw OAuthServerException::invalidRequest('client_id');
150
        }
151
152
        // If the client is confidential require the client secret
153
        $clientSecret = $this->getRequestParameter(
154
            'client_secret',
155
            $request,
156
            $this->getServerParameter('PHP_AUTH_PW', $request)
157
        );
158
159
        $client = $this->clientRepository->getClientEntity(
160
            $clientId,
161
            $this->getIdentifier(),
162
            $clientSecret,
163
            true
164
        );
165
166
        if (!$client instanceof ClientEntityInterface) {
167
            $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
168
            throw OAuthServerException::invalidClient();
169
        }
170
171
        // If a redirect URI is provided ensure it matches what is pre-registered
172
        $redirectUri = $this->getRequestParameter('redirect_uri', $request, null);
173
        if ($redirectUri !== null) {
174
            if (
175
                is_string($client->getRedirectUri())
176
                && (strcmp($client->getRedirectUri(), $redirectUri) !== 0)
177
            ) {
178
                $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
179
                throw OAuthServerException::invalidClient();
180
            } elseif (
181
                is_array($client->getRedirectUri())
182
                && in_array($redirectUri, $client->getRedirectUri()) === false
183
            ) {
184
                $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
185
                throw OAuthServerException::invalidClient();
186
            }
187
        }
188
189
        return $client;
190
    }
191
192
    /**
193
     * Validate scopes in the request.
194
     *
195
     * @param string $scopes
196
     * @param string $redirectUri
197
     *
198
     * @throws \League\OAuth2\Server\Exception\OAuthServerException
199
     *
200
     * @return \League\OAuth2\Server\Entities\ScopeEntityInterface[]
201
     */
202
    public function validateScopes(
203
        $scopes,
204
        $redirectUri = null
205
    ) {
206
        $scopesList = array_filter(
207
            explode(self::SCOPE_DELIMITER_STRING, trim($scopes)),
208
            function ($scope) {
209
                return !empty($scope);
210
            }
211
        );
212
213
        $scopes = [];
214
        foreach ($scopesList as $scopeItem) {
215
            $scope = $this->scopeRepository->getScopeEntityByIdentifier($scopeItem);
216
217
            if (!$scope instanceof ScopeEntityInterface) {
218
                throw OAuthServerException::invalidScope($scopeItem, $redirectUri);
219
            }
220
221
            $scopes[] = $scope;
222
        }
223
224
        return $scopes;
225
    }
226
227
    /**
228
     * Retrieve request parameter.
229
     *
230
     * @param string                                   $parameter
231
     * @param \Psr\Http\Message\ServerRequestInterface $request
232
     * @param mixed                                    $default
233
     *
234
     * @return null|string
235
     */
236
    protected function getRequestParameter($parameter, ServerRequestInterface $request, $default = null)
237
    {
238
        $requestParameters = (array) $request->getParsedBody();
239
240
        return isset($requestParameters[$parameter]) ? $requestParameters[$parameter] : $default;
241
    }
242
243
    /**
244
     * Retrieve query string parameter.
245
     *
246
     * @param string                                   $parameter
247
     * @param \Psr\Http\Message\ServerRequestInterface $request
248
     * @param mixed                                    $default
249
     *
250
     * @return null|string
251
     */
252
    protected function getQueryStringParameter($parameter, ServerRequestInterface $request, $default = null)
253
    {
254
        return isset($request->getQueryParams()[$parameter]) ? $request->getQueryParams()[$parameter] : $default;
255
    }
256
257
    /**
258
     * Retrieve cookie parameter.
259
     *
260
     * @param string                                   $parameter
261
     * @param \Psr\Http\Message\ServerRequestInterface $request
262
     * @param mixed                                    $default
263
     *
264
     * @return null|string
265
     */
266
    protected function getCookieParameter($parameter, ServerRequestInterface $request, $default = null)
267
    {
268
        return isset($request->getCookieParams()[$parameter]) ? $request->getCookieParams()[$parameter] : $default;
269
    }
270
271
    /**
272
     * Retrieve server parameter.
273
     *
274
     * @param string                                   $parameter
275
     * @param \Psr\Http\Message\ServerRequestInterface $request
276
     * @param mixed                                    $default
277
     *
278
     * @return null|string
279
     */
280
    protected function getServerParameter($parameter, ServerRequestInterface $request, $default = null)
281
    {
282
        return isset($request->getServerParams()[$parameter]) ? $request->getServerParams()[$parameter] : $default;
283
    }
284
285
    /**
286
     * Issue an access token.
287
     *
288
     * @param \DateInterval                                         $accessTokenTTL
289
     * @param \League\OAuth2\Server\Entities\ClientEntityInterface  $client
290
     * @param string                                                $userIdentifier
291
     * @param \League\OAuth2\Server\Entities\ScopeEntityInterface[] $scopes
292
     *
293
     * @return \League\OAuth2\Server\Entities\AccessTokenEntityInterface
294
     */
295
    protected function issueAccessToken(
296
        \DateInterval $accessTokenTTL,
297
        ClientEntityInterface $client,
298
        $userIdentifier,
299
        array $scopes = []
300
    ) {
301
        $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
302
303
        $accessToken = $this->accessTokenRepository->getNewToken($client, $scopes, $userIdentifier);
304
        $accessToken->setClient($client);
305
        $accessToken->setUserIdentifier($userIdentifier);
306
        $accessToken->setExpiryDateTime((new \DateTime())->add($accessTokenTTL));
307
308
        foreach ($scopes as $scope) {
309
            $accessToken->addScope($scope);
310
        }
311
312
        while ($maxGenerationAttempts-- > 0) {
313
            $accessToken->setIdentifier($this->generateUniqueIdentifier());
314
            try {
315
                $this->accessTokenRepository->persistNewAccessToken($accessToken);
316
                return $accessToken;
317
            } catch (UniqueTokenIdentifierConstraintViolationException $e) {
318
                if ($maxGenerationAttempts === 0) {
319
                    throw $e;
320
                }
321
            }
322
        }
323
    }
324
325
    /**
326
     * Issue an auth code.
327
     *
328
     * @param \DateInterval                                         $authCodeTTL
329
     * @param \League\OAuth2\Server\Entities\ClientEntityInterface  $client
330
     * @param string                                                $userIdentifier
331
     * @param string                                                $redirectUri
332
     * @param \League\OAuth2\Server\Entities\ScopeEntityInterface[] $scopes
333
     *
334
     * @return \League\OAuth2\Server\Entities\AuthCodeEntityInterface
335
     */
336
    protected function issueAuthCode(
337
        \DateInterval $authCodeTTL,
338
        ClientEntityInterface $client,
339
        $userIdentifier,
340
        $redirectUri,
341
        array $scopes = []
342
    ) {
343
        $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
344
345
        $authCode = $this->authCodeRepository->getNewAuthCode();
346
        $authCode->setExpiryDateTime((new \DateTime())->add($authCodeTTL));
347
        $authCode->setClient($client);
348
        $authCode->setUserIdentifier($userIdentifier);
349
        $authCode->setRedirectUri($redirectUri);
350
351
        foreach ($scopes as $scope) {
352
            $authCode->addScope($scope);
353
        }
354
355
        while ($maxGenerationAttempts-- > 0) {
356
            $authCode->setIdentifier($this->generateUniqueIdentifier());
357
            try {
358
                $this->authCodeRepository->persistNewAuthCode($authCode);
359
                return $authCode;
360
            } catch (UniqueTokenIdentifierConstraintViolationException $e) {
361
                if ($maxGenerationAttempts === 0) {
362
                    throw $e;
363
                }
364
            }
365
        }
366
    }
367
368
    /**
369
     * @param \League\OAuth2\Server\Entities\AccessTokenEntityInterface $accessToken
370
     *
371
     * @return \League\OAuth2\Server\Entities\RefreshTokenEntityInterface
372
     */
373
    protected function issueRefreshToken(AccessTokenEntityInterface $accessToken)
374
    {
375
        $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
376
377
        $refreshToken = $this->refreshTokenRepository->getNewRefreshToken();
378
        $refreshToken->setExpiryDateTime((new \DateTime())->add($this->refreshTokenTTL));
379
        $refreshToken->setAccessToken($accessToken);
380
381
        while ($maxGenerationAttempts-- > 0) {
382
            $refreshToken->setIdentifier($this->generateUniqueIdentifier());
383
            try {
384
                $this->refreshTokenRepository->persistNewRefreshToken($refreshToken);
385
                return $refreshToken;
386
            } catch (UniqueTokenIdentifierConstraintViolationException $e) {
387
                if ($maxGenerationAttempts === 0) {
388
                    throw $e;
389
                }
390
            }
391
        }
392
    }
393
394
    /**
395
     * Generate a new unique identifier.
396
     *
397
     * @param int $length
398
     *
399
     * @throws \League\OAuth2\Server\Exception\OAuthServerException
400
     *
401
     * @return string
402
     */
403
    protected function generateUniqueIdentifier($length = 40)
404
    {
405
        try {
406
            return bin2hex(random_bytes($length));
407
            // @codeCoverageIgnoreStart
408
        } catch (\TypeError $e) {
409
            throw OAuthServerException::serverError('An unexpected error has occurred');
410
        } catch (\Error $e) {
411
            throw OAuthServerException::serverError('An unexpected error has occurred');
412
        } catch (\Exception $e) {
413
            // If you get this message, the CSPRNG failed hard.
414
            throw OAuthServerException::serverError('Could not generate a random string');
415
        }
416
        // @codeCoverageIgnoreEnd
417
    }
418
419
    /**
420
     * {@inheritdoc}
421
     */
422
    public function canRespondToAccessTokenRequest(ServerRequestInterface $request)
423
    {
424
        $requestParameters = (array) $request->getParsedBody();
425
426
        return (
427
            array_key_exists('grant_type', $requestParameters)
428
            && $requestParameters['grant_type'] === $this->getIdentifier()
429
        );
430
    }
431
432
    /**
433
     * {@inheritdoc}
434
     */
435
    public function canRespondToAuthorizationRequest(ServerRequestInterface $request)
436
    {
437
        return false;
438
    }
439
440
    /**
441
     * {@inheritdoc}
442
     */
443
    public function validateAuthorizationRequest(ServerRequestInterface $request)
444
    {
445
        throw new \LogicException('This grant cannot validate an authorization request');
446
    }
447
448
    /**
449
     * {@inheritdoc}
450
     */
451
    public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest)
452
    {
453
        throw new \LogicException('This grant cannot complete an authorization request');
454
    }
455
}
456