Add PageSizeConstraint and ChangeTagsConstraint

Bug: T157658
Change-Id: I2c7b4f77cd07c72b42eaeb7e37095461c92b367a
This commit is contained in:
DannyS712 2020-10-27 18:36:46 +00:00
parent 722b5a07d6
commit cc8bab4111
9 changed files with 482 additions and 30 deletions

View file

@ -22,7 +22,9 @@
use MediaWiki\Block\DatabaseBlock;
use MediaWiki\Content\IContentHandlerFactory;
use MediaWiki\EditPage\Constraint\ChangeTagsConstraint;
use MediaWiki\EditPage\Constraint\EditConstraintRunner;
use MediaWiki\EditPage\Constraint\PageSizeConstraint;
use MediaWiki\EditPage\Constraint\SpamRegexConstraint;
use MediaWiki\EditPage\Constraint\UnicodeConstraint;
use MediaWiki\EditPage\Constraint\UserBlockConstraint;
@ -2071,6 +2073,8 @@ ERROR;
return $status;
}
$this->contentLength = strlen( $this->textbox1 );
// BEGINNING OF MIGRATION TO EDITCONSTRAINT SYSTEM (see T157658)
$constraintFactory = MediaWikiServices::getInstance()->getService( '_EditConstraintFactory' );
$constraintRunner = new EditConstraintRunner();
@ -2145,42 +2149,41 @@ ERROR;
$constraintRunner->addConstraint(
new UserRateLimitConstraint( $user, $changingContentModel )
);
$constraintRunner->addConstraint(
// Same constraint is used to check size before and after merging the
// edits, which use different failure codes
$constraintFactory->newPageSizeConstraint(
$this->contentLength,
PageSizeConstraint::BEFORE_MERGE
)
);
if ( $this->changeTags ) {
$constraintRunner->addConstraint(
new ChangeTagsConstraint( $user, $this->changeTags )
);
}
// Check the constraints
if ( $constraintRunner->checkConstraints() === false ) {
$failed = $constraintRunner->getFailedConstraint();
if ( $failed instanceof SpamRegexConstraint ) {
if ( $failed instanceof PageSizeConstraint ) {
// Error will be displayed by showEditForm()
$this->tooBig = true;
} elseif ( $failed instanceof SpamRegexConstraint ) {
$result['spam'] = $failed->getMatch();
} elseif ( $failed instanceof UserBlockConstraint && !wfReadOnly() ) {
} elseif ( $failed instanceof UserBlockConstraint ) {
// Auto-block user's IP if the account was "hard" blocked
$user->spreadAnyEditBlock();
if ( !wfReadOnly() ) {
$user->spreadAnyEditBlock();
}
}
$statusValue = $failed->getLegacyStatus();
$status = Status::wrap( $statusValue );
return $status;
}
// END OF MIGRATION TO EDITCONSTRAINT SYSTEM
$this->contentLength = strlen( $this->textbox1 );
$config = $this->context->getConfig();
$maxArticleSize = $config->get( 'MaxArticleSize' );
if ( $this->contentLength > $maxArticleSize * 1024 ) {
// Error will be displayed by showEditForm()
$this->tooBig = true;
$status->setResult( false, self::AS_CONTENT_TOO_BIG );
return $status;
}
if ( $this->changeTags ) {
$changeTagsStatus = ChangeTags::canAddTagsAccompanyingChange(
$this->changeTags, $user );
if ( !$changeTagsStatus->isOK() ) {
$changeTagsStatus->value = self::AS_CHANGE_TAG_ERROR;
return $changeTagsStatus;
}
}
// END OF MIGRATION TO EDITCONSTRAINT SYSTEM (continued below)
# If the article has been deleted while editing, don't save it without
# confirmation
@ -2437,11 +2440,32 @@ ERROR;
// Check for length errors again now that the section is merged in
$this->contentLength = strlen( $this->toEditText( $content ) );
if ( $this->contentLength > $maxArticleSize * 1024 ) {
$this->tooBig = true;
$status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
// BEGINNING OF MIGRATION TO EDITCONSTRAINT SYSTEM (see T157658)
// Create a new runner to avoid rechecking the prior constraints, use the same factory
$constraintRunner = new EditConstraintRunner();
$constraintRunner->addConstraint(
// Same constraint is used to check size before and after merging the
// edits, which use different failure codes
$constraintFactory->newPageSizeConstraint(
$this->contentLength,
PageSizeConstraint::AFTER_MERGE
)
);
// Check the constraints
if ( $constraintRunner->checkConstraints() === false ) {
$failed = $constraintRunner->getFailedConstraint();
if ( $failed instanceof PageSizeConstraint ) {
// Error will be displayed by showEditForm()
$this->tooBig = true;
}
$statusValue = $failed->getLegacyStatus();
$status = Status::wrap( $statusValue );
return $status;
}
// END OF MIGRATION TO EDITCONSTRAINT SYSTEM
$flags = EDIT_AUTOSUMMARY |
( $new ? EDIT_NEW : EDIT_UPDATE ) |

View file

@ -1525,10 +1525,12 @@ return [
// of dependencies will be needed by different constraints. It is not part of
// the public interface and has no corresponding method in MediaWikiServices
return new EditConstraintFactory(
// Multiple constraints need loggers
LoggerFactory::getProvider(),
// Multiple
new ServiceOptions(
EditConstraintFactory::CONSTRUCTOR_OPTIONS,
$services->getMainConfig()
),
LoggerFactory::getProvider(),
$services->getPermissionManager(),
// ReadOnlyConstraint

View file

@ -0,0 +1,84 @@
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
namespace MediaWiki\EditPage\Constraint;
use ChangeTags;
use StatusValue;
use User;
/**
* Verify user can add change tags
*
* @since 1.36
* @internal
* @author DannyS712
*/
class ChangeTagsConstraint implements IEditConstraint {
/** @var User */
private $user;
/** @var array */
private $tags;
/** @var StatusValue|string|null */
private $result;
/**
* @param User $user
* @param array $tags
*/
public function __construct(
User $user,
array $tags
) {
$this->user = $user;
$this->tags = $tags;
}
public function checkConstraint() : string {
// TODO inject a service once canAddTagsAccompanyingChange is moved to a
// service as part of T245964
$changeTagStatus = ChangeTags::canAddTagsAccompanyingChange(
$this->tags,
$this->user
);
if ( $changeTagStatus->isOK() ) {
$this->result = self::CONSTRAINT_PASSED;
return self::CONSTRAINT_PASSED;
}
$this->result = $changeTagStatus; // The same status object is returned
return self::CONSTRAINT_FAILED;
}
public function getLegacyStatus() : StatusValue {
if ( $this->result === self::CONSTRAINT_PASSED ) {
$statusValue = StatusValue::newGood();
} else {
$statusValue = $this->result;
$statusValue->value = self::AS_CHANGE_TAG_ERROR;
}
return $statusValue;
}
}

View file

@ -20,6 +20,7 @@
namespace MediaWiki\EditPage\Constraint;
use MediaWiki\Config\ServiceOptions;
use MediaWiki\EditPage\SpamChecker;
use MediaWiki\Linker\LinkTarget;
use MediaWiki\Logger\Spi;
@ -38,6 +39,15 @@ use User;
*/
class EditConstraintFactory {
/** @internal */
public const CONSTRUCTOR_OPTIONS = [
// PageSizeConstraint
'MaxArticleSize',
];
/** @var ServiceOptions */
private $options;
/** @var Spi */
private $loggerFactory;
@ -58,18 +68,23 @@ class EditConstraintFactory {
* The checks in EditPage use wfDebugLog and logged to different channels, hence the need
* for multiple loggers retrieved from the Spi. TODO can they be combined into the same channel?
*
* @param ServiceOptions $options
* @param Spi $loggerFactory
* @param PermissionManager $permissionManager
* @param ReadOnlyMode $readOnlyMode
* @param SpamChecker $spamRegexChecker
*/
public function __construct(
ServiceOptions $options,
Spi $loggerFactory,
PermissionManager $permissionManager,
ReadOnlyMode $readOnlyMode,
SpamChecker $spamRegexChecker
) {
$options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
// Multiple
$this->options = $options;
$this->loggerFactory = $loggerFactory;
$this->permissionManager = $permissionManager;
@ -129,6 +144,22 @@ class EditConstraintFactory {
);
}
/**
* @param int $contentSize
* @param string $type
* @return PageSizeConstraint
*/
public function newPageSizeConstraint(
int $contentSize,
string $type
) : PageSizeConstraint {
return new PageSizeConstraint(
$this->options->get( 'MaxArticleSize' ),
$contentSize,
$type
);
}
/**
* @return ReadOnlyConstraint
*/

View file

@ -0,0 +1,93 @@
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
namespace MediaWiki\EditPage\Constraint;
use InvalidArgumentException;
use StatusValue;
/**
* Verify the page isn't larger than the maximum
*
* This is used for both checking the size //before// merging the edit, and checking the size
* //after// applying the edit, and the result codes they return are different, hence the need
* to pass the error code in the constructor to avoid duplication
*
* @since 1.36
* @internal
* @author DannyS712
*/
class PageSizeConstraint implements IEditConstraint {
/**
* Same constraint is used for two different errors, use these
* to specify which one should be used
*/
public const BEFORE_MERGE = 'check-before-edit-merge';
public const AFTER_MERGE = 'check-after-edit-merge';
/** @var int */
private $contentSize;
/** @var int */
private $maxSize;
/** @var int */
private $errorCode;
/**
* @param int $maxSize In kilobytes, from $wgMaxArticleSize
* @param int $contentSize
* @param string $type
*/
public function __construct(
int $maxSize,
int $contentSize,
string $type
) {
$this->maxSize = $maxSize * 1024; // Convert from kilobytes
$this->contentSize = $contentSize;
if ( $type === self::BEFORE_MERGE ) {
$this->errorCode = self::AS_CONTENT_TOO_BIG;
} elseif ( $type === self::AFTER_MERGE ) {
$this->errorCode = self::AS_MAX_ARTICLE_SIZE_EXCEEDED;
} else {
throw new InvalidArgumentException( "Invalid type: $type" );
}
}
public function checkConstraint() : string {
return $this->contentSize > $this->maxSize ?
self::CONSTRAINT_FAILED :
self::CONSTRAINT_PASSED;
}
public function getLegacyStatus() : StatusValue {
$statusValue = StatusValue::newGood();
if ( $this->contentSize > $this->maxSize ) {
// Either self::AS_CONTENT_TOO_BIG, if it was too big before merging,
// or self::AS_MAX_ARTICLE_SIZE_EXCEEDED, if it was too big after merging
$statusValue->setResult( false, $this->errorCode );
}
return $statusValue;
}
}

View file

@ -224,7 +224,7 @@ $wgAutoloadClasses += [
# tests/phpunit/unit/includes
'BadFileLookupTest' => "$testDir/phpunit/unit/includes/BadFileLookupTest.php",
# tests/phpunit/unit/includes/editpage/Constraint
# tests/phpunit/unit/includes/editpage/Constraint and tests/phpunit/integration/includes/editpage/Constraint
'EditConstraintTestTrait' => "$testDir/phpunit/unit/includes/editpage/Constraint/EditConstraintTestTrait.php",
# tests/phpunit/unit/includes/filebackend

View file

@ -155,6 +155,27 @@ class EditPageConstraintsTest extends MediaWikiLangTestCase {
return WikiPage::factory( $title );
}
/** ChangeTagsConstraint integration */
public function testChangeTagsConstraint() {
// Remove rights
$this->mergeMwGlobalArrayValue(
'wgRevokePermissions',
[ 'user' => [ 'applychangetags' => true ] ]
);
$edit = [
'wpTextbox1' => 'Text',
'wpChangeTags' => 'tag-name'
];
$this->assertEdit(
'EditPageTest_changeTagsConstraint',
null,
null,
$edit,
EditPage::AS_CHANGE_TAG_ERROR,
'expected AS_CHANGE_TAG_ERROR update'
);
}
/** ContentModelChangeConstraint integration */
public function testContentModelChangeConstraint() {
$user = $this->createMock( User::class );
@ -261,6 +282,47 @@ class EditPageConstraintsTest extends MediaWikiLangTestCase {
yield 'Registered user' => [ false, EditPage::AS_IMAGE_REDIRECT_LOGGED ];
}
/** PageSizeConstraint integration */
public function testPageSizeConstraintBeforeMerge() {
// Max size: 1 kilobyte
$this->setMwGlobals( [
'wgMaxArticleSize' => 1
] );
$edit = [
'wpTextbox1' => str_repeat( 'text', 1000 )
];
$this->assertEdit(
'EditPageTest_pageSizeConstraintBeforeMerge',
null,
null,
$edit,
EditPage::AS_CONTENT_TOO_BIG,
'expected AS_CONTENT_TOO_BIG update'
);
}
/** PageSizeConstraint integration */
public function testPageSizeConstraintAfterMerge() {
// Max size: 1 kilobyte
$this->setMwGlobals( [
'wgMaxArticleSize' => 1
] );
$edit = [
'wpSection' => 'new',
'wpTextbox1' => str_repeat( 'b', 600 )
];
$this->assertEdit(
'EditPageTest_pageSizeConstraintAfterMerge',
str_repeat( 'a', 600 ),
null,
$edit,
EditPage::AS_MAX_ARTICLE_SIZE_EXCEEDED,
'expected AS_MAX_ARTICLE_SIZE_EXCEEDED update'
);
}
/** ReadOnlyConstraint integration */
public function testReadOnlyConstraint() {
$readOnlyMode = $this->createMock( ReadOnlyMode::class );

View file

@ -0,0 +1,74 @@
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
use MediaWiki\EditPage\Constraint\ChangeTagsConstraint;
use MediaWiki\EditPage\Constraint\IEditConstraint;
/**
* Tests the ChangeTagsConstraint
*
* @author DannyS712
*
* @covers \MediaWiki\EditPage\Constraint\ChangeTagsConstraint
* @group database
*/
class ChangeTagsConstraintTest extends MediaWikiIntegrationTestCase {
use EditConstraintTestTrait;
protected function setUp() : void {
parent::setUp();
$this->tablesUsed = array_merge(
$this->tablesUsed,
[ 'change_tag', 'change_tag_def', 'logging' ]
);
$this->mergeMwGlobalArrayValue( 'wgGroupPermissions', [
'sysop' => [ 'applychangetags' => true ]
] );
$this->mergeMwGlobalArrayValue( 'wgRevokePermissions', [
'noapplytags' => [ 'applychangetags' => true ]
] );
}
public function testPass() {
$tagName = 'tag-for-constraint-test-pass';
ChangeTags::defineTag( $tagName );
$constraint = new ChangeTagsConstraint(
$this->getTestSysop()->getUser(),
[ $tagName ]
);
$this->assertConstraintPassed( $constraint );
}
public function testFailure() {
$tagName = 'tag-for-constraint-test-fail';
ChangeTags::defineTag( $tagName );
$constraint = new ChangeTagsConstraint(
$this->getTestUser( [ 'noapplytags' ] )->getUser(),
[ $tagName ]
);
$this->assertConstraintFailed(
$constraint,
IEditConstraint::AS_CHANGE_TAG_ERROR
);
}
}

View file

@ -0,0 +1,82 @@
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
use MediaWiki\EditPage\Constraint\IEditConstraint;
use MediaWiki\EditPage\Constraint\PageSizeConstraint;
/**
* Tests the PageSizeConstraint
*
* @author DannyS712
*
* @covers \MediaWiki\EditPage\Constraint\PageSizeConstraint
*/
class PageSizeConstraintTest extends MediaWikiUnitTestCase {
use EditConstraintTestTrait;
/**
* @dataProvider provideCodes
* @param string $type
* @param int $errorCode unused
*/
public function testPass( string $type, int $errorCode ) {
// 1023 < ( 1 * 1024 )
$constraint = new PageSizeConstraint(
1,
1023,
$type
);
$this->assertConstraintPassed( $constraint );
}
/**
* @dataProvider provideCodes
* @param string $type
* @param int $errorCode
*/
public function testFailure( string $type, int $errorCode ) {
// 1025 > ( 1 * 1024 )
$constraint = new PageSizeConstraint(
1,
1025,
$type
);
$this->assertConstraintFailed( $constraint, $errorCode );
}
public function provideCodes() {
return [
'Before merge - CONTENT_TOO_BIG' => [
PageSizeConstraint::BEFORE_MERGE,
IEditConstraint::AS_CONTENT_TOO_BIG
],
'After merge - MAX_ARTICLE_SIZE' => [
PageSizeConstraint::AFTER_MERGE,
IEditConstraint::AS_MAX_ARTICLE_SIZE_EXCEEDED
]
];
}
public function testInvalidType() {
$this->expectException( InvalidArgumentException::class );
new PageSizeConstraint( 1, 1023, 'FooBar' );
}
}