MediaWiki master
ApiUserrights.php
Go to the documentation of this file.
1<?php
2
12namespace MediaWiki\Api;
13
28
32class ApiUserrights extends ApiBase {
33
35
37 private $mUser = null;
38
39 private UserGroupManager $userGroupManager;
40 private WatchedItemStoreInterface $watchedItemStore;
41 private UserGroupAssignmentService $userGroupAssignmentService;
42 private MultiFormatUserIdentityLookup $multiFormatUserIdentityLookup;
43
44 public function __construct(
45 ApiMain $mainModule,
46 string $moduleName,
47 UserGroupManager $userGroupManager,
48 WatchedItemStoreInterface $watchedItemStore,
49 WatchlistManager $watchlistManager,
50 UserOptionsLookup $userOptionsLookup,
51 UserGroupAssignmentService $userGroupAssignmentService,
52 MultiFormatUserIdentityLookup $multiFormatUserIdentityLookup,
53 ) {
54 parent::__construct( $mainModule, $moduleName );
55 $this->userGroupManager = $userGroupManager;
56 $this->watchedItemStore = $watchedItemStore;
57
58 // Variables needed in ApiWatchlistTrait trait
59 $this->watchlistExpiryEnabled = $this->getConfig()->get( MainConfigNames::WatchlistExpiry );
60 $this->watchlistMaxDuration =
62 $this->watchlistManager = $watchlistManager;
63 $this->userOptionsLookup = $userOptionsLookup;
64 $this->userGroupAssignmentService = $userGroupAssignmentService;
65 $this->multiFormatUserIdentityLookup = $multiFormatUserIdentityLookup;
66 }
67
68 public function execute() {
69 $pUser = $this->getUser();
70
71 // Deny if the user is blocked and doesn't have the full 'userrights' permission.
72 // This matches what Special:UserRights does for the web UI.
73 if ( !$this->getAuthority()->isAllowed( 'userrights' ) ) {
74 $block = $pUser->getBlock( IDBAccessObject::READ_LATEST );
75 if ( $block && $block->isSitewide() ) {
76 $this->dieBlocked( $block );
77 }
78 }
79
80 $params = $this->extractRequestParams();
81
82 // Figure out expiry times from the input
83 $expiry = (array)$params['expiry'];
84 $add = (array)$params['add'];
85 if ( !$add ) {
86 $expiry = [];
87 } elseif ( count( $expiry ) !== count( $add ) ) {
88 if ( count( $expiry ) === 1 ) {
89 $expiry = array_fill( 0, count( $add ), $expiry[0] );
90 } else {
91 $this->dieWithError( [
92 'apierror-toofewexpiries',
93 count( $expiry ),
94 count( $add )
95 ] );
96 }
97 }
98
99 // Validate the expiries
100 $groupExpiries = [];
101 foreach ( $expiry as $index => $expiryValue ) {
102 $group = $add[$index];
103 $groupExpiries[$group] = UserGroupAssignmentService::expiryToTimestamp( $expiryValue );
104
105 if ( $groupExpiries[$group] === false ) {
106 $this->dieWithError( [ 'apierror-invalidexpiry', wfEscapeWikiText( $expiryValue ) ] );
107 }
108
109 // not allowed to have things expiring in the past
110 if ( $groupExpiries[$group] && $groupExpiries[$group] < wfTimestampNow() ) {
111 $this->dieWithError( [ 'apierror-pastexpiry', wfEscapeWikiText( $expiryValue ) ] );
112 }
113 }
114
115 $user = $this->getUrUser( $params );
116
117 $tags = $params['tags'];
118
119 // Check if user can add tags
120 if ( $tags !== null ) {
121 $ableToTag = ChangeTags::canAddTagsAccompanyingChange( $tags, $this->getAuthority() );
122 if ( !$ableToTag->isOK() ) {
123 $this->dieStatus( $ableToTag );
124 }
125 }
126
127 $r = [];
128 $r['user'] = $user->getName();
129 $r['userid'] = $user->getId( $user->getWikiId() );
130 [ $r['added'], $r['removed'] ] = $this->userGroupAssignmentService->saveChangesToUserGroups(
131 $this->getUser(),
132 $user,
133 $add,
134 // Don't pass null to saveChangesToUserGroups() for array params, cast to empty array
135 (array)$params['remove'],
136 $groupExpiries,
137 $params['reason'],
138 (array)$tags
139 );
140
141 $userPage = Title::makeTitle( NS_USER, $user->getName() );
142 $watchlistExpiry = $this->getExpiryFromParams( $params, $userPage, $this->getUser() );
143 $watchuser = $params['watchuser'];
144 if ( $watchuser && $user->getWikiId() === UserIdentity::LOCAL ) {
145 $this->setWatch( 'watch', $userPage, $this->getUser(), null, $watchlistExpiry );
146 } else {
147 $watchuser = false;
148 $watchlistExpiry = null;
149 }
150 $r['watchuser'] = $watchuser;
151 if ( $watchlistExpiry !== null ) {
152 $r['watchlistexpiry'] = $this->getWatchlistExpiry(
153 $this->watchedItemStore,
154 $userPage,
155 $this->getUser()
156 );
157 }
158
159 $result = $this->getResult();
160 ApiResult::setIndexedTagName( $r['added'], 'group' );
161 ApiResult::setIndexedTagName( $r['removed'], 'group' );
162 $result->addValue( null, $this->getModuleName(), $r );
163 }
164
169 private function getUrUser( array $params ) {
170 if ( $this->mUser !== null ) {
171 return $this->mUser;
172 }
173
174 $this->requireOnlyOneParameter( $params, 'user', 'userid' );
175
176 $userDesignator = $params['user'] ?? '#' . $params['userid'];
177 $status = $this->multiFormatUserIdentityLookup->getUserIdentity( $userDesignator, $this->getAuthority() );
178 if ( !$status->isOK() ) {
179 $this->dieStatus( $status );
180 }
181
182 $user = $status->value;
183 $canHaveRights = $this->userGroupAssignmentService->targetCanHaveUserGroups( $user );
184 if ( !$canHaveRights ) {
185 // Return different errors for anons and temp. accounts to keep consistent behavior
186 $this->dieWithError(
187 $user->isRegistered() ? [ 'userrights-no-group', $user->getName() ] : 'nosuchusershort'
188 );
189 }
190
191 $this->mUser = $user;
192
193 return $user;
194 }
195
197 public function mustBePosted() {
198 return true;
199 }
200
202 public function isWriteMode() {
203 return true;
204 }
205
207 public function getAllowedParams( $flags = 0 ) {
208 $allGroups = $this->userGroupManager->listAllGroups();
209
210 if ( $flags & ApiBase::GET_VALUES_FOR_HELP ) {
211 sort( $allGroups );
212 }
213
214 $params = [
215 'user' => [
216 ParamValidator::PARAM_TYPE => 'user',
217 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'id' ],
218 ],
219 'userid' => [
220 ParamValidator::PARAM_TYPE => 'integer',
221 ParamValidator::PARAM_DEPRECATED => true,
222 ],
223 'add' => [
224 ParamValidator::PARAM_TYPE => $allGroups,
225 ParamValidator::PARAM_ISMULTI => true
226 ],
227 'expiry' => [
228 ParamValidator::PARAM_ISMULTI => true,
229 ParamValidator::PARAM_ALLOW_DUPLICATES => true,
230 ParamValidator::PARAM_DEFAULT => 'infinite',
231 ],
232 'remove' => [
233 ParamValidator::PARAM_TYPE => $allGroups,
234 ParamValidator::PARAM_ISMULTI => true
235 ],
236 'reason' => [
237 ParamValidator::PARAM_DEFAULT => ''
238 ],
239 'token' => [
240 // Standard definition automatically inserted
241 ApiBase::PARAM_HELP_MSG_APPEND => [ 'api-help-param-token-webui' ],
242 ],
243 'tags' => [
244 ParamValidator::PARAM_TYPE => 'tags',
245 ParamValidator::PARAM_ISMULTI => true
246 ],
247 'watchuser' => false,
248 ];
249
250 // Params appear in the docs in the order they are defined,
251 // which is why this is here and not at the bottom.
252 // @todo Find better way to support insertion at arbitrary position
253 if ( $this->watchlistExpiryEnabled ) {
254 $params += [
255 'watchlistexpiry' => [
256 ParamValidator::PARAM_TYPE => 'expiry',
257 ExpiryDef::PARAM_MAX => $this->watchlistMaxDuration,
258 ExpiryDef::PARAM_USE_MAX => true,
259 ]
260 ];
261 }
262
263 return $params;
264 }
265
267 public function needsToken() {
268 return 'userrights';
269 }
270
272 protected function getWebUITokenSalt( array $params ) {
273 return $this->getUrUser( $params )->getName();
274 }
275
277 protected function getExamplesMessages() {
278 return [
279 'action=userrights&user=FooBot&add=bot&remove=sysop|bureaucrat&token=123ABC'
280 => 'apihelp-userrights-example-user',
281 'action=userrights&userid=123&add=bot&remove=sysop|bureaucrat&token=123ABC'
282 => 'apihelp-userrights-example-userid',
283 'action=userrights&user=SometimeSysop&add=sysop&expiry=1%20month&token=123ABC'
284 => 'apihelp-userrights-example-expiry',
285 ];
286 }
287
289 public function getHelpUrls() {
290 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:User_group_membership';
291 }
292}
293
295class_alias( ApiUserrights::class, 'ApiUserrights' );
const NS_USER
Definition Defines.php:53
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
wfEscapeWikiText( $input)
Escapes the given text so that it may be output using addWikiText() without any linking,...
This abstract class implements many basic API functions, and is the base of all API classes.
Definition ApiBase.php:61
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition ApiBase.php:1511
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:543
getResult()
Get the result object.
Definition ApiBase.php:682
const PARAM_HELP_MSG_APPEND
((string|array|Message)[]) Specify additional i18n messages to append to the normal message for this ...
Definition ApiBase.php:175
dieBlocked(Block $block)
Throw an ApiUsageException, which will (if uncaught) call the main module's error handler and die wit...
Definition ApiBase.php:1539
dieStatus(StatusValue $status)
Throw an ApiUsageException based on the Status object.
Definition ApiBase.php:1562
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:823
requireOnlyOneParameter( $params,... $required)
Die if 0 or more than one of a certain set of parameters is set and not false.
Definition ApiBase.php:961
const GET_VALUES_FOR_HELP
getAllowedParams() flag: When this is set, the result could take longer to generate,...
Definition ApiBase.php:245
This is the main API class, used for both external and internal processing.
Definition ApiMain.php:66
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
getWebUITokenSalt(array $params)
Fetch the salt used in the Web UI corresponding to this module.Only override this if the Web UI uses ...
getHelpUrls()
Return links to more detailed help pages about the module.1.25, returning boolean false is deprecated...
__construct(ApiMain $mainModule, string $moduleName, UserGroupManager $userGroupManager, WatchedItemStoreInterface $watchedItemStore, WatchlistManager $watchlistManager, UserOptionsLookup $userOptionsLookup, UserGroupAssignmentService $userGroupAssignmentService, MultiFormatUserIdentityLookup $multiFormatUserIdentityLookup,)
isWriteMode()
Indicates whether this module requires write access to the wiki.API modules must override this method...
getExamplesMessages()
Returns usage examples for this module.Return value has query strings as keys, with values being eith...
needsToken()
Returns the token type this module requires in order to execute.Modules are strongly encouraged to us...
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
mustBePosted()
Indicates whether this module must be called with a POST request.Implementations of this method must ...
Recent changes tagging.
makeTitle( $linkId)
Convert a link ID to a Title.to override Title
A class containing constants representing the names of configuration variables.
const WatchlistExpiry
Name constant for the WatchlistExpiry setting, for use with Config::get()
const WatchlistExpiryMaxDuration
Name constant for the WatchlistExpiryMaxDuration setting, for use with Config::get()
Type definition for user types.
Definition UserDef.php:27
Represents a title within MediaWiki.
Definition Title.php:69
A service to look up user identities based on the user input.
Provides access to user options.
This class represents a service that provides high-level operations on user groups.
Manage user group memberships.
Service for formatting and validating API parameters.
Type definition for expiry timestamps.
Definition ExpiryDef.php:18
trait ApiWatchlistTrait
An ApiWatchlistTrait adds class properties and convenience methods for APIs that allow you to watch a...
Interface for objects representing user identity.
Interface for database access objects.
setWatch(string $watch, PageIdentity $page, User $user, ?string $userOption=null, ?string $expiry=null)
Set a watch (or unwatch) based the based on a watchlist parameter.
getExpiryFromParams(array $params, ?PageIdentity $page=null, ?UserIdentity $user=null, string $userOption='watchdefault-expiry')
Get formatted expiry from the given parameters.
getWatchlistExpiry(WatchedItemStoreInterface $store, PageIdentity $page, UserIdentity $user)
Get existing expiry from the database.