CSteipp has submitted this change and it was merged.

Change subject: Added Special:MWOAuthManageMyGrants page
......................................................................


Added Special:MWOAuthManageMyGrants page

* Various small cleanups

Change-Id: I669bd4b750cdfafd18dad60d8cdabdc1675113f4
---
M OAuth.config.php
M OAuth.setup.php
M backend/MWOAuthConsumer.php
A backend/MWOAuthConsumerAcceptance.php
M backend/MWOAuthDAO.php
A control/MWOAuthConsumerAcceptanceSubmitControl.php
M control/MWOAuthConsumerSubmitControl.php
M control/MWOAuthDAOAccessControl.php
M control/MWOAuthSubmitControl.php
M frontend/MWOAuthUI.setup.php
M frontend/language/MWOAuth.i18n.php
M frontend/specialpages/actions/MWOAuthManageConsumers.php
A frontend/specialpages/actions/MWOAuthManageMyGrants.php
13 files changed, 720 insertions(+), 84 deletions(-)

Approvals:
  CSteipp: Verified; Looks good to me, approved



diff --git a/OAuth.config.php b/OAuth.config.php
index 36a9ff6..fc4e9d0 100644
--- a/OAuth.config.php
+++ b/OAuth.config.php
@@ -12,6 +12,7 @@
 $wgAvailableRights[] = 'mwoauthsuppress';
 $wgAvailableRights[] = 'mwoauthviewsuppressed';
 $wgAvailableRights[] = 'mwoauthviewprivate';
+$wgAvailableRights[] = 'mwoauthmanagemyconsumers';
 
 # End of configuration variables.
 # ########
diff --git a/OAuth.setup.php b/OAuth.setup.php
index 6714af5..8b30872 100644
--- a/OAuth.setup.php
+++ b/OAuth.setup.php
@@ -35,6 +35,7 @@
 
                $classes['MWOAuthConsumerRegistration'] = 
"$spActionDir/MWOAuthConsumerRegistration.php";
                $classes['MWOAuthManageConsumers'] = 
"$spActionDir/MWOAuthManageConsumers.php";
+               $classes['MWOAuthManageMyGrants'] = 
"$spActionDir/MWOAuthManageMyGrants.php";
                # Special:MWOAuth/authorize
                # Special:MWOAuth/initiate?
                # Special:MWOAuth/token?
@@ -45,11 +46,14 @@
                # Data access objects
                $classes['MWOAuthDAO'] = "$backendDir/MWOAuthDAO.php";
                $classes['MWOAuthConsumer'] = "$backendDir/MWOAuthConsumer.php";
+               $classes['MWOAuthConsumerAcceptance'] = 
"$backendDir/MWOAuthConsumerAcceptance.php";
 
                # Control logic
                $classes['MWOAuthDAOAccessControl'] = 
"$controlDir/MWOAuthDAOAccessControl.php";
                $classes['MWOAuthSubmitControl'] = 
"$controlDir/MWOAuthSubmitControl.php";
                $classes['MWOAuthConsumerSubmitControl'] = 
"$controlDir/MWOAuthConsumerSubmitControl.php";
+               $classes['MWOAuthConsumerAcceptanceSubmitControl'] =
+                       
"$controlDir/MWOAuthConsumerAcceptanceSubmitControl.php";
 
                # Schema changes
                $classes['MWOAuthUpdaterHooks'] = 
"$schemaDir/MWOAuthUpdater.hooks.php";
diff --git a/backend/MWOAuthConsumer.php b/backend/MWOAuthConsumer.php
index d18e6f4..c6066e9 100644
--- a/backend/MWOAuthConsumer.php
+++ b/backend/MWOAuthConsumer.php
@@ -26,7 +26,7 @@
        protected $id;
        /** @var string Hex token */
        protected $consumerKey;
-       /** @var integer Publisher user ID */
+       /** @var integer Publisher user ID (on central wiki) */
        protected $userId;
        /** @var string Version used for handshake breaking changes */
        protected $version;
@@ -46,6 +46,8 @@
        protected $secretKey;
        /** @var string RSA key */
        protected $rsaKey;
+       /** @var array List of grants */
+       protected $grants;
        /** @var array IP restrictions */
        protected $restrictions;
        /** @var integer MWOAuthConsumer::STAGE_* constant */
@@ -182,7 +184,8 @@
                $row['oarc_stage_timestamp'] = $db->timestamp( 
$row['oarc_stage_timestamp'] );
                $row['oarc_restrictions'] = FormatJSON::encode( 
$row['oarc_restrictions'] );
                $row['oarc_grants'] = FormatJSON::encode( $row['oarc_grants'] );
-               $row['oarc_email_authenticated'] = $db->timestamp( 
$row['oarc_email_authenticated'] );
+               $row['oarc_email_authenticated'] =
+                       $db->timestampOrNull( $row['oarc_email_authenticated'] 
);
                return $row;
        }
 
@@ -191,7 +194,8 @@
                $row['oarc_stage_timestamp'] = wfTimestamp( TS_MW, 
$row['oarc_stage_timestamp'] );
                $row['oarc_restrictions'] = FormatJSON::decode( 
$row['oarc_restrictions'], true );
                $row['oarc_grants'] = FormatJSON::decode( $row['oarc_grants'], 
true );
-               $row['oarc_email_authenticated'] = wfTimestamp( TS_MW, 
$row['oarc_email_authenticated'] );
+               $row['oarc_email_authenticated'] =
+                       wfTimestampOrNull( TS_MW, 
$row['oarc_email_authenticated'] );
                return $row;
        }
 
diff --git a/backend/MWOAuthConsumerAcceptance.php 
b/backend/MWOAuthConsumerAcceptance.php
new file mode 100644
index 0000000..33a4687
--- /dev/null
+++ b/backend/MWOAuthConsumerAcceptance.php
@@ -0,0 +1,110 @@
+<?php
+/*
+ (c) Aaron Schulz 2013, GPL
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ http://www.gnu.org/copyleft/gpl.html
+*/
+
+/**
+ * Representation of an OAuth consumer acceptance
+ */
+class MWOAuthConsumerAcceptance extends MWOAuthDAO {
+       /** @var string Wiki ID the application can be used on (or "*" for all) 
*/
+       protected $wiki;
+       /** @var integer Publisher user ID (on central wiki) */
+       protected $userId;
+       /** @var integer */
+       protected $consumerId;
+       /** @var string Hex token */
+       protected $accessToken;
+       /** @var string Hex token */
+       protected $secretToken;
+       /** @var array List of grants */
+       protected $grants;
+       /** @var string TS_MW timestamp of acceptance */
+       protected $accepted;
+
+       protected static function getSchema() {
+               return array(
+                       'idField'        => 'accessToken',
+                       'table'          => 'oauth_accepted_consumer',
+                       'fieldColumnMap' => array(
+                               'wiki'         => 'oaac_wiki',
+                               'userId'       => 'oaac_user_id',
+                               'consumerId'   => 'oaac_consumer_id',
+                               'accessToken'  => 'oaac_access_token',
+                               'accessSecret' => 'oaac_access_secret',
+                               'grants'       => 'oaac_grants',
+                               'accepted'     => 'oaac_accepted'
+                       ),
+               );
+       }
+
+       protected static function getFieldPermissionChecks() {
+               return array(
+                       'accessSecret' => 'userCanSeePrivate'
+               );
+       }
+
+       /**
+        * @param DatabaseBase $db
+        * @param string $token Access token
+        * @param integer $flags MWOAuthConsumerAcceptance::READ_* bitfield
+        * @return MWOAuthConsumerAcceptance|bool
+        */
+       public static function newFromToken( DatabaseBase $db, $token, $flags = 
0 ) {
+               $row = $db->selectRow( static::getTable(),
+                       array_values( static::getFieldColumnMap() ),
+                       array( 'oaac_access_token' => $token ),
+                       __METHOD__,
+                       ( $flags & self::READ_LOCKING ) ? array( 'FOR UPDATE' ) 
: array()
+               );
+
+               if ( $row ) {
+                       $consumer = new self();
+                       $consumer->loadFromRow( $db, $row );
+                       return $consumer;
+               } else {
+                       return false;
+               }
+       }
+
+       protected function normalizeValues() {
+               $this->userId = (int)$this->userId;
+               $this->consumerId = (int)$this->consumerId;
+               $this->accepted = wfTimestamp( TS_MW, $this->accepted );
+       }
+
+       protected function encodeRow( DatabaseBase $db, $row ) {
+               $row['oaac_grants'] = FormatJSON::encode( $row['oaac_grants'] );
+               $row['oaac_accepted'] = $db->timestamp( $row['oaac_accepted'] );
+               return $row;
+       }
+
+       protected function decodeRow( DatabaseBase $db, $row ) {;
+               $row['oaac_grants'] = FormatJSON::decode( $row['oaac_grants'], 
true );
+               $row['oaac_accepted'] = wfTimestamp( TS_MW, 
$row['oaac_accepted'] );
+               return $row;
+       }
+
+       protected function userCanSeePrivate( $name, RequestContext $context ) {
+               if ( !$context->getUser()->isAllowed( 'mwoauthviewprivate' ) ) {
+                       return $context->msg( 'mwoauth-field-private' );
+               } else {
+                       return true;
+               }
+       }
+}
diff --git a/backend/MWOAuthDAO.php b/backend/MWOAuthDAO.php
index a9113e1..dfb0aeb 100644
--- a/backend/MWOAuthDAO.php
+++ b/backend/MWOAuthDAO.php
@@ -22,6 +22,8 @@
  * Representation of a Data Access Object
  */
 abstract class MWOAuthDAO implements IDBAccessObject {
+       private $daoOrigin = 'new'; // string; object construction origin
+
        /**
         * @throws Exception
         */
@@ -138,7 +140,7 @@
        public function save( DatabaseBase $dbw ) {
                $uniqueId = $this->getIdValue();
                $idColumn = static::getIdColumn();
-               if ( $uniqueId ) {
+               if ( $this->daoOrigin === 'db' ) {
                        $dbw->update(
                                static::getTable(),
                                $this->getRowArray( $dbw ),
@@ -314,6 +316,7 @@
                        $values[$field] = $row[$column];
                }
                $this->loadFromValues( $values );
+               $this->daoOrigin = 'db';
        }
 
        /**
diff --git a/control/MWOAuthConsumerAcceptanceSubmitControl.php 
b/control/MWOAuthConsumerAcceptanceSubmitControl.php
new file mode 100644
index 0000000..422ca9d
--- /dev/null
+++ b/control/MWOAuthConsumerAcceptanceSubmitControl.php
@@ -0,0 +1,142 @@
+<?php
+/*
+ (c) Aaron Schulz 2013, GPL
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ http://www.gnu.org/copyleft/gpl.html
+*/
+
+/**
+ * This handles the core logic of submitting/approving application
+ * consumer requests and the logic of managing approved consumers
+ *
+ * @TODO: improve error messages
+ */
+class MWOAuthConsumerAcceptanceSubmitControl extends MWOAuthSubmitControl {
+       protected function getRequiredFields() {
+               return array(
+                       'accept'    => array(
+                               'consumerKey' => '/^[0-9a-f]{32}$/',
+                               'wiki'        => function( $s ) {
+                                       return WikiMap::getWiki( $s ) || $s === 
'*'; },
+                               'grants'      => function( $s ) {
+                                       // @TODO: beef up
+                                       return is_array( FormatJSON::decode( 
$s, true ) ); }
+                       ),
+                       'update'  => array(
+                               'accessToken' => '/^[0-9a-f]{32}$/',
+                               'wiki'        => function( $s ) {
+                                       return WikiMap::getWiki( $s ) || $s === 
'*'; },
+                               'grants'      => function( $s ) {
+                                       // @TODO: beef up
+                                       return is_array( FormatJSON::decode( 
$s, true ) ); }
+                       ),
+                       'renounce'  => array(
+                               'accessToken' => '/^[0-9a-f]{32}$/',
+                       ),
+               );
+       }
+
+       protected function checkBasePermissions() {
+               $user = $this->getUser();
+               if ( !$user->getID() ) {
+                       return $this->failure( 'not_logged_in', 
'badaccess-group0' );
+               } elseif ( !$user->isAllowed( 'mwoauthmanagemygrants' ) ) {
+                       return $this->failure( 'permission_denied', 
'badaccess-group0' );
+               } elseif ( wfReadOnly() ) {
+                       return $this->failure( 'readonly', 'readonlytext', 
wfReadOnlyReason() );
+               } elseif ( !MWOAuthUtils::isCentralWiki() ) { // sanity
+                       // We attach consumers to the ID of a user on the 
management wiki
+                       throw new MWException( "This can only be used from the 
OAuth management wiki." );
+               }
+               return $this->success();
+       }
+
+       protected function processAction( $action ) {
+               global $wgMemc;
+
+               $user = $this->getUser(); // proposer or admin
+               $dbw = MWOAuthUtils::getCentralDB( DB_MASTER );
+
+               switch ( $action ) {
+               /*
+               case 'accept': // @TODO: unused (WIP)
+                       $cmr = MWOAuthConsumer::newFromKey( $dbw, 
$this->vals['consumerKey'] );
+                       if ( !$cmr ) {
+                               return $this->failure( 'invalid_consumer_key', 
'mwoauth-invalid-consumer-key' );
+                       } elseif ( $cmr->get( 'stage' ) !== 
MWOAuthConsumer::STAGE_APPROVED ) {
+                               return $this->failure( 'permission_denied', 
'badaccess-group0' );
+                       }
+
+                       // @TODO: handle exceptions
+                       $store = new MWOAuthDataStore( $wgMemc, 
MWOAuthUtils::getCentralDB( DB_MASTER ) );
+                       $oauthServer = new MWOAuthServer( $store );
+                       $oauthServer->add_signature_method( new 
OAuthSignatureMethod_HMAC_SHA1() );
+                       $oauthServer->add_signature_method( new 
MWOAuthSignatureMethod_RSA_SHA1( $store ) );
+
+                       $callback = $oauthServer->authorize(
+                               $this->vals['consumerKey'], 
$this->vals['requestToken'], $this->getUser() );
+
+                       // WIP: PUT THIS IN authorize()
+                       $cmra = MWOAuthConsumerAcceptance::newFromArray( array(
+                               'wiki'         => $this->vals['wiki'],
+                               'userId'       => $this->getUser()->getId(),
+                               'consumerId'   => $cmr->getId(),
+                               'accessToken'  => ...,
+                               'accessSecret' => ...,
+                               'grants'       => FormatJSON::decode( 
$this->vals['grants'], true ),
+                               'accepted'     => wfTimestampNow()
+                       ) );
+                       return $this->success( $cmra );
+                       $cmra->save();
+               */
+               case 'update':
+                       $cmra = MWOAuthConsumerAcceptance::newFromToken( $dbw, 
$this->vals['accessToken'] );
+                       if ( !$cmra ) {
+                               return $this->failure( 'invalid_access_token', 
'mwoauth-invalid-access-token' );
+                       } elseif ( $cmra->get( 'userId' ) !== 
$this->getUser()->getId() ) {
+                               return $this->failure( 'invalid_access_token', 
'mwoauth-invalid-access-token' );
+                       }
+
+                       $cmra->setFields( array(
+                               'wiki'   => $this->vals['wiki'],
+                               'grants' => FormatJSON::decode( 
$this->vals['grants'], true ) ) );
+                       $cmra->save( $dbw );
+
+                       return $this->success( $cmra );
+               case 'renounce':
+                       $cmra = MWOAuthConsumerAcceptance::newFromToken( $dbw, 
$this->vals['accessToken'] );
+                       if ( !$cmra ) {
+                               return $this->failure( 'invalid_access_token', 
'mwoauth-invalid-access-token' );
+                       } elseif ( $cmra->get( 'userId' ) !== 
$this->getUser()->getId() ) {
+                               return $this->failure( 'invalid_access_token', 
'mwoauth-invalid-access-token' );
+                       }
+
+                       $cmra->delete( $dbw );
+
+                       return $this->success( $cmra );
+               }
+       }
+
+       /**
+        * @param DatabaseBase $db
+        * @param int $userId
+        * @return Title
+        */
+       protected function getLogTitle( DatabaseBase $db, $userId ) {
+               $name = $db->selectField( 'user', 'user_name', array( 'user_id' 
=> $userId ) );
+               return Title::makeTitleSafe( NS_USER, $name );
+       }
+}
diff --git a/control/MWOAuthConsumerSubmitControl.php 
b/control/MWOAuthConsumerSubmitControl.php
index b721203..8eae118 100644
--- a/control/MWOAuthConsumerSubmitControl.php
+++ b/control/MWOAuthConsumerSubmitControl.php
@@ -19,14 +19,13 @@
 */
 
 /**
- * Handle the logic of submitting a application consumer request
+ * This handles the core logic of approving/disabling consumers
+ * from using particular user accounts
  *
  * @TODO: improve error messages
  */
 class MWOAuthConsumerSubmitControl extends MWOAuthSubmitControl {
-       protected function getRequiredFields() {
-               return array(
-                       // @TODO: handle input trimming
+       protected function getRequiredFields() {return array(
                        // Proposer (application administrator) actions:
                        'propose'     => array(
                                'name'         => '/^.{1,128}$/',
@@ -137,16 +136,9 @@
                        $logEntry->setTarget( $this->getLogTitle( $dbw, 
$cmr->get( 'userId' ) ) );
                        $logEntry->setComment( $this->vals['description'] );
                        $logEntry->setParameters( array( '4:consumer' => 
$cmr->get( 'consumerKey' ) ) );
-                       $logid = $logEntry->insert( $dbw );
-                       // @TODO: Core wrapper
-                       $dbw->insert( 'log_search',
-                               array(
-                                       'ls_field'  => 'OAuthConsumer',
-                                       'ls_value'  => $cmr->get( 'consumerKey' 
),
-                                       'ls_log_id' => $logid ),
-                               __METHOD__,
-                               'IGNORE'
-                       );
+                       $logEntry->setRelations( array(
+                               'OAuthConsumer' => array( $cmr->get( 
'consumerKey' ) )
+                       ) );
 
                        return $this->success( $cmr );
                case 'update':
@@ -178,16 +170,10 @@
                        $logEntry->setTarget( $this->getLogTitle( $dbw, 
$cmr->get( 'userId' ) ) );
                        $logEntry->setComment( $this->vals['reason'] );
                        $logEntry->setParameters( array( '4:consumer' => 
$cmr->get( 'consumerKey' ) ) );
+                       $logEntry->setRelations( array(
+                               'OAuthConsumer' => array( $cmr->get( 
'consumerKey' ) )
+                       ) );
                        $logid = $logEntry->insert( $dbw );
-                       // @TODO: Core wrapper
-                       $dbw->insert( 'log_search',
-                               array(
-                                       'ls_field'  => 'OAuthConsumer',
-                                       'ls_value'  => $cmr->get( 'consumerKey' 
),
-                                       'ls_log_id' => $logid ),
-                               __METHOD__,
-                               'IGNORE'
-                       );
 
                        return $this->success( $cmr );
                case 'approve':
@@ -219,16 +205,10 @@
                        $logEntry->setTarget( $this->getLogTitle( $dbw, 
$cmr->get( 'userId' ) ) );
                        $logEntry->setComment( $this->vals['reason'] );
                        $logEntry->setParameters( array( '4:consumer' => 
$cmr->get( 'consumerKey' ) ) );
+                       $logEntry->setRelations( array(
+                               'OAuthConsumer' => array( $cmr->get( 
'consumerKey' ) )
+                       ) );
                        $logid = $logEntry->insert( $dbw );
-                       // @TODO: Core wrapper
-                       $dbw->insert( 'log_search',
-                               array(
-                                       'ls_field'  => 'OAuthConsumer',
-                                       'ls_value'  => $cmr->get( 'consumerKey' 
),
-                                       'ls_log_id' => $logid ),
-                               __METHOD__,
-                               'IGNORE'
-                       );
 
                        // @TODO: email/notifications?
 
@@ -258,16 +238,10 @@
                        $logEntry->setTarget( $this->getLogTitle( $dbw, 
$cmr->get( 'userId' ) ) );
                        $logEntry->setComment( $this->vals['reason'] );
                        $logEntry->setParameters( array( '4:consumer' => 
$cmr->get( 'consumerKey' ) ) );
+                       $logEntry->setRelations( array(
+                               'OAuthConsumer' => array( $cmr->get( 
'consumerKey' ) )
+                       ) );
                        $logid = $logEntry->insert( $dbw );
-                       // @TODO: Core wrapper
-                       $dbw->insert( 'log_search',
-                               array(
-                                       'ls_field'  => 'OAuthConsumer',
-                                       'ls_value'  => $cmr->get( 'consumerKey' 
),
-                                       'ls_log_id' => $logid ),
-                               __METHOD__,
-                               'IGNORE'
-                       );
 
                        // @TODO: email/notifications?
 
@@ -301,16 +275,10 @@
                        $logEntry->setTarget( $this->getLogTitle( $dbw, 
$cmr->get( 'userId' ) ) );
                        $logEntry->setComment( $this->vals['reason'] );
                        $logEntry->setParameters( array( '4:consumer' => 
$cmr->get( 'consumerKey' ) ) );
+                       $logEntry->setRelations( array(
+                               'OAuthConsumer' => array( $cmr->get( 
'consumerKey' ) )
+                       ) );
                        $logid = $logEntry->insert( $dbw );
-                       // @TODO: Core wrapper
-                       $dbw->insert( 'log_search',
-                               array(
-                                       'ls_field'  => 'OAuthConsumer',
-                                       'ls_value'  => $cmr->get( 'consumerKey' 
),
-                                       'ls_log_id' => $logid ),
-                               __METHOD__,
-                               'IGNORE'
-                       );
 
                        // @TODO: email/notifications?
 
@@ -340,16 +308,10 @@
                        $logEntry->setTarget( $this->getLogTitle( $dbw, 
$cmr->get( 'userId' ) ) );
                        $logEntry->setComment( $this->vals['reason'] );
                        $logEntry->setParameters( array( '4:consumer' => 
$cmr->get( 'consumerKey' ) ) );
+                       $logEntry->setRelations( array(
+                               'OAuthConsumer' => array( $cmr->get( 
'consumerKey' ) )
+                       ) );
                        $logid = $logEntry->insert( $dbw );
-                       // @TODO: Core wrapper
-                       $dbw->insert( 'log_search',
-                               array(
-                                       'ls_field'  => 'OAuthConsumer',
-                                       'ls_value'  => $cmr->get( 'consumerKey' 
),
-                                       'ls_log_id' => $logid ),
-                               __METHOD__,
-                               'IGNORE'
-                       );
 
                        // @TODO: email/notifications?
 
diff --git a/control/MWOAuthDAOAccessControl.php 
b/control/MWOAuthDAOAccessControl.php
index bb6054a..d303534 100644
--- a/control/MWOAuthDAOAccessControl.php
+++ b/control/MWOAuthDAOAccessControl.php
@@ -69,8 +69,8 @@
         */
        final public function get( $name, $sCallback = null ) {
                $msg = $this->dao->userCanAccess( $name, $this->context );
-               if ( $msg instanceof Message ) {
-                       return $msg;
+               if ( $msg !== true ) {
+                       return $msg; // should be a Message object
                } else {
                        $value = $this->dao->get( $name );
                        return $sCallback ? call_user_func( $sCallback, $value 
) : $value;
diff --git a/control/MWOAuthSubmitControl.php b/control/MWOAuthSubmitControl.php
index c8fa742..9829898 100644
--- a/control/MWOAuthSubmitControl.php
+++ b/control/MWOAuthSubmitControl.php
@@ -94,12 +94,18 @@
         */
        protected function validateFields( array $required ) {
                foreach ( $required as $field => $validator ) {
-                       if ( is_null( $this->vals[$field] ) ) {
+                       if ( !isset( $this->vals[$field] ) ) {
                                // @TODO: check for field-specific message first
                                return $this->failure( "missing_field_$field", 
'mwoauth-missing-field', $field );
+                       } elseif ( !is_scalar( $this->vals[$field] ) ) {
+                               // @TODO: check for field-specific message first
+                               return $this->failure( "invalid_field_$field", 
'mwoauth-invalid-field', $field );
+                       }
+                       if ( is_string( $this->vals[$field] ) ) {
+                               $this->vals[$field] = trim( $this->vals[$field] 
); // trim all input
                        }
                        $valid = is_string( $validator ) // regex
-                               ? is_scalar( $this->vals[$field] ) && 
preg_match( $validator, $this->vals[$field] )
+                               ? preg_match( $validator, $this->vals[$field] )
                                : $validator( $this->vals[$field] );
                        if ( !$valid ) {
                                // @TODO: check for field-specific message first
diff --git a/frontend/MWOAuthUI.setup.php b/frontend/MWOAuthUI.setup.php
index 1f83b48..f2343f7 100644
--- a/frontend/MWOAuthUI.setup.php
+++ b/frontend/MWOAuthUI.setup.php
@@ -15,6 +15,7 @@
                        $groups['MWOAuthConsumerRegistration'] = 'users';
                        $pages['MWOAuthManageConsumers'] = 
'MWOAuthManageConsumers';
                        $groups['MWOAuthManageConsumers'] = 'users';
+                       $pages['MWOAuthManageMyGrants'] = 
'MWOAuthManageMyGrants';
                }
        }
 
diff --git a/frontend/language/MWOAuth.i18n.php 
b/frontend/language/MWOAuth.i18n.php
index 58517a2..68c47fa 100644
--- a/frontend/language/MWOAuth.i18n.php
+++ b/frontend/language/MWOAuth.i18n.php
@@ -20,15 +20,17 @@
        'mwoauth-consumer-key' => 'Consumer key:',
        'mwoauth-consumer-name' => 'Application name:',
        'mwoauth-consumer-version' => 'Major version:',
+       'mwoauth-consumer-user' => 'Publisher:',
        'mwoauth-consumer-stage' => 'Current status:',
        'mwoauth-consumer-email' => 'Contact E-mail address:',
        'mwoauth-consumer-description' => 'Application description:',
        'mwoauth-consumer-callbackurl' => 'OAuth "callback" URL:',
-       'mwoauth-consumer-grantsneeded' => 'Grants used (JSON):',
-       'mwoauth-consumer-wiki' => 'Single-wiki usage:',
+       'mwoauth-consumer-grantsneeded' => 'Applicable grants (JSON):',
+       'mwoauth-consumer-wiki' => 'Applicable wiki:',
        'mwoauth-consumer-restrictions' => 'Usage restictions (JSON):',
        'mwoauth-consumer-rsakey' => 'Consumer RSA key:',
        'mwoauth-consumer-secretkey' => 'Consumer secret token:',
+       'mwoauth-consumer-accesstoken' => 'Access token:',
        'mwoauth-consumer-reason' => 'Reason:',
        'mwoauth-consumer-alreadyexists' => 'A consumer with this 
name/version/author combination already exists',
        'mwoauth-consumer-not-accepted' => 'Cannot update information for a 
pending consumer request',
@@ -37,6 +39,7 @@
        'mwoauth-consumer-not-disabled' => 'The consumer is not currently 
disabled',
        'mwoauth-consumer-not-approved' => 'The consumer is not approved (it 
may have been disabled)',
        'mwoauth-invalid-consumer-key' => 'No consumer exists with the given 
key.',
+       'mwoauth-invalid-access-token' => 'No access token exists with the 
given key.',
 
        'mwoauth-consumer-stage-proposed' => 'proposed',
        'mwoauth-consumer-stage-rejected' => 'rejected',
@@ -112,6 +115,32 @@
        'mwoauthmanageconsumers-success-disabled' => 'Consumer successfuly 
disabled.',
        'mwoauthmanageconsumers-success-reanable' => 'Consumer successfuly 
re-enabled.',
 
+       'mwoauthmanagemygrants' => 'Manage account OAuth grants',
+       'mwoauthmanagemygrants-navigation' => 'Navigation:',
+       'mwoauthmanagemygrants-showlist' => 'Consumer list',
+       'mwoauthmanagemygrants-none' => 'No consumers have access on behalf of 
your account.',
+       'mwoauthmanagemygrants-name' => 'Consumer name',
+       'mwoauthmanagemygrants-user' => 'Publisher',
+       'mwoauthmanagemygrants-description' => 'Description',
+       'mwoauthmanagemygrants-wiki' => 'Applicable wiki',
+       'mwoauthmanagemygrants-wikiallowed' => 'Allowed on wiki',
+       'mwoauthmanagemygrants-grants' => 'Applicable grants',
+       'mwoauthmanagemygrants-grantsallowed' => 'Grants allowed:',
+       'mwoauthmanagemygrants-applicablegrantsallowed' => 'Applicable grants 
allowed:',
+       'mwoauthmanagemygrants-consumerkey' => 'Consumer key',
+       'mwoauthmanagemygrants-review' => 'Review/manage access',
+       'mwoauthmanagemygrants-grantaccept' => 'Granted to consumer',
+       'mwoauthmanagemygrants-confirm-text' => 'Use the form below to revoke 
access or grants for an OAuth consumer to act on your behalf.
+
+Note that if you authorized a consumer to only have access to a subset of 
wikis (site projects), then there will be multiple access tokens for that 
consumer.',
+       'mwoauthmanagemygrants-confirm-legend' => 'Manage consumer access 
token',
+       'mwoauthmanagemygrants-update' => 'Update access token grants',
+       'mwoauthmanagemygrants-renounce' => 'De-authorize and delete access 
token',
+       'mwoauthmanagemygrants-action' => 'Change status:',
+       'mwoauthmanagemygrants-confirm-submit' => 'Update access token status',
+       'mwoauthmanagemygrants-success-update' => 'The access token for this 
consumer was successfully updated.',
+       'mwoauthmanagemygrants-success-renounce' => 'The access token for this 
consumer was successfully deleted.',
+
        'mwoauth-logentry-consumer-propose' => 'proposed an OAuth consumer 
(consumer key $2)',
        'mwoauth-logentry-consumer-update' => 'updated an OAuth consumer 
(consumer key $2)',
        'mwoauth-logentry-consumer-approve' => 'approved an OAuth consumer by 
$1 (consumer key $2)',
diff --git a/frontend/specialpages/actions/MWOAuthManageConsumers.php 
b/frontend/specialpages/actions/MWOAuthManageConsumers.php
index d524b0c..267c214 100644
--- a/frontend/specialpages/actions/MWOAuthManageConsumers.php
+++ b/frontend/specialpages/actions/MWOAuthManageConsumers.php
@@ -76,8 +76,6 @@
                        break;
                }
 
-               $this->addQueueSubtitleLinks( $consumerKey );
-
                if ( $this->stage !== null ) {
                        $this->stageKey = $stageKey;
                        if ( $consumerKey ) {
@@ -88,6 +86,8 @@
                } else {
                        $this->showMainHub();
                }
+
+               $this->addQueueSubtitleLinks( $consumerKey );
 
                $this->getOutput()->addModules( 'ext.MWOAuth' ); // CSS
        }
@@ -103,21 +103,21 @@
                $listLinks = array();
                if ( $consumerKey || $this->stage !== 
MWOAuthConsumer::STAGE_PROPOSED ) {
                        $listLinks[] = Linker::linkKnown(
-                               SpecialPage::getSafeTitleFor( 
'MWOAuthManageConsumers', 'proposed' ),
+                               $this->getTitle( 'proposed' ),
                                $this->msg( 
'mwoauthmanageconsumers-showproposed' )->escaped() );
                } else {
                        $listLinks[] = $this->msg( 
'mwoauthmanageconsumers-showproposed' )->escaped();
                }
                if ( $consumerKey || $this->stage !== 
MWOAuthConsumer::STAGE_REJECTED ) {
                        $listLinks[] = Linker::linkKnown(
-                               SpecialPage::getSafeTitleFor( 
'MWOAuthManageConsumers', 'rejected' ),
+                               $this->getTitle( 'rejected' ),
                                $this->msg( 
'mwoauthmanageconsumers-showrejected' )->escaped() );
                } else {
                        $listLinks[] = $this->msg( 
'mwoauthmanageconsumers-showrejected' )->escaped();
                }
                if ( $consumerKey || $this->stage !== 
MWOAuthConsumer::STAGE_EXPIRED ) {
                        $listLinks[] = Linker::linkKnown(
-                               SpecialPage::getSafeTitleFor( 
'MWOAuthManageConsumers', 'expired' ),
+                               $this->getTitle( 'expired' ),
                                $this->msg( 
'mwoauthmanageconsumers-showexpired' )->escaped() );
                } else {
                        $listLinks[] = $this->msg( 
'mwoauthmanageconsumers-showexpired' )->escaped();
@@ -128,15 +128,8 @@
                $viewall = $this->msg( 'parentheses' )->rawParams( 
Linker::linkKnown(
                        $this->getTitle(), $this->msg( 
'mwoauthmanageconsumers-main' )->escaped() ) );
 
-               // Give grep a chance to find the usages:
-               // mwoauthmanageconsumers-type-proposed, 
mwoauthmanageconsumers-type-reject,
-               // mwoauthmanageconsumers-type-expired
                $this->getOutput()->setSubtitle(
                        "<strong>" . $this->msg( 'mwoauthmanageconsumers-type' 
)->escaped() . " <i>" .
-                       ( $this->stageKey !== null
-                               ? $this->msg( 
"mwoauthmanageconsumers-type-{$this->stageKey}" )->escaped()
-                               : ''
-                       ) .
                        "</i></strong> [{$linkHtml}] 
<strong>{$viewall}</strong>" );
        }
 
@@ -171,7 +164,7 @@
                                '<li>' .
                                "<$tag>" .
                                Linker::makeKnownLinkObj(
-                                       SpecialPage::getSafeTitleFor( 
'MWOAuthManageConsumers', $stageKey ),
+                                       $this->getTitle( $stageKey ),
                                        // Give grep a chance to find the 
usages:
                                        // mwoauthmanageconsumers-q-proposed, 
mwoauthmanageconsumers-q-reject,
                                        // mwoauthmanageconsumers-q-expired
@@ -189,7 +182,7 @@
                        $out->addHTML(
                                '<li>' .
                                Linker::makeKnownLinkObj(
-                                       SpecialPage::getSafeTitleFor( 
'MWOAuthManageConsumers', $stageKey ),
+                                       $this->getTitle( $stageKey ),
                                        // Give grep a chance to find the 
usages:
                                        // mwoauthmanageconsumers-l-proposed, 
mwoauthmanageconsumers-l-reject,
                                        // mwoauthmanageconsumers-l-expired
@@ -243,6 +236,11 @@
                                        'label-message' => 
'mwoauth-consumer-version',
                                        'default' => $cmr->get( 'version' )
                                ),
+                               'user' => array(
+                                       'type' => 'info',
+                                       'label-message' => 
'mwoauth-consumer-user',
+                                       'default' => $cmr->get( 'userId', 
array( 'User', 'whoIs' ) )
+                               ),
                                'stage' => array(
                                        'type' => 'info',
                                        'label-message' => 
'mwoauth-consumer-stage',
@@ -286,6 +284,11 @@
                                        'default' => $cmr->get( 'restrictions', 
array( 'FormatJSON', 'encode' ) ),
                                        'readonly' => true,
                                        'rows' => 5
+                               ),
+                               'secretKey' => array(
+                                       'type' => 'info',
+                                       'label-message' => 
'mwoauth-consumer-secretkey',
+                                       'default' => $cmr->get( 'secretKey' )
                                ),
                                'rsaKey' => array(
                                        'type' => 'textarea',
@@ -335,6 +338,9 @@
                $status = $form->show();
                if ( $status instanceof Status && $status->isOk() ) {
                        $type = 
self::$stageKeyMap[$status->value['result']->get( 'stage' )];
+                       // Uses messages 
mwoauthmanageconsumers-success-proposed,
+                       // mwoauthmanageconsumers-success-rejected, 
mwoauthmanageconsumers-success-approved,
+                       // mwoauthmanageconsumers-success-disabled, 
mwoauthmanageconsumers-success-reenabled
                        $this->getOutput()->addWikiMsg( 
"mwoauthmanageconsumers-success-$type" );
                        $this->getOutput()->returnToMain();
                } else {
@@ -392,7 +398,7 @@
                $stageKey = self::$stageKeyMap[$cmr->get( 'stage' )];
 
                $link = Linker::linkKnown(
-                       SpecialPage::getSafeTitleFor( 'MWOAuthManageConsumers', 
"{$stageKey}/{$cmrKey}" ),
+                       $this->getTitle( "{$stageKey}/{$cmrKey}" ),
                        $this->msg( 'mwoauthmanageconsumers-review' )->escaped()
                );
 
diff --git a/frontend/specialpages/actions/MWOAuthManageMyGrants.php 
b/frontend/specialpages/actions/MWOAuthManageMyGrants.php
new file mode 100644
index 0000000..b5d8064
--- /dev/null
+++ b/frontend/specialpages/actions/MWOAuthManageMyGrants.php
@@ -0,0 +1,368 @@
+<?php
+/*
+ (c) Aaron Schulz 2013, GPL
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ http://www.gnu.org/copyleft/gpl.html
+*/
+
+/**
+ * Special page for listing consumers this user granted access to and
+ * for manage the specific grants given or revoking access for the consumer
+ */
+class MWOAuthManageMyGrants extends UnlistedSpecialPage {
+       protected static $stageKeyMap = array(
+               MWOAuthConsumer::STAGE_PROPOSED => 'proposed',
+               MWOAuthConsumer::STAGE_REJECTED => 'rejected',
+               MWOAuthConsumer::STAGE_EXPIRED  => 'expired',
+               MWOAuthConsumer::STAGE_APPROVED => 'approved',
+               MWOAuthConsumer::STAGE_DISABLED => 'disabled',
+       );
+
+       public function __construct() {
+               parent::__construct( 'MWOAuthManageMyGrants', 
'mwoauthmanagemygrants' );
+       }
+
+       public function execute( $par ) {
+               $user = $this->getUser();
+               $request = $this->getRequest();
+
+               if ( !$user->isAllowed( 'mwoauthmanagemygrants' ) ) {
+                       throw new PermissionsError( 'mwoauthmanagemygrants' );
+               } elseif ( !$user->getID() ) {
+                       throw new PermissionsError( 'user' );
+               }
+
+               $this->setHeaders();
+               $this->getOutput()->disallowUserJs();
+
+               // Format is 
Special:MWOAuthManageMyGrants[/list|/manage/<accesstoken>]
+               $navigation = explode( '/', $par );
+               $typeKey = isset( $navigation[0] ) ? $navigation[0] : null;
+               $accessToken = isset( $navigation[1] ) ? $navigation[1] : null;
+
+               switch ( $typeKey ) {
+               case 'manage':
+                       $this->handleConsumerForm( $accessToken );
+                       break;
+               case 'list':
+                       // fall through
+               default:
+                       $this->showConsumerList();
+                       break;
+               }
+
+               $this->addSubtitleLinks( $accessToken );
+
+               $this->getOutput()->addModules( 'ext.MWOAuth' ); // CSS
+       }
+
+       /**
+        * Show other parent page link
+        *
+        * @param type $accessToken
+        * @return void
+        */
+       protected function addSubtitleLinks( $accessToken ) {
+               $listLinks = array();
+               if ( $accessToken ) {
+                       $listLinks[] = Linker::linkKnown(
+                               $this->getTitle( 'proposed' ),
+                               $this->msg( 'mwoauthmanagemygrants-showlist' 
)->escaped() );
+               } else {
+                       $listLinks[] = $this->msg( 
'mwoauthmanagemygrants-showlist' )->escaped();
+               }
+
+               $linkHtml = $this->getLanguage()->pipeList( $listLinks );
+
+               $this->getOutput()->setSubtitle(
+                       "<strong>" . $this->msg( 
'mwoauthmanagemygrants-navigation' )->escaped() .
+                       "</strong> [{$linkHtml}]" );
+       }
+
+       /**
+        * Show the form to approve/reject/disable/re-enable consumers
+        *
+        * @param string $accessToken
+        * @return void
+        */
+       protected function handleConsumerForm( $accessToken ) {
+               $user = $this->getUser();
+               $db = MWOAuthUtils::getCentralDB( DB_SLAVE );
+
+               $cmra = MWOAuthDAOAccessControl::wrap(
+                       MWOAuthConsumerAcceptance::newFromToken( $db, 
$accessToken ), $this->getContext() );
+               if ( !$cmra ) {
+                       $this->getOutput()->addHtml( $this->msg( 
'mwoauth-invalid-access-token' )->escaped() );
+                       return;
+               }
+
+               $cmr = MWOAuthDAOAccessControl::wrap(
+                       MWOAuthConsumer::newFromId( $db, $cmra->get( 
'consumerId' ) ), $this->getContext() );
+               if ( $cmr->get( 'deleted' ) && !$user->isAllowed( 
'mwoauthviewsuppressed' ) ) {
+                       throw new PermissionsError( 'mwoauthviewsuppressed' );
+               }
+
+               $form = new HTMLForm(
+                       array(
+                               'accessToken' => array(
+                                       'type' => 'text',
+                                       'label-message' => 
'mwoauth-consumer-accesstoken',
+                                       'default' => $cmra->get( 'accessToken' 
),
+                                       'size' => '40',
+                                       'readonly' => true
+                               ),
+                               'name' => array(
+                                       'type' => 'info',
+                                       'label-message' => 
'mwoauth-consumer-name',
+                                       'default' => $cmr->get( 'name' )
+                               ),
+                               'user' => array(
+                                       'type' => 'info',
+                                       'label-message' => 
'mwoauth-consumer-user',
+                                       'default' => $cmr->get( 'userId', 
array( 'User', 'whoIs' ) )
+                               ),
+                               'version' => array(
+                                       'type' => 'info',
+                                       'label-message' => 
'mwoauth-consumer-version',
+                                       'default' => $cmr->get( 'version' )
+                               ),
+                               'consumerKey' => array(
+                                       'type' => 'info',
+                                       'label-message' => 
'mwoauth-consumer-key',
+                                       'default' => $cmr->get( 'consumerKey' )
+                               ),
+                               'description' => array(
+                                       'type' => 'textarea',
+                                       'label-message' => 
'mwoauth-consumer-description',
+                                       'default' => $cmr->get( 'description' ),
+                                       'readonly' => true,
+                                       'rows' => 5
+                               ),
+                               'grants'  => array(
+                                       'type' => 'checkmatrix',
+                                       'label-message' => 
'mwoauthmanagemygrants-applicablegrantsallowed',
+                                       'columns' => array(
+                                               $this->msg( 
'mwoauthmanagemygrants-grantaccept' )->escaped() => 'grant'
+                                       ),
+                                       'rows' => array_combine(
+                                               $cmr->get( 'grants' ), // 
@TODO: use messages
+                                               $cmr->get( 'grants' )
+                                       ),
+                                       'default' => array_map(
+                                               function( $g ) { return 
"grant-$g"; },
+                                               $cmra->get( 'grants' )
+                                       )
+                                       // @TODO: tooltips
+                               ),
+                               'usedOnWiki' => array(
+                                       'type' => 'info',
+                                       'label-message' => 
'mwoauth-consumer-wiki',
+                                       'default' => $cmr->get( 'wiki' )
+                               ),
+                               'wiki' => array(
+                                       'type' => 'text',
+                                       'label-message' => 
'mwoauthmanagemygrants-wikiallowed',
+                                       'default' => $cmra->get( 'wiki' )
+                               ),
+                               'action' => array(
+                                       'type' => 'radio',
+                                       'label-message' => 
'mwoauthmanagemygrants-action',
+                                       'required' => true,
+                                       'options' => array(
+                                               $this->msg( 
'mwoauthmanagemygrants-update' )->escaped() => 'update',
+                                               $this->msg( 
'mwoauthmanagemygrants-renounce' )->escaped() => 'renounce' )
+                               )
+                       ),
+                       $this->getContext()
+               );
+               $act = null;
+               $form->setSubmitCallback( function( array $data, IContextSource 
$context ) use ( &$act ) {
+                       $act = $data['action']; // this will be valid on success
+                       $data['grants'] = FormatJSON::encode( // adapt form to 
controller
+                               preg_replace( '/^grant-/', '', $data['grants'] 
) );
+                       $controller = new 
MWOAuthConsumerAcceptanceSubmitControl( $context, $data );
+                       return $controller->submit();
+               } );
+
+               $form->setWrapperLegendMsg( 
'mwoauthmanagemygrants-confirm-legend' );
+               $form->setSubmitTextMsg( 'mwoauthmanagemygrants-confirm-submit' 
);
+               $form->addPreText(
+                       $this->msg( 'mwoauthmanagemygrants-confirm-text' 
)->parseAsBlock() );
+
+               $status = $form->show();
+               if ( $status instanceof Status && $status->isOk() ) {
+                       // Uses messages mwoauthmanagemygrants-success-update,
+                       // mwoauthmanagemygrants-success-renounce
+                       $this->getOutput()->addWikiMsg( 
"mwoauthmanagemygrants-success-$act" );
+                       $this->getOutput()->returnToMain();
+               }
+       }
+
+       /**
+        * Show a paged list of consumers with links to details
+        *
+        * @param string $accessToken
+        * @return void
+        */
+       protected function showConsumerList() {
+               $out = $this->getOutput();
+
+               $pager = new MWOAuthManageMyGrantsPager( $this, array(), 
$this->getUser()->getId() );
+
+               if ( $pager->getNumRows() ) {
+                       $out->addHTML( $pager->getNavigationBar() );
+                       $out->addHTML( $pager->getBody() );
+                       $out->addHTML( $pager->getNavigationBar() );
+               } else {
+                       $out->addWikiMsg( "mwoauthmanagemygrants-none" );
+               }
+       }
+
+       /**
+        * @param DatabaseBase $db
+        * @param sdtclass $row
+        * @return string
+        */
+       public function formatRow( $db, $row ) {
+               $cmr = MWOAuthDAOAccessControl::wrap(
+                       MWOAuthConsumer::newFromRow( $db, $row ), 
$this->getContext() );
+               $cmra = MWOAuthDAOAccessControl::wrap(
+                       MWOAuthConsumerAcceptance::newFromRow( $db, $row ), 
$this->getContext() );
+
+               $cmrKey = $cmr->get( 'consumerKey' );
+               $stageKey = self::$stageKeyMap[$cmr->get( 'stage' )];
+
+               $link = Linker::linkKnown(
+                       $this->getTitle( 'manage/' . $cmra->get( 'accessToken' 
) ),
+                       $this->msg( 'mwoauthmanagemygrants-review' )->escaped()
+               );
+
+               $time = $this->getLanguage()->timeanddate(
+                       wfTimestamp( TS_MW, $cmr->get( 'registration' ) ), true 
);
+
+               $encStageKey = htmlspecialchars( $stageKey ); // sanity
+               $r = "<li 
class='mw-mwoauthmanagemygrants-type-{$encStageKey}'>";
+
+               $r .= $time . " (<strong>{$link}</strong>)";
+
+               $lang = $this->getLanguage();
+               $data = array(
+                       'mwoauthmanagemygrants-name' =>
+                               $cmr->get( 'name', function( $s ) use ( $cmr ) {
+                                       return $s . ' [' . $cmr->get( 'version' 
) . ']'; } ),
+                       'mwoauthmanagemygrants-user' => $cmr->get( 'userId', 
array( 'User', 'whoIs' ) ),
+                       'mwoauthmanagemygrants-description' =>
+                               $cmr->get( 'description', function( $s ) use ( 
$lang ) {
+                                       return $lang->truncate( $s, 10024 ); } 
),
+                       'mwoauthmanagemygrants-wiki' => $cmr->get( 'wiki' ),
+                       'mwoauthmanagemygrants-wikiallowed' => $cmra->get( 
'wiki' ),
+                       'mwoauthmanagemygrants-grants' => $lang->commaList( 
$cmr->get( 'grants' ) ),
+                       'mwoauthmanagemygrants-grantsallowed' => 
$lang->commaList( $cmra->get( 'grants' ) ),
+                       'mwoauthmanagemygrants-consumerkey' => $cmr->get( 
'consumerKey' )
+               );
+
+               $r .= "<table 
class='mw-mwoauthmanagemygrants-body-{$encStageKey}' " .
+                       "cellspacing='1' cellpadding='3' border='1' 
width='100%'>";
+               foreach ( $data as $msg => $value ) {
+                       $r .= '<tr>' .
+                               '<td><strong>' . $this->msg( $msg )->escaped() 
. '</strong></td>' .
+                               '<td width=\'90%\'>' . htmlspecialchars( $value 
) . '</td>' .
+                               '</tr>';
+               }
+               $r .= '</table>';
+
+               $r .= '</li>';
+
+               return $r;
+       }
+}
+
+/**
+ * Query to list out consumers that have an access token for this user
+ *
+ * @TODO: use UserCache
+ */
+class MWOAuthManageMyGrantsPager extends ReverseChronologicalPager {
+       public $mForm, $mConds;
+
+       function __construct( $form, $conds, $user ) {
+               $this->mForm = $form;
+               $this->mConds = $conds;
+
+               $this->mConds[] = 'oaac_consumer_id = oarc_id';
+               $this->mConds['oaac_user_id'] = $user;
+
+               parent::__construct();
+               # Treat 20 as the default limit, since each entry takes up 5 
rows.
+               $urlLimit = $this->mRequest->getInt( 'limit' );
+               $this->mLimit = $urlLimit ? $urlLimit : 20;
+       }
+
+       /**
+        * @return Title
+        */
+       function getTitle() {
+               return $this->mForm->getFullTitle();
+       }
+
+       /**
+        * @param $row
+        * @return string
+        */
+       function formatRow( $row ) {
+               return $this->mForm->formatRow( $this->mDb, $row );
+       }
+
+       /**
+        * @return string
+        */
+       function getStartBody() {
+               if ( $this->getNumRows() ) {
+                       return '<ul>';
+               } else {
+                       return '';
+               }
+       }
+
+       /**
+        * @return string
+        */
+       function getEndBody() {
+               if ( $this->getNumRows() ) {
+                       return '</ul>';
+               } else {
+                       return '';
+               }
+       }
+
+       /**
+        * @return array
+        */
+       function getQueryInfo() {
+               return array(
+                       'tables' => array( 'oauth_accepted_consumer', 
'oauth_registered_consumer' ),
+                       'fields' => array( '*' ),
+                       'conds'  => $this->mConds
+               );
+       }
+
+       /**
+        * @return string
+        */
+       function getIndexField() {
+               return 'oaac_accepted';
+       }
+}

-- 
To view, visit https://gerrit.wikimedia.org/r/72990
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I669bd4b750cdfafd18dad60d8cdabdc1675113f4
Gerrit-PatchSet: 3
Gerrit-Project: mediawiki/extensions/OAuth
Gerrit-Branch: master
Gerrit-Owner: Aaron Schulz <[email protected]>
Gerrit-Reviewer: CSteipp <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to