3 namespace MediaWiki\Auth
;
5 use MediaWiki\MediaWikiServices
;
10 * @covers MediaWiki\Auth\LegacyHookPreAuthenticationProvider
12 class LegacyHookPreAuthenticationProviderTest
extends \MediaWikiTestCase
{
14 * Get an instance of the provider
15 * @return LegacyHookPreAuthenticationProvider
17 protected function getProvider() {
18 $request = $this->getMockBuilder( 'FauxRequest' )
19 ->setMethods( [ 'getIP' ] )->getMock();
20 $request->expects( $this->any() )->method( 'getIP' )->will( $this->returnValue( '127.0.0.42' ) );
22 $manager = new AuthManager(
24 MediaWikiServices
::getInstance()->getMainConfig()
27 $provider = new LegacyHookPreAuthenticationProvider();
28 $provider->setManager( $manager );
29 $provider->setLogger( new \Psr\Log\
NullLogger() );
30 $provider->setConfig( new \
HashConfig( [
31 'PasswordAttemptThrottle' => [ 'count' => 23, 'seconds' => 42 ],
37 * Sets a mock on a hook
39 * @param object $expect From $this->once(), $this->never(), etc.
40 * @return object $mock->expects( $expect )->method( ... ).
42 protected function hook( $hook, $expect ) {
43 $mock = $this->getMockBuilder( __CLASS__
)->setMethods( [ "on$hook" ] )->getMock();
44 $this->mergeMwGlobalArrayValue( 'wgHooks', [
47 return $mock->expects( $expect )->method( "on$hook" );
54 protected function unhook( $hook ) {
55 $this->mergeMwGlobalArrayValue( 'wgHooks', [
60 // Stubs for hooks taking reference parameters
61 public function onLoginUserMigrated( $user, &$msg ) {
63 public function onAbortLogin( $user, $password, &$abort, &$msg ) {
65 public function onAbortNewAccount( $user, &$abortError, &$abortStatus ) {
67 public function onAbortAutoAccount( $user, &$abortError ) {
71 * @dataProvider provideTestForAuthentication
72 * @param string|null $username
73 * @param string|null $password
74 * @param string|null $msgForLoginUserMigrated
75 * @param int|null $abortForAbortLogin
76 * @param string|null $msgForAbortLogin
77 * @param string|null $failMsg
78 * @param array $failParams
80 public function testTestForAuthentication(
82 $msgForLoginUserMigrated, $abortForAbortLogin, $msgForAbortLogin,
83 $failMsg, $failParams = []
86 if ( $username === null ) {
87 $this->hook( 'LoginUserMigrated', $this->never() );
88 $this->hook( 'AbortLogin', $this->never() );
90 if ( $password === null ) {
91 $req = $this->getMockForAbstractClass( AuthenticationRequest
::class );
93 $req = new PasswordAuthenticationRequest();
94 $req->action
= AuthManager
::ACTION_LOGIN
;
95 $req->password
= $password;
97 $req->username
= $username;
98 $reqs[get_class( $req )] = $req;
100 $h = $this->hook( 'LoginUserMigrated', $this->once() );
101 if ( $msgForLoginUserMigrated !== null ) {
102 $h->will( $this->returnCallback(
103 function ( $user, &$msg ) use ( $username, $msgForLoginUserMigrated ) {
104 $this->assertInstanceOf( 'User', $user );
105 $this->assertSame( $username, $user->getName() );
106 $msg = $msgForLoginUserMigrated;
110 $this->hook( 'AbortLogin', $this->never() );
112 $h->will( $this->returnCallback(
113 function ( $user, &$msg ) use ( $username ) {
114 $this->assertInstanceOf( 'User', $user );
115 $this->assertSame( $username, $user->getName() );
119 $h2 = $this->hook( 'AbortLogin', $this->once() );
120 if ( $abortForAbortLogin !== null ) {
121 $h2->will( $this->returnCallback(
122 function ( $user, $pass, &$abort, &$msg )
123 use ( $username, $password, $abortForAbortLogin, $msgForAbortLogin )
125 $this->assertInstanceOf( 'User', $user );
126 $this->assertSame( $username, $user->getName() );
127 if ( $password !== null ) {
128 $this->assertSame( $password, $pass );
130 $this->assertInternalType( 'string', $pass );
132 $abort = $abortForAbortLogin;
133 $msg = $msgForAbortLogin;
138 $h2->will( $this->returnCallback(
139 function ( $user, $pass, &$abort, &$msg ) use ( $username, $password ) {
140 $this->assertInstanceOf( 'User', $user );
141 $this->assertSame( $username, $user->getName() );
142 if ( $password !== null ) {
143 $this->assertSame( $password, $pass );
145 $this->assertInternalType( 'string', $pass );
155 $status = $this->getProvider()->testForAuthentication( $reqs );
157 $this->unhook( 'LoginUserMigrated' );
158 $this->unhook( 'AbortLogin' );
160 if ( $failMsg === null ) {
161 $this->assertEquals( \StatusValue
::newGood(), $status, 'should succeed' );
163 $this->assertInstanceOf( 'StatusValue', $status, 'should fail (type)' );
164 $this->assertFalse( $status->isOk(), 'should fail (ok)' );
165 $errors = $status->getErrors();
166 $this->assertEquals( $failMsg, $errors[0]['message'], 'should fail (message)' );
167 $this->assertEquals( $failParams, $errors[0]['params'], 'should fail (params)' );
171 public static function provideTestForAuthentication() {
173 'No valid requests' => [
174 null, null, null, null, null, null
176 'No hook errors' => [
177 'User', 'PaSsWoRd', null, null, null, null
179 'No hook errors, no password' => [
180 'User', null, null, null, null, null
182 'LoginUserMigrated no message' => [
183 'User', 'PaSsWoRd', false, null, null, 'login-migrated-generic'
185 'LoginUserMigrated with message' => [
186 'User', 'PaSsWoRd', 'LUM-abort', null, null, 'LUM-abort'
188 'LoginUserMigrated with message and params' => [
189 'User', 'PaSsWoRd', [ 'LUM-abort', 'foo' ], null, null, 'LUM-abort', [ 'foo' ]
191 'AbortLogin, SUCCESS' => [
192 'User', 'PaSsWoRd', null, \LoginForm
::SUCCESS
, null, null
194 'AbortLogin, NEED_TOKEN, no message' => [
195 'User', 'PaSsWoRd', null, \LoginForm
::NEED_TOKEN
, null, 'nocookiesforlogin'
197 'AbortLogin, NEED_TOKEN, with message' => [
198 'User', 'PaSsWoRd', null, \LoginForm
::NEED_TOKEN
, 'needtoken', 'needtoken'
200 'AbortLogin, WRONG_TOKEN, no message' => [
201 'User', 'PaSsWoRd', null, \LoginForm
::WRONG_TOKEN
, null, 'sessionfailure'
203 'AbortLogin, WRONG_TOKEN, with message' => [
204 'User', 'PaSsWoRd', null, \LoginForm
::WRONG_TOKEN
, 'wrongtoken', 'wrongtoken'
206 'AbortLogin, ILLEGAL, no message' => [
207 'User', 'PaSsWoRd', null, \LoginForm
::ILLEGAL
, null, 'noname'
209 'AbortLogin, ILLEGAL, with message' => [
210 'User', 'PaSsWoRd', null, \LoginForm
::ILLEGAL
, 'badname', 'badname'
212 'AbortLogin, NO_NAME, no message' => [
213 'User', 'PaSsWoRd', null, \LoginForm
::NO_NAME
, null, 'noname'
215 'AbortLogin, NO_NAME, with message' => [
216 'User', 'PaSsWoRd', null, \LoginForm
::NO_NAME
, 'badname', 'badname'
218 'AbortLogin, WRONG_PASS, no message' => [
219 'User', 'PaSsWoRd', null, \LoginForm
::WRONG_PASS
, null, 'wrongpassword'
221 'AbortLogin, WRONG_PASS, with message' => [
222 'User', 'PaSsWoRd', null, \LoginForm
::WRONG_PASS
, 'badpass', 'badpass'
224 'AbortLogin, WRONG_PLUGIN_PASS, no message' => [
225 'User', 'PaSsWoRd', null, \LoginForm
::WRONG_PLUGIN_PASS
, null, 'wrongpassword'
227 'AbortLogin, WRONG_PLUGIN_PASS, with message' => [
228 'User', 'PaSsWoRd', null, \LoginForm
::WRONG_PLUGIN_PASS
, 'badpass', 'badpass'
230 'AbortLogin, NOT_EXISTS, no message' => [
231 "User'", 'A', null, \LoginForm
::NOT_EXISTS
, null, 'nosuchusershort', [ 'User'' ]
233 'AbortLogin, NOT_EXISTS, with message' => [
234 "User'", 'A', null, \LoginForm
::NOT_EXISTS
, 'badname', 'badname', [ 'User'' ]
236 'AbortLogin, EMPTY_PASS, no message' => [
237 'User', 'PaSsWoRd', null, \LoginForm
::EMPTY_PASS
, null, 'wrongpasswordempty'
239 'AbortLogin, EMPTY_PASS, with message' => [
240 'User', 'PaSsWoRd', null, \LoginForm
::EMPTY_PASS
, 'badpass', 'badpass'
242 'AbortLogin, RESET_PASS, no message' => [
243 'User', 'PaSsWoRd', null, \LoginForm
::RESET_PASS
, null, 'resetpass_announce'
245 'AbortLogin, RESET_PASS, with message' => [
246 'User', 'PaSsWoRd', null, \LoginForm
::RESET_PASS
, 'resetpass', 'resetpass'
248 'AbortLogin, THROTTLED, no message' => [
249 'User', 'PaSsWoRd', null, \LoginForm
::THROTTLED
, null, 'login-throttled',
250 [ \Message
::durationParam( 42 ) ]
252 'AbortLogin, THROTTLED, with message' => [
253 'User', 'PaSsWoRd', null, \LoginForm
::THROTTLED
, 't', 't',
254 [ \Message
::durationParam( 42 ) ]
256 'AbortLogin, USER_BLOCKED, no message' => [
257 "User'", 'P', null, \LoginForm
::USER_BLOCKED
, null, 'login-userblocked', [ 'User'' ]
259 'AbortLogin, USER_BLOCKED, with message' => [
260 "User'", 'P', null, \LoginForm
::USER_BLOCKED
, 'blocked', 'blocked', [ 'User'' ]
262 'AbortLogin, ABORTED, no message' => [
263 "User'", 'P', null, \LoginForm
::ABORTED
, null, 'login-abort-generic', [ 'User'' ]
265 'AbortLogin, ABORTED, with message' => [
266 "User'", 'P', null, \LoginForm
::ABORTED
, 'aborted', 'aborted', [ 'User'' ]
268 'AbortLogin, USER_MIGRATED, no message' => [
269 'User', 'P', null, \LoginForm
::USER_MIGRATED
, null, 'login-migrated-generic'
271 'AbortLogin, USER_MIGRATED, with message' => [
272 'User', 'P', null, \LoginForm
::USER_MIGRATED
, 'migrated', 'migrated'
274 'AbortLogin, USER_MIGRATED, with message and params' => [
275 'User', 'P', null, \LoginForm
::USER_MIGRATED
, [ 'migrated', 'foo' ],
276 'migrated', [ 'foo' ]
282 * @dataProvider provideTestForAccountCreation
284 * @param Status|null $status
285 * @param StatusValue $result Result
287 public function testTestForAccountCreation( $msg, $status, $result ) {
288 $this->hook( 'AbortNewAccount', $this->once() )
289 ->will( $this->returnCallback( function ( $user, &$error, &$abortStatus )
290 use ( $msg, $status )
292 $this->assertInstanceOf( 'User', $user );
293 $this->assertSame( 'User', $user->getName() );
295 $abortStatus = $status;
296 return $error === null && $status === null;
299 $user = \User
::newFromName( 'User' );
300 $creator = \User
::newFromName( 'UTSysop' );
301 $ret = $this->getProvider()->testForAccountCreation( $user, $creator, [] );
303 $this->unhook( 'AbortNewAccount' );
305 $this->assertEquals( $result, $ret );
308 public static function provideTestForAccountCreation() {
310 'No hook errors' => [
311 null, null, \StatusValue
::newGood()
313 'AbortNewAccount, old style' => [
314 'foobar', null, \StatusValue
::newFatal(
315 \Message
::newFromKey( 'createaccount-hook-aborted' )->rawParams( 'foobar' )
318 'AbortNewAccount, new style' => [
320 \Status
::newFatal( 'aborted!', 'param' ),
321 \StatusValue
::newFatal( 'aborted!', 'param' )
327 * @dataProvider provideTestUserForCreation
328 * @param string|null $error
329 * @param string|null $failMsg
331 public function testTestUserForCreation( $error, $failMsg ) {
332 $testUser = self
::getTestUser()->getUser();
333 $provider = $this->getProvider();
334 $options = [ 'flags' => \User
::READ_LOCKING
, 'creating' => true ];
336 $this->hook( 'AbortNewAccount', $this->never() );
337 $this->hook( 'AbortAutoAccount', $this->once() )
338 ->will( $this->returnCallback( function ( $user, &$abortError ) use ( $testUser, $error ) {
339 $this->assertInstanceOf( 'User', $user );
340 $this->assertSame( $testUser->getName(), $user->getName() );
341 $abortError = $error;
342 return $error === null;
344 $status = $provider->testUserForCreation(
345 $testUser, AuthManager
::AUTOCREATE_SOURCE_SESSION
, $options
347 $this->unhook( 'AbortNewAccount' );
348 $this->unhook( 'AbortAutoAccount' );
349 if ( $failMsg === null ) {
350 $this->assertEquals( \StatusValue
::newGood(), $status, 'should succeed' );
352 $this->assertInstanceOf( 'StatusValue', $status, 'should fail (type)' );
353 $this->assertFalse( $status->isOk(), 'should fail (ok)' );
354 $errors = $status->getErrors();
355 $this->assertEquals( $failMsg, $errors[0]['message'], 'should fail (message)' );
358 $this->hook( 'AbortAutoAccount', $this->never() );
359 $this->hook( 'AbortNewAccount', $this->never() );
360 $status = $provider->testUserForCreation( $testUser, false, $options );
361 $this->unhook( 'AbortNewAccount' );
362 $this->unhook( 'AbortAutoAccount' );
363 $this->assertEquals( \StatusValue
::newGood(), $status, 'should succeed' );
366 public static function provideTestUserForCreation() {
368 'Success' => [ null, null ],
369 'Fail, no message' => [ false, 'login-abort-generic' ],
370 'Fail, with message' => [ 'fail', 'fail' ],