5use Wikimedia\TestingAccessWrapper;
14 $this->setMwGlobals( [
15 'wgCaptchaClass' => SimpleCaptcha::class,
16 'wgCaptchaBadLoginAttempts' => 1,
17 'wgCaptchaBadLoginPerUserAttempts' => 1,
18 'wgCaptchaStorageClass' => CaptchaHashStore::class,
19 'wgMainCacheType' => __METHOD__,
23 $services = \MediaWiki\MediaWikiServices::getInstance();
24 if ( method_exists( $services,
'getLocalClusterObjectCache' ) ) {
25 $this->setService(
'LocalClusterObjectCache',
new HashBagOStuff() );
34 TestingAccessWrapper::newFromClass( ConfirmEditHooks::class )->instanceCreated =
false;
41 $action, $username, $triggers, $needsCaptcha, $preTestCallback =
null
44 if ( $preTestCallback ) {
45 $fn = array_shift( $preTestCallback );
46 call_user_func_array( [ $this, $fn ], $preTestCallback );
50 $request = RequestContext::getMain()->getRequest();
51 $request->setCookie(
'UserName', $username );
54 $provider->setManager( AuthManager::singleton() );
55 $reqs = $provider->getAuthenticationRequests( $action, [
'username' => $username ] );
56 if ( $needsCaptcha ) {
57 $this->assertCount( 1, $reqs );
58 $this->assertInstanceOf( CaptchaAuthenticationRequest::class, $reqs[0] );
60 $this->assertEmpty( $reqs );
66 [ AuthManager::ACTION_LOGIN,
null, [], false ],
67 [ AuthManager::ACTION_LOGIN,
null, [
'badlogin' ], false ],
68 [ AuthManager::ACTION_LOGIN,
null, [
'badlogin' ],
true, [
'blockLogin',
'Foo' ] ],
69 [ AuthManager::ACTION_LOGIN,
null, [
'badloginperuser' ],
false, [
'blockLogin',
'Foo' ] ],
70 [ AuthManager::ACTION_LOGIN,
'Foo', [
'badloginperuser' ],
false, [
'blockLogin',
'Bar' ] ],
71 [ AuthManager::ACTION_LOGIN,
'Foo', [
'badloginperuser' ],
true, [
'blockLogin',
'Foo' ] ],
72 [ AuthManager::ACTION_LOGIN,
null, [
'badloginperuser' ],
true, [
'flagSession' ] ],
73 [ AuthManager::ACTION_CREATE,
null, [], false ],
74 [ AuthManager::ACTION_CREATE,
null, [
'createaccount' ],
true ],
75 [ AuthManager::ACTION_CREATE,
'UTSysop', [
'createaccount' ], false ],
76 [ AuthManager::ACTION_LINK,
null, [], false ],
77 [ AuthManager::ACTION_CHANGE,
null, [], false ],
78 [ AuthManager::ACTION_REMOVE,
null, [], false ],
86 $provider->setManager( AuthManager::singleton() );
88 $reqs = $provider->getAuthenticationRequests( AuthManager::ACTION_CREATE,
89 [
'username' =>
'Foo' ] );
91 $this->assertCount( 1, $reqs );
92 $this->assertInstanceOf( CaptchaAuthenticationRequest::class, $reqs[0] );
94 $id = $reqs[0]->captchaId;
95 $data = TestingAccessWrapper::newFromObject( $reqs[0] )->captchaData;
96 $this->assertEquals( $captcha->retrieveCaptcha( $id ), $data + [
'index' => $id ] );
103 $isBadLoginPerUserTriggered, $result
105 $this->
setMwHook(
'PingLimiter',
function ( $user, $action, &$result ) {
110 $captcha = $this->getMock( SimpleCaptcha::class,
111 [
'isBadLoginTriggered',
'isBadLoginPerUserTriggered' ] );
112 $captcha->expects( $this->any() )->method(
'isBadLoginTriggered' )
113 ->willReturn( $isBadLoginTriggered );
114 $captcha->expects( $this->any() )->method(
'isBadLoginPerUserTriggered' )
115 ->willReturn( $isBadLoginPerUserTriggered );
116 $this->setMwGlobals(
'wgCaptcha', $captcha );
117 TestingAccessWrapper::newFromClass( ConfirmEditHooks::class )->instanceCreated =
true;
119 $provider->setManager( AuthManager::singleton() );
121 $status = $provider->testForAuthentication( $req ? [ $req ] : [] );
122 $this->assertEquals( $result, $status->isGood() );
131 'badlogin' => [
$fallback,
true,
false, false ],
132 'badloginperuser, no username' => [
null,
false,
true,
true ],
133 'badloginperuser' => [
$fallback,
false,
true, false ],
134 'non-existent captcha' => [ $this->
getCaptchaRequest(
'123',
'4' ),
true,
true, false ],
135 'wrong captcha' => [ $this->
getCaptchaRequest(
'345',
'6' ),
true,
true, false ],
144 $this->
setMwHook(
'PingLimiter',
function ( &$user, $action, &$result ) {
148 $this->
setTriggers( $disableTrigger ? [] : [
'createaccount' ] );
150 $user = User::newFromName(
'Foo' );
152 $provider->setManager( AuthManager::singleton() );
154 $status = $provider->testForAccountCreation( $user, $creator, $req ? [ $req ] : [] );
155 $this->assertEquals( $result, $status->isGood() );
159 $user = User::newFromName(
'Bar' );
160 $sysop = User::newFromName(
'UTSysop' );
163 'no captcha' => [
null, $user, false ],
164 'non-existent captcha' => [ $this->
getCaptchaRequest(
'123',
'4' ), $user, false ],
167 'user is exempt' => [
null, $sysop,
true ],
168 'disabled' => [
null, $user,
true,
'disable' ],
173 $this->
setTriggers( [
'badlogin',
'badloginperuser' ] );
175 $user = User::newFromName(
'Foo' );
176 $anotherUser = User::newFromName(
'Bar' );
178 $provider->setManager( AuthManager::singleton() );
180 $this->assertFalse( $captcha->isBadLoginTriggered() );
181 $this->assertFalse( $captcha->isBadLoginPerUserTriggered( $user ) );
183 $provider->postAuthentication( $user, \
MediaWiki\Auth\AuthenticationResponse::newFail(
186 $this->assertTrue( $captcha->isBadLoginTriggered() );
187 $this->assertTrue( $captcha->isBadLoginPerUserTriggered( $user ) );
188 $this->assertFalse( $captcha->isBadLoginPerUserTriggered( $anotherUser ) );
190 $provider->postAuthentication( $user, \
MediaWiki\Auth\AuthenticationResponse::newPass(
'Foo' ) );
192 $this->assertFalse( $captcha->isBadLoginPerUserTriggered( $user ) );
198 $user = User::newFromName(
'Foo' );
200 $provider->setManager( AuthManager::singleton() );
202 $this->assertFalse( $captcha->isBadLoginTriggered() );
203 $this->assertFalse( $captcha->isBadLoginPerUserTriggered( $user ) );
205 $provider->postAuthentication( $user, \
MediaWiki\Auth\AuthenticationResponse::newFail(
208 $this->assertFalse( $captcha->isBadLoginTriggered() );
209 $this->assertFalse( $captcha->isBadLoginPerUserTriggered( $user ) );
216 $this->mergeMwGlobalArrayValue(
225 $provider->setManager( AuthManager::singleton() );
226 $providerAccess = TestingAccessWrapper::newFromObject( $provider );
228 foreach ( $attempts as $attempt ) {
229 if ( !empty( $attempts[3] ) ) {
230 $this->
setMwHook(
'PingLimiter',
function ( &$user, $action, &$result ) {
235 $this->
setMwHook(
'PingLimiter',
function () {
241 $success = $providerAccess->verifyCaptcha( $captcha, [ $attempts[0] ], $attempts[1] );
242 $this->assertEquals( $attempts[2],
$success );
247 $sysop = User::newFromName(
'UTSysop' );
262 'pinglimiter disabled' => [
271 $req->captchaWord = $word;
272 $req->username = $username;
278 $captcha->increaseBadLoginCounter( $username );
282 RequestContext::getMain()->getRequest()->getSession()
283 ->set(
'ConfirmEdit:loginCaptchaPerUserTriggered',
true );
287 $types = [
'edit',
'create',
'sendemail',
'addurl',
'createaccount',
'badlogin',
289 $captchaTriggers = array_combine( $types, array_map(
function (
$type ) use ( $triggers ) {
290 return in_array(
$type, $triggers,
true );
292 $this->setMwGlobals(
'wgCaptchaTriggers', $captchaTriggers );
301 protected function setMwHook( $hook, callable $callback ) {
302 $this->mergeMwGlobalArrayValue(
'wgHooks', [ $hook => $callback ] );
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Generic captcha authentication request class.
@covers CaptchaPreAuthenticationProvider @group Database
testTestForAuthentication( $req, $isBadLoginTriggered, $isBadLoginPerUserTriggered, $result)
@dataProvider provideTestForAuthentication
provideTestForAuthentication()
testTestForAccountCreation( $req, $creator, $result, $disableTrigger=false)
@dataProvider provideTestForAccountCreation
getCaptchaRequest( $id, $word, $username=null)
testGetAuthenticationRequests_store()
setMwHook( $hook, callable $callback)
Set a $wgHooks handler for a given hook and remove all other handlers (though not ones set via Hooks:...
provideGetAuthenticationRequests()
testPostAuthentication_disabled()
provideTestForAccountCreation()
testPingLimiter(array $attempts)
@dataProvider providePingLimiter
testGetAuthenticationRequests( $action, $username, $triggers, $needsCaptcha, $preTestCallback=null)
@dataProvider provideGetAuthenticationRequests
static unsetInstanceForTests()
static get()
Get somewhere to store captcha data that will persist between requests.
Simple store for keeping values in an associative array for the current process.
Demo CAPTCHA (not for production usage) and base class for real CAPTCHAs.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...