mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/ConfirmEdit
synced 2024-11-24 00:04:15 +00:00
3b195090fe
Why: - In the production WMF deployment of AbuseFilter and ConfirmEdit, we load ConfirmEdit first, then AbuseFilter. That means that ConfirmEdit's onEditFilterMergedContent hook fires before AbuseFilter's. The problem is that AbuseFilter uses onEditFilterMergedContent to evaluate its rules and consequences, so an AbuseFilter rule that defines a "showcaptcha" consequence becomes a no-op, as it fires after ConfirmEdit has already decided to show or not show a CAPTCHA to a user. - All of that is to say: we need a way to tell ConfirmEdit to show a CAPTCHA at the time that AbuseFilter's consequences are invoked, which could be before or after ConfirmEdit's EditFilterMergedContent hook invocation, depending on how the wiki has decided to load the extensions What: - Define a flag for "shouldForceShowCaptcha", that other extensions can set on the SimpleCaptcha base class to indicate that ConfirmEdit must show a CAPTCHA (users with "skipcaptcha" right are still exempt) - Check the isCaptchaSolved() and shouldForShowCaptcha() flags in ::triggersCaptcha, and also check if ConfirmEdit's EditFilterMergedContent hook already ran - In CaptchaConsequence, set the forceShowCaptcha property on the SimpleCaptcha base class - [misc] Add getter/setter for the captchaSolved property and the other new class properties Depends-On: I7dd3a7c41606dcf5123518c2d3d0f4355f5edfd3 Bug: T20110 Change-Id: Idc47bdae8007da938f31e1c0f33e9be4813f41d7
220 lines
6.1 KiB
PHP
220 lines
6.1 KiB
PHP
<?php
|
|
|
|
use MediaWiki\Config\Config;
|
|
use MediaWiki\Context\RequestContext;
|
|
use MediaWiki\Extension\ConfirmEdit\CaptchaTriggers;
|
|
use MediaWiki\Extension\ConfirmEdit\SimpleCaptcha\SimpleCaptcha;
|
|
use MediaWiki\Request\WebRequest;
|
|
use MediaWiki\Title\Title;
|
|
use MediaWiki\User\User;
|
|
use Wikimedia\ScopedCallback;
|
|
use Wikimedia\TestingAccessWrapper;
|
|
|
|
/**
|
|
* @covers \MediaWiki\Extension\ConfirmEdit\SimpleCaptcha\SimpleCaptcha
|
|
*/
|
|
class CaptchaTest extends MediaWikiIntegrationTestCase {
|
|
|
|
/** @var ScopedCallback[] */
|
|
private $hold = [];
|
|
|
|
public function tearDown(): void {
|
|
// Destroy any ScopedCallbacks being held
|
|
$this->hold = [];
|
|
parent::tearDown();
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideSimpleTriggersCaptcha
|
|
*/
|
|
public function testTriggersCaptcha( $action, $expectedResult ) {
|
|
$captcha = new SimpleCaptcha();
|
|
$this->setMwGlobals( [
|
|
'wgCaptchaTriggers' => [
|
|
$action => $expectedResult,
|
|
]
|
|
] );
|
|
$this->assertEquals( $expectedResult, $captcha->triggersCaptcha( $action ) );
|
|
}
|
|
|
|
public static function provideSimpleTriggersCaptcha() {
|
|
$data = [];
|
|
$captchaTriggers = new ReflectionClass( CaptchaTriggers::class );
|
|
$constants = $captchaTriggers->getConstants();
|
|
foreach ( $constants as $const ) {
|
|
$data[] = [ $const, true ];
|
|
$data[] = [ $const, false ];
|
|
}
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideNamespaceOverwrites
|
|
*/
|
|
public function testNamespaceTriggersOverwrite( $trigger, $expected ) {
|
|
$captcha = new SimpleCaptcha();
|
|
$this->setMwGlobals( [
|
|
'wgCaptchaTriggers' => [
|
|
$trigger => !$expected,
|
|
],
|
|
'wgCaptchaTriggersOnNamespace' => [
|
|
0 => [
|
|
$trigger => $expected,
|
|
],
|
|
],
|
|
] );
|
|
$title = Title::newFromText( 'Main' );
|
|
$this->assertEquals( $expected, $captcha->triggersCaptcha( $trigger, $title ) );
|
|
}
|
|
|
|
public static function provideNamespaceOverwrites() {
|
|
return [
|
|
[ 'edit', true ],
|
|
[ 'edit', false ],
|
|
];
|
|
}
|
|
|
|
private function setCaptchaTriggersAttribute( $trigger, $value ) {
|
|
// Avoid clobbering captcha triggers registered by other extensions
|
|
$this->setMwGlobals( 'wgCaptchaTriggers', $GLOBALS['wgCaptchaTriggers'] );
|
|
|
|
$this->hold[] = ExtensionRegistry::getInstance()->setAttributeForTest(
|
|
'CaptchaTriggers', [ $trigger => $value ]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideAttributeSet
|
|
*/
|
|
public function testCaptchaTriggersAttributeSetTrue( $trigger, $value ) {
|
|
$this->setCaptchaTriggersAttribute( $trigger, $value );
|
|
$captcha = new SimpleCaptcha();
|
|
$this->assertEquals( $value, $captcha->triggersCaptcha( $trigger ) );
|
|
}
|
|
|
|
public static function provideAttributeSet() {
|
|
return [
|
|
[ 'test', true ],
|
|
[ 'test', false ],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideAttributeOverwritten
|
|
*/
|
|
public function testCaptchaTriggersAttributeGetsOverwritten( $trigger, $expected ) {
|
|
$this->setMwGlobals( 'wgCaptchaTriggers', [ $trigger => $expected ] );
|
|
$this->setCaptchaTriggersAttribute( $trigger, !$expected );
|
|
$captcha = new SimpleCaptcha();
|
|
$this->assertEquals( $expected, $captcha->triggersCaptcha( $trigger ) );
|
|
}
|
|
|
|
public static function provideAttributeOverwritten() {
|
|
return [
|
|
[ 'edit', true ],
|
|
[ 'edit', false ],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideCanSkipCaptchaUserright
|
|
*/
|
|
public function testCanSkipCaptchaUserright( $userIsAllowed, $expected ) {
|
|
$testObject = new SimpleCaptcha();
|
|
$user = $this->createMock( User::class );
|
|
$user->method( 'isAllowed' )->willReturn( $userIsAllowed );
|
|
|
|
$actual = $testObject->canSkipCaptcha( $user, RequestContext::getMain()->getConfig() );
|
|
|
|
$this->assertEquals( $expected, $actual );
|
|
}
|
|
|
|
public static function provideCanSkipCaptchaUserright() {
|
|
return [
|
|
[ true, true ],
|
|
[ false, false ]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideCanSkipCaptchaMailconfirmed
|
|
*/
|
|
public function testCanSkipCaptchaMailconfirmed( $allowUserConfirmEmail,
|
|
$userIsMailConfirmed, $expected ) {
|
|
$testObject = new SimpleCaptcha();
|
|
$user = $this->createMock( User::class );
|
|
$user->method( 'isEmailConfirmed' )->willReturn( $userIsMailConfirmed );
|
|
$config = $this->createMock( Config::class );
|
|
$config->method( 'get' )->willReturn( $allowUserConfirmEmail );
|
|
|
|
$actual = $testObject->canSkipCaptcha( $user, $config );
|
|
|
|
$this->assertEquals( $expected, $actual );
|
|
}
|
|
|
|
public static function provideCanSkipCaptchaMailconfirmed() {
|
|
return [
|
|
[ false, false, false ],
|
|
[ false, true, false ],
|
|
[ true, false, false ],
|
|
[ true, true, true ],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideCanSkipCaptchaIPWhitelisted
|
|
*/
|
|
public function testCanSkipCaptchaIPWhitelisted( $requestIP, $IPWhitelist, $expected ) {
|
|
$testObject = new SimpleCaptcha();
|
|
$config = $this->createMock( Config::class );
|
|
$request = $this->createMock( WebRequest::class );
|
|
$request->method( 'getIP' )->willReturn( $requestIP );
|
|
|
|
$this->setMwGlobals( [
|
|
'wgRequest' => $request,
|
|
'wgCaptchaWhitelistIP' => $IPWhitelist
|
|
] );
|
|
|
|
$actual = $testObject->canSkipCaptcha( RequestContext::getMain()->getUser(), $config );
|
|
|
|
$this->assertEquals( $expected, $actual );
|
|
}
|
|
|
|
public static function provideCanSkipCaptchaIPWhitelisted() {
|
|
return ( [
|
|
[ '127.0.0.1', [ '127.0.0.1', '127.0.0.2' ], true ],
|
|
[ '127.0.0.1', [], false ]
|
|
]
|
|
);
|
|
}
|
|
|
|
public function testTriggersCaptchaReturnsEarlyIfCaptchaSolved() {
|
|
$this->setMwGlobals( [
|
|
'wgCaptchaTriggers' => [
|
|
'edit' => true,
|
|
]
|
|
] );
|
|
$testObject = new SimpleCaptcha();
|
|
/** @var SimpleCaptcha|TestingAccessWrapper $wrapper */
|
|
$wrapper = TestingAccessWrapper::newFromObject( $testObject );
|
|
$wrapper->captchaSolved = true;
|
|
$this->assertFalse( $wrapper->triggersCaptcha( 'edit' ), 'CAPTCHA is not triggered if already solved' );
|
|
}
|
|
|
|
public function testForceShowCaptcha() {
|
|
$this->setMwGlobals( [
|
|
'wgCaptchaTriggers' => [
|
|
'edit' => false,
|
|
]
|
|
] );
|
|
$testObject = new SimpleCaptcha();
|
|
/** @var SimpleCaptcha|TestingAccessWrapper $wrapper */
|
|
$wrapper = TestingAccessWrapper::newFromObject( $testObject );
|
|
$this->assertFalse(
|
|
$wrapper->triggersCaptcha( 'edit' ), 'CAPTCHA is not triggered by edit action in this configuration'
|
|
);
|
|
$wrapper->setForceShowCaptcha( true );
|
|
$this->assertTrue( $wrapper->triggersCaptcha( 'edit' ), 'Force showing a CAPTCHA if flag is set' );
|
|
}
|
|
}
|