Skip to content

Commit

Permalink
encrypt / decrypt the device code
Browse files Browse the repository at this point in the history
formatting

formatting

simplify tests

revert example changes

formatting

fix style

fix sa

encrypt device code

Revert "no encryption on this grant"

This reverts commit 7522a46.
  • Loading branch information
hafezdivandari committed Nov 12, 2024
1 parent 7522a46 commit c9639be
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 57 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Support for PHP 8.4 (PR #1454)

### Fixed
- Fixed bug where scopes were not set on access token when using device authorization grant (PR #1412)
- Fixed device code encryption / decryption and bug where scopes were not set on access token when using device authorization grant (PR #1412)

## [9.0.1] - released 2024-10-14
### Fixed
Expand Down
9 changes: 0 additions & 9 deletions examples/src/Repositories/DeviceCodeRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
use League\OAuth2\Server\Repositories\DeviceCodeRepositoryInterface;
use OAuth2ServerExamples\Entities\ClientEntity;
use OAuth2ServerExamples\Entities\DeviceCodeEntity;
use OAuth2ServerExamples\Entities\ScopeEntity;

class DeviceCodeRepository implements DeviceCodeRepositoryInterface
{
Expand Down Expand Up @@ -50,14 +49,6 @@ public function getDeviceCodeEntityByDeviceCode($deviceCode): ?DeviceCodeEntityI
$deviceCodeEntity->setIdentifier($deviceCode);
$deviceCodeEntity->setExpiryDateTime(new DateTimeImmutable('now +1 hour'));
$deviceCodeEntity->setClient($clientEntity);
$deviceCodeEntity->setLastPolledAt(new DateTimeImmutable());

$scopes = [];
foreach ($scopes as $scope) {
$scopeEntity = new ScopeEntity();
$scopeEntity->setIdentifier($scope);
$deviceCodeEntity->addScope($scopeEntity);
}

// The user identifier should be set when the user authenticates on the
// OAuth server, along with whether they approved the request
Expand Down
46 changes: 34 additions & 12 deletions src/Grant/DeviceCodeGrant.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
use League\OAuth2\Server\RequestEvent;
use League\OAuth2\Server\ResponseTypes\DeviceCodeResponse;
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
use LogicException;
use Psr\Http\Message\ServerRequestInterface;
use TypeError;

Expand Down Expand Up @@ -96,6 +97,7 @@ public function respondToDeviceAuthorizationRequest(ServerRequestInterface $requ
);

$response = new DeviceCodeResponse();
$response->setEncryptionKey($this->encryptionKey);

if ($this->includeVerificationUriComplete === true) {
$response->includeVerificationUriComplete();
Expand Down Expand Up @@ -177,38 +179,58 @@ public function respondToAccessTokenRequest(
*/
protected function validateDeviceCode(ServerRequestInterface $request, ClientEntityInterface $client): DeviceCodeEntityInterface
{
$deviceCode = $this->getRequestParameter('device_code', $request);
$encryptedDeviceCode = $this->getRequestParameter('device_code', $request);

if (is_null($deviceCode)) {
if (is_null($encryptedDeviceCode)) {
throw OAuthServerException::invalidRequest('device_code');
}

$deviceCodeEntity = $this->deviceCodeRepository->getDeviceCodeEntityByDeviceCode(
$deviceCode
);

if ($deviceCodeEntity instanceof DeviceCodeEntityInterface === false) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request));
try {
$deviceCodePayload = json_decode($this->decrypt($encryptedDeviceCode));
} catch (LogicException $e) {
throw OAuthServerException::invalidRequest('code', 'Cannot decrypt the device code', $e);
}

throw OAuthServerException::invalidGrant();
if (!property_exists($deviceCodePayload, 'device_code_id')) {
throw OAuthServerException::invalidRequest('device_code', 'Device code malformed');
}

if (time() > $deviceCodeEntity->getExpiryDateTime()->getTimestamp()) {
if (time() > $deviceCodePayload->expire_time) {
throw OAuthServerException::expiredToken('device_code');
}

if ($this->deviceCodeRepository->isDeviceCodeRevoked($deviceCode) === true) {
if ($this->deviceCodeRepository->isDeviceCodeRevoked($deviceCodePayload->device_code_id) === true) {
throw OAuthServerException::invalidRequest('device_code', 'Device code has been revoked');
}

if ($deviceCodeEntity->getClient()->getIdentifier() !== $client->getIdentifier()) {
if ($deviceCodePayload->client_id !== $client->getIdentifier()) {
throw OAuthServerException::invalidRequest('device_code', 'Device code was not issued to this client');
}

$deviceCodeEntity = $this->deviceCodeRepository->getDeviceCodeEntityByDeviceCode(
$deviceCodePayload->device_code_id
);

if ($deviceCodeEntity instanceof DeviceCodeEntityInterface === false) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request));

throw OAuthServerException::invalidGrant();
}

if ($this->deviceCodePolledTooSoon($deviceCodeEntity->getLastPolledAt()) === true) {
throw OAuthServerException::slowDown();
}

$deviceCodeEntity->setIdentifier($deviceCodePayload->device_code_id);
$deviceCodeEntity->setClient($client);
$deviceCodeEntity->setExpiryDateTime((new DateTimeImmutable())->setTimestamp($deviceCodePayload->expire_time));

$scopes = $this->validateScopes($deviceCodePayload->scopes);

foreach ($scopes as $scope) {
$deviceCodeEntity->addScope($scope);
}

return $deviceCodeEntity;
}

Expand Down
15 changes: 14 additions & 1 deletion src/ResponseTypes/DeviceCodeResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,21 @@ public function generateHttpResponse(ResponseInterface $response): ResponseInter
{
$expireDateTime = $this->deviceCodeEntity->getExpiryDateTime()->getTimestamp();

$payload = [
'client_id' => $this->deviceCodeEntity->getClient()->getIdentifier(),
'device_code_id' => $this->deviceCodeEntity->getIdentifier(),
'scopes' => $this->deviceCodeEntity->getScopes(),
'expire_time' => $expireDateTime,
];

$jsonPayload = json_encode($payload);

if ($jsonPayload === false) {
throw new LogicException('An error was encountered when JSON encoding the device code request response');
}

$responseParams = [
'device_code' => $this->deviceCodeEntity->getIdentifier(),
'device_code' => $this->encrypt($jsonPayload),
'user_code' => $this->deviceCodeEntity->getUserCode(),
'verification_uri' => $this->deviceCodeEntity->getVerificationUri(),
'expires_in' => $expireDateTime - time(),
Expand Down
Loading

0 comments on commit c9639be

Please sign in to comment.