From 395fe60176a4df28e309d97be86ff12a3c5bd1e8 Mon Sep 17 00:00:00 2001 From: Subin Siby Date: Wed, 7 Dec 2016 22:34:02 +0530 Subject: [PATCH] Block API: Allow blocking/unblocking by user's ID Add feature to block/unblock users by their ID. For this,a new parameter `userid` is added to block & unblock API request. Bug: T34496 Change-Id: I084a4e275cd937053c505cd388a365b316990ece --- includes/api/ApiBlock.php | 31 ++++++++++++----- includes/api/ApiUnblock.php | 15 ++++++++- includes/api/i18n/en.json | 9 +++-- includes/api/i18n/qqq.json | 3 ++ tests/phpunit/includes/api/ApiBlockTest.php | 37 +++++++++++++++++++++ 5 files changed, 83 insertions(+), 12 deletions(-) diff --git a/includes/api/ApiBlock.php b/includes/api/ApiBlock.php index a4ea3857bb..3774f099a7 100644 --- a/includes/api/ApiBlock.php +++ b/includes/api/ApiBlock.php @@ -46,6 +46,8 @@ class ApiBlock extends ApiBase { $user = $this->getUser(); $params = $this->extractRequestParams(); + $this->requireOnlyOneParameter( $params, 'user', 'userid' ); + # bug 15810: blocked admins should have limited access here if ( $user->isBlocked() ) { $status = SpecialBlock::checkUnblockSelf( $params['user'], $user ); @@ -58,13 +60,24 @@ class ApiBlock extends ApiBase { } } - $target = User::newFromName( $params['user'] ); - // Bug 38633 - if the target is a user (not an IP address), but it - // doesn't exist or is unusable, error. - if ( $target instanceof User && - ( $target->isAnon() /* doesn't exist */ || !User::isUsableName( $target->getName() ) ) - ) { - $this->dieWithError( [ 'nosuchusershort', $params['user'] ], 'nosuchuser' ); + if ( $params['userid'] !== null ) { + $username = User::whoIs( $params['userid'] ); + + if ( $username === false ) { + $this->dieWithError( [ 'apierror-nosuchuserid', $params['userid'] ], 'nosuchuserid' ); + } else { + $params['user'] = $username; + } + } else { + $target = User::newFromName( $params['user'] ); + + // Bug 38633 - if the target is a user (not an IP address), but it + // doesn't exist or is unusable, error. + if ( $target instanceof User && + ( $target->isAnon() /* doesn't exist */ || !User::isUsableName( $target->getName() ) ) + ) { + $this->dieWithError( [ 'nosuchusershort', $params['user'] ], 'nosuchuser' ); + } } if ( $params['hidename'] && !$user->isAllowed( 'hideuser' ) ) { @@ -137,7 +150,9 @@ class ApiBlock extends ApiBase { return [ 'user' => [ ApiBase::PARAM_TYPE => 'user', - ApiBase::PARAM_REQUIRED => true + ], + 'userid' => [ + ApiBase::PARAM_TYPE => 'integer', ], 'expiry' => 'never', 'reason' => '', diff --git a/includes/api/ApiUnblock.php b/includes/api/ApiUnblock.php index 523a888d12..3eeb7a490e 100644 --- a/includes/api/ApiUnblock.php +++ b/includes/api/ApiUnblock.php @@ -39,7 +39,7 @@ class ApiUnblock extends ApiBase { $user = $this->getUser(); $params = $this->extractRequestParams(); - $this->requireOnlyOneParameter( $params, 'id', 'user' ); + $this->requireOnlyOneParameter( $params, 'id', 'user', 'userid' ); if ( !$user->isAllowed( 'block' ) ) { $this->dieWithError( 'apierror-permissiondenied-unblock', 'permissiondenied' ); @@ -64,6 +64,16 @@ class ApiUnblock extends ApiBase { } } + if ( $params['userid'] !== null ) { + $username = User::whoIs( $params['userid'] ); + + if ( $username === false ) { + $this->dieWithError( [ 'apierror-nosuchuserid', $params['userid'] ], 'nosuchuserid' ); + } else { + $params['user'] = $username; + } + } + $data = [ 'Target' => is_null( $params['id'] ) ? $params['user'] : "#{$params['id']}", 'Reason' => $params['reason'], @@ -97,6 +107,9 @@ class ApiUnblock extends ApiBase { ApiBase::PARAM_TYPE => 'integer', ], 'user' => null, + 'userid' => [ + ApiBase::PARAM_TYPE => 'integer' + ], 'reason' => '', 'tags' => [ ApiBase::PARAM_TYPE => 'tags', diff --git a/includes/api/i18n/en.json b/includes/api/i18n/en.json index a37b7cf461..240e167549 100644 --- a/includes/api/i18n/en.json +++ b/includes/api/i18n/en.json @@ -25,7 +25,8 @@ "apihelp-main-param-errorsuselocal": "If given, error texts will use locally-customized messages from the {{ns:MediaWiki}} namespace.", "apihelp-block-description": "Block a user.", - "apihelp-block-param-user": "Username, IP address, or IP address range to block.", + "apihelp-block-param-user": "Username, IP address, or IP address range to block. Cannot be used together with $1userid", + "apihelp-block-param-userid": "User ID to block. Cannot be used together with $1user.", "apihelp-block-param-expiry": "Expiry time. May be relative (e.g. 5 months or 2 weeks) or absolute (e.g. 2014-09-18T12:34:56Z). If set to infinite, indefinite, or never, the block will never expire.", "apihelp-block-param-reason": "Reason for block.", "apihelp-block-param-anononly": "Block anonymous users only (i.e. disable anonymous edits for this IP address).", @@ -1377,8 +1378,9 @@ "apihelp-tokens-example-emailmove": "Retrieve an email token and a move token.", "apihelp-unblock-description": "Unblock a user.", - "apihelp-unblock-param-id": "ID of the block to unblock (obtained through list=blocks). Cannot be used together with $1user.", - "apihelp-unblock-param-user": "Username, IP address or IP address range to unblock. Cannot be used together with $1id.", + "apihelp-unblock-param-id": "ID of the block to unblock (obtained through list=blocks). Cannot be used together with $1user or $luserid.", + "apihelp-unblock-param-user": "Username, IP address or IP address range to unblock. Cannot be used together with $1id or $luserid.", + "apihelp-unblock-param-userid": "User ID to unblock. Cannot be used together with $1id or $1user.", "apihelp-unblock-param-reason": "Reason for unblock.", "apihelp-unblock-param-tags": "Change tags to apply to the entry in the block log.", "apihelp-unblock-example-id": "Unblock block ID #105.", @@ -1653,6 +1655,7 @@ "apierror-nosuchrevid": "There is no revision with ID $1.", "apierror-nosuchsection": "There is no section $1.", "apierror-nosuchsection-what": "There is no section $1 in $2.", + "apierror-nosuchuserid": "There is no user with ID $1.", "apierror-notarget": "You have not specified a valid target for this action.", "apierror-notpatrollable": "The revision r$1 can't be patrolled as it's too old.", "apierror-nouploadmodule": "No upload module set.", diff --git a/includes/api/i18n/qqq.json b/includes/api/i18n/qqq.json index 0b78fa5607..2a1c913fc5 100644 --- a/includes/api/i18n/qqq.json +++ b/includes/api/i18n/qqq.json @@ -35,6 +35,7 @@ "apihelp-main-param-errorsuselocal": "{{doc-apihelp-param|main|errorsuselocal}}", "apihelp-block-description": "{{doc-apihelp-description|block}}", "apihelp-block-param-user": "{{doc-apihelp-param|block|user}}", + "apihelp-block-param-userid": "{{doc-apihelp-param|block|userid}}", "apihelp-block-param-expiry": "{{doc-apihelp-param|block|expiry}}\n{{doc-important|Do not translate \"5 months\", \"2 weeks\", \"infinite\", \"indefinite\" or \"never\"!}}", "apihelp-block-param-reason": "{{doc-apihelp-param|block|reason}}", "apihelp-block-param-anononly": "{{doc-apihelp-param|block|anononly}}\n* See also {{msg-mw|ipb-hardblock}}", @@ -1284,6 +1285,7 @@ "apihelp-unblock-description": "{{doc-apihelp-description|unblock}}", "apihelp-unblock-param-id": "{{doc-apihelp-param|unblock|id}}", "apihelp-unblock-param-user": "{{doc-apihelp-param|unblock|user}}", + "apihelp-unblock-param-userid": "{{doc-apihelp-param|unblock|userid}}", "apihelp-unblock-param-reason": "{{doc-apihelp-param|unblock|reason}}", "apihelp-unblock-param-tags": "{{doc-apihelp-param|unblock|tags}}", "apihelp-unblock-example-id": "{{doc-apihelp-example|unblock}}", @@ -1547,6 +1549,7 @@ "apierror-nosuchrevid": "{{doc-apierror}}\n\nParameters:\n* $1 - Revision ID number.", "apierror-nosuchsection": "{{doc-apierror}}\n\nParameters:\n* $1 - Section identifier. Probably a number or \"T-\" followed by a number.", "apierror-nosuchsection-what": "{{doc-apierror}}\n\nParameters:\n* $1 - Section identifier. Probably a number or \"T-\" followed by a number.\n* $2 - Page title, revision ID formatted with {{msg-mw|revid}}, or page ID formatted with {{msg-mw|pageid}}.", + "apierror-nosuchuserid": "{{doc-apierror}}", "apierror-notarget": "{{doc-apierror}}", "apierror-notpatrollable": "{{doc-apierror}}\n\nParameters:\n* $1 - Revision ID number.", "apierror-nouploadmodule": "{{doc-apierror}}", diff --git a/tests/phpunit/includes/api/ApiBlockTest.php b/tests/phpunit/includes/api/ApiBlockTest.php index 08fc1286cf..832a113f0e 100644 --- a/tests/phpunit/includes/api/ApiBlockTest.php +++ b/tests/phpunit/includes/api/ApiBlockTest.php @@ -13,6 +13,14 @@ class ApiBlockTest extends ApiTestCase { $this->doLogin(); } + protected function tearDown() { + $block = Block::newFromTarget( 'UTApiBlockee' ); + if ( !is_null( $block ) ) { + $block->delete(); + } + parent::tearDown(); + } + protected function getTokens() { return $this->getTokenList( self::$users['sysop'] ); } @@ -64,6 +72,35 @@ class ApiBlockTest extends ApiTestCase { $this->assertEquals( 'infinity', $block->mExpiry ); } + /** + * Block by user ID + */ + public function testMakeNormalBlockId() { + $tokens = $this->getTokens(); + $user = User::newFromName( 'UTApiBlockee' ); + + if ( !$user->getId() ) { + $this->markTestIncomplete( "The user UTApiBlockee does not exist." ); + } + + if ( !array_key_exists( 'blocktoken', $tokens ) ) { + $this->markTestIncomplete( "No block token found" ); + } + + $data = $this->doApiRequest( [ + 'action' => 'block', + 'userid' => $user->getId(), + 'reason' => 'Some reason', + 'token' => $tokens['blocktoken'] ], null, false, self::$users['sysop']->getUser() ); + + $block = Block::newFromTarget( 'UTApiBlockee' ); + + $this->assertTrue( !is_null( $block ), 'Block is valid.' ); + $this->assertEquals( 'UTApiBlockee', (string)$block->getTarget() ); + $this->assertEquals( 'Some reason', $block->mReason ); + $this->assertEquals( 'infinity', $block->mExpiry ); + } + /** * @expectedException ApiUsageException * @expectedExceptionMessage The "token" parameter must be set -- 2.20.1