From 7812d56d024c68286b9fa7d28a46aa881e62b539 Mon Sep 17 00:00:00 2001 From: Mark Wane Date: Mon, 15 Jul 2024 20:16:00 +0100 Subject: [PATCH] Initial commit --- .gitignore | 1 + .htaccess | 1 + form.php | 33 +++++++++++++ functions.php | 80 ++++++++++++++++++++++++++++++ index.php | 132 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 247 insertions(+) create mode 100644 .gitignore create mode 100644 .htaccess create mode 100644 form.php create mode 100644 functions.php create mode 100644 index.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4f4773f --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +config.php diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..ebb215c --- /dev/null +++ b/.htaccess @@ -0,0 +1 @@ +PerlHeaderParserHandler Lemonldap::NG::Handler::ApacheMP2 diff --git a/form.php b/form.php new file mode 100644 index 0000000..e07fcf2 --- /dev/null +++ b/form.php @@ -0,0 +1,33 @@ + + + + IndieAuth + + + +

IndieAuth

+ +

Error

+

+ +

Authenticate

+
+

You are about to login with client

+ 0) : ?> +

With the following scopes

+
+ Scopes + $checkbox) : ?> +
+ + +
+ +
+ +

After login you will be redirected to

+ + +
+ + diff --git a/functions.php b/functions.php new file mode 100644 index 0000000..b7bc512 --- /dev/null +++ b/functions.php @@ -0,0 +1,80 @@ + $expires) { + return false; + } + $body = $message . $expires . base64_url_decode($code_parts[2]); + $signature = hash_hmac("sha256", $body, $key); + return hash_equals($signature, $code_parts[1]); +} + +function filter_input_regexp($type, $variable, $regexp, $flags = null) { + $options = [ + "options" => ["regexp" => $regexp], + ]; + if ($flags !== null) { + $options["flags"] = $flags; + } + return filter_input($type, $variable, FILTER_VALIDATE_REGEXP, $options); +} + +function get_q_value($mime, $accept) { + $fulltype = preg_replace('@^([^/]+\/).+$@', '$1*', $mime); + $regex = implode("", [ + "/(?<=^|,)\s*(\*\/\*|", + preg_quote($fulltype, "/"), + "|", + preg_quote($mime, "/"), + ')\s*(?:[^,]*?;\s*q\s*=\s*([0-9.]+))?\s*(?:,|$)/', + ]); + $out = preg_match_all($regex, $accept, $matches); + $types = array_combine($matches[1], $matches[2]); + if (array_key_exists($mime, $types)) { + $q = $types[$mime]; + } elseif (array_key_exists($fulltype, $types)) { + $q = $types[$fulltype]; + } elseif (array_key_exists("*/*", $types)) { + $q = $types["*/*"]; + } else { + return 0; + } + return $q === "" ? 1 : floatval($q); +} + +function base64_url_encode($string) { + $string = base64_encode($string); + $string = rtrim($string, "="); + $string = strtr($string, "+/", "-_"); + return $string; +} + +function base64_url_decode($string) { + $string = strtr($string, "-_", "+/"); + $padding = strlen($string) % 4; + if ($padding !== 0) { + $string .= str_repeat("=", 4 - $padding); + } + $string = base64_decode($string); + return $string; +} diff --git a/index.php b/index.php new file mode 100644 index 0000000..9a4a22f --- /dev/null +++ b/index.php @@ -0,0 +1,132 @@ + USER_URL); + $code_parts = explode(':', $code, 3); + $accept_header = $_SERVER['HTTP_ACCEPT'] ?: '*/*'; + + if ( '' !== $code_parts[2] ) { + $response['scope'] = base64_url_decode($code_parts[2]); + } + + $json = get_q_value('application/json', $accept_header); + $form = get_q_value('application/x-www-form-urlencoded', $accept_header); + + if ( 0 === $json && 0 === $form ){ + $error = 'Client does not accept JSON or Form encoded responses'; + http_response_code(406); + include 'form.php'; + die(); + } elseif ( $json >= $form ){ + header('Content-Type: application/json'); + exit( json_encode($response) ); + } else { + header('Content-Type: application/x-www-form-urlencoded'); + exit( http_build_query($response) ); + } +} + +// No code submitted, +// Check login +if ( is_null($_SERVER["REMOTE_USER"]) ) { + $error = 'Not logged in. Login on the SSO portal'; + http_response_code(403); + include 'form.php'; + die(); +} elseif ( USER_NAME !== $_SERVER["REMOTE_USER"] ){ + $error = 'This system is for ' . USER_NAME . ' only.'; + http_response_code(403); + include 'form.php'; + die(); +} +// Filter client data +$me = filter_input( INPUT_GET, 'me', FILTER_VALIDATE_URL ); +$client_id = filter_input( INPUT_GET, 'client_id', FILTER_VALIDATE_URL ); +$redirect_uri = filter_input( INPUT_GET, 'redirect_uri', FILTER_VALIDATE_URL ); +$state = filter_input_regexp( INPUT_GET, 'state', '@^[\x20-\x7E]*$@' ); +$response_type = filter_input_regexp( INPUT_GET, 'response_type', '@^(id|code)?$@' ); +$scope = filter_input_regexp( INPUT_GET, 'scope', '@^([\x21\x23-\x5B\x5D-\x7E]+( [\x21\x23-\x5B\x5D-\x7E]+)*)?$@' ); + +if ( ! is_string($client_id) ) { + $error .= 'Invalid Client ID
'; +} +if ( ! is_string($redirect_uri) ){ + $error .= 'Invalid redirect URI
'; +} +if ( false === $state ) { + $error .= 'Invalid state
'; +} +if ( false === $response_type ) { + $error .= 'Invalid response type
'; +} + +if ( $error ){ + http_response_code(400); + include 'form.php'; + die(); +} + +$csrf_code = filter_input( INPUT_POST, '_csrf', FILTER_UNSAFE_RAW ); +// If form submitted +if ( ! is_null($csrf_code) ) { + if ( !verify_signed_code( APP_KEY, $client_id . $redirect_uri . $state, $csrf_code ) ){ + $error = 'Invalid CSFR code'; + http_response_code(400); + include 'form.php'; + die(); + } + + $scope = filter_input_regexp( INPUT_POST, 'scopes', '@^[\x21\x23-\x5B\x5D-\x7E]+$@', FILTER_REQUIRE_ARRAY ); + if ( ! is_null($scope) ) { + if ( false === $scope || in_array( false, $scope, true ) ) { + $error = 'Provided scopes contain illegal characters'; + } + $scope = implode( ' ', $scope ); + } + + $code = create_signed_code( APP_KEY, USER_URL . $redirect_uri . $client_id, 5 * 60, $scope ); + + $final_redir = $redirect_uri; + if ( strpos($redirect_uri, '?') === false ) { + $final_redir .= '?'; + } else { + $final_redir .= '&'; + } + + $parameters = array( + 'code' => $code, + 'me' => USER_URL + ); + if ( ! is_null($state) ) { + $parameters['state'] = $state; + } + $final_redir .= http_build_query($parameters); + header('Location: ' . $final_redir, true, 302); +} else { + $csrf_code = create_signed_code(APP_KEY, $client_id . $redirect_uri . $state, 2 * 60); + include 'form.php'; +}