Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
87.04% |
141 / 162 |
|
50.00% |
4 / 8 |
CRAP | |
0.00% |
0 / 1 |
ApiBlock | |
87.04% |
141 / 162 |
|
50.00% |
4 / 8 |
24.15 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
1 | |||
execute | |
91.57% |
76 / 83 |
|
0.00% |
0 / 1 |
14.12 | |||
mustBePosted | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isWriteMode | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getAllowedParams | |
87.50% |
49 / 56 |
|
0.00% |
0 / 1 |
3.02 | |||
needsToken | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getExamplesMessages | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
getHelpUrls | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * Copyright © 2007 Roan Kattouw <roan.kattouw@gmail.com> |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by |
7 | * the Free Software Foundation; either version 2 of the License, or |
8 | * (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License along |
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
18 | * http://www.gnu.org/copyleft/gpl.html |
19 | * |
20 | * @file |
21 | */ |
22 | |
23 | use MediaWiki\Block\AbstractBlock; |
24 | use MediaWiki\Block\BlockActionInfo; |
25 | use MediaWiki\Block\BlockPermissionCheckerFactory; |
26 | use MediaWiki\Block\BlockUserFactory; |
27 | use MediaWiki\Block\BlockUtils; |
28 | use MediaWiki\Block\DatabaseBlock; |
29 | use MediaWiki\Block\Restriction\ActionRestriction; |
30 | use MediaWiki\Block\Restriction\NamespaceRestriction; |
31 | use MediaWiki\Block\Restriction\PageRestriction; |
32 | use MediaWiki\MainConfigNames; |
33 | use MediaWiki\ParamValidator\TypeDef\TitleDef; |
34 | use MediaWiki\ParamValidator\TypeDef\UserDef; |
35 | use MediaWiki\Title\Title; |
36 | use MediaWiki\Title\TitleFactory; |
37 | use MediaWiki\User\Options\UserOptionsLookup; |
38 | use MediaWiki\User\UserIdentity; |
39 | use MediaWiki\User\UserIdentityLookup; |
40 | use MediaWiki\Watchlist\WatchlistManager; |
41 | use Wikimedia\ParamValidator\ParamValidator; |
42 | use Wikimedia\ParamValidator\TypeDef\ExpiryDef; |
43 | |
44 | /** |
45 | * API module that facilitates the blocking of users. Requires API write mode |
46 | * to be enabled. |
47 | * |
48 | * @ingroup API |
49 | */ |
50 | class ApiBlock extends ApiBase { |
51 | |
52 | use ApiBlockInfoTrait; |
53 | use ApiWatchlistTrait; |
54 | |
55 | private BlockPermissionCheckerFactory $blockPermissionCheckerFactory; |
56 | private BlockUserFactory $blockUserFactory; |
57 | private TitleFactory $titleFactory; |
58 | private UserIdentityLookup $userIdentityLookup; |
59 | private WatchedItemStoreInterface $watchedItemStore; |
60 | private BlockUtils $blockUtils; |
61 | private BlockActionInfo $blockActionInfo; |
62 | |
63 | /** |
64 | * @param ApiMain $main |
65 | * @param string $action |
66 | * @param BlockPermissionCheckerFactory $blockPermissionCheckerFactory |
67 | * @param BlockUserFactory $blockUserFactory |
68 | * @param TitleFactory $titleFactory |
69 | * @param UserIdentityLookup $userIdentityLookup |
70 | * @param WatchedItemStoreInterface $watchedItemStore |
71 | * @param BlockUtils $blockUtils |
72 | * @param BlockActionInfo $blockActionInfo |
73 | * @param WatchlistManager $watchlistManager |
74 | * @param UserOptionsLookup $userOptionsLookup |
75 | */ |
76 | public function __construct( |
77 | ApiMain $main, |
78 | $action, |
79 | BlockPermissionCheckerFactory $blockPermissionCheckerFactory, |
80 | BlockUserFactory $blockUserFactory, |
81 | TitleFactory $titleFactory, |
82 | UserIdentityLookup $userIdentityLookup, |
83 | WatchedItemStoreInterface $watchedItemStore, |
84 | BlockUtils $blockUtils, |
85 | BlockActionInfo $blockActionInfo, |
86 | WatchlistManager $watchlistManager, |
87 | UserOptionsLookup $userOptionsLookup |
88 | ) { |
89 | parent::__construct( $main, $action ); |
90 | |
91 | $this->blockPermissionCheckerFactory = $blockPermissionCheckerFactory; |
92 | $this->blockUserFactory = $blockUserFactory; |
93 | $this->titleFactory = $titleFactory; |
94 | $this->userIdentityLookup = $userIdentityLookup; |
95 | $this->watchedItemStore = $watchedItemStore; |
96 | $this->blockUtils = $blockUtils; |
97 | $this->blockActionInfo = $blockActionInfo; |
98 | |
99 | // Variables needed in ApiWatchlistTrait trait |
100 | $this->watchlistExpiryEnabled = $this->getConfig()->get( MainConfigNames::WatchlistExpiry ); |
101 | $this->watchlistMaxDuration = |
102 | $this->getConfig()->get( MainConfigNames::WatchlistExpiryMaxDuration ); |
103 | $this->watchlistManager = $watchlistManager; |
104 | $this->userOptionsLookup = $userOptionsLookup; |
105 | } |
106 | |
107 | /** |
108 | * Blocks the user specified in the parameters for the given expiry, with the |
109 | * given reason, and with all other settings provided in the params. If the block |
110 | * succeeds, produces a result containing the details of the block and notice |
111 | * of success. If it fails, the result will specify the nature of the error. |
112 | */ |
113 | public function execute() { |
114 | $this->checkUserRightsAny( 'block' ); |
115 | $params = $this->extractRequestParams(); |
116 | $this->requireOnlyOneParameter( $params, 'user', 'userid' ); |
117 | |
118 | // Make sure $target contains a parsed target |
119 | if ( $params['user'] !== null ) { |
120 | $target = $params['user']; |
121 | } else { |
122 | $target = $this->userIdentityLookup->getUserIdentityByUserId( $params['userid'] ); |
123 | if ( !$target ) { |
124 | $this->dieWithError( [ 'apierror-nosuchuserid', $params['userid'] ], 'nosuchuserid' ); |
125 | } |
126 | } |
127 | [ $target, $targetType ] = $this->blockUtils->parseBlockTarget( $target ); |
128 | |
129 | if ( |
130 | $params['noemail'] && |
131 | !$this->blockPermissionCheckerFactory |
132 | ->newBlockPermissionChecker( |
133 | $target, |
134 | $this->getAuthority() |
135 | ) |
136 | ->checkEmailPermissions() |
137 | ) { |
138 | $this->dieWithError( 'apierror-cantblock-email' ); |
139 | } |
140 | |
141 | $restrictions = []; |
142 | if ( $params['partial'] ) { |
143 | $pageRestrictions = array_map( |
144 | [ PageRestriction::class, 'newFromTitle' ], |
145 | (array)$params['pagerestrictions'] |
146 | ); |
147 | |
148 | $namespaceRestrictions = array_map( static function ( $id ) { |
149 | return new NamespaceRestriction( 0, $id ); |
150 | }, (array)$params['namespacerestrictions'] ); |
151 | $restrictions = array_merge( $pageRestrictions, $namespaceRestrictions ); |
152 | |
153 | if ( $this->getConfig()->get( MainConfigNames::EnablePartialActionBlocks ) ) { |
154 | $actionRestrictions = array_map( function ( $action ) { |
155 | return new ActionRestriction( 0, $this->blockActionInfo->getIdFromAction( $action ) ); |
156 | }, (array)$params['actionrestrictions'] ); |
157 | $restrictions = array_merge( $restrictions, $actionRestrictions ); |
158 | } |
159 | } |
160 | |
161 | $status = $this->blockUserFactory->newBlockUser( |
162 | $target, |
163 | $this->getAuthority(), |
164 | $params['expiry'], |
165 | $params['reason'], |
166 | [ |
167 | 'isCreateAccountBlocked' => $params['nocreate'], |
168 | 'isEmailBlocked' => $params['noemail'], |
169 | 'isHardBlock' => !$params['anononly'], |
170 | 'isAutoblocking' => $params['autoblock'], |
171 | 'isUserTalkEditBlocked' => !$params['allowusertalk'], |
172 | 'isHideUser' => $params['hidename'], |
173 | 'isPartial' => $params['partial'], |
174 | ], |
175 | $restrictions, |
176 | $params['tags'] |
177 | )->placeBlock( $params['reblock'] ); |
178 | |
179 | if ( !$status->isOK() ) { |
180 | $this->dieStatus( $status ); |
181 | } |
182 | |
183 | $block = $status->value; |
184 | |
185 | $watchlistExpiry = $this->getExpiryFromParams( $params ); |
186 | $userPage = Title::makeTitle( NS_USER, $block->getTargetName() ); |
187 | |
188 | if ( $params['watchuser'] && $targetType !== AbstractBlock::TYPE_RANGE ) { |
189 | $this->setWatch( 'watch', $userPage, $this->getUser(), null, $watchlistExpiry ); |
190 | } |
191 | |
192 | $res = []; |
193 | |
194 | $res['user'] = $block->getTargetName(); |
195 | $res['userID'] = $target instanceof UserIdentity ? $target->getId() : 0; |
196 | |
197 | if ( $block instanceof DatabaseBlock ) { |
198 | $res['expiry'] = ApiResult::formatExpiry( $block->getExpiry(), 'infinite' ); |
199 | $res['id'] = $block->getId(); |
200 | } else { |
201 | # should be unreachable |
202 | $res['expiry'] = ''; // @codeCoverageIgnore |
203 | $res['id'] = ''; // @codeCoverageIgnore |
204 | } |
205 | |
206 | $res['reason'] = $params['reason']; |
207 | $res['anononly'] = $params['anononly']; |
208 | $res['nocreate'] = $params['nocreate']; |
209 | $res['autoblock'] = $params['autoblock']; |
210 | $res['noemail'] = $params['noemail']; |
211 | $res['hidename'] = $block->getHideName(); |
212 | $res['allowusertalk'] = $params['allowusertalk']; |
213 | $res['watchuser'] = $params['watchuser']; |
214 | if ( $watchlistExpiry ) { |
215 | $expiry = $this->getWatchlistExpiry( |
216 | $this->watchedItemStore, |
217 | $userPage, |
218 | $this->getUser() |
219 | ); |
220 | $res['watchlistexpiry'] = $expiry; |
221 | } |
222 | $res['partial'] = $params['partial']; |
223 | $res['pagerestrictions'] = $params['pagerestrictions']; |
224 | $res['namespacerestrictions'] = $params['namespacerestrictions']; |
225 | if ( $this->getConfig()->get( MainConfigNames::EnablePartialActionBlocks ) ) { |
226 | $res['actionrestrictions'] = $params['actionrestrictions']; |
227 | } |
228 | |
229 | $this->getResult()->addValue( null, $this->getModuleName(), $res ); |
230 | } |
231 | |
232 | public function mustBePosted() { |
233 | return true; |
234 | } |
235 | |
236 | public function isWriteMode() { |
237 | return true; |
238 | } |
239 | |
240 | public function getAllowedParams() { |
241 | $params = [ |
242 | 'user' => [ |
243 | ParamValidator::PARAM_TYPE => 'user', |
244 | UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'temp', 'cidr', 'id' ], |
245 | ], |
246 | 'userid' => [ |
247 | ParamValidator::PARAM_TYPE => 'integer', |
248 | ParamValidator::PARAM_DEPRECATED => true, |
249 | ], |
250 | 'expiry' => 'never', |
251 | 'reason' => '', |
252 | 'anononly' => false, |
253 | 'nocreate' => false, |
254 | 'autoblock' => false, |
255 | 'noemail' => false, |
256 | 'hidename' => false, |
257 | 'allowusertalk' => false, |
258 | 'reblock' => false, |
259 | 'watchuser' => false, |
260 | ]; |
261 | |
262 | // Params appear in the docs in the order they are defined, |
263 | // which is why this is here and not at the bottom. |
264 | // @todo Find better way to support insertion at arbitrary position |
265 | if ( $this->watchlistExpiryEnabled ) { |
266 | $params += [ |
267 | 'watchlistexpiry' => [ |
268 | ParamValidator::PARAM_TYPE => 'expiry', |
269 | ExpiryDef::PARAM_MAX => $this->watchlistMaxDuration, |
270 | ExpiryDef::PARAM_USE_MAX => true, |
271 | ] |
272 | ]; |
273 | } |
274 | |
275 | $params += [ |
276 | 'tags' => [ |
277 | ParamValidator::PARAM_TYPE => 'tags', |
278 | ParamValidator::PARAM_ISMULTI => true, |
279 | ], |
280 | 'partial' => false, |
281 | 'pagerestrictions' => [ |
282 | ParamValidator::PARAM_TYPE => 'title', |
283 | TitleDef::PARAM_MUST_EXIST => true, |
284 | |
285 | // TODO: TitleDef returns instances of TitleValue when PARAM_RETURN_OBJECT is |
286 | // truthy. At the time of writing, |
287 | // MediaWiki\Block\Restriction\PageRestriction::newFromTitle accepts either |
288 | // string or instance of Title. |
289 | //TitleDef::PARAM_RETURN_OBJECT => true, |
290 | |
291 | ParamValidator::PARAM_ISMULTI => true, |
292 | ParamValidator::PARAM_ISMULTI_LIMIT1 => 10, |
293 | ParamValidator::PARAM_ISMULTI_LIMIT2 => 10, |
294 | ], |
295 | 'namespacerestrictions' => [ |
296 | ParamValidator::PARAM_ISMULTI => true, |
297 | ParamValidator::PARAM_TYPE => 'namespace', |
298 | ], |
299 | ]; |
300 | |
301 | if ( $this->getConfig()->get( MainConfigNames::EnablePartialActionBlocks ) ) { |
302 | $params += [ |
303 | 'actionrestrictions' => [ |
304 | ParamValidator::PARAM_ISMULTI => true, |
305 | ParamValidator::PARAM_TYPE => array_keys( |
306 | $this->blockActionInfo->getAllBlockActions() |
307 | ), |
308 | ], |
309 | ]; |
310 | } |
311 | |
312 | return $params; |
313 | } |
314 | |
315 | public function needsToken() { |
316 | return 'csrf'; |
317 | } |
318 | |
319 | protected function getExamplesMessages() { |
320 | // phpcs:disable Generic.Files.LineLength |
321 | return [ |
322 | 'action=block&user=192.0.2.5&expiry=3%20days&reason=First%20strike&token=123ABC' |
323 | => 'apihelp-block-example-ip-simple', |
324 | 'action=block&user=Vandal&expiry=never&reason=Vandalism&nocreate=&autoblock=&noemail=&token=123ABC' |
325 | => 'apihelp-block-example-user-complex', |
326 | ]; |
327 | // phpcs:enable |
328 | } |
329 | |
330 | public function getHelpUrls() { |
331 | return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Block'; |
332 | } |
333 | } |