authentication works
authorChristian Weiske <cweiske@cweiske.de>
Wed, 3 Aug 2016 19:56:27 +0000 (21:56 +0200)
committerChristian Weiske <cweiske@cweiske.de>
Wed, 3 Aug 2016 19:56:27 +0000 (21:56 +0200)
data/templates/auth-choose.htm [new file with mode: 0644]
data/templates/auth-index.htm [new file with mode: 0644]
src/anoweco/Storage.php
www/auth.php
www/user.php
www/www-header.php

diff --git a/data/templates/auth-choose.htm b/data/templates/auth-choose.htm
new file mode 100644 (file)
index 0000000..6aab92b
--- /dev/null
@@ -0,0 +1,46 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+  <meta charset="utf-8"/>
+  <title>Select your identity</title>
+ </head>
+ <body>
+  <h1>Select your identity</h1>
+  <p>
+   You may log in to {{client_id}} with any identity you like.
+  </p>
+
+  <form method="post" action="{{formaction}}">
+   {% for key, value in auth %}
+   <input type="hidden" name="auth[{{key}}]" value="{{value}}"/>
+   {% endfor %}
+
+   <ul>
+    <li>
+     <label>
+      <input type="radio" name="id[mode]" value="anonymous"/> Anonymous
+     </label>
+    </li>
+    <li>
+     <label>
+      <input type="radio" name="id[mode]" value="data"/> With a name
+     </label>
+     <ul>
+      <li>
+       <label for="id-name">Name:</label>
+       <input type="text" name="id[name]" id="id-name"/>
+      </li>
+      <li>
+       <label for="id-email">E-Mail:</label>
+       <input type="email" name="id[email]" id="id-email"/>
+       (used to fetch avatar image. not stored.)
+      </li>      
+     </ul>
+     <p>
+      All values are optional.
+     </p>
+    </li>
+   </ul>
+   <button type="submit">Login</button>
+  </form>
+ </body>
+</html>
diff --git a/data/templates/auth-index.htm b/data/templates/auth-index.htm
new file mode 100644 (file)
index 0000000..ce6329b
--- /dev/null
@@ -0,0 +1,18 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+  <meta charset="utf-8"/>
+  <title>IndieAuth endpoint</title>
+ </head>
+ <body>
+  <h1>IndieAuth endpoint</h1>
+  <p>
+   This is an anonymous authentication and authorization
+   endpoint for the IndieAuth protocol.
+  </p>
+  <p>
+   When asked about your website on an IndieAuth login screen,
+   simply type:
+  </p>
+  <pre>{{baseurl}}</pre>
+ </body>
+</html>
index a843774..f9a0f4f 100644 (file)
@@ -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();
+    }
 }
 ?>
index 847fbb4..fbad17e 100644 (file)
 <?php
-require_once 'Net/URL2.php';
+namespace anoweco;
+/**
+ * IndieAuth endpoint
+ *
+ * @author Christian Weiske <cweiske@cweiske.de>
+ */
+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();
+    }
 }
 ?>
index 95b817f..691933a 100644 (file)
@@ -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');
index a221ca5..1b4a685 100644 (file)
@@ -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');