AbstractGrant   F
last analyzed

Complexity

Total Complexity 75

Size/Duplication

Total Lines 580
Duplicated Lines 0 %

Test Coverage

Coverage 88.07%

Importance

Changes 9
Bugs 0 Features 0
Metric Value
eloc 161
dl 0
loc 580
ccs 155
cts 176
cp 0.8807
rs 2.4
c 9
b 0
f 0
wmc 75

37 Methods

Rating   Name   Duplication   Size   Complexity  
A setScopeRepository() 0 3 1
A revokeRefreshTokens() 0 3 1
A setRefreshTokenRepository() 0 3 1
A setPrivateKey() 0 3 1
A setAccessTokenRepository() 0 3 1
A setUserRepository() 0 3 1
A setRefreshTokenTTL() 0 3 1
A setClientRepository() 0 3 1
A setDefaultScope() 0 3 1
A setAuthCodeRepository() 0 3 1
A getIntervalVisibility() 0 3 1
A respondToDeviceAuthorizationRequest() 0 3 1
A getServerParameter() 0 3 1
A parseParam() 0 19 5
A canRespondToAuthorizationRequest() 0 3 1
A supportsGrantType() 0 4 2
A getClientEntityOrFail() 0 14 3
A canRespondToAccessTokenRequest() 0 7 2
A completeDeviceAuthorizationRequest() 0 3 1
A getQueryStringParameter() 0 3 1
A issueRefreshToken() 0 32 6
A getClientCredentials() 0 13 2
A setIncludeVerificationUriComplete() 0 3 1
A issueAuthCode() 0 37 6
A completeAuthorizationRequest() 0 3 1
A getRequestParameter() 0 3 1
A issueAccessToken() 0 27 4
A getBasicAuthCredentials() 0 28 6
A setIntervalVisibility() 0 3 1
A getCookieParameter() 0 3 1
A validateClient() 0 19 4
A validateScopes() 0 21 5
A generateUniqueIdentifier() 0 14 4
A validateRedirectUri() 0 10 2
A convertScopesQueryStringToArray() 0 3 1
A canRespondToDeviceAuthorizationRequest() 0 3 1
A validateAuthorizationRequest() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like AbstractGrant often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AbstractGrant, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * OAuth 2.0 Abstract grant.
5
 *
6
 * @author      Alex Bilbie <[email protected]>
7
 * @copyright   Copyright (c) Alex Bilbie
8
 * @license     http://mit-license.org/
9
 *
10
 * @link        https://github.com/thephpleague/oauth2-server
11
 */
12
13
declare(strict_types=1);
14
15
namespace League\OAuth2\Server\Grant;
16
17
use DateInterval;
18
use DateTimeImmutable;
19
use DomainException;
20
use Error;
21
use Exception;
22
use League\OAuth2\Server\CryptKeyInterface;
23
use League\OAuth2\Server\CryptTrait;
24
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
25
use League\OAuth2\Server\Entities\AuthCodeEntityInterface;
26
use League\OAuth2\Server\Entities\ClientEntityInterface;
27
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
28
use League\OAuth2\Server\Entities\ScopeEntityInterface;
29
use League\OAuth2\Server\EventEmitting\EmitterAwarePolyfill;
30
use League\OAuth2\Server\Exception\OAuthServerException;
31
use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException;
32
use League\OAuth2\Server\RedirectUriValidators\RedirectUriValidator;
33
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
34
use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface;
35
use League\OAuth2\Server\Repositories\ClientRepositoryInterface;
36
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
37
use League\OAuth2\Server\Repositories\ScopeRepositoryInterface;
38
use League\OAuth2\Server\Repositories\UserRepositoryInterface;
39
use League\OAuth2\Server\RequestEvent;
40
use League\OAuth2\Server\RequestTypes\AuthorizationRequestInterface;
41
use League\OAuth2\Server\ResponseTypes\DeviceCodeResponse;
42
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
43
use LogicException;
44
use Psr\Http\Message\ServerRequestInterface;
45
use TypeError;
46
47
use function array_filter;
48
use function array_key_exists;
49
use function base64_decode;
50
use function bin2hex;
51
use function explode;
52
use function is_string;
53
use function random_bytes;
54
use function substr;
55
use function trim;
56
57
/**
58
 * Abstract grant class.
59
 */
60
abstract class AbstractGrant implements GrantTypeInterface
61
{
62
    use EmitterAwarePolyfill;
63
    use CryptTrait;
64
65
    protected const SCOPE_DELIMITER_STRING = ' ';
66
67
    protected const MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS = 10;
68
69
    protected ClientRepositoryInterface $clientRepository;
70
71
    protected AccessTokenRepositoryInterface $accessTokenRepository;
72
73
    protected ScopeRepositoryInterface $scopeRepository;
74
75
    protected AuthCodeRepositoryInterface $authCodeRepository;
76
77
    protected RefreshTokenRepositoryInterface $refreshTokenRepository;
78
79
    protected UserRepositoryInterface $userRepository;
80
81
    protected DateInterval $refreshTokenTTL;
82
83
    protected CryptKeyInterface $privateKey;
84
85
    protected string $defaultScope;
86
87
    protected bool $revokeRefreshTokens = true;
88
89 101
    public function setClientRepository(ClientRepositoryInterface $clientRepository): void
90
    {
91 101
        $this->clientRepository = $clientRepository;
92
    }
93
94 55
    public function setAccessTokenRepository(AccessTokenRepositoryInterface $accessTokenRepository): void
95
    {
96 55
        $this->accessTokenRepository = $accessTokenRepository;
97
    }
98
99 70
    public function setScopeRepository(ScopeRepositoryInterface $scopeRepository): void
100
    {
101 70
        $this->scopeRepository = $scopeRepository;
102
    }
103
104 94
    public function setRefreshTokenRepository(RefreshTokenRepositoryInterface $refreshTokenRepository): void
105
    {
106 94
        $this->refreshTokenRepository = $refreshTokenRepository;
107
    }
108
109 54
    public function setAuthCodeRepository(AuthCodeRepositoryInterface $authCodeRepository): void
110
    {
111 54
        $this->authCodeRepository = $authCodeRepository;
112
    }
113
114 6
    public function setUserRepository(UserRepositoryInterface $userRepository): void
115
    {
116 6
        $this->userRepository = $userRepository;
117
    }
118
119
    /**
120
     * {@inheritdoc}
121
     */
122 3
    public function setRefreshTokenTTL(DateInterval $refreshTokenTTL): void
123
    {
124 3
        $this->refreshTokenTTL = $refreshTokenTTL;
125
    }
126
127
    /**
128
     * Set the private key
129
     */
130 47
    public function setPrivateKey(CryptKeyInterface $privateKey): void
131
    {
132 47
        $this->privateKey = $privateKey;
133
    }
134
135 39
    public function setDefaultScope(string $scope): void
136
    {
137 39
        $this->defaultScope = $scope;
138
    }
139
140 12
    public function revokeRefreshTokens(bool $willRevoke): void
141
    {
142 12
        $this->revokeRefreshTokens = $willRevoke;
143
    }
144
145
    /**
146
     * Validate the client.
147
     *
148
     * @throws OAuthServerException
149
     */
150 66
    protected function validateClient(ServerRequestInterface $request): ClientEntityInterface
151
    {
152 66
        [$clientId, $clientSecret] = $this->getClientCredentials($request);
153
154 63
        $client = $this->getClientEntityOrFail($clientId, $request);
155
156 55
        if ($client->isConfidential()) {
157 16
            if ($clientSecret === '') {
158 4
                throw OAuthServerException::invalidRequest('client_secret');
159
            }
160
161 12
            if ($this->clientRepository->validateClient($clientId, $clientSecret, $this->getIdentifier()) === false) {
162
                $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
163
164
                throw OAuthServerException::invalidClient($request);
165
            }
166
        }
167
168 51
        return $client;
169
    }
170
171
    /**
172
     * Wrapper around ClientRepository::getClientEntity() that ensures we emit
173
     * an event and throw an exception if the repo doesn't return a client
174
     * entity.
175
     *
176
     * This is a bit of defensive coding because the interface contract
177
     * doesn't actually enforce non-null returns/exception-on-no-client so
178
     * getClientEntity might return null. By contrast, this method will
179
     * always either return a ClientEntityInterface or throw.
180
     *
181
     * @throws OAuthServerException
182
     */
183 91
    protected function getClientEntityOrFail(string $clientId, ServerRequestInterface $request): ClientEntityInterface
184
    {
185 91
        $client = $this->clientRepository->getClientEntity($clientId);
186
187 91
        if ($client instanceof ClientEntityInterface === false) {
188 11
            $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
189 11
            throw OAuthServerException::invalidClient($request);
190
        }
191
192 80
        if ($this->supportsGrantType($client, $this->getIdentifier()) === false) {
193 1
            throw OAuthServerException::unauthorizedClient();
194
        }
195
196 79
        return $client;
197
    }
198
199
    /**
200
     * Returns true if the given client is authorized to use the given grant type.
201
     */
202 83
    protected function supportsGrantType(ClientEntityInterface $client, string $grantType): bool
203
    {
204 83
        return method_exists($client, 'supportsGrantType') === false
205 83
            || $client->supportsGrantType($grantType) === true;
206
    }
207
208
    /**
209
     * Gets the client credentials from the request from the request body or
210
     * the Http Basic Authorization header
211
     *
212
     * @return array{0:non-empty-string,1:string}
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{0:non-empty-string,1:string} at position 4 could not be parsed: Unknown type name 'non-empty-string' at position 4 in array{0:non-empty-string,1:string}.
Loading history...
213
     *
214
     * @throws OAuthServerException
215
     */
216 67
    protected function getClientCredentials(ServerRequestInterface $request): array
217
    {
218 67
        [$basicAuthUser, $basicAuthPassword] = $this->getBasicAuthCredentials($request);
219
220 67
        $clientId = $this->getRequestParameter('client_id', $request, $basicAuthUser);
221
222 67
        if ($clientId === null) {
223 3
            throw OAuthServerException::invalidRequest('client_id');
224
        }
225
226 64
        $clientSecret = $this->getRequestParameter('client_secret', $request, $basicAuthPassword);
227
228 63
        return [$clientId, $clientSecret ?? ''];
229
    }
230
231
    /**
232
     * Validate redirectUri from the request. If a redirect URI is provided
233
     * ensure it matches what is pre-registered
234
     *
235
     * @throws OAuthServerException
236
     */
237 17
    protected function validateRedirectUri(
238
        string $redirectUri,
239
        ClientEntityInterface $client,
240
        ServerRequestInterface $request
241
    ): void {
242 17
        $validator = new RedirectUriValidator($client->getRedirectUri());
243
244 17
        if (!$validator->validateRedirectUri($redirectUri)) {
245 4
            $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
246 4
            throw OAuthServerException::invalidClient($request);
247
        }
248
    }
249
250
    /**
251
     * Validate scopes in the request.
252
     *
253
     * @param null|string|string[] $scopes
254
     *
255
     * @throws OAuthServerException
256
     *
257
     * @return ScopeEntityInterface[]
258
     */
259 52
    public function validateScopes(string|array|null $scopes, ?string $redirectUri = null): array
260
    {
261 52
        if ($scopes === null) {
0 ignored issues
show
introduced by
The condition $scopes === null is always false.
Loading history...
262
            $scopes = [];
263 52
        } elseif (is_string($scopes)) {
0 ignored issues
show
introduced by
The condition is_string($scopes) is always false.
Loading history...
264 37
            $scopes = $this->convertScopesQueryStringToArray($scopes);
265
        }
266
267 52
        $validScopes = [];
268
269 52
        foreach ($scopes as $scopeItem) {
270 52
            $scope = $this->scopeRepository->getScopeEntityByIdentifier($scopeItem);
271
272 52
            if ($scope instanceof ScopeEntityInterface === false) {
273 7
                throw OAuthServerException::invalidScope($scopeItem, $redirectUri);
274
            }
275
276 45
            $validScopes[] = $scope;
277
        }
278
279 45
        return $validScopes;
280
    }
281
282
    /**
283
     * Converts a scopes query string to an array to easily iterate for validation.
284
     *
285
     * @return string[]
286
     */
287 37
    private function convertScopesQueryStringToArray(string $scopes): array
288
    {
289 37
        return array_filter(explode(self::SCOPE_DELIMITER_STRING, trim($scopes)), static fn ($scope) => $scope !== '');
290
    }
291
292
    /**
293
     * Parse request parameter.
294
     *
295
     * @param array<array-key, mixed> $request
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key, mixed> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, mixed>.
Loading history...
296
     *
297
     * @return non-empty-string|null
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string|null at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string|null.
Loading history...
298
     *
299
     * @throws OAuthServerException
300
     */
301 100
    private static function parseParam(string $parameter, array $request, ?string $default = null): ?string
302
    {
303 100
        $value = $request[$parameter] ?? '';
304
305 100
        if (is_scalar($value)) {
306 100
            $value = trim((string) $value);
307
        } else {
308 2
            throw OAuthServerException::invalidRequest($parameter);
309
        }
310
311 100
        if ($value === '') {
312 80
            $value = $default === null ? null : trim($default);
313
314 80
            if ($value === '') {
315
                $value = null;
316
            }
317
        }
318
319 100
        return $value;
320
    }
321
322
    /**
323
     * Retrieve request parameter.
324
     *
325
     * @return non-empty-string|null
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string|null at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string|null.
Loading history...
326
     *
327
     * @throws OAuthServerException
328
     */
329 75
    protected function getRequestParameter(string $parameter, ServerRequestInterface $request, ?string $default = null): ?string
330
    {
331 75
        return self::parseParam($parameter, (array) $request->getParsedBody(), $default);
332
    }
333
334
    /**
335
     * Retrieve HTTP Basic Auth credentials with the Authorization header
336
     * of a request. First index of the returned array is the username,
337
     * second is the password (so list() will work). If the header does
338
     * not exist, or is otherwise an invalid HTTP Basic header, return
339
     * [null, null].
340
     *
341
     * @return array{0:non-empty-string,1:string}|array{0:null,1:null}
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{0:non-empty-string...g}|array{0:null,1:null} at position 4 could not be parsed: Unknown type name 'non-empty-string' at position 4 in array{0:non-empty-string,1:string}|array{0:null,1:null}.
Loading history...
342
     */
343 73
    protected function getBasicAuthCredentials(ServerRequestInterface $request): array
344
    {
345 73
        if (!$request->hasHeader('Authorization')) {
346 63
            return [null, null];
347
        }
348
349 10
        $header = $request->getHeader('Authorization')[0];
350 10
        if (stripos($header, 'Basic ') !== 0) {
351 3
            return [null, null];
352
        }
353
354 7
        $decoded = base64_decode(substr($header, 6), true);
355
356 7
        if ($decoded === false) {
357 1
            return [null, null];
358
        }
359
360 6
        if (str_contains($decoded, ':') === false) {
361 2
            return [null, null]; // HTTP Basic header without colon isn't valid
362
        }
363
364 4
        [$username, $password] = explode(':', $decoded, 2);
365
366 4
        if ($username === '') {
367
            return [null, null];
368
        }
369
370 4
        return [$username, $password];
371
    }
372
373
    /**
374
     * Retrieve query string parameter.
375
     *
376
     * @return non-empty-string|null
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string|null at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string|null.
Loading history...
377
     *
378
     * @throws OAuthServerException
379
     */
380 24
    protected function getQueryStringParameter(string $parameter, ServerRequestInterface $request, ?string $default = null): ?string
381
    {
382 24
        return self::parseParam($parameter, $request->getQueryParams(), $default);
383
    }
384
385
    /**
386
     * Retrieve cookie parameter.
387
     *
388
     * @return non-empty-string|null
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string|null at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string|null.
Loading history...
389
     *
390
     * @throws OAuthServerException
391
     */
392 1
    protected function getCookieParameter(string $parameter, ServerRequestInterface $request, ?string $default = null): ?string
393
    {
394 1
        return self::parseParam($parameter, $request->getCookieParams(), $default);
395
    }
396
397
    /**
398
     * Retrieve server parameter.
399
     *
400
     * @return non-empty-string|null
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string|null at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string|null.
Loading history...
401
     *
402
     * @throws OAuthServerException
403
     */
404 31
    protected function getServerParameter(string $parameter, ServerRequestInterface $request, ?string $default = null): ?string
405
    {
406 31
        return self::parseParam($parameter, $request->getServerParams(), $default);
407
    }
408
409
    /**
410
     * Issue an access token.
411
     *
412
     * @param ScopeEntityInterface[] $scopes
413
     *
414
     * @throws OAuthServerException
415
     * @throws UniqueTokenIdentifierConstraintViolationException
416
     */
417 28
    protected function issueAccessToken(
418
        DateInterval $accessTokenTTL,
419
        ClientEntityInterface $client,
420
        string|null $userIdentifier,
421
        array $scopes = []
422
    ): AccessTokenEntityInterface {
423 28
        $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
424
425 28
        $accessToken = $this->accessTokenRepository->getNewToken($client, $scopes, $userIdentifier);
426 28
        $accessToken->setExpiryDateTime((new DateTimeImmutable())->add($accessTokenTTL));
427 28
        $accessToken->setPrivateKey($this->privateKey);
428
429 28
        while ($maxGenerationAttempts-- > 0) {
430 28
            $accessToken->setIdentifier($this->generateUniqueIdentifier());
431
            try {
432 28
                $this->accessTokenRepository->persistNewAccessToken($accessToken);
433
434 26
                return $accessToken;
435 3
            } catch (UniqueTokenIdentifierConstraintViolationException $e) {
436 2
                if ($maxGenerationAttempts === 0) {
437 1
                    throw $e;
438
                }
439
            }
440
        }
441
442
        // This should never be hit. It is here to work around a PHPStan false error
443
        return $accessToken;
444
    }
445
446
    /**
447
     * Issue an auth code.
448
     *
449
     * @param non-empty-string       $userIdentifier
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
450
     * @param ScopeEntityInterface[] $scopes
451
     *
452
     * @throws OAuthServerException
453
     * @throws UniqueTokenIdentifierConstraintViolationException
454
     */
455 7
    protected function issueAuthCode(
456
        DateInterval $authCodeTTL,
457
        ClientEntityInterface $client,
458
        string $userIdentifier,
459
        ?string $redirectUri,
460
        array $scopes = []
461
    ): AuthCodeEntityInterface {
462 7
        $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
463
464 7
        $authCode = $this->authCodeRepository->getNewAuthCode();
465 7
        $authCode->setExpiryDateTime((new DateTimeImmutable())->add($authCodeTTL));
466 7
        $authCode->setClient($client);
467 7
        $authCode->setUserIdentifier($userIdentifier);
468
469 7
        if ($redirectUri !== null) {
470 1
            $authCode->setRedirectUri($redirectUri);
471
        }
472
473 7
        foreach ($scopes as $scope) {
474 1
            $authCode->addScope($scope);
475
        }
476
477 7
        while ($maxGenerationAttempts-- > 0) {
478 7
            $authCode->setIdentifier($this->generateUniqueIdentifier());
479
            try {
480 7
                $this->authCodeRepository->persistNewAuthCode($authCode);
481
482 5
                return $authCode;
483 3
            } catch (UniqueTokenIdentifierConstraintViolationException $e) {
484 2
                if ($maxGenerationAttempts === 0) {
485 1
                    throw $e;
486
                }
487
            }
488
        }
489
490
        // This should never be hit. It is here to work around a PHPStan false error
491
        return $authCode;
492
    }
493
494
    /**
495
     * @throws OAuthServerException
496
     * @throws UniqueTokenIdentifierConstraintViolationException
497
     */
498 23
    protected function issueRefreshToken(AccessTokenEntityInterface $accessToken): ?RefreshTokenEntityInterface
499
    {
500 23
        if ($this->supportsGrantType($accessToken->getClient(), 'refresh_token') === false) {
501 1
            return null;
502
        }
503
504 22
        $refreshToken = $this->refreshTokenRepository->getNewRefreshToken();
505
506 22
        if ($refreshToken === null) {
507 5
            return null;
508
        }
509
510 17
        $refreshToken->setExpiryDateTime((new DateTimeImmutable())->add($this->refreshTokenTTL));
511 17
        $refreshToken->setAccessToken($accessToken);
512
513 17
        $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
514
515 17
        while ($maxGenerationAttempts-- > 0) {
516 17
            $refreshToken->setIdentifier($this->generateUniqueIdentifier());
517
            try {
518 17
                $this->refreshTokenRepository->persistNewRefreshToken($refreshToken);
519
520 15
                return $refreshToken;
521 3
            } catch (UniqueTokenIdentifierConstraintViolationException $e) {
522 2
                if ($maxGenerationAttempts === 0) {
523 1
                    throw $e;
524
                }
525
            }
526
        }
527
528
        // This should never be hit. It is here to work around a PHPStan false error
529
        return $refreshToken;
530
    }
531
532
    /**
533
     * Generate a new unique identifier.
534
     *
535
     * @return non-empty-string
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
536
     *
537
     * @throws OAuthServerException
538
     */
539 42
    protected function generateUniqueIdentifier(int $length = 40): string
540
    {
541
        try {
542 42
            if ($length < 1) {
543
                throw new DomainException('Length must be a positive integer');
544
            }
545
546 42
            return bin2hex(random_bytes($length));
547
            // @codeCoverageIgnoreStart
548
        } catch (TypeError | Error $e) {
549
            throw OAuthServerException::serverError('An unexpected error has occurred', $e);
550
        } catch (Exception $e) {
551
            // If you get this message, the CSPRNG failed hard.
552
            throw OAuthServerException::serverError('Could not generate a random string', $e);
553
        }
554
        // @codeCoverageIgnoreEnd
555
    }
556
557
    /**
558
     * {@inheritdoc}
559
     */
560 5
    public function canRespondToAccessTokenRequest(ServerRequestInterface $request): bool
561
    {
562 5
        $requestParameters = (array) $request->getParsedBody();
563
564 5
        return (
565 5
            array_key_exists('grant_type', $requestParameters)
566 5
            && $requestParameters['grant_type'] === $this->getIdentifier()
567 5
        );
568
    }
569
570
    /**
571
     * {@inheritdoc}
572
     */
573 1
    public function canRespondToAuthorizationRequest(ServerRequestInterface $request): bool
574
    {
575 1
        return false;
576
    }
577
578
    /**
579
     * {@inheritdoc}
580
     */
581 1
    public function validateAuthorizationRequest(ServerRequestInterface $request): AuthorizationRequestInterface
582
    {
583 1
        throw new LogicException('This grant cannot validate an authorization request');
584
    }
585
586
    /**
587
     * {@inheritdoc}
588
     */
589 1
    public function completeAuthorizationRequest(AuthorizationRequestInterface $authorizationRequest): ResponseTypeInterface
590
    {
591 1
        throw new LogicException('This grant cannot complete an authorization request');
592
    }
593
594
    /**
595
     * {@inheritdoc}
596
     */
597
    public function canRespondToDeviceAuthorizationRequest(ServerRequestInterface $request): bool
598
    {
599
        return false;
600
    }
601
602
    /**
603
     * {@inheritdoc}
604
     */
605
    public function respondToDeviceAuthorizationRequest(ServerRequestInterface $request): DeviceCodeResponse
606
    {
607
        throw new LogicException('This grant cannot validate a device authorization request');
608
    }
609
610
    /**
611
     * {@inheritdoc}
612
     */
613
    public function completeDeviceAuthorizationRequest(string $deviceCode, string $userId, bool $userApproved): void
614
    {
615
        throw new LogicException('This grant cannot complete a device authorization request');
616
    }
617
618
    /**
619
     * {@inheritdoc}
620
     */
621
    public function setIntervalVisibility(bool $intervalVisibility): void
622
    {
623
        throw new LogicException('This grant does not support the interval parameter');
624
    }
625
626
    /**
627
     * {@inheritdoc}
628
     */
629
    public function getIntervalVisibility(): bool
630
    {
631
        return false;
632
    }
633
634
    /**
635
     * {@inheritdoc}
636
     */
637
    public function setIncludeVerificationUriComplete(bool $includeVerificationUriComplete): void
638
    {
639
        throw new LogicException('This grant does not support the verification_uri_complete parameter');
640
    }
641
}
642