Anomie has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/73977


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, 168 insertions(+), 1 deletion(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/OAuth 
refs/changes/77/73977/1

diff --git a/OAuth.config.php b/OAuth.config.php
index 3c4f9ae..6f80576 100644
--- a/OAuth.config.php
+++ b/OAuth.config.php
@@ -50,5 +50,10 @@
 
 $wgGroupPermissions['user']['mwoauthmanagemygrants'] = true;
 
+$wgMWOauthDisabledApiModules = array(
+       'ApiLogin',
+       'ApiLogout',
+);
+
 # End of configuration variables.
 # ########
diff --git a/api/MWOAuthAPI.setup.php b/api/MWOAuthAPI.setup.php
index 43a2ff1..be73597 100644
--- a/api/MWOAuthAPI.setup.php
+++ b/api/MWOAuthAPI.setup.php
@@ -4,11 +4,167 @@
  */
 class MWOAuthAPISetup {
        /**
+        * 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() {
+               global $wgMemc, $wgRequest;
+               if ( !MWOAuthUtils::hasOAuthHeaders( $wgRequest ) ) {
+                       return null;
+               }
+               try {
+                       $dbr = MWOAuthUtils::getCentralDB( DB_SLAVE );
+                       $oauthRequest = MWOAuthRequest::newFromRequest( 
$wgRequest );
+                       $store = new MWOAuthDataStore( $wgMemc, $dbr );
+                       $serv = new MWOAuthServer( $store );
+                       list( , $accesstoken ) = $serv->verify_request( 
$oauthRequest );
+                       return $accesstoken;
+               } catch ( OAuthException $ex ) {
+                       $msg = wfMessage( 'mwoauth-invalid-authorization', 
$ex->getMessage() );
+                       if ( defined( 'MW_API' ) ) {
+                               $msg = $msg->inLanguage( 'en' )->useDatabase( 
false )->plain();
+                               throw new UsageException( $msg, 
'mwoauth-invalid-authorization' );
+                       } else {
+                               throw new ErrorPageError( 
'mwoauth-invalid-authorization-title', $msg );
+                       }
+               }
+       }
+
+       /**
         * 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'] = array( __CLASS__, 
'onUserLoadFromSession' );
+               $hooks['UserLoadAfterLoadFromSession'] = array( __CLASS__, 
'onUserLoadAfterLoadFromSession' );
+               $hooks['UserGetRights'] = array( __CLASS__, 'onUserGetRights' );
+               $hooks['UserIsEveryoneAllowed'] = array( __CLASS__, 
'onUserIsEveryoneAllowed' );
+               $hooks['ApiCheckCanExecute'] = array( __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 ) {
+               try {
+                       $user->isOauthSessionUser = self::getOAuthAccessToken() 
!== null;
+
+                       // For good measure, make sure $user->mRights is cleared
+                       if ( $user->isOauthSessionUser ) {
+                               $user->clearInstanceCache();
+                       }
+               } 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 same ErrorPageError when it is safe 
to do so.
+                       $user->isOauthSessionUser = false;
+               }
+               return true;
+       }
+
+       /**
+        * Called after user is loaded from session data
+        *
+        * Sanity check: If the user somehow missed our onUserLoadFromSession, 
then
+        * there are multiple fancy auth mechanisms going on. Don't allow that.
+        *
+        * @throws ErrorPageError
+        * @throws MWException
+        * @param User $user
+        * @return boolean
+        */
+       public static function onUserLoadAfterLoadFromSession( User $user ) {
+               if (
+                       // This checks if the headers are present and valid, 
throwing the
+                       // ErrorPageError that we couldn't throw from 
UserLoadFromSession
+                       // if necessary.
+                       self::getOAuthAccessToken() !== null &&
+
+                       // And this makes sure our UserLoadFromSession hook was 
really
+                       // called, if OAuth headers are present. Otherwise 
something may
+                       // have already loaded the non-OAuth user rights before 
our
+                       // UserGetRights hook was activated.
+                       !( isset( $user->isOauthSessionUser ) && 
$user->isOauthSessionUser )
+               ) {
+                       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 ) {
+               global $wgMemc, $wgRequest;
+               if ( isset( $user->isOauthSessionUser ) && 
$user->isOauthSessionUser ) {
+                       $dbr = MWOAuthUtils::getCentralDB( DB_SLAVE );
+                       $accesstoken = self::getOAuthAccessToken();
+                       $access = MWOAuthConsumerAcceptance::newFromToken( 
$dbr, $accesstoken );
+                       $grantRights = MWOAuthUtils::getGrantRights( 
$access->get( 'grants' ) );
+                       $rights = array_intersect( $rights, $grantRights );
+               }
+               return true;
+       }
+
+       /**
+        * Called to check if everyone has a particular user right
+        *
+        * @param string $right
+        * @return boolean
+        */
+       public static function onUserIsEveryoneAllowed( $right ) {
+               global $wgRequest;
+               if ( MWOAuthUtils::hasOAuthHeaders( $wgRequest ) ) {
+                       /** @todo: If we implement a "default" grant, return 
true for rights granted there. */
+                       return false;
+               }
+               return true;
+       }
+
+       /**
+        * Disable certain API modules when used 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->isOauthSessionUser ) || 
!$user->isOauthSessionUser ) {
+                       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 2ff6c44..cae008a 100644
--- a/frontend/language/MWOAuth.i18n.php
+++ b/frontend/language/MWOAuth.i18n.php
@@ -178,6 +178,9 @@
        '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-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 reqested rights below. Only allow 
applications that you trust to use these permissions as you would.',
        'mwoauth-form-legal' => '',
        'mwoauth-form-button-approve' => 'Yes, allow',
@@ -384,6 +387,9 @@
        '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-grants-editpages' => '{{Identical|Edit page}}',
        'mwoauth-grants-movepages' => '{{Identical|Move page}}',
        'mwoauth-grants-createpages' => '{{Identical|Create page}}',

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

Gerrit-MessageType: newchange
Gerrit-Change-Id: I8e9784b2bf4e5b85b5a691a3354e421381ae75b3
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/OAuth
Gerrit-Branch: master
Gerrit-Owner: Anomie <[email protected]>

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

Reply via email to