MediaWiki REL1_28
AuthManagerTest.php
Go to the documentation of this file.
1<?php
2
3namespace MediaWiki\Auth;
4
7use Psr\Log\LogLevel;
9use Wikimedia\ScopedCallback;
10
18 protected $request;
20 protected $config;
22 protected $logger;
23
24 protected $preauthMocks = [];
25 protected $primaryauthMocks = [];
26 protected $secondaryauthMocks = [];
27
29 protected $manager;
31 protected $managerPriv;
32
33 protected function setUp() {
34 parent::setUp();
35
36 $this->setMwGlobals( [ 'wgAuth' => null ] );
37 $this->stashMwGlobals( [ 'wgHooks' ] );
38 }
39
46 protected function hook( $hook, $expect ) {
48 $mock = $this->getMock( __CLASS__, [ "on$hook" ] );
49 $wgHooks[$hook] = [ $mock ];
50 return $mock->expects( $expect )->method( "on$hook" );
51 }
52
57 protected function unhook( $hook ) {
59 $wgHooks[$hook] = [];
60 }
61
68 protected function message( $key, $params = [] ) {
69 if ( $key === null ) {
70 return null;
71 }
72 if ( $key instanceof \MessageSpecifier ) {
73 $params = $key->getParams();
74 $key = $key->getKey();
75 }
76 return new \Message( $key, $params, \Language::factory( 'en' ) );
77 }
78
84 protected function initializeConfig() {
85 $config = [
86 'preauth' => [
87 ],
88 'primaryauth' => [
89 ],
90 'secondaryauth' => [
91 ],
92 ];
93
94 foreach ( [ 'preauth', 'primaryauth', 'secondaryauth' ] as $type ) {
95 $key = $type . 'Mocks';
96 foreach ( $this->$key as $mock ) {
97 $config[$type][$mock->getUniqueId()] = [ 'factory' => function () use ( $mock ) {
98 return $mock;
99 } ];
100 }
101 }
102
103 $this->config->set( 'AuthManagerConfig', $config );
104 $this->config->set( 'LanguageCode', 'en' );
105 $this->config->set( 'NewUserLog', false );
106 }
107
112 protected function initializeManager( $regen = false ) {
113 if ( $regen || !$this->config ) {
114 $this->config = new \HashConfig();
115 }
116 if ( $regen || !$this->request ) {
117 $this->request = new \FauxRequest();
118 }
119 if ( !$this->logger ) {
120 $this->logger = new \TestLogger();
121 }
122
123 if ( $regen || !$this->config->has( 'AuthManagerConfig' ) ) {
124 $this->initializeConfig();
125 }
126 $this->manager = new AuthManager( $this->request, $this->config );
127 $this->manager->setLogger( $this->logger );
128 $this->managerPriv = \TestingAccessWrapper::newFromObject( $this->manager );
129 }
130
137 protected function getMockSessionProvider( $canChangeUser = null, array $methods = [] ) {
138 if ( !$this->config ) {
139 $this->config = new \HashConfig();
140 $this->initializeConfig();
141 }
142 $this->config->set( 'ObjectCacheSessionExpiry', 100 );
143
144 $methods[] = '__toString';
145 $methods[] = 'describe';
146 if ( $canChangeUser !== null ) {
147 $methods[] = 'canChangeUser';
148 }
149 $provider = $this->getMockBuilder( 'DummySessionProvider' )
150 ->setMethods( $methods )
151 ->getMock();
152 $provider->expects( $this->any() )->method( '__toString' )
153 ->will( $this->returnValue( 'MockSessionProvider' ) );
154 $provider->expects( $this->any() )->method( 'describe' )
155 ->will( $this->returnValue( 'MockSessionProvider sessions' ) );
156 if ( $canChangeUser !== null ) {
157 $provider->expects( $this->any() )->method( 'canChangeUser' )
158 ->will( $this->returnValue( $canChangeUser ) );
159 }
160 $this->config->set( 'SessionProviders', [
161 [ 'factory' => function () use ( $provider ) {
162 return $provider;
163 } ],
164 ] );
165
166 $manager = new \MediaWiki\Session\SessionManager( [
167 'config' => $this->config,
168 'logger' => new \Psr\Log\NullLogger(),
169 'store' => new \HashBagOStuff(),
170 ] );
171 \TestingAccessWrapper::newFromObject( $manager )->getProvider( (string)$provider );
172
173 $reset = \MediaWiki\Session\TestUtils::setSessionManagerSingleton( $manager );
174
175 if ( $this->request ) {
176 $manager->getSessionForRequest( $this->request );
177 }
178
179 return [ $provider, $reset ];
180 }
181
182 public function testSingleton() {
183 // Temporarily clear out the global singleton, if any, to test creating
184 // one.
185 $rProp = new \ReflectionProperty( AuthManager::class, 'instance' );
186 $rProp->setAccessible( true );
187 $old = $rProp->getValue();
188 $cb = new ScopedCallback( [ $rProp, 'setValue' ], [ $old ] );
189 $rProp->setValue( null );
190
191 $singleton = AuthManager::singleton();
192 $this->assertInstanceOf( AuthManager::class, AuthManager::singleton() );
193 $this->assertSame( $singleton, AuthManager::singleton() );
194 $this->assertSame( \RequestContext::getMain()->getRequest(), $singleton->getRequest() );
195 $this->assertSame(
196 \RequestContext::getMain()->getConfig(),
197 \TestingAccessWrapper::newFromObject( $singleton )->config
198 );
199 }
200
201 public function testCanAuthenticateNow() {
202 $this->initializeManager();
203
204 list( $provider, $reset ) = $this->getMockSessionProvider( false );
205 $this->assertFalse( $this->manager->canAuthenticateNow() );
206 ScopedCallback::consume( $reset );
207
208 list( $provider, $reset ) = $this->getMockSessionProvider( true );
209 $this->assertTrue( $this->manager->canAuthenticateNow() );
210 ScopedCallback::consume( $reset );
211 }
212
213 public function testNormalizeUsername() {
214 $mocks = [
215 $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class ),
216 $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class ),
217 $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class ),
218 $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class ),
219 ];
220 foreach ( $mocks as $key => $mock ) {
221 $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( $key ) );
222 }
223 $mocks[0]->expects( $this->once() )->method( 'providerNormalizeUsername' )
224 ->with( $this->identicalTo( 'XYZ' ) )
225 ->willReturn( 'Foo' );
226 $mocks[1]->expects( $this->once() )->method( 'providerNormalizeUsername' )
227 ->with( $this->identicalTo( 'XYZ' ) )
228 ->willReturn( 'Foo' );
229 $mocks[2]->expects( $this->once() )->method( 'providerNormalizeUsername' )
230 ->with( $this->identicalTo( 'XYZ' ) )
231 ->willReturn( null );
232 $mocks[3]->expects( $this->once() )->method( 'providerNormalizeUsername' )
233 ->with( $this->identicalTo( 'XYZ' ) )
234 ->willReturn( 'Bar!' );
235
236 $this->primaryauthMocks = $mocks;
237
238 $this->initializeManager();
239
240 $this->assertSame( [ 'Foo', 'Bar!' ], $this->manager->normalizeUsername( 'XYZ' ) );
241 }
242
247 public function testSecuritySensitiveOperationStatus( $mutableSession ) {
248 $this->logger = new \Psr\Log\NullLogger();
249 $user = \User::newFromName( 'UTSysop' );
250 $provideUser = null;
251 $reauth = $mutableSession ? AuthManager::SEC_REAUTH : AuthManager::SEC_FAIL;
252
253 list( $provider, $reset ) = $this->getMockSessionProvider(
254 $mutableSession, [ 'provideSessionInfo' ]
255 );
256 $provider->expects( $this->any() )->method( 'provideSessionInfo' )
257 ->will( $this->returnCallback( function () use ( $provider, &$provideUser ) {
258 return new SessionInfo( SessionInfo::MIN_PRIORITY, [
259 'provider' => $provider,
261 'persisted' => true,
262 'userInfo' => UserInfo::newFromUser( $provideUser, true )
263 ] );
264 } ) );
265 $this->initializeManager();
266
267 $this->config->set( 'ReauthenticateTime', [] );
268 $this->config->set( 'AllowSecuritySensitiveOperationIfCannotReauthenticate', [] );
269 $provideUser = new \User;
270 $session = $provider->getManager()->getSessionForRequest( $this->request );
271 $this->assertSame( 0, $session->getUser()->getId(), 'sanity check' );
272
273 // Anonymous user => reauth
274 $session->set( 'AuthManager:lastAuthId', 0 );
275 $session->set( 'AuthManager:lastAuthTimestamp', time() - 5 );
276 $this->assertSame( $reauth, $this->manager->securitySensitiveOperationStatus( 'foo' ) );
277
278 $provideUser = $user;
279 $session = $provider->getManager()->getSessionForRequest( $this->request );
280 $this->assertSame( $user->getId(), $session->getUser()->getId(), 'sanity check' );
281
282 // Error for no default (only gets thrown for non-anonymous user)
283 $session->set( 'AuthManager:lastAuthId', $user->getId() + 1 );
284 $session->set( 'AuthManager:lastAuthTimestamp', time() - 5 );
285 try {
286 $this->manager->securitySensitiveOperationStatus( 'foo' );
287 $this->fail( 'Expected exception not thrown' );
288 } catch ( \UnexpectedValueException $ex ) {
289 $this->assertSame(
290 $mutableSession
291 ? '$wgReauthenticateTime lacks a default'
292 : '$wgAllowSecuritySensitiveOperationIfCannotReauthenticate lacks a default',
293 $ex->getMessage()
294 );
295 }
296
297 if ( $mutableSession ) {
298 $this->config->set( 'ReauthenticateTime', [
299 'test' => 100,
300 'test2' => -1,
301 'default' => 10,
302 ] );
303
304 // Mismatched user ID
305 $session->set( 'AuthManager:lastAuthId', $user->getId() + 1 );
306 $session->set( 'AuthManager:lastAuthTimestamp', time() - 5 );
307 $this->assertSame(
308 AuthManager::SEC_REAUTH, $this->manager->securitySensitiveOperationStatus( 'foo' )
309 );
310 $this->assertSame(
311 AuthManager::SEC_REAUTH, $this->manager->securitySensitiveOperationStatus( 'test' )
312 );
313 $this->assertSame(
314 AuthManager::SEC_OK, $this->manager->securitySensitiveOperationStatus( 'test2' )
315 );
316
317 // Missing time
318 $session->set( 'AuthManager:lastAuthId', $user->getId() );
319 $session->set( 'AuthManager:lastAuthTimestamp', null );
320 $this->assertSame(
321 AuthManager::SEC_REAUTH, $this->manager->securitySensitiveOperationStatus( 'foo' )
322 );
323 $this->assertSame(
324 AuthManager::SEC_REAUTH, $this->manager->securitySensitiveOperationStatus( 'test' )
325 );
326 $this->assertSame(
327 AuthManager::SEC_OK, $this->manager->securitySensitiveOperationStatus( 'test2' )
328 );
329
330 // Recent enough to pass
331 $session->set( 'AuthManager:lastAuthTimestamp', time() - 5 );
332 $this->assertSame(
333 AuthManager::SEC_OK, $this->manager->securitySensitiveOperationStatus( 'foo' )
334 );
335
336 // Not recent enough to pass
337 $session->set( 'AuthManager:lastAuthTimestamp', time() - 20 );
338 $this->assertSame(
339 AuthManager::SEC_REAUTH, $this->manager->securitySensitiveOperationStatus( 'foo' )
340 );
341 // But recent enough for the 'test' operation
342 $this->assertSame(
343 AuthManager::SEC_OK, $this->manager->securitySensitiveOperationStatus( 'test' )
344 );
345 } else {
346 $this->config->set( 'AllowSecuritySensitiveOperationIfCannotReauthenticate', [
347 'test' => false,
348 'default' => true,
349 ] );
350
351 $this->assertEquals(
352 AuthManager::SEC_OK, $this->manager->securitySensitiveOperationStatus( 'foo' )
353 );
354
355 $this->assertEquals(
356 AuthManager::SEC_FAIL, $this->manager->securitySensitiveOperationStatus( 'test' )
357 );
358 }
359
360 // Test hook, all three possible values
361 foreach ( [
363 AuthManager::SEC_REAUTH => $reauth,
365 ] as $hook => $expect ) {
366 $this->hook( 'SecuritySensitiveOperationStatus', $this->exactly( 2 ) )
367 ->with(
368 $this->anything(),
369 $this->anything(),
370 $this->callback( function ( $s ) use ( $session ) {
371 return $s->getId() === $session->getId();
372 } ),
373 $mutableSession ? $this->equalTo( 500, 1 ) : $this->equalTo( -1 )
374 )
375 ->will( $this->returnCallback( function ( &$v ) use ( $hook ) {
376 $v = $hook;
377 return true;
378 } ) );
379 $session->set( 'AuthManager:lastAuthTimestamp', time() - 500 );
380 $this->assertEquals(
381 $expect, $this->manager->securitySensitiveOperationStatus( 'test' ), "hook $hook"
382 );
383 $this->assertEquals(
384 $expect, $this->manager->securitySensitiveOperationStatus( 'test2' ), "hook $hook"
385 );
386 $this->unhook( 'SecuritySensitiveOperationStatus' );
387 }
388
389 ScopedCallback::consume( $reset );
390 }
391
392 public function onSecuritySensitiveOperationStatus( &$status, $operation, $session, $time ) {
393 }
394
396 return [
397 [ true ],
398 [ false ],
399 ];
400 }
401
408 public function testUserCanAuthenticate( $primary1Can, $primary2Can, $expect ) {
409 $mock1 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
410 $mock1->expects( $this->any() )->method( 'getUniqueId' )
411 ->will( $this->returnValue( 'primary1' ) );
412 $mock1->expects( $this->any() )->method( 'testUserCanAuthenticate' )
413 ->with( $this->equalTo( 'UTSysop' ) )
414 ->will( $this->returnValue( $primary1Can ) );
415 $mock2 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
416 $mock2->expects( $this->any() )->method( 'getUniqueId' )
417 ->will( $this->returnValue( 'primary2' ) );
418 $mock2->expects( $this->any() )->method( 'testUserCanAuthenticate' )
419 ->with( $this->equalTo( 'UTSysop' ) )
420 ->will( $this->returnValue( $primary2Can ) );
421 $this->primaryauthMocks = [ $mock1, $mock2 ];
422
423 $this->initializeManager( true );
424 $this->assertSame( $expect, $this->manager->userCanAuthenticate( 'UTSysop' ) );
425 }
426
427 public static function provideUserCanAuthenticate() {
428 return [
429 [ false, false, false ],
430 [ true, false, true ],
431 [ false, true, true ],
432 [ true, true, true ],
433 ];
434 }
435
436 public function testRevokeAccessForUser() {
437 $this->initializeManager();
438
439 $mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
440 $mock->expects( $this->any() )->method( 'getUniqueId' )
441 ->will( $this->returnValue( 'primary' ) );
442 $mock->expects( $this->once() )->method( 'providerRevokeAccessForUser' )
443 ->with( $this->equalTo( 'UTSysop' ) );
444 $this->primaryauthMocks = [ $mock ];
445
446 $this->initializeManager( true );
447 $this->logger->setCollect( true );
448
449 $this->manager->revokeAccessForUser( 'UTSysop' );
450
451 $this->assertSame( [
452 [ LogLevel::INFO, 'Revoking access for {user}' ],
453 ], $this->logger->getBuffer() );
454 }
455
456 public function testProviderCreation() {
457 $mocks = [
458 'pre' => $this->getMockForAbstractClass( PreAuthenticationProvider::class ),
459 'primary' => $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class ),
460 'secondary' => $this->getMockForAbstractClass( SecondaryAuthenticationProvider::class ),
461 ];
462 foreach ( $mocks as $key => $mock ) {
463 $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( $key ) );
464 $mock->expects( $this->once() )->method( 'setLogger' );
465 $mock->expects( $this->once() )->method( 'setManager' );
466 $mock->expects( $this->once() )->method( 'setConfig' );
467 }
468 $this->preauthMocks = [ $mocks['pre'] ];
469 $this->primaryauthMocks = [ $mocks['primary'] ];
470 $this->secondaryauthMocks = [ $mocks['secondary'] ];
471
472 // Normal operation
473 $this->initializeManager();
474 $this->assertSame(
475 $mocks['primary'],
476 $this->managerPriv->getAuthenticationProvider( 'primary' )
477 );
478 $this->assertSame(
479 $mocks['secondary'],
480 $this->managerPriv->getAuthenticationProvider( 'secondary' )
481 );
482 $this->assertSame(
483 $mocks['pre'],
484 $this->managerPriv->getAuthenticationProvider( 'pre' )
485 );
486 $this->assertSame(
487 [ 'pre' => $mocks['pre'] ],
488 $this->managerPriv->getPreAuthenticationProviders()
489 );
490 $this->assertSame(
491 [ 'primary' => $mocks['primary'] ],
492 $this->managerPriv->getPrimaryAuthenticationProviders()
493 );
494 $this->assertSame(
495 [ 'secondary' => $mocks['secondary'] ],
496 $this->managerPriv->getSecondaryAuthenticationProviders()
497 );
498
499 // Duplicate IDs
500 $mock1 = $this->getMockForAbstractClass( PreAuthenticationProvider::class );
501 $mock2 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
502 $mock1->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
503 $mock2->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
504 $this->preauthMocks = [ $mock1 ];
505 $this->primaryauthMocks = [ $mock2 ];
506 $this->secondaryauthMocks = [];
507 $this->initializeManager( true );
508 try {
509 $this->managerPriv->getAuthenticationProvider( 'Y' );
510 $this->fail( 'Expected exception not thrown' );
511 } catch ( \RuntimeException $ex ) {
512 $class1 = get_class( $mock1 );
513 $class2 = get_class( $mock2 );
514 $this->assertSame(
515 "Duplicate specifications for id X (classes $class1 and $class2)", $ex->getMessage()
516 );
517 }
518
519 // Wrong classes
520 $mock = $this->getMockForAbstractClass( AuthenticationProvider::class );
521 $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
522 $class = get_class( $mock );
523 $this->preauthMocks = [ $mock ];
524 $this->primaryauthMocks = [ $mock ];
525 $this->secondaryauthMocks = [ $mock ];
526 $this->initializeManager( true );
527 try {
528 $this->managerPriv->getPreAuthenticationProviders();
529 $this->fail( 'Expected exception not thrown' );
530 } catch ( \RuntimeException $ex ) {
531 $this->assertSame(
532 "Expected instance of MediaWiki\\Auth\\PreAuthenticationProvider, got $class",
533 $ex->getMessage()
534 );
535 }
536 try {
537 $this->managerPriv->getPrimaryAuthenticationProviders();
538 $this->fail( 'Expected exception not thrown' );
539 } catch ( \RuntimeException $ex ) {
540 $this->assertSame(
541 "Expected instance of MediaWiki\\Auth\\PrimaryAuthenticationProvider, got $class",
542 $ex->getMessage()
543 );
544 }
545 try {
546 $this->managerPriv->getSecondaryAuthenticationProviders();
547 $this->fail( 'Expected exception not thrown' );
548 } catch ( \RuntimeException $ex ) {
549 $this->assertSame(
550 "Expected instance of MediaWiki\\Auth\\SecondaryAuthenticationProvider, got $class",
551 $ex->getMessage()
552 );
553 }
554
555 // Sorting
556 $mock1 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
557 $mock2 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
558 $mock3 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
559 $mock1->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'A' ) );
560 $mock2->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'B' ) );
561 $mock3->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'C' ) );
562 $this->preauthMocks = [];
563 $this->primaryauthMocks = [ $mock1, $mock2, $mock3 ];
564 $this->secondaryauthMocks = [];
565 $this->initializeConfig();
566 $config = $this->config->get( 'AuthManagerConfig' );
567
568 $this->initializeManager( false );
569 $this->assertSame(
570 [ 'A' => $mock1, 'B' => $mock2, 'C' => $mock3 ],
571 $this->managerPriv->getPrimaryAuthenticationProviders(),
572 'sanity check'
573 );
574
575 $config['primaryauth']['A']['sort'] = 100;
576 $config['primaryauth']['C']['sort'] = -1;
577 $this->config->set( 'AuthManagerConfig', $config );
578 $this->initializeManager( false );
579 $this->assertSame(
580 [ 'C' => $mock3, 'B' => $mock2, 'A' => $mock1 ],
581 $this->managerPriv->getPrimaryAuthenticationProviders()
582 );
583 }
584
585 public function testSetDefaultUserOptions() {
586 $this->initializeManager();
587
588 $context = \RequestContext::getMain();
589 $reset = new ScopedCallback( [ $context, 'setLanguage' ], [ $context->getLanguage() ] );
590 $context->setLanguage( 'de' );
591 $this->setMwGlobals( 'wgContLang', \Language::factory( 'zh' ) );
592
593 $user = \User::newFromName( self::usernameForCreation() );
594 $user->addToDatabase();
595 $oldToken = $user->getToken();
596 $this->managerPriv->setDefaultUserOptions( $user, false );
597 $user->saveSettings();
598 $this->assertNotEquals( $oldToken, $user->getToken() );
599 $this->assertSame( 'zh', $user->getOption( 'language' ) );
600 $this->assertSame( 'zh', $user->getOption( 'variant' ) );
601
602 $user = \User::newFromName( self::usernameForCreation() );
603 $user->addToDatabase();
604 $oldToken = $user->getToken();
605 $this->managerPriv->setDefaultUserOptions( $user, true );
606 $user->saveSettings();
607 $this->assertNotEquals( $oldToken, $user->getToken() );
608 $this->assertSame( 'de', $user->getOption( 'language' ) );
609 $this->assertSame( 'zh', $user->getOption( 'variant' ) );
610
611 $this->setMwGlobals( 'wgContLang', \Language::factory( 'en' ) );
612
613 $user = \User::newFromName( self::usernameForCreation() );
614 $user->addToDatabase();
615 $oldToken = $user->getToken();
616 $this->managerPriv->setDefaultUserOptions( $user, true );
617 $user->saveSettings();
618 $this->assertNotEquals( $oldToken, $user->getToken() );
619 $this->assertSame( 'de', $user->getOption( 'language' ) );
620 $this->assertSame( null, $user->getOption( 'variant' ) );
621 }
622
624 $mockA = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
625 $mockB = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
626 $mockB2 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
627 $mockA->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'A' ) );
628 $mockB->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'B' ) );
629 $mockB2->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'B' ) );
630 $this->primaryauthMocks = [ $mockA ];
631
632 $this->logger = new \TestLogger( true );
633
634 // Test without first initializing the configured providers
635 $this->initializeManager();
636 $this->manager->forcePrimaryAuthenticationProviders( [ $mockB ], 'testing' );
637 $this->assertSame(
638 [ 'B' => $mockB ], $this->managerPriv->getPrimaryAuthenticationProviders()
639 );
640 $this->assertSame( null, $this->managerPriv->getAuthenticationProvider( 'A' ) );
641 $this->assertSame( $mockB, $this->managerPriv->getAuthenticationProvider( 'B' ) );
642 $this->assertSame( [
643 [ LogLevel::WARNING, 'Overriding AuthManager primary authn because testing' ],
644 ], $this->logger->getBuffer() );
645 $this->logger->clearBuffer();
646
647 // Test with first initializing the configured providers
648 $this->initializeManager();
649 $this->assertSame( $mockA, $this->managerPriv->getAuthenticationProvider( 'A' ) );
650 $this->assertSame( null, $this->managerPriv->getAuthenticationProvider( 'B' ) );
651 $this->request->getSession()->setSecret( 'AuthManager::authnState', 'test' );
652 $this->request->getSession()->setSecret( 'AuthManager::accountCreationState', 'test' );
653 $this->manager->forcePrimaryAuthenticationProviders( [ $mockB ], 'testing' );
654 $this->assertSame(
655 [ 'B' => $mockB ], $this->managerPriv->getPrimaryAuthenticationProviders()
656 );
657 $this->assertSame( null, $this->managerPriv->getAuthenticationProvider( 'A' ) );
658 $this->assertSame( $mockB, $this->managerPriv->getAuthenticationProvider( 'B' ) );
659 $this->assertNull( $this->request->getSession()->getSecret( 'AuthManager::authnState' ) );
660 $this->assertNull(
661 $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' )
662 );
663 $this->assertSame( [
664 [ LogLevel::WARNING, 'Overriding AuthManager primary authn because testing' ],
665 [
666 LogLevel::WARNING,
667 'PrimaryAuthenticationProviders have already been accessed! I hope nothing breaks.'
668 ],
669 ], $this->logger->getBuffer() );
670 $this->logger->clearBuffer();
671
672 // Test duplicate IDs
673 $this->initializeManager();
674 try {
675 $this->manager->forcePrimaryAuthenticationProviders( [ $mockB, $mockB2 ], 'testing' );
676 $this->fail( 'Expected exception not thrown' );
677 } catch ( \RuntimeException $ex ) {
678 $class1 = get_class( $mockB );
679 $class2 = get_class( $mockB2 );
680 $this->assertSame(
681 "Duplicate specifications for id B (classes $class2 and $class1)", $ex->getMessage()
682 );
683 }
684
685 // Wrong classes
686 $mock = $this->getMockForAbstractClass( AuthenticationProvider::class );
687 $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
688 $class = get_class( $mock );
689 try {
690 $this->manager->forcePrimaryAuthenticationProviders( [ $mock ], 'testing' );
691 $this->fail( 'Expected exception not thrown' );
692 } catch ( \RuntimeException $ex ) {
693 $this->assertSame(
694 "Expected instance of MediaWiki\\Auth\\PrimaryAuthenticationProvider, got $class",
695 $ex->getMessage()
696 );
697 }
698
699 }
700
701 public function testBeginAuthentication() {
702 $this->initializeManager();
703
704 // Immutable session
705 list( $provider, $reset ) = $this->getMockSessionProvider( false );
706 $this->hook( 'UserLoggedIn', $this->never() );
707 $this->request->getSession()->setSecret( 'AuthManager::authnState', 'test' );
708 try {
709 $this->manager->beginAuthentication( [], 'http://localhost/' );
710 $this->fail( 'Expected exception not thrown' );
711 } catch ( \LogicException $ex ) {
712 $this->assertSame( 'Authentication is not possible now', $ex->getMessage() );
713 }
714 $this->unhook( 'UserLoggedIn' );
715 $this->assertNull( $this->request->getSession()->getSecret( 'AuthManager::authnState' ) );
716 ScopedCallback::consume( $reset );
717 $this->initializeManager( true );
718
719 // CreatedAccountAuthenticationRequest
720 $user = \User::newFromName( 'UTSysop' );
721 $reqs = [
722 new CreatedAccountAuthenticationRequest( $user->getId(), $user->getName() )
723 ];
724 $this->hook( 'UserLoggedIn', $this->never() );
725 try {
726 $this->manager->beginAuthentication( $reqs, 'http://localhost/' );
727 $this->fail( 'Expected exception not thrown' );
728 } catch ( \LogicException $ex ) {
729 $this->assertSame(
730 'CreatedAccountAuthenticationRequests are only valid on the same AuthManager ' .
731 'that created the account',
732 $ex->getMessage()
733 );
734 }
735 $this->unhook( 'UserLoggedIn' );
736
737 $this->request->getSession()->clear();
738 $this->request->getSession()->setSecret( 'AuthManager::authnState', 'test' );
739 $this->managerPriv->createdAccountAuthenticationRequests = [ $reqs[0] ];
740 $this->hook( 'UserLoggedIn', $this->once() )
741 ->with( $this->callback( function ( $u ) use ( $user ) {
742 return $user->getId() === $u->getId() && $user->getName() === $u->getName();
743 } ) );
744 $this->hook( 'AuthManagerLoginAuthenticateAudit', $this->once() );
745 $this->logger->setCollect( true );
746 $ret = $this->manager->beginAuthentication( $reqs, 'http://localhost/' );
747 $this->logger->setCollect( false );
748 $this->unhook( 'UserLoggedIn' );
749 $this->unhook( 'AuthManagerLoginAuthenticateAudit' );
750 $this->assertSame( AuthenticationResponse::PASS, $ret->status );
751 $this->assertSame( $user->getName(), $ret->username );
752 $this->assertSame( $user->getId(), $this->request->getSessionData( 'AuthManager:lastAuthId' ) );
753 $this->assertEquals(
754 time(), $this->request->getSessionData( 'AuthManager:lastAuthTimestamp' ),
755 'timestamp ±1', 1
756 );
757 $this->assertNull( $this->request->getSession()->getSecret( 'AuthManager::authnState' ) );
758 $this->assertSame( $user->getId(), $this->request->getSession()->getUser()->getId() );
759 $this->assertSame( [
760 [ LogLevel::INFO, 'Logging in {user} after account creation' ],
761 ], $this->logger->getBuffer() );
762 }
763
764 public function testCreateFromLogin() {
765 $user = \User::newFromName( 'UTSysop' );
766 $req1 = $this->getMock( AuthenticationRequest::class );
767 $req2 = $this->getMock( AuthenticationRequest::class );
768 $req3 = $this->getMock( AuthenticationRequest::class );
769 $userReq = new UsernameAuthenticationRequest;
770 $userReq->username = 'UTDummy';
771
772 $req1->returnToUrl = 'http://localhost/';
773 $req2->returnToUrl = 'http://localhost/';
774 $req3->returnToUrl = 'http://localhost/';
775 $req3->username = 'UTDummy';
776 $userReq->returnToUrl = 'http://localhost/';
777
778 // Passing one into beginAuthentication(), and an immediate FAIL
779 $primary = $this->getMockForAbstractClass( AbstractPrimaryAuthenticationProvider::class );
780 $this->primaryauthMocks = [ $primary ];
781 $this->initializeManager( true );
783 $res->createRequest = $req1;
784 $primary->expects( $this->any() )->method( 'beginPrimaryAuthentication' )
785 ->will( $this->returnValue( $res ) );
787 null, [ $req2->getUniqueId() => $req2 ]
788 );
789 $this->logger->setCollect( true );
790 $ret = $this->manager->beginAuthentication( [ $createReq ], 'http://localhost/' );
791 $this->logger->setCollect( false );
792 $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
793 $this->assertInstanceOf( CreateFromLoginAuthenticationRequest::class, $ret->createRequest );
794 $this->assertSame( $req1, $ret->createRequest->createRequest );
795 $this->assertEquals( [ $req2->getUniqueId() => $req2 ], $ret->createRequest->maybeLink );
796
797 // UI, then FAIL in beginAuthentication()
798 $primary = $this->getMockBuilder( AbstractPrimaryAuthenticationProvider::class )
799 ->setMethods( [ 'continuePrimaryAuthentication' ] )
800 ->getMockForAbstractClass();
801 $this->primaryauthMocks = [ $primary ];
802 $this->initializeManager( true );
803 $primary->expects( $this->any() )->method( 'beginPrimaryAuthentication' )
804 ->will( $this->returnValue(
805 AuthenticationResponse::newUI( [ $req1 ], wfMessage( 'foo' ) )
806 ) );
808 $res->createRequest = $req2;
809 $primary->expects( $this->any() )->method( 'continuePrimaryAuthentication' )
810 ->will( $this->returnValue( $res ) );
811 $this->logger->setCollect( true );
812 $ret = $this->manager->beginAuthentication( [], 'http://localhost/' );
813 $this->assertSame( AuthenticationResponse::UI, $ret->status, 'sanity check' );
814 $ret = $this->manager->continueAuthentication( [] );
815 $this->logger->setCollect( false );
816 $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
817 $this->assertInstanceOf( CreateFromLoginAuthenticationRequest::class, $ret->createRequest );
818 $this->assertSame( $req2, $ret->createRequest->createRequest );
819 $this->assertEquals( [], $ret->createRequest->maybeLink );
820
821 // Pass into beginAccountCreation(), see that maybeLink and createRequest get copied
822 $primary = $this->getMockForAbstractClass( AbstractPrimaryAuthenticationProvider::class );
823 $this->primaryauthMocks = [ $primary ];
824 $this->initializeManager( true );
825 $createReq = new CreateFromLoginAuthenticationRequest( $req3, [ $req2 ] );
826 $createReq->returnToUrl = 'http://localhost/';
827 $createReq->username = 'UTDummy';
828 $res = AuthenticationResponse::newUI( [ $req1 ], wfMessage( 'foo' ) );
829 $primary->expects( $this->any() )->method( 'beginPrimaryAccountCreation' )
830 ->with( $this->anything(), $this->anything(), [ $userReq, $createReq, $req3 ] )
831 ->will( $this->returnValue( $res ) );
832 $primary->expects( $this->any() )->method( 'accountCreationType' )
833 ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
834 $this->logger->setCollect( true );
835 $ret = $this->manager->beginAccountCreation(
836 $user, [ $userReq, $createReq ], 'http://localhost/'
837 );
838 $this->logger->setCollect( false );
839 $this->assertSame( AuthenticationResponse::UI, $ret->status );
840 $state = $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' );
841 $this->assertNotNull( $state );
842 $this->assertEquals( [ $userReq, $createReq, $req3 ], $state['reqs'] );
843 $this->assertEquals( [ $req2 ], $state['maybeLink'] );
844 }
845
854 public function testAuthentication(
855 StatusValue $preResponse, array $primaryResponses, array $secondaryResponses,
856 array $managerResponses, $link = false
857 ) {
858 $this->initializeManager();
859 $user = \User::newFromName( 'UTSysop' );
860 $id = $user->getId();
861 $name = $user->getName();
862
863 // Set up lots of mocks...
865 $req->rememberMe = (bool)rand( 0, 1 );
866 $req->pre = $preResponse;
867 $req->primary = $primaryResponses;
868 $req->secondary = $secondaryResponses;
869 $mocks = [];
870 foreach ( [ 'pre', 'primary', 'secondary' ] as $key ) {
871 $class = ucfirst( $key ) . 'AuthenticationProvider';
872 $mocks[$key] = $this->getMockForAbstractClass(
873 "MediaWiki\\Auth\\$class", [], "Mock$class"
874 );
875 $mocks[$key]->expects( $this->any() )->method( 'getUniqueId' )
876 ->will( $this->returnValue( $key ) );
877 $mocks[$key . '2'] = $this->getMockForAbstractClass(
878 "MediaWiki\\Auth\\$class", [], "Mock$class"
879 );
880 $mocks[$key . '2']->expects( $this->any() )->method( 'getUniqueId' )
881 ->will( $this->returnValue( $key . '2' ) );
882 $mocks[$key . '3'] = $this->getMockForAbstractClass(
883 "MediaWiki\\Auth\\$class", [], "Mock$class"
884 );
885 $mocks[$key . '3']->expects( $this->any() )->method( 'getUniqueId' )
886 ->will( $this->returnValue( $key . '3' ) );
887 }
888 foreach ( $mocks as $mock ) {
889 $mock->expects( $this->any() )->method( 'getAuthenticationRequests' )
890 ->will( $this->returnValue( [] ) );
891 }
892
893 $mocks['pre']->expects( $this->once() )->method( 'testForAuthentication' )
894 ->will( $this->returnCallback( function ( $reqs ) use ( $req ) {
895 $this->assertContains( $req, $reqs );
896 return $req->pre;
897 } ) );
898
899 $ct = count( $req->primary );
900 $callback = $this->returnCallback( function ( $reqs ) use ( $req ) {
901 $this->assertContains( $req, $reqs );
902 return array_shift( $req->primary );
903 } );
904 $mocks['primary']->expects( $this->exactly( min( 1, $ct ) ) )
905 ->method( 'beginPrimaryAuthentication' )
906 ->will( $callback );
907 $mocks['primary']->expects( $this->exactly( max( 0, $ct - 1 ) ) )
908 ->method( 'continuePrimaryAuthentication' )
909 ->will( $callback );
910 if ( $link ) {
911 $mocks['primary']->expects( $this->any() )->method( 'accountCreationType' )
912 ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_LINK ) );
913 }
914
915 $ct = count( $req->secondary );
916 $callback = $this->returnCallback( function ( $user, $reqs ) use ( $id, $name, $req ) {
917 $this->assertSame( $id, $user->getId() );
918 $this->assertSame( $name, $user->getName() );
919 $this->assertContains( $req, $reqs );
920 return array_shift( $req->secondary );
921 } );
922 $mocks['secondary']->expects( $this->exactly( min( 1, $ct ) ) )
923 ->method( 'beginSecondaryAuthentication' )
924 ->will( $callback );
925 $mocks['secondary']->expects( $this->exactly( max( 0, $ct - 1 ) ) )
926 ->method( 'continueSecondaryAuthentication' )
927 ->will( $callback );
928
930 $mocks['pre2']->expects( $this->atMost( 1 ) )->method( 'testForAuthentication' )
931 ->will( $this->returnValue( StatusValue::newGood() ) );
932 $mocks['primary2']->expects( $this->atMost( 1 ) )->method( 'beginPrimaryAuthentication' )
933 ->will( $this->returnValue( $abstain ) );
934 $mocks['primary2']->expects( $this->never() )->method( 'continuePrimaryAuthentication' );
935 $mocks['secondary2']->expects( $this->atMost( 1 ) )->method( 'beginSecondaryAuthentication' )
936 ->will( $this->returnValue( $abstain ) );
937 $mocks['secondary2']->expects( $this->never() )->method( 'continueSecondaryAuthentication' );
938 $mocks['secondary3']->expects( $this->atMost( 1 ) )->method( 'beginSecondaryAuthentication' )
939 ->will( $this->returnValue( $abstain ) );
940 $mocks['secondary3']->expects( $this->never() )->method( 'continueSecondaryAuthentication' );
941
942 $this->preauthMocks = [ $mocks['pre'], $mocks['pre2'] ];
943 $this->primaryauthMocks = [ $mocks['primary'], $mocks['primary2'] ];
944 $this->secondaryauthMocks = [
945 $mocks['secondary3'], $mocks['secondary'], $mocks['secondary2'],
946 // So linking happens
948 ];
949 $this->initializeManager( true );
950 $this->logger->setCollect( true );
951
952 $constraint = \PHPUnit_Framework_Assert::logicalOr(
953 $this->equalTo( AuthenticationResponse::PASS ),
954 $this->equalTo( AuthenticationResponse::FAIL )
955 );
956 $providers = array_filter(
957 array_merge(
958 $this->preauthMocks, $this->primaryauthMocks, $this->secondaryauthMocks
959 ),
960 function ( $p ) {
961 return is_callable( [ $p, 'expects' ] );
962 }
963 );
964 foreach ( $providers as $p ) {
965 $p->postCalled = false;
966 $p->expects( $this->atMost( 1 ) )->method( 'postAuthentication' )
967 ->willReturnCallback( function ( $user, $response ) use ( $constraint, $p ) {
968 if ( $user !== null ) {
969 $this->assertInstanceOf( 'User', $user );
970 $this->assertSame( 'UTSysop', $user->getName() );
971 }
972 $this->assertInstanceOf( AuthenticationResponse::class, $response );
973 $this->assertThat( $response->status, $constraint );
974 $p->postCalled = $response->status;
975 } );
976 }
977
978 $session = $this->request->getSession();
979 $session->setRememberUser( !$req->rememberMe );
980
981 foreach ( $managerResponses as $i => $response ) {
984 if ( $success ) {
985 $this->hook( 'UserLoggedIn', $this->once() )
986 ->with( $this->callback( function ( $user ) use ( $id, $name ) {
987 return $user->getId() === $id && $user->getName() === $name;
988 } ) );
989 } else {
990 $this->hook( 'UserLoggedIn', $this->never() );
991 }
992 if ( $success || (
995 $response->message->getKey() !== 'authmanager-authn-not-in-progress' &&
996 $response->message->getKey() !== 'authmanager-authn-no-primary'
997 )
998 ) {
999 $this->hook( 'AuthManagerLoginAuthenticateAudit', $this->once() );
1000 } else {
1001 $this->hook( 'AuthManagerLoginAuthenticateAudit', $this->never() );
1002 }
1003
1004 $ex = null;
1005 try {
1006 if ( !$i ) {
1007 $ret = $this->manager->beginAuthentication( [ $req ], 'http://localhost/' );
1008 } else {
1009 $ret = $this->manager->continueAuthentication( [ $req ] );
1010 }
1011 if ( $response instanceof \Exception ) {
1012 $this->fail( 'Expected exception not thrown', "Response $i" );
1013 }
1014 } catch ( \Exception $ex ) {
1015 if ( !$response instanceof \Exception ) {
1016 throw $ex;
1017 }
1018 $this->assertEquals( $response->getMessage(), $ex->getMessage(), "Response $i, exception" );
1019 $this->assertNull( $session->getSecret( 'AuthManager::authnState' ),
1020 "Response $i, exception, session state" );
1021 $this->unhook( 'UserLoggedIn' );
1022 $this->unhook( 'AuthManagerLoginAuthenticateAudit' );
1023 return;
1024 }
1025
1026 $this->unhook( 'UserLoggedIn' );
1027 $this->unhook( 'AuthManagerLoginAuthenticateAudit' );
1028
1029 $this->assertSame( 'http://localhost/', $req->returnToUrl );
1030
1031 $ret->message = $this->message( $ret->message );
1032 $this->assertEquals( $response, $ret, "Response $i, response" );
1033 if ( $success ) {
1034 $this->assertSame( $id, $session->getUser()->getId(),
1035 "Response $i, authn" );
1036 } else {
1037 $this->assertSame( 0, $session->getUser()->getId(),
1038 "Response $i, authn" );
1039 }
1040 if ( $success || $response->status === AuthenticationResponse::FAIL ) {
1041 $this->assertNull( $session->getSecret( 'AuthManager::authnState' ),
1042 "Response $i, session state" );
1043 foreach ( $providers as $p ) {
1044 $this->assertSame( $response->status, $p->postCalled,
1045 "Response $i, post-auth callback called" );
1046 }
1047 } else {
1048 $this->assertNotNull( $session->getSecret( 'AuthManager::authnState' ),
1049 "Response $i, session state" );
1050 foreach ( $ret->neededRequests as $neededReq ) {
1051 $this->assertEquals( AuthManager::ACTION_LOGIN, $neededReq->action,
1052 "Response $i, neededRequest action" );
1053 }
1054 $this->assertEquals(
1055 $ret->neededRequests,
1056 $this->manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN_CONTINUE ),
1057 "Response $i, continuation check"
1058 );
1059 foreach ( $providers as $p ) {
1060 $this->assertFalse( $p->postCalled, "Response $i, post-auth callback not called" );
1061 }
1062 }
1063
1064 $state = $session->getSecret( 'AuthManager::authnState' );
1065 $maybeLink = isset( $state['maybeLink'] ) ? $state['maybeLink'] : [];
1066 if ( $link && $response->status === AuthenticationResponse::RESTART ) {
1067 $this->assertEquals(
1068 $response->createRequest->maybeLink,
1069 $maybeLink,
1070 "Response $i, maybeLink"
1071 );
1072 } else {
1073 $this->assertEquals( [], $maybeLink, "Response $i, maybeLink" );
1074 }
1075 }
1076
1077 if ( $success ) {
1078 $this->assertSame( $req->rememberMe, $session->shouldRememberUser(),
1079 'rememberMe checkbox had effect' );
1080 } else {
1081 $this->assertNotSame( $req->rememberMe, $session->shouldRememberUser(),
1082 'rememberMe checkbox wasn\'t applied' );
1083 }
1084 }
1085
1086 public function provideAuthentication() {
1087 $user = \User::newFromName( 'UTSysop' );
1088 $id = $user->getId();
1089 $name = $user->getName();
1090
1091 $rememberReq = new RememberMeAuthenticationRequest;
1092 $rememberReq->action = AuthManager::ACTION_LOGIN;
1093
1094 $req = $this->getMockForAbstractClass( AuthenticationRequest::class );
1095 $req->foobar = 'baz';
1096 $restartResponse = AuthenticationResponse::newRestart(
1097 $this->message( 'authmanager-authn-no-local-user' )
1098 );
1099 $restartResponse->neededRequests = [ $rememberReq ];
1100
1101 $restartResponse2Pass = AuthenticationResponse::newPass( null );
1102 $restartResponse2Pass->linkRequest = $req;
1103 $restartResponse2 = AuthenticationResponse::newRestart(
1104 $this->message( 'authmanager-authn-no-local-user-link' )
1105 );
1106 $restartResponse2->createRequest = new CreateFromLoginAuthenticationRequest(
1107 null, [ $req->getUniqueId() => $req ]
1108 );
1109 $restartResponse2->createRequest->action = AuthManager::ACTION_LOGIN;
1110 $restartResponse2->neededRequests = [ $rememberReq, $restartResponse2->createRequest ];
1111
1112 return [
1113 'Failure in pre-auth' => [
1114 StatusValue::newFatal( 'fail-from-pre' ),
1115 [],
1116 [],
1117 [
1118 AuthenticationResponse::newFail( $this->message( 'fail-from-pre' ) ),
1120 $this->message( 'authmanager-authn-not-in-progress' )
1121 ),
1122 ]
1123 ],
1124 'Failure in primary' => [
1125 StatusValue::newGood(),
1126 $tmp = [
1127 AuthenticationResponse::newFail( $this->message( 'fail-from-primary' ) ),
1128 ],
1129 [],
1130 $tmp
1131 ],
1132 'All primary abstain' => [
1133 StatusValue::newGood(),
1134 [
1136 ],
1137 [],
1138 [
1139 AuthenticationResponse::newFail( $this->message( 'authmanager-authn-no-primary' ) )
1140 ]
1141 ],
1142 'Primary UI, then redirect, then fail' => [
1143 StatusValue::newGood(),
1144 $tmp = [
1145 AuthenticationResponse::newUI( [ $req ], $this->message( '...' ) ),
1146 AuthenticationResponse::newRedirect( [ $req ], '/foo.html', [ 'foo' => 'bar' ] ),
1147 AuthenticationResponse::newFail( $this->message( 'fail-in-primary-continue' ) ),
1148 ],
1149 [],
1150 $tmp
1151 ],
1152 'Primary redirect, then abstain' => [
1153 StatusValue::newGood(),
1154 [
1156 [ $req ], '/foo.html', [ 'foo' => 'bar' ]
1157 ),
1159 ],
1160 [],
1161 [
1162 $tmp,
1163 new \DomainException(
1164 'MockPrimaryAuthenticationProvider::continuePrimaryAuthentication() returned ABSTAIN'
1165 )
1166 ]
1167 ],
1168 'Primary UI, then pass with no local user' => [
1169 StatusValue::newGood(),
1170 [
1171 $tmp = AuthenticationResponse::newUI( [ $req ], $this->message( '...' ) ),
1173 ],
1174 [],
1175 [
1176 $tmp,
1177 $restartResponse,
1178 ]
1179 ],
1180 'Primary UI, then pass with no local user (link type)' => [
1181 StatusValue::newGood(),
1182 [
1183 $tmp = AuthenticationResponse::newUI( [ $req ], $this->message( '...' ) ),
1184 $restartResponse2Pass,
1185 ],
1186 [],
1187 [
1188 $tmp,
1189 $restartResponse2,
1190 ],
1191 true
1192 ],
1193 'Primary pass with invalid username' => [
1194 StatusValue::newGood(),
1195 [
1197 ],
1198 [],
1199 [
1200 new \DomainException( 'MockPrimaryAuthenticationProvider returned an invalid username: <>' ),
1201 ]
1202 ],
1203 'Secondary fail' => [
1204 StatusValue::newGood(),
1205 [
1207 ],
1208 $tmp = [
1209 AuthenticationResponse::newFail( $this->message( 'fail-in-secondary' ) ),
1210 ],
1211 $tmp
1212 ],
1213 'Secondary UI, then abstain' => [
1214 StatusValue::newGood(),
1215 [
1217 ],
1218 [
1219 $tmp = AuthenticationResponse::newUI( [ $req ], $this->message( '...' ) ),
1221 ],
1222 [
1223 $tmp,
1225 ]
1226 ],
1227 'Secondary pass' => [
1228 StatusValue::newGood(),
1229 [
1231 ],
1232 [
1234 ],
1235 [
1237 ]
1238 ],
1239 ];
1240 }
1241
1248 public function testUserExists( $primary1Exists, $primary2Exists, $expect ) {
1249 $mock1 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
1250 $mock1->expects( $this->any() )->method( 'getUniqueId' )
1251 ->will( $this->returnValue( 'primary1' ) );
1252 $mock1->expects( $this->any() )->method( 'testUserExists' )
1253 ->with( $this->equalTo( 'UTSysop' ) )
1254 ->will( $this->returnValue( $primary1Exists ) );
1255 $mock2 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
1256 $mock2->expects( $this->any() )->method( 'getUniqueId' )
1257 ->will( $this->returnValue( 'primary2' ) );
1258 $mock2->expects( $this->any() )->method( 'testUserExists' )
1259 ->with( $this->equalTo( 'UTSysop' ) )
1260 ->will( $this->returnValue( $primary2Exists ) );
1261 $this->primaryauthMocks = [ $mock1, $mock2 ];
1262
1263 $this->initializeManager( true );
1264 $this->assertSame( $expect, $this->manager->userExists( 'UTSysop' ) );
1265 }
1266
1267 public static function provideUserExists() {
1268 return [
1269 [ false, false, false ],
1270 [ true, false, true ],
1271 [ false, true, true ],
1272 [ true, true, true ],
1273 ];
1274 }
1275
1282 public function testAllowsAuthenticationDataChange( $primaryReturn, $secondaryReturn, $expect ) {
1283 $req = $this->getMockForAbstractClass( AuthenticationRequest::class );
1284
1285 $mock1 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
1286 $mock1->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( '1' ) );
1287 $mock1->expects( $this->any() )->method( 'providerAllowsAuthenticationDataChange' )
1288 ->with( $this->equalTo( $req ) )
1289 ->will( $this->returnValue( $primaryReturn ) );
1290 $mock2 = $this->getMockForAbstractClass( SecondaryAuthenticationProvider::class );
1291 $mock2->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( '2' ) );
1292 $mock2->expects( $this->any() )->method( 'providerAllowsAuthenticationDataChange' )
1293 ->with( $this->equalTo( $req ) )
1294 ->will( $this->returnValue( $secondaryReturn ) );
1295
1296 $this->primaryauthMocks = [ $mock1 ];
1297 $this->secondaryauthMocks = [ $mock2 ];
1298 $this->initializeManager( true );
1299 $this->assertEquals( $expect, $this->manager->allowsAuthenticationDataChange( $req ) );
1300 }
1301
1302 public static function provideAllowsAuthenticationDataChange() {
1303 $ignored = \Status::newGood( 'ignored' );
1304 $ignored->warning( 'authmanager-change-not-supported' );
1305
1306 $okFromPrimary = StatusValue::newGood();
1307 $okFromPrimary->warning( 'warning-from-primary' );
1308 $okFromSecondary = StatusValue::newGood();
1309 $okFromSecondary->warning( 'warning-from-secondary' );
1310
1311 return [
1312 [
1313 StatusValue::newGood(),
1314 StatusValue::newGood(),
1315 \Status::newGood(),
1316 ],
1317 [
1318 StatusValue::newGood(),
1319 StatusValue::newGood( 'ignore' ),
1320 \Status::newGood(),
1321 ],
1322 [
1323 StatusValue::newGood( 'ignored' ),
1324 StatusValue::newGood(),
1325 \Status::newGood(),
1326 ],
1327 [
1328 StatusValue::newGood( 'ignored' ),
1329 StatusValue::newGood( 'ignored' ),
1330 $ignored,
1331 ],
1332 [
1333 StatusValue::newFatal( 'fail from primary' ),
1334 StatusValue::newGood(),
1335 \Status::newFatal( 'fail from primary' ),
1336 ],
1337 [
1338 $okFromPrimary,
1339 StatusValue::newGood(),
1340 \Status::wrap( $okFromPrimary ),
1341 ],
1342 [
1343 StatusValue::newGood(),
1344 StatusValue::newFatal( 'fail from secondary' ),
1345 \Status::newFatal( 'fail from secondary' ),
1346 ],
1347 [
1348 StatusValue::newGood(),
1349 $okFromSecondary,
1350 \Status::wrap( $okFromSecondary ),
1351 ],
1352 ];
1353 }
1354
1356 $req = $this->getMockForAbstractClass( AuthenticationRequest::class );
1357 $req->username = 'UTSysop';
1358
1359 $mock1 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
1360 $mock1->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( '1' ) );
1361 $mock1->expects( $this->once() )->method( 'providerChangeAuthenticationData' )
1362 ->with( $this->equalTo( $req ) );
1363 $mock2 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
1364 $mock2->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( '2' ) );
1365 $mock2->expects( $this->once() )->method( 'providerChangeAuthenticationData' )
1366 ->with( $this->equalTo( $req ) );
1367
1368 $this->primaryauthMocks = [ $mock1, $mock2 ];
1369 $this->initializeManager( true );
1370 $this->logger->setCollect( true );
1371 $this->manager->changeAuthenticationData( $req );
1372 $this->assertSame( [
1373 [ LogLevel::INFO, 'Changing authentication data for {user} class {what}' ],
1374 ], $this->logger->getBuffer() );
1375 }
1376
1377 public function testCanCreateAccounts() {
1378 $types = [
1382 ];
1383
1384 foreach ( $types as $type => $can ) {
1385 $mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
1386 $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( $type ) );
1387 $mock->expects( $this->any() )->method( 'accountCreationType' )
1388 ->will( $this->returnValue( $type ) );
1389 $this->primaryauthMocks = [ $mock ];
1390 $this->initializeManager( true );
1391 $this->assertSame( $can, $this->manager->canCreateAccounts(), $type );
1392 }
1393 }
1394
1397
1398 $this->stashMwGlobals( [ 'wgGroupPermissions' ] );
1399
1400 $this->initializeManager( true );
1401
1402 $wgGroupPermissions['*']['createaccount'] = true;
1403 $this->assertEquals(
1404 \Status::newGood(),
1405 $this->manager->checkAccountCreatePermissions( new \User )
1406 );
1407
1408 $this->setMwGlobals( [ 'wgReadOnly' => 'Because' ] );
1409 $this->assertEquals(
1410 \Status::newFatal( 'readonlytext', 'Because' ),
1411 $this->manager->checkAccountCreatePermissions( new \User )
1412 );
1413 $this->setMwGlobals( [ 'wgReadOnly' => false ] );
1414
1415 $wgGroupPermissions['*']['createaccount'] = false;
1416 $status = $this->manager->checkAccountCreatePermissions( new \User );
1417 $this->assertFalse( $status->isOK() );
1418 $this->assertTrue( $status->hasMessage( 'badaccess-groups' ) );
1419 $wgGroupPermissions['*']['createaccount'] = true;
1420
1421 $user = \User::newFromName( 'UTBlockee' );
1422 if ( $user->getID() == 0 ) {
1423 $user->addToDatabase();
1424 \TestUser::setPasswordForUser( $user, 'UTBlockeePassword' );
1425 $user->saveSettings();
1426 }
1427 $oldBlock = \Block::newFromTarget( 'UTBlockee' );
1428 if ( $oldBlock ) {
1429 // An old block will prevent our new one from saving.
1430 $oldBlock->delete();
1431 }
1432 $blockOptions = [
1433 'address' => 'UTBlockee',
1434 'user' => $user->getID(),
1435 'reason' => __METHOD__,
1436 'expiry' => time() + 100500,
1437 'createAccount' => true,
1438 ];
1439 $block = new \Block( $blockOptions );
1440 $block->insert();
1441 $status = $this->manager->checkAccountCreatePermissions( $user );
1442 $this->assertFalse( $status->isOK() );
1443 $this->assertTrue( $status->hasMessage( 'cantcreateaccount-text' ) );
1444
1445 $blockOptions = [
1446 'address' => '127.0.0.0/24',
1447 'reason' => __METHOD__,
1448 'expiry' => time() + 100500,
1449 'createAccount' => true,
1450 ];
1451 $block = new \Block( $blockOptions );
1452 $block->insert();
1453 $scopeVariable = new ScopedCallback( [ $block, 'delete' ] );
1454 $status = $this->manager->checkAccountCreatePermissions( new \User );
1455 $this->assertFalse( $status->isOK() );
1456 $this->assertTrue( $status->hasMessage( 'cantcreateaccount-range-text' ) );
1457 ScopedCallback::consume( $scopeVariable );
1458
1459 $this->setMwGlobals( [
1460 'wgEnableDnsBlacklist' => true,
1461 'wgDnsBlacklistUrls' => [
1462 'local.wmftest.net', // This will resolve for every subdomain, which works to test "listed?"
1463 ],
1464 'wgProxyWhitelist' => [],
1465 ] );
1466 $status = $this->manager->checkAccountCreatePermissions( new \User );
1467 $this->assertFalse( $status->isOK() );
1468 $this->assertTrue( $status->hasMessage( 'sorbs_create_account_reason' ) );
1469 $this->setMwGlobals( 'wgProxyWhitelist', [ '127.0.0.1' ] );
1470 $status = $this->manager->checkAccountCreatePermissions( new \User );
1471 $this->assertTrue( $status->isGood() );
1472 }
1473
1478 private static function usernameForCreation( $uniq = '' ) {
1479 $i = 0;
1480 do {
1481 $username = "UTAuthManagerTestAccountCreation" . $uniq . ++$i;
1482 } while ( \User::newFromName( $username )->getId() !== 0 );
1483 return $username;
1484 }
1485
1486 public function testCanCreateAccount() {
1488 $this->initializeManager();
1489
1490 $this->assertEquals(
1491 \Status::newFatal( 'authmanager-create-disabled' ),
1492 $this->manager->canCreateAccount( $username )
1493 );
1494
1495 $mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
1496 $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
1497 $mock->expects( $this->any() )->method( 'accountCreationType' )
1498 ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
1499 $mock->expects( $this->any() )->method( 'testUserExists' )->will( $this->returnValue( true ) );
1500 $mock->expects( $this->any() )->method( 'testUserForCreation' )
1501 ->will( $this->returnValue( StatusValue::newGood() ) );
1502 $this->primaryauthMocks = [ $mock ];
1503 $this->initializeManager( true );
1504
1505 $this->assertEquals(
1506 \Status::newFatal( 'userexists' ),
1507 $this->manager->canCreateAccount( $username )
1508 );
1509
1510 $mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
1511 $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
1512 $mock->expects( $this->any() )->method( 'accountCreationType' )
1513 ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
1514 $mock->expects( $this->any() )->method( 'testUserExists' )->will( $this->returnValue( false ) );
1515 $mock->expects( $this->any() )->method( 'testUserForCreation' )
1516 ->will( $this->returnValue( StatusValue::newGood() ) );
1517 $this->primaryauthMocks = [ $mock ];
1518 $this->initializeManager( true );
1519
1520 $this->assertEquals(
1521 \Status::newFatal( 'noname' ),
1522 $this->manager->canCreateAccount( $username . '<>' )
1523 );
1524
1525 $this->assertEquals(
1526 \Status::newFatal( 'userexists' ),
1527 $this->manager->canCreateAccount( 'UTSysop' )
1528 );
1529
1530 $this->assertEquals(
1531 \Status::newGood(),
1532 $this->manager->canCreateAccount( $username )
1533 );
1534
1535 $mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
1536 $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
1537 $mock->expects( $this->any() )->method( 'accountCreationType' )
1538 ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
1539 $mock->expects( $this->any() )->method( 'testUserExists' )->will( $this->returnValue( false ) );
1540 $mock->expects( $this->any() )->method( 'testUserForCreation' )
1541 ->will( $this->returnValue( StatusValue::newFatal( 'fail' ) ) );
1542 $this->primaryauthMocks = [ $mock ];
1543 $this->initializeManager( true );
1544
1545 $this->assertEquals(
1546 \Status::newFatal( 'fail' ),
1547 $this->manager->canCreateAccount( $username )
1548 );
1549 }
1550
1551 public function testBeginAccountCreation() {
1552 $creator = \User::newFromName( 'UTSysop' );
1553 $userReq = new UsernameAuthenticationRequest;
1554 $this->logger = new \TestLogger( false, function ( $message, $level ) {
1555 return $level === LogLevel::DEBUG ? null : $message;
1556 } );
1557 $this->initializeManager();
1558
1559 $this->request->getSession()->setSecret( 'AuthManager::accountCreationState', 'test' );
1560 $this->hook( 'LocalUserCreated', $this->never() );
1561 try {
1562 $this->manager->beginAccountCreation(
1563 $creator, [], 'http://localhost/'
1564 );
1565 $this->fail( 'Expected exception not thrown' );
1566 } catch ( \LogicException $ex ) {
1567 $this->assertEquals( 'Account creation is not possible', $ex->getMessage() );
1568 }
1569 $this->unhook( 'LocalUserCreated' );
1570 $this->assertNull(
1571 $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' )
1572 );
1573
1574 $mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
1575 $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
1576 $mock->expects( $this->any() )->method( 'accountCreationType' )
1577 ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
1578 $mock->expects( $this->any() )->method( 'testUserExists' )->will( $this->returnValue( true ) );
1579 $mock->expects( $this->any() )->method( 'testUserForCreation' )
1580 ->will( $this->returnValue( StatusValue::newGood() ) );
1581 $this->primaryauthMocks = [ $mock ];
1582 $this->initializeManager( true );
1583
1584 $this->hook( 'LocalUserCreated', $this->never() );
1585 $ret = $this->manager->beginAccountCreation( $creator, [], 'http://localhost/' );
1586 $this->unhook( 'LocalUserCreated' );
1587 $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
1588 $this->assertSame( 'noname', $ret->message->getKey() );
1589
1590 $this->hook( 'LocalUserCreated', $this->never() );
1591 $userReq->username = self::usernameForCreation();
1592 $userReq2 = new UsernameAuthenticationRequest;
1593 $userReq2->username = $userReq->username . 'X';
1594 $ret = $this->manager->beginAccountCreation(
1595 $creator, [ $userReq, $userReq2 ], 'http://localhost/'
1596 );
1597 $this->unhook( 'LocalUserCreated' );
1598 $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
1599 $this->assertSame( 'noname', $ret->message->getKey() );
1600
1601 $this->setMwGlobals( [ 'wgReadOnly' => 'Because' ] );
1602 $this->hook( 'LocalUserCreated', $this->never() );
1603 $userReq->username = self::usernameForCreation();
1604 $ret = $this->manager->beginAccountCreation( $creator, [ $userReq ], 'http://localhost/' );
1605 $this->unhook( 'LocalUserCreated' );
1606 $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
1607 $this->assertSame( 'readonlytext', $ret->message->getKey() );
1608 $this->assertSame( [ 'Because' ], $ret->message->getParams() );
1609 $this->setMwGlobals( [ 'wgReadOnly' => false ] );
1610
1611 $this->hook( 'LocalUserCreated', $this->never() );
1612 $userReq->username = self::usernameForCreation();
1613 $ret = $this->manager->beginAccountCreation( $creator, [ $userReq ], 'http://localhost/' );
1614 $this->unhook( 'LocalUserCreated' );
1615 $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
1616 $this->assertSame( 'userexists', $ret->message->getKey() );
1617
1618 $mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
1619 $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
1620 $mock->expects( $this->any() )->method( 'accountCreationType' )
1621 ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
1622 $mock->expects( $this->any() )->method( 'testUserExists' )->will( $this->returnValue( false ) );
1623 $mock->expects( $this->any() )->method( 'testUserForCreation' )
1624 ->will( $this->returnValue( StatusValue::newFatal( 'fail' ) ) );
1625 $this->primaryauthMocks = [ $mock ];
1626 $this->initializeManager( true );
1627
1628 $this->hook( 'LocalUserCreated', $this->never() );
1629 $userReq->username = self::usernameForCreation();
1630 $ret = $this->manager->beginAccountCreation( $creator, [ $userReq ], 'http://localhost/' );
1631 $this->unhook( 'LocalUserCreated' );
1632 $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
1633 $this->assertSame( 'fail', $ret->message->getKey() );
1634
1635 $mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
1636 $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
1637 $mock->expects( $this->any() )->method( 'accountCreationType' )
1638 ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
1639 $mock->expects( $this->any() )->method( 'testUserExists' )->will( $this->returnValue( false ) );
1640 $mock->expects( $this->any() )->method( 'testUserForCreation' )
1641 ->will( $this->returnValue( StatusValue::newGood() ) );
1642 $this->primaryauthMocks = [ $mock ];
1643 $this->initializeManager( true );
1644
1645 $this->hook( 'LocalUserCreated', $this->never() );
1646 $userReq->username = self::usernameForCreation() . '<>';
1647 $ret = $this->manager->beginAccountCreation( $creator, [ $userReq ], 'http://localhost/' );
1648 $this->unhook( 'LocalUserCreated' );
1649 $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
1650 $this->assertSame( 'noname', $ret->message->getKey() );
1651
1652 $this->hook( 'LocalUserCreated', $this->never() );
1653 $userReq->username = $creator->getName();
1654 $ret = $this->manager->beginAccountCreation( $creator, [ $userReq ], 'http://localhost/' );
1655 $this->unhook( 'LocalUserCreated' );
1656 $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
1657 $this->assertSame( 'userexists', $ret->message->getKey() );
1658
1659 $mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
1660 $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
1661 $mock->expects( $this->any() )->method( 'accountCreationType' )
1662 ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
1663 $mock->expects( $this->any() )->method( 'testUserExists' )->will( $this->returnValue( false ) );
1664 $mock->expects( $this->any() )->method( 'testUserForCreation' )
1665 ->will( $this->returnValue( StatusValue::newGood() ) );
1666 $mock->expects( $this->any() )->method( 'testForAccountCreation' )
1667 ->will( $this->returnValue( StatusValue::newFatal( 'fail' ) ) );
1668 $this->primaryauthMocks = [ $mock ];
1669 $this->initializeManager( true );
1670
1671 $req = $this->getMockBuilder( UserDataAuthenticationRequest::class )
1672 ->setMethods( [ 'populateUser' ] )
1673 ->getMock();
1674 $req->expects( $this->any() )->method( 'populateUser' )
1675 ->willReturn( \StatusValue::newFatal( 'populatefail' ) );
1676 $userReq->username = self::usernameForCreation();
1677 $ret = $this->manager->beginAccountCreation(
1678 $creator, [ $userReq, $req ], 'http://localhost/'
1679 );
1680 $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
1681 $this->assertSame( 'populatefail', $ret->message->getKey() );
1682
1684 $userReq->username = self::usernameForCreation();
1685
1686 $ret = $this->manager->beginAccountCreation(
1687 $creator, [ $userReq, $req ], 'http://localhost/'
1688 );
1689 $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
1690 $this->assertSame( 'fail', $ret->message->getKey() );
1691
1692 $this->manager->beginAccountCreation(
1693 \User::newFromName( $userReq->username ), [ $userReq, $req ], 'http://localhost/'
1694 );
1695 $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
1696 $this->assertSame( 'fail', $ret->message->getKey() );
1697 }
1698
1700 $creator = \User::newFromName( 'UTSysop' );
1702 $this->logger = new \TestLogger( false, function ( $message, $level ) {
1703 return $level === LogLevel::DEBUG ? null : $message;
1704 } );
1705 $this->initializeManager();
1706
1707 $session = [
1708 'userid' => 0,
1709 'username' => $username,
1710 'creatorid' => 0,
1711 'creatorname' => $username,
1712 'reqs' => [],
1713 'primary' => null,
1714 'primaryResponse' => null,
1715 'secondary' => [],
1716 'ranPreTests' => true,
1717 ];
1718
1719 $this->hook( 'LocalUserCreated', $this->never() );
1720 try {
1721 $this->manager->continueAccountCreation( [] );
1722 $this->fail( 'Expected exception not thrown' );
1723 } catch ( \LogicException $ex ) {
1724 $this->assertEquals( 'Account creation is not possible', $ex->getMessage() );
1725 }
1726 $this->unhook( 'LocalUserCreated' );
1727
1728 $mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
1729 $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
1730 $mock->expects( $this->any() )->method( 'accountCreationType' )
1731 ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
1732 $mock->expects( $this->any() )->method( 'testUserExists' )->will( $this->returnValue( false ) );
1733 $mock->expects( $this->any() )->method( 'beginPrimaryAccountCreation' )->will(
1734 $this->returnValue( AuthenticationResponse::newFail( $this->message( 'fail' ) ) )
1735 );
1736 $this->primaryauthMocks = [ $mock ];
1737 $this->initializeManager( true );
1738
1739 $this->request->getSession()->setSecret( 'AuthManager::accountCreationState', null );
1740 $this->hook( 'LocalUserCreated', $this->never() );
1741 $ret = $this->manager->continueAccountCreation( [] );
1742 $this->unhook( 'LocalUserCreated' );
1743 $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
1744 $this->assertSame( 'authmanager-create-not-in-progress', $ret->message->getKey() );
1745
1746 $this->request->getSession()->setSecret( 'AuthManager::accountCreationState',
1747 [ 'username' => "$username<>" ] + $session );
1748 $this->hook( 'LocalUserCreated', $this->never() );
1749 $ret = $this->manager->continueAccountCreation( [] );
1750 $this->unhook( 'LocalUserCreated' );
1751 $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
1752 $this->assertSame( 'noname', $ret->message->getKey() );
1753 $this->assertNull(
1754 $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' )
1755 );
1756
1757 $this->request->getSession()->setSecret( 'AuthManager::accountCreationState', $session );
1758 $this->hook( 'LocalUserCreated', $this->never() );
1759 $cache = \ObjectCache::getLocalClusterInstance();
1760 $lock = $cache->getScopedLock( $cache->makeGlobalKey( 'account', md5( $username ) ) );
1761 $ret = $this->manager->continueAccountCreation( [] );
1762 unset( $lock );
1763 $this->unhook( 'LocalUserCreated' );
1764 $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
1765 $this->assertSame( 'usernameinprogress', $ret->message->getKey() );
1766 // This error shouldn't remove the existing session, because the
1767 // raced-with process "owns" it.
1768 $this->assertSame(
1769 $session, $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' )
1770 );
1771
1772 $this->request->getSession()->setSecret( 'AuthManager::accountCreationState',
1773 [ 'username' => $creator->getName() ] + $session );
1774 $this->setMwGlobals( [ 'wgReadOnly' => 'Because' ] );
1775 $this->hook( 'LocalUserCreated', $this->never() );
1776 $ret = $this->manager->continueAccountCreation( [] );
1777 $this->unhook( 'LocalUserCreated' );
1778 $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
1779 $this->assertSame( 'readonlytext', $ret->message->getKey() );
1780 $this->assertSame( [ 'Because' ], $ret->message->getParams() );
1781 $this->setMwGlobals( [ 'wgReadOnly' => false ] );
1782
1783 $this->request->getSession()->setSecret( 'AuthManager::accountCreationState',
1784 [ 'username' => $creator->getName() ] + $session );
1785 $this->hook( 'LocalUserCreated', $this->never() );
1786 $ret = $this->manager->continueAccountCreation( [] );
1787 $this->unhook( 'LocalUserCreated' );
1788 $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
1789 $this->assertSame( 'userexists', $ret->message->getKey() );
1790 $this->assertNull(
1791 $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' )
1792 );
1793
1794 $this->request->getSession()->setSecret( 'AuthManager::accountCreationState',
1795 [ 'userid' => $creator->getId() ] + $session );
1796 $this->hook( 'LocalUserCreated', $this->never() );
1797 try {
1798 $ret = $this->manager->continueAccountCreation( [] );
1799 $this->fail( 'Expected exception not thrown' );
1800 } catch ( \UnexpectedValueException $ex ) {
1801 $this->assertEquals( "User \"{$username}\" should exist now, but doesn't!", $ex->getMessage() );
1802 }
1803 $this->unhook( 'LocalUserCreated' );
1804 $this->assertNull(
1805 $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' )
1806 );
1807
1808 $id = $creator->getId();
1809 $name = $creator->getName();
1810 $this->request->getSession()->setSecret( 'AuthManager::accountCreationState',
1811 [ 'username' => $name, 'userid' => $id + 1 ] + $session );
1812 $this->hook( 'LocalUserCreated', $this->never() );
1813 try {
1814 $ret = $this->manager->continueAccountCreation( [] );
1815 $this->fail( 'Expected exception not thrown' );
1816 } catch ( \UnexpectedValueException $ex ) {
1817 $this->assertEquals(
1818 "User \"{$name}\" exists, but ID $id != " . ( $id + 1 ) . '!', $ex->getMessage()
1819 );
1820 }
1821 $this->unhook( 'LocalUserCreated' );
1822 $this->assertNull(
1823 $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' )
1824 );
1825
1826 $req = $this->getMockBuilder( UserDataAuthenticationRequest::class )
1827 ->setMethods( [ 'populateUser' ] )
1828 ->getMock();
1829 $req->expects( $this->any() )->method( 'populateUser' )
1830 ->willReturn( \StatusValue::newFatal( 'populatefail' ) );
1831 $this->request->getSession()->setSecret( 'AuthManager::accountCreationState',
1832 [ 'reqs' => [ $req ] ] + $session );
1833 $ret = $this->manager->continueAccountCreation( [] );
1834 $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
1835 $this->assertSame( 'populatefail', $ret->message->getKey() );
1836 $this->assertNull(
1837 $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' )
1838 );
1839 }
1840
1850 public function testAccountCreation(
1851 StatusValue $preTest, $primaryTest, $secondaryTest,
1852 array $primaryResponses, array $secondaryResponses, array $managerResponses
1853 ) {
1854 $creator = \User::newFromName( 'UTSysop' );
1856
1857 $this->initializeManager();
1858
1859 // Set up lots of mocks...
1860 $req = $this->getMockForAbstractClass( AuthenticationRequest::class );
1861 $req->preTest = $preTest;
1862 $req->primaryTest = $primaryTest;
1863 $req->secondaryTest = $secondaryTest;
1864 $req->primary = $primaryResponses;
1865 $req->secondary = $secondaryResponses;
1866 $mocks = [];
1867 foreach ( [ 'pre', 'primary', 'secondary' ] as $key ) {
1868 $class = ucfirst( $key ) . 'AuthenticationProvider';
1869 $mocks[$key] = $this->getMockForAbstractClass(
1870 "MediaWiki\\Auth\\$class", [], "Mock$class"
1871 );
1872 $mocks[$key]->expects( $this->any() )->method( 'getUniqueId' )
1873 ->will( $this->returnValue( $key ) );
1874 $mocks[$key]->expects( $this->any() )->method( 'testUserForCreation' )
1875 ->will( $this->returnValue( StatusValue::newGood() ) );
1876 $mocks[$key]->expects( $this->any() )->method( 'testForAccountCreation' )
1877 ->will( $this->returnCallback(
1878 function ( $user, $creatorIn, $reqs )
1879 use ( $username, $creator, $req, $key )
1880 {
1881 $this->assertSame( $username, $user->getName() );
1882 $this->assertSame( $creator->getId(), $creatorIn->getId() );
1883 $this->assertSame( $creator->getName(), $creatorIn->getName() );
1884 $foundReq = false;
1885 foreach ( $reqs as $r ) {
1886 $this->assertSame( $username, $r->username );
1887 $foundReq = $foundReq || get_class( $r ) === get_class( $req );
1888 }
1889 $this->assertTrue( $foundReq, '$reqs contains $req' );
1890 $k = $key . 'Test';
1891 return $req->$k;
1892 }
1893 ) );
1894
1895 for ( $i = 2; $i <= 3; $i++ ) {
1896 $mocks[$key . $i] = $this->getMockForAbstractClass(
1897 "MediaWiki\\Auth\\$class", [], "Mock$class"
1898 );
1899 $mocks[$key . $i]->expects( $this->any() )->method( 'getUniqueId' )
1900 ->will( $this->returnValue( $key . $i ) );
1901 $mocks[$key . $i]->expects( $this->any() )->method( 'testUserForCreation' )
1902 ->will( $this->returnValue( StatusValue::newGood() ) );
1903 $mocks[$key . $i]->expects( $this->atMost( 1 ) )->method( 'testForAccountCreation' )
1904 ->will( $this->returnValue( StatusValue::newGood() ) );
1905 }
1906 }
1907
1908 $mocks['primary']->expects( $this->any() )->method( 'accountCreationType' )
1909 ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
1910 $mocks['primary']->expects( $this->any() )->method( 'testUserExists' )
1911 ->will( $this->returnValue( false ) );
1912 $ct = count( $req->primary );
1913 $callback = $this->returnCallback( function ( $user, $creator, $reqs ) use ( $username, $req ) {
1914 $this->assertSame( $username, $user->getName() );
1915 $this->assertSame( 'UTSysop', $creator->getName() );
1916 $foundReq = false;
1917 foreach ( $reqs as $r ) {
1918 $this->assertSame( $username, $r->username );
1919 $foundReq = $foundReq || get_class( $r ) === get_class( $req );
1920 }
1921 $this->assertTrue( $foundReq, '$reqs contains $req' );
1922 return array_shift( $req->primary );
1923 } );
1924 $mocks['primary']->expects( $this->exactly( min( 1, $ct ) ) )
1925 ->method( 'beginPrimaryAccountCreation' )
1926 ->will( $callback );
1927 $mocks['primary']->expects( $this->exactly( max( 0, $ct - 1 ) ) )
1928 ->method( 'continuePrimaryAccountCreation' )
1929 ->will( $callback );
1930
1931 $ct = count( $req->secondary );
1932 $callback = $this->returnCallback( function ( $user, $creator, $reqs ) use ( $username, $req ) {
1933 $this->assertSame( $username, $user->getName() );
1934 $this->assertSame( 'UTSysop', $creator->getName() );
1935 $foundReq = false;
1936 foreach ( $reqs as $r ) {
1937 $this->assertSame( $username, $r->username );
1938 $foundReq = $foundReq || get_class( $r ) === get_class( $req );
1939 }
1940 $this->assertTrue( $foundReq, '$reqs contains $req' );
1941 return array_shift( $req->secondary );
1942 } );
1943 $mocks['secondary']->expects( $this->exactly( min( 1, $ct ) ) )
1944 ->method( 'beginSecondaryAccountCreation' )
1945 ->will( $callback );
1946 $mocks['secondary']->expects( $this->exactly( max( 0, $ct - 1 ) ) )
1947 ->method( 'continueSecondaryAccountCreation' )
1948 ->will( $callback );
1949
1951 $mocks['primary2']->expects( $this->any() )->method( 'accountCreationType' )
1952 ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_LINK ) );
1953 $mocks['primary2']->expects( $this->any() )->method( 'testUserExists' )
1954 ->will( $this->returnValue( false ) );
1955 $mocks['primary2']->expects( $this->atMost( 1 ) )->method( 'beginPrimaryAccountCreation' )
1956 ->will( $this->returnValue( $abstain ) );
1957 $mocks['primary2']->expects( $this->never() )->method( 'continuePrimaryAccountCreation' );
1958 $mocks['primary3']->expects( $this->any() )->method( 'accountCreationType' )
1959 ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_NONE ) );
1960 $mocks['primary3']->expects( $this->any() )->method( 'testUserExists' )
1961 ->will( $this->returnValue( false ) );
1962 $mocks['primary3']->expects( $this->never() )->method( 'beginPrimaryAccountCreation' );
1963 $mocks['primary3']->expects( $this->never() )->method( 'continuePrimaryAccountCreation' );
1964 $mocks['secondary2']->expects( $this->atMost( 1 ) )
1965 ->method( 'beginSecondaryAccountCreation' )
1966 ->will( $this->returnValue( $abstain ) );
1967 $mocks['secondary2']->expects( $this->never() )->method( 'continueSecondaryAccountCreation' );
1968 $mocks['secondary3']->expects( $this->atMost( 1 ) )
1969 ->method( 'beginSecondaryAccountCreation' )
1970 ->will( $this->returnValue( $abstain ) );
1971 $mocks['secondary3']->expects( $this->never() )->method( 'continueSecondaryAccountCreation' );
1972
1973 $this->preauthMocks = [ $mocks['pre'], $mocks['pre2'] ];
1974 $this->primaryauthMocks = [ $mocks['primary3'], $mocks['primary'], $mocks['primary2'] ];
1975 $this->secondaryauthMocks = [
1976 $mocks['secondary3'], $mocks['secondary'], $mocks['secondary2']
1977 ];
1978
1979 $this->logger = new \TestLogger( true, function ( $message, $level ) {
1980 return $level === LogLevel::DEBUG ? null : $message;
1981 } );
1982 $expectLog = [];
1983 $this->initializeManager( true );
1984
1985 $constraint = \PHPUnit_Framework_Assert::logicalOr(
1986 $this->equalTo( AuthenticationResponse::PASS ),
1987 $this->equalTo( AuthenticationResponse::FAIL )
1988 );
1989 $providers = array_merge(
1990 $this->preauthMocks, $this->primaryauthMocks, $this->secondaryauthMocks
1991 );
1992 foreach ( $providers as $p ) {
1993 $p->postCalled = false;
1994 $p->expects( $this->atMost( 1 ) )->method( 'postAccountCreation' )
1995 ->willReturnCallback( function ( $user, $creator, $response )
1996 use ( $constraint, $p, $username )
1997 {
1998 $this->assertInstanceOf( 'User', $user );
1999 $this->assertSame( $username, $user->getName() );
2000 $this->assertSame( 'UTSysop', $creator->getName() );
2001 $this->assertInstanceOf( AuthenticationResponse::class, $response );
2002 $this->assertThat( $response->status, $constraint );
2003 $p->postCalled = $response->status;
2004 } );
2005 }
2006
2007 // We're testing with $wgNewUserLog = false, so assert that it worked
2008 $dbw = wfGetDB( DB_MASTER );
2009 $maxLogId = $dbw->selectField( 'logging', 'MAX(log_id)', [ 'log_type' => 'newusers' ] );
2010
2011 $first = true;
2012 $created = false;
2013 foreach ( $managerResponses as $i => $response ) {
2016 if ( $i === 'created' ) {
2017 $created = true;
2018 $this->hook( 'LocalUserCreated', $this->once() )
2019 ->with(
2020 $this->callback( function ( $user ) use ( $username ) {
2021 return $user->getName() === $username;
2022 } ),
2023 $this->equalTo( false )
2024 );
2025 $expectLog[] = [ LogLevel::INFO, "Creating user {user} during account creation" ];
2026 } else {
2027 $this->hook( 'LocalUserCreated', $this->never() );
2028 }
2029
2030 $ex = null;
2031 try {
2032 if ( $first ) {
2033 $userReq = new UsernameAuthenticationRequest;
2034 $userReq->username = $username;
2035 $ret = $this->manager->beginAccountCreation(
2036 $creator, [ $userReq, $req ], 'http://localhost/'
2037 );
2038 } else {
2039 $ret = $this->manager->continueAccountCreation( [ $req ] );
2040 }
2041 if ( $response instanceof \Exception ) {
2042 $this->fail( 'Expected exception not thrown', "Response $i" );
2043 }
2044 } catch ( \Exception $ex ) {
2045 if ( !$response instanceof \Exception ) {
2046 throw $ex;
2047 }
2048 $this->assertEquals( $response->getMessage(), $ex->getMessage(), "Response $i, exception" );
2049 $this->assertNull(
2050 $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' ),
2051 "Response $i, exception, session state"
2052 );
2053 $this->unhook( 'LocalUserCreated' );
2054 return;
2055 }
2056
2057 $this->unhook( 'LocalUserCreated' );
2058
2059 $this->assertSame( 'http://localhost/', $req->returnToUrl );
2060
2061 if ( $success ) {
2062 $this->assertNotNull( $ret->loginRequest, "Response $i, login marker" );
2063 $this->assertContains(
2064 $ret->loginRequest, $this->managerPriv->createdAccountAuthenticationRequests,
2065 "Response $i, login marker"
2066 );
2067
2068 $expectLog[] = [
2069 LogLevel::INFO,
2070 "MediaWiki\Auth\AuthManager::continueAccountCreation: Account creation succeeded for {user}"
2071 ];
2072
2073 // Set some fields in the expected $response that we couldn't
2074 // know in provideAccountCreation().
2075 $response->username = $username;
2076 $response->loginRequest = $ret->loginRequest;
2077 } else {
2078 $this->assertNull( $ret->loginRequest, "Response $i, login marker" );
2079 $this->assertSame( [], $this->managerPriv->createdAccountAuthenticationRequests,
2080 "Response $i, login marker" );
2081 }
2082 $ret->message = $this->message( $ret->message );
2083 $this->assertEquals( $response, $ret, "Response $i, response" );
2084 if ( $success || $response->status === AuthenticationResponse::FAIL ) {
2085 $this->assertNull(
2086 $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' ),
2087 "Response $i, session state"
2088 );
2089 foreach ( $providers as $p ) {
2090 $this->assertSame( $response->status, $p->postCalled,
2091 "Response $i, post-auth callback called" );
2092 }
2093 } else {
2094 $this->assertNotNull(
2095 $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' ),
2096 "Response $i, session state"
2097 );
2098 foreach ( $ret->neededRequests as $neededReq ) {
2099 $this->assertEquals( AuthManager::ACTION_CREATE, $neededReq->action,
2100 "Response $i, neededRequest action" );
2101 }
2102 $this->assertEquals(
2103 $ret->neededRequests,
2104 $this->manager->getAuthenticationRequests( AuthManager::ACTION_CREATE_CONTINUE ),
2105 "Response $i, continuation check"
2106 );
2107 foreach ( $providers as $p ) {
2108 $this->assertFalse( $p->postCalled, "Response $i, post-auth callback not called" );
2109 }
2110 }
2111
2112 if ( $created ) {
2113 $this->assertNotEquals( 0, \User::idFromName( $username ) );
2114 } else {
2115 $this->assertEquals( 0, \User::idFromName( $username ) );
2116 }
2117
2118 $first = false;
2119 }
2120
2121 $this->assertSame( $expectLog, $this->logger->getBuffer() );
2122
2123 $this->assertSame(
2124 $maxLogId,
2125 $dbw->selectField( 'logging', 'MAX(log_id)', [ 'log_type' => 'newusers' ] )
2126 );
2127 }
2128
2129 public function provideAccountCreation() {
2130 $req = $this->getMockForAbstractClass( AuthenticationRequest::class );
2131 $good = StatusValue::newGood();
2132
2133 return [
2134 'Pre-creation test fail in pre' => [
2135 StatusValue::newFatal( 'fail-from-pre' ), $good, $good,
2136 [],
2137 [],
2138 [
2139 AuthenticationResponse::newFail( $this->message( 'fail-from-pre' ) ),
2140 ]
2141 ],
2142 'Pre-creation test fail in primary' => [
2143 $good, StatusValue::newFatal( 'fail-from-primary' ), $good,
2144 [],
2145 [],
2146 [
2147 AuthenticationResponse::newFail( $this->message( 'fail-from-primary' ) ),
2148 ]
2149 ],
2150 'Pre-creation test fail in secondary' => [
2151 $good, $good, StatusValue::newFatal( 'fail-from-secondary' ),
2152 [],
2153 [],
2154 [
2155 AuthenticationResponse::newFail( $this->message( 'fail-from-secondary' ) ),
2156 ]
2157 ],
2158 'Failure in primary' => [
2159 $good, $good, $good,
2160 $tmp = [
2161 AuthenticationResponse::newFail( $this->message( 'fail-from-primary' ) ),
2162 ],
2163 [],
2164 $tmp
2165 ],
2166 'All primary abstain' => [
2167 $good, $good, $good,
2168 [
2170 ],
2171 [],
2172 [
2173 AuthenticationResponse::newFail( $this->message( 'authmanager-create-no-primary' ) )
2174 ]
2175 ],
2176 'Primary UI, then redirect, then fail' => [
2177 $good, $good, $good,
2178 $tmp = [
2179 AuthenticationResponse::newUI( [ $req ], $this->message( '...' ) ),
2180 AuthenticationResponse::newRedirect( [ $req ], '/foo.html', [ 'foo' => 'bar' ] ),
2181 AuthenticationResponse::newFail( $this->message( 'fail-in-primary-continue' ) ),
2182 ],
2183 [],
2184 $tmp
2185 ],
2186 'Primary redirect, then abstain' => [
2187 $good, $good, $good,
2188 [
2190 [ $req ], '/foo.html', [ 'foo' => 'bar' ]
2191 ),
2193 ],
2194 [],
2195 [
2196 $tmp,
2197 new \DomainException(
2198 'MockPrimaryAuthenticationProvider::continuePrimaryAccountCreation() returned ABSTAIN'
2199 )
2200 ]
2201 ],
2202 'Primary UI, then pass; secondary abstain' => [
2203 $good, $good, $good,
2204 [
2205 $tmp1 = AuthenticationResponse::newUI( [ $req ], $this->message( '...' ) ),
2207 ],
2208 [
2210 ],
2211 [
2212 $tmp1,
2213 'created' => AuthenticationResponse::newPass( '' ),
2214 ]
2215 ],
2216 'Primary pass; secondary UI then pass' => [
2217 $good, $good, $good,
2218 [
2220 ],
2221 [
2222 $tmp1 = AuthenticationResponse::newUI( [ $req ], $this->message( '...' ) ),
2224 ],
2225 [
2226 'created' => $tmp1,
2228 ]
2229 ],
2230 'Primary pass; secondary fail' => [
2231 $good, $good, $good,
2232 [
2234 ],
2235 [
2236 AuthenticationResponse::newFail( $this->message( '...' ) ),
2237 ],
2238 [
2239 'created' => new \DomainException(
2240 'MockSecondaryAuthenticationProvider::beginSecondaryAccountCreation() returned FAIL. ' .
2241 'Secondary providers are not allowed to fail account creation, ' .
2242 'that should have been done via testForAccountCreation().'
2243 )
2244 ]
2245 ],
2246 ];
2247 }
2248
2254 public function testAccountCreationLogging( $isAnon, $logSubtype ) {
2255 $creator = $isAnon ? new \User : \User::newFromName( 'UTSysop' );
2257
2258 $this->initializeManager();
2259
2260 // Set up lots of mocks...
2261 $mock = $this->getMockForAbstractClass(
2262 "MediaWiki\\Auth\\PrimaryAuthenticationProvider", []
2263 );
2264 $mock->expects( $this->any() )->method( 'getUniqueId' )
2265 ->will( $this->returnValue( 'primary' ) );
2266 $mock->expects( $this->any() )->method( 'testUserForCreation' )
2267 ->will( $this->returnValue( StatusValue::newGood() ) );
2268 $mock->expects( $this->any() )->method( 'testForAccountCreation' )
2269 ->will( $this->returnValue( StatusValue::newGood() ) );
2270 $mock->expects( $this->any() )->method( 'accountCreationType' )
2271 ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
2272 $mock->expects( $this->any() )->method( 'testUserExists' )
2273 ->will( $this->returnValue( false ) );
2274 $mock->expects( $this->any() )->method( 'beginPrimaryAccountCreation' )
2275 ->will( $this->returnValue( AuthenticationResponse::newPass( $username ) ) );
2276 $mock->expects( $this->any() )->method( 'finishAccountCreation' )
2277 ->will( $this->returnValue( $logSubtype ) );
2278
2279 $this->primaryauthMocks = [ $mock ];
2280 $this->initializeManager( true );
2281 $this->logger->setCollect( true );
2282
2283 $this->config->set( 'NewUserLog', true );
2284
2285 $dbw = wfGetDB( DB_MASTER );
2286 $maxLogId = $dbw->selectField( 'logging', 'MAX(log_id)', [ 'log_type' => 'newusers' ] );
2287
2288 $userReq = new UsernameAuthenticationRequest;
2289 $userReq->username = $username;
2290 $reasonReq = new CreationReasonAuthenticationRequest;
2291 $reasonReq->reason = $this->toString();
2292 $ret = $this->manager->beginAccountCreation(
2293 $creator, [ $userReq, $reasonReq ], 'http://localhost/'
2294 );
2295
2296 $this->assertSame( AuthenticationResponse::PASS, $ret->status );
2297
2298 $user = \User::newFromName( $username );
2299 $this->assertNotEquals( 0, $user->getId(), 'sanity check' );
2300 $this->assertNotEquals( $creator->getId(), $user->getId(), 'sanity check' );
2301
2302 $data = \DatabaseLogEntry::getSelectQueryData();
2303 $rows = iterator_to_array( $dbw->select(
2304 $data['tables'],
2305 $data['fields'],
2306 [
2307 'log_id > ' . (int)$maxLogId,
2308 'log_type' => 'newusers'
2309 ] + $data['conds'],
2310 __METHOD__,
2311 $data['options'],
2312 $data['join_conds']
2313 ) );
2314 $this->assertCount( 1, $rows );
2315 $entry = \DatabaseLogEntry::newFromRow( reset( $rows ) );
2316
2317 $this->assertSame( $logSubtype ?: ( $isAnon ? 'create' : 'create2' ), $entry->getSubtype() );
2318 $this->assertSame(
2319 $isAnon ? $user->getId() : $creator->getId(),
2320 $entry->getPerformer()->getId()
2321 );
2322 $this->assertSame(
2323 $isAnon ? $user->getName() : $creator->getName(),
2324 $entry->getPerformer()->getName()
2325 );
2326 $this->assertSame( $user->getUserPage()->getFullText(), $entry->getTarget()->getFullText() );
2327 $this->assertSame( [ '4::userid' => $user->getId() ], $entry->getParameters() );
2328 $this->assertSame( $this->toString(), $entry->getComment() );
2329 }
2330
2331 public static function provideAccountCreationLogging() {
2332 return [
2333 [ true, null ],
2334 [ true, 'foobar' ],
2335 [ false, null ],
2336 [ false, 'byemail' ],
2337 ];
2338 }
2339
2340 public function testAutoAccountCreation() {
2342
2343 // PHPUnit seems to have a bug where it will call the ->with()
2344 // callbacks for our hooks again after the test is run (WTF?), which
2345 // breaks here because $username no longer matches $user by the end of
2346 // the testing.
2347 $workaroundPHPUnitBug = false;
2348
2350 $this->initializeManager();
2351
2352 $this->stashMwGlobals( [ 'wgGroupPermissions' ] );
2353 $wgGroupPermissions['*']['createaccount'] = true;
2354 $wgGroupPermissions['*']['autocreateaccount'] = false;
2355
2356 \ObjectCache::$instances[__METHOD__] = new \HashBagOStuff();
2357 $this->setMwGlobals( [ 'wgMainCacheType' => __METHOD__ ] );
2358
2359 // Set up lots of mocks...
2360 $mocks = [];
2361 foreach ( [ 'pre', 'primary', 'secondary' ] as $key ) {
2362 $class = ucfirst( $key ) . 'AuthenticationProvider';
2363 $mocks[$key] = $this->getMockForAbstractClass(
2364 "MediaWiki\\Auth\\$class", [], "Mock$class"
2365 );
2366 $mocks[$key]->expects( $this->any() )->method( 'getUniqueId' )
2367 ->will( $this->returnValue( $key ) );
2368 }
2369
2370 $good = StatusValue::newGood();
2371 $callback = $this->callback( function ( $user ) use ( &$username, &$workaroundPHPUnitBug ) {
2372 return $workaroundPHPUnitBug || $user->getName() === $username;
2373 } );
2374
2375 $mocks['pre']->expects( $this->exactly( 12 ) )->method( 'testUserForCreation' )
2376 ->with( $callback, $this->identicalTo( AuthManager::AUTOCREATE_SOURCE_SESSION ) )
2377 ->will( $this->onConsecutiveCalls(
2378 StatusValue::newFatal( 'ok' ), StatusValue::newFatal( 'ok' ), // For testing permissions
2379 StatusValue::newFatal( 'fail-in-pre' ), $good, $good,
2380 $good, // backoff test
2381 $good, // addToDatabase fails test
2382 $good, // addToDatabase throws test
2383 $good, // addToDatabase exists test
2384 $good, $good, $good // success
2385 ) );
2386
2387 $mocks['primary']->expects( $this->any() )->method( 'accountCreationType' )
2388 ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
2389 $mocks['primary']->expects( $this->any() )->method( 'testUserExists' )
2390 ->will( $this->returnValue( true ) );
2391 $mocks['primary']->expects( $this->exactly( 9 ) )->method( 'testUserForCreation' )
2392 ->with( $callback, $this->identicalTo( AuthManager::AUTOCREATE_SOURCE_SESSION ) )
2393 ->will( $this->onConsecutiveCalls(
2394 StatusValue::newFatal( 'fail-in-primary' ), $good,
2395 $good, // backoff test
2396 $good, // addToDatabase fails test
2397 $good, // addToDatabase throws test
2398 $good, // addToDatabase exists test
2399 $good, $good, $good
2400 ) );
2401 $mocks['primary']->expects( $this->exactly( 3 ) )->method( 'autoCreatedAccount' )
2402 ->with( $callback, $this->identicalTo( AuthManager::AUTOCREATE_SOURCE_SESSION ) );
2403
2404 $mocks['secondary']->expects( $this->exactly( 8 ) )->method( 'testUserForCreation' )
2405 ->with( $callback, $this->identicalTo( AuthManager::AUTOCREATE_SOURCE_SESSION ) )
2406 ->will( $this->onConsecutiveCalls(
2407 StatusValue::newFatal( 'fail-in-secondary' ),
2408 $good, // backoff test
2409 $good, // addToDatabase fails test
2410 $good, // addToDatabase throws test
2411 $good, // addToDatabase exists test
2412 $good, $good, $good
2413 ) );
2414 $mocks['secondary']->expects( $this->exactly( 3 ) )->method( 'autoCreatedAccount' )
2415 ->with( $callback, $this->identicalTo( AuthManager::AUTOCREATE_SOURCE_SESSION ) );
2416
2417 $this->preauthMocks = [ $mocks['pre'] ];
2418 $this->primaryauthMocks = [ $mocks['primary'] ];
2419 $this->secondaryauthMocks = [ $mocks['secondary'] ];
2420 $this->initializeManager( true );
2421 $session = $this->request->getSession();
2422
2423 $logger = new \TestLogger( true, function ( $m ) {
2424 $m = str_replace( 'MediaWiki\\Auth\\AuthManager::autoCreateUser: ', '', $m );
2425 return $m;
2426 } );
2427 $this->manager->setLogger( $logger );
2428
2429 try {
2430 $user = \User::newFromName( 'UTSysop' );
2431 $this->manager->autoCreateUser( $user, 'InvalidSource', true );
2432 $this->fail( 'Expected exception not thrown' );
2433 } catch ( \InvalidArgumentException $ex ) {
2434 $this->assertSame( 'Unknown auto-creation source: InvalidSource', $ex->getMessage() );
2435 }
2436
2437 // First, check an existing user
2438 $session->clear();
2439 $user = \User::newFromName( 'UTSysop' );
2440 $this->hook( 'LocalUserCreated', $this->never() );
2441 $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
2442 $this->unhook( 'LocalUserCreated' );
2443 $expect = \Status::newGood();
2444 $expect->warning( 'userexists' );
2445 $this->assertEquals( $expect, $ret );
2446 $this->assertNotEquals( 0, $user->getId() );
2447 $this->assertSame( 'UTSysop', $user->getName() );
2448 $this->assertEquals( $user->getId(), $session->getUser()->getId() );
2449 $this->assertSame( [
2450 [ LogLevel::DEBUG, '{username} already exists locally' ],
2451 ], $logger->getBuffer() );
2452 $logger->clearBuffer();
2453
2454 $session->clear();
2455 $user = \User::newFromName( 'UTSysop' );
2456 $this->hook( 'LocalUserCreated', $this->never() );
2457 $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, false );
2458 $this->unhook( 'LocalUserCreated' );
2459 $expect = \Status::newGood();
2460 $expect->warning( 'userexists' );
2461 $this->assertEquals( $expect, $ret );
2462 $this->assertNotEquals( 0, $user->getId() );
2463 $this->assertSame( 'UTSysop', $user->getName() );
2464 $this->assertEquals( 0, $session->getUser()->getId() );
2465 $this->assertSame( [
2466 [ LogLevel::DEBUG, '{username} already exists locally' ],
2467 ], $logger->getBuffer() );
2468 $logger->clearBuffer();
2469
2470 // Wiki is read-only
2471 $session->clear();
2472 $this->setMwGlobals( [ 'wgReadOnly' => 'Because' ] );
2473 $user = \User::newFromName( $username );
2474 $this->hook( 'LocalUserCreated', $this->never() );
2475 $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
2476 $this->unhook( 'LocalUserCreated' );
2477 $this->assertEquals( \Status::newFatal( 'readonlytext', 'Because' ), $ret );
2478 $this->assertEquals( 0, $user->getId() );
2479 $this->assertNotEquals( $username, $user->getName() );
2480 $this->assertEquals( 0, $session->getUser()->getId() );
2481 $this->assertSame( [
2482 [ LogLevel::DEBUG, 'denied by wfReadOnly(): {reason}' ],
2483 ], $logger->getBuffer() );
2484 $logger->clearBuffer();
2485 $this->setMwGlobals( [ 'wgReadOnly' => false ] );
2486
2487 // Session blacklisted
2488 $session->clear();
2489 $session->set( 'AuthManager::AutoCreateBlacklist', 'test' );
2490 $user = \User::newFromName( $username );
2491 $this->hook( 'LocalUserCreated', $this->never() );
2492 $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
2493 $this->unhook( 'LocalUserCreated' );
2494 $this->assertEquals( \Status::newFatal( 'test' ), $ret );
2495 $this->assertEquals( 0, $user->getId() );
2496 $this->assertNotEquals( $username, $user->getName() );
2497 $this->assertEquals( 0, $session->getUser()->getId() );
2498 $this->assertSame( [
2499 [ LogLevel::DEBUG, 'blacklisted in session {sessionid}' ],
2500 ], $logger->getBuffer() );
2501 $logger->clearBuffer();
2502
2503 $session->clear();
2504 $session->set( 'AuthManager::AutoCreateBlacklist', StatusValue::newFatal( 'test2' ) );
2505 $user = \User::newFromName( $username );
2506 $this->hook( 'LocalUserCreated', $this->never() );
2507 $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
2508 $this->unhook( 'LocalUserCreated' );
2509 $this->assertEquals( \Status::newFatal( 'test2' ), $ret );
2510 $this->assertEquals( 0, $user->getId() );
2511 $this->assertNotEquals( $username, $user->getName() );
2512 $this->assertEquals( 0, $session->getUser()->getId() );
2513 $this->assertSame( [
2514 [ LogLevel::DEBUG, 'blacklisted in session {sessionid}' ],
2515 ], $logger->getBuffer() );
2516 $logger->clearBuffer();
2517
2518 // Uncreatable name
2519 $session->clear();
2520 $user = \User::newFromName( $username . '@' );
2521 $this->hook( 'LocalUserCreated', $this->never() );
2522 $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
2523 $this->unhook( 'LocalUserCreated' );
2524 $this->assertEquals( \Status::newFatal( 'noname' ), $ret );
2525 $this->assertEquals( 0, $user->getId() );
2526 $this->assertNotEquals( $username . '@', $user->getId() );
2527 $this->assertEquals( 0, $session->getUser()->getId() );
2528 $this->assertSame( [
2529 [ LogLevel::DEBUG, 'name "{username}" is not creatable' ],
2530 ], $logger->getBuffer() );
2531 $logger->clearBuffer();
2532 $this->assertSame( 'noname', $session->get( 'AuthManager::AutoCreateBlacklist' ) );
2533
2534 // IP unable to create accounts
2535 $wgGroupPermissions['*']['createaccount'] = false;
2536 $wgGroupPermissions['*']['autocreateaccount'] = false;
2537 $session->clear();
2538 $user = \User::newFromName( $username );
2539 $this->hook( 'LocalUserCreated', $this->never() );
2540 $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
2541 $this->unhook( 'LocalUserCreated' );
2542 $this->assertEquals( \Status::newFatal( 'authmanager-autocreate-noperm' ), $ret );
2543 $this->assertEquals( 0, $user->getId() );
2544 $this->assertNotEquals( $username, $user->getName() );
2545 $this->assertEquals( 0, $session->getUser()->getId() );
2546 $this->assertSame( [
2547 [ LogLevel::DEBUG, 'IP lacks the ability to create or autocreate accounts' ],
2548 ], $logger->getBuffer() );
2549 $logger->clearBuffer();
2550 $this->assertSame(
2551 'authmanager-autocreate-noperm', $session->get( 'AuthManager::AutoCreateBlacklist' )
2552 );
2553
2554 // Test that both permutations of permissions are allowed
2555 // (this hits the two "ok" entries in $mocks['pre'])
2556 $wgGroupPermissions['*']['createaccount'] = false;
2557 $wgGroupPermissions['*']['autocreateaccount'] = true;
2558 $session->clear();
2559 $user = \User::newFromName( $username );
2560 $this->hook( 'LocalUserCreated', $this->never() );
2561 $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
2562 $this->unhook( 'LocalUserCreated' );
2563 $this->assertEquals( \Status::newFatal( 'ok' ), $ret );
2564
2565 $wgGroupPermissions['*']['createaccount'] = true;
2566 $wgGroupPermissions['*']['autocreateaccount'] = false;
2567 $session->clear();
2568 $user = \User::newFromName( $username );
2569 $this->hook( 'LocalUserCreated', $this->never() );
2570 $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
2571 $this->unhook( 'LocalUserCreated' );
2572 $this->assertEquals( \Status::newFatal( 'ok' ), $ret );
2573 $logger->clearBuffer();
2574
2575 // Test lock fail
2576 $session->clear();
2577 $user = \User::newFromName( $username );
2578 $this->hook( 'LocalUserCreated', $this->never() );
2579 $cache = \ObjectCache::getLocalClusterInstance();
2580 $lock = $cache->getScopedLock( $cache->makeGlobalKey( 'account', md5( $username ) ) );
2581 $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
2582 unset( $lock );
2583 $this->unhook( 'LocalUserCreated' );
2584 $this->assertEquals( \Status::newFatal( 'usernameinprogress' ), $ret );
2585 $this->assertEquals( 0, $user->getId() );
2586 $this->assertNotEquals( $username, $user->getName() );
2587 $this->assertEquals( 0, $session->getUser()->getId() );
2588 $this->assertSame( [
2589 [ LogLevel::DEBUG, 'Could not acquire account creation lock' ],
2590 ], $logger->getBuffer() );
2591 $logger->clearBuffer();
2592
2593 // Test pre-authentication provider fail
2594 $session->clear();
2595 $user = \User::newFromName( $username );
2596 $this->hook( 'LocalUserCreated', $this->never() );
2597 $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
2598 $this->unhook( 'LocalUserCreated' );
2599 $this->assertEquals( \Status::newFatal( 'fail-in-pre' ), $ret );
2600 $this->assertEquals( 0, $user->getId() );
2601 $this->assertNotEquals( $username, $user->getName() );
2602 $this->assertEquals( 0, $session->getUser()->getId() );
2603 $this->assertSame( [
2604 [ LogLevel::DEBUG, 'Provider denied creation of {username}: {reason}' ],
2605 ], $logger->getBuffer() );
2606 $logger->clearBuffer();
2607 $this->assertEquals(
2608 StatusValue::newFatal( 'fail-in-pre' ), $session->get( 'AuthManager::AutoCreateBlacklist' )
2609 );
2610
2611 $session->clear();
2612 $user = \User::newFromName( $username );
2613 $this->hook( 'LocalUserCreated', $this->never() );
2614 $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
2615 $this->unhook( 'LocalUserCreated' );
2616 $this->assertEquals( \Status::newFatal( 'fail-in-primary' ), $ret );
2617 $this->assertEquals( 0, $user->getId() );
2618 $this->assertNotEquals( $username, $user->getName() );
2619 $this->assertEquals( 0, $session->getUser()->getId() );
2620 $this->assertSame( [
2621 [ LogLevel::DEBUG, 'Provider denied creation of {username}: {reason}' ],
2622 ], $logger->getBuffer() );
2623 $logger->clearBuffer();
2624 $this->assertEquals(
2625 StatusValue::newFatal( 'fail-in-primary' ), $session->get( 'AuthManager::AutoCreateBlacklist' )
2626 );
2627
2628 $session->clear();
2629 $user = \User::newFromName( $username );
2630 $this->hook( 'LocalUserCreated', $this->never() );
2631 $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
2632 $this->unhook( 'LocalUserCreated' );
2633 $this->assertEquals( \Status::newFatal( 'fail-in-secondary' ), $ret );
2634 $this->assertEquals( 0, $user->getId() );
2635 $this->assertNotEquals( $username, $user->getName() );
2636 $this->assertEquals( 0, $session->getUser()->getId() );
2637 $this->assertSame( [
2638 [ LogLevel::DEBUG, 'Provider denied creation of {username}: {reason}' ],
2639 ], $logger->getBuffer() );
2640 $logger->clearBuffer();
2641 $this->assertEquals(
2642 StatusValue::newFatal( 'fail-in-secondary' ), $session->get( 'AuthManager::AutoCreateBlacklist' )
2643 );
2644
2645 // Test backoff
2646 $cache = \ObjectCache::getLocalClusterInstance();
2647 $backoffKey = wfMemcKey( 'AuthManager', 'autocreate-failed', md5( $username ) );
2648 $cache->set( $backoffKey, true );
2649 $session->clear();
2650 $user = \User::newFromName( $username );
2651 $this->hook( 'LocalUserCreated', $this->never() );
2652 $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
2653 $this->unhook( 'LocalUserCreated' );
2654 $this->assertEquals( \Status::newFatal( 'authmanager-autocreate-exception' ), $ret );
2655 $this->assertEquals( 0, $user->getId() );
2656 $this->assertNotEquals( $username, $user->getName() );
2657 $this->assertEquals( 0, $session->getUser()->getId() );
2658 $this->assertSame( [
2659 [ LogLevel::DEBUG, '{username} denied by prior creation attempt failures' ],
2660 ], $logger->getBuffer() );
2661 $logger->clearBuffer();
2662 $this->assertSame( null, $session->get( 'AuthManager::AutoCreateBlacklist' ) );
2663 $cache->delete( $backoffKey );
2664
2665 // Test addToDatabase fails
2666 $session->clear();
2667 $user = $this->getMock( 'User', [ 'addToDatabase' ] );
2668 $user->expects( $this->once() )->method( 'addToDatabase' )
2669 ->will( $this->returnValue( \Status::newFatal( 'because' ) ) );
2670 $user->setName( $username );
2671 $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
2672 $this->assertEquals( \Status::newFatal( 'because' ), $ret );
2673 $this->assertEquals( 0, $user->getId() );
2674 $this->assertNotEquals( $username, $user->getName() );
2675 $this->assertEquals( 0, $session->getUser()->getId() );
2676 $this->assertSame( [
2677 [ LogLevel::INFO, 'creating new user ({username}) - from: {from}' ],
2678 [ LogLevel::ERROR, '{username} failed with message {msg}' ],
2679 ], $logger->getBuffer() );
2680 $logger->clearBuffer();
2681 $this->assertSame( null, $session->get( 'AuthManager::AutoCreateBlacklist' ) );
2682
2683 // Test addToDatabase throws an exception
2684 $cache = \ObjectCache::getLocalClusterInstance();
2685 $backoffKey = wfMemcKey( 'AuthManager', 'autocreate-failed', md5( $username ) );
2686 $this->assertFalse( $cache->get( $backoffKey ), 'sanity check' );
2687 $session->clear();
2688 $user = $this->getMock( 'User', [ 'addToDatabase' ] );
2689 $user->expects( $this->once() )->method( 'addToDatabase' )
2690 ->will( $this->throwException( new \Exception( 'Excepted' ) ) );
2691 $user->setName( $username );
2692 try {
2693 $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
2694 $this->fail( 'Expected exception not thrown' );
2695 } catch ( \Exception $ex ) {
2696 $this->assertSame( 'Excepted', $ex->getMessage() );
2697 }
2698 $this->assertEquals( 0, $user->getId() );
2699 $this->assertEquals( 0, $session->getUser()->getId() );
2700 $this->assertSame( [
2701 [ LogLevel::INFO, 'creating new user ({username}) - from: {from}' ],
2702 [ LogLevel::ERROR, '{username} failed with exception {exception}' ],
2703 ], $logger->getBuffer() );
2704 $logger->clearBuffer();
2705 $this->assertSame( null, $session->get( 'AuthManager::AutoCreateBlacklist' ) );
2706 $this->assertNotEquals( false, $cache->get( $backoffKey ) );
2707 $cache->delete( $backoffKey );
2708
2709 // Test addToDatabase fails because the user already exists.
2710 $session->clear();
2711 $user = $this->getMock( 'User', [ 'addToDatabase' ] );
2712 $user->expects( $this->once() )->method( 'addToDatabase' )
2713 ->will( $this->returnCallback( function () use ( $username, &$user ) {
2714 $oldUser = \User::newFromName( $username );
2715 $status = $oldUser->addToDatabase();
2716 $this->assertTrue( $status->isOK(), 'sanity check' );
2717 $user->setId( $oldUser->getId() );
2718 return \Status::newFatal( 'userexists' );
2719 } ) );
2720 $user->setName( $username );
2721 $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
2722 $expect = \Status::newGood();
2723 $expect->warning( 'userexists' );
2724 $this->assertEquals( $expect, $ret );
2725 $this->assertNotEquals( 0, $user->getId() );
2726 $this->assertEquals( $username, $user->getName() );
2727 $this->assertEquals( $user->getId(), $session->getUser()->getId() );
2728 $this->assertSame( [
2729 [ LogLevel::INFO, 'creating new user ({username}) - from: {from}' ],
2730 [ LogLevel::INFO, '{username} already exists locally (race)' ],
2731 ], $logger->getBuffer() );
2732 $logger->clearBuffer();
2733 $this->assertSame( null, $session->get( 'AuthManager::AutoCreateBlacklist' ) );
2734
2735 // Success!
2736 $session->clear();
2738 $user = \User::newFromName( $username );
2739 $this->hook( 'AuthPluginAutoCreate', $this->once() )
2740 ->with( $callback );
2741 $this->hideDeprecated( 'AuthPluginAutoCreate hook (used in ' .
2742 get_class( $wgHooks['AuthPluginAutoCreate'][0] ) . '::onAuthPluginAutoCreate)' );
2743 $this->hook( 'LocalUserCreated', $this->once() )
2744 ->with( $callback, $this->equalTo( true ) );
2745 $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
2746 $this->unhook( 'LocalUserCreated' );
2747 $this->unhook( 'AuthPluginAutoCreate' );
2748 $this->assertEquals( \Status::newGood(), $ret );
2749 $this->assertNotEquals( 0, $user->getId() );
2750 $this->assertEquals( $username, $user->getName() );
2751 $this->assertEquals( $user->getId(), $session->getUser()->getId() );
2752 $this->assertSame( [
2753 [ LogLevel::INFO, 'creating new user ({username}) - from: {from}' ],
2754 ], $logger->getBuffer() );
2755 $logger->clearBuffer();
2756
2757 $dbw = wfGetDB( DB_MASTER );
2758 $maxLogId = $dbw->selectField( 'logging', 'MAX(log_id)', [ 'log_type' => 'newusers' ] );
2759 $session->clear();
2761 $user = \User::newFromName( $username );
2762 $this->hook( 'LocalUserCreated', $this->once() )
2763 ->with( $callback, $this->equalTo( true ) );
2764 $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, false );
2765 $this->unhook( 'LocalUserCreated' );
2766 $this->assertEquals( \Status::newGood(), $ret );
2767 $this->assertNotEquals( 0, $user->getId() );
2768 $this->assertEquals( $username, $user->getName() );
2769 $this->assertEquals( 0, $session->getUser()->getId() );
2770 $this->assertSame( [
2771 [ LogLevel::INFO, 'creating new user ({username}) - from: {from}' ],
2772 ], $logger->getBuffer() );
2773 $logger->clearBuffer();
2774 $this->assertSame(
2775 $maxLogId,
2776 $dbw->selectField( 'logging', 'MAX(log_id)', [ 'log_type' => 'newusers' ] )
2777 );
2778
2779 $this->config->set( 'NewUserLog', true );
2780 $session->clear();
2782 $user = \User::newFromName( $username );
2783 $ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, false );
2784 $this->assertEquals( \Status::newGood(), $ret );
2785 $logger->clearBuffer();
2786
2787 $data = \DatabaseLogEntry::getSelectQueryData();
2788 $rows = iterator_to_array( $dbw->select(
2789 $data['tables'],
2790 $data['fields'],
2791 [
2792 'log_id > ' . (int)$maxLogId,
2793 'log_type' => 'newusers'
2794 ] + $data['conds'],
2795 __METHOD__,
2796 $data['options'],
2797 $data['join_conds']
2798 ) );
2799 $this->assertCount( 1, $rows );
2800 $entry = \DatabaseLogEntry::newFromRow( reset( $rows ) );
2801
2802 $this->assertSame( 'autocreate', $entry->getSubtype() );
2803 $this->assertSame( $user->getId(), $entry->getPerformer()->getId() );
2804 $this->assertSame( $user->getName(), $entry->getPerformer()->getName() );
2805 $this->assertSame( $user->getUserPage()->getFullText(), $entry->getTarget()->getFullText() );
2806 $this->assertSame( [ '4::userid' => $user->getId() ], $entry->getParameters() );
2807
2808 $workaroundPHPUnitBug = true;
2809 }
2810
2817 public function testGetAuthenticationRequests( $action, $expect, $state = [] ) {
2818 $makeReq = function ( $key ) use ( $action ) {
2819 $req = $this->getMock( AuthenticationRequest::class );
2820 $req->expects( $this->any() )->method( 'getUniqueId' )
2821 ->will( $this->returnValue( $key ) );
2822 $req->action = $action === AuthManager::ACTION_UNLINK ? AuthManager::ACTION_REMOVE : $action;
2823 $req->key = $key;
2824 return $req;
2825 };
2826 $cmpReqs = function ( $a, $b ) {
2827 $ret = strcmp( get_class( $a ), get_class( $b ) );
2828 if ( !$ret ) {
2829 $ret = strcmp( $a->key, $b->key );
2830 }
2831 return $ret;
2832 };
2833
2834 $good = StatusValue::newGood();
2835
2836 $mocks = [];
2837 foreach ( [ 'pre', 'primary', 'secondary' ] as $key ) {
2838 $class = ucfirst( $key ) . 'AuthenticationProvider';
2839 $mocks[$key] = $this->getMockForAbstractClass(
2840 "MediaWiki\\Auth\\$class", [], "Mock$class"
2841 );
2842 $mocks[$key]->expects( $this->any() )->method( 'getUniqueId' )
2843 ->will( $this->returnValue( $key ) );
2844 $mocks[$key]->expects( $this->any() )->method( 'getAuthenticationRequests' )
2845 ->will( $this->returnCallback( function ( $action ) use ( $key, $makeReq ) {
2846 return [ $makeReq( "$key-$action" ), $makeReq( 'generic' ) ];
2847 } ) );
2848 $mocks[$key]->expects( $this->any() )->method( 'providerAllowsAuthenticationDataChange' )
2849 ->will( $this->returnValue( $good ) );
2850 }
2851
2852 $primaries = [];
2853 foreach ( [
2857 ] as $type ) {
2858 $class = 'PrimaryAuthenticationProvider';
2859 $mocks["primary-$type"] = $this->getMockForAbstractClass(
2860 "MediaWiki\\Auth\\$class", [], "Mock$class"
2861 );
2862 $mocks["primary-$type"]->expects( $this->any() )->method( 'getUniqueId' )
2863 ->will( $this->returnValue( "primary-$type" ) );
2864 $mocks["primary-$type"]->expects( $this->any() )->method( 'accountCreationType' )
2865 ->will( $this->returnValue( $type ) );
2866 $mocks["primary-$type"]->expects( $this->any() )->method( 'getAuthenticationRequests' )
2867 ->will( $this->returnCallback( function ( $action ) use ( $type, $makeReq ) {
2868 return [ $makeReq( "primary-$type-$action" ), $makeReq( 'generic' ) ];
2869 } ) );
2870 $mocks["primary-$type"]->expects( $this->any() )
2871 ->method( 'providerAllowsAuthenticationDataChange' )
2872 ->will( $this->returnValue( $good ) );
2873 $this->primaryauthMocks[] = $mocks["primary-$type"];
2874 }
2875
2876 $mocks['primary2'] = $this->getMockForAbstractClass(
2877 PrimaryAuthenticationProvider::class, [], "MockPrimaryAuthenticationProvider"
2878 );
2879 $mocks['primary2']->expects( $this->any() )->method( 'getUniqueId' )
2880 ->will( $this->returnValue( 'primary2' ) );
2881 $mocks['primary2']->expects( $this->any() )->method( 'accountCreationType' )
2882 ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_LINK ) );
2883 $mocks['primary2']->expects( $this->any() )->method( 'getAuthenticationRequests' )
2884 ->will( $this->returnValue( [] ) );
2885 $mocks['primary2']->expects( $this->any() )
2886 ->method( 'providerAllowsAuthenticationDataChange' )
2887 ->will( $this->returnCallback( function ( $req ) use ( $good ) {
2888 return $req->key === 'generic' ? StatusValue::newFatal( 'no' ) : $good;
2889 } ) );
2890 $this->primaryauthMocks[] = $mocks['primary2'];
2891
2892 $this->preauthMocks = [ $mocks['pre'] ];
2893 $this->secondaryauthMocks = [ $mocks['secondary'] ];
2894 $this->initializeManager( true );
2895
2896 if ( $state ) {
2897 if ( isset( $state['continueRequests'] ) ) {
2898 $state['continueRequests'] = array_map( $makeReq, $state['continueRequests'] );
2899 }
2900 if ( $action === AuthManager::ACTION_LOGIN_CONTINUE ) {
2901 $this->request->getSession()->setSecret( 'AuthManager::authnState', $state );
2902 } elseif ( $action === AuthManager::ACTION_CREATE_CONTINUE ) {
2903 $this->request->getSession()->setSecret( 'AuthManager::accountCreationState', $state );
2904 } elseif ( $action === AuthManager::ACTION_LINK_CONTINUE ) {
2905 $this->request->getSession()->setSecret( 'AuthManager::accountLinkState', $state );
2906 }
2907 }
2908
2909 $expectReqs = array_map( $makeReq, $expect );
2910 if ( $action === AuthManager::ACTION_LOGIN ) {
2912 $req->action = $action;
2914 $expectReqs[] = $req;
2915 } elseif ( $action === AuthManager::ACTION_CREATE ) {
2917 $req->action = $action;
2918 $expectReqs[] = $req;
2920 $req->action = $action;
2922 $expectReqs[] = $req;
2923 }
2924 usort( $expectReqs, $cmpReqs );
2925
2926 $actual = $this->manager->getAuthenticationRequests( $action );
2927 foreach ( $actual as $req ) {
2928 // Don't test this here.
2930 }
2931 usort( $actual, $cmpReqs );
2932
2933 $this->assertEquals( $expectReqs, $actual );
2934
2935 // Test CreationReasonAuthenticationRequest gets returned
2936 if ( $action === AuthManager::ACTION_CREATE ) {
2938 $req->action = $action;
2940 $expectReqs[] = $req;
2941 usort( $expectReqs, $cmpReqs );
2942
2943 $actual = $this->manager->getAuthenticationRequests( $action, \User::newFromName( 'UTSysop' ) );
2944 foreach ( $actual as $req ) {
2945 // Don't test this here.
2947 }
2948 usort( $actual, $cmpReqs );
2949
2950 $this->assertEquals( $expectReqs, $actual );
2951 }
2952 }
2953
2954 public static function provideGetAuthenticationRequests() {
2955 return [
2956 [
2958 [ 'pre-login', 'primary-none-login', 'primary-create-login',
2959 'primary-link-login', 'secondary-login', 'generic' ],
2960 ],
2961 [
2963 [ 'pre-create', 'primary-none-create', 'primary-create-create',
2964 'primary-link-create', 'secondary-create', 'generic' ],
2965 ],
2966 [
2968 [ 'primary-link-link', 'generic' ],
2969 ],
2970 [
2972 [ 'primary-none-change', 'primary-create-change', 'primary-link-change',
2973 'secondary-change' ],
2974 ],
2975 [
2977 [ 'primary-none-remove', 'primary-create-remove', 'primary-link-remove',
2978 'secondary-remove' ],
2979 ],
2980 [
2982 [ 'primary-link-remove' ],
2983 ],
2984 [
2986 [],
2987 ],
2988 [
2990 $reqs = [ 'continue-login', 'foo', 'bar' ],
2991 [
2992 'continueRequests' => $reqs,
2993 ],
2994 ],
2995 [
2997 [],
2998 ],
2999 [
3001 $reqs = [ 'continue-create', 'foo', 'bar' ],
3002 [
3003 'continueRequests' => $reqs,
3004 ],
3005 ],
3006 [
3008 [],
3009 ],
3010 [
3012 $reqs = [ 'continue-link', 'foo', 'bar' ],
3013 [
3014 'continueRequests' => $reqs,
3015 ],
3016 ],
3017 ];
3018 }
3019
3021 $makeReq = function ( $key, $required ) {
3022 $req = $this->getMock( AuthenticationRequest::class );
3023 $req->expects( $this->any() )->method( 'getUniqueId' )
3024 ->will( $this->returnValue( $key ) );
3026 $req->key = $key;
3027 $req->required = $required;
3028 return $req;
3029 };
3030 $cmpReqs = function ( $a, $b ) {
3031 $ret = strcmp( get_class( $a ), get_class( $b ) );
3032 if ( !$ret ) {
3033 $ret = strcmp( $a->key, $b->key );
3034 }
3035 return $ret;
3036 };
3037
3038 $good = StatusValue::newGood();
3039
3040 $primary1 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
3041 $primary1->expects( $this->any() )->method( 'getUniqueId' )
3042 ->will( $this->returnValue( 'primary1' ) );
3043 $primary1->expects( $this->any() )->method( 'accountCreationType' )
3044 ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
3045 $primary1->expects( $this->any() )->method( 'getAuthenticationRequests' )
3046 ->will( $this->returnCallback( function ( $action ) use ( $makeReq ) {
3047 return [
3048 $makeReq( "primary-shared", AuthenticationRequest::REQUIRED ),
3049 $makeReq( "required", AuthenticationRequest::REQUIRED ),
3050 $makeReq( "optional", AuthenticationRequest::OPTIONAL ),
3051 $makeReq( "foo", AuthenticationRequest::REQUIRED ),
3052 $makeReq( "bar", AuthenticationRequest::REQUIRED ),
3053 $makeReq( "baz", AuthenticationRequest::OPTIONAL ),
3054 ];
3055 } ) );
3056
3057 $primary2 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
3058 $primary2->expects( $this->any() )->method( 'getUniqueId' )
3059 ->will( $this->returnValue( 'primary2' ) );
3060 $primary2->expects( $this->any() )->method( 'accountCreationType' )
3061 ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
3062 $primary2->expects( $this->any() )->method( 'getAuthenticationRequests' )
3063 ->will( $this->returnCallback( function ( $action ) use ( $makeReq ) {
3064 return [
3065 $makeReq( "primary-shared", AuthenticationRequest::REQUIRED ),
3066 $makeReq( "required2", AuthenticationRequest::REQUIRED ),
3067 $makeReq( "optional2", AuthenticationRequest::OPTIONAL ),
3068 ];
3069 } ) );
3070
3071 $secondary = $this->getMockForAbstractClass( SecondaryAuthenticationProvider::class );
3072 $secondary->expects( $this->any() )->method( 'getUniqueId' )
3073 ->will( $this->returnValue( 'secondary' ) );
3074 $secondary->expects( $this->any() )->method( 'getAuthenticationRequests' )
3075 ->will( $this->returnCallback( function ( $action ) use ( $makeReq ) {
3076 return [
3077 $makeReq( "foo", AuthenticationRequest::OPTIONAL ),
3078 $makeReq( "bar", AuthenticationRequest::REQUIRED ),
3079 $makeReq( "baz", AuthenticationRequest::REQUIRED ),
3080 ];
3081 } ) );
3082
3083 $rememberReq = new RememberMeAuthenticationRequest;
3084 $rememberReq->action = AuthManager::ACTION_LOGIN;
3085
3086 $this->primaryauthMocks = [ $primary1, $primary2 ];
3087 $this->secondaryauthMocks = [ $secondary ];
3088 $this->initializeManager( true );
3089
3090 $actual = $this->manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN );
3091 $expected = [
3092 $rememberReq,
3093 $makeReq( "primary-shared", AuthenticationRequest::PRIMARY_REQUIRED ),
3094 $makeReq( "required", AuthenticationRequest::PRIMARY_REQUIRED ),
3095 $makeReq( "required2", AuthenticationRequest::PRIMARY_REQUIRED ),
3096 $makeReq( "optional", AuthenticationRequest::OPTIONAL ),
3097 $makeReq( "optional2", AuthenticationRequest::OPTIONAL ),
3098 $makeReq( "foo", AuthenticationRequest::PRIMARY_REQUIRED ),
3099 $makeReq( "bar", AuthenticationRequest::REQUIRED ),
3100 $makeReq( "baz", AuthenticationRequest::REQUIRED ),
3101 ];
3102 usort( $actual, $cmpReqs );
3103 usort( $expected, $cmpReqs );
3104 $this->assertEquals( $expected, $actual );
3105
3106 $this->primaryauthMocks = [ $primary1 ];
3107 $this->secondaryauthMocks = [ $secondary ];
3108 $this->initializeManager( true );
3109
3110 $actual = $this->manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN );
3111 $expected = [
3112 $rememberReq,
3113 $makeReq( "primary-shared", AuthenticationRequest::PRIMARY_REQUIRED ),
3114 $makeReq( "required", AuthenticationRequest::PRIMARY_REQUIRED ),
3115 $makeReq( "optional", AuthenticationRequest::OPTIONAL ),
3116 $makeReq( "foo", AuthenticationRequest::PRIMARY_REQUIRED ),
3117 $makeReq( "bar", AuthenticationRequest::REQUIRED ),
3118 $makeReq( "baz", AuthenticationRequest::REQUIRED ),
3119 ];
3120 usort( $actual, $cmpReqs );
3121 usort( $expected, $cmpReqs );
3122 $this->assertEquals( $expected, $actual );
3123 }
3124
3125 public function testAllowsPropertyChange() {
3126 $mocks = [];
3127 foreach ( [ 'primary', 'secondary' ] as $key ) {
3128 $class = ucfirst( $key ) . 'AuthenticationProvider';
3129 $mocks[$key] = $this->getMockForAbstractClass(
3130 "MediaWiki\\Auth\\$class", [], "Mock$class"
3131 );
3132 $mocks[$key]->expects( $this->any() )->method( 'getUniqueId' )
3133 ->will( $this->returnValue( $key ) );
3134 $mocks[$key]->expects( $this->any() )->method( 'providerAllowsPropertyChange' )
3135 ->will( $this->returnCallback( function ( $prop ) use ( $key ) {
3136 return $prop !== $key;
3137 } ) );
3138 }
3139
3140 $this->primaryauthMocks = [ $mocks['primary'] ];
3141 $this->secondaryauthMocks = [ $mocks['secondary'] ];
3142 $this->initializeManager( true );
3143
3144 $this->assertTrue( $this->manager->allowsPropertyChange( 'foo' ) );
3145 $this->assertFalse( $this->manager->allowsPropertyChange( 'primary' ) );
3146 $this->assertFalse( $this->manager->allowsPropertyChange( 'secondary' ) );
3147 }
3148
3149 public function testAutoCreateOnLogin() {
3151
3152 $req = $this->getMock( AuthenticationRequest::class );
3153
3154 $mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
3155 $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'primary' ) );
3156 $mock->expects( $this->any() )->method( 'beginPrimaryAuthentication' )
3157 ->will( $this->returnValue( AuthenticationResponse::newPass( $username ) ) );
3158 $mock->expects( $this->any() )->method( 'accountCreationType' )
3159 ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
3160 $mock->expects( $this->any() )->method( 'testUserExists' )->will( $this->returnValue( true ) );
3161 $mock->expects( $this->any() )->method( 'testUserForCreation' )
3162 ->will( $this->returnValue( StatusValue::newGood() ) );
3163
3164 $mock2 = $this->getMockForAbstractClass( SecondaryAuthenticationProvider::class );
3165 $mock2->expects( $this->any() )->method( 'getUniqueId' )
3166 ->will( $this->returnValue( 'secondary' ) );
3167 $mock2->expects( $this->any() )->method( 'beginSecondaryAuthentication' )->will(
3168 $this->returnValue(
3169 AuthenticationResponse::newUI( [ $req ], $this->message( '...' ) )
3170 )
3171 );
3172 $mock2->expects( $this->any() )->method( 'continueSecondaryAuthentication' )
3173 ->will( $this->returnValue( AuthenticationResponse::newAbstain() ) );
3174 $mock2->expects( $this->any() )->method( 'testUserForCreation' )
3175 ->will( $this->returnValue( StatusValue::newGood() ) );
3176
3177 $this->primaryauthMocks = [ $mock ];
3178 $this->secondaryauthMocks = [ $mock2 ];
3179 $this->initializeManager( true );
3180 $this->manager->setLogger( new \Psr\Log\NullLogger() );
3181 $session = $this->request->getSession();
3182 $session->clear();
3183
3184 $this->assertSame( 0, \User::newFromName( $username )->getId(),
3185 'sanity check' );
3186
3187 $callback = $this->callback( function ( $user ) use ( $username ) {
3188 return $user->getName() === $username;
3189 } );
3190
3191 $this->hook( 'UserLoggedIn', $this->never() );
3192 $this->hook( 'LocalUserCreated', $this->once() )->with( $callback, $this->equalTo( true ) );
3193 $ret = $this->manager->beginAuthentication( [], 'http://localhost/' );
3194 $this->unhook( 'LocalUserCreated' );
3195 $this->unhook( 'UserLoggedIn' );
3196 $this->assertSame( AuthenticationResponse::UI, $ret->status );
3197
3198 $id = (int)\User::newFromName( $username )->getId();
3199 $this->assertNotSame( 0, \User::newFromName( $username )->getId() );
3200 $this->assertSame( 0, $session->getUser()->getId() );
3201
3202 $this->hook( 'UserLoggedIn', $this->once() )->with( $callback );
3203 $this->hook( 'LocalUserCreated', $this->never() );
3204 $ret = $this->manager->continueAuthentication( [] );
3205 $this->unhook( 'LocalUserCreated' );
3206 $this->unhook( 'UserLoggedIn' );
3207 $this->assertSame( AuthenticationResponse::PASS, $ret->status );
3208 $this->assertSame( $username, $ret->username );
3209 $this->assertSame( $id, $session->getUser()->getId() );
3210 }
3211
3212 public function testAutoCreateFailOnLogin() {
3214
3215 $mock = $this->getMockForAbstractClass(
3216 PrimaryAuthenticationProvider::class, [], "MockPrimaryAuthenticationProvider" );
3217 $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'primary' ) );
3218 $mock->expects( $this->any() )->method( 'beginPrimaryAuthentication' )
3219 ->will( $this->returnValue( AuthenticationResponse::newPass( $username ) ) );
3220 $mock->expects( $this->any() )->method( 'accountCreationType' )
3221 ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
3222 $mock->expects( $this->any() )->method( 'testUserExists' )->will( $this->returnValue( true ) );
3223 $mock->expects( $this->any() )->method( 'testUserForCreation' )
3224 ->will( $this->returnValue( StatusValue::newFatal( 'fail-from-primary' ) ) );
3225
3226 $this->primaryauthMocks = [ $mock ];
3227 $this->initializeManager( true );
3228 $this->manager->setLogger( new \Psr\Log\NullLogger() );
3229 $session = $this->request->getSession();
3230 $session->clear();
3231
3232 $this->assertSame( 0, $session->getUser()->getId(),
3233 'sanity check' );
3234 $this->assertSame( 0, \User::newFromName( $username )->getId(),
3235 'sanity check' );
3236
3237 $this->hook( 'UserLoggedIn', $this->never() );
3238 $this->hook( 'LocalUserCreated', $this->never() );
3239 $ret = $this->manager->beginAuthentication( [], 'http://localhost/' );
3240 $this->unhook( 'LocalUserCreated' );
3241 $this->unhook( 'UserLoggedIn' );
3242 $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
3243 $this->assertSame( 'authmanager-authn-autocreate-failed', $ret->message->getKey() );
3244
3245 $this->assertSame( 0, \User::newFromName( $username )->getId() );
3246 $this->assertSame( 0, $session->getUser()->getId() );
3247 }
3248
3250 $this->initializeManager( true );
3251
3252 $this->assertNull( $this->manager->getAuthenticationSessionData( 'foo' ) );
3253 $this->manager->setAuthenticationSessionData( 'foo', 'foo!' );
3254 $this->manager->setAuthenticationSessionData( 'bar', 'bar!' );
3255 $this->assertSame( 'foo!', $this->manager->getAuthenticationSessionData( 'foo' ) );
3256 $this->assertSame( 'bar!', $this->manager->getAuthenticationSessionData( 'bar' ) );
3257 $this->manager->removeAuthenticationSessionData( 'foo' );
3258 $this->assertNull( $this->manager->getAuthenticationSessionData( 'foo' ) );
3259 $this->assertSame( 'bar!', $this->manager->getAuthenticationSessionData( 'bar' ) );
3260 $this->manager->removeAuthenticationSessionData( 'bar' );
3261 $this->assertNull( $this->manager->getAuthenticationSessionData( 'bar' ) );
3262
3263 $this->manager->setAuthenticationSessionData( 'foo', 'foo!' );
3264 $this->manager->setAuthenticationSessionData( 'bar', 'bar!' );
3265 $this->manager->removeAuthenticationSessionData( null );
3266 $this->assertNull( $this->manager->getAuthenticationSessionData( 'foo' ) );
3267 $this->assertNull( $this->manager->getAuthenticationSessionData( 'bar' ) );
3268
3269 }
3270
3271 public function testCanLinkAccounts() {
3272 $types = [
3276 ];
3277
3278 foreach ( $types as $type => $can ) {
3279 $mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
3280 $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( $type ) );
3281 $mock->expects( $this->any() )->method( 'accountCreationType' )
3282 ->will( $this->returnValue( $type ) );
3283 $this->primaryauthMocks = [ $mock ];
3284 $this->initializeManager( true );
3285 $this->assertSame( $can, $this->manager->canCreateAccounts(), $type );
3286 }
3287 }
3288
3289 public function testBeginAccountLink() {
3290 $user = \User::newFromName( 'UTSysop' );
3291 $this->initializeManager();
3292
3293 $this->request->getSession()->setSecret( 'AuthManager::accountLinkState', 'test' );
3294 try {
3295 $this->manager->beginAccountLink( $user, [], 'http://localhost/' );
3296 $this->fail( 'Expected exception not thrown' );
3297 } catch ( \LogicException $ex ) {
3298 $this->assertEquals( 'Account linking is not possible', $ex->getMessage() );
3299 }
3300 $this->assertNull( $this->request->getSession()->getSecret( 'AuthManager::accountLinkState' ) );
3301
3302 $mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
3303 $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
3304 $mock->expects( $this->any() )->method( 'accountCreationType' )
3305 ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_LINK ) );
3306 $this->primaryauthMocks = [ $mock ];
3307 $this->initializeManager( true );
3308
3309 $ret = $this->manager->beginAccountLink( new \User, [], 'http://localhost/' );
3310 $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
3311 $this->assertSame( 'noname', $ret->message->getKey() );
3312
3313 $ret = $this->manager->beginAccountLink(
3314 \User::newFromName( 'UTDoesNotExist' ), [], 'http://localhost/'
3315 );
3316 $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
3317 $this->assertSame( 'authmanager-userdoesnotexist', $ret->message->getKey() );
3318 }
3319
3320 public function testContinueAccountLink() {
3321 $user = \User::newFromName( 'UTSysop' );
3322 $this->initializeManager();
3323
3324 $session = [
3325 'userid' => $user->getId(),
3326 'username' => $user->getName(),
3327 'primary' => 'X',
3328 ];
3329
3330 try {
3331 $this->manager->continueAccountLink( [] );
3332 $this->fail( 'Expected exception not thrown' );
3333 } catch ( \LogicException $ex ) {
3334 $this->assertEquals( 'Account linking is not possible', $ex->getMessage() );
3335 }
3336
3337 $mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
3338 $mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
3339 $mock->expects( $this->any() )->method( 'accountCreationType' )
3340 ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_LINK ) );
3341 $mock->expects( $this->any() )->method( 'beginPrimaryAccountLink' )->will(
3342 $this->returnValue( AuthenticationResponse::newFail( $this->message( 'fail' ) ) )
3343 );
3344 $this->primaryauthMocks = [ $mock ];
3345 $this->initializeManager( true );
3346
3347 $this->request->getSession()->setSecret( 'AuthManager::accountLinkState', null );
3348 $ret = $this->manager->continueAccountLink( [] );
3349 $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
3350 $this->assertSame( 'authmanager-link-not-in-progress', $ret->message->getKey() );
3351
3352 $this->request->getSession()->setSecret( 'AuthManager::accountLinkState',
3353 [ 'username' => $user->getName() . '<>' ] + $session );
3354 $ret = $this->manager->continueAccountLink( [] );
3355 $this->assertSame( AuthenticationResponse::FAIL, $ret->status );
3356 $this->assertSame( 'noname', $ret->message->getKey() );
3357 $this->assertNull( $this->request->getSession()->getSecret( 'AuthManager::accountLinkState' ) );
3358
3359 $id = $user->getId();
3360 $this->request->getSession()->setSecret( 'AuthManager::accountLinkState',
3361 [ 'userid' => $id + 1 ] + $session );
3362 try {
3363 $ret = $this->manager->continueAccountLink( [] );
3364 $this->fail( 'Expected exception not thrown' );
3365 } catch ( \UnexpectedValueException $ex ) {
3366 $this->assertEquals(
3367 "User \"{$user->getName()}\" is valid, but ID $id != " . ( $id + 1 ) . '!',
3368 $ex->getMessage()
3369 );
3370 }
3371 $this->assertNull( $this->request->getSession()->getSecret( 'AuthManager::accountLinkState' ) );
3372 }
3373
3380 public function testAccountLink(
3381 StatusValue $preTest, array $primaryResponses, array $managerResponses
3382 ) {
3383 $user = \User::newFromName( 'UTSysop' );
3384
3385 $this->initializeManager();
3386
3387 // Set up lots of mocks...
3388 $req = $this->getMockForAbstractClass( AuthenticationRequest::class );
3389 $req->primary = $primaryResponses;
3390 $mocks = [];
3391
3392 foreach ( [ 'pre', 'primary' ] as $key ) {
3393 $class = ucfirst( $key ) . 'AuthenticationProvider';
3394 $mocks[$key] = $this->getMockForAbstractClass(
3395 "MediaWiki\\Auth\\$class", [], "Mock$class"
3396 );
3397 $mocks[$key]->expects( $this->any() )->method( 'getUniqueId' )
3398 ->will( $this->returnValue( $key ) );
3399
3400 for ( $i = 2; $i <= 3; $i++ ) {
3401 $mocks[$key . $i] = $this->getMockForAbstractClass(
3402 "MediaWiki\\Auth\\$class", [], "Mock$class"
3403 );
3404 $mocks[$key . $i]->expects( $this->any() )->method( 'getUniqueId' )
3405 ->will( $this->returnValue( $key . $i ) );
3406 }
3407 }
3408
3409 $mocks['pre']->expects( $this->any() )->method( 'testForAccountLink' )
3410 ->will( $this->returnCallback(
3411 function ( $u )
3412 use ( $user, $preTest )
3413 {
3414 $this->assertSame( $user->getId(), $u->getId() );
3415 $this->assertSame( $user->getName(), $u->getName() );
3416 return $preTest;
3417 }
3418 ) );
3419
3420 $mocks['pre2']->expects( $this->atMost( 1 ) )->method( 'testForAccountLink' )
3421 ->will( $this->returnValue( StatusValue::newGood() ) );
3422
3423 $mocks['primary']->expects( $this->any() )->method( 'accountCreationType' )
3424 ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_LINK ) );
3425 $ct = count( $req->primary );
3426 $callback = $this->returnCallback( function ( $u, $reqs ) use ( $user, $req ) {
3427 $this->assertSame( $user->getId(), $u->getId() );
3428 $this->assertSame( $user->getName(), $u->getName() );
3429 $foundReq = false;
3430 foreach ( $reqs as $r ) {
3431 $this->assertSame( $user->getName(), $r->username );
3432 $foundReq = $foundReq || get_class( $r ) === get_class( $req );
3433 }
3434 $this->assertTrue( $foundReq, '$reqs contains $req' );
3435 return array_shift( $req->primary );
3436 } );
3437 $mocks['primary']->expects( $this->exactly( min( 1, $ct ) ) )
3438 ->method( 'beginPrimaryAccountLink' )
3439 ->will( $callback );
3440 $mocks['primary']->expects( $this->exactly( max( 0, $ct - 1 ) ) )
3441 ->method( 'continuePrimaryAccountLink' )
3442 ->will( $callback );
3443
3445 $mocks['primary2']->expects( $this->any() )->method( 'accountCreationType' )
3446 ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_LINK ) );
3447 $mocks['primary2']->expects( $this->atMost( 1 ) )->method( 'beginPrimaryAccountLink' )
3448 ->will( $this->returnValue( $abstain ) );
3449 $mocks['primary2']->expects( $this->never() )->method( 'continuePrimaryAccountLink' );
3450 $mocks['primary3']->expects( $this->any() )->method( 'accountCreationType' )
3451 ->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
3452 $mocks['primary3']->expects( $this->never() )->method( 'beginPrimaryAccountLink' );
3453 $mocks['primary3']->expects( $this->never() )->method( 'continuePrimaryAccountLink' );
3454
3455 $this->preauthMocks = [ $mocks['pre'], $mocks['pre2'] ];
3456 $this->primaryauthMocks = [ $mocks['primary3'], $mocks['primary2'], $mocks['primary'] ];
3457 $this->logger = new \TestLogger( true, function ( $message, $level ) {
3458 return $level === LogLevel::DEBUG ? null : $message;
3459 } );
3460 $this->initializeManager( true );
3461
3462 $constraint = \PHPUnit_Framework_Assert::logicalOr(
3463 $this->equalTo( AuthenticationResponse::PASS ),
3464 $this->equalTo( AuthenticationResponse::FAIL )
3465 );
3466 $providers = array_merge( $this->preauthMocks, $this->primaryauthMocks );
3467 foreach ( $providers as $p ) {
3468 $p->postCalled = false;
3469 $p->expects( $this->atMost( 1 ) )->method( 'postAccountLink' )
3470 ->willReturnCallback( function ( $user, $response ) use ( $constraint, $p ) {
3471 $this->assertInstanceOf( 'User', $user );
3472 $this->assertSame( 'UTSysop', $user->getName() );
3473 $this->assertInstanceOf( AuthenticationResponse::class, $response );
3474 $this->assertThat( $response->status, $constraint );
3475 $p->postCalled = $response->status;
3476 } );
3477 }
3478
3479 $first = true;
3480 $created = false;
3481 $expectLog = [];
3482 foreach ( $managerResponses as $i => $response ) {
3483 if ( $response instanceof AuthenticationResponse &&
3485 ) {
3486 $expectLog[] = [ LogLevel::INFO, 'Account linked to {user} by primary' ];
3487 }
3488
3489 $ex = null;
3490 try {
3491 if ( $first ) {
3492 $ret = $this->manager->beginAccountLink( $user, [ $req ], 'http://localhost/' );
3493 } else {
3494 $ret = $this->manager->continueAccountLink( [ $req ] );
3495 }
3496 if ( $response instanceof \Exception ) {
3497 $this->fail( 'Expected exception not thrown', "Response $i" );
3498 }
3499 } catch ( \Exception $ex ) {
3500 if ( !$response instanceof \Exception ) {
3501 throw $ex;
3502 }
3503 $this->assertEquals( $response->getMessage(), $ex->getMessage(), "Response $i, exception" );
3504 $this->assertNull( $this->request->getSession()->getSecret( 'AuthManager::accountLinkState' ),
3505 "Response $i, exception, session state" );
3506 return;
3507 }
3508
3509 $this->assertSame( 'http://localhost/', $req->returnToUrl );
3510
3511 $ret->message = $this->message( $ret->message );
3512 $this->assertEquals( $response, $ret, "Response $i, response" );
3513 if ( $response->status === AuthenticationResponse::PASS ||
3515 ) {
3516 $this->assertNull( $this->request->getSession()->getSecret( 'AuthManager::accountLinkState' ),
3517 "Response $i, session state" );
3518 foreach ( $providers as $p ) {
3519 $this->assertSame( $response->status, $p->postCalled,
3520 "Response $i, post-auth callback called" );
3521 }
3522 } else {
3523 $this->assertNotNull(
3524 $this->request->getSession()->getSecret( 'AuthManager::accountLinkState' ),
3525 "Response $i, session state"
3526 );
3527 foreach ( $ret->neededRequests as $neededReq ) {
3528 $this->assertEquals( AuthManager::ACTION_LINK, $neededReq->action,
3529 "Response $i, neededRequest action" );
3530 }
3531 $this->assertEquals(
3532 $ret->neededRequests,
3533 $this->manager->getAuthenticationRequests( AuthManager::ACTION_LINK_CONTINUE ),
3534 "Response $i, continuation check"
3535 );
3536 foreach ( $providers as $p ) {
3537 $this->assertFalse( $p->postCalled, "Response $i, post-auth callback not called" );
3538 }
3539 }
3540
3541 $first = false;
3542 }
3543
3544 $this->assertSame( $expectLog, $this->logger->getBuffer() );
3545 }
3546
3547 public function provideAccountLink() {
3548 $req = $this->getMockForAbstractClass( AuthenticationRequest::class );
3549 $good = StatusValue::newGood();
3550
3551 return [
3552 'Pre-link test fail in pre' => [
3553 StatusValue::newFatal( 'fail-from-pre' ),
3554 [],
3555 [
3556 AuthenticationResponse::newFail( $this->message( 'fail-from-pre' ) ),
3557 ]
3558 ],
3559 'Failure in primary' => [
3560 $good,
3561 $tmp = [
3562 AuthenticationResponse::newFail( $this->message( 'fail-from-primary' ) ),
3563 ],
3564 $tmp
3565 ],
3566 'All primary abstain' => [
3567 $good,
3568 [
3570 ],
3571 [
3572 AuthenticationResponse::newFail( $this->message( 'authmanager-link-no-primary' ) )
3573 ]
3574 ],
3575 'Primary UI, then redirect, then fail' => [
3576 $good,
3577 $tmp = [
3578 AuthenticationResponse::newUI( [ $req ], $this->message( '...' ) ),
3579 AuthenticationResponse::newRedirect( [ $req ], '/foo.html', [ 'foo' => 'bar' ] ),
3580 AuthenticationResponse::newFail( $this->message( 'fail-in-primary-continue' ) ),
3581 ],
3582 $tmp
3583 ],
3584 'Primary redirect, then abstain' => [
3585 $good,
3586 [
3588 [ $req ], '/foo.html', [ 'foo' => 'bar' ]
3589 ),
3591 ],
3592 [
3593 $tmp,
3594 new \DomainException(
3595 'MockPrimaryAuthenticationProvider::continuePrimaryAccountLink() returned ABSTAIN'
3596 )
3597 ]
3598 ],
3599 'Primary UI, then pass' => [
3600 $good,
3601 [
3602 $tmp1 = AuthenticationResponse::newUI( [ $req ], $this->message( '...' ) ),
3604 ],
3605 [
3606 $tmp1,
3608 ]
3609 ],
3610 'Primary pass' => [
3611 $good,
3612 [
3614 ],
3615 [
3617 ]
3618 ],
3619 ];
3620 }
3621}
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
</td >< td > &</td >< td > t want your writing to be edited mercilessly and redistributed at will
$wgGroupPermissions
Permission keys given to users in each group.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfMemcKey()
Make a cache key for the local wiki.
Simple store for keeping values in an associative array for the current process.
setMwGlobals( $pairs, $value=null)
hideDeprecated( $function)
Don't throw a warning if $function is deprecated and called later.
stashMwGlobals( $globalKeys)
Stashes the global, will be restored in tearDown()
AuthManager Database MediaWiki\Auth\AuthManager.
onSecuritySensitiveOperationStatus(&$status, $operation, $session, $time)
testUserExists( $primary1Exists, $primary2Exists, $expect)
provideUserExists
message( $key, $params=[])
Ensure a value is a clean Message object.
testAllowsAuthenticationDataChange( $primaryReturn, $secondaryReturn, $expect)
provideAllowsAuthenticationDataChange
testAccountCreationLogging( $isAnon, $logSubtype)
provideAccountCreationLogging
getMockSessionProvider( $canChangeUser=null, array $methods=[])
Setup SessionManager with a mock session provider.
testGetAuthenticationRequests( $action, $expect, $state=[])
provideGetAuthenticationRequests
initializeConfig()
Initialize the AuthManagerConfig variable in $this->config.
hook( $hook, $expect)
Sets a mock on a hook.
testAccountLink(StatusValue $preTest, array $primaryResponses, array $managerResponses)
provideAccountLink
testAuthentication(StatusValue $preResponse, array $primaryResponses, array $secondaryResponses, array $managerResponses, $link=false)
provideAuthentication
testUserCanAuthenticate( $primary1Can, $primary2Can, $expect)
provideUserCanAuthenticate
Psr Log LoggerInterface $logger
initializeManager( $regen=false)
Initialize $this->manager.
TestingAccessWrapper $managerPriv
testSecuritySensitiveOperationStatus( $mutableSession)
provideSecuritySensitiveOperationStatus
testAccountCreation(StatusValue $preTest, $primaryTest, $secondaryTest, array $primaryResponses, array $secondaryResponses, array $managerResponses)
provideAccountCreation
This serves as the entry point to the authentication system.
const ACTION_UNLINK
Like ACTION_REMOVE but for linking providers only.
const SEC_FAIL
Security-sensitive should not be performed.
const ACTION_LOGIN_CONTINUE
Continue a login process that was interrupted by the need for user input or communication with an ext...
const SEC_REAUTH
Security-sensitive operations should re-authenticate.
const ACTION_CREATE_CONTINUE
Continue a user creation process that was interrupted by the need for user input or communication wit...
static singleton()
Get the global AuthManager.
const SEC_OK
Security-sensitive operations are ok.
const ACTION_LINK_CONTINUE
Continue a user linking process that was interrupted by the need for user input or communication with...
const ACTION_CHANGE
Change a user's credentials.
const ACTION_REMOVE
Remove a user's credentials.
const ACTION_LINK
Link an existing user to a third-party account.
const AUTOCREATE_SOURCE_SESSION
Auto-creation is due to SessionManager.
const ACTION_LOGIN
Log in with an existing (not necessarily local) user.
const ACTION_CREATE
Create a new user.
const OPTIONAL
Indicates that the request is not required for authentication to proceed.
const PRIMARY_REQUIRED
Indicates that the request is required by a primary authentication provider.
const REQUIRED
Indicates that the request is required for authentication to proceed.
This is a value object to hold authentication response data.
const FAIL
Indicates that the authentication failed.
const PASS
Indicates that the authentication succeeded.
const UI
Indicates that the authentication needs further user input of some sort.
static newRedirect(array $reqs, $redirectTarget, $redirectApiData=null)
static newUI(array $reqs, Message $msg, $msgtype='warning')
const RESTART
Indicates that third-party authentication succeeded but no user exists.
This transfers state between the login and account creation flows.
Returned from account creation to allow for logging into the created account.
Authentication request for the reason given for account creation.
This is an authentication request added by AuthManager to show a "remember me" checkbox.
This represents additional user data requested on the account creation form.
AuthenticationRequest to ensure something with a username is present.
Value object returned by SessionProvider.
Object holding data about a session's user.
Definition UserInfo.php:51
static newFromUser(User $user, $verified=false)
Create an instance from an existing User object.
Definition UserInfo.php:116
static BagOStuff[] $instances
Map of (id => BagOStuff)
static getMain()
Static methods.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Circumvent access restrictions on object internals.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:48
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
$res
Definition database.txt:21
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition deferred.txt:11
when a variable name is used in a it is silently declared as a new local masking the global
Definition design.txt:95
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such and we might be restricted by PHP settings such as safe mode or open_basedir We cannot assume that the software even has read access anywhere useful Many shared hosts run all users web applications under the same so they can t rely on Unix and must forbid reads to even standard directories like tmp lest users read each others files We cannot assume that the user has the ability to install or run any programs not written as web accessible PHP scripts Since anything that works on cheap shared hosting will work if you have shell or root access MediaWiki s design is based around catering to the lowest common denominator Although we support higher end setups as the way many things work by default is tailored toward shared hosting These defaults are unconventional from the point of view of and they certainly aren t ideal for someone who s installing MediaWiki as MediaWiki does not conform to normal Unix filesystem layout Hopefully we ll offer direct support for standard layouts in the but for now *any change to the location of files is unsupported *Moving things and leaving symlinks will *probably *not break anything
this hook is for auditing only $req
Definition hooks.txt:1010
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set $status
Definition hooks.txt:1049
the array() calling protocol came about after MediaWiki 1.4rc1.
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition hooks.txt:249
namespace are movable Hooks may change this value to override the return value of MWNamespace::isMovable(). 'NewDifferenceEngine' do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition hooks.txt:2568
see documentation in includes Linker php for Linker::makeImageLink & $time
Definition hooks.txt:1752
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt;div ...>$1&lt;/div>"). - flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException':Called before an exception(or PHP error) is logged. This is meant for integration with external error aggregation services
returning false will NOT prevent logging a wrapping ErrorException instead of letting the login form give the generic error message that the account does not exist For when the account has been renamed or deleted or an array to pass a message key and parameters create2 Corresponds to logging log_action database field and which is displayed in the UI similar to $comment this hook should only be used to add variables that depend on the current page request
Definition hooks.txt:2158
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return true
Definition hooks.txt:1950
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses & $ret
Definition hooks.txt:1949
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition hooks.txt:2900
$wgHooks['ArticleShow'][]
Definition hooks.txt:110
this hook is for auditing only or null if authentication failed before getting that far $username
Definition hooks.txt:807
this hook is for auditing only $response
Definition hooks.txt:805
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:304
processing should stop and the error should be shown to the user * false
Definition hooks.txt:189
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition injection.txt:37
Interface for configuration instances.
Definition Config.php:28
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
const TYPE_LINK
Provider can link to existing accounts elsewhere.
const TYPE_NONE
Provider cannot create or link to accounts.
$context
Definition load.php:50
$cache
Definition mcc.php:33
const DB_MASTER
Definition defines.php:23
$params