Merge "Revert "Log the reason why revision->getContent() returns null""
[lhc/web/wiklou.git] / tests / phpunit / includes / auth / LegacyHookPreAuthenticationProviderTest.php
1 <?php
2
3 namespace MediaWiki\Auth;
4
5 use MediaWiki\MediaWikiServices;
6
7 /**
8 * @group AuthManager
9 * @group Database
10 * @covers MediaWiki\Auth\LegacyHookPreAuthenticationProvider
11 */
12 class LegacyHookPreAuthenticationProviderTest extends \MediaWikiTestCase {
13 /**
14 * Get an instance of the provider
15 * @return LegacyHookPreAuthenticationProvider
16 */
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' ) );
21
22 $manager = new AuthManager(
23 $request,
24 MediaWikiServices::getInstance()->getMainConfig()
25 );
26
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 ],
32 ] ) );
33 return $provider;
34 }
35
36 /**
37 * Sets a mock on a hook
38 * @param string $hook
39 * @param object $expect From $this->once(), $this->never(), etc.
40 * @return object $mock->expects( $expect )->method( ... ).
41 */
42 protected function hook( $hook, $expect ) {
43 $mock = $this->getMockBuilder( __CLASS__ )->setMethods( [ "on$hook" ] )->getMock();
44 $this->mergeMwGlobalArrayValue( 'wgHooks', [
45 $hook => [ $mock ],
46 ] );
47 return $mock->expects( $expect )->method( "on$hook" );
48 }
49
50 /**
51 * Unsets a hook
52 * @param string $hook
53 */
54 protected function unhook( $hook ) {
55 $this->mergeMwGlobalArrayValue( 'wgHooks', [
56 $hook => [],
57 ] );
58 }
59
60 // Stubs for hooks taking reference parameters
61 public function onLoginUserMigrated( $user, &$msg ) {
62 }
63 public function onAbortLogin( $user, $password, &$abort, &$msg ) {
64 }
65 public function onAbortNewAccount( $user, &$abortError, &$abortStatus ) {
66 }
67 public function onAbortAutoAccount( $user, &$abortError ) {
68 }
69
70 /**
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
79 */
80 public function testTestForAuthentication(
81 $username, $password,
82 $msgForLoginUserMigrated, $abortForAbortLogin, $msgForAbortLogin,
83 $failMsg, $failParams = []
84 ) {
85 $reqs = [];
86 if ( $username === null ) {
87 $this->hook( 'LoginUserMigrated', $this->never() );
88 $this->hook( 'AbortLogin', $this->never() );
89 } else {
90 if ( $password === null ) {
91 $req = $this->getMockForAbstractClass( AuthenticationRequest::class );
92 } else {
93 $req = new PasswordAuthenticationRequest();
94 $req->action = AuthManager::ACTION_LOGIN;
95 $req->password = $password;
96 }
97 $req->username = $username;
98 $reqs[get_class( $req )] = $req;
99
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::class, $user );
105 $this->assertSame( $username, $user->getName() );
106 $msg = $msgForLoginUserMigrated;
107 return false;
108 }
109 ) );
110 $this->hook( 'AbortLogin', $this->never() );
111 } else {
112 $h->will( $this->returnCallback(
113 function ( $user, &$msg ) use ( $username ) {
114 $this->assertInstanceOf( \User::class, $user );
115 $this->assertSame( $username, $user->getName() );
116 return true;
117 }
118 ) );
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 )
124 {
125 $this->assertInstanceOf( \User::class, $user );
126 $this->assertSame( $username, $user->getName() );
127 if ( $password !== null ) {
128 $this->assertSame( $password, $pass );
129 } else {
130 $this->assertInternalType( 'string', $pass );
131 }
132 $abort = $abortForAbortLogin;
133 $msg = $msgForAbortLogin;
134 return false;
135 }
136 ) );
137 } else {
138 $h2->will( $this->returnCallback(
139 function ( $user, $pass, &$abort, &$msg ) use ( $username, $password ) {
140 $this->assertInstanceOf( \User::class, $user );
141 $this->assertSame( $username, $user->getName() );
142 if ( $password !== null ) {
143 $this->assertSame( $password, $pass );
144 } else {
145 $this->assertInternalType( 'string', $pass );
146 }
147 return true;
148 }
149 ) );
150 }
151 }
152 }
153 unset( $h, $h2 );
154
155 $status = $this->getProvider()->testForAuthentication( $reqs );
156
157 $this->unhook( 'LoginUserMigrated' );
158 $this->unhook( 'AbortLogin' );
159
160 if ( $failMsg === null ) {
161 $this->assertEquals( \StatusValue::newGood(), $status, 'should succeed' );
162 } else {
163 $this->assertInstanceOf( \StatusValue::class, $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)' );
168 }
169 }
170
171 public static function provideTestForAuthentication() {
172 return [
173 'No valid requests' => [
174 null, null, null, null, null, null
175 ],
176 'No hook errors' => [
177 'User', 'PaSsWoRd', null, null, null, null
178 ],
179 'No hook errors, no password' => [
180 'User', null, null, null, null, null
181 ],
182 'LoginUserMigrated no message' => [
183 'User', 'PaSsWoRd', false, null, null, 'login-migrated-generic'
184 ],
185 'LoginUserMigrated with message' => [
186 'User', 'PaSsWoRd', 'LUM-abort', null, null, 'LUM-abort'
187 ],
188 'LoginUserMigrated with message and params' => [
189 'User', 'PaSsWoRd', [ 'LUM-abort', 'foo' ], null, null, 'LUM-abort', [ 'foo' ]
190 ],
191 'AbortLogin, SUCCESS' => [
192 'User', 'PaSsWoRd', null, \LoginForm::SUCCESS, null, null
193 ],
194 'AbortLogin, NEED_TOKEN, no message' => [
195 'User', 'PaSsWoRd', null, \LoginForm::NEED_TOKEN, null, 'nocookiesforlogin'
196 ],
197 'AbortLogin, NEED_TOKEN, with message' => [
198 'User', 'PaSsWoRd', null, \LoginForm::NEED_TOKEN, 'needtoken', 'needtoken'
199 ],
200 'AbortLogin, WRONG_TOKEN, no message' => [
201 'User', 'PaSsWoRd', null, \LoginForm::WRONG_TOKEN, null, 'sessionfailure'
202 ],
203 'AbortLogin, WRONG_TOKEN, with message' => [
204 'User', 'PaSsWoRd', null, \LoginForm::WRONG_TOKEN, 'wrongtoken', 'wrongtoken'
205 ],
206 'AbortLogin, ILLEGAL, no message' => [
207 'User', 'PaSsWoRd', null, \LoginForm::ILLEGAL, null, 'noname'
208 ],
209 'AbortLogin, ILLEGAL, with message' => [
210 'User', 'PaSsWoRd', null, \LoginForm::ILLEGAL, 'badname', 'badname'
211 ],
212 'AbortLogin, NO_NAME, no message' => [
213 'User', 'PaSsWoRd', null, \LoginForm::NO_NAME, null, 'noname'
214 ],
215 'AbortLogin, NO_NAME, with message' => [
216 'User', 'PaSsWoRd', null, \LoginForm::NO_NAME, 'badname', 'badname'
217 ],
218 'AbortLogin, WRONG_PASS, no message' => [
219 'User', 'PaSsWoRd', null, \LoginForm::WRONG_PASS, null, 'wrongpassword'
220 ],
221 'AbortLogin, WRONG_PASS, with message' => [
222 'User', 'PaSsWoRd', null, \LoginForm::WRONG_PASS, 'badpass', 'badpass'
223 ],
224 'AbortLogin, WRONG_PLUGIN_PASS, no message' => [
225 'User', 'PaSsWoRd', null, \LoginForm::WRONG_PLUGIN_PASS, null, 'wrongpassword'
226 ],
227 'AbortLogin, WRONG_PLUGIN_PASS, with message' => [
228 'User', 'PaSsWoRd', null, \LoginForm::WRONG_PLUGIN_PASS, 'badpass', 'badpass'
229 ],
230 'AbortLogin, NOT_EXISTS, no message' => [
231 "User'", 'A', null, \LoginForm::NOT_EXISTS, null, 'nosuchusershort', [ 'User&#39;' ]
232 ],
233 'AbortLogin, NOT_EXISTS, with message' => [
234 "User'", 'A', null, \LoginForm::NOT_EXISTS, 'badname', 'badname', [ 'User&#39;' ]
235 ],
236 'AbortLogin, EMPTY_PASS, no message' => [
237 'User', 'PaSsWoRd', null, \LoginForm::EMPTY_PASS, null, 'wrongpasswordempty'
238 ],
239 'AbortLogin, EMPTY_PASS, with message' => [
240 'User', 'PaSsWoRd', null, \LoginForm::EMPTY_PASS, 'badpass', 'badpass'
241 ],
242 'AbortLogin, RESET_PASS, no message' => [
243 'User', 'PaSsWoRd', null, \LoginForm::RESET_PASS, null, 'resetpass_announce'
244 ],
245 'AbortLogin, RESET_PASS, with message' => [
246 'User', 'PaSsWoRd', null, \LoginForm::RESET_PASS, 'resetpass', 'resetpass'
247 ],
248 'AbortLogin, THROTTLED, no message' => [
249 'User', 'PaSsWoRd', null, \LoginForm::THROTTLED, null, 'login-throttled',
250 [ \Message::durationParam( 42 ) ]
251 ],
252 'AbortLogin, THROTTLED, with message' => [
253 'User', 'PaSsWoRd', null, \LoginForm::THROTTLED, 't', 't',
254 [ \Message::durationParam( 42 ) ]
255 ],
256 'AbortLogin, USER_BLOCKED, no message' => [
257 "User'", 'P', null, \LoginForm::USER_BLOCKED, null, 'login-userblocked', [ 'User&#39;' ]
258 ],
259 'AbortLogin, USER_BLOCKED, with message' => [
260 "User'", 'P', null, \LoginForm::USER_BLOCKED, 'blocked', 'blocked', [ 'User&#39;' ]
261 ],
262 'AbortLogin, ABORTED, no message' => [
263 "User'", 'P', null, \LoginForm::ABORTED, null, 'login-abort-generic', [ 'User&#39;' ]
264 ],
265 'AbortLogin, ABORTED, with message' => [
266 "User'", 'P', null, \LoginForm::ABORTED, 'aborted', 'aborted', [ 'User&#39;' ]
267 ],
268 'AbortLogin, USER_MIGRATED, no message' => [
269 'User', 'P', null, \LoginForm::USER_MIGRATED, null, 'login-migrated-generic'
270 ],
271 'AbortLogin, USER_MIGRATED, with message' => [
272 'User', 'P', null, \LoginForm::USER_MIGRATED, 'migrated', 'migrated'
273 ],
274 'AbortLogin, USER_MIGRATED, with message and params' => [
275 'User', 'P', null, \LoginForm::USER_MIGRATED, [ 'migrated', 'foo' ],
276 'migrated', [ 'foo' ]
277 ],
278 ];
279 }
280
281 /**
282 * @dataProvider provideTestForAccountCreation
283 * @param string $msg
284 * @param Status|null $status
285 * @param StatusValue $result
286 */
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 )
291 {
292 $this->assertInstanceOf( \User::class, $user );
293 $this->assertSame( 'User', $user->getName() );
294 $error = $msg;
295 $abortStatus = $status;
296 return $error === null && $status === null;
297 } ) );
298
299 $user = \User::newFromName( 'User' );
300 $creator = \User::newFromName( 'UTSysop' );
301 $ret = $this->getProvider()->testForAccountCreation( $user, $creator, [] );
302
303 $this->unhook( 'AbortNewAccount' );
304
305 $this->assertEquals( $result, $ret );
306 }
307
308 public static function provideTestForAccountCreation() {
309 return [
310 'No hook errors' => [
311 null, null, \StatusValue::newGood()
312 ],
313 'AbortNewAccount, old style' => [
314 'foobar', null, \StatusValue::newFatal(
315 \Message::newFromKey( 'createaccount-hook-aborted' )->rawParams( 'foobar' )
316 )
317 ],
318 'AbortNewAccount, new style' => [
319 'foobar',
320 \Status::newFatal( 'aborted!', 'param' ),
321 \StatusValue::newFatal( 'aborted!', 'param' )
322 ],
323 ];
324 }
325
326 /**
327 * @dataProvider provideTestUserForCreation
328 * @param string|null $error
329 * @param string|null $failMsg
330 */
331 public function testTestUserForCreation( $error, $failMsg ) {
332 $testUser = self::getTestUser()->getUser();
333 $provider = $this->getProvider();
334 $options = [ 'flags' => \User::READ_LOCKING, 'creating' => true ];
335
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::class, $user );
340 $this->assertSame( $testUser->getName(), $user->getName() );
341 $abortError = $error;
342 return $error === null;
343 } ) );
344 $status = $provider->testUserForCreation(
345 $testUser, AuthManager::AUTOCREATE_SOURCE_SESSION, $options
346 );
347 $this->unhook( 'AbortNewAccount' );
348 $this->unhook( 'AbortAutoAccount' );
349 if ( $failMsg === null ) {
350 $this->assertEquals( \StatusValue::newGood(), $status, 'should succeed' );
351 } else {
352 $this->assertInstanceOf( \StatusValue::class, $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)' );
356 }
357
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' );
364 }
365
366 public static function provideTestUserForCreation() {
367 return [
368 'Success' => [ null, null ],
369 'Fail, no message' => [ false, 'login-abort-generic' ],
370 'Fail, with message' => [ 'fail', 'fail' ],
371 ];
372 }
373 }