From 0973cd31c37deb400302218dbc6cceb15a56da15 Mon Sep 17 00:00:00 2001
From: flashwave
Date: Sat, 20 Jul 2024 18:48:31 +0000
Subject: [PATCH] and finally the device code flow!
---
oatmeal.php | 2 +-
src/DeviceCodeRoutes.php | 359 ++++++++++++++++++++++++++++++++++++++-
src/HomeRoutes.php | 4 +-
3 files changed, 359 insertions(+), 6 deletions(-)
diff --git a/oatmeal.php b/oatmeal.php
index d5d453e..6713a12 100644
--- a/oatmeal.php
+++ b/oatmeal.php
@@ -27,4 +27,4 @@ $oatmeal->register(new HomeRoutes);
$oatmeal->register(new AuthzCodeRoutes($oatmeal->getCSRFP()));
$oatmeal->register(new RefreshTokenRoutes($oatmeal->getCSRFP()));
$oatmeal->register(new ClientCredsRoutes($oatmeal->getCSRFP()));
-$oatmeal->register(new DeviceCodeRoutes);
+$oatmeal->register(new DeviceCodeRoutes($oatmeal->getCSRFP()));
diff --git a/src/DeviceCodeRoutes.php b/src/DeviceCodeRoutes.php
index d8fac98..f679e54 100644
--- a/src/DeviceCodeRoutes.php
+++ b/src/DeviceCodeRoutes.php
@@ -1,11 +1,364 @@
redirect('/device_code/authorise');
+ }
+
+ #[HttpGet('/device_code/authorise')]
+ public function getAuthorise(): string {
+ return <<
+
+Oatmeal / Device code
+
+Oatmeal / Device code
+
+ Device codes allow you to do authentications on device where its impractical or impossible to enter your password.
+ I will also be using it as a way to secure do authentication over plain HTTP!
+
+
+Return
+HTML;
+ }
+
+ #[HttpPost('/device_code/authorise')]
+ public function postAuthorise($response, $request) {
+ if(!$request->isFormContent())
+ return 400;
+ $content = $request->getContent();
+
+ $csrfp = (string)$content->getParam('csrfp');
+ if(!$this->csrfp->verifyToken($csrfp))
+ return 403;
+
+ $authoriseUri = (string)$content->getParam('device_authorise_uri');
+ if(filter_var($authoriseUri, FILTER_VALIDATE_URL) === false) {
+ $response->setStatusCode(400);
+ return <<
+
+Oatmeal / Device code
+Oatmeal / Device code
+Provided Device Authorise URI was not a valid absolute URI.
+Return
+HTML;
+ }
+
+ $clientId = (string)$content->getParam('client_id');
+ $clientSecret = (string)$content->getParam('client_secret');
+ $scope = (string)$content->getParam('scope');
+
+ $headers = [];
+ $body = ['scope' => $scope];
+
+ if($clientSecret === '')
+ $body['client_id'] = $clientId;
+ else
+ $headers[] = sprintf('Authorization: Basic %s', base64_encode(sprintf('%s:%s', $clientId, $clientSecret)));
+
+ $body = Tools::shuffleArray($body);
+ $response = Tools::fetch($authoriseUri, headers: $headers, body: $body);
+
+ $authoriseUri = htmlspecialchars($authoriseUri);
+ $headers = htmlspecialchars(json_encode($headers, JSON_PRETTY_PRINT));
+ $body = htmlspecialchars(json_encode($body, JSON_PRETTY_PRINT));
+
+ $form = 'Could not display form because the request failed.
';
+
+ $decoded = json_decode($response);
+ if($decoded !== null) {
+ $response = json_encode($decoded, JSON_PRETTY_PRINT);
+
+ $vericationUri = htmlspecialchars($decoded->verification_uri ?? '');
+ $vericationUriComplete = htmlspecialchars($decoded->verification_uri_complete ?? '');
+ $deviceCode = htmlspecialchars($decoded->device_code ?? '');
+ $userCode = htmlspecialchars($decoded->user_code ?? '');
+
+ if(!is_string($deviceCode))
+ $form = 'Could not display form because the device_code
is missing from the response.
';
+ elseif(!is_string($userCode))
+ $form = 'Could not display form because the user_code
is missing from the response.
';
+ elseif(!is_string($vericationUri))
+ $form = 'Could not display form because the verification_uri
is missing from the response.
';
+ else {
+ $form = <<Here's the form I promised!
+
+
+HTML;
+ }
+ }
+
+ $response = htmlspecialchars($response);
+
+ return <<
+
+Oatmeal / Device code
+
+Oatmeal / Device code
+
+ Below is the request and response data from your device authorisation request.
+ Even further below is the form you can use to actually request an access token!
+ Normally you would open verification_uri
on a separate device and enter the user_code
or just scan the verification_uri_complete
as a QR Code or something like that (if present in the response), but you can also just open it in a new tab.
+
+
+
+ Authorise URI (POST):
+
+
+
+
+
+ Headers:
+
+
+
+
+
+ Request body (sent as application/x-www-form-urlencoded, presented as JSON for consistency):
+
+
+
+
+
+ Response body:
+
+
+
+{$form}
+Return
+HTML;
+ }
+
+ #[HttpGet('/device_code/token')]
+ public function getToken($response, $request) {
+ $csrfp = (string)$request->getParam('csrfp');
+ if(!$this->csrfp->verifyToken($csrfp))
+ return 403;
+
+ $tokenUri = (string)$request->getParam('token_uri');
+ if(filter_var($tokenUri, FILTER_VALIDATE_URL) === false) {
+ $response->setStatusCode(400);
+ return <<
+
+Oatmeal / Device code
+Oatmeal / Device code
+Provided Token URI was not a valid absolute URI.
+Return
+HTML;
+ }
+
+ $clientId = (string)$request->getParam('client_id');
+ $clientSecret = (string)$request->getParam('client_secret');
+ $deviceCode = (string)$request->getParam('device_code');
+ $grantType = (string)$request->getParam('type');
+ $auth = (string)$request->getParam('auth');
+
+ $headers = [];
+ $body = [
+ 'grant_type' => $grantType === 'short' ? 'device_code' : 'urn:ietf:params:oauth:grant-type:device_code',
+ 'device_code' => $deviceCode,
+ ];
+
+ if($clientSecret === '')
+ $body['client_id'] = $clientId;
+ elseif($auth === 'body' || ($auth !== 'header' && mt_rand(0, 10) > 5)) {
+ $body['client_id'] = $clientId;
+ $body['client_secret'] = $clientSecret;
+ } else
+ $headers[] = sprintf('Authorization: Basic %s', base64_encode(sprintf('%s:%s', $clientId, $clientSecret)));
+
+ $body = Tools::shuffleArray($body);
+ $response = Tools::fetch($tokenUri, headers: $headers, body: $body);
+
+ $tokenUri = htmlspecialchars($tokenUri);
+ $headers = htmlspecialchars(json_encode($headers, JSON_PRETTY_PRINT));
+ $body = htmlspecialchars(json_encode($body, JSON_PRETTY_PRINT));
+
+ $decoded = json_decode($response);
+ if($decoded !== null)
+ $response = json_encode($decoded, JSON_PRETTY_PRINT);
+
+ $response = htmlspecialchars($response);
+
+ return <<
+
+Oatmeal / Device code
+
+Oatmeal / Device code
+Below is the request and response data from your device code result. If the response contains a refresh_token field, you can move on to the Refresh token flow !
+
+
+ Token URI (POST):
+
+
+
+
+
+ Headers:
+
+
+
+
+
+ Request body (sent as application/x-www-form-urlencoded, presented as JSON for consistency):
+
+
+
+
+
+ Response body:
+
+
+
+Return
+HTML;
}
}
diff --git a/src/HomeRoutes.php b/src/HomeRoutes.php
index 8e143dd..f61a537 100644
--- a/src/HomeRoutes.php
+++ b/src/HomeRoutes.php
@@ -15,8 +15,8 @@ final class HomeRoutes extends RouteHandler {
Hello and welcome to Oatmeal!
This is a quick and dirty utility for testing your OAuth2 server implementation.
- It is tailored to my own Hanyuu project for Flashii ID, however it should do for OAuth2.1 and the Device Code extension.
- I'm not going to bother with making this page look nice, the most you'll get it functional Javascript and/or CSS touch ups if really necessary.
+ It is tailored to my own Hanyuu project for Flashii ID, however it should do for general implementations of OAuth2.1 and the Device Code extension.
+ I'm not going to bother with making this page look nice, the most you'll get is utilitarian Javascript and/or CSS touch ups if really necessary.
Select A Flow™