From 3c52c2c64b9230b3c290ad439c82e146e05b37fc Mon Sep 17 00:00:00 2001 From: Roan Kattouw Date: Tue, 24 Mar 2009 16:04:50 +0000 Subject: [PATCH] * API: (bug 15935) Add action=userrights to the API * Add ustoken=userrights to list=users * Move the non-UI part of UserrightsPage::saveUserGroups() to the static and more generic doSaveUserGroups() * Add a $reason parameter to UserrightsPage::addLogEntry() and make it and its helpers static * Move UserrightsPage::changeableGroups() and changeableByGroup() to the User class and make the latter static * In doSaveUserGroups(), drop groups that the user doesn't have from $remove (and those that they do have from $add), and return array($add, $remove) * Fix up a comment in ApiQueryRecentChanges --- RELEASE-NOTES | 1 + includes/AutoLoader.php | 1 + includes/User.php | 108 +++++++++++++++ includes/api/ApiMain.php | 1 + includes/api/ApiQueryRecentChanges.php | 11 +- includes/api/ApiQueryUsers.php | 51 ++++++- includes/specials/SpecialUserrights.php | 170 +++++++----------------- 7 files changed, 211 insertions(+), 132 deletions(-) diff --git a/RELEASE-NOTES b/RELEASE-NOTES index ab11533782..af7abc3062 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -330,6 +330,7 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN when someone tries to use them * BREAKING CHANGE: action=purge requires write rights and, for anonymous users, a POST request +* (bug 15935) Added action=userrights to add/remove users to/from groups === Languages updated in 1.15 === diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 85e7e6685b..d830c03278 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -290,6 +290,7 @@ $wgAutoloadLocalClasses = array( 'ApiRollback' => 'includes/api/ApiRollback.php', 'ApiUnblock' => 'includes/api/ApiUnblock.php', 'ApiUndelete' => 'includes/api/ApiUndelete.php', + 'ApiUserrights' => 'includes/api/ApiUserrights.php', 'ApiWatch' => 'includes/api/ApiWatch.php', 'Services_JSON' => 'includes/api/ApiFormatJson_json.php', 'Services_JSON_Error' => 'includes/api/ApiFormatJson_json.php', diff --git a/includes/User.php b/includes/User.php index 191de40f13..fad1fb8db5 100644 --- a/includes/User.php +++ b/includes/User.php @@ -3231,6 +3231,114 @@ class User { return $text; } } + + /** + * Returns an array of the groups that a particular group can add/remove. + * + * @param $group String: the group to check for whether it can add/remove + * @return Array array( 'add' => array( addablegroups ), + * 'remove' => array( removablegroups ), + * 'add-self' => array( addablegroups to self), + * 'remove-self' => array( removable groups from self) ) + */ + static function changeableByGroup( $group ) { + global $wgAddGroups, $wgRemoveGroups, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf; + + $groups = array( 'add' => array(), 'remove' => array(), 'add-self' => array(), 'remove-self' => array() ); + if( empty($wgAddGroups[$group]) ) { + // Don't add anything to $groups + } elseif( $wgAddGroups[$group] === true ) { + // You get everything + $groups['add'] = self::getAllGroups(); + } elseif( is_array($wgAddGroups[$group]) ) { + $groups['add'] = $wgAddGroups[$group]; + } + + // Same thing for remove + if( empty($wgRemoveGroups[$group]) ) { + } elseif($wgRemoveGroups[$group] === true ) { + $groups['remove'] = self::getAllGroups(); + } elseif( is_array($wgRemoveGroups[$group]) ) { + $groups['remove'] = $wgRemoveGroups[$group]; + } + + // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility + if( empty($wgGroupsAddToSelf['user']) || $wgGroupsAddToSelf['user'] !== true ) { + foreach($wgGroupsAddToSelf as $key => $value) { + if( is_int($key) ) { + $wgGroupsAddToSelf['user'][] = $value; + } + } + } + + if( empty($wgGroupsRemoveFromSelf['user']) || $wgGroupsRemoveFromSelf['user'] !== true ) { + foreach($wgGroupsRemoveFromSelf as $key => $value) { + if( is_int($key) ) { + $wgGroupsRemoveFromSelf['user'][] = $value; + } + } + } + + // Now figure out what groups the user can add to him/herself + if( empty($wgGroupsAddToSelf[$group]) ) { + } elseif( $wgGroupsAddToSelf[$group] === true ) { + // No idea WHY this would be used, but it's there + $groups['add-self'] = User::getAllGroups(); + } elseif( is_array($wgGroupsAddToSelf[$group]) ) { + $groups['add-self'] = $wgGroupsAddToSelf[$group]; + } + + if( empty($wgGroupsRemoveFromSelf[$group]) ) { + } elseif( $wgGroupsRemoveFromSelf[$group] === true ) { + $groups['remove-self'] = User::getAllGroups(); + } elseif( is_array($wgGroupsRemoveFromSelf[$group]) ) { + $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group]; + } + + return $groups; + } + + /** + * Returns an array of groups that this user can add and remove + * @return Array array( 'add' => array( addablegroups ), + * 'remove' => array( removablegroups ), + * 'add-self' => array( addablegroups to self), + * 'remove-self' => array( removable groups from self) ) + */ + function changeableGroups() { + if( $this->isAllowed( 'userrights' ) ) { + // This group gives the right to modify everything (reverse- + // compatibility with old "userrights lets you change + // everything") + // Using array_merge to make the groups reindexed + $all = array_merge( User::getAllGroups() ); + return array( + 'add' => $all, + 'remove' => $all, + 'add-self' => array(), + 'remove-self' => array() + ); + } + + // Okay, it's not so simple, we will have to go through the arrays + $groups = array( + 'add' => array(), + 'remove' => array(), + 'add-self' => array(), + 'remove-self' => array() ); + $addergroups = $this->getEffectiveGroups(); + + foreach ($addergroups as $addergroup) { + $groups = array_merge_recursive( + $groups, $this->changeableByGroup($addergroup) + ); + $groups['add'] = array_unique( $groups['add'] ); + $groups['remove'] = array_unique( $groups['remove'] ); + $groups['add-self'] = array_unique( $groups['add-self'] ); + $groups['remove-self'] = array_unique( $groups['remove-self'] ); + } + return $groups; + } /** * Increment the user's edit-count field. diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php index 3fa0d72f2b..42aaacc8c0 100644 --- a/includes/api/ApiMain.php +++ b/includes/api/ApiMain.php @@ -80,6 +80,7 @@ class ApiMain extends ApiBase { 'watch' => 'ApiWatch', 'patrol' => 'ApiPatrol', 'import' => 'ApiImport', + 'userrights' => 'ApiUserrights', ); /** diff --git a/includes/api/ApiQueryRecentChanges.php b/includes/api/ApiQueryRecentChanges.php index 9dacb86c8e..94b1feeb52 100644 --- a/includes/api/ApiQueryRecentChanges.php +++ b/includes/api/ApiQueryRecentChanges.php @@ -43,12 +43,13 @@ class ApiQueryRecentChanges extends ApiQueryBase { private $fld_comment = false, $fld_user = false, $fld_flags = false, $fld_timestamp = false, $fld_title = false, $fld_ids = false, $fld_sizes = false; - + /** + * Get an array mapping token names to their handler functions. + * The prototype for a token function is func($pageid, $title, $rc) + * it should return a token or false (permission denied) + * @return array(tokenname => function) + */ protected function getTokenFunctions() { - // tokenname => function - // function prototype is func($pageid, $title, $rev) - // should return token or false - // Don't call the hooks twice if(isset($this->tokenFunctions)) return $this->tokenFunctions; diff --git a/includes/api/ApiQueryUsers.php b/includes/api/ApiQueryUsers.php index 51fa44e368..9f51c75705 100644 --- a/includes/api/ApiQueryUsers.php +++ b/includes/api/ApiQueryUsers.php @@ -39,6 +39,36 @@ if (!defined('MEDIAWIKI')) { public function __construct($query, $moduleName) { parent :: __construct($query, $moduleName, 'us'); } + + /** + * Get an array mapping token names to their handler functions. + * The prototype for a token function is func($user) + * it should return a token or false (permission denied) + * @return array(tokenname => function) + */ + protected function getTokenFunctions() { + // Don't call the hooks twice + if(isset($this->tokenFunctions)) + return $this->tokenFunctions; + + // If we're in JSON callback mode, no tokens can be obtained + if(!is_null($this->getMain()->getRequest()->getVal('callback'))) + return array(); + + $this->tokenFunctions = array( + 'userrights' => array( 'ApiQueryUsers', 'getUserrightsToken' ), + ); + wfRunHooks('APIQueryUsersTokens', array(&$this->tokenFunctions)); + return $this->tokenFunctions; + } + + public static function getUserrightsToken($user) + { + global $wgUser; + // Since the permissions check for userrights is non-trivial, + // don't bother with it here + return $wgUser->editToken($user->getName()); + } public function execute() { $params = $this->extractRequestParams(); @@ -115,6 +145,18 @@ if (!defined('MEDIAWIKI')) { } if(isset($this->prop['emailable']) && $user->canReceiveEmail()) $data[$name]['emailable'] = ''; + if(!is_null($params['token'])) + { + $tokenFunctions = $this->getTokenFunctions(); + foreach($params['token'] as $t) + { + $val = call_user_func($tokenFunctions[$t], $user); + if($val === false) + $this->setWarning("Action '$t' is not allowed for the current user"); + else + $data[$name][$t . 'token'] = $val; + } + } } } // Second pass: add result data to $retval @@ -153,7 +195,11 @@ if (!defined('MEDIAWIKI')) { ), 'users' => array( ApiBase :: PARAM_ISMULTI => true - ) + ), + 'token' => array( + ApiBase :: PARAM_TYPE => array_keys($this->getTokenFunctions()), + ApiBase :: PARAM_ISMULTI => true + ), ); } @@ -167,7 +213,8 @@ if (!defined('MEDIAWIKI')) { ' registration - adds the user\'s registration timestamp', ' emailable - tags if the user can and wants to receive e-mail through [[Special:Emailuser]]', ), - 'users' => 'A list of users to obtain the same information for' + 'users' => 'A list of users to obtain the same information for', + 'token' => 'Which tokens to obtain for each user', ); } diff --git a/includes/specials/SpecialUserrights.php b/includes/specials/SpecialUserrights.php index 5e824d001a..11fc9bdbf3 100644 --- a/includes/specials/SpecialUserrights.php +++ b/includes/specials/SpecialUserrights.php @@ -149,29 +149,46 @@ class UserrightsPage extends SpecialPage { $removegroup[] = $group; } } + self::doSaveUserGroups( $user, $addgroup, $removegroup, $reason ); + } + + /** + * Save user groups changes in the database. + * + * @param $user User object + * @param $add Array of groups to add + * @param $remove Array of groups to remove + * @param $reason String: reason for group change + * @return Array: Tuple of added, then removed groups + */ + static function doSaveUserGroups( User $user, $add, $remove, $reason = '' ) { + global $wgUser; // Validate input set... - $changeable = $this->changeableGroups(); - $addable = array_merge( $changeable['add'], $this->isself ? $changeable['add-self'] : array() ); - $removable = array_merge( $changeable['remove'], $this->isself ? $changeable['remove-self'] : array() ); + $isself = ($user->getName() == $wgUser->getName()); + $groups = $user->getGroups(); + $changeable = $wgUser->changeableGroups(); + $addable = array_merge( $changeable['add'], $isself ? $changeable['add-self'] : array() ); + $removable = array_merge( $changeable['remove'], $isself ? $changeable['remove-self'] : array() ); - $removegroup = array_unique( - array_intersect( (array)$removegroup, $removable ) ); - $addgroup = array_unique( - array_intersect( (array)$addgroup, $addable ) ); + $remove = array_unique( + array_intersect( (array)$remove, $removable, $groups ) ); + $add = array_unique( array_diff( + array_intersect( (array)$add, $addable ), + $groups ) ); $oldGroups = $user->getGroups(); $newGroups = $oldGroups; // remove then add groups - if( $removegroup ) { - $newGroups = array_diff($newGroups, $removegroup); - foreach( $removegroup as $group ) { + if( $remove ) { + $newGroups = array_diff($newGroups, $remove); + foreach( $remove as $group ) { $user->removeGroup( $group ); } } - if( $addgroup ) { - $newGroups = array_merge($newGroups, $addgroup); - foreach( $addgroup as $group ) { + if( $add ) { + $newGroups = array_merge($newGroups, $add); + foreach( $add as $group ) { $user->addGroup( $group ); } } @@ -182,29 +199,27 @@ class UserrightsPage extends SpecialPage { wfDebug( 'oldGroups: ' . print_r( $oldGroups, true ) ); wfDebug( 'newGroups: ' . print_r( $newGroups, true ) ); - if( $user instanceof User ) { - // hmmm - wfRunHooks( 'UserRights', array( &$user, $addgroup, $removegroup ) ); - } + wfRunHooks( 'UserRights', array( &$user, $add, $remove ) ); if( $newGroups != $oldGroups ) { - $this->addLogEntry( $user, $oldGroups, $newGroups ); + self::addLogEntry( $user, $oldGroups, $newGroups, $reason ); } + return array( $add, $remove ); } + /** * Add a rights log entry for an action. */ - function addLogEntry( $user, $oldGroups, $newGroups ) { - global $wgRequest; + static function addLogEntry( $user, $oldGroups, $newGroups, $reason ) { $log = new LogPage( 'rights' ); $log->addEntry( 'rights', $user->getUserPage(), - $wgRequest->getText( 'user-reason' ), + $reason, array( - $this->makeGroupNameListForLog( $oldGroups ), - $this->makeGroupNameListForLog( $newGroups ) + self::makeGroupNameListForLog( $oldGroups ), + self::makeGroupNameListForLog( $newGroups ) ) ); } @@ -293,7 +308,7 @@ class UserrightsPage extends SpecialPage { return $user; } - function makeGroupNameList( $ids ) { + static function makeGroupNameList( $ids ) { if( empty( $ids ) ) { return wfMsgForContent( 'rightsnone' ); } else { @@ -301,11 +316,11 @@ class UserrightsPage extends SpecialPage { } } - function makeGroupNameListForLog( $ids ) { + static function makeGroupNameListForLog( $ids ) { if( empty( $ids ) ) { return ''; } else { - return $this->makeGroupNameList( $ids ); + return self::makeGroupNameList( $ids ); } } @@ -511,111 +526,16 @@ class UserrightsPage extends SpecialPage { } /** - * Returns an array of the groups that the user can add/remove. + * Returns $wgUser->changeableGroups() * * @return Array array( 'add' => array( addablegroups ), 'remove' => array( removablegroups ) , 'add-self' => array( addablegroups to self), 'remove-self' => array( removable groups from self) ) */ function changeableGroups() { global $wgUser; - - if( $wgUser->isAllowed( 'userrights' ) ) { - // This group gives the right to modify everything (reverse- - // compatibility with old "userrights lets you change - // everything") - // Using array_merge to make the groups reindexed - $all = array_merge( User::getAllGroups() ); - return array( - 'add' => $all, - 'remove' => $all, - 'add-self' => array(), - 'remove-self' => array() - ); - } - - // Okay, it's not so simple, we will have to go through the arrays - $groups = array( - 'add' => array(), - 'remove' => array(), - 'add-self' => array(), - 'remove-self' => array() ); - $addergroups = $wgUser->getEffectiveGroups(); - - foreach ($addergroups as $addergroup) { - $groups = array_merge_recursive( - $groups, $this->changeableByGroup($addergroup) - ); - $groups['add'] = array_unique( $groups['add'] ); - $groups['remove'] = array_unique( $groups['remove'] ); - $groups['add-self'] = array_unique( $groups['add-self'] ); - $groups['remove-self'] = array_unique( $groups['remove-self'] ); - } - + $groups = $wgUser->changeableGroups(); // Run a hook because we can - wfRunHooks( 'UserrightsChangeableGroups', array( $this, $wgUser, $addergroups, &$groups ) ); - - return $groups; - } - - /** - * Returns an array of the groups that a particular group can add/remove. - * - * @param $group String: the group to check for whether it can add/remove - * @return Array array( 'add' => array( addablegroups ), 'remove' => array( removablegroups ) , 'add-self' => array( addablegroups to self), 'remove-self' => array( removable groups from self) ) - */ - private function changeableByGroup( $group ) { - global $wgAddGroups, $wgRemoveGroups, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf; - - $groups = array( 'add' => array(), 'remove' => array(), 'add-self' => array(), 'remove-self' => array() ); - if( empty($wgAddGroups[$group]) ) { - // Don't add anything to $groups - } elseif( $wgAddGroups[$group] === true ) { - // You get everything - $groups['add'] = User::getAllGroups(); - } elseif( is_array($wgAddGroups[$group]) ) { - $groups['add'] = $wgAddGroups[$group]; - } - - // Same thing for remove - if( empty($wgRemoveGroups[$group]) ) { - } elseif($wgRemoveGroups[$group] === true ) { - $groups['remove'] = User::getAllGroups(); - } elseif( is_array($wgRemoveGroups[$group]) ) { - $groups['remove'] = $wgRemoveGroups[$group]; - } - - // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility - if( empty($wgGroupsAddToSelf['user']) || $wgGroupsAddToSelf['user'] !== true ) { - foreach($wgGroupsAddToSelf as $key => $value) { - if( is_int($key) ) { - $wgGroupsAddToSelf['user'][] = $value; - } - } - } - - if( empty($wgGroupsRemoveFromSelf['user']) || $wgGroupsRemoveFromSelf['user'] !== true ) { - foreach($wgGroupsRemoveFromSelf as $key => $value) { - if( is_int($key) ) { - $wgGroupsRemoveFromSelf['user'][] = $value; - } - } - } - - // Now figure out what groups the user can add to him/herself - if( empty($wgGroupsAddToSelf[$group]) ) { - } elseif( $wgGroupsAddToSelf[$group] === true ) { - // No idea WHY this would be used, but it's there - $groups['add-self'] = User::getAllGroups(); - } elseif( is_array($wgGroupsAddToSelf[$group]) ) { - $groups['add-self'] = $wgGroupsAddToSelf[$group]; - } - - if( empty($wgGroupsRemoveFromSelf[$group]) ) { - } elseif( $wgGroupsRemoveFromSelf[$group] === true ) { - $groups['remove-self'] = User::getAllGroups(); - } elseif( is_array($wgGroupsRemoveFromSelf[$group]) ) { - $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group]; - } - + wfRunHooks( 'UserrightsChangeableGroups', array( $this, + $wgUser, $wgUser->getEffectiveGroups(), &$groups ) ); return $groups; } -- 2.20.1