]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Add support for an anonymous OAuth consumer. Note: this requires a
authorZach Copley <zach@status.net>
Wed, 20 Oct 2010 03:54:53 +0000 (20:54 -0700)
committerZach Copley <zach@status.net>
Wed, 20 Oct 2010 03:54:53 +0000 (20:54 -0700)
small DB tweak.  Oauth_application_user needs to have the primary
compound key: (profile_id, application_id, token).

http://status.net/open-source/issues/2761

This should also make it possible to have multiple access tokens
per application.

http://status.net/open-source/issues/2788

actions/apioauthaccesstoken.php
actions/apioauthauthorize.php
actions/apistatusesupdate.php
actions/oauthconnectionssettings.php
classes/Oauth_application_user.php
classes/Profile.php
classes/statusnet.ini
db/statusnet.sql
lib/apiauth.php
lib/apioauthstore.php
lib/applicationlist.php

index 6b36d1919e030c136cb7379a4c4d8ee9bc6d338f..21e0049cec9dea0e02d126946011ecdea2ede5a5 100644 (file)
@@ -81,7 +81,7 @@ class ApiOauthAccessTokenAction extends ApiOauthAction
             $app = $datastore->getAppByRequestToken($this->reqToken);
             $atok = $server->fetch_access_token($req);
 
-        } catch (OAuthException $e) {
+        } catch (Exception $e) {
             common_log(LOG_WARNING, 'API OAuthException - ' . $e->getMessage());
             common_debug(var_export($req, true));
             $code = $e->getCode();
@@ -99,7 +99,7 @@ class ApiOauthAccessTokenAction extends ApiOauthAction
                 $this->verifier
             );
 
-            common_log(LOG_WARNIGN, $msg);
+            common_log(LOG_WARNING, $msg);
             $this->clientError(_("Invalid request token or verifier.", 400, 'text'));
 
         } else {
index eb1000e25216b6bbe2b65d0db28ab9e568ae0fb0..01cbca18f7eccab1495e1dec43ec00715a6b7bd9 100644 (file)
@@ -177,21 +177,6 @@ class ApiOauthAuthorizeAction extends Action
                 $this->serverError($e->getMessage());
             }
 
-            // Check to see if there was a previous token associated
-            // with this user/app and kill it. If the user is doing this she
-            // probably doesn'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__);
-                    $this->serverError(_('Database error deleting OAuth application user.'));
-                }
-            }
-
             // associated the authorized req token with the user and the app
 
             $appUser = new Oauth_application_user();
index 4715f7002755695d6afecbbab08ef08c85cfe59b..91dcdd10fc7b1da3350769e70ae15cb130e0da1e 100644 (file)
@@ -150,7 +150,6 @@ require_once INSTALLDIR . '/lib/mediafile.php';
 
 class ApiStatusesUpdateAction extends ApiAuthAction
 {
-    var $source                = null;
     var $status                = null;
     var $in_reply_to_status_id = null;
     var $lat                   = null;
index 1fa70662fc88690c294abc3ad59fe319c769241b..72624de84dce61480f5223856a8238628d284dbc 100644 (file)
@@ -22,7 +22,7 @@
  * @category  Settings
  * @package   StatusNet
  * @author    Zach Copley <zach@status.net>
- * @copyright 2008-2009 StatusNet, Inc.
+ * @copyright 2008-2010 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/
  */
@@ -50,13 +50,13 @@ require_once INSTALLDIR . '/lib/apioauthstore.php';
 class OauthconnectionssettingsAction extends ConnectSettingsAction
 {
 
-    var $page = null;
-    var $id   = null;
+    var $page        = null;
+    var $oauth_token = null;
 
     function prepare($args)
     {
         parent::prepare($args);
-        $this->id = (int)$this->arg('id');
+        $this->oauth_token = $this->arg('oauth_token');
         $this->page = ($this->arg('page')) ? ($this->arg('page') + 0) : 1;
         return true;
     }
@@ -80,7 +80,7 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction
 
     function getInstructions()
     {
-        return _('You have allowed the following applications to access your account.');
+        return _('The following connections exist for your account.');
     }
 
     /**
@@ -97,22 +97,26 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction
         $offset = ($this->page - 1) * APPS_PER_PAGE;
         $limit  =  APPS_PER_PAGE + 1;
 
-        $application = $profile->getApplications($offset, $limit);
+        $connection = $profile->getConnectedApps($offset, $limit);
 
         $cnt = 0;
 
-        if (!empty($application)) {
-            $al = new ApplicationList($application, $user, $this, true);
-            $cnt = $al->show();
+        if (!empty($connection)) {
+            $cal = new ConnectedAppsList($connection, $user, $this);
+            $cnt = $cal->show();
         }
 
         if ($cnt == 0) {
             $this->showEmptyListMessage();
         }
 
-        $this->pagination($this->page > 1, $cnt > APPS_PER_PAGE,
-                          $this->page, 'connectionssettings',
-                          array('nickname' => $user->nickname));
+        $this->pagination(
+            $this->page > 1,
+            $cnt > APPS_PER_PAGE,
+            $this->page,
+            'connectionssettings',
+            array('nickname' => $user->nickname)
+        );
     }
 
     /**
@@ -138,11 +142,7 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction
         }
 
         if ($this->arg('revoke')) {
-            $this->revokeAccess($this->id);
-
-            // XXX: Show some indicator to the user of what's been done.
-
-            $this->showPage();
+            $this->revokeAccess($this->oauth_token);
         } else {
             $this->clientError(_('Unexpected form submission.'), 401);
             return false;
@@ -150,32 +150,27 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction
     }
 
     /**
-     * Revoke access to an authorized OAuth application
+     * Revoke an access token
+     *
+     * XXX: Confirm revoke before doing it
      *
      * @param int $appId the ID of the application
      *
      */
 
-    function revokeAccess($appId)
+    function revokeAccess($token)
     {
         $cur = common_current_user();
 
-        $app = Oauth_application::staticGet('id', $appId);
-
-        if (empty($app)) {
-            $this->clientError(_('No such application.'), 404);
-            return false;
-        }
-
-        // XXX: Transaction here?
-
-        $appUser = Oauth_application_user::getByKeys($cur, $app);
+        $appUser = Oauth_application_user::getByUserAndToken($cur, $token);
 
         if (empty($appUser)) {
             $this->clientError(_('You are not a user of that application.'), 401);
             return false;
         }
 
+        $app = Oauth_application::staticGet('id', $appUser->application_id);
+
         $datastore = new ApiStatusNetOAuthDataStore();
         $datastore->revoke_token($appUser->token, 1);
 
@@ -187,10 +182,25 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction
             return false;
         }
 
-        $msg = 'User %s (id: %d) revoked access to app %s (id: %d)';
-        common_log(LOG_INFO, sprintf($msg, $cur->nickname,
-                                     $cur->id, $app->name, $app->id));
-
+        $msg = 'API OAuth - user %s (id: %d) revoked access token %s for app id %d';
+        common_log(
+            LOG_INFO,
+            sprintf(
+                $msg,
+                $cur->nickname,
+                $cur->id,
+                $appUser->token,
+                $appUser->application_id
+            )
+        );
+
+        $msg = sprintf(
+            _('You have successfully revoked access for %s and the access token starting with %s'),
+             $app->name,
+             substr($appUser->token, 0, 7)
+        );
+
+        $this->showForm($msg, true);
     }
 
     function showEmptyListMessage()
@@ -204,15 +214,20 @@ class OauthconnectionssettingsAction extends ConnectSettingsAction
 
     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('oauthappssettings')),
-               'here.');
-       $this->elementEnd('p');
+        $cur = common_current_user();
+
+        $this->element('h2', null, 'Developers');
+        $this->elementStart('p');
+
+        $devMsg = sprintf(
+            _('Are you a developer? [Register an OAuth client application](%s) to use with this instance of StatusNet.'),
+            common_local_url('oauthappssettings')
+        );
+
+        $output = common_markup_to_html($devMsg);
+
+        $this->raw($output);
+        $this->elementEnd('p');
     }
 
 }
index 3d4238d640fc4db3e4055477e157acbef97ee45f..fcf6553ffed5ec0cec039893501cb49fd3fbfbb8 100644 (file)
@@ -13,7 +13,7 @@ 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 $token;                           // varchar(255) primary_key not_null
     public $created;                         // datetime   not_null
     public $modified;                        // timestamp   not_null default_CURRENT_TIMESTAMP
 
@@ -24,20 +24,51 @@ class Oauth_application_user extends Memcached_DataObject
     /* the code above is auto generated do not remove the tag below */
     ###END_AUTOCODE
 
-    static function getByKeys($user, $app)
+    static function getByUserAndToken($user, $token)
     {
-        if (empty($user) || empty($app)) {
+        if (empty($user) || empty($token)) {
             return null;
         }
 
         $oau = new Oauth_application_user();
 
-        $oau->profile_id     = $user->id;
-        $oau->application_id = $app->id;
+        $oau->profile_id = $user->id;
+        $oau->token      = $token;
         $oau->limit(1);
 
         $result = $oau->find(true);
 
         return empty($result) ? null : $oau;
     }
+
+    function updateKeys(&$orig)
+    {
+        $this->_connect();
+        $parts = array();
+        foreach (array('profile_id', 'application_id', 'token', 'access_type') as $k) {
+            if (strcmp($this->$k, $orig->$k) != 0) {
+                $parts[] = $k . ' = ' . $this->_quote($this->$k);
+            }
+        }
+        if (count($parts) == 0) {
+            # No changes
+            return true;
+        }
+        $toupdate = implode(', ', $parts);
+
+        $table = $this->tableName();
+        if(common_config('db','quote_identifiers')) {
+            $table = '"' . $table . '"';
+        }
+        $qry = 'UPDATE ' . $table . ' SET ' . $toupdate .
+          ' WHERE profile_id = ' . $orig->profile_id
+          . ' AND application_id = ' . $orig->application_id
+          . " AND token = '$orig->token'";
+        $orig->decache();
+        $result = $this->query($qry);
+        if ($result) {
+            $this->encache();
+        }
+        return $result;
+    }
 }
index 12ce5d9b6ce0025062401a844854f18810b3be6d..a32051d07dbc0c05b7bc5788124cc5dbb3b0a141 100644 (file)
@@ -401,10 +401,10 @@ class Profile extends Memcached_DataObject
         return $profile;
     }
 
-    function getApplications($offset = 0, $limit = null)
+    function getConnectedApps($offset = 0, $limit = null)
     {
         $qry =
-          'SELECT a.* ' .
+          'SELECT u.* ' .
           'FROM oauth_application_user u, oauth_application a ' .
           'WHERE u.profile_id = %d ' .
           'AND a.id = u.application_id ' .
@@ -419,11 +419,11 @@ class Profile extends Memcached_DataObject
             }
         }
 
-        $application = new Oauth_application();
+        $apps = new Oauth_application_user();
 
-        $cnt = $application->query(sprintf($qry, $this->id));
+        $cnt = $apps->query(sprintf($qry, $this->id));
 
-        return $application;
+        return $apps;
     }
 
     function subscriptionCount()
index 3fb8ee208ba17c850abd0df50544028b0e504712..7aa115fecd28e80f60ed9ad0400dca3b3f32c4d0 100644 (file)
@@ -393,13 +393,14 @@ name = U
 profile_id = 129
 application_id = 129
 access_type = 17
-token = 2
+token = 130
 created = 142
 modified = 384
 
 [oauth_application_user__keys]
 profile_id = K
 application_id = K
+token = K
 
 [profile]
 id = 129
index 3f95948e1ed5ede5d9cde05511f78fb49f657021..4ae7e56841a720ffffd290b4a02c558e7a2cced4 100644 (file)
@@ -231,10 +231,10 @@ 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',
-    token varchar(255) comment 'request or access token',
+    token varchar(255) not null comment 'request or access token',
     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)
+    constraint primary key (profile_id, application_id, token)
 ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
 
 /* These are used by JanRain OpenID library */
index a1c698bba969dfd3cee7d6cc7ee7046c03b16bdb..0ebd7aa10560d964240f940b2f178e563eaf435f 100644 (file)
@@ -178,8 +178,10 @@ class ApiAuthAction extends ApiAction
             }
 
             // set the source attr
+            if ($app->name != 'anonymous') {
+                $this->source = $app->name;
+            }
 
-            $this->source = $app->name;
 
             $appUser = Oauth_application_user::staticGet('token', $access_token);
 
index 6e0039bdd96a40554e0babec3a2e4bed18724187..e30eea129fb24f0e52d72123c57dfee9945404e8 100644 (file)
@@ -23,16 +23,43 @@ require_once INSTALLDIR . '/lib/oauthstore.php';
 
 class ApiStatusNetOAuthDataStore extends StatusNetOAuthDataStore
 {
-    function lookup_consumer($consumer_key)
+    function lookup_consumer($consumerKey)
     {
-        $con = Consumer::staticGet('consumer_key', $consumer_key);
+        $con = Consumer::staticGet('consumer_key', $consumerKey);
 
         if (!$con) {
-            return null;
+
+            // Create an anon consumer and anon application if one
+            // doesn't exist already
+            if ($consumerKey == 'anonymous') {
+                $con = new Consumer();
+                $con->consumer_key    = $consumerKey;
+                $con->consumer_secret = $consumerKey;
+                $result = $con->insert();
+                if (!$result) {
+                    $this->serverError(_("Could not create anonymous consumer."));
+                }
+                $app               = new OAuth_application();
+                $app->consumer_key = $con->consumer_key;
+                $app->name         = 'anonymous';
+
+                // XXX: allow the user to set the access type when
+                // authorizing? Currently we default to r+w for anonymous
+                // OAuth client applications
+                $app->access_type  = 3; // read + write
+                $id = $app->insert();
+                if (!$id) {
+                    $this->serverError(_("Could not create anonymous OAuth application."));
+                }
+            } else {
+                return null;
+            }
         }
 
-        return new OAuthConsumer($con->consumer_key,
-                                 $con->consumer_secret);
+        return new OAuthConsumer(
+            $con->consumer_key,
+            $con->consumer_secret
+        );
     }
 
     function getAppByRequestToken($token_key)
@@ -94,7 +121,7 @@ class ApiStatusNetOAuthDataStore extends StatusNetOAuthDataStore
 
         if ($rt->find(true) && $rt->state == 1 && $rt->verifier == $verifier) { // authorized
 
-            common_debug('request token found.', __FILE__);
+            common_debug('request token found.');
 
             // find the associated user of the app
 
@@ -140,6 +167,7 @@ class ApiStatusNetOAuthDataStore extends StatusNetOAuthDataStore
                 // update the token from req to access for the user
 
                 $orig = clone($appUser);
+
                 $appUser->token = $at->tok;
 
                 // It's at this point that we change the access type
@@ -150,11 +178,10 @@ class ApiStatusNetOAuthDataStore extends StatusNetOAuthDataStore
 
                 $appUser->access_type = $app->access_type;
 
-                $result = $appUser->update($orig);
+                $result = $appUser->updateKeys($orig);
 
-                if (empty($result)) {
-                    common_debug('couldn\'t update OAuth app user.');
-                    return null;
+                if (!$result) {
+                    throw new Exception('Couldn\'t update OAuth app user.');
                 }
 
                 // Okay, good
@@ -179,9 +206,9 @@ class ApiStatusNetOAuthDataStore extends StatusNetOAuthDataStore
      * @return void
      */
     public function revoke_token($token_key, $type = 0) {
-        $rt = new Token();
-        $rt->tok = $token_key;
-        $rt->type = $type;
+        $rt        = new Token();
+        $rt->tok   = $token_key;
+        $rt->type  = $type;
         $rt->state = 0;
 
         if (!$rt->find(true)) {
index 8b6e3a8addcc3ebf82ccb9c0e7e608795a1b3a75..6801fb6cf1e428eaf90b72ed17e16392b650ec02 100644 (file)
@@ -22,7 +22,7 @@
  * @category  Application
  * @package   StatusNet
  * @author    Zach Copley <zach@status.net>
- * @copyright 2008-2009 StatusNet, Inc.
+ * @copyright 2008-2010 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/
  */
@@ -55,14 +55,13 @@ class ApplicationList extends Widget
     /** Action object using us. */
     var $action = null;
 
-    function __construct($application, $owner=null, $action=null, $connections = false)
+    function __construct($application, $owner=null, $action=null)
     {
         parent::__construct($action);
 
         $this->application = $application;
         $this->owner       = $owner;
         $this->action      = $action;
-        $this->connections = $connections;
     }
 
     function show()
@@ -88,24 +87,34 @@ class ApplicationList extends Widget
     {
         $user = common_current_user();
 
-        $this->out->elementStart('li', array('class' => 'application',
-                                             'id' => 'oauthclient-' . $this->application->id));
+        $this->out->elementStart(
+            'li',
+            array(
+                'class' => 'application',
+                'id'    => 'oauthclient-' . $this->application->id
+            )
+        );
 
         $this->out->elementStart('span', 'vcard author');
-        if (!$this->connections) {
-            $this->out->elementStart('a',
-                                     array('href' => common_local_url('showapplication',
-                                                                      array('id' => $this->application->id)),
-                                                                      'class' => 'url'));
-
-        } else {
-            $this->out->elementStart('a', array('href' =>  $this->application->source_url,
-                                                'class' => 'url'));
-        }
+
+        $this->out->elementStart(
+            'a',
+            array(
+                'href' => common_local_url(
+                    'showapplication',
+                    array('id' => $this->application->id)),
+                    'class' => 'url'
+            )
+        );
 
         if (!empty($this->application->icon)) {
-            $this->out->element('img', array('src' => $this->application->icon,
-                                             'class' => 'photo avatar'));
+            $this->out->element(
+                'img',
+                array(
+                    'src' => $this->application->icon,
+                    'class' => 'photo avatar'
+                )
+            );
         }
 
         $this->out->element('span', 'fn', $this->application->name);
@@ -114,51 +123,58 @@ class ApplicationList extends Widget
 
         $this->out->raw(' by ');
 
-        $this->out->element('a', array('href' => $this->application->homepage,
-                                       'class' => 'url'),
-                                 $this->application->organization);
+        $this->out->element(
+            'a',
+            array(
+                'href' => $this->application->homepage,
+                'class' => 'url'
+            ),
+            $this->application->organization
+        );
 
         $this->out->element('p', 'note', $this->application->description);
         $this->out->elementEnd('li');
 
-        if ($this->connections) {
-            $appUser = Oauth_application_user::getByKeys($this->owner, $this->application);
+    }
 
-            if (empty($appUser)) {
-                common_debug("empty appUser!");
-            }
+    /* Override this in subclasses. */
+    function showOwnerControls()
+    {
+        return;
+    }
 
-            $this->out->elementStart('li');
-
-            // TRANS: Application access type
-            $readWriteText = _('read-write');
-            // TRANS: Application access type
-            $readOnlyText = _('read-only');
-
-            $access = ($this->application->access_type & Oauth_application::$writeAccess)
-              ? $readWriteText : $readOnlyText;
-            $modifiedDate = common_date_string($appUser->modified);
-            // TRANS: Used in application list. %1$s is a modified date, %2$s is access type ("read-write" or "read-only")
-            $txt = sprintf(_('Approved %1$s - "%2$s" access.'),$modifiedDate,$access);
-
-            $this->out->raw($txt);
-            $this->out->elementEnd('li');
-
-            $this->out->elementStart('li', 'entity_revoke');
-            $this->out->elementStart('form', array('id' => 'form_revoke_app',
-                                                   'class' => 'form_revoke_app',
-                                                   'method' => 'POST',
-                                                   'action' =>
-                                                   common_local_url('oauthconnectionssettings')));
-            $this->out->elementStart('fieldset');
-            $this->out->hidden('id', $this->application->id);
-            $this->out->hidden('token', common_session_token());
-            // TRANS: Button label
-            $this->out->submit('revoke', _m('BUTTON','Revoke'));
-            $this->out->elementEnd('fieldset');
-            $this->out->elementEnd('form');
-            $this->out->elementEnd('li');
-        }
+}
+
+
+/**
+ * Widget to show a list of connected OAuth clients
+ *
+ * @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 ConnectedAppsList extends Widget
+{
+    /** Current connected application query */
+    var $connection = null;
+
+    /** Owner of this list */
+    var $owner = null;
+
+    /** Action object using us. */
+    var $action = null;
+
+    function __construct($connection, $owner=null, $action=null)
+    {
+        parent::__construct($action);
+
+        common_debug("ConnectedAppsList constructor");
+
+        $this->connection = $connection;
+        $this->owner       = $owner;
+        $this->action      = $action;
     }
 
     /* Override this in subclasses. */
@@ -166,4 +182,124 @@ class ApplicationList extends Widget
     {
         return;
     }
+
+    function show()
+    {
+        $this->out->elementStart('ul', 'applications');
+
+        $cnt = 0;
+
+        while ($this->connection->fetch()) {
+            $cnt++;
+            if($cnt > APPS_PER_PAGE) {
+                break;
+            }
+            $this->showConnection();
+        }
+
+        $this->out->elementEnd('ul');
+
+        return $cnt;
+    }
+
+    function showConnection()
+    {
+        $app = Oauth_application::staticGet('id', $this->connection->application_id);
+
+        $this->out->elementStart(
+            'li',
+            array(
+                'class' => 'application',
+                'id'    => 'oauthclient-' . $app->id
+            )
+        );
+
+        $this->out->elementStart('span', 'vcard author');
+
+        $this->out->elementStart(
+            'a',
+            array(
+                'href' => $app->source_url,
+                'class' => 'url'
+            )
+        );
+
+        if (!empty($app->icon)) {
+            $this->out->element(
+                'img',
+                array(
+                    'src' => $app->icon,
+                    'class' => 'photo avatar'
+                )
+            );
+        }
+        if ($app->name != 'anonymous') {
+            $this->out->element('span', 'fn', $app->name);
+        }
+        $this->out->elementEnd('a');
+
+        if ($app->name == 'anonymous') {
+            $this->out->element('span', 'fn', "Unknown application");
+        }
+
+        $this->out->elementEnd('span');
+
+        if ($app->name != 'anonymous') {
+
+            $this->out->raw(_(' by '));
+
+            $this->out->element(
+                'a',
+                array(
+                    'href' => $app->homepage,
+                    'class' => 'url'
+                ),
+                $app->organization
+            );
+        }
+
+        // TRANS: Application access type
+        $readWriteText = _('read-write');
+        // TRANS: Application access type
+        $readOnlyText = _('read-only');
+
+        $access = ($this->connection->access_type & Oauth_application::$writeAccess)
+            ? $readWriteText : $readOnlyText;
+        $modifiedDate = common_date_string($this->connection->modified);
+        // TRANS: Used in application list. %1$s is a modified date, %2$s is access type ("read-write" or "read-only")
+        $txt = sprintf(_('Approved %1$s - "%2$s" access.'), $modifiedDate, $access);
+
+        $this->out->raw(" - $txt");
+        if (!empty($app->description)) {
+            $this->out->element(
+                'p', array('class' => 'application_description'),
+                $app->description
+            );
+        }
+        $this->out->element(
+            'p', array(
+            'class' => 'access_token'),
+            _('Access token starting with: ') . substr($this->connection->token, 0, 7)
+        );
+
+        $this->out->elementStart(
+            'form',
+            array(
+                'id' => 'form_revoke_app',
+                'class' => 'form_revoke_app',
+                'method' => 'POST',
+                'action' => common_local_url('oauthconnectionssettings')
+            )
+        );
+        $this->out->elementStart('fieldset');
+        $this->out->hidden('oauth_token', $this->connection->token);
+        $this->out->hidden('token', common_session_token());
+        // TRANS: Button label
+        $this->out->submit('revoke', _('Revoke'));
+        $this->out->elementEnd('fieldset');
+        $this->out->elementEnd('form');
+
+        $this->out->elementEnd('li');
+
+    }
 }