1<?php 2 3namespace MediaWiki\Auth; 4 5use HashConfig; 6use MediaWiki\Tests\Unit\Auth\AuthenticationProviderTestTrait; 7use MediaWikiIntegrationTestCase; 8use stdClass; 9use TestLogger; 10use Wikimedia\TestingAccessWrapper; 11 12/** 13 * @group AuthManager 14 * @group Database 15 * @covers \MediaWiki\Auth\ThrottlePreAuthenticationProvider 16 */ 17class ThrottlePreAuthenticationProviderTest extends MediaWikiIntegrationTestCase { 18 use AuthenticationProviderTestTrait; 19 20 public function testConstructor() { 21 $provider = new ThrottlePreAuthenticationProvider(); 22 $providerPriv = TestingAccessWrapper::newFromObject( $provider ); 23 $config = new HashConfig( [ 24 'AccountCreationThrottle' => [ [ 25 'count' => 123, 26 'seconds' => 86400, 27 ] ], 28 'PasswordAttemptThrottle' => [ [ 29 'count' => 5, 30 'seconds' => 300, 31 ] ], 32 ] ); 33 $this->initProvider( $provider, $config ); 34 $this->assertSame( [ 35 'accountCreationThrottle' => [ [ 'count' => 123, 'seconds' => 86400 ] ], 36 'passwordAttemptThrottle' => [ [ 'count' => 5, 'seconds' => 300 ] ] 37 ], $providerPriv->throttleSettings ); 38 $accountCreationThrottle = TestingAccessWrapper::newFromObject( 39 $providerPriv->accountCreationThrottle ); 40 $this->assertSame( [ [ 'count' => 123, 'seconds' => 86400 ] ], 41 $accountCreationThrottle->conditions ); 42 $passwordAttemptThrottle = TestingAccessWrapper::newFromObject( 43 $providerPriv->passwordAttemptThrottle ); 44 $this->assertSame( [ [ 'count' => 5, 'seconds' => 300 ] ], 45 $passwordAttemptThrottle->conditions ); 46 47 $provider = new ThrottlePreAuthenticationProvider( [ 48 'accountCreationThrottle' => [ [ 'count' => 43, 'seconds' => 10000 ] ], 49 'passwordAttemptThrottle' => [ [ 'count' => 11, 'seconds' => 100 ] ], 50 ] ); 51 $providerPriv = TestingAccessWrapper::newFromObject( $provider ); 52 $config = new HashConfig( [ 53 'AccountCreationThrottle' => [ [ 54 'count' => 123, 55 'seconds' => 86400, 56 ] ], 57 'PasswordAttemptThrottle' => [ [ 58 'count' => 5, 59 'seconds' => 300, 60 ] ], 61 ] ); 62 $this->initProvider( $provider, $config ); 63 $this->assertSame( [ 64 'accountCreationThrottle' => [ [ 'count' => 43, 'seconds' => 10000 ] ], 65 'passwordAttemptThrottle' => [ [ 'count' => 11, 'seconds' => 100 ] ], 66 ], $providerPriv->throttleSettings ); 67 68 $cache = new \HashBagOStuff(); 69 $provider = new ThrottlePreAuthenticationProvider( [ 'cache' => $cache ] ); 70 $providerPriv = TestingAccessWrapper::newFromObject( $provider ); 71 $config = new HashConfig( [ 72 'AccountCreationThrottle' => [ [ 'count' => 1, 'seconds' => 1 ] ], 73 'PasswordAttemptThrottle' => [ [ 'count' => 1, 'seconds' => 1 ] ], 74 ] ); 75 $this->initProvider( $provider, $config ); 76 $accountCreationThrottle = TestingAccessWrapper::newFromObject( 77 $providerPriv->accountCreationThrottle ); 78 $this->assertSame( $cache, $accountCreationThrottle->cache ); 79 $passwordAttemptThrottle = TestingAccessWrapper::newFromObject( 80 $providerPriv->passwordAttemptThrottle ); 81 $this->assertSame( $cache, $passwordAttemptThrottle->cache ); 82 } 83 84 public function testDisabled() { 85 $provider = new ThrottlePreAuthenticationProvider( [ 86 'accountCreationThrottle' => [], 87 'passwordAttemptThrottle' => [], 88 'cache' => new \HashBagOStuff(), 89 ] ); 90 $this->initProvider( 91 $provider, 92 new HashConfig( [ 93 'AccountCreationThrottle' => null, 94 'PasswordAttemptThrottle' => null, 95 ] ), 96 null, 97 $this->getServiceContainer()->getAuthManager() 98 ); 99 100 $this->assertEquals( 101 \StatusValue::newGood(), 102 $provider->testForAccountCreation( 103 \User::newFromName( 'Created' ), 104 \User::newFromName( 'Creator' ), 105 [] 106 ) 107 ); 108 $this->assertEquals( 109 \StatusValue::newGood(), 110 $provider->testForAuthentication( [] ) 111 ); 112 } 113 114 /** 115 * @dataProvider provideTestForAccountCreation 116 * @param string $creatorname 117 * @param bool $succeed 118 * @param bool $hook 119 */ 120 public function testTestForAccountCreation( $creatorname, $succeed, $hook ) { 121 $provider = new ThrottlePreAuthenticationProvider( [ 122 'accountCreationThrottle' => [ [ 'count' => 2, 'seconds' => 86400 ] ], 123 'cache' => new \HashBagOStuff(), 124 ] ); 125 $this->initProvider( 126 $provider, 127 new HashConfig( [ 128 'AccountCreationThrottle' => null, 129 'PasswordAttemptThrottle' => null, 130 ] ), 131 null, 132 $this->getServiceContainer()->getAuthManager(), 133 $this->getServiceContainer()->getHookContainer() 134 ); 135 136 $user = \User::newFromName( 'RandomUser' ); 137 $creator = \User::newFromName( $creatorname ); 138 if ( $hook ) { 139 $mock = $this->getMockBuilder( stdClass::class ) 140 ->addMethods( [ 'onExemptFromAccountCreationThrottle' ] ) 141 ->getMock(); 142 $mock->method( 'onExemptFromAccountCreationThrottle' ) 143 ->willReturn( false ); 144 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 145 'ExemptFromAccountCreationThrottle' => [ $mock ], 146 ] ); 147 } 148 149 $this->assertTrue( 150 151 $provider->testForAccountCreation( $user, $creator, [] )->isOK(), 152 'attempt #1' 153 ); 154 $this->assertTrue( 155 156 $provider->testForAccountCreation( $user, $creator, [] )->isOK(), 157 'attempt #2' 158 ); 159 $this->assertEquals( 160 $succeed ? true : false, 161 $provider->testForAccountCreation( $user, $creator, [] )->isOK(), 162 'attempt #3' 163 ); 164 } 165 166 public static function provideTestForAccountCreation() { 167 return [ 168 'Normal user' => [ 'NormalUser', false, false ], 169 'Sysop' => [ 'UTSysop', true, false ], 170 'Normal user with hook' => [ 'NormalUser', true, true ], 171 ]; 172 } 173 174 public function testTestForAuthentication() { 175 $provider = new ThrottlePreAuthenticationProvider( [ 176 'passwordAttemptThrottle' => [ [ 'count' => 2, 'seconds' => 86400 ] ], 177 'cache' => new \HashBagOStuff(), 178 ] ); 179 $this->initProvider( 180 $provider, 181 new HashConfig( [ 182 'AccountCreationThrottle' => null, 183 'PasswordAttemptThrottle' => null, 184 ] ), 185 null, 186 $this->getServiceContainer()->getAuthManager() 187 ); 188 189 $req = new UsernameAuthenticationRequest; 190 $req->username = 'SomeUser'; 191 for ( $i = 1; $i <= 3; $i++ ) { 192 $status = $provider->testForAuthentication( [ $req ] ); 193 $this->assertEquals( $i < 3, $status->isGood(), "attempt #$i" ); 194 } 195 $this->assertCount( 1, $status->getErrors() ); 196 $msg = new \Message( $status->getErrors()[0]['message'], $status->getErrors()[0]['params'] ); 197 $this->assertEquals( 'login-throttled', $msg->getKey() ); 198 199 $provider->postAuthentication( \User::newFromName( 'SomeUser' ), 200 AuthenticationResponse::newFail( wfMessage( 'foo' ) ) ); 201 $this->assertFalse( $provider->testForAuthentication( [ $req ] )->isGood(), 'after FAIL' ); 202 203 $provider->postAuthentication( \User::newFromName( 'SomeUser' ), 204 AuthenticationResponse::newPass() ); 205 $this->assertTrue( $provider->testForAuthentication( [ $req ] )->isGood(), 'after PASS' ); 206 207 $req1 = new UsernameAuthenticationRequest; 208 $req1->username = 'foo'; 209 $req2 = new UsernameAuthenticationRequest; 210 $req2->username = 'bar'; 211 $this->assertTrue( $provider->testForAuthentication( [ $req1, $req2 ] )->isGood() ); 212 213 $req = new UsernameAuthenticationRequest; 214 $req->username = 'Some user'; 215 $provider->testForAuthentication( [ $req ] ); 216 $req->username = 'Some_user'; 217 $provider->testForAuthentication( [ $req ] ); 218 $req->username = 'some user'; 219 $status = $provider->testForAuthentication( [ $req ] ); 220 $this->assertFalse( $status->isGood(), 'denormalized usernames are normalized' ); 221 } 222 223 public function testPostAuthentication() { 224 $provider = new ThrottlePreAuthenticationProvider( [ 225 'passwordAttemptThrottle' => [], 226 'cache' => new \HashBagOStuff(), 227 ] ); 228 $this->initProvider( 229 $provider, 230 new HashConfig( [ 231 'AccountCreationThrottle' => null, 232 'PasswordAttemptThrottle' => null, 233 ] ), 234 null, 235 $this->getServiceContainer()->getAuthManager() 236 ); 237 $provider->postAuthentication( \User::newFromName( 'SomeUser' ), 238 AuthenticationResponse::newPass() ); 239 240 $provider = new ThrottlePreAuthenticationProvider( [ 241 'passwordAttemptThrottle' => [ [ 'count' => 2, 'seconds' => 86400 ] ], 242 'cache' => new \HashBagOStuff(), 243 ] ); 244 $logger = new TestLogger( true ); 245 $this->initProvider( 246 $provider, 247 new HashConfig( [ 248 'AccountCreationThrottle' => null, 249 'PasswordAttemptThrottle' => null, 250 ] ), 251 $logger, 252 $this->getServiceContainer()->getAuthManager() 253 ); 254 $provider->postAuthentication( \User::newFromName( 'SomeUser' ), 255 AuthenticationResponse::newPass() ); 256 $this->assertSame( [ 257 [ \Psr\Log\LogLevel::INFO, 'throttler data not found for {user}' ], 258 ], $logger->getBuffer() ); 259 } 260} 261