Enforce partial blocks

Enforce partial blocks and display a slightly different block
notice depending on if the block is sitewide or not

Bug: T197117
Depends-On: I675316dddf272fd0d6172ecad3882160752bf780
Change-Id: I8a3635a4a04a33912eb139b7b13c4bd874183d31
This commit is contained in:
Dayllan Maza 2018-08-26 21:45:18 -04:00 committed by Dbarratt
parent a562611e5b
commit d67121f6d3
13 changed files with 254 additions and 27 deletions

View file

@ -1146,7 +1146,10 @@ class Block {
* @return bool|null Null for unrecognized rights.
*/
public function prevents( $action, $x = null ) {
global $wgBlockDisablesLogin;
$config = RequestContext::getMain()->getConfig();
$blockDisablesLogin = $config->get( 'BlockDisablesLogin' );
$blockAllowsUTEdit = $config->get( 'BlockAllowsUTEdit' );
$res = null;
switch ( $action ) {
case 'edit':
@ -1159,14 +1162,22 @@ class Block {
case 'sendemail':
$res = wfSetVar( $this->mBlockEmail, $x );
break;
case 'upload':
// Until T6995 is completed
$res = $this->isSitewide();
break;
case 'editownusertalk':
$res = wfSetVar( $this->mDisableUsertalk, $x );
// edit own user talk can be disabled by config
if ( !$blockAllowsUTEdit ) {
$res = true;
}
break;
case 'read':
$res = false;
break;
}
if ( !$res && $wgBlockDisablesLogin ) {
if ( !$res && $blockDisablesLogin ) {
// If a block would disable login, then it should
// prevent any action that all users cannot do
$anon = new User;
@ -1692,6 +1703,29 @@ class Block {
* @return array
*/
public function getPermissionsError( IContextSource $context ) {
$params = $this->getBlockErrorParams( $context );
$msg = 'blockedtext';
if ( $this->getSystemBlockType() !== null ) {
$msg = 'systemblockedtext';
} elseif ( $this->mAuto ) {
$msg = 'autoblockedtext';
} elseif ( !$this->isSitewide() ) {
$msg = 'blockedtext-partial';
}
array_unshift( $params, $msg );
return $params;
}
/**
* Get block information used in different block error messages
*
* @param IContextSource $context
* @return array
*/
public function getBlockErrorParams( IContextSource $context ) {
$blocker = $this->getBlocker();
if ( $blocker instanceof User ) { // local user
$blockerUserpage = $blocker->getUserPage();
@ -1708,14 +1742,10 @@ class Block {
/* $ip returns who *is* being blocked, $intended contains who was meant to be blocked.
* This could be a username, an IP range, or a single IP. */
$intended = $this->getTarget();
$systemBlockType = $this->getSystemBlockType();
$lang = $context->getLanguage();
return [
$systemBlockType !== null
? 'systemblockedtext'
: ( $this->mAuto ? 'autoblockedtext' : 'blockedtext' ),
$link,
$reason,
$context->getRequest()->getIP(),
@ -1762,4 +1792,34 @@ class Block {
return $this;
}
/**
* Checks if a block prevents an edit on a given article
*
* @param \Title $title
* @return bool
*/
public function preventsEdit( \Title $title ) {
$blocked = $this->isSitewide();
// user talk page has it's own rules
// This check happens before partial blocks because the flag
// to allow user to edit their user talk page could be
// overwritten by a partial block restriction (E.g. user talk namespace)
$user = $this->getTarget();
if ( $title->equals( $user->getTalkPage() ) ) {
$blocked = $this->prevents( 'editownusertalk' );
}
if ( !$this->isSitewide() ) {
$restrictions = $this->getRestrictions();
foreach ( $restrictions as $restriction ) {
if ( $restriction->matches( $title ) ) {
$blocked = true;
}
}
}
return $blocked;
}
}

View file

@ -1853,6 +1853,12 @@ abstract class ApiBase extends ContextSource {
'blocked',
[ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $user->getBlock() ) ]
) );
} elseif ( is_array( $error ) && $error[0] === 'blockedtext-partial' && $user->getBlock() ) {
$status->fatal( ApiMessage::create(
'apierror-blocked-partial',
'blocked',
[ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $user->getBlock() ) ]
) );
} elseif ( is_array( $error ) && $error[0] === 'autoblockedtext' && $user->getBlock() ) {
$status->fatal( ApiMessage::create(
'apierror-autoblocked',
@ -2027,6 +2033,12 @@ abstract class ApiBase extends ContextSource {
'autoblocked',
[ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $block ) ]
);
} elseif ( !$block->isSitewide() ) {
$this->dieWithError(
'apierror-blocked-partial',
'blocked',
[ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $block ) ]
);
} else {
$this->dieWithError(
'apierror-blocked',

View file

@ -414,11 +414,7 @@ class ApiEditPage extends ApiBase {
// obvious that this is even possible.
// @codeCoverageIgnoreStart
case EditPage::AS_BLOCKED_PAGE_FOR_USER:
$this->dieWithError(
'apierror-blocked',
'blocked',
[ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $user->getBlock() ) ]
);
$this->dieBlocked( $user->getBlock() );
case EditPage::AS_READ_ONLY_PAGE:
$this->dieReadOnly();

View file

@ -1706,6 +1706,7 @@
"apierror-bad-watchlist-token": "Incorrect watchlist token provided. Please set a correct token in [[Special:Preferences]].",
"apierror-blockedfrommail": "You have been blocked from sending email.",
"apierror-blocked": "You have been blocked from editing.",
"apierror-blocked-partial": "You have been blocked from editing this page.",
"apierror-botsnotsupported": "This interface is not supported for bots.",
"apierror-cannot-async-upload-file": "The parameters <var>async</var> and <var>file</var> cannot be combined. If you want asynchronous processing of your uploaded file, first upload it to stash (using the <var>stash</var> parameter) and then publish the stashed file asynchronously (using <var>filekey</var> and <var>async</var>).",
"apierror-cannotreauthenticate": "This action is not available as your identity cannot be verified.",

View file

@ -1594,6 +1594,7 @@
"apierror-bad-watchlist-token": "{{doc-apierror}}",
"apierror-blockedfrommail": "{{doc-apierror}}",
"apierror-blocked": "{{doc-apierror}}",
"apierror-blocked-partial": "{{doc-apierror}}",
"apierror-botsnotsupported": "{{doc-apierror}}",
"apierror-cannot-async-upload-file": "{{doc-apierror}}",
"apierror-cannotreauthenticate": "{{doc-apierror}}",

View file

@ -131,7 +131,7 @@ class SpecialEmailUser extends UnlistedSpecialPage {
case 'badaccess':
throw new PermissionsError( 'sendemail' );
case 'blockedemailuser':
throw new UserBlockedError( $this->getUser()->mBlock );
throw $this->getBlockedEmailError();
case 'actionthrottledtext':
throw new ThrottledError;
case 'mailnologin':
@ -524,4 +524,17 @@ class SpecialEmailUser extends UnlistedSpecialPage {
protected function getGroupName() {
return 'users';
}
/**
* Builds an error message based on the block params
*
* @return ErrorPageError
*/
private function getBlockedEmailError() {
$block = $this->getUser()->mBlock;
$params = $block->getBlockErrorParams( $this->getContext() );
$msg = $block->isSitewide() ? 'blockedtext' : 'blocked-email-user';
return new ErrorPageError( 'blockedtitle', $msg, $params );
}
}

View file

@ -177,7 +177,7 @@ class SpecialUpload extends SpecialPage {
}
# Check blocks
if ( $user->isBlocked() ) {
if ( $user->isBlockedFromUpload() ) {
throw new UserBlockedError( $user->getBlock() );
}

View file

@ -2297,21 +2297,22 @@ class User implements IDBAccessObject, UserIdentity {
* Check if user is blocked from editing a particular article
*
* @param Title $title Title to check
* @param bool $bFromSlave Whether to check the replica DB instead of the master
* @param bool $fromSlave Whether to check the replica DB instead of the master
* @return bool
*/
public function isBlockedFrom( $title, $bFromSlave = false ) {
global $wgBlockAllowsUTEdit;
public function isBlockedFrom( $title, $fromSlave = false ) {
$blocked = $this->isHidden();
$blocked = $this->isBlocked( $bFromSlave );
$allowUsertalk = ( $wgBlockAllowsUTEdit ? $this->mAllowUsertalk : false );
// If a user's name is suppressed, they cannot make edits anywhere
if ( !$this->mHideName && $allowUsertalk && $title->getText() === $this->getName()
&& $title->getNamespace() == NS_USER_TALK ) {
$blocked = false;
wfDebug( __METHOD__ . ": self-talk page, ignoring any blocks\n" );
if ( !$blocked ) {
$block = $this->getBlock( $fromSlave );
if ( $block ) {
$blocked = $block->preventsEdit( $title );
}
}
// only for the purpose of the hook. We really don't need this here.
$allowUsertalk = $this->mAllowUsertalk;
Hooks::run( 'UserIsBlockedFrom', [ $this, $title, &$blocked, &$allowUsertalk ] );
return $blocked;
@ -2418,7 +2419,7 @@ class User implements IDBAccessObject, UserIdentity {
*/
public function isHidden() {
if ( $this->mHideName !== null ) {
return $this->mHideName;
return (bool)$this->mHideName;
}
$this->getBlockedStatus();
if ( !$this->mHideName ) {
@ -2428,7 +2429,7 @@ class User implements IDBAccessObject, UserIdentity {
$this->mHideName = $authUser && $authUser->isHidden();
Hooks::run( 'UserIsHidden', [ $this, &$this->mHideName ] );
}
return $this->mHideName;
return (bool)$this->mHideName;
}
/**
@ -4518,6 +4519,16 @@ class User implements IDBAccessObject, UserIdentity {
return $this->mBlock && $this->mBlock->prevents( 'sendemail' );
}
/**
* Get whether the user is blocked from using Special:Upload
*
* @return bool
*/
public function isBlockedFromUpload() {
$this->getBlockedStatus();
return $this->mBlock && $this->mBlock->prevents( 'upload' );
}
/**
* Get whether the user is allowed to create an account.
* @return bool

View file

@ -666,6 +666,8 @@
"subject-preview": "Preview of subject:",
"previewerrortext": "An error occurred while attempting to preview your changes.",
"blockedtitle": "User is blocked",
"blocked-email-user": "<strong>Your username has been blocked from sending email. You can still edit other pages on this wiki.</strong> You can view the full block details at [[Special:MyContributions|account contributions]].\n\nThe block was made by $1.\n\nThe reason given is <em>$2</em>.\n\n* Start of block: $8\n* Expiration of block: $6\n* Intended blockee: $7\n* Block ID #$5",
"blockedtext-partial": "<strong>Your username or IP address has been blocked from making changes to this page. You can still edit other pages on this wiki.</strong> You can view the full block details at [[Special:MyContributions|account contributions]].\n\nThe block was made by $1.\n\nThe reason given is <em>$2</em>.\n\n* Start of block: $8\n* Expiration of block: $6\n* Intended blockee: $7\n* Block ID #$5",
"blockedtext": "<strong>Your username or IP address has been blocked.</strong>\n\nThe block was made by $1.\nThe reason given is <em>$2</em>.\n\n* Start of block: $8\n* Expiration of block: $6\n* Intended blockee: $7\n\nYou can contact $1 or another [[{{MediaWiki:Grouppage-sysop}}|administrator]] to discuss the block.\nYou cannot use the \"{{int:emailuser}}\" feature unless a valid email address is specified in your [[Special:Preferences|account preferences]] and you have not been blocked from using it.\nYour current IP address is $3, and the block ID is #$5.\nPlease include all above details in any queries you make.",
"autoblockedtext": "Your IP address has been automatically blocked because it was used by another user, who was blocked by $1.\nThe reason given is:\n\n:<em>$2</em>\n\n* Start of block: $8\n* Expiration of block: $6\n* Intended blockee: $7\n\nYou may contact $1 or one of the other [[{{MediaWiki:Grouppage-sysop}}|administrators]] to discuss the block.\n\nNote that you may not use the \"{{int:emailuser}}\" feature unless you have a valid email address registered in your [[Special:Preferences|user preferences]] and you have not been blocked from using it.\n\nYour current IP address is $3, and the block ID is #$5.\nPlease include all above details in any queries you make.",
"systemblockedtext": "Your username or IP address has been automatically blocked by MediaWiki.\nThe reason given is:\n\n:<em>$2</em>\n\n* Start of block: $8\n* Expiration of block: $6\n* Intended blockee: $7\n\nYour current IP address is $3.\nPlease include all above details in any queries you make.",

View file

@ -868,6 +868,8 @@
"subject-preview": "Used as label for preview of the section title when adding a new section on a talk page.\n\nShould match {{msg-mw|subject}}.\n\nSee also:\n* {{msg-mw|Summary-preview}}\n\n{{Identical|Subject}}",
"previewerrortext": "When a user has the editing preference LivePreview enabled, clicked the Preview or Show Changes button in the edit page and the action did not succeed.",
"blockedtitle": "Used as title displayed for blocked users. The corresponding message body is one of the following messages:\n* {{msg-mw|Blockedtext|notext=1}}\n* {{msg-mw|Autoblockedtext|notext=1}}\n* {{msg-mw|Systemblockedtext}}",
"blocked-email-user": "Text displayed to partially blocked users that are blocked from sending email.\n\n\"email this user\" should be consistent with {{msg-mw|Emailuser}}.\n\nParameters:\n* $1 - the blocking sysop (with a link to his/her userpage)\n* $2 - the reason for the block\n* $3 - the current IP address of the blocked user\n* $4 - (Unused) the blocking sysop's username (plain text, without the link)\n* $5 - the unique numeric identifier of the applied autoblock\n* $6 - the expiry of the block\n* $7 - the intended target of the block (what the blocking user specified in the blocking form)\n* $8 - the timestamp when the block started\nSee also:\n* {{msg-mw|Grouppage-sysop}}\n* {{msg-mw|Autoblockedtext}}\n* {{msg-mw|Systemblockedtext}}",
"blockedtext-partial": "Text displayed to partially blocked users.\n\n\"email this user\" should be consistent with {{msg-mw|Emailuser}}.\n\nParameters:\n* $1 - the blocking sysop (with a link to his/her userpage)\n* $2 - the reason for the block\n* $3 - the current IP address of the blocked user\n* $4 - (Unused) the blocking sysop's username (plain text, without the link)\n* $5 - the unique numeric identifier of the applied autoblock\n* $6 - the expiry of the block\n* $7 - the intended target of the block (what the blocking user specified in the blocking form)\n* $8 - the timestamp when the block started\nSee also:\n* {{msg-mw|Grouppage-sysop}}\n* {{msg-mw|Autoblockedtext}}\n* {{msg-mw|Systemblockedtext}}",
"blockedtext": "Text displayed to blocked users.\n\n\"email this user\" should be consistent with {{msg-mw|Emailuser}}.\n\nParameters:\n* $1 - the blocking sysop (with a link to his/her userpage)\n* $2 - the reason for the block\n* $3 - the current IP address of the blocked user\n* $4 - (Unused) the blocking sysop's username (plain text, without the link)\n* $5 - the unique numeric identifier of the applied autoblock\n* $6 - the expiry of the block\n* $7 - the intended target of the block (what the blocking user specified in the blocking form)\n* $8 - the timestamp when the block started\nSee also:\n* {{msg-mw|Grouppage-sysop}}\n* {{msg-mw|Autoblockedtext}}\n* {{msg-mw|Systemblockedtext}}",
"autoblockedtext": "Text displayed to automatically blocked users.\n\n\"email this user\" should be consistent with {{msg-mw|Emailuser}}.\n\nParameters:\n* $1 - the blocking sysop (with a link to his/her userpage)\n* $2 - the reason for the block (in case of autoblocks: {{msg-mw|autoblocker}})\n* $3 - the current IP address of the blocked user\n* $4 - (Unused) the blocking sysop's username (plain text, without the link). Use it for GENDER.\n* $5 - the unique numeric identifier of the applied autoblock\n* $6 - the expiry of the block\n* $7 - the intended target of the block (what the blocking user specified in the blocking form)\n* $8 - the timestamp when the block started\nSee also:\n* {{msg-mw|Grouppage-sysop}}\n* {{msg-mw|Blockedtext}}\n* {{msg-mw|Systemblockedtext}}",
"systemblockedtext": "Text displayed to requests blocked by MediaWiki configuration.\n\n\"email this user\" should be consistent with {{msg-mw|Emailuser}}.\n\nParameters:\n* $1 - (Unused) A dummy user attributed as the blocker, possibly as a link to a user page.\n* $2 - the reason for the block\n* $3 - the current IP address of the blocked user\n* $4 - (Unused) the dummy blocking user's username (plain text, without the link).\n* $5 - A short string indicating the type of system block.\n* $6 - the expiry of the block\n* $7 - the intended target of the block\n* $8 - the timestamp when the block started\nSee also:\n* {{msg-mw|Grouppage-sysop}}\n* {{msg-mw|Blockedtext}}\n* {{msg-mw|Autoblockedtext}}",

View file

@ -1,5 +1,6 @@
<?php
use MediaWiki\Block\BlockRestriction;
use MediaWiki\Block\Restriction\PageRestriction;
/**
@ -610,4 +611,115 @@ class BlockTest extends MediaWikiLangTestCase {
$block->delete();
}
/**
* @covers Block::preventsEdit
*/
public function testPreventsEditReturnsTrueOnSitewideBlock() {
$user = $this->getTestUser()->getUser();
$block = new Block( [
'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 40 * 60 * 60 ) ),
'allowUsertalk' => true,
'sitewide' => true
] );
$block->setTarget( $user );
$block->setBlocker( $this->getTestSysop()->getUser() );
$block->insert();
$title = $this->getExistingTestPage( 'Foo' )->getTitle();
$this->assertTrue( $block->preventsEdit( $title ) );
$block->delete();
}
/**
* @covers Block::preventsEdit
*/
public function testPreventsEditOnPartialBlock() {
$user = $this->getTestUser()->getUser();
$block = new Block( [
'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 40 * 60 * 60 ) ),
'allowUsertalk' => true,
'sitewide' => false
] );
$block->setTarget( $user );
$block->setBlocker( $this->getTestSysop()->getUser() );
$block->insert();
$pageFoo = $this->getExistingTestPage( 'Foo' );
$pageBar = $this->getExistingTestPage( 'Bar' );
$pageRestriction = new PageRestriction( $block->getId(), $pageFoo->getId() );
BlockRestriction::insert( [ $pageRestriction ] );
$this->assertTrue( $block->preventsEdit( $pageFoo->getTitle() ) );
$this->assertFalse( $block->preventsEdit( $pageBar->getTitle() ) );
$block->delete();
}
/**
* @covers Block::preventsEdit
* @dataProvider preventsEditOnUserTalkProvider
*/
public function testPreventsEditOnUserTalkPage(
$allowUsertalk, $sitewide, $result, $blockAllowsUTEdit = true
) {
$this->setMwGlobals( [
'wgBlockAllowsUTEdit' => $blockAllowsUTEdit,
] );
$user = $this->getTestUser()->getUser();
$block = new Block( [
'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 40 * 60 * 60 ) ),
'allowUsertalk' => $allowUsertalk,
'sitewide' => $sitewide
] );
$block->setTarget( $user );
$block->setBlocker( $this->getTestSysop()->getUser() );
$block->insert();
$this->assertEquals( $result, $block->preventsEdit( $user->getTalkPage() ) );
$block->delete();
}
public function preventsEditOnUserTalkProvider() {
return [
[
'allowUsertalk' => false,
'sitewide' => true,
'result' => true,
],
[
'allowUsertalk' => true,
'sitewide' => true,
'result' => false,
],
[
'allowUsertalk' => true,
'sitewide' => false,
'result' => false,
],
[
'allowUsertalk' => false,
'sitewide' => false,
'result' => true,
],
[
'allowUsertalk' => true,
'sitewide' => true,
'result' => true,
'blockAllowsUTEdit' => false
],
[
'allowUsertalk' => true,
'sitewide' => false,
'result' => true,
'blockAllowsUTEdit' => false
],
];
}
}

View file

@ -969,5 +969,22 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
'Useruser', 'test', '23:00, 31 December 1969', '127.0.8.1',
$wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ],
$this->title->getUserPermissionsErrors( 'move-target', $this->user ) );
// partial block message test
$this->user->mBlockedby = $this->user->getName();
$this->user->mBlock = new Block( [
'address' => '127.0.8.1',
'by' => $this->user->getId(),
'reason' => 'no reason given',
'timestamp' => $now,
'sitewide' => false,
'expiry' => 10,
] );
$this->assertEquals( [ [ 'blockedtext-partial',
'[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
'Useruser', null, '23:00, 31 December 1969', '127.0.8.1',
$wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ],
$this->title->getUserPermissionsErrors( 'move-target', $this->user ) );
}
}

View file

@ -107,7 +107,7 @@ class ApiQueryInfoTest extends ApiTestCase {
'user' => $badActor->getId(),
'by' => $sysop->getId(),
'expiry' => 'infinity',
'sitewide' => 0,
'sitewide' => 1,
'enableAutoblock' => true,
] );