Skip to content

Commit 34a14d3

Browse files
authored
Merge pull request #1410 from hafezdivandari/master-fix-some-bugs
Fix including interval in response of device authorization request
2 parents 902d5f2 + 03dcdd7 commit 34a14d3

File tree

4 files changed

+71
-15
lines changed

4 files changed

+71
-15
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1616
- In the Auth Code grant, when requesting an access token with an invalid auth code, we now respond with an invalid_grant error instead of invalid_request (PR #1433)
1717
- Fixed spec compliance issue where device access token request was mistakenly expecting to receive scopes in the request (PR #1412)
1818
- Refresh tokens pre version 9 might have had user IDs set as ints which meant they were incorrectly rejected. We now cast these values to strings to allow old refresh tokens (PR #1436)
19+
- Fixed bug on setting interval visibility of device authorization grant (PR #1410)
20+
- Fix a bug where the new poll date were not persisted when `slow_down` error happens, because the exception is thrown before calling `persistDeviceCode`. (PR #1410)
21+
- Fix a bug where `slow_down` error response may have been returned even after the user has completed the auth flow (already approved / denied the request). (PR #1410)
1922

2023
## [9.0.1] - released 2024-10-14
2124
### Fixed

src/Grant/DeviceCodeGrant.php

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public function __construct(
5151
RefreshTokenRepositoryInterface $refreshTokenRepository,
5252
private DateInterval $deviceCodeTTL,
5353
string $verificationUri,
54-
private int $retryInterval = 5
54+
private readonly int $retryInterval = 5
5555
) {
5656
$this->setDeviceCodeRepository($deviceCodeRepository);
5757
$this->setRefreshTokenRepository($refreshTokenRepository);
@@ -101,6 +101,10 @@ public function respondToDeviceAuthorizationRequest(ServerRequestInterface $requ
101101
$response->includeVerificationUriComplete();
102102
}
103103

104+
if ($this->intervalVisibility === true) {
105+
$response->includeInterval();
106+
}
107+
104108
$response->setDeviceCodeEntity($deviceCodeEntity);
105109

106110
return $response;
@@ -139,11 +143,17 @@ public function respondToAccessTokenRequest(
139143
$client = $this->validateClient($request);
140144
$deviceCodeEntity = $this->validateDeviceCode($request, $client);
141145

142-
$deviceCodeEntity->setLastPolledAt(new DateTimeImmutable());
143-
$this->deviceCodeRepository->persistDeviceCode($deviceCodeEntity);
144-
145-
// If device code has no user associated, respond with pending
146+
// If device code has no user associated, respond with pending or slow down
146147
if (is_null($deviceCodeEntity->getUserIdentifier())) {
148+
$shouldSlowDown = $this->deviceCodePolledTooSoon($deviceCodeEntity->getLastPolledAt());
149+
150+
$deviceCodeEntity->setLastPolledAt(new DateTimeImmutable());
151+
$this->deviceCodeRepository->persistDeviceCode($deviceCodeEntity);
152+
153+
if ($shouldSlowDown) {
154+
throw OAuthServerException::slowDown();
155+
}
156+
147157
throw OAuthServerException::authorizationPending();
148158
}
149159

@@ -205,10 +215,6 @@ protected function validateDeviceCode(ServerRequestInterface $request, ClientEnt
205215
throw OAuthServerException::invalidRequest('device_code', 'Device code was not issued to this client');
206216
}
207217

208-
if ($this->deviceCodePolledTooSoon($deviceCodeEntity->getLastPolledAt()) === true) {
209-
throw OAuthServerException::slowDown();
210-
}
211-
212218
return $deviceCodeEntity;
213219
}
214220

@@ -258,10 +264,7 @@ protected function issueDeviceCode(
258264
$deviceCode->setExpiryDateTime((new DateTimeImmutable())->add($deviceCodeTTL));
259265
$deviceCode->setClient($client);
260266
$deviceCode->setVerificationUri($verificationUri);
261-
262-
if ($this->getIntervalVisibility() === true) {
263-
$deviceCode->setInterval($this->retryInterval);
264-
}
267+
$deviceCode->setInterval($this->retryInterval);
265268

266269
foreach ($scopes as $scope) {
267270
$deviceCode->addScope($scope);

src/ResponseTypes/DeviceCodeResponse.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class DeviceCodeResponse extends AbstractResponseType
2525
{
2626
protected DeviceCodeEntityInterface $deviceCodeEntity;
2727
private bool $includeVerificationUriComplete = false;
28-
private const DEFAULT_INTERVAL = 5;
28+
private bool $includeInterval = false;
2929

3030
/**
3131
* {@inheritdoc}
@@ -45,7 +45,7 @@ public function generateHttpResponse(ResponseInterface $response): ResponseInter
4545
$responseParams['verification_uri_complete'] = $this->deviceCodeEntity->getVerificationUriComplete();
4646
}
4747

48-
if ($this->deviceCodeEntity->getInterval() !== self::DEFAULT_INTERVAL) {
48+
if ($this->includeInterval === true) {
4949
$responseParams['interval'] = $this->deviceCodeEntity->getInterval();
5050
}
5151

@@ -79,6 +79,11 @@ public function includeVerificationUriComplete(): void
7979
$this->includeVerificationUriComplete = true;
8080
}
8181

82+
public function includeInterval(): void
83+
{
84+
$this->includeInterval = true;
85+
}
86+
8287
/**
8388
* Add custom fields to your Bearer Token response here, then override
8489
* AuthorizationServer::getResponseType() to pull in your version of

tests/Grant/DeviceCodeGrantTest.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,51 @@ public function testSettingDeviceCodeIntervalRate(): void
656656
$this::assertEquals(self::INTERVAL_RATE, $deviceCode->interval);
657657
}
658658

659+
public function testSettingInternalVisibility(): void
660+
{
661+
$client = new ClientEntity();
662+
$client->setIdentifier('foo');
663+
664+
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
665+
$clientRepositoryMock->method('getClientEntity')->willReturn($client);
666+
667+
$deviceCode = new DeviceCodeEntity();
668+
669+
$deviceCodeRepository = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock();
670+
$deviceCodeRepository->method('getNewDeviceCode')->willReturn($deviceCode);
671+
672+
$scope = new ScopeEntity();
673+
$scope->setIdentifier('basic');
674+
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
675+
$scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope);
676+
677+
$grant = new DeviceCodeGrant(
678+
$deviceCodeRepository,
679+
$this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(),
680+
new DateInterval('PT10M'),
681+
'http://foo/bar',
682+
);
683+
684+
$grant->setClientRepository($clientRepositoryMock);
685+
$grant->setDefaultScope(self::DEFAULT_SCOPE);
686+
$grant->setScopeRepository($scopeRepositoryMock);
687+
$grant->setIntervalVisibility(true);
688+
689+
$request = (new ServerRequest())->withParsedBody([
690+
'client_id' => 'foo',
691+
'scope' => 'basic',
692+
]);
693+
694+
$deviceCodeResponse = $grant
695+
->respondToDeviceAuthorizationRequest($request)
696+
->generateHttpResponse(new Response());
697+
698+
$deviceCode = json_decode((string) $deviceCodeResponse->getBody());
699+
700+
$this::assertObjectHasProperty('interval', $deviceCode);
701+
$this::assertEquals(5, $deviceCode->interval);
702+
}
703+
659704
public function testIssueAccessDeniedError(): void
660705
{
661706
$client = new ClientEntity();

0 commit comments

Comments
 (0)