Passed
Pull Request — master (#1470)
by
unknown
33:14
created

AbstractGrant::getBasicAuthCredentials()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 28
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 6.0106

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 14
nc 6
nop 1
dl 0
loc 28
ccs 14
cts 15
cp 0.9333
crap 6.0106
rs 9.2222
c 1
b 0
f 0
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;
0 ignored issues
show
Bug introduced by
The type League\OAuth2\Server\CryptTrait was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

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