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