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 public function __construct(
40 ApiMain $mainModule,
41 string $moduleName,
42 private readonly UserGroupManager $userGroupManager,
43 WatchedItemStoreInterface $watchedItemStore,
44 WatchlistManager $watchlistManager,
45 UserOptionsLookup $userOptionsLookup,
46 private readonly UserGroupAssignmentService $userGroupAssignmentService,
47 private readonly MultiFormatUserIdentityLookup $multiFormatUserIdentityLookup,
48 ) {
49 parent::__construct( $mainModule, $moduleName );
50 $this->watchedItemStore = $watchedItemStore;
51
52 // Variables needed in ApiWatchlistTrait trait
53 $this->watchlistExpiryEnabled = $this->getConfig()->get( MainConfigNames::WatchlistExpiry );
54 $this->watchlistMaxDuration =
56 $this->watchlistManager = $watchlistManager;
57 $this->userOptionsLookup = $userOptionsLookup;
58 }
59
60 public function execute() {
61 $pUser = $this->getUser();
62
63 // Deny if the user is blocked and doesn't have the full 'userrights' permission.
64 // This matches what Special:UserRights does for the web UI.
65 if ( !$this->getAuthority()->isAllowed( 'userrights' ) ) {
66 $block = $pUser->getBlock( IDBAccessObject::READ_LATEST );
67 if ( $block && $block->isSitewide() ) {
68 $this->dieBlocked( $block );
69 }
70 }
71
72 $params = $this->extractRequestParams();
73
74 // Figure out expiry times from the input
75 $expiry = (array)$params['expiry'];
76 $add = (array)$params['add'];
77 if ( !$add ) {
78 $expiry = [];
79 } elseif ( count( $expiry ) !== count( $add ) ) {
80 if ( count( $expiry ) === 1 ) {
81 $expiry = array_fill( 0, count( $add ), $expiry[0] );
82 } else {
83 $this->dieWithError( [
84 'apierror-toofewexpiries',
85 count( $expiry ),
86 count( $add )
87 ] );
88 }
89 }
90
91 // Validate the expiries
92 $groupExpiries = [];
93 foreach ( $expiry as $index => $expiryValue ) {
94 $group = $add[$index];
95 $groupExpiries[$group] = UserGroupAssignmentService::expiryToTimestamp( $expiryValue );
96
97 if ( $groupExpiries[$group] === false ) {
98 $this->dieWithError( [ 'apierror-invalidexpiry', wfEscapeWikiText( $expiryValue ) ] );
99 }
100
101 // not allowed to have things expiring in the past
102 if ( $groupExpiries[$group] && $groupExpiries[$group] < wfTimestampNow() ) {
103 $this->dieWithError( [ 'apierror-pastexpiry', wfEscapeWikiText( $expiryValue ) ] );
104 }
105 }
106
107 $user = $this->getUrUser( $params );
108
109 $tags = $params['tags'];
110
111 // Check if user can add tags
112 if ( $tags !== null ) {
113 $ableToTag = ChangeTags::canAddTagsAccompanyingChange( $tags, $this->getAuthority() );
114 if ( !$ableToTag->isOK() ) {
115 $this->dieStatus( $ableToTag );
116 }
117 }
118
119 $r = [];
120 $r['user'] = $user->getName();
121 $r['userid'] = $user->getId( $user->getWikiId() );
122 [ $r['added'], $r['removed'] ] = $this->userGroupAssignmentService->saveChangesToUserGroups(
123 $this->getUser(),
124 $user,
125 $add,
126 // Don't pass null to saveChangesToUserGroups() for array params, cast to empty array
127 (array)$params['remove'],
128 $groupExpiries,
129 $params['reason'],
130 (array)$tags
131 );
132
133 $userPage = Title::makeTitle( NS_USER, $user->getName() );
134 $watchlistExpiry = $this->getExpiryFromParams( $params, $userPage, $this->getUser() );
135 $watchuser = $params['watchuser'];
136 if ( $watchuser && $user->getWikiId() === UserIdentity::LOCAL ) {
137 $this->setWatch( 'watch', $userPage, $this->getUser(), null, $watchlistExpiry );
138 } else {
139 $watchuser = false;
140 $watchlistExpiry = null;
141 }
142 $r['watchuser'] = $watchuser;
143 if ( $watchlistExpiry !== null ) {
144 $r['watchlistexpiry'] = $this->getWatchlistExpiry(
145 $this->watchedItemStore,
146 $userPage,
147 $this->getUser()
148 );
149 }
150
151 $result = $this->getResult();
152 ApiResult::setIndexedTagName( $r['added'], 'group' );
153 ApiResult::setIndexedTagName( $r['removed'], 'group' );
154 $result->addValue( null, $this->getModuleName(), $r );
155 }
156
161 private function getUrUser( array $params ) {
162 if ( $this->mUser !== null ) {
163 return $this->mUser;
164 }
165
166 $this->requireOnlyOneParameter( $params, 'user', 'userid' );
167
168 $userDesignator = $params['user'] ?? '#' . $params['userid'];
169 $status = $this->multiFormatUserIdentityLookup->getUserIdentity( $userDesignator, $this->getAuthority() );
170 if ( !$status->isOK() ) {
171 $this->dieStatus( $status );
172 }
173
174 $user = $status->value;
175 $canHaveRights = $this->userGroupAssignmentService->targetCanHaveUserGroups( $user );
176 if ( !$canHaveRights ) {
177 // Return different errors for anons and temp. accounts to keep consistent behavior
178 $this->dieWithError(
179 $user->isRegistered() ? [ 'userrights-no-group', $user->getName() ] : 'nosuchusershort'
180 );
181 }
182
183 $this->mUser = $user;
184
185 return $user;
186 }
187
189 public function mustBePosted() {
190 return true;
191 }
192
194 public function isWriteMode() {
195 return true;
196 }
197
199 public function getAllowedParams( $flags = 0 ) {
200 $allGroups = $this->userGroupManager->listAllGroups();
201
202 if ( $flags & ApiBase::GET_VALUES_FOR_HELP ) {
203 sort( $allGroups );
204 }
205
206 $params = [
207 'user' => [
208 ParamValidator::PARAM_TYPE => 'user',
209 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'id' ],
210 ],
211 'userid' => [
212 ParamValidator::PARAM_TYPE => 'integer',
213 ParamValidator::PARAM_DEPRECATED => true,
214 ],
215 'add' => [
216 ParamValidator::PARAM_TYPE => $allGroups,
217 ParamValidator::PARAM_ISMULTI => true
218 ],
219 'expiry' => [
220 ParamValidator::PARAM_ISMULTI => true,
221 ParamValidator::PARAM_ALLOW_DUPLICATES => true,
222 ParamValidator::PARAM_DEFAULT => 'infinite',
223 ],
224 'remove' => [
225 ParamValidator::PARAM_TYPE => $allGroups,
226 ParamValidator::PARAM_ISMULTI => true
227 ],
228 'reason' => [
229 ParamValidator::PARAM_DEFAULT => ''
230 ],
231 'token' => [
232 // Standard definition automatically inserted
233 ApiBase::PARAM_HELP_MSG_APPEND => [ 'api-help-param-token-webui' ],
234 ],
235 'tags' => [
236 ParamValidator::PARAM_TYPE => 'tags',
237 ParamValidator::PARAM_ISMULTI => true
238 ],
239 'watchuser' => false,
240 ];
241
242 // Params appear in the docs in the order they are defined,
243 // which is why this is here and not at the bottom.
244 // @todo Find better way to support insertion at arbitrary position
245 if ( $this->watchlistExpiryEnabled ) {
246 $params += [
247 'watchlistexpiry' => [
248 ParamValidator::PARAM_TYPE => 'expiry',
249 ExpiryDef::PARAM_MAX => $this->watchlistMaxDuration,
250 ExpiryDef::PARAM_USE_MAX => true,
251 ]
252 ];
253 }
254
255 return $params;
256 }
257
259 public function needsToken() {
260 return 'userrights';
261 }
262
264 protected function getWebUITokenSalt( array $params ) {
265 return $this->getUrUser( $params )->getName();
266 }
267
269 protected function getExamplesMessages() {
270 return [
271 'action=userrights&user=FooBot&add=bot&remove=sysop|bureaucrat&token=123ABC'
272 => 'apihelp-userrights-example-user',
273 'action=userrights&userid=123&add=bot&remove=sysop|bureaucrat&token=123ABC'
274 => 'apihelp-userrights-example-userid',
275 'action=userrights&user=SometimeSysop&add=sysop&expiry=1%20month&token=123ABC'
276 => 'apihelp-userrights-example-expiry',
277 ];
278 }
279
281 public function getHelpUrls() {
282 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:User_group_membership';
283 }
284}
285
287class_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:60
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition ApiBase.php:1522
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:557
getResult()
Get the result object.
Definition ApiBase.php:696
const PARAM_HELP_MSG_APPEND
((string|array|Message)[]) Specify additional i18n messages to append to the normal message for this ...
Definition ApiBase.php:174
dieBlocked(Block $block)
Throw an ApiUsageException, which will (if uncaught) call the main module's error handler and die wit...
Definition ApiBase.php:1550
dieStatus(StatusValue $status)
Throw an ApiUsageException based on the Status object.
Definition ApiBase.php:1573
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:837
requireOnlyOneParameter( $params,... $required)
Die if 0 or more than one of a certain set of parameters is set and not false.
Definition ApiBase.php:975
const GET_VALUES_FOR_HELP
getAllowedParams() flag: When this is set, the result could take longer to generate,...
Definition ApiBase.php:244
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.
__construct(ApiMain $mainModule, string $moduleName, private readonly UserGroupManager $userGroupManager, WatchedItemStoreInterface $watchedItemStore, WatchlistManager $watchlistManager, UserOptionsLookup $userOptionsLookup, private readonly UserGroupAssignmentService $userGroupAssignmentService, private readonly MultiFormatUserIdentityLookup $multiFormatUserIdentityLookup,)
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...
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.