Completed
Pull Request — master (#601)
by
unknown
43:50 queued 08:43
created

AbstractGrant::generateUniqueIdentifier()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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