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!

+
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ Authentication: + + + +
+
+ Grant type: + + +
+
+ + +
+
+ +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. +

+
+ +
+
+ +
+
+ +
+
+ +
+{$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!

+
+ +
+
+ +
+
+ +
+
+ +
+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™