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