CSteipp has submitted this change and it was merged.

Change subject: Hooks to tie OAuth in with User rights and the API
......................................................................


Hooks to tie OAuth in with User rights and the API

** WIP, do not merge yet **

This is a draft of appropriate hooks to have the User object created for
the session to have its rights adjusted based on the OAuth grants,
without having all other User objects also affected (because that would
break stuff like ApiQueryUsers).

Note this is a rough draft and still needs testing and security review,
and the implementation may change greatly by the time that's done.

Change-Id: I8e9784b2bf4e5b85b5a691a3354e421381ae75b3
---
M OAuth.config.php
M api/MWOAuthAPI.setup.php
M frontend/language/MWOAuth.i18n.php
3 files changed, 225 insertions(+), 1 deletion(-)

Approvals:
  CSteipp: Verified; Looks good to me, approved
  jenkins-bot: Verified



diff --git a/OAuth.config.php b/OAuth.config.php
index 8902ec6..f0d82fe 100644
--- a/OAuth.config.php
+++ b/OAuth.config.php
@@ -127,5 +127,11 @@
 /** @var bool Require HTTPs for user transactions that might send out secret 
tokens */
 $wgMWOAuthSecureTokenTransfer = false;
 
+/** @var array List of API module classes to disable when OAuth is used for 
the request. */
+$wgMWOauthDisabledApiModules = array(
+       'ApiLogin',
+       'ApiLogout',
+);
+
 # End of configuration variables.
 # ########
diff --git a/api/MWOAuthAPI.setup.php b/api/MWOAuthAPI.setup.php
index 43a2ff1..23767f7 100644
--- a/api/MWOAuthAPI.setup.php
+++ b/api/MWOAuthAPI.setup.php
@@ -4,11 +4,205 @@
  */
 class MWOAuthAPISetup {
        /**
+        * Create the appropriate type of exception to throw, based on MW_API
+        *
+        * @param string $msgKey Key for the error message
+        * Varargs: normal message parameters.
+        * @return MWException
+        */
+       private static function makeException( $msgKey /* ... */ ) {
+               $params = func_get_args();
+               array_shift( $params );
+               $msg = wfMessage( $msgKey, $params );
+               if ( defined( 'MW_API' ) ) {
+                       $msg = $msg->inLanguage( 'en' )->useDatabase( false 
)->plain();
+                       return new UsageException( $msg, $msgKey );
+               } else {
+                       return new ErrorPageError( 
'mwoauth-invalid-authorization-title', $msg );
+               }
+       }
+
+       /**
+        * Validate the OAuth headers and fetch the access token
+        *
+        * @throws UsageException if the headers are invalid and MW_API is 
defined
+        * @throws ErrorPageError if the headers are invalid and MW_API is not 
defined
+        * @return OAuthToken|null
+        */
+       private static function getOAuthAccessToken() {
+               static $result = false;
+               if ( $result === false ) {
+                       $context = RequestContext::getMain();
+                       $request = $context->getRequest();
+                       $title = $context->getTitle();
+                       if ( !MWOAuthUtils::hasOAuthHeaders( $request ) || 
$title->isSpecial( 'MWOAuth' ) ) {
+                               $result = null;
+                       } else {
+                               try {
+                                       $server = 
MWOAuthUtils::newMWOAuthServer();
+                                       $oauthRequest = 
MWOAuthRequest::fromRequest( $request );
+                                       list( , $result ) = 
$server->verify_request( $oauthRequest );
+                               } catch ( OAuthException $ex ) {
+                                       $result = $ex;
+                               }
+                       }
+               }
+
+               if ( $result instanceof OAuthException ) {
+                       throw self::makeException( 
'mwoauth-invalid-authorization', $result->getMessage() );
+               }
+               return $result;
+       }
+
+       /**
         * Register hooks handlers
-        * @param $hooks Array $wgHooks (assoc array of hooks and handlers)
+        * @param Array $hooks $wgHooks (assoc array of hooks and handlers)
         * @return void
         */
        public static function defineHookHandlers( array &$hooks ) {
+               $hooks['UserLoadFromSession'][] = __CLASS__ . 
'::onUserLoadFromSession';
+               $hooks['UserLoadAfterLoadFromSession'][] = __CLASS__ . 
'::onUserLoadAfterLoadFromSession';
+               $hooks['UserGetRights'][] = __CLASS__ . '::onUserGetRights';
+               $hooks['UserIsEveryoneAllowed'][] = __CLASS__ . 
'::onUserIsEveryoneAllowed';
+               $hooks['ApiCheckCanExecute'][] = __CLASS__ . 
'::onApiCheckCanExecute';
+       }
 
+       /**
+        * User is being loaded from session data
+        *
+        * We need to validate the OAuth credentials, and tag this user object.
+        *
+        * @throws UsageException
+        * @param User $user
+        * @param boolean|null &$result Set to a boolean to skip the normal 
loading
+        * @return boolean
+        */
+       public static function onUserLoadFromSession( User $user, &$result ) {
+               $user->oAuthSessionData = array();
+               try {
+                       $accesstoken = self::getOAuthAccessToken();
+                       if ( $accesstoken !== null ) {
+                               $dbr = MWOAuthUtils::getCentralDB( DB_SLAVE );
+                               $access = 
MWOAuthConsumerAcceptance::newFromToken( $dbr, $accesstoken->key );
+                               if ( $access->get( 'wiki' ) !== '*' && 
$access->get( 'wiki' ) !== wfWikiID() ) {
+                                       throw self::makeException( 
'mwoauth-invalid-authorization-wrong-wiki', wfWikiID() );
+                               }
+                               $consumer = MWOAuthConsumer::newFromId( $dbr, 
$access->get( 'consumerId' ) );
+                               if ( $consumer->get( 'stage' ) !== 
MWOAuthConsumer::STAGE_APPROVED ) {
+                                       throw self::makeException( 
'mwoauth-invalid-authorization-not-approved' );
+                               }
+                               $localUser = 
MWOAuthUtils::getLocalUserFromCentralId( $access->get( 'userId' ) );
+                               if ( !$localUser || !$localUser->isLoggedIn() ) 
{
+                                       throw self::makeException( 
'mwoauth-invalid-authorization-invalid-user' );
+                               }
+                               if ( $user->isLoggedIn() && $user->getId() !== 
$localUser->getId() ) {
+                                       throw self::makeException( 
'mwoauth-invalid-authorization-wrong-user' );
+                               }
+                               $user->setID( $localUser->getId() );
+                               $user->loadFromId();
+                               $user->oAuthSessionData += array(
+                                       'accesstoken' => $accesstoken,
+                                       'rights' => 
MWOAuthUtils::getGrantRights( $access->get( 'grants' ) ),
+                               );
+                               $result = true;
+                       }
+               } catch( ErrorPageError $ex ) {
+                       // We can't throw an ErrorPageError from 
UserLoadFromSession,
+                       // because OutputPage needs a User object and it 
wouldn't be
+                       // available yet. The UserLoadAfterLoadFromSession hook 
function
+                       // will throw the ErrorPageError when it is safe to do 
so.
+                       $user->oAuthSessionData['exception'] = $ex;
+                       $result = false;
+                       return false;
+               }
+               return true;
+       }
+
+       /**
+        * Called after user is loaded from session data
+        *
+        * If the user somehow missed our onUserLoadFromSession, then there are
+        * multiple fancy auth mechanisms going on. Don't allow that.
+        *
+        * If UserLoadFromSession couldn't throw an ErrorPageError, throw it 
now.
+        *
+        * @throws ErrorPageError
+        * @throws MWException
+        * @param User $user
+        * @return boolean
+        */
+       public static function onUserLoadAfterLoadFromSession( User $user ) {
+               // If there was an exception that couldn't be thrown from
+               // UserLoadFromSession, throw it now.
+               if ( isset( $user->oAuthSessionData['exception'] ) ) {
+                       throw $user->oAuthSessionData['exception'];
+               }
+
+               // If we have OAuth headers, the oAuthSessionData had better be 
valid
+               if ( self::getOAuthAccessToken() !== null &&
+                       !isset( $user->oAuthSessionData['accesstoken'] )
+               ) {
+                       throw new MWException( __METHOD__ . ': OAuth headers 
are present, but the ' .
+                               __CLASS__ . '::onUserLoadFromSession hook 
function was not called' );
+               }
+
+               return true;
+       }
+
+       /**
+        * Called when the user's rights are being fetched
+        *
+        * @param User $user
+        * @param array &$rights current rights list
+        * @return boolean
+        */
+       public static function onUserGetRights( User $user, array &$rights ) {
+               if ( isset( $user->oAuthSessionData['rights'] ) ) {
+                       $rights = array_intersect( $rights, 
$user->oAuthSessionData['rights'] );
+               }
+               return true;
+       }
+
+       /**
+        * Called to check if everyone has a particular user right
+        *
+        * @param string $right
+        * @return boolean
+        */
+       public static function onUserIsEveryoneAllowed( $right ) {
+               /** @todo: If we implement a "default" grant, return true for 
rights granted there. */
+               return false;
+       }
+
+       /**
+        * Disable certain API modules when used with OAuth
+        *
+        * Modules such as ApiLogin and ApiLogout make no sense with OAuth.
+        *
+        * @param ApiBase $module
+        * @param User $user
+        * @param string|array &$message
+        * @return boolean
+        */
+       public static function onApiCheckCanExecute( ApiBase $module, User 
$user, &$message ) {
+               global $wgMWOauthDisabledApiModules;
+
+               if ( !isset( $user->oAuthSessionData['accesstoken'] ) ) {
+                       return true;
+               }
+
+               foreach ( $wgMWOauthDisabledApiModules as $badModule ) {
+                       if ( $module instanceof $badModule ) {
+                               // Awful interface, API.
+                               
ApiBase::$messageMap['mwoauth-api-module-disabled'] = array(
+                                       'code' => 'mwoauth-api-module-disabled',
+                                       'info' => 'The "$1" module is not 
available with OAuth.',
+                               );
+                               $message = array( 
'mwoauth-api-module-disabled', $module->getModuleName() );
+                               return false;
+                       }
+               }
+
+               return true;
        }
 }
diff --git a/frontend/language/MWOAuth.i18n.php 
b/frontend/language/MWOAuth.i18n.php
index 0daa31b..1863f84 100644
--- a/frontend/language/MWOAuth.i18n.php
+++ b/frontend/language/MWOAuth.i18n.php
@@ -185,6 +185,13 @@
        'mwoauthserver-invalid-request-token' => 'Invalid token in your 
request.',
        'mwoauthserver-invalid-user-hookabort' => 'This user cannot use OAuth.',
 
+       'mwoauth-invalid-authorization-title' => 'OAuth authorization error',
+       'mwoauth-invalid-authorization' => 'The authorization headers in your 
request are not valid: $1',
+       'mwoauth-invalid-authorization-wrong-wiki' => 'The authorization 
headers in your request are not valid for $1',
+       'mwoauth-invalid-authorization-invalid-user' => 'The authorization 
headers in your request are for a user that doesn\'t exist here',
+       'mwoauth-invalid-authorization-wrong-user' => 'The authorization 
headers in your request are for a different user',
+       'mwoauth-invalid-authorization-not-approved' => 'The authorization 
headers in your request are for an OAuth consumer that is not currently 
approved',
+
        'mwoauth-form-description' => 'The following application is requesting 
to use MediaWiki on your behalf. The application will be able to perform any 
actions that are allowed with the list if requested rights below. Only allow 
applications that you trust to use these permissions as you would.',
        'mwoauth-form-existing' => "'''This application is requesting 
authorization to MediaWiki on your behalf, but you have already granted 
access:'''
 *  Grants: $1
@@ -432,6 +439,23 @@
        'mwoauth-authorize-form-description' => '{{Identical|Application 
description}}',
        'mwoauth-authorize-form-version' => '{{Identical|Application version}}',
        'mwoauth-authorize-form-wiki' => '{{Identical|Wiki}}',
+       'mwoauth-invalid-authorization-title' => 'Title of the error page when 
the Authorization header is invalid',
+       'mwoauth-invalid-authorization' => 'Text of the error page when the 
Authorization header is invalid. Parameters are:
+* $1 - Specific error message from the OAuth layer, probably not localized',
+       'mwoauth-invalid-authorization-wrong-wiki' => 'Text of the error page 
when the Authorization header is for the wrong wiki. Parameters are:
+* $1 - wiki id',
+       'mwoauth-invalid-authorization-invalid-user' => 'Text of the error page 
when the Authorization header is for a user that doesn\'t exist',
+       'mwoauth-invalid-authorization-wrong-user' => 'Text of the error page 
when the Authorization header is for the wrong user',
+       'mwoauth-invalid-authorization-not-approved' => 'Text of the error page 
when the Authorization header is for a consumer that isn\'t approved',
+       'mwoauth-grants-editpages' => '{{Identical|Edit page}}',
+       'mwoauth-grants-movepages' => '{{Identical|Move page}}',
+       'mwoauth-grants-createpages' => '{{Identical|Create page}}',
+       'mwoauth-grants-deletepages' => '{{Identical|Delete page}}',
+       'mwoauth-grants-upload' => '{{Identical|Upload file}}',
+
+       'mwoauthconsumerregistration-updated' => 'Shown as success message',
+       'mwoauthconsumerregistration-secretreset' => 'Shown on success message. 
Parameters:
+* $1 - new secret token',
        'mwoauth-callback-not-oob' => 'Warning that the OAuth developer failed 
to include the required "oauth_callback" parameter, which must be set to the 
case-sensitive string "oob"',
        'right-mwoauthproposeconsumer' => 
'{{doc-right|mwoauthproposeconsumer}}',
        'right-mwoauthupdateconsumer' => '{{doc-right|mwoauthupdateconsumer}}',

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

Gerrit-MessageType: merged
Gerrit-Change-Id: I8e9784b2bf4e5b85b5a691a3354e421381ae75b3
Gerrit-PatchSet: 10
Gerrit-Project: mediawiki/extensions/OAuth
Gerrit-Branch: master
Gerrit-Owner: Anomie <[email protected]>
Gerrit-Reviewer: Aaron Schulz <[email protected]>
Gerrit-Reviewer: Anomie <[email protected]>
Gerrit-Reviewer: CSteipp <[email protected]>
Gerrit-Reviewer: Tim Starling <[email protected]>
Gerrit-Reviewer: jenkins-bot

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

Reply via email to