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
::class )
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 $mockClass = get_class( $mock );
48 $this->hideDeprecated( "$hook hook (used in $mockClass::on$hook)" );
49 return $mock->expects( $expect )->method( "on$hook" );
56 protected function unhook( $hook ) {
57 $this->mergeMwGlobalArrayValue( 'wgHooks', [
62 // Stubs for hooks taking reference parameters
63 public function onLoginUserMigrated( $user, &$msg ) {
65 public function onAbortLogin( $user, $password, &$abort, &$msg ) {
67 public function onAbortNewAccount( $user, &$abortError, &$abortStatus ) {
69 public function onAbortAutoAccount( $user, &$abortError ) {
73 * @dataProvider provideTestForAuthentication
74 * @param string|null $username
75 * @param string|null $password
76 * @param string|null $msgForLoginUserMigrated
77 * @param int|null $abortForAbortLogin
78 * @param string|null $msgForAbortLogin
79 * @param string|null $failMsg
80 * @param array $failParams
82 public function testTestForAuthentication(
84 $msgForLoginUserMigrated, $abortForAbortLogin, $msgForAbortLogin,
85 $failMsg, $failParams = []
88 if ( $username === null ) {
89 $this->hook( 'LoginUserMigrated', $this->never() );
90 $this->hook( 'AbortLogin', $this->never() );
92 if ( $password === null ) {
93 $req = $this->getMockForAbstractClass( AuthenticationRequest
::class );
95 $req = new PasswordAuthenticationRequest();
96 $req->action
= AuthManager
::ACTION_LOGIN
;
97 $req->password
= $password;
99 $req->username
= $username;
100 $reqs[get_class( $req )] = $req;
102 $h = $this->hook( 'LoginUserMigrated', $this->once() );
103 if ( $msgForLoginUserMigrated !== null ) {
104 $h->will( $this->returnCallback(
105 function ( $user, &$msg ) use ( $username, $msgForLoginUserMigrated ) {
106 $this->assertInstanceOf( \User
::class, $user );
107 $this->assertSame( $username, $user->getName() );
108 $msg = $msgForLoginUserMigrated;
112 $this->hook( 'AbortLogin', $this->never() );
114 $h->will( $this->returnCallback(
115 function ( $user, &$msg ) use ( $username ) {
116 $this->assertInstanceOf( \User
::class, $user );
117 $this->assertSame( $username, $user->getName() );
121 $h2 = $this->hook( 'AbortLogin', $this->once() );
122 if ( $abortForAbortLogin !== null ) {
123 $h2->will( $this->returnCallback(
124 function ( $user, $pass, &$abort, &$msg )
125 use ( $username, $password, $abortForAbortLogin, $msgForAbortLogin )
127 $this->assertInstanceOf( \User
::class, $user );
128 $this->assertSame( $username, $user->getName() );
129 if ( $password !== null ) {
130 $this->assertSame( $password, $pass );
132 $this->assertInternalType( 'string', $pass );
134 $abort = $abortForAbortLogin;
135 $msg = $msgForAbortLogin;
140 $h2->will( $this->returnCallback(
141 function ( $user, $pass, &$abort, &$msg ) use ( $username, $password ) {
142 $this->assertInstanceOf( \User
::class, $user );
143 $this->assertSame( $username, $user->getName() );
144 if ( $password !== null ) {
145 $this->assertSame( $password, $pass );
147 $this->assertInternalType( 'string', $pass );
157 $status = $this->getProvider()->testForAuthentication( $reqs );
159 $this->unhook( 'LoginUserMigrated' );
160 $this->unhook( 'AbortLogin' );
162 if ( $failMsg === null ) {
163 $this->assertEquals( \StatusValue
::newGood(), $status, 'should succeed' );
165 $this->assertInstanceOf( \StatusValue
::class, $status, 'should fail (type)' );
166 $this->assertFalse( $status->isOk(), 'should fail (ok)' );
167 $errors = $status->getErrors();
168 $this->assertEquals( $failMsg, $errors[0]['message'], 'should fail (message)' );
169 $this->assertEquals( $failParams, $errors[0]['params'], 'should fail (params)' );
173 public static function provideTestForAuthentication() {
175 'No valid requests' => [
176 null, null, null, null, null, null
178 'No hook errors' => [
179 'User', 'PaSsWoRd', null, null, null, null
181 'No hook errors, no password' => [
182 'User', null, null, null, null, null
184 'LoginUserMigrated no message' => [
185 'User', 'PaSsWoRd', false, null, null, 'login-migrated-generic'
187 'LoginUserMigrated with message' => [
188 'User', 'PaSsWoRd', 'LUM-abort', null, null, 'LUM-abort'
190 'LoginUserMigrated with message and params' => [
191 'User', 'PaSsWoRd', [ 'LUM-abort', 'foo' ], null, null, 'LUM-abort', [ 'foo' ]
193 'AbortLogin, SUCCESS' => [
194 'User', 'PaSsWoRd', null, \LoginForm
::SUCCESS
, null, null
196 'AbortLogin, NEED_TOKEN, no message' => [
197 'User', 'PaSsWoRd', null, \LoginForm
::NEED_TOKEN
, null, 'nocookiesforlogin'
199 'AbortLogin, NEED_TOKEN, with message' => [
200 'User', 'PaSsWoRd', null, \LoginForm
::NEED_TOKEN
, 'needtoken', 'needtoken'
202 'AbortLogin, WRONG_TOKEN, no message' => [
203 'User', 'PaSsWoRd', null, \LoginForm
::WRONG_TOKEN
, null, 'sessionfailure'
205 'AbortLogin, WRONG_TOKEN, with message' => [
206 'User', 'PaSsWoRd', null, \LoginForm
::WRONG_TOKEN
, 'wrongtoken', 'wrongtoken'
208 'AbortLogin, ILLEGAL, no message' => [
209 'User', 'PaSsWoRd', null, \LoginForm
::ILLEGAL
, null, 'noname'
211 'AbortLogin, ILLEGAL, with message' => [
212 'User', 'PaSsWoRd', null, \LoginForm
::ILLEGAL
, 'badname', 'badname'
214 'AbortLogin, NO_NAME, no message' => [
215 'User', 'PaSsWoRd', null, \LoginForm
::NO_NAME
, null, 'noname'
217 'AbortLogin, NO_NAME, with message' => [
218 'User', 'PaSsWoRd', null, \LoginForm
::NO_NAME
, 'badname', 'badname'
220 'AbortLogin, WRONG_PASS, no message' => [
221 'User', 'PaSsWoRd', null, \LoginForm
::WRONG_PASS
, null, 'wrongpassword'
223 'AbortLogin, WRONG_PASS, with message' => [
224 'User', 'PaSsWoRd', null, \LoginForm
::WRONG_PASS
, 'badpass', 'badpass'
226 'AbortLogin, WRONG_PLUGIN_PASS, no message' => [
227 'User', 'PaSsWoRd', null, \LoginForm
::WRONG_PLUGIN_PASS
, null, 'wrongpassword'
229 'AbortLogin, WRONG_PLUGIN_PASS, with message' => [
230 'User', 'PaSsWoRd', null, \LoginForm
::WRONG_PLUGIN_PASS
, 'badpass', 'badpass'
232 'AbortLogin, NOT_EXISTS, no message' => [
233 "User'", 'A', null, \LoginForm
::NOT_EXISTS
, null, 'nosuchusershort', [ 'User'' ]
235 'AbortLogin, NOT_EXISTS, with message' => [
236 "User'", 'A', null, \LoginForm
::NOT_EXISTS
, 'badname', 'badname', [ 'User'' ]
238 'AbortLogin, EMPTY_PASS, no message' => [
239 'User', 'PaSsWoRd', null, \LoginForm
::EMPTY_PASS
, null, 'wrongpasswordempty'
241 'AbortLogin, EMPTY_PASS, with message' => [
242 'User', 'PaSsWoRd', null, \LoginForm
::EMPTY_PASS
, 'badpass', 'badpass'
244 'AbortLogin, RESET_PASS, no message' => [
245 'User', 'PaSsWoRd', null, \LoginForm
::RESET_PASS
, null, 'resetpass_announce'
247 'AbortLogin, RESET_PASS, with message' => [
248 'User', 'PaSsWoRd', null, \LoginForm
::RESET_PASS
, 'resetpass', 'resetpass'
250 'AbortLogin, THROTTLED, no message' => [
251 'User', 'PaSsWoRd', null, \LoginForm
::THROTTLED
, null, 'login-throttled',
252 [ \Message
::durationParam( 42 ) ]
254 'AbortLogin, THROTTLED, with message' => [
255 'User', 'PaSsWoRd', null, \LoginForm
::THROTTLED
, 't', 't',
256 [ \Message
::durationParam( 42 ) ]
258 'AbortLogin, USER_BLOCKED, no message' => [
259 "User'", 'P', null, \LoginForm
::USER_BLOCKED
, null, 'login-userblocked', [ 'User'' ]
261 'AbortLogin, USER_BLOCKED, with message' => [
262 "User'", 'P', null, \LoginForm
::USER_BLOCKED
, 'blocked', 'blocked', [ 'User'' ]
264 'AbortLogin, ABORTED, no message' => [
265 "User'", 'P', null, \LoginForm
::ABORTED
, null, 'login-abort-generic', [ 'User'' ]
267 'AbortLogin, ABORTED, with message' => [
268 "User'", 'P', null, \LoginForm
::ABORTED
, 'aborted', 'aborted', [ 'User'' ]
270 'AbortLogin, USER_MIGRATED, no message' => [
271 'User', 'P', null, \LoginForm
::USER_MIGRATED
, null, 'login-migrated-generic'
273 'AbortLogin, USER_MIGRATED, with message' => [
274 'User', 'P', null, \LoginForm
::USER_MIGRATED
, 'migrated', 'migrated'
276 'AbortLogin, USER_MIGRATED, with message and params' => [
277 'User', 'P', null, \LoginForm
::USER_MIGRATED
, [ 'migrated', 'foo' ],
278 'migrated', [ 'foo' ]
284 * @dataProvider provideTestForAccountCreation
286 * @param Status|null $status
287 * @param StatusValue $result
289 public function testTestForAccountCreation( $msg, $status, $result ) {
290 $this->hook( 'AbortNewAccount', $this->once() )
291 ->will( $this->returnCallback( function ( $user, &$error, &$abortStatus )
292 use ( $msg, $status )
294 $this->assertInstanceOf( \User
::class, $user );
295 $this->assertSame( 'User', $user->getName() );
297 $abortStatus = $status;
298 return $error === null && $status === null;
301 $user = \User
::newFromName( 'User' );
302 $creator = \User
::newFromName( 'UTSysop' );
303 $ret = $this->getProvider()->testForAccountCreation( $user, $creator, [] );
305 $this->unhook( 'AbortNewAccount' );
307 $this->assertEquals( $result, $ret );
310 public static function provideTestForAccountCreation() {
312 'No hook errors' => [
313 null, null, \StatusValue
::newGood()
315 'AbortNewAccount, old style' => [
316 'foobar', null, \StatusValue
::newFatal(
317 \Message
::newFromKey( 'createaccount-hook-aborted' )->rawParams( 'foobar' )
320 'AbortNewAccount, new style' => [
322 \Status
::newFatal( 'aborted!', 'param' ),
323 \StatusValue
::newFatal( 'aborted!', 'param' )
329 * @dataProvider provideTestUserForCreation
330 * @param string|null $error
331 * @param string|null $failMsg
333 public function testTestUserForCreation( $error, $failMsg ) {
334 $testUser = self
::getTestUser()->getUser();
335 $provider = $this->getProvider();
336 $options = [ 'flags' => \User
::READ_LOCKING
, 'creating' => true ];
338 $this->hook( 'AbortNewAccount', $this->never() );
339 $this->hook( 'AbortAutoAccount', $this->once() )
340 ->will( $this->returnCallback( function ( $user, &$abortError ) use ( $testUser, $error ) {
341 $this->assertInstanceOf( \User
::class, $user );
342 $this->assertSame( $testUser->getName(), $user->getName() );
343 $abortError = $error;
344 return $error === null;
346 $status = $provider->testUserForCreation(
347 $testUser, AuthManager
::AUTOCREATE_SOURCE_SESSION
, $options
349 $this->unhook( 'AbortNewAccount' );
350 $this->unhook( 'AbortAutoAccount' );
351 if ( $failMsg === null ) {
352 $this->assertEquals( \StatusValue
::newGood(), $status, 'should succeed' );
354 $this->assertInstanceOf( \StatusValue
::class, $status, 'should fail (type)' );
355 $this->assertFalse( $status->isOk(), 'should fail (ok)' );
356 $errors = $status->getErrors();
357 $this->assertEquals( $failMsg, $errors[0]['message'], 'should fail (message)' );
360 $this->hook( 'AbortAutoAccount', $this->never() );
361 $this->hook( 'AbortNewAccount', $this->never() );
362 $status = $provider->testUserForCreation( $testUser, false, $options );
363 $this->unhook( 'AbortNewAccount' );
364 $this->unhook( 'AbortAutoAccount' );
365 $this->assertEquals( \StatusValue
::newGood(), $status, 'should succeed' );
368 public static function provideTestUserForCreation() {
370 'Success' => [ null, null ],
371 'Fail, no message' => [ false, 'login-abort-generic' ],
372 'Fail, with message' => [ 'fail', 'fail' ],