wip
authorChristian Weiske <cweiske@cweiske.de>
Thu, 14 Aug 2014 16:41:34 +0000 (18:41 +0200)
committerChristian Weiske <cweiske@cweiske.de>
Thu, 14 Aug 2014 16:41:34 +0000 (18:41 +0200)
14 files changed:
appinfo/app.php [new file with mode: 0755]
appinfo/application.php [new file with mode: 0644]
appinfo/database.xml [new file with mode: 0755]
appinfo/info.xml [new file with mode: 0755]
appinfo/routes.php [new file with mode: 0644]
appinfo/version [new file with mode: 0755]
controller/accesscontroller.php [new file with mode: 0644]
controller/apicontroller.php [new file with mode: 0644]
controller/oauthcontroller.php [new file with mode: 0644]
img/notes.png [new file with mode: 0644]
index.php [new file with mode: 0755]
src [new symlink]
templates/main.php [new file with mode: 0755]
templates/settings.php [new file with mode: 0755]

diff --git a/appinfo/app.php b/appinfo/app.php
new file mode 100755 (executable)
index 0000000..3e319df
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+require_once (__DIR__ . '/../src/grauphel/Autoloader.php');
+grauphel\Autoloader::register();
+
+//OCP\App::registerAdmin( 'apptemplate', 'settings' );
+
+OCP\App::addNavigationEntry( array( 
+       'id' => 'grauphel',
+       'order' => 2342,
+       'href' => OCP\Util::linkTo( 'grauphel', 'index.php' ),
+       'icon' => OCP\Util::imagePath( 'grauphel', 'notes.png' ),
+       'name' => 'Tomboy notes'
+));
diff --git a/appinfo/application.php b/appinfo/application.php
new file mode 100644 (file)
index 0000000..ecee129
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+namespace OCA\Grauphel\AppInfo;
+use \OCP\AppFramework\App;
+
+class Application extends App
+{
+    public function __construct(array $urlParams=array())
+    {
+        parent::__construct('grauphel', $urlParams);
+
+        $container = $this->getContainer();
+
+        /**
+         * Controllers
+         */
+        $container->registerService(
+           'ApiController',
+            function($c) {
+                return new \OCA\Grauphel\Controller\ApiController(
+                    $c->query('AppName'),
+                    $c->query('Request')
+                );
+            }
+        );
+        $container->registerService(
+           'AccessController',
+            function($c) {
+                 return new \OCA\Grauphel\Controller\AccessController(
+                    $c->query('AppName'),
+                    $c->query('Request')
+                );
+            }
+        );
+        $container->registerService(
+           'OAuthController',
+            function($c) {
+                return new \OCA\Grauphel\Controller\OAuthController(
+                    $c->query('AppName'),
+                    $c->query('Request')
+                );
+            }
+        );
+    }
+}
+?>
diff --git a/appinfo/database.xml b/appinfo/database.xml
new file mode 100755 (executable)
index 0000000..ae089ca
--- /dev/null
@@ -0,0 +1,173 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<database>
+        <name>*dbname*</name>
+        <create>true</create>
+        <overwrite>false</overwrite>
+        <charset>utf8</charset>
+        <table>
+               <name>*dbprefix*grauphel_oauth_tokens</name>
+               <declaration>
+                       <field>
+                               <name>token_id</name>
+                               <type>integer</type>
+                               <default>0</default>
+                               <notnull>true</notnull>
+                               <autoincrement>1</autoincrement>
+                               <length>11</length>
+                       </field>
+                       <field>
+                               <name>token_user</name>
+                               <type>text</type>
+                               <notnull>false</notnull>
+                               <length>64</length>
+                       </field>
+                       <field>
+                               <name>token_type</name>
+                               <type>text</type>
+                               <notnull>true</notnull>
+                               <length>16</length>
+                       </field>
+                       <field>
+                               <name>token_key</name>
+                               <type>text</type>
+                               <notnull>true</notnull>
+                               <length>128</length>
+                       </field>
+                       <field>
+                               <name>token_secret</name>
+                               <type>text</type>
+                               <notnull>true</notnull>
+                               <length>128</length>
+                       </field>
+                       <field>
+                               <name>token_verifier</name>
+                               <type>text</type>
+                               <notnull>true</notnull>
+                               <length>128</length>
+                       </field>
+                       <field>
+                               <name>token_callback</name>
+                               <type>text</type>
+                               <notnull>true</notnull>
+                               <length>2048</length>
+                       </field>
+               </declaration>
+       </table>
+
+       <table>
+               <name>*dbprefix*grauphel_notes</name>
+               <declaration>
+                       <field>
+                               <name>note_id</name>
+                               <type>integer</type>
+                               <default>0</default>
+                               <notnull>true</notnull>
+                               <autoincrement>1</autoincrement>
+                               <length>11</length>
+                       </field>
+                       <field>
+                               <name>note_user</name>
+                               <type>text</type>
+                               <notnull>false</notnull>
+                               <length>64</length>
+                       </field>
+
+                       <field>
+                               <name>note_guid</name>
+                               <type>text</type>
+                               <notnull>true</notnull>
+                               <length>128</length>
+                       </field>
+
+                       <field>
+                               <name>note_create_date</name>
+                               <type>text</type>
+                               <notnull>true</notnull>
+                               <length>32</length>
+                       </field>
+                       <field>
+                               <name>note_last_change_date</name>
+                               <type>text</type>
+                               <notnull>true</notnull>
+                               <length>32</length>
+                       </field>
+                       <field>
+                               <name>note_last_metadata_change_date</name>
+                               <type>text</type>
+                               <notnull>true</notnull>
+                               <length>32</length>
+                       </field>
+
+                       <field>
+                               <name>note_title</name>
+                               <type>text</type>
+                               <notnull>true</notnull>
+                               <length>1024</length>
+                       </field>
+                       <field>
+                               <name>note_content</name>
+                               <type>clob</type>
+                               <notnull>true</notnull>
+                       </field>
+                       <field>
+                               <name>note_content_version</name>
+                               <type>text</type>
+                               <notnull>true</notnull>
+                               <length>16</length>
+                       </field>
+                       <field>
+                               <name>note_open_on_startup</name>
+                               <type>integer</type>
+                               <default>0</default>
+                               <notnull>true</notnull>
+                               <length>1</length>
+                       </field>
+                       <field>
+                               <name>note_pinned</name>
+                               <type>integer</type>
+                               <default>0</default>
+                               <notnull>true</notnull>
+                               <length>1</length>
+                       </field>
+                       <field>
+                               <name>note_tags</name>
+                               <type>text</type>
+                               <notnull>true</notnull>
+                               <length>1024</length>
+                       </field>
+               </declaration>
+       </table>
+
+        <table>
+               <name>*dbprefix*grauphel_syncdata</name>
+               <declaration>
+                       <field>
+                               <name>syncdata_id</name>
+                               <type>integer</type>
+                               <default>0</default>
+                               <notnull>true</notnull>
+                               <autoincrement>1</autoincrement>
+                               <length>11</length>
+                       </field>
+                       <field>
+                               <name>syncdata_user</name>
+                               <type>text</type>
+                               <notnull>false</notnull>
+                               <length>64</length>
+                       </field>
+                       <field>
+                               <name>syncdata_current_sync_guid</name>
+                               <type>text</type>
+                               <notnull>true</notnull>
+                               <length>64</length>
+                       </field>
+                       <field>
+                               <name>syncdata_latest_sync_revision</name>
+                               <type>integer</type>
+                               <default>0</default>
+                               <notnull>true</notnull>
+                               <length>11</length>
+                       </field>
+               </declaration>
+       </table>
+</database>
diff --git a/appinfo/info.xml b/appinfo/info.xml
new file mode 100755 (executable)
index 0000000..ac0f9ec
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0"?>
+<info>
+       <id>grauphel</id>
+       <name>Grauphel: Tomboy note server</name>
+       <description>Tomboy REST API server to sync notes between devices</description>
+       <version>0.1</version>
+       <licence>AGPL3 or later</licence>
+       <author>Christian Weiske</author>
+       <requiremin>7</requiremin>
+</info>
diff --git a/appinfo/routes.php b/appinfo/routes.php
new file mode 100644 (file)
index 0000000..da62dc9
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+namespace OCA\Grauphel\AppInfo;
+
+$application = new Application();
+$application->registerRoutes(
+    $this,
+    array(
+        'routes' => array(
+            array(
+                'url'  => '/test',
+                'name' => 'access#test',
+            ),
+
+            array(
+                'url'  => '/authorize',
+                'name' => 'access#authorize',
+                'verb' => 'GET',
+            ),/*
+            array(
+                'url'  => '/authorize',
+                'name' => 'access#authorize',
+                'verb' => 'POST',
+                ),*/
+            array(
+                'url'  => '/login',
+                'name' => 'access#login',
+                'verb' => 'GET',
+            ),
+
+            array(
+                'url'  => '/oauth/access_token',
+                'name' => 'oauth#accessToken',
+                'verb' => 'POST',
+            ),
+            array(
+                'url'  => '/oauth/authorize',
+                'name' => 'oauth#authorize',
+                'verb' => 'GET',
+            ),
+            array(
+                'url'  => '/oauth/request_token',
+                'name' => 'oauth#requestToken',
+                'verb' => 'POST',
+            ),
+
+            array(
+                'url'  => '/api/1.0',
+                'name' => 'api#index',
+                'verb' => 'GET',
+            ),
+            array(
+                'url'  => '/api/1.0/{user}/note/{guid}',
+                'name' => 'api#note',
+                'verb' => 'GET',
+            ),
+            array(
+                'url'  => '/api/1.0/{user}/notes',
+                'name' => 'api#notes',
+                'verb' => 'GET',
+            ),
+            array(
+                'url'  => '/api/1.0/{user}/notes',
+                'name' => 'api#notes',
+                'verb' => 'POST',
+            ),
+            array(
+                'url'  => '/api/1.0/{user}',
+                'name' => 'api#user',
+                'verb' => 'GET',
+            ),
+        )
+    )
+);
+
+?>
diff --git a/appinfo/version b/appinfo/version
new file mode 100755 (executable)
index 0000000..49d5957
--- /dev/null
@@ -0,0 +1 @@
+0.1
diff --git a/controller/accesscontroller.php b/controller/accesscontroller.php
new file mode 100644 (file)
index 0000000..68789b7
--- /dev/null
@@ -0,0 +1,145 @@
+<?php
+/**
+ * Part of grauphel
+ *
+ * PHP version 5
+ *
+ * @category  Tools
+ * @package   Grauphel
+ * @author    Christian Weiske <cweiske@cweiske.de>
+ * @copyright 2014 Christian Weiske
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL v3
+ * @link      http://cweiske.de/grauphel.htm
+ */
+namespace OCA\Grauphel\Controller;
+use \OCP\AppFramework\Controller;
+
+/**
+ * Login and authorization handling
+ *
+ * @category  Tools
+ * @package   Grauphel
+ * @author    Christian Weiske <cweiske@cweiske.de>
+ * @copyright 2014 Christian Weiske
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL v3
+ * @version   Release: @package_version@
+ * @link      http://cweiske.de/grauphel.htm
+ */
+class AccessController extends Controller
+{
+    public function login($returnUrl = null)
+    {
+        $returnUrl = $this->loadReturnUrl($returnUrl);
+
+        if (isset($_POST['user']) && trim($_POST['user']) != '') {
+            $this->deps->frontend->setUser(trim($_POST['user']));
+            header('Location: ' . $returnUrl);
+            exit(0);
+        }
+
+        $hFormUrl = htmlspecialchars(
+            $this->deps->urlGen->addParams(
+                $this->deps->urlGen->accessLogin(),
+                array('returnurl' => $returnUrl)
+            )
+        );
+        //FIXME: do some real login
+        header('HTTP/1.0 200 OK');
+
+        echo <<<HTM
+<html>
+ <head>
+  <title>grauphel login</title>
+ </head>
+ <body>
+  <form method="post" action="$hFormUrl">
+   <p>
+    Log into <em>grauphel</em>:
+   </p>
+   <label>
+    User name:
+    <input id="user" type="text" name="user" size="20" value=""/>
+   </label>
+   <input type="submit" value="Login" />
+  </form>
+  <script type="text/javascript">
+//FIXME
+/*
+document.getElementById('user').value = 'cweiske';
+document.forms[0].submit();
+/**/
+  </script>
+ </body>
+</html>
+HTM;
+        exit(0);
+    }
+
+    public function authorize($returnUrl = null)
+    {
+        var_dump('asd');die();
+        $returnUrl = $this->loadReturnUrl($returnUrl);
+
+        if (isset($_POST['auth'])) {
+            if ($_POST['auth'] == 'ok') {
+                $this->deps->frontend->setAuth(true);
+            } else if ($_POST['auth'] == 'cancel') {
+                $this->deps->frontend->setAuth(false);
+            }
+            header('Location: ' . $returnUrl);
+            exit(0);
+        }
+
+        header('HTTP/1.0 200 OK');
+        $hFormUrl = htmlspecialchars(
+            $this->deps->urlGen->addParams(
+                $this->deps->urlGen->accessAuthorize(),
+                array('returnurl' => $returnUrl)
+            )
+        );
+
+        echo <<<HTM
+<html>
+ <head>
+  <title>grauphel authorization</title>
+ </head>
+ <body>
+  <form method="post" action="$hFormUrl">
+   <p>
+    Shall application FIXME get full access to the notes?
+   </p>
+   <button type="submit" name="auth" value="ok">Yes, authorize</button>
+   <button type="submit" name="auth" value="cancel">No, decline</button>
+ </body>
+</html>
+HTM;
+        exit(0);
+    }
+
+    protected function loadReturnUrl($returnUrl = null)
+    {
+        if ($returnUrl === null) {
+            if (isset($_GET['returnurl'])) {
+                $returnUrl = $_GET['returnurl'];
+            } else {
+                $returnUrl = $this->deps->urlGen->index();
+            }
+        }
+        return $returnUrl;
+    }
+
+    /**
+     * @NoAdminRequired
+     * @NoCSRFRequired
+     * @PublicPage
+     */
+    public function test()
+    {
+        var_dump('asd');die();
+        $this->registerResponder('xml', function($value) {
+                return new XMLResponse($value);
+            });
+        return array('foo' => 'bar');
+    }
+}
+?>
diff --git a/controller/apicontroller.php b/controller/apicontroller.php
new file mode 100644 (file)
index 0000000..5e743e0
--- /dev/null
@@ -0,0 +1,213 @@
+<?php
+/**
+ * Part of grauphel
+ *
+ * PHP version 5
+ *
+ * @category  Tools
+ * @package   Grauphel
+ * @author    Christian Weiske <cweiske@cweiske.de>
+ * @copyright 2014 Christian Weiske
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL v3
+ * @link      http://cweiske.de/grauphel.htm
+ */
+namespace OCA\Grauphel\Controller;
+use \OCP\AppFramework\Controller;
+
+/**
+ * Tomboy's REST API
+ *
+ * @category  Tools
+ * @package   Grauphel
+ * @author    Christian Weiske <cweiske@cweiske.de>
+ * @copyright 2014 Christian Weiske
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL v3
+ * @version   Release: @package_version@
+ * @link      http://cweiske.de/grauphel.htm
+ */
+class ApiController extends Controller
+{
+    /**
+     * /api/1.0
+     */
+    public function index()
+    {
+        var_dump('asd');die();
+        $authenticated = false;
+        $oauth = new OAuth();
+        $oauth->setDeps($this->deps);
+        $urlGen = $this->deps->urlGen;
+
+        try {
+            $provider = new \OAuthProvider();
+            $oauth->registerHandler($provider)
+                ->registerAccessTokenHandler($provider);
+            $provider->checkOAuthRequest($urlGen->fullPath());
+            $authenticated = true;
+            $token = $this->deps->tokens->load('access', $provider->token);
+            $username = $token->user;
+
+        } catch (OAuth_Exception $e) {
+            $this->deps->renderer->errorOut($e->getMessage());
+        } catch (\OAuthException $e) {
+            if ($e->getCode() != OAUTH_PARAMETER_ABSENT) {
+                $oauth->error($e);
+            }
+        }
+
+        $data = array(
+            'oauth_request_token_url' => $urlGen->oauthRequestToken(),
+            'oauth_authorize_url'     => $urlGen->oauthAuthorize(),
+            'oauth_access_token_url'  => $urlGen->oauthAccessToken(),
+            'api-version' => '1.0',
+        );
+
+        if ($authenticated) {
+            $data['user-ref'] = array(
+                'api-ref' => $urlGen->user($username),
+                'href'    => $urlGen->userHtml($username),
+            );
+        }
+
+        $this->deps->renderer->sendJson($data);
+    }
+
+    /**
+     * GET /api/1.0/$user/notes/$noteguid
+     */
+    public function note()
+    {
+        $username = $this->deps->urlGen->loadUsername();
+        $guid     = $this->deps->urlGen->loadGuid();
+        $oauth = new OAuth();
+        $oauth->setDeps($this->deps);
+        $oauth->verifyOAuthUser($username, $this->deps->urlGen->note($username, $guid));
+
+        $note = $this->deps->notes->load($username, $guid, false);
+        if ($note === null) {
+            header('HTTP/1.0 404 Not Found');
+            header('Content-type: text/plain');
+            echo "Note does not exist\n";
+            exit(1);
+        }
+
+        $data = array('note' => array($note));
+        $this->deps->renderer->sendJson($data);
+    }
+
+    /**
+     * GET|PUT /api/1.0/$user/notes
+     */
+    public function notes()
+    {
+        $username = $this->deps->urlGen->loadUsername();
+        $oauth = new OAuth();
+        $oauth->setDeps($this->deps);
+        $oauth->verifyOAuthUser($username, $this->deps->urlGen->notes($username));
+
+        $syncdata = $this->deps->notes->loadSyncData($username);
+
+        $this->handleNoteSave($username, $syncdata);
+
+        $since = null;
+        if (isset($_GET['since'])) {
+            $since = (int) $_GET['since'];
+        }
+
+        if (isset($_GET['include_notes']) && $_GET['include_notes']) {
+            $notes = $this->deps->notes->loadNotesFull($username, $since);
+        } else {
+            $notes = $this->deps->notes->loadNotesOverview($username, $since);
+        }
+
+        //work around bug https://bugzilla.gnome.org/show_bug.cgi?id=734313
+        foreach ($notes as $note) {
+            if (isset($note->{'note-content-version'})) {
+                $note->{'note-content-version'} = 0.3;
+            }
+        }
+
+        $data = array(
+            'latest-sync-revision' => $syncdata->latestSyncRevision,
+            'notes' => $notes,
+        );
+        $this->deps->renderer->sendJson($data);
+    }
+
+    protected function handleNoteSave($username, $syncdata)
+    {
+        if ($_SERVER['REQUEST_METHOD'] != 'PUT') {
+            return;
+        }
+
+        $data = file_get_contents('php://input');
+        $putObj = json_decode($data);
+        if ($putObj === NULL) {
+            errorOut('Invalid JSON data in PUT request');
+        }
+
+        //structural validation
+        if (!isset($putObj->{'latest-sync-revision'})) {
+            errorOut('Missing "latest-sync-revision"');
+        }
+        if (!isset($putObj->{'note-changes'})) {
+            errorOut('Missing "note-changes"');
+        }
+        foreach ($putObj->{'note-changes'} as $note) {
+            if (!isset($note->guid) || $note->guid == '') {
+                errorOut('Missing "guid" on note');
+            }
+        }
+
+        //content validation
+        if ($putObj->{'latest-sync-revision'} != $syncdata->latestSyncRevision +1
+            && $syncdata->latestSyncRevision != -1
+        ) {
+            errorOut('Wrong "latest-sync-revision". You are not up to date.');
+        }
+
+        //update
+        ++$syncdata->latestSyncRevision;
+        foreach ($putObj->{'note-changes'} as $noteUpdate) {
+            $note = $this->deps->notes->load($username, $noteUpdate->guid);
+            if (isset($noteUpdate->command) && $noteUpdate->command == 'delete') {
+                $this->deps->notes->delete($username, $noteUpdate->guid);
+            } else {
+                $this->deps->notes->update(
+                    $note, $noteUpdate, $syncdata->latestSyncRevision
+                );
+                $this->deps->notes->save($username, $note);
+            }
+        }
+
+        $this->deps->notes->saveSyncData($username, $syncdata);
+    }
+
+    /**
+     * GET /api/1.0/$user
+     */
+    public function user()
+    {
+        $username = $this->deps->urlGen->loadUsername();
+
+        $oauth = new OAuth();
+        $oauth->setDeps($this->deps);
+        $oauth->verifyOAuthUser($username, $this->deps->urlGen->user($username));
+
+        $syncdata = $this->deps->notes->loadSyncData($username);
+
+        $data = array(
+            'user-name'  => $username,
+            'first-name' => null,
+            'last-name'  => null,
+            'notes-ref'  => array(
+                'api-ref' => $this->deps->urlGen->notes($username),
+                'href'    => null,
+            ),
+            'latest-sync-revision' => $syncdata->latestSyncRevision,
+            'current-sync-guid'    => $syncdata->currentSyncGuid,
+        );
+        $this->deps->renderer->sendJson($data);
+    }
+}
+?>
diff --git a/controller/oauthcontroller.php b/controller/oauthcontroller.php
new file mode 100644 (file)
index 0000000..cb20832
--- /dev/null
@@ -0,0 +1,178 @@
+<?php
+/**
+ * Part of grauphel
+ *
+ * PHP version 5
+ *
+ * @category  Tools
+ * @package   Grauphel
+ * @author    Christian Weiske <cweiske@cweiske.de>
+ * @copyright 2014 Christian Weiske
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL v3
+ * @link      http://cweiske.de/grauphel.htm
+ */
+namespace OCA\Grauphel\Controller;
+use \OCP\AppFramework\Controller;
+
+/**
+ * OAuth handling
+ *
+ * @category  Tools
+ * @package   Grauphel
+ * @author    Christian Weiske <cweiske@cweiske.de>
+ * @copyright 2014 Christian Weiske
+ * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL v3
+ * @version   Release: @package_version@
+ * @link      http://cweiske.de/grauphel.htm
+ */
+class OAuthController extends Controller
+{
+    /**
+     * Handle out an access token after verifying the verification token
+     * OAuth step 3 of 3
+     */
+    public function accessTokenAction()
+    {
+        $oauth = new OAuth();
+        $oauth->setDeps($this->deps);
+
+        try {
+            $provider = new \OAuthProvider();
+            $oauth->registerHandler($provider)
+                ->registerVerificationTokenHandler($provider);
+            $provider->checkOAuthRequest($this->deps->urlGen->oauthAccessToken());
+
+            $token = $this->deps->tokens->loadAndDelete('verify', $provider->token);
+
+            $newToken = new OAuth_Token('access');
+            $newToken->tokenKey = 'a' . bin2hex($provider->generateToken(8));
+            $newToken->secret   = 's' . bin2hex($provider->generateToken(8));
+            $newToken->user     = $token->user;
+            $this->deps->tokens->store($newToken);
+
+            $this->deps->renderer->sendFormData(
+                array(
+                    'oauth_token'        => $newToken->tokenKey,
+                    'oauth_token_secret' => $newToken->secret,
+                )
+            );
+            exit(0);
+        } catch (\OAuthException $e) {
+            $oauth->error($e);
+        }
+    }
+
+    /**
+     * Log the user in and let him authorize that the app may access notes
+     * OAuth step 2 of 3
+     */
+    public function authorizeAction()
+    {
+        $oauth = new OAuth();
+        $oauth->setDeps($this->deps);
+
+        if (!isset($_REQUEST['oauth_token'])) {
+            $this->deps->renderer->errorOut('oauth_token missing');
+        }
+        if (!$oauth->validateToken($_REQUEST['oauth_token'])) {
+            $this->deps->renderer->errorOut('Invalid token string');
+        }
+
+        $reqToken = $_REQUEST['oauth_token'];
+
+        try {
+            $token = $this->deps->tokens->load('temp', $reqToken);
+        } catch (OAuth_Exception $e) {
+            $this->deps->renderer->errorOut($e->getMessage());
+        }
+
+        $authState = $this->deps->frontend->doAuthorize(
+            $this->deps->urlGen->current()
+        );
+        if ($authState === null) {
+            //this should not happen; doAuthorize() must block
+            exit(1);
+        }
+
+        try {
+            $token = $this->deps->tokens->loadAndDelete('temp', $reqToken);
+        } catch (OAuth_Exception $e) {
+            $this->deps->renderer->errorOut($e->getMessage());
+        }
+
+        if ($authState === false) {
+            //user declined
+
+            //http://wiki.oauth.net/w/page/12238543/ProblemReporting
+            $callback = $this->deps->urlGen->addParams(
+                $token->callback,
+                array(
+                    'oauth_token'   => $token->tokenKey,
+                    'oauth_problem' => 'permission_denied',
+                )
+            );
+            header('Location: ' . $callback, true, 302);
+            exit(0);
+        }
+
+        //the user is logged in and authorized
+        $provider = new \OAuthProvider();
+
+        $newToken = new OAuth_Token('verify');
+        $newToken->tokenKey = $token->tokenKey;
+        $newToken->secret   = $token->secret;
+        $newToken->verifier = 'v' . bin2hex($provider->generateToken(8));
+        $newToken->user     = $this->deps->frontend->getUser();
+
+        $this->deps->tokens->store($newToken);
+
+        //redirect
+        //FIXME: if no callback is given, show the token to the user
+        $callback = $this->deps->urlGen->addParams(
+            $token->callback,
+            array(
+                'oauth_token'    => $newToken->tokenKey,
+                'oauth_verifier' => $newToken->verifier
+            )
+        );
+
+        header('Location: ' . $callback, true, 302);
+        exit();
+    }
+
+    /**
+     * Create and return a request token.
+     * OAuth step 1 of 3
+     */
+    public function requestTokenAction()
+    {
+        $oauth = new OAuth();
+        $oauth->setDeps($this->deps);
+
+        try {
+            $provider = new \OAuthProvider();
+            $oauth->registerHandler($provider);
+            $provider->isRequestTokenEndpoint(true);
+            $provider->checkOAuthRequest($this->deps->urlGen->oauthRequestToken());
+
+            //store token + callback URI for later
+            $token = new OAuth_Token('temp');
+            $token->tokenKey = 'r' . bin2hex($provider->generateToken(8));
+            $token->secret   = 's' . bin2hex($provider->generateToken(8));
+            $token->callback = $provider->callback;
+
+            $this->deps->tokens->store($token);
+
+            $this->deps->renderer->sendFormData(
+                array(
+                    'oauth_token'              => $token->tokenKey,
+                    'oauth_token_secret'       => $token->secret,
+                    'oauth_callback_confirmed' => 'TRUE'
+                )
+            );
+        } catch (\OAuthException $e) {
+            $oauth->error($e);
+        }
+    }
+}
+?>
diff --git a/img/notes.png b/img/notes.png
new file mode 100644 (file)
index 0000000..a03a639
Binary files /dev/null and b/img/notes.png differ
diff --git a/index.php b/index.php
new file mode 100755 (executable)
index 0000000..1129766
--- /dev/null
+++ b/index.php
@@ -0,0 +1,10 @@
+<?php
+
+// Check if we are a user
+OCP\User::checkLoggedIn();
+
+$somesetting = OCP\Config::getSystemValue( "somesetting", '' );
+OCP\App::setActiveNavigationEntry( 'grauphel' );
+$tmpl = new OCP\Template( 'grauphel', 'main', 'user' );
+$tmpl->assign( 'somesetting', $somesetting );
+$tmpl->printPage();
diff --git a/src b/src
new file mode 120000 (symlink)
index 0000000..acc4488
--- /dev/null
+++ b/src
@@ -0,0 +1 @@
+../../grauphel/src
\ No newline at end of file
diff --git a/templates/main.php b/templates/main.php
new file mode 100755 (executable)
index 0000000..527de40
--- /dev/null
@@ -0,0 +1,3 @@
+<h1>tomboy notes</h1>
+
+<?php p($l->t('Some Setting'));?>: "<?php p($_['somesetting']); ?>"
diff --git a/templates/settings.php b/templates/settings.php
new file mode 100755 (executable)
index 0000000..476a202
--- /dev/null
@@ -0,0 +1,8 @@
+<form id="apptemplate">
+       <div class="section">
+               <h2><?php p($l->t('App Template'));?></h2>
+               <input type="text" name="somesetting" id="somesetting" value="<?php p($_['url']); ?>" placeholder="<?php p($l->t('Some Setting'));?>" />
+               <br />
+               <span class="msg"></span>
+       </div>
+</form>