3 namespace MediaWiki\Auth
;
8 * @covers MediaWiki\Auth\LegacyHookPreAuthenticationProvider
10 class LegacyHookPreAuthenticationProviderTest
extends \MediaWikiTestCase
{
11 protected function setUp() {
12 global $wgDisableAuthManager;
15 if ( $wgDisableAuthManager ) {
16 $this->markTestSkipped( '$wgDisableAuthManager is set' );
21 * Get an instance of the provider
22 * @return LegacyHookPreAuthenticationProvider
24 protected function getProvider() {
25 $request = $this->getMock( 'FauxRequest', [ 'getIP' ] );
26 $request->expects( $this->any() )->method( 'getIP' )->will( $this->returnValue( '127.0.0.42' ) );
28 $manager = new AuthManager(
29 $request, \ConfigFactory
::getDefaultInstance()->makeConfig( 'main' )
32 $provider = new LegacyHookPreAuthenticationProvider();
33 $provider->setManager( $manager );
34 $provider->setLogger( new \Psr\Log\
NullLogger() );
35 $provider->setConfig( new \
HashConfig( [
36 'PasswordAttemptThrottle' => [ 'count' => 23, 'seconds' => 42 ],
42 * Sets a mock on a hook
44 * @param object $expect From $this->once(), $this->never(), etc.
45 * @return object $mock->expects( $expect )->method( ... ).
47 protected function hook( $hook, $expect ) {
48 $mock = $this->getMock( __CLASS__
, [ "on$hook" ] );
49 $this->mergeMwGlobalArrayValue( 'wgHooks', [
52 return $mock->expects( $expect )->method( "on$hook" );
59 protected function unhook( $hook ) {
60 $this->mergeMwGlobalArrayValue( 'wgHooks', [
65 // Stubs for hooks taking reference parameters
66 public function onLoginUserMigrated( $user, &$msg ) {
68 public function onAbortLogin( $user, $password, &$abort, &$msg ) {
70 public function onAbortNewAccount( $user, &$abortError, &$abortStatus ) {
72 public function onAbortAutoAccount( $user, &$abortError ) {
76 * @dataProvider provideTestForAuthentication
77 * @param string|null $username
78 * @param string|null $password
79 * @param string|null $msgForLoginUserMigrated
80 * @param int|null $abortForAbortLogin
81 * @param string|null $msgForAbortLogin
82 * @param string|null $failMsg
83 * @param array $failParams
85 public function testTestForAuthentication(
87 $msgForLoginUserMigrated, $abortForAbortLogin, $msgForAbortLogin,
88 $failMsg, $failParams = []
91 if ( $username === null ) {
92 $this->hook( 'LoginUserMigrated', $this->never() );
93 $this->hook( 'AbortLogin', $this->never() );
95 if ( $password === null ) {
96 $req = $this->getMockForAbstractClass( AuthenticationRequest
::class );
98 $req = new PasswordAuthenticationRequest();
99 $req->action
= AuthManager
::ACTION_LOGIN
;
100 $req->password
= $password;
102 $req->username
= $username;
103 $reqs[get_class( $req )] = $req;
105 $h = $this->hook( 'LoginUserMigrated', $this->once() );
106 if ( $msgForLoginUserMigrated !== null ) {
107 $h->will( $this->returnCallback(
108 function ( $user, &$msg ) use ( $username, $msgForLoginUserMigrated ) {
109 $this->assertInstanceOf( 'User', $user );
110 $this->assertSame( $username, $user->getName() );
111 $msg = $msgForLoginUserMigrated;
115 $this->hook( 'AbortLogin', $this->never() );
117 $h->will( $this->returnCallback(
118 function ( $user, &$msg ) use ( $username ) {
119 $this->assertInstanceOf( 'User', $user );
120 $this->assertSame( $username, $user->getName() );
124 $h2 = $this->hook( 'AbortLogin', $this->once() );
125 if ( $abortForAbortLogin !== null ) {
126 $h2->will( $this->returnCallback(
127 function ( $user, $pass, &$abort, &$msg )
128 use ( $username, $password, $abortForAbortLogin, $msgForAbortLogin )
130 $this->assertInstanceOf( 'User', $user );
131 $this->assertSame( $username, $user->getName() );
132 if ( $password !== null ) {
133 $this->assertSame( $password, $pass );
135 $this->assertInternalType( 'string', $pass );
137 $abort = $abortForAbortLogin;
138 $msg = $msgForAbortLogin;
143 $h2->will( $this->returnCallback(
144 function ( $user, $pass, &$abort, &$msg ) use ( $username, $password ) {
145 $this->assertInstanceOf( 'User', $user );
146 $this->assertSame( $username, $user->getName() );
147 if ( $password !== null ) {
148 $this->assertSame( $password, $pass );
150 $this->assertInternalType( 'string', $pass );
160 $status = $this->getProvider()->testForAuthentication( $reqs );
162 $this->unhook( 'LoginUserMigrated' );
163 $this->unhook( 'AbortLogin' );
165 if ( $failMsg === null ) {
166 $this->assertEquals( \StatusValue
::newGood(), $status, 'should succeed' );
168 $this->assertInstanceOf( 'StatusValue', $status, 'should fail (type)' );
169 $this->assertFalse( $status->isOk(), 'should fail (ok)' );
170 $errors = $status->getErrors();
171 $this->assertEquals( $failMsg, $errors[0]['message'], 'should fail (message)' );
172 $this->assertEquals( $failParams, $errors[0]['params'], 'should fail (params)' );
176 public static function provideTestForAuthentication() {
178 'No valid requests' => [
179 null, null, null, null, null, null
181 'No hook errors' => [
182 'User', 'PaSsWoRd', null, null, null, null
184 'No hook errors, no password' => [
185 'User', null, null, null, null, null
187 'LoginUserMigrated no message' => [
188 'User', 'PaSsWoRd', false, null, null, 'login-migrated-generic'
190 'LoginUserMigrated with message' => [
191 'User', 'PaSsWoRd', 'LUM-abort', null, null, 'LUM-abort'
193 'LoginUserMigrated with message and params' => [
194 'User', 'PaSsWoRd', [ 'LUM-abort', 'foo' ], null, null, 'LUM-abort', [ 'foo' ]
196 'AbortLogin, SUCCESS' => [
197 'User', 'PaSsWoRd', null, \LoginForm
::SUCCESS
, null, null
199 'AbortLogin, NEED_TOKEN, no message' => [
200 'User', 'PaSsWoRd', null, \LoginForm
::NEED_TOKEN
, null, 'nocookiesforlogin'
202 'AbortLogin, NEED_TOKEN, with message' => [
203 'User', 'PaSsWoRd', null, \LoginForm
::NEED_TOKEN
, 'needtoken', 'needtoken'
205 'AbortLogin, WRONG_TOKEN, no message' => [
206 'User', 'PaSsWoRd', null, \LoginForm
::WRONG_TOKEN
, null, 'sessionfailure'
208 'AbortLogin, WRONG_TOKEN, with message' => [
209 'User', 'PaSsWoRd', null, \LoginForm
::WRONG_TOKEN
, 'wrongtoken', 'wrongtoken'
211 'AbortLogin, ILLEGAL, no message' => [
212 'User', 'PaSsWoRd', null, \LoginForm
::ILLEGAL
, null, 'noname'
214 'AbortLogin, ILLEGAL, with message' => [
215 'User', 'PaSsWoRd', null, \LoginForm
::ILLEGAL
, 'badname', 'badname'
217 'AbortLogin, NO_NAME, no message' => [
218 'User', 'PaSsWoRd', null, \LoginForm
::NO_NAME
, null, 'noname'
220 'AbortLogin, NO_NAME, with message' => [
221 'User', 'PaSsWoRd', null, \LoginForm
::NO_NAME
, 'badname', 'badname'
223 'AbortLogin, WRONG_PASS, no message' => [
224 'User', 'PaSsWoRd', null, \LoginForm
::WRONG_PASS
, null, 'wrongpassword'
226 'AbortLogin, WRONG_PASS, with message' => [
227 'User', 'PaSsWoRd', null, \LoginForm
::WRONG_PASS
, 'badpass', 'badpass'
229 'AbortLogin, WRONG_PLUGIN_PASS, no message' => [
230 'User', 'PaSsWoRd', null, \LoginForm
::WRONG_PLUGIN_PASS
, null, 'wrongpassword'
232 'AbortLogin, WRONG_PLUGIN_PASS, with message' => [
233 'User', 'PaSsWoRd', null, \LoginForm
::WRONG_PLUGIN_PASS
, 'badpass', 'badpass'
235 'AbortLogin, NOT_EXISTS, no message' => [
236 "User'", 'A', null, \LoginForm
::NOT_EXISTS
, null, 'nosuchusershort', [ 'User'' ]
238 'AbortLogin, NOT_EXISTS, with message' => [
239 "User'", 'A', null, \LoginForm
::NOT_EXISTS
, 'badname', 'badname', [ 'User'' ]
241 'AbortLogin, EMPTY_PASS, no message' => [
242 'User', 'PaSsWoRd', null, \LoginForm
::EMPTY_PASS
, null, 'wrongpasswordempty'
244 'AbortLogin, EMPTY_PASS, with message' => [
245 'User', 'PaSsWoRd', null, \LoginForm
::EMPTY_PASS
, 'badpass', 'badpass'
247 'AbortLogin, RESET_PASS, no message' => [
248 'User', 'PaSsWoRd', null, \LoginForm
::RESET_PASS
, null, 'resetpass_announce'
250 'AbortLogin, RESET_PASS, with message' => [
251 'User', 'PaSsWoRd', null, \LoginForm
::RESET_PASS
, 'resetpass', 'resetpass'
253 'AbortLogin, THROTTLED, no message' => [
254 'User', 'PaSsWoRd', null, \LoginForm
::THROTTLED
, null, 'login-throttled',
255 [ \Message
::durationParam( 42 ) ]
257 'AbortLogin, THROTTLED, with message' => [
258 'User', 'PaSsWoRd', null, \LoginForm
::THROTTLED
, 't', 't',
259 [ \Message
::durationParam( 42 ) ]
261 'AbortLogin, USER_BLOCKED, no message' => [
262 "User'", 'P', null, \LoginForm
::USER_BLOCKED
, null, 'login-userblocked', [ 'User'' ]
264 'AbortLogin, USER_BLOCKED, with message' => [
265 "User'", 'P', null, \LoginForm
::USER_BLOCKED
, 'blocked', 'blocked', [ 'User'' ]
267 'AbortLogin, ABORTED, no message' => [
268 "User'", 'P', null, \LoginForm
::ABORTED
, null, 'login-abort-generic', [ 'User'' ]
270 'AbortLogin, ABORTED, with message' => [
271 "User'", 'P', null, \LoginForm
::ABORTED
, 'aborted', 'aborted', [ 'User'' ]
273 'AbortLogin, USER_MIGRATED, no message' => [
274 'User', 'P', null, \LoginForm
::USER_MIGRATED
, null, 'login-migrated-generic'
276 'AbortLogin, USER_MIGRATED, with message' => [
277 'User', 'P', null, \LoginForm
::USER_MIGRATED
, 'migrated', 'migrated'
279 'AbortLogin, USER_MIGRATED, with message and params' => [
280 'User', 'P', null, \LoginForm
::USER_MIGRATED
, [ 'migrated', 'foo' ],
281 'migrated', [ 'foo' ]
287 * @dataProvider provideTestForAccountCreation
289 * @param Status|null $status
290 * @param StatusValue Result
292 public function testTestForAccountCreation( $msg, $status, $result ) {
293 $this->hook( 'AbortNewAccount', $this->once() )
294 ->will( $this->returnCallback( function ( $user, &$error, &$abortStatus )
295 use ( $msg, $status )
297 $this->assertInstanceOf( 'User', $user );
298 $this->assertSame( 'User', $user->getName() );
300 $abortStatus = $status;
301 return $error === null && $status === null;
304 $user = \User
::newFromName( 'User' );
305 $creator = \User
::newFromName( 'UTSysop' );
306 $ret = $this->getProvider()->testForAccountCreation( $user, $creator, [] );
308 $this->unhook( 'AbortNewAccount' );
310 $this->assertEquals( $result, $ret );
313 public static function provideTestForAccountCreation() {
315 'No hook errors' => [
316 null, null, \StatusValue
::newGood()
318 'AbortNewAccount, old style' => [
319 'foobar', null, \StatusValue
::newFatal(
320 \Message
::newFromKey( 'createaccount-hook-aborted' )->rawParams( 'foobar' )
323 'AbortNewAccount, new style' => [
325 \Status
::newFatal( 'aborted!', 'param' ),
326 \StatusValue
::newFatal( 'aborted!', 'param' )
332 * @dataProvider provideTestUserForCreation
333 * @param string|null $error
334 * @param string|null $failMsg
336 public function testTestUserForCreation( $error, $failMsg ) {
337 $this->hook( 'AbortNewAccount', $this->never() );
338 $this->hook( 'AbortAutoAccount', $this->once() )
339 ->will( $this->returnCallback( function ( $user, &$abortError ) use ( $error ) {
340 $this->assertInstanceOf( 'User', $user );
341 $this->assertSame( 'UTSysop', $user->getName() );
342 $abortError = $error;
343 return $error === null;
346 $status = $this->getProvider()->testUserForCreation(
347 \User
::newFromName( 'UTSysop' ), AuthManager
::AUTOCREATE_SOURCE_SESSION
350 $this->unhook( 'AbortNewAccount' );
351 $this->unhook( 'AbortAutoAccount' );
353 if ( $failMsg === null ) {
354 $this->assertEquals( \StatusValue
::newGood(), $status, 'should succeed' );
356 $this->assertInstanceOf( 'StatusValue', $status, 'should fail (type)' );
357 $this->assertFalse( $status->isOk(), 'should fail (ok)' );
358 $errors = $status->getErrors();
359 $this->assertEquals( $failMsg, $errors[0]['message'], 'should fail (message)' );
362 $this->hook( 'AbortAutoAccount', $this->never() );
363 $this->hook( 'AbortNewAccount', $this->once() )
364 ->will( $this->returnCallback(
365 function ( $user, &$abortError, &$abortStatus ) use ( $error ) {
366 $this->assertInstanceOf( 'User', $user );
367 $this->assertSame( 'UTSysop', $user->getName() );
368 $abortError = $error;
369 return $error === null;
372 $status = $this->getProvider()->testUserForCreation( \User
::newFromName( 'UTSysop' ), false );
373 $this->unhook( 'AbortNewAccount' );
374 $this->unhook( 'AbortAutoAccount' );
375 if ( $failMsg === null ) {
376 $this->assertEquals( \StatusValue
::newGood(), $status, 'should succeed' );
378 $this->assertInstanceOf( 'StatusValue', $status, 'should fail (type)' );
379 $this->assertFalse( $status->isOk(), 'should fail (ok)' );
380 $errors = $status->getErrors();
381 $msg = $errors[0]['message'];
382 $this->assertInstanceOf( \Message
::class, $msg );
384 'createaccount-hook-aborted', $msg->getKey(), 'should fail (message)'
388 if ( $error !== false ) {
389 $this->hook( 'AbortAutoAccount', $this->never() );
390 $this->hook( 'AbortNewAccount', $this->once() )
391 ->will( $this->returnCallback(
392 function ( $user, &$abortError, &$abortStatus ) use ( $error ) {
393 $this->assertInstanceOf( 'User', $user );
394 $this->assertSame( 'UTSysop', $user->getName() );
395 $abortStatus = $error ? \Status
::newFatal( $error ) : \Status
::newGood();
396 return $error === null;
399 $status = $this->getProvider()->testUserForCreation( \User
::newFromName( 'UTSysop' ), false );
400 $this->unhook( 'AbortNewAccount' );
401 $this->unhook( 'AbortAutoAccount' );
402 if ( $failMsg === null ) {
403 $this->assertEquals( \StatusValue
::newGood(), $status, 'should succeed' );
405 $this->assertInstanceOf( 'StatusValue', $status, 'should fail (type)' );
406 $this->assertFalse( $status->isOk(), 'should fail (ok)' );
407 $errors = $status->getErrors();
408 $this->assertEquals( $failMsg, $errors[0]['message'], 'should fail (message)' );
413 public static function provideTestUserForCreation() {
415 'Success' => [ null, null ],
416 'Fail, no message' => [ false, 'login-abort-generic' ],
417 'Fail, with message' => [ 'fail', 'fail' ],