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
* (bug 18099) Using appendtext to edit a non-existent page causes an interface
message to be included in the page text
* Added uiprop=changeablegroups to meta=userinfo
'add-self' => array( addablegroups to self ),
'remove-self' => array( removable groups from self )
)
+
+'UserRightsLogEntry': before a log entry is added by UserrightsPage::addLogEntry(). If you use this hook to add your own log entries, you can return false to prevent the usual log entry from being added
+$user: User or UserrightsProxy object
+$oldGroups: Array of groups the user was a member of before the change
+$newGroups: Array of groups the user is a member of after the change
+$reason: User-provided summary
+
'UserRetrieveNewTalks': called when retrieving "You have new messages!" message(s)
$user: user retrieving new talks messages
$talks: array of new talks page(s)
'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',
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.
'watch' => 'ApiWatch',
'patrol' => 'ApiPatrol',
'import' => 'ApiImport',
+ 'userrights' => 'ApiUserrights',
);
/**
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;
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();
}
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
),
'users' => array(
ApiBase :: PARAM_ISMULTI => true
- )
+ ),
+ 'token' => array(
+ ApiBase :: PARAM_TYPE => array_keys($this->getTokenFunctions()),
+ ApiBase :: PARAM_ISMULTI => true
+ ),
);
}
' 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',
);
}
--- /dev/null
+<?php
+
+/*
+ * Created on Mar 24, 2009
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2009 Roan Kattouw <Firstname>.<Lastname>@home.nl
+ *
+ * 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
+ */
+
+if (!defined('MEDIAWIKI')) {
+ // Eclipse helper - will be ignored in production
+ require_once ("ApiBase.php");
+}
+
+
+/**
+ * @ingroup API
+ */
+class ApiUserrights extends ApiBase {
+
+ public function __construct($main, $action) {
+ parent :: __construct($main, $action);
+ }
+
+ public function execute() {
+ global $wgUser;
+ $params = $this->extractRequestParams();
+ if(is_null($params['user']))
+ $this->dieUsageMsg(array('missingparam', 'user'));
+ $user = User::newFromName($params['user']);
+ if($user->isAnon())
+ $this->dieUsageMsg(array('nosuchuser', $params['user']));
+ if(is_null($params['token']))
+ $this->dieUsageMsg(array('missingparam', 'token'));
+ if(!$wgUser->matchEditToken($params['token'], $user->getName()))
+ $this->dieUsageMsg(array('sessionfailure'));
+
+ $r['user'] = $user->getName();
+ list($r['added'], $r['removed']) =
+ UserrightsPage::doSaveUserGroups(
+ $user, (array)$params['add'],
+ (array)$params['remove'], $params['reason']);
+
+ $this->getResult()->setIndexedTagName($r['added'], 'group');
+ $this->getResult()->setIndexedTagName($r['removed'], 'group');
+ $this->getResult()->addValue(null, $this->getModuleName(), $r);
+ }
+
+ public function mustBePosted() {
+ return true;
+ }
+
+ public function isWriteMode() {
+ return true;
+ }
+
+ public function getAllowedParams() {
+ return array (
+ 'user' => array(
+ ApiBase :: PARAM_TYPE => 'user'
+ ),
+ 'add' => array(
+ ApiBase :: PARAM_TYPE => User::getAllGroups(),
+ ApiBase :: PARAM_ISMULTI => true
+ ),
+ 'remove' => array(
+ ApiBase :: PARAM_TYPE => User::getAllGroups(),
+ ApiBase :: PARAM_ISMULTI => true
+ ),
+ 'token' => null,
+ 'reason' => array(
+ ApiBase :: PARAM_DFLT => ''
+ )
+ );
+ }
+
+ public function getParamDescription() {
+ return array (
+ 'user' => 'User name',
+ 'add' => 'Add the user to these groups',
+ 'remove' => 'Remove the user from these groups',
+ 'token' => 'A userrights token previously retrieved through list=users',
+ 'reason' => 'Reason for the change',
+ );
+ }
+
+ public function getDescription() {
+ return array(
+ 'Add/remove a user to/from groups',
+ );
+ }
+
+ protected function getExamples() {
+ return array (
+ 'api.php?action=userrights&user=FooBot&add=bot&remove=sysop|bureaucrat&token=123ABC'
+ );
+ }
+
+ public function getVersion() {
+ return __CLASS__ . ': $Id$';
+ }
+}
$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, $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 );
}
}
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 ) {
+ // Just overriding addLogEntry in a subclass would be cleaner,
+ // but that requires PHP 5.3 (late static bindings)
+ if( !wfRunHooks( 'UserRightsLogEntry', array( $user, $oldGroups,
+ $newGroups, $reason ) ) ) {
+ return;
+ }
+
$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 )
)
);
}
return $user;
}
- function makeGroupNameList( $ids ) {
+ static function makeGroupNameList( $ids ) {
if( empty( $ids ) ) {
return wfMsgForContent( 'rightsnone' );
} else {
}
}
- function makeGroupNameListForLog( $ids ) {
+ static function makeGroupNameListForLog( $ids ) {
if( empty( $ids ) ) {
return '';
} else {
- return $this->makeGroupNameList( $ids );
+ return self::makeGroupNameList( $ids );
}
}
}
/**
- * 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;
}