From 0199ed68a4be8a55ec06212c466ed6a6f29b78e2 Mon Sep 17 00:00:00 2001 From: Christian Weiske Date: Wed, 3 Aug 2016 21:56:27 +0200 Subject: [PATCH] authentication works --- data/templates/auth-choose.htm | 46 +++++++++++ data/templates/auth-index.htm | 18 +++++ src/anoweco/Storage.php | 31 ++++++++ www/auth.php | 134 +++++++++++++++++++++++++++++++-- www/user.php | 2 +- www/www-header.php | 49 ++++++++++++ 6 files changed, 271 insertions(+), 9 deletions(-) create mode 100644 data/templates/auth-choose.htm create mode 100644 data/templates/auth-index.htm diff --git a/data/templates/auth-choose.htm b/data/templates/auth-choose.htm new file mode 100644 index 0000000..6aab92b --- /dev/null +++ b/data/templates/auth-choose.htm @@ -0,0 +1,46 @@ + + + + Select your identity + + +

Select your identity

+

+ You may log in to {{client_id}} with any identity you like. +

+ +
+ {% for key, value in auth %} + + {% endfor %} + + + +
+ + diff --git a/data/templates/auth-index.htm b/data/templates/auth-index.htm new file mode 100644 index 0000000..ce6329b --- /dev/null +++ b/data/templates/auth-index.htm @@ -0,0 +1,18 @@ + + + + IndieAuth endpoint + + +

IndieAuth endpoint

+

+ This is an anonymous authentication and authorization + endpoint for the IndieAuth protocol. +

+

+ When asked about your website on an IndieAuth login screen, + simply type: +

+
{{baseurl}}
+ + diff --git a/src/anoweco/Storage.php b/src/anoweco/Storage.php index a843774..f9a0f4f 100644 --- a/src/anoweco/Storage.php +++ b/src/anoweco/Storage.php @@ -97,5 +97,36 @@ class Storage } return $row; } + + public function findUser($name, $imageurl) + { + $stmt = $this->db->prepare( + 'SELECT user_id FROM users' + . ' WHERE user_name = ? AND user_imageurl = ?' + ); + $stmt->execute([$name, $imageurl]); + $row = $stmt->fetchObject(); + + if ($row === false) { + return null; + } + return $row->user_id; + } + + public function createUser($name, $imageurl) + { + $stmt = $this->db->prepare( + 'INSERT INTO users SET' + . ' user_name = :name' + . ', user_imageurl = :imageurl' + ); + $stmt->execute( + array( + ':name' => $name, + ':imageurl' => $imageurl, + ) + ); + return $this->db->lastInsertId(); + } } ?> diff --git a/www/auth.php b/www/auth.php index 847fbb4..fbad17e 100644 --- a/www/auth.php +++ b/www/auth.php @@ -1,16 +1,134 @@ + */ +header('HTTP/1.0 500 Internal Server Error'); +require 'www-header.php'; + +function getOrCreateUser($mode, $name, $email) +{ + if ($mode == 'anonymous') { + $name = 'Anonymous'; + $email = ''; + } else { + if ($name == '') { + $name = 'Anonymous'; + } + } + $imageurl = getImageUrl($email); + + $storage = new Storage(); + $id = $storage->findUser($name, $imageurl); + if ($id !== null) { + return $id; + } + $id = $storage->createUser($name, $imageurl); + return $id; +} + +function getImageUrl($email) +{ + //FIXME: libravatar + return Urls::userImg((object)['user_imageurl' => '']); +} header('IndieAuth: authorization_endpoint'); if ($_SERVER['REQUEST_METHOD'] == 'GET') { - //var_dump($_GET);die(); - $token = 'keinthema'; - $url = new Net_URL2($_GET['redirect_uri']); - $url->setQueryVariable('code', $token); - $url->setQueryVariable('me', $_GET['me']); - $url->setQueryVariable('state', $_GET['state']); - header('Location: ' . $url->getURL()); + if (count($_GET) == 0) { + //no parameters - show the index page + header('HTTP/1.0 200 OK'); + header('Content-Type: text/html'); + render('auth-index', ['baseurl' => Urls::full('/')]); + exit(); + } + + $me = verifyUrlParameter($_GET, 'me'); + $redirect_uri = verifyUrlParameter($_GET, 'redirect_uri'); + $client_id = verifyUrlParameter($_GET, 'client_id'); + $state = getOptionalParameter($_GET, 'state', null); + $response_type = getOptionalParameter($_GET, 'response_type', 'id'); + $scope = getOptionalParameter($_GET, 'scope', null); + + //FIXME: if $me is an actual user, load his data + + //let the user choose his identity + header('HTTP/1.0 200 OK'); + render( + 'auth-choose', + array( + 'auth' => array( + 'redirect_uri' => $redirect_uri, + 'client_id' => $client_id, + 'state' => $state, + 'response_type' => $response_type, + 'scope' => $scope, + ), + 'formaction' => '/auth.php?action=login', + ) + ); exit(); } else if ($_SERVER['REQUEST_METHOD'] == 'POST') { + if (isset($_GET['action']) && $_GET['action'] == 'login') { + //log the user in + $auth = $_POST['auth']; + $redirect_uri = verifyUrlParameter($auth, 'redirect_uri'); + $client_id = verifyUrlParameter($auth, 'client_id'); + $state = getOptionalParameter($auth, 'state', null); + $response_type = getOptionalParameter($auth, 'response_type', 'id'); + $scope = getOptionalParameter($auth, 'scope', null); + + $id = $_POST['id']; + verifyParameter($id, 'mode'); + + $userId = getOrCreateUser( + $id['mode'], trim($id['name']), trim($id['email']) + ); + $me = Urls::full(Urls::user($userId)); + + $code = base64_encode( + http_build_query( + [ + 'emoji' => '\360\237\222\251', + 'me' => $me, + 'scope' => $scope, + 'signature' => 'FIXME', + ] + ) + ); + + //redirect back to client + $url = new \Net_URL2($redirect_uri); + if ($response_type == 'code') { + $url->setQueryVariable('code', $code); + } + $url->setQueryVariable('me', $me); + $url->setQueryVariable('state', $state); + header('Location: ' . $url->getURL()); + exit(); + } else { + //auth code verification + $code = base64_decode(verifyParameter($_POST, 'code')); + $redirect_uri = verifyUrlParameter($_POST, 'redirect_uri'); + $client_id = verifyUrlParameter($_POST, 'client_id'); + $state = getOptionalParameter($_POST, 'state', null); + + parse_str($code, $codeParts); + $emoji = verifyParameter($codeParts, 'emoji'); + $signature = verifyParameter($codeParts, 'signature'); + $me = verifyUrlParameter($codeParts, 'me'); + if ($emoji != '\360\237\222\251') { + error('Dog poo missing'); + } + if ($signature != 'FIXME') { + error('Invalid signature'); + } + header('HTTP/1.0 200 OK'); + header('Content-type: application/x-www-form-urlencoded'); + echo http_build_query(['me' => $me]); + exit(); + } } ?> diff --git a/www/user.php b/www/user.php index 95b817f..691933a 100644 --- a/www/user.php +++ b/www/user.php @@ -18,7 +18,7 @@ if (!is_numeric($_GET['id'])) { $id = intval($_GET['id']); $storage = new Storage(); -$rowUser = $storage->getUser($id); +$rowUser = $storage->getUser($id); if ($rowUser === null) { header('HTTP/1.0 404 Not Found'); header('Content-Type: text/plain'); diff --git a/www/www-header.php b/www/www-header.php index a221ca5..1b4a685 100644 --- a/www/www-header.php +++ b/www/www-header.php @@ -13,6 +13,55 @@ $twig = new \Twig_Environment( ); //$twig->addExtension(new Twig_Extension_Debug()); +function verifyParameter($givenParams, $paramName) +{ + if (!isset($givenParams[$paramName])) { + error('"' . $paramName . '" parameter missing'); + } + return $givenParams[$paramName]; +} + +function verifyUrlParameter($givenParams, $paramName) +{ + verifyParameter($givenParams, $paramName); + $url = parse_url($givenParams[$paramName]); + if (!isset($url['scheme'])) { + error('Invalid URL in "' . $paramName . '" parameter: scheme missing'); + } + if (!isset($url['host'])) { + error('Invalid URL in "' . $paramName . '" parameter: host missing'); + } + + return $givenParams[$paramName]; +} + +function getOptionalParameter($givenParams, $paramName, $default) +{ + if (!isset($givenParams[$paramName])) { + return $default; + } + return $givenParams[$paramName]; +} + +/** + * Send out an error + * + * @param string $status HTTP status code line + * @param string $code One of the allowed status types: + * - forbidden + * - insufficient_scope + * - invalid_request + * - not_found + * @param string $description + */ +function error($description, $status = 'HTTP/1.0 400 Bad Request') +{ + header($status); + header('Content-Type: text/plain'); + echo $description . "\n"; + exit(1); +} + function render($tplname, $vars = array(), $return = false) { $template = $GLOBALS['twig']->loadTemplate($tplname . '.htm'); -- 2.30.2