From c0b832d19fc478d67752a6692bf71f178346e14a Mon Sep 17 00:00:00 2001
From: Zach Copley <zach@status.net>
Date: Tue, 10 Nov 2009 17:10:56 -0800
Subject: Add new OAuth application tables and DataObjects. Also add a new
 column for consumer secret to consumer table.

---
 db/statusnet.sql | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)

(limited to 'db/statusnet.sql')

diff --git a/db/statusnet.sql b/db/statusnet.sql
index a9ed66cb4..be0d14092 100644
--- a/db/statusnet.sql
+++ b/db/statusnet.sql
@@ -176,6 +176,7 @@ create table fave (
 
 create table consumer (
     consumer_key varchar(255) primary key comment 'unique identifier, root URL',
+    consumer_secret varchar(255) not null comment 'secret value',
     seed char(32) not null comment 'seed for new tokens by this consumer',
 
     created datetime not null comment 'date this record was created',
@@ -207,6 +208,31 @@ create table nonce (
     constraint primary key (consumer_key, ts, nonce)
 ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
 
+create table oauth_application (
+    id integer auto_increment primary key comment 'unique identifier',
+    consumer_key varchar(255) not null comment 'application consumer key' references consumer (consumer_key),
+    name varchar(255) not null comment 'name of the application',
+    description varchar(255) comment 'description of the application',
+    icon varchar(255) not null comment 'application icon',
+    source_url varchar(255) comment 'application homepage - used for source link',
+    organization varchar(255) comment 'name of the organization running the application',
+    homepage varchar(255) comment 'homepage for the organization',
+    callback_url varchar(255) not null comment 'url to redirect to after authentication',
+    type tinyint default 0 comment 'type of app, 0 = browser, 1 = desktop',
+    access_type tinyint default 0 comment 'default access type, 0 = read-write, 1 = read-only',
+    created datetime not null comment 'date this record was created',
+    modified timestamp comment 'date this record was modified'
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table oauth_application_user (
+    user_id integer not null comment 'id of the application user' references user (id),
+    application_id integer not null comment 'id of the application' references oauth_application (id),
+    access_type tinyint default 0 comment 'access type, 0 = read-write, 1 = read-only',
+    created datetime not null comment 'date this record was created',
+
+    constraint primary key (user_id, application_id)
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
 /* These are used by JanRain OpenID library */
 
 create table oid_associations (
-- 
cgit v1.2.3-54-g00ecf


From efd84cadc03f005fd04c76ac35fb8d40f3680644 Mon Sep 17 00:00:00 2001
From: Zach Copley <zach@status.net>
Date: Thu, 12 Nov 2009 19:34:13 -0800
Subject: Changed the OAuth app tables to refer to profiles instead of users.
 Added an owner column to oauth_application.

---
 classes/Oauth_application.php      | 23 ++++++++++++-----------
 classes/Oauth_application_user.php | 14 +++++++-------
 db/statusnet.sql                   |  9 +++++----
 3 files changed, 24 insertions(+), 22 deletions(-)
 mode change 100755 => 100644 classes/Oauth_application.php
 mode change 100755 => 100644 classes/Oauth_application_user.php

(limited to 'db/statusnet.sql')

diff --git a/classes/Oauth_application.php b/classes/Oauth_application.php
old mode 100755
new mode 100644
index 6ad2db6dd..e2862bf97
--- a/classes/Oauth_application.php
+++ b/classes/Oauth_application.php
@@ -2,32 +2,33 @@
 /**
  * Table Definition for oauth_application
  */
-require_once 'classes/Memcached_DataObject';
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
 
-class Oauth_application extends Memcached_DataObject 
+class Oauth_application extends Memcached_DataObject
 {
     ###START_AUTOCODE
     /* the code below is auto generated do not remove the above tag */
 
     public $__table = 'oauth_application';               // table name
     public $id;                              // int(4)  primary_key not_null
+    public $owner;                           // int(4)   not_null
     public $consumer_key;                    // varchar(255)   not_null
     public $name;                            // varchar(255)   not_null
-    public $description;                     // varchar(255)  
+    public $description;                     // varchar(255)
     public $icon;                            // varchar(255)   not_null
-    public $source_url;                      // varchar(255)  
-    public $organization;                    // varchar(255)  
-    public $homepage;                        // varchar(255)  
+    public $source_url;                      // varchar(255)
+    public $organization;                    // varchar(255)
+    public $homepage;                        // varchar(255)
     public $callback_url;                    // varchar(255)   not_null
-    public $type;                            // tinyint(1)  
-    public $access_type;                     // tinyint(1)  
+    public $type;                            // tinyint(1)
+    public $access_type;                     // tinyint(1)
     public $created;                         // datetime   not_null
     public $modified;                        // timestamp   not_null default_CURRENT_TIMESTAMP
 
     /* Static get */
-    function staticGet($k,$v=null)
-    { return Memcached_DataObject::staticGet('Oauth_application_user',$k,$v); }
-
+    function staticGet($k,$v=NULL) {
+	return Memcached_DataObject::staticGet('Oauth_application',$k,$v);
+    }
     /* the code above is auto generated do not remove the tag below */
     ###END_AUTOCODE
 }
diff --git a/classes/Oauth_application_user.php b/classes/Oauth_application_user.php
old mode 100755
new mode 100644
index a8922f5e7..9e45ece25
--- a/classes/Oauth_application_user.php
+++ b/classes/Oauth_application_user.php
@@ -2,23 +2,23 @@
 /**
  * Table Definition for oauth_application_user
  */
-require_once 'classes/Memcached_DataObject';
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
 
-class Oauth_application_user extends Memcached_DataObject 
+class Oauth_application_user extends Memcached_DataObject
 {
     ###START_AUTOCODE
     /* the code below is auto generated do not remove the above tag */
 
     public $__table = 'oauth_application_user';          // table name
-    public $user_id;                         // int(4)  primary_key not_null
+    public $profile_id;                      // int(4)  primary_key not_null
     public $application_id;                  // int(4)  primary_key not_null
-    public $access_type;                     // tinyint(1)  
+    public $access_type;                     // tinyint(1)
     public $created;                         // datetime   not_null
 
     /* Static get */
-    function staticGet($k,$v=null)
-    { return Memcached_DataObject::staticGet('Oauth_application_user',$k,$v); }
-
+    function staticGet($k,$v=NULL) {
+	return Memcached_DataObject::staticGet('Oauth_application_user',$k,$v);
+    }
     /* the code above is auto generated do not remove the tag below */
     ###END_AUTOCODE
 }
diff --git a/db/statusnet.sql b/db/statusnet.sql
index be0d14092..03e6115e5 100644
--- a/db/statusnet.sql
+++ b/db/statusnet.sql
@@ -210,6 +210,7 @@ create table nonce (
 
 create table oauth_application (
     id integer auto_increment primary key comment 'unique identifier',
+    owner integer not null comment 'owner of the application' references profile (id),
     consumer_key varchar(255) not null comment 'application consumer key' references consumer (consumer_key),
     name varchar(255) not null comment 'name of the application',
     description varchar(255) comment 'description of the application',
@@ -219,18 +220,18 @@ create table oauth_application (
     homepage varchar(255) comment 'homepage for the organization',
     callback_url varchar(255) not null comment 'url to redirect to after authentication',
     type tinyint default 0 comment 'type of app, 0 = browser, 1 = desktop',
-    access_type tinyint default 0 comment 'default access type, 0 = read-write, 1 = read-only',
+    access_type tinyint default 0 comment 'default access type, bit 1 = read, bit 2 = write',
     created datetime not null comment 'date this record was created',
     modified timestamp comment 'date this record was modified'
 ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
 
 create table oauth_application_user (
-    user_id integer not null comment 'id of the application user' references user (id),
+    profile_id integer not null comment 'user of the application' references profile (id),
     application_id integer not null comment 'id of the application' references oauth_application (id),
-    access_type tinyint default 0 comment 'access type, 0 = read-write, 1 = read-only',
+    access_type tinyint default 0 comment 'access type, bit 1 = read, bit 2 = write, bit 3 = revoked',
     created datetime not null comment 'date this record was created',
 
-    constraint primary key (user_id, application_id)
+    constraint primary key (profile_id, application_id)
 ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
 
 /* These are used by JanRain OpenID library */
-- 
cgit v1.2.3-54-g00ecf


From 3c2b05d222a55cd1e148f3f887bf55e924898f1b Mon Sep 17 00:00:00 2001
From: Zach Copley <zach@status.net>
Date: Mon, 16 Nov 2009 16:58:49 -0800
Subject: Workflow for registering new OAuth apps pretty much done.

---
 actions/apps.php                     |  63 +++++++-
 actions/editapplication.php          | 246 ++++++++++++++++++++++++++++
 actions/newapplication.php           | 133 ++++++++++-----
 actions/oauthconnectionssettings.php |  13 ++
 actions/showapplication.php          | 306 +++++++++++++++++++++++++++++++++++
 classes/Consumer.php                 |  16 +-
 classes/Oauth_application.php        |  44 +++++
 db/statusnet.sql                     |   2 +-
 lib/applicationeditform.php          | 135 +++++++++++++---
 lib/applicationlist.php              |  46 ++++--
 lib/default.php                      |   2 +
 lib/router.php                       |  25 ++-
 12 files changed, 949 insertions(+), 82 deletions(-)
 create mode 100644 actions/editapplication.php
 create mode 100644 actions/showapplication.php

(limited to 'db/statusnet.sql')

diff --git a/actions/apps.php b/actions/apps.php
index d4cea1e3e..e6500599f 100644
--- a/actions/apps.php
+++ b/actions/apps.php
@@ -31,7 +31,8 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
     exit(1);
 }
 
-require_once INSTALLDIR . '/lib/connectsettingsaction.php';
+require_once INSTALLDIR . '/lib/settingsaction.php';
+require_once INSTALLDIR . '/lib/applicationlist.php';
 
 /**
  * Show a user's registered OAuth applications
@@ -45,8 +46,23 @@ require_once INSTALLDIR . '/lib/connectsettingsaction.php';
  * @see      SettingsAction
  */
 
-class AppsAction extends ConnectSettingsAction
+class AppsAction extends SettingsAction
 {
+    var $page = 0;
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+        $this->page = ($this->arg('page')) ? ($this->arg('page') + 0) : 1;
+
+        if (!common_logged_in()) {
+            $this->clientError(_('You must be logged in to list your applications.'));
+            return false;
+        }
+
+        return true;
+    }
+
     /**
      * Title of the page
      *
@@ -79,6 +95,49 @@ class AppsAction extends ConnectSettingsAction
     {
         $user = common_current_user();
 
+        $offset = ($this->page - 1) * APPS_PER_PAGE;
+        $limit  =  APPS_PER_PAGE + 1;
+
+        $application = new Oauth_application();
+        $application->owner = $user->id;
+        $application->limit($offset, $limit);
+        $application->orderBy('created DESC');
+        $application->find();
+
+        $cnt = 0;
+
+        if ($application) {
+            $al = new ApplicationList($application, $user, $this);
+            $cnt = $al->show();
+            if (0 == $cnt) {
+                $this->showEmptyListMessage();
+            }
+        }
+
+        $this->element('a',
+            array('href' => common_local_url(
+                'newapplication',
+                array('nickname' => $user->nickname)
+                )
+            ),
+            'Register a new application »');
+
+        $this->pagination(
+            $this->page > 1,
+            $cnt > APPS_PER_PAGE,
+            $this->page,
+            'apps',
+            array('nickname' => $user->nickname)
+        );
+    }
+
+    function showEmptyListMessage()
+    {
+        $message = sprintf(_('You have not registered any applications yet.'));
+
+        $this->elementStart('div', 'guide');
+        $this->raw(common_markup_to_html($message));
+        $this->elementEnd('div');
     }
 
     /**
diff --git a/actions/editapplication.php b/actions/editapplication.php
new file mode 100644
index 000000000..3af482844
--- /dev/null
+++ b/actions/editapplication.php
@@ -0,0 +1,246 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Edit an OAuth Application
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Applications
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2008-2009 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+    exit(1);
+}
+
+/**
+ * Edit the details of an OAuth application
+ *
+ * This is the form for editing an application
+ *
+ * @category Application
+ * @package  StatusNet
+ * @author   Zach Copley <zach@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ */
+
+class EditApplicationAction extends OwnerDesignAction
+{
+    var $msg = null;
+
+    var $app = null;
+
+    function title()
+    {
+        return _('Edit Application');
+    }
+
+    /**
+     * Prepare to run
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        if (!common_logged_in()) {
+            $this->clientError(_('You must be logged in to edit an application.'));
+            return false;
+        }
+
+        $id = (int)$this->arg('id');
+        $this->app = Oauth_application::staticGet($id);
+
+        if (!$this->app) {
+            $this->clientError(_('No such application.'));
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * On GET, show the form. On POST, try to save the group.
+     *
+     * @param array $args unused
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+
+            // CSRF protection
+            $token = $this->trimmed('token');
+            if (!$token || $token != common_session_token()) {
+                $this->clientError(_('There was a problem with your session token.'));
+                return;
+            }
+
+            $cur = common_current_user();
+
+            if ($this->arg('cancel')) {
+                common_redirect(common_local_url('showapplication',
+                    array(
+                        'nickname' => $cur->nickname,
+                        'id' => $this->app->id)
+                    ), 303);
+            } elseif ($this->arg('save')) {
+                $this->trySave();
+            } else {
+                $this->clientError(_('Unexpected form submission.'));
+            }
+        } else {
+            $this->showForm();
+        }
+    }
+
+    function showForm($msg=null)
+    {
+        $this->msg = $msg;
+        $this->showPage();
+    }
+
+    function showContent()
+    {
+        $form = new ApplicationEditForm($this, $this->app);
+        $form->show();
+    }
+
+    function showPageNotice()
+    {
+        if (!empty($this->msg)) {
+            $this->element('p', 'error', $this->msg);
+        } else {
+            $this->element('p', 'instructions',
+                           _('Use this form to edit your application.'));
+        }
+    }
+
+    function trySave()
+    {
+        $name         = $this->trimmed('name');
+        $description  = $this->trimmed('description');
+        $source_url   = $this->trimmed('source_url');
+        $organization = $this->trimmed('organization');
+        $homepage     = $this->trimmed('homepage');
+        $callback_url = $this->trimmed('callback_url');
+        $type         = $this->arg('app_type');
+        $access_type  = $this->arg('access_type');
+
+        if (empty($name)) {
+             $this->showForm(_('Name is required.'));
+             return;
+        } elseif (mb_strlen($name) > 255) {
+            $this->showForm(_('Name is too long (max 255 chars).'));
+            return;
+        } elseif (empty($description)) {
+            $this->showForm(_('Description is required.'));
+            return;
+        } elseif (Oauth_application::descriptionTooLong($description)) {
+            $this->showForm(sprintf(
+                _('Description is too long (max %d chars).'),
+                Oauth_application::maxDescription()));
+            return;
+        } elseif (empty($source_url)) {
+            $this->showForm(_('Source URL is required.'));
+            return;
+        } elseif ((strlen($source_url) > 0)
+            && !Validate::uri(
+                $source_url,
+                array('allowed_schemes' => array('http', 'https'))
+                )
+            )
+        {
+            $this->showForm(_('Source URL is not valid.'));
+            return;
+        } elseif (empty($organization)) {
+            $this->showForm(_('Organization is required.'));
+            return;
+        } elseif (mb_strlen($organization) > 255) {
+            $this->showForm(_('Organization is too long (max 255 chars).'));
+            return;
+        } elseif (empty($homepage)) {
+            $this->showForm(_('Organization homepage is required.'));
+            return;
+        } elseif ((strlen($homepage) > 0)
+            && !Validate::uri(
+                $homepage,
+                array('allowed_schemes' => array('http', 'https'))
+                )
+            )
+        {
+            $this->showForm(_('Homepage is not a valid URL.'));
+            return;
+        } elseif (empty($callback_url)) {
+            $this->showForm(_('Callback is required.'));
+            return;
+        } elseif (strlen($callback_url) > 0
+            && !Validate::uri(
+                $source_url,
+                array('allowed_schemes' => array('http', 'https'))
+                )
+            )
+        {
+            $this->showForm(_('Callback URL is not valid.'));
+            return;
+        }
+
+        $cur = common_current_user();
+
+        // Checked in prepare() above
+
+        assert(!is_null($cur));
+
+        $orig = clone($this->app);
+
+        $this->app->name         = $name;
+        $this->app->description  = $description;
+        $this->app->source_url   = $source_url;
+        $this->app->organization = $organization;
+        $this->app->homepage     = $homepage;
+        $this->app->callback_url = $callback_url;
+        $this->app->type         = $type;
+
+        if ($access_type == 'r') {
+            $this->app->setAccessFlags(true, false);
+        } else {
+            $this->app->setAccessFlags(true, true);
+        }
+
+        $result = $this->app->update($orig);
+
+        if (!$result) {
+            common_log_db_error($app, 'UPDATE', __FILE__);
+            $this->serverError(_('Could not update application.'));
+        }
+
+        common_redirect(common_local_url('apps',
+            array('nickname' => $cur->nickname)), 303);
+    }
+
+}
+
diff --git a/actions/newapplication.php b/actions/newapplication.php
index a78a856b1..9d8635270 100644
--- a/actions/newapplication.php
+++ b/actions/newapplication.php
@@ -43,7 +43,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
  * @link     http://status.net/
  */
 
-class NewApplicationAction extends Action
+class NewApplicationAction extends OwnerDesignAction
 {
     var $msg;
 
@@ -61,7 +61,7 @@ class NewApplicationAction extends Action
         parent::prepare($args);
 
         if (!common_logged_in()) {
-            $this->clientError(_('You must be logged in to create a group.'));
+            $this->clientError(_('You must be logged in to register an application.'));
             return false;
         }
 
@@ -81,8 +81,19 @@ class NewApplicationAction extends Action
     function handle($args)
     {
         parent::handle($args);
+
         if ($_SERVER['REQUEST_METHOD'] == 'POST') {
-            $this->trySave();
+
+            $cur = common_current_user();
+
+            if ($this->arg('cancel')) {
+                common_redirect(common_local_url('apps',
+                    array('nickname' => $cur->nickname)), 303);
+            } elseif ($this->arg('save')) {
+                $this->trySave();
+            } else {
+                $this->clientError(_('Unexpected form submission.'));
+            }
         } else {
             $this->showForm();
         }
@@ -112,55 +123,73 @@ class NewApplicationAction extends Action
 
     function trySave()
     {
-        $name              = $this->trimmed('name');
-        $description       = $this->trimmed('description'); 
-        $source_url        = $this->trimmed('source_url');
-        $organization      = $this->trimmed('organization');
-        $homepage          = $this->trimmed('application');
-        $callback_url      = $this->trimmed('callback_url');
-        $this->type        = $this->trimmed('type');
-        $this->access_type = $this->trimmed('access_type');
-         
-        if (!is_null($name) && mb_strlen($name) > 255) {
+        $name         = $this->trimmed('name');
+        $description  = $this->trimmed('description');
+        $source_url   = $this->trimmed('source_url');
+        $organization = $this->trimmed('organization');
+        $homepage     = $this->trimmed('homepage');
+        $callback_url = $this->trimmed('callback_url');
+        $type         = $this->arg('app_type');
+        $access_type  = $this->arg('access_type');
+
+        if (empty($name)) {
+             $this->showForm(_('Name is required.'));
+             return;
+        } elseif (mb_strlen($name) > 255) {
             $this->showForm(_('Name is too long (max 255 chars).'));
             return;
-        } else if (User_group::descriptionTooLong($description)) {
+        } elseif (empty($description)) {
+            $this->showForm(_('Description is required.'));
+            return;
+        } elseif (Oauth_application::descriptionTooLong($description)) {
             $this->showForm(sprintf(
-                _('description is too long (max %d chars).'), 
+                _('Description is too long (max %d chars).'),
                 Oauth_application::maxDescription()));
             return;
-        } elseif (!is_null($source_url) 
-            && (strlen($source_url) > 0) 
+        } elseif (empty($source_url)) {
+            $this->showForm(_('Source URL is required.'));
+            return;
+        } elseif ((strlen($source_url) > 0)
             && !Validate::uri(
                 $source_url,
                 array('allowed_schemes' => array('http', 'https'))
                 )
-            ) 
+            )
         {
             $this->showForm(_('Source URL is not valid.'));
             return;
-        } elseif (!is_null($homepage) 
-            && (strlen($homepage) > 0) 
+        } elseif (empty($organization)) {
+            $this->showForm(_('Organization is required.'));
+            return;
+        } elseif (mb_strlen($organization) > 255) {
+            $this->showForm(_('Organization is too long (max 255 chars).'));
+            return;
+        } elseif (empty($homepage)) {
+            $this->showForm(_('Organization homepage is required.'));
+            return;
+        } elseif ((strlen($homepage) > 0)
             && !Validate::uri(
                 $homepage,
                 array('allowed_schemes' => array('http', 'https'))
                 )
-            ) 
+            )
         {
             $this->showForm(_('Homepage is not a valid URL.'));
-            return; 
-        } elseif (!is_null($callback_url) 
-            && (strlen($callback_url) > 0) 
+            return;
+        } elseif (empty($callback_url)) {
+            $this->showForm(_('Callback is required.'));
+            return;
+        } elseif (strlen($callback_url) > 0
             && !Validate::uri(
                 $source_url,
                 array('allowed_schemes' => array('http', 'https'))
                 )
-            ) 
+            )
         {
             $this->showForm(_('Callback URL is not valid.'));
             return;
         }
-        
+
         $cur = common_current_user();
 
         // Checked in prepare() above
@@ -171,31 +200,53 @@ class NewApplicationAction extends Action
 
         $app->query('BEGIN');
 
-        $app->name    = $name;
-        $app->owner  = $cur->id;
-        $app->description = $description;
-        $app->source_url = $souce_url;
+        $app->name         = $name;
+        $app->owner        = $cur->id;
+        $app->description  = $description;
+        $app->source_url   = $source_url;
         $app->organization = $organization;
-        $app->homepage = $homepage;
+        $app->homepage     = $homepage;
         $app->callback_url = $callback_url;
-        $app->type = $type;
-        $app->access_type = $access_type;
-        
+        $app->type         = $type;
+
+        // Yeah, I dunno why I chose bit flags. I guess so I could
+        // copy this value directly to Oauth_application_user
+        // access_type which I think does need bit flags -- Z
+
+        if ($access_type == 'r') {
+            $app->setAccessFlags(true, false);
+        } else {
+            $app->setAccessFlags(true, true);
+        }
+
+        $app->created = common_sql_now();
+
         // generate consumer key and secret
-   
-        $app->created     = common_sql_now();
+
+        $consumer = Consumer::generateNew();
+
+        $result = $consumer->insert();
+
+        if (!$result) {
+            common_log_db_error($consumer, 'INSERT', __FILE__);
+            $this->serverError(_('Could not create application.'));
+        }
+
+        $app->consumer_key = $consumer->consumer_key;
 
         $result = $app->insert();
 
         if (!$result) {
-            common_log_db_error($group, 'INSERT', __FILE__);
+            common_log_db_error($app, 'INSERT', __FILE__);
             $this->serverError(_('Could not create application.'));
+            $app->query('ROLLBACK');
         }
-       
-        $group->query('COMMIT');
 
-        common_redirect($group->homeUrl(), 303);
-        
+        $app->query('COMMIT');
+
+        common_redirect(common_local_url('apps',
+            array('nickname' => $cur->nickname)), 303);
+
     }
 
 }
diff --git a/actions/oauthconnectionssettings.php b/actions/oauthconnectionssettings.php
index 6ec9f7027..e4b5af158 100644
--- a/actions/oauthconnectionssettings.php
+++ b/actions/oauthconnectionssettings.php
@@ -132,4 +132,17 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction
         $this->elementEnd('div');
     }
 
+    function showSections()
+    {
+       $cur = common_current_user();
+
+       $this->element('h2', null, 'Developers');
+       $this->elementStart('p');
+       $this->raw(_('Developers can edit the registration settings for their applications '));
+       $this->element('a',
+           array('href' => common_local_url('apps', array('nickname' => $cur->nickname))),
+               'here.');
+       $this->elementEnd('p');
+    }
+
 }
diff --git a/actions/showapplication.php b/actions/showapplication.php
new file mode 100644
index 000000000..6b8eff4a6
--- /dev/null
+++ b/actions/showapplication.php
@@ -0,0 +1,306 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Show an OAuth application
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Application
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2008-2009 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+    exit(1);
+}
+
+/**
+ * Show an OAuth application
+ *
+ * @category Application
+ * @package  StatusNet
+ * @author   Zach Copley <zach@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ */
+
+class ShowApplicationAction extends OwnerDesignAction
+{
+    /**
+     * Application to show
+     */
+
+    var $application = null;
+
+    /**
+     * User who owns the app
+     */
+
+    var $owner = null;
+
+
+    var $msg = null;
+
+    var $success = null;
+
+    /**
+     * Load attributes based on database arguments
+     *
+     * Loads all the DB stuff
+     *
+     * @param array $args $_REQUEST array
+     *
+     * @return success flag
+     */
+
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $id = (int)$this->arg('id');
+
+        $this->application  = Oauth_application::staticGet($id);
+        $this->owner        = User::staticGet($this->application->owner);
+
+        if (!common_logged_in()) {
+            $this->clientError(_('You must be logged in to view an application.'));
+            return false;
+        }
+
+        if (empty($this->application)) {
+            $this->clientError(_('No such application.'), 404);
+            return false;
+        }
+
+        $cur = common_current_user();
+
+        if ($cur->id != $this->owner->id) {
+            $this->clientError(_('You are not the owner of this application.'), 401);
+        }
+
+        return true;
+    }
+
+    /**
+     * Handle the request
+     *
+     * Shows info about the app
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+
+            // CSRF protection
+            $token = $this->trimmed('token');
+            if (!$token || $token != common_session_token()) {
+                $this->clientError(_('There was a problem with your session token.'));
+                return;
+            }
+
+            if ($this->arg('reset')) {
+                $this->resetKey();
+            }
+        } else {
+            $this->showPage();
+        }
+    }
+
+    /**
+     * Title of the page
+     *
+     * @return string title of the page
+     */
+
+    function title()
+    {
+        if (!empty($this->application->name)) {
+            return 'Application: ' . $this->application->name;
+        }
+    }
+
+    function showPageNotice()
+    {
+        if (!empty($this->msg)) {
+            $this->element('div', ($this->success) ? 'success' : 'error', $this->msg);
+        }
+    }
+
+    function showContent()
+    {
+
+        $cur = common_current_user();
+
+        $this->elementStart('div', 'entity_actions');
+
+        $this->element('a',
+            array('href' =>
+                common_local_url(
+                    'editapplication',
+                    array(
+                        'nickname' => $this->owner->nickname,
+                        'id' => $this->application->id
+                    )
+                )
+            ), 'Edit application');
+
+        $this->elementStart('form', array(
+            'id' => 'forma_reset_key',
+            'class' => 'form_reset_key',
+            'method' => 'POST',
+            'action' => common_local_url('showapplication',
+                array('nickname' => $cur->nickname,
+                      'id' => $this->application->id))));
+
+        $this->elementStart('fieldset');
+        $this->hidden('token', common_session_token());
+        $this->submit('reset', _('Reset Consumer key/secret'));
+        $this->elementEnd('fieldset');
+        $this->elementEnd('form');
+
+        $this->elementEnd('div');
+
+        $consumer = $this->application->getConsumer();
+
+        $this->elementStart('div', 'entity-application');
+
+        $this->elementStart('ul', 'entity_application_details');
+
+        $this->elementStart('li', 'entity_application_name');
+        $this->element('span', array('class' => 'big'), $this->application->name);
+        $this->raw(sprintf(_(' by %1$s'), $this->application->organization));
+        $this->elementEnd('li');
+
+        $this->element('li', 'entity_application_description', $this->application->description);
+
+        $this->elementStart('li', 'entity_application_statistics');
+
+        $defaultAccess = ($this->application->access_type & Oauth_application::$writeAccess)
+            ? 'read-write' : 'read-only';
+        $profile = Profile::staticGet($this->application->owner);
+        $userCnt = 0; // XXX: count how many users use the app
+
+        $this->raw(sprintf(
+            _('Created by %1$s - %2$s access by default - %3$d users.'),
+              $profile->getBestName(),
+              $defaultAccess,
+              $userCnt
+            ));
+
+        $this->elementEnd('li');
+
+        $this->elementEnd('ul');
+
+        $this->elementStart('dl', 'entity_consumer_key');
+        $this->element('dt', null, _('Consumer key'));
+        $this->element('dd', 'label', $consumer->consumer_key);
+        $this->elementEnd('dl');
+
+        $this->elementStart('dl', 'entity_consumer_secret');
+        $this->element('dt', null, _('Consumer secret'));
+        $this->element('dd', 'label', $consumer->consumer_secret);
+        $this->elementEnd('dl');
+
+        $this->elementStart('dl', 'entity_request_token_url');
+        $this->element('dt', null, _('Request token URL'));
+        $this->element('dd', 'label', common_local_url('oauthrequesttoken'));
+        $this->elementEnd('dl');
+
+        $this->elementStart('dl', 'entity_access_token_url');
+        $this->element('dt', null, _('Access token URL'));
+        $this->element('dd', 'label', common_local_url('oauthaccesstoken'));
+        $this->elementEnd('dl');
+
+        $this->elementStart('dl', 'entity_authorize_url');
+        $this->element('dt', null, _('Authorize URL'));
+        $this->element('dd', 'label', common_local_url('oauthauthorize'));
+        $this->elementEnd('dl');
+
+        $this->element('p', 'oauth-signature-note',
+            '*We support hmac-sha1 signatures. We do not support the plaintext signature method.');
+
+        $this->elementEnd('div');
+
+        $this->elementStart('div', 'entity-list-apps');
+        $this->element('a',
+            array(
+                'href' => common_local_url(
+                    'apps',
+                    array('nickname' => $this->owner->nickname)
+                )
+            ),
+            'View your applications');
+        $this->elementEnd('div');
+    }
+
+    function resetKey()
+    {
+        $this->application->query('BEGIN');
+
+        $consumer = $this->application->getConsumer();
+        $result = $consumer->delete();
+
+        if (!$result) {
+            common_log_db_error($consumer, 'DELETE', __FILE__);
+            $this->success = false;
+            $this->msg = ('Unable to reset consumer key and secret.');
+            $this->showPage();
+            return;
+        }
+
+        $consumer = Consumer::generateNew();
+
+        $result = $consumer->insert();
+
+        if (!$result) {
+            common_log_db_error($consumer, 'INSERT', __FILE__);
+            $this->application->query('ROLLBACK');
+            $this->success = false;
+            $this->msg = ('Unable to reset consumer key and secret.');
+            $this->showPage();
+            return;
+        }
+
+        $orig = clone($this->application);
+        $this->application->consumer_key = $consumer->consumer_key;
+        $result = $this->application->update($orig);
+
+        if (!$result) {
+            common_log_db_error($application, 'UPDATE', __FILE__);
+            $this->application->query('ROLLBACK');
+            $this->success = false;
+            $this->msg = ('Unable to reset consumer key and secret.');
+            $this->showPage();
+            return;
+        }
+
+        $this->application->query('COMMIT');
+
+        $this->success = true;
+        $this->msg = ('Consumer key and secret reset.');
+        $this->showPage();
+    }
+
+}
+
diff --git a/classes/Consumer.php b/classes/Consumer.php
index d17f183a8..ad64a8491 100644
--- a/classes/Consumer.php
+++ b/classes/Consumer.php
@@ -4,7 +4,7 @@
  */
 require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
 
-class Consumer extends Memcached_DataObject 
+class Consumer extends Memcached_DataObject
 {
     ###START_AUTOCODE
     /* the code below is auto generated do not remove the above tag */
@@ -22,4 +22,18 @@ class Consumer extends Memcached_DataObject
 
     /* the code above is auto generated do not remove the tag below */
     ###END_AUTOCODE
+
+    static function generateNew()
+    {
+        $cons = new Consumer();
+        $rand = common_good_rand(16);
+
+        $cons->seed            = $rand;
+        $cons->consumer_key    = md5(time() + $rand);
+        $cons->consumer_secret = md5(md5(time() + time() + $rand));
+        $cons->created         = common_sql_now();
+
+        return $cons;
+    }
+
 }
diff --git a/classes/Oauth_application.php b/classes/Oauth_application.php
index e2862bf97..ef1bbf6d9 100644
--- a/classes/Oauth_application.php
+++ b/classes/Oauth_application.php
@@ -31,4 +31,48 @@ class Oauth_application extends Memcached_DataObject
     }
     /* the code above is auto generated do not remove the tag below */
     ###END_AUTOCODE
+
+    // Bit flags
+    public static $readAccess  = 1;
+    public static $writeAccess = 2;
+
+    public static $browser = 1;
+    public static $desktop = 2;
+
+    function getConsumer()
+    {
+        return Consumer::staticGet('consumer_key', $this->consumer_key);
+    }
+
+    static function maxDesc()
+    {
+        $desclimit = common_config('application', 'desclimit');
+        // null => use global limit (distinct from 0!)
+        if (is_null($desclimit)) {
+            $desclimit = common_config('site', 'textlimit');
+        }
+        return $desclimit;
+    }
+
+    static function descriptionTooLong($desc)
+    {
+        $desclimit = self::maxDesc();
+        return ($desclimit > 0 && !empty($desc) && (mb_strlen($desc) > $desclimit));
+    }
+
+    function setAccessFlags($read, $write)
+    {
+        if ($read) {
+            $this->access_type |= self::$readAccess;
+        } else {
+            $this->access_type &= ~self::$readAccess;
+        }
+
+        if ($write) {
+            $this->access_type |= self::$writeAccess;
+        } else {
+            $this->access_type &= ~self::$writeAccess;
+        }
+    }
+
 }
diff --git a/db/statusnet.sql b/db/statusnet.sql
index 03e6115e5..a2740b60c 100644
--- a/db/statusnet.sql
+++ b/db/statusnet.sql
@@ -219,7 +219,7 @@ create table oauth_application (
     organization varchar(255) comment 'name of the organization running the application',
     homepage varchar(255) comment 'homepage for the organization',
     callback_url varchar(255) not null comment 'url to redirect to after authentication',
-    type tinyint default 0 comment 'type of app, 0 = browser, 1 = desktop',
+    type tinyint default 0 comment 'type of app, 1 = browser, 2 = desktop',
     access_type tinyint default 0 comment 'default access type, bit 1 = read, bit 2 = write',
     created datetime not null comment 'date this record was created',
     modified timestamp comment 'date this record was modified'
diff --git a/lib/applicationeditform.php b/lib/applicationeditform.php
index 3fd45876a..ed187ba0b 100644
--- a/lib/applicationeditform.php
+++ b/lib/applicationeditform.php
@@ -100,11 +100,16 @@ class ApplicationEditForm extends Form
 
     function action()
     {
-        if ($this->application) {
+        $cur = common_current_user();
+
+        if (!empty($this->application)) {
             return common_local_url('editapplication',
-                                    array('id' => $this->application->id));
+                array('id' => $this->application->id,
+                      'nickname' => $cur->nickname)
+            );
         } else {
-            return common_local_url('newapplication');
+            return common_local_url('newapplication',
+                array('nickname' => $cur->nickname));
         }
     }
 
@@ -116,7 +121,7 @@ class ApplicationEditForm extends Form
 
     function formLegend()
     {
-        $this->out->element('legend', null, _('Register a new application'));
+        $this->out->element('legend', null, _('Edit application'));
     }
 
     /**
@@ -130,7 +135,7 @@ class ApplicationEditForm extends Form
         if ($this->application) {
             $id                = $this->application->id;
             $name              = $this->application->name;
-            $description       = $this->application->description; 
+            $description       = $this->application->description;
             $source_url        = $this->application->source_url;
             $organization      = $this->application->organization;
             $homepage          = $this->application->homepage;
@@ -151,34 +156,46 @@ class ApplicationEditForm extends Form
 
         $this->out->elementStart('ul', 'form_data');
         $this->out->elementStart('li');
-        
+
         $this->out->hidden('application_id', $id);
+        $this->out->hidden('token', common_session_token());
+
         $this->out->input('name', _('Name'),
                           ($this->out->arg('name')) ? $this->out->arg('name') : $name);
-                    
+
         $this->out->elementEnd('li');
-        
+
         $this->out->elementStart('li');
-        $this->out->input('description', _('Description'),
-                          ($this->out->arg('Description')) ? $this->out->arg('discription') : $description);
+
+        $maxDesc = Oauth_application::maxDesc();
+        if ($maxDesc > 0) {
+            $descInstr = sprintf(_('Describe your application in %d chars'),
+                                $maxDesc);
+        } else {
+            $descInstr = _('Describe your application');
+        }
+        $this->out->textarea('description', _('Description'),
+                        ($this->out->arg('description')) ? $this->out->arg('description') : $description,
+                        $descInstr);
+
         $this->out->elementEnd('li');
-        
+
         $this->out->elementStart('li');
         $this->out->input('source_url', _('Source URL'),
                           ($this->out->arg('source_url')) ? $this->out->arg('source_url') : $source_url,
                           _('URL of the homepage of this application'));
-        $this->out->elementEnd('li');        
+        $this->out->elementEnd('li');
 
         $this->out->elementStart('li');
-        $this->out->input('Organization', _('Organization'),
-                          ($this->out->arg('organization')) ? $this->out->arg('organization') : $orgranization,
+        $this->out->input('organization', _('Organization'),
+                          ($this->out->arg('organization')) ? $this->out->arg('organization') : $organization,
                           _('Organization responsible for this application'));
         $this->out->elementEnd('li');
 
         $this->out->elementStart('li');
         $this->out->input('homepage', _('Homepage'),
                           ($this->out->arg('homepage')) ? $this->out->arg('homepage') : $homepage,
-                          _('URL of the homepage of the organization'));
+                          _('URL for the homepage of the organization'));
         $this->out->elementEnd('li');
 
         $this->out->elementStart('li');
@@ -188,17 +205,86 @@ class ApplicationEditForm extends Form
         $this->out->elementEnd('li');
 
         $this->out->elementStart('li');
-        $this->out->input('type', _('Application type'),
-                          ($this->out->arg('type')) ? $this->out->arg('type') : $type,
-                          _('Type of application, browser or desktop'));
+
+        $attrs = array('name' => 'app_type',
+                       'type' => 'radio',
+                       'id' => 'app_type-browser',
+                       'class' => 'radio',
+                       'value' => Oauth_application::$browser);
+
+        // Default to Browser
+
+        if ($this->application->type == Oauth_application::$browser
+            || empty($this->applicaiton->type)) {
+            $attrs['checked'] = 'checked';
+        }
+
+        $this->out->element('input', $attrs);
+
+        $this->out->element('label', array('for' => 'app_type-browser',
+                                      'class' => 'radio'),
+                                      _('Browser'));
+
+        $attrs = array('name' => 'app_type',
+                       'type' => 'radio',
+                       'id' => 'app_type-dekstop',
+                       'class' => 'radio',
+                       'value' => Oauth_application::$desktop);
+
+        if ($this->application->type == Oauth_application::$desktop) {
+            $attrs['checked'] = 'checked';
+        }
+
+        $this->out->element('input', $attrs);
+
+        $this->out->element('label', array('for' => 'app_type-desktop',
+                                      'class' => 'radio'),
+                                      _('Desktop'));
+        $this->out->element('p', 'form_guide', _('Type of application, browser or desktop'));
         $this->out->elementEnd('li');
-        
+
         $this->out->elementStart('li');
-        $this->out->input('access_type', _('Default access'),
-                          ($this->out->arg('access_type')) ? $this->out->arg('access_type') : $access_type,
-                          _('Default access for this application: read-write, or read-only'));
+
+        $attrs = array('name' => 'default_access_type',
+                       'type' => 'radio',
+                       'id' => 'default_access_type-r',
+                       'class' => 'radio',
+                       'value' => 'r');
+
+        // default to read-only access
+
+        if ($this->application->access_type & Oauth_application::$readAccess
+            || empty($this->application->access_type)) {
+            $attrs['checked'] = 'checked';
+        }
+
+        $this->out->element('input', $attrs);
+
+        $this->out->element('label', array('for' => 'default_access_type-ro',
+                                      'class' => 'radio'),
+                                      _('Read-only'));
+
+        $attrs = array('name' => 'default_access_type',
+                       'type' => 'radio',
+                       'id' => 'default_access_type-rw',
+                       'class' => 'radio',
+                       'value' => 'rw');
+
+        if ($this->application->access_type & Oauth_application::$readAccess
+            && $this->application->access_type & Oauth_application::$writeAccess
+        ) {
+            $attrs['checked'] = 'checked';
+        }
+
+        $this->out->element('input', $attrs);
+
+        $this->out->element('label', array('for' => 'default_access_type-rw',
+                                      'class' => 'radio'),
+                                      _('Read-write'));
+        $this->out->element('p', 'form_guide', _('Default access for this application: read-only, or read-write'));
+
         $this->out->elementEnd('li');
-        
+
         $this->out->elementEnd('ul');
     }
 
@@ -210,6 +296,7 @@ class ApplicationEditForm extends Form
 
     function formActions()
     {
-        $this->out->submit('submit', _('Save'));
+        $this->out->submit('save', _('Save'));
+        $this->out->submit('cancel', _('Cancel'));
     }
 }
diff --git a/lib/applicationlist.php b/lib/applicationlist.php
index fed784bb6..3141ea974 100644
--- a/lib/applicationlist.php
+++ b/lib/applicationlist.php
@@ -20,7 +20,7 @@
  * You should have received a copy of the GNU Affero General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  *
- * @category  Public
+ * @category  Application
  * @package   StatusNet
  * @author    Zach Copley <zach@status.net>
  * @copyright 2008-2009 StatusNet, Inc.
@@ -39,7 +39,7 @@ define('APPS_PER_PAGE', 20);
 /**
  * Widget to show a list of OAuth applications
  *
- * @category Public
+ * @category Application
  * @package  StatusNet
  * @author   Zach Copley <zach@status.net>
  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
@@ -50,10 +50,10 @@ class ApplicationList extends Widget
 {
     /** Current application, application query */
     var $application = null;
-    
+
     /** Owner of this list */
     var $owner = null;
-    
+
     /** Action object using us. */
     var $action = null;
 
@@ -87,14 +87,42 @@ class ApplicationList extends Widget
 
     function showApplication()
     {
-        $this->out->elementStart('li', array('class' => 'application',
-                                             'id' => 'oauthclient-' . $this->application->id));
 
         $user = common_current_user();
 
-	$this->out->raw($this->application->name);
-	
-	$this->out->elementEnd('li');
+        $this->out->elementStart('li', array('class' => 'application',
+                                             'id' => 'oauthclient-' . $this->application->id));
+
+        $this->out->elementStart('a',
+            array('href' => common_local_url(
+                    'showapplication',
+                    array(
+                        'nickname' => $user->nickname,
+                        'id' => $this->application->id
+                        )
+                    ),
+                'class' => 'url')
+            );
+
+	    $this->out->raw($this->application->name);
+	    $this->out->elementEnd('a');
+
+	    $this->out->raw(' by ');
+
+	    $this->out->elementStart('a',
+            array(
+                'href' => $this->application->homepage,
+                'class' => 'url'
+                )
+            );
+	    $this->out->raw($this->application->organization);
+	    $this->out->elementEnd('a');
+
+	    $this->out->elementStart('p', 'note');
+        $this->out->raw($this->application->description);
+        $this->out->elementEnd('p');
+
+	    $this->out->elementEnd('li');
     }
 
     /* Override this in subclasses. */
diff --git a/lib/default.php b/lib/default.php
index e3a043de1..b6ee72279 100644
--- a/lib/default.php
+++ b/lib/default.php
@@ -211,6 +211,8 @@ $default =
               'uploads' => true,
               'filecommand' => '/usr/bin/file',
               ),
+        'application' =>
+        array('desclimit' => null),
         'group' =>
         array('maxaliases' => 3,
               'desclimit' => null),
diff --git a/lib/router.php b/lib/router.php
index 7b65ae215..a8dbbf6d0 100644
--- a/lib/router.php
+++ b/lib/router.php
@@ -641,13 +641,30 @@ class Router
                             array('nickname' => '[a-zA-Z0-9]{1,64}'));
             }
             
-            $m->connect('apps/new', array('action' => 'newapplication'));
-            
-            $m->connect(':nickname/apps/edit', 
+            $m->connect(':nickname/apps', 
+                array('action' => 'apps'),
+                array('nickname' => '['.NICKNAME_FMT.']{1,64}'));
+            $m->connect(':nickname/apps/show/:id', 
+                array('action' => 'showapplication'),
+                array('nickname' => '['.NICKNAME_FMT.']{1,64}',
+                      'id' => '[0-9]+')
+            );
+            $m->connect(':nickname/apps/new',
+                array('action' => 'newapplication'),
+                array('nickname' => '['.NICKNAME_FMT.']{1,64}'));
+            $m->connect(':nickname/apps/edit/:id', 
                 array('action' => 'editapplication'),
-                array('nickname' => '['.NICKNAME_FMT.']{1,64}')
+                array('nickname' => '['.NICKNAME_FMT.']{1,64}',
+                      'id' => '[0-9]+')
             );
 
+            $m->connect('oauth/request_token',
+                array('action' => 'oauthrequesttoken'));
+            $m->connect('oauth/access_token',
+                array('action' => 'oauthaccesstoken'));
+            $m->connect('oauth/authorize',
+                array('action' => 'oauthauthorize'));
+
             foreach (array('subscriptions', 'subscribers') as $a) {
                 $m->connect(':nickname/'.$a.'/:tag',
                             array('action' => $a),
-- 
cgit v1.2.3-54-g00ecf


From c473a39a7da07fbe5b80fec4c08111a554691c3a Mon Sep 17 00:00:00 2001
From: Zach Copley <zach@status.net>
Date: Sun, 10 Jan 2010 23:03:30 -0800
Subject: Associate request tokens with OAuth apps and app users

---
 actions/apioauthauthorize.php      | 64 +++++++++++++++++++++++++++++---------
 classes/Oauth_application_user.php | 24 +++++++++++++-
 classes/statusnet.ini              |  4 +++
 db/statusnet.sql                   |  5 ++-
 4 files changed, 81 insertions(+), 16 deletions(-)

(limited to 'db/statusnet.sql')

diff --git a/actions/apioauthauthorize.php b/actions/apioauthauthorize.php
index 895a0c6e5..48d5087ef 100644
--- a/actions/apioauthauthorize.php
+++ b/actions/apioauthauthorize.php
@@ -125,19 +125,12 @@ class ApiOauthAuthorizeAction extends Action
         parent::handle($args);
 
         if ($_SERVER['REQUEST_METHOD'] == 'POST') {
-            /* Use a session token for CSRF protection. */
-            $token = $this->trimmed('token');
-            if (!$token || $token != common_session_token()) {
-                $this->showForm(_('There was a problem with your session token. '.
-                                  'Try again, please.'));
-                return;
-            }
 
             $this->handlePost();
 
         } else {
 
-            common_debug('ApiOauthAuthorize::handle()');
+	    // XXX: make better error messages
 
             if (empty($this->oauth_token)) {
 
@@ -160,7 +153,7 @@ class ApiOauthAuthorizeAction extends Action
 
     function handlePost()
     {
-        /* Use a session token for CSRF protection. */
+        // check session token for CSRF protection.
 
         $token = $this->trimmed('token');
 
@@ -175,25 +168,66 @@ class ApiOauthAuthorizeAction extends Action
             return;
         }
 
-        // is the user already logged in?
-
         // check creds
 
+	$user = null;
+
         if (!common_logged_in()) {
             $user = common_check_user($this->nickname, $this->password);
             if (empty($user)) {
                 $this->showForm(_("Invalid nickname / password!"));
                 return;
             }
-        }
+        } else {
+	    $user = common_current_user();
+	}
 
         if ($this->arg('allow')) {
 
+	    // mark the req token as authorized
+
             $this->store->authorize_token($this->oauth_token);
 
+	    // Check to see if there was a previous token associated
+	    // with this user/app and kill it. If you're doing this you
+	    // probably don't want any old tokens anyway.
+
+	    $appUser = Oauth_application_user::getByKeys($user, $this->app);
+
+	    if (!empty($appUser)) {
+		$result = $appUser->delete();
+
+		if (!$result) {
+		    common_log_db_error($appUser, 'DELETE', __FILE__);
+		    throw new ServerException(_('DB error deleting OAuth app user.'));
+		    return;
+		}
+	    }
+
+	    // associated the new req token with the user and the app
+
+	    $appUser = new Oauth_application_user();
+
+	    $appUser->profile_id     = $user->id;
+	    $appUser->application_id = $this->app->id;
+	    $appUser->access_type    = $this->app->access_type;
+	    $appUser->token          = $this->oauth_token;
+	    $appUser->created        = common_sql_now();
+
+	    $result = $appUser->insert();
+
+	    if (!$result) {
+		common_log_db_error($appUser, 'INSERT', __FILE__);
+		throw new ServerException(_('DB error inserting OAuth app user.'));
+		return;
+	    }
+
             // if we have a callback redirect and provide the token
 
             if (!empty($this->callback)) {
+
+		// XXX: Need better way to build this redirect url.
+
                 $target_url = $this->callback . '?oauth_token=' . $this->oauth_token;
                 common_redirect($target_url, 303);
             }
@@ -202,7 +236,7 @@ class ApiOauthAuthorizeAction extends Action
 
             $this->elementStart('p');
 
-            // XXX: Do verifier code?
+            // XXX: Do OAuth 1.0a verifier code?
 
             $this->raw(sprintf(_("The request token %s has been authorized. " .
                                  'Please exchange it for an access token.'),
@@ -233,7 +267,9 @@ class ApiOauthAuthorizeAction extends Action
     function showScripts()
     {
         parent::showScripts();
-      //  $this->autofocus('nickname');
+	if (!common_logged_in()) {
+	    $this->autofocus('nickname');
+	}
     }
 
     /**
diff --git a/classes/Oauth_application_user.php b/classes/Oauth_application_user.php
index 9e45ece25..e4c018f21 100644
--- a/classes/Oauth_application_user.php
+++ b/classes/Oauth_application_user.php
@@ -13,12 +13,34 @@ class Oauth_application_user extends Memcached_DataObject
     public $profile_id;                      // int(4)  primary_key not_null
     public $application_id;                  // int(4)  primary_key not_null
     public $access_type;                     // tinyint(1)
+    public $token;                           // varchar(255)
+    public $secret;                          // varchar(255)
+    public $verifier;                        // varchar(255)
     public $created;                         // datetime   not_null
+    public $modified;                        // timestamp   not_null default_CURRENT_TIMESTAMP
 
     /* Static get */
     function staticGet($k,$v=NULL) {
-	return Memcached_DataObject::staticGet('Oauth_application_user',$k,$v);
+        return Memcached_DataObject::staticGet('Oauth_application_user',$k,$v);
     }
     /* the code above is auto generated do not remove the tag below */
     ###END_AUTOCODE
+
+    static function getByKeys($user, $app)
+    {
+        if (empty($user) || empty($app)) {
+            return null;
+        }
+
+        $oau = new Oauth_application_user();
+
+        $oau->profile_id     = $user->id;
+	$oau->application_id = $app->id;
+        $oau->limit(1);
+
+        $result = $oau->find(true);
+
+        return empty($result) ? null : $oau;
+    }
+
 }
diff --git a/classes/statusnet.ini b/classes/statusnet.ini
index 0cbe60a5a..43f6c4466 100644
--- a/classes/statusnet.ini
+++ b/classes/statusnet.ini
@@ -372,7 +372,11 @@ id = N
 profile_id = 129
 application_id = 129
 access_type = 17
+token = 2
+secret = 2
+verifier = 2
 created = 142
+modified = 384
 
 [oauth_application_user__keys]
 profile_id = K
diff --git a/db/statusnet.sql b/db/statusnet.sql
index a2740b60c..eb4706067 100644
--- a/db/statusnet.sql
+++ b/db/statusnet.sql
@@ -229,8 +229,11 @@ create table oauth_application_user (
     profile_id integer not null comment 'user of the application' references profile (id),
     application_id integer not null comment 'id of the application' references oauth_application (id),
     access_type tinyint default 0 comment 'access type, bit 1 = read, bit 2 = write, bit 3 = revoked',
+    token varchar(255) comment 'authorization token',
+    secret varchar(255) comment 'token secret',
+    verifier varchar(255) not null comment 'verification code',
     created datetime not null comment 'date this record was created',
-
+    modified timestamp comment 'date this record was modified',
     constraint primary key (profile_id, application_id)
 ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
 
-- 
cgit v1.2.3-54-g00ecf


From c2337ab47c183ab9758f5db8632ac9cd480a282a Mon Sep 17 00:00:00 2001
From: Zach Copley <zach@status.net>
Date: Mon, 11 Jan 2010 12:17:36 -0800
Subject: Decided we didn't need to keep the token secret in the
 Oauth_application_user record

---
 classes/Oauth_application_user.php | 1 -
 classes/statusnet.ini              | 1 -
 db/statusnet.sql                   | 3 +--
 3 files changed, 1 insertion(+), 4 deletions(-)

(limited to 'db/statusnet.sql')

diff --git a/classes/Oauth_application_user.php b/classes/Oauth_application_user.php
index e4c018f21..a05371f56 100644
--- a/classes/Oauth_application_user.php
+++ b/classes/Oauth_application_user.php
@@ -14,7 +14,6 @@ class Oauth_application_user extends Memcached_DataObject
     public $application_id;                  // int(4)  primary_key not_null
     public $access_type;                     // tinyint(1)
     public $token;                           // varchar(255)
-    public $secret;                          // varchar(255)
     public $verifier;                        // varchar(255)
     public $created;                         // datetime   not_null
     public $modified;                        // timestamp   not_null default_CURRENT_TIMESTAMP
diff --git a/classes/statusnet.ini b/classes/statusnet.ini
index 43f6c4466..b358bbf60 100644
--- a/classes/statusnet.ini
+++ b/classes/statusnet.ini
@@ -373,7 +373,6 @@ profile_id = 129
 application_id = 129
 access_type = 17
 token = 2
-secret = 2
 verifier = 2
 created = 142
 modified = 384
diff --git a/db/statusnet.sql b/db/statusnet.sql
index eb4706067..c3160bcf8 100644
--- a/db/statusnet.sql
+++ b/db/statusnet.sql
@@ -229,8 +229,7 @@ create table oauth_application_user (
     profile_id integer not null comment 'user of the application' references profile (id),
     application_id integer not null comment 'id of the application' references oauth_application (id),
     access_type tinyint default 0 comment 'access type, bit 1 = read, bit 2 = write, bit 3 = revoked',
-    token varchar(255) comment 'authorization token',
-    secret varchar(255) comment 'token secret',
+    token varchar(255) comment 'request or access token',
     verifier varchar(255) not null comment 'verification code',
     created datetime not null comment 'date this record was created',
     modified timestamp comment 'date this record was modified',
-- 
cgit v1.2.3-54-g00ecf


From 7694955cd654becf8f03dc4eca271d8188141596 Mon Sep 17 00:00:00 2001
From: Zach Copley <zach@status.net>
Date: Wed, 13 Jan 2010 01:16:42 +0000
Subject: Callback URL can be null

---
 db/statusnet.sql | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'db/statusnet.sql')

diff --git a/db/statusnet.sql b/db/statusnet.sql
index c3160bcf8..b1867e7f0 100644
--- a/db/statusnet.sql
+++ b/db/statusnet.sql
@@ -218,7 +218,7 @@ create table oauth_application (
     source_url varchar(255) comment 'application homepage - used for source link',
     organization varchar(255) comment 'name of the organization running the application',
     homepage varchar(255) comment 'homepage for the organization',
-    callback_url varchar(255) not null comment 'url to redirect to after authentication',
+    callback_url varchar(255) comment 'url to redirect to after authentication',
     type tinyint default 0 comment 'type of app, 1 = browser, 2 = desktop',
     access_type tinyint default 0 comment 'default access type, bit 1 = read, bit 2 = write',
     created datetime not null comment 'date this record was created',
-- 
cgit v1.2.3-54-g00ecf


From d33040089d660d897183df8e94c2a65bb8c8c85f Mon Sep 17 00:00:00 2001
From: Zach Copley <zach@status.net>
Date: Wed, 13 Jan 2010 21:14:22 +0000
Subject: Remove verifier from Oauth_application_user (not needed there)

---
 classes/Oauth_application_user.php | 1 -
 classes/statusnet.ini              | 1 -
 db/statusnet.sql                   | 1 -
 3 files changed, 3 deletions(-)

(limited to 'db/statusnet.sql')

diff --git a/classes/Oauth_application_user.php b/classes/Oauth_application_user.php
index 618d68133..57986281f 100644
--- a/classes/Oauth_application_user.php
+++ b/classes/Oauth_application_user.php
@@ -14,7 +14,6 @@ class Oauth_application_user extends Memcached_DataObject
     public $application_id;                  // int(4)  primary_key not_null
     public $access_type;                     // tinyint(1)
     public $token;                           // varchar(255)
-    public $verifier;                        // varchar(255)
     public $created;                         // datetime   not_null
     public $modified;                        // timestamp   not_null default_CURRENT_TIMESTAMP
 
diff --git a/classes/statusnet.ini b/classes/statusnet.ini
index b358bbf60..2b5f2c225 100644
--- a/classes/statusnet.ini
+++ b/classes/statusnet.ini
@@ -373,7 +373,6 @@ profile_id = 129
 application_id = 129
 access_type = 17
 token = 2
-verifier = 2
 created = 142
 modified = 384
 
diff --git a/db/statusnet.sql b/db/statusnet.sql
index b1867e7f0..3a74f9d7a 100644
--- a/db/statusnet.sql
+++ b/db/statusnet.sql
@@ -230,7 +230,6 @@ create table oauth_application_user (
     application_id integer not null comment 'id of the application' references oauth_application (id),
     access_type tinyint default 0 comment 'access type, bit 1 = read, bit 2 = write, bit 3 = revoked',
     token varchar(255) comment 'request or access token',
-    verifier varchar(255) not null comment 'verification code',
     created datetime not null comment 'date this record was created',
     modified timestamp comment 'date this record was modified',
     constraint primary key (profile_id, application_id)
-- 
cgit v1.2.3-54-g00ecf


From 6efbf2777ac1ba934829a8e9ae381ca280621c0c Mon Sep 17 00:00:00 2001
From: Zach Copley <zach@status.net>
Date: Wed, 13 Jan 2010 21:31:19 +0000
Subject: Add verifier and verified callback to token for OAuth 1.0a

---
 classes/Token.php     | 6 ++++--
 classes/statusnet.ini | 2 ++
 db/statusnet.sql      | 2 ++
 3 files changed, 8 insertions(+), 2 deletions(-)

(limited to 'db/statusnet.sql')

diff --git a/classes/Token.php b/classes/Token.php
index 1fabd72f1..a129d1fd1 100644
--- a/classes/Token.php
+++ b/classes/Token.php
@@ -4,7 +4,7 @@
  */
 require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
 
-class Token extends Memcached_DataObject 
+class Token extends Memcached_DataObject
 {
     ###START_AUTOCODE
     /* the code below is auto generated do not remove the above tag */
@@ -14,7 +14,9 @@ class Token extends Memcached_DataObject
     public $tok;                             // char(32)  primary_key not_null
     public $secret;                          // char(32)   not_null
     public $type;                            // tinyint(1)   not_null
-    public $state;                           // tinyint(1)  
+    public $state;                           // tinyint(1)
+    public $verifier;                        // varchar(255)
+    public $verified_callback;               // varchar(255)
     public $created;                         // datetime()   not_null
     public $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP
 
diff --git a/classes/statusnet.ini b/classes/statusnet.ini
index 2b5f2c225..6203650a6 100644
--- a/classes/statusnet.ini
+++ b/classes/statusnet.ini
@@ -516,6 +516,8 @@ tok = 130
 secret = 130
 type = 145
 state = 17
+verifier = 2
+verified_callback = 2
 created = 142
 modified = 384
 
diff --git a/db/statusnet.sql b/db/statusnet.sql
index 3a74f9d7a..17de4fd0d 100644
--- a/db/statusnet.sql
+++ b/db/statusnet.sql
@@ -189,6 +189,8 @@ create table token (
     secret char(32) not null comment 'secret value',
     type tinyint not null default 0 comment 'request or access',
     state tinyint default 0 comment 'for requests, 0 = initial, 1 = authorized, 2 = used',
+    verifier varchar(255) comment 'verifier string for OAuth 1.0a',
+    verified_callback varchar(255) comment 'verified callback URL for OAuth 1.0a',
 
     created datetime not null comment 'date this record was created',
     modified timestamp comment 'date this record was modified',
-- 
cgit v1.2.3-54-g00ecf

