8 use Psr\Log\LoggerInterface;
12 use Wikimedia\ScopedCallback;
13 use Wikimedia\TestingAccessWrapper;
43 protected function hook( $hook, $expect ) {
44 $mock = $this->getMockBuilder( __CLASS__ )
45 ->setMethods( [
"on$hook" ] )
48 return $mock->expects( $expect )->method(
"on$hook" );
67 if ( $key ===
null ) {
72 $key = $key->getKey();
90 foreach ( (
new \ReflectionClass( $expected ) )->getProperties()
as $prop ) {
91 $name = $prop->getName();
92 $usedMsg = ltrim(
"$msg ($name)" );
93 if (
$name ===
'message' && $expected->message ) {
94 $this->assertSame( $expected->message->serialize(), $actual->message->serialize(),
97 $this->assertEquals( $expected->$name, $actual->$name, $usedMsg );
117 foreach ( [
'preauth',
'primaryauth',
'secondaryauth' ]
as $type ) {
118 $key =
$type .
'Mocks';
119 foreach ( $this->$key
as $mock ) {
120 $config[
$type][$mock->getUniqueId()] = [
'factory' =>
function ()
use ( $mock ) {
126 $this->config->set(
'AuthManagerConfig',
$config );
127 $this->config->set(
'LanguageCode',
'en' );
128 $this->config->set(
'NewUserLog',
false );
136 if ( $regen || !$this->config ) {
137 $this->config = new \HashConfig();
139 if ( $regen || !$this->
request ) {
140 $this->
request = new \FauxRequest();
142 if ( !$this->logger ) {
143 $this->logger = new \TestLogger();
146 if ( $regen || !$this->config->has(
'AuthManagerConfig' ) ) {
150 $this->manager->setLogger( $this->logger );
151 $this->managerPriv = TestingAccessWrapper::newFromObject( $this->manager );
161 if ( !$this->config ) {
162 $this->config = new \HashConfig();
165 $this->config->set(
'ObjectCacheSessionExpiry', 100 );
167 $methods[] =
'__toString';
168 $methods[] =
'describe';
169 if ( $canChangeUser !==
null ) {
170 $methods[] =
'canChangeUser';
173 ->setMethods( $methods )
175 $provider->expects( $this->
any() )->method(
'__toString' )
176 ->will( $this->returnValue(
'MockSessionProvider' ) );
177 $provider->expects( $this->
any() )->method(
'describe' )
178 ->will( $this->returnValue(
'MockSessionProvider sessions' ) );
179 if ( $canChangeUser !==
null ) {
180 $provider->expects( $this->
any() )->method(
'canChangeUser' )
181 ->will( $this->returnValue( $canChangeUser ) );
183 $this->config->set(
'SessionProviders', [
184 [
'factory' =>
function ()
use ( $provider ) {
189 $manager = new \MediaWiki\Session\SessionManager( [
190 'config' => $this->config,
191 'logger' =>
new \Psr\Log\NullLogger(),
194 TestingAccessWrapper::newFromObject(
$manager )->getProvider( (
string)$provider );
202 return [ $provider, $reset ];
209 $rProp->setAccessible(
true );
210 $old = $rProp->getValue();
211 $cb =
new ScopedCallback( [ $rProp,
'setValue' ], [ $old ] );
212 $rProp->setValue(
null );
220 TestingAccessWrapper::newFromObject( $singleton )->config
228 $this->assertFalse( $this->manager->canAuthenticateNow() );
229 ScopedCallback::consume( $reset );
232 $this->assertTrue( $this->manager->canAuthenticateNow() );
233 ScopedCallback::consume( $reset );
243 foreach ( $mocks
as $key => $mock ) {
244 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue( $key ) );
246 $mocks[0]->expects( $this->once() )->method(
'providerNormalizeUsername' )
247 ->with( $this->identicalTo(
'XYZ' ) )
248 ->willReturn(
'Foo' );
249 $mocks[1]->expects( $this->once() )->method(
'providerNormalizeUsername' )
250 ->with( $this->identicalTo(
'XYZ' ) )
251 ->willReturn(
'Foo' );
252 $mocks[2]->expects( $this->once() )->method(
'providerNormalizeUsername' )
253 ->with( $this->identicalTo(
'XYZ' ) )
254 ->willReturn(
null );
255 $mocks[3]->expects( $this->once() )->method(
'providerNormalizeUsername' )
256 ->with( $this->identicalTo(
'XYZ' ) )
257 ->willReturn(
'Bar!' );
259 $this->primaryauthMocks = $mocks;
263 $this->assertSame( [
'Foo',
'Bar!' ], $this->manager->normalizeUsername(
'XYZ' ) );
271 $this->logger = new \Psr\Log\NullLogger();
277 $mutableSession, [
'provideSessionInfo' ]
279 $provider->expects( $this->
any() )->method(
'provideSessionInfo' )
280 ->will( $this->returnCallback(
function ()
use ( $provider, &$provideUser ) {
282 'provider' => $provider,
290 $this->config->set(
'ReauthenticateTime', [] );
291 $this->config->set(
'AllowSecuritySensitiveOperationIfCannotReauthenticate', [] );
293 $session = $provider->getManager()->getSessionForRequest( $this->
request );
294 $this->assertSame( 0, $session->getUser()->getId(),
'sanity check' );
297 $session->set(
'AuthManager:lastAuthId', 0 );
298 $session->set(
'AuthManager:lastAuthTimestamp', time() - 5 );
299 $this->assertSame( $reauth, $this->manager->securitySensitiveOperationStatus(
'foo' ) );
301 $provideUser =
$user;
302 $session = $provider->getManager()->getSessionForRequest( $this->
request );
303 $this->assertSame(
$user->getId(), $session->getUser()->getId(),
'sanity check' );
306 $session->set(
'AuthManager:lastAuthId',
$user->getId() + 1 );
307 $session->set(
'AuthManager:lastAuthTimestamp', time() - 5 );
309 $this->manager->securitySensitiveOperationStatus(
'foo' );
310 $this->fail(
'Expected exception not thrown' );
311 }
catch ( \UnexpectedValueException $ex ) {
314 ?
'$wgReauthenticateTime lacks a default'
315 :
'$wgAllowSecuritySensitiveOperationIfCannotReauthenticate lacks a default',
320 if ( $mutableSession ) {
321 $this->config->set(
'ReauthenticateTime', [
328 $session->set(
'AuthManager:lastAuthId',
$user->getId() + 1 );
329 $session->set(
'AuthManager:lastAuthTimestamp', time() - 5 );
341 $session->set(
'AuthManager:lastAuthId',
$user->getId() );
342 $session->set(
'AuthManager:lastAuthTimestamp',
null );
354 $session->set(
'AuthManager:lastAuthTimestamp', time() - 5 );
360 $session->set(
'AuthManager:lastAuthTimestamp', time() - 20 );
369 $this->config->set(
'AllowSecuritySensitiveOperationIfCannotReauthenticate', [
388 ]
as $hook => $expect ) {
389 $this->
hook(
'SecuritySensitiveOperationStatus', $this->exactly( 2 ) )
393 $this->callback(
function (
$s )
use ( $session ) {
394 return $s->getId() === $session->getId();
396 $mutableSession ? $this->equalTo( 500, 1 ) : $this->equalTo( -1 )
398 ->
will( $this->returnCallback(
function ( &$v )
use ( $hook ) {
402 $session->set(
'AuthManager:lastAuthTimestamp', time() - 500 );
404 $expect, $this->manager->securitySensitiveOperationStatus(
'test' ),
"hook $hook"
407 $expect, $this->manager->securitySensitiveOperationStatus(
'test2' ),
"hook $hook"
409 $this->
unhook(
'SecuritySensitiveOperationStatus' );
412 ScopedCallback::consume( $reset );
433 $mock1->expects( $this->
any() )->method(
'getUniqueId' )
434 ->will( $this->returnValue(
'primary1' ) );
435 $mock1->expects( $this->
any() )->method(
'testUserCanAuthenticate' )
436 ->with( $this->equalTo(
'UTSysop' ) )
437 ->will( $this->returnValue( $primary1Can ) );
439 $mock2->expects( $this->
any() )->method(
'getUniqueId' )
440 ->will( $this->returnValue(
'primary2' ) );
441 $mock2->expects( $this->
any() )->method(
'testUserCanAuthenticate' )
442 ->with( $this->equalTo(
'UTSysop' ) )
443 ->will( $this->returnValue( $primary2Can ) );
444 $this->primaryauthMocks = [ $mock1, $mock2 ];
447 $this->assertSame( $expect, $this->manager->userCanAuthenticate(
'UTSysop' ) );
452 [
false,
false,
false ],
453 [
true,
false,
true ],
454 [
false,
true,
true ],
455 [
true,
true,
true ],
463 $mock->expects( $this->
any() )->method(
'getUniqueId' )
464 ->will( $this->returnValue(
'primary' ) );
465 $mock->expects( $this->once() )->method(
'providerRevokeAccessForUser' )
466 ->with( $this->equalTo(
'UTSysop' ) );
467 $this->primaryauthMocks = [ $mock ];
470 $this->logger->setCollect(
true );
472 $this->manager->revokeAccessForUser(
'UTSysop' );
475 [ LogLevel::INFO,
'Revoking access for {user}' ],
476 ], $this->logger->getBuffer() );
485 foreach ( $mocks
as $key => $mock ) {
486 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue( $key ) );
487 $mock->expects( $this->once() )->method(
'setLogger' );
488 $mock->expects( $this->once() )->method(
'setManager' );
489 $mock->expects( $this->once() )->method(
'setConfig' );
491 $this->preauthMocks = [ $mocks[
'pre'] ];
492 $this->primaryauthMocks = [ $mocks[
'primary'] ];
493 $this->secondaryauthMocks = [ $mocks[
'secondary'] ];
499 $this->managerPriv->getAuthenticationProvider(
'primary' )
503 $this->managerPriv->getAuthenticationProvider(
'secondary' )
507 $this->managerPriv->getAuthenticationProvider(
'pre' )
510 [
'pre' => $mocks[
'pre'] ],
511 $this->managerPriv->getPreAuthenticationProviders()
514 [
'primary' => $mocks[
'primary'] ],
515 $this->managerPriv->getPrimaryAuthenticationProviders()
518 [
'secondary' => $mocks[
'secondary'] ],
519 $this->managerPriv->getSecondaryAuthenticationProviders()
525 $mock1->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
526 $mock2->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
527 $this->preauthMocks = [ $mock1 ];
528 $this->primaryauthMocks = [ $mock2 ];
529 $this->secondaryauthMocks = [];
532 $this->managerPriv->getAuthenticationProvider(
'Y' );
533 $this->fail(
'Expected exception not thrown' );
534 }
catch ( \RuntimeException $ex ) {
535 $class1 = get_class( $mock1 );
536 $class2 = get_class( $mock2 );
538 "Duplicate specifications for id X (classes $class1 and $class2)", $ex->getMessage()
544 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
545 $class = get_class( $mock );
546 $this->preauthMocks = [ $mock ];
547 $this->primaryauthMocks = [ $mock ];
548 $this->secondaryauthMocks = [ $mock ];
551 $this->managerPriv->getPreAuthenticationProviders();
552 $this->fail(
'Expected exception not thrown' );
553 }
catch ( \RuntimeException $ex ) {
555 "Expected instance of MediaWiki\\Auth\\PreAuthenticationProvider, got $class",
560 $this->managerPriv->getPrimaryAuthenticationProviders();
561 $this->fail(
'Expected exception not thrown' );
562 }
catch ( \RuntimeException $ex ) {
564 "Expected instance of MediaWiki\\Auth\\PrimaryAuthenticationProvider, got $class",
569 $this->managerPriv->getSecondaryAuthenticationProviders();
570 $this->fail(
'Expected exception not thrown' );
571 }
catch ( \RuntimeException $ex ) {
573 "Expected instance of MediaWiki\\Auth\\SecondaryAuthenticationProvider, got $class",
582 $mock1->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'A' ) );
583 $mock2->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'B' ) );
584 $mock3->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'C' ) );
585 $this->preauthMocks = [];
586 $this->primaryauthMocks = [ $mock1, $mock2, $mock3 ];
587 $this->secondaryauthMocks = [];
589 $config = $this->config->
get(
'AuthManagerConfig' );
593 [
'A' => $mock1,
'B' => $mock2,
'C' => $mock3 ],
594 $this->managerPriv->getPrimaryAuthenticationProviders(),
598 $config[
'primaryauth'][
'A'][
'sort'] = 100;
599 $config[
'primaryauth'][
'C'][
'sort'] = -1;
600 $this->config->set(
'AuthManagerConfig',
$config );
603 [
'C' => $mock3,
'B' => $mock2,
'A' => $mock1 ],
604 $this->managerPriv->getPrimaryAuthenticationProviders()
612 $contLang, $useContextLang, $expectedLang, $expectedVariant
622 $user->addToDatabase();
623 $oldToken =
$user->getToken();
624 $this->managerPriv->setDefaultUserOptions(
$user, $useContextLang );
625 $user->saveSettings();
626 $this->assertNotEquals( $oldToken,
$user->getToken() );
627 $this->assertSame( $expectedLang,
$user->getOption(
'language' ) );
628 $this->assertSame( $expectedVariant,
$user->getOption(
'variant' ) );
633 [
'zh',
false,
'zh',
'zh' ],
634 [
'zh',
true,
'de',
'zh' ],
635 [
'fr',
true,
'de',
null ],
643 $mockA->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'A' ) );
644 $mockB->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'B' ) );
645 $mockB2->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'B' ) );
646 $this->primaryauthMocks = [ $mockA ];
648 $this->logger = new \TestLogger(
true );
652 $this->manager->forcePrimaryAuthenticationProviders( [ $mockB ],
'testing' );
654 [
'B' => $mockB ], $this->managerPriv->getPrimaryAuthenticationProviders()
656 $this->assertSame(
null, $this->managerPriv->getAuthenticationProvider(
'A' ) );
657 $this->assertSame( $mockB, $this->managerPriv->getAuthenticationProvider(
'B' ) );
659 [ LogLevel::WARNING,
'Overriding AuthManager primary authn because testing' ],
660 ], $this->logger->getBuffer() );
661 $this->logger->clearBuffer();
665 $this->assertSame( $mockA, $this->managerPriv->getAuthenticationProvider(
'A' ) );
666 $this->assertSame(
null, $this->managerPriv->getAuthenticationProvider(
'B' ) );
667 $this->
request->getSession()->setSecret(
'AuthManager::authnState',
'test' );
668 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState',
'test' );
669 $this->manager->forcePrimaryAuthenticationProviders( [ $mockB ],
'testing' );
671 [
'B' => $mockB ], $this->managerPriv->getPrimaryAuthenticationProviders()
673 $this->assertSame(
null, $this->managerPriv->getAuthenticationProvider(
'A' ) );
674 $this->assertSame( $mockB, $this->managerPriv->getAuthenticationProvider(
'B' ) );
675 $this->assertNull( $this->
request->getSession()->getSecret(
'AuthManager::authnState' ) );
677 $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' )
680 [ LogLevel::WARNING,
'Overriding AuthManager primary authn because testing' ],
683 'PrimaryAuthenticationProviders have already been accessed! I hope nothing breaks.'
685 ], $this->logger->getBuffer() );
686 $this->logger->clearBuffer();
691 $this->manager->forcePrimaryAuthenticationProviders( [ $mockB, $mockB2 ],
'testing' );
692 $this->fail(
'Expected exception not thrown' );
693 }
catch ( \RuntimeException $ex ) {
694 $class1 = get_class( $mockB );
695 $class2 = get_class( $mockB2 );
697 "Duplicate specifications for id B (classes $class2 and $class1)", $ex->getMessage()
703 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
704 $class = get_class( $mock );
706 $this->manager->forcePrimaryAuthenticationProviders( [ $mock ],
'testing' );
707 $this->fail(
'Expected exception not thrown' );
708 }
catch ( \RuntimeException $ex ) {
710 "Expected instance of MediaWiki\\Auth\\PrimaryAuthenticationProvider, got $class",
721 $this->
hook(
'UserLoggedIn', $this->never() );
722 $this->
request->getSession()->setSecret(
'AuthManager::authnState',
'test' );
724 $this->manager->beginAuthentication( [],
'http://localhost/' );
725 $this->fail(
'Expected exception not thrown' );
726 }
catch ( \LogicException $ex ) {
727 $this->assertSame(
'Authentication is not possible now', $ex->getMessage() );
729 $this->
unhook(
'UserLoggedIn' );
730 $this->assertNull( $this->
request->getSession()->getSecret(
'AuthManager::authnState' ) );
731 ScopedCallback::consume( $reset );
739 $this->
hook(
'UserLoggedIn', $this->never() );
741 $this->manager->beginAuthentication( $reqs,
'http://localhost/' );
742 $this->fail(
'Expected exception not thrown' );
743 }
catch ( \LogicException $ex ) {
745 'CreatedAccountAuthenticationRequests are only valid on the same AuthManager ' .
746 'that created the account',
750 $this->
unhook(
'UserLoggedIn' );
752 $this->
request->getSession()->clear();
753 $this->
request->getSession()->setSecret(
'AuthManager::authnState',
'test' );
754 $this->managerPriv->createdAccountAuthenticationRequests = [ $reqs[0] ];
755 $this->
hook(
'UserLoggedIn', $this->once() )
756 ->with( $this->callback(
function ( $u )
use (
$user ) {
757 return $user->getId() === $u->getId() &&
$user->getName() === $u->getName();
759 $this->
hook(
'AuthManagerLoginAuthenticateAudit', $this->once() );
760 $this->logger->setCollect(
true );
761 $ret = $this->manager->beginAuthentication( $reqs,
'http://localhost/' );
762 $this->logger->setCollect(
false );
763 $this->
unhook(
'UserLoggedIn' );
764 $this->
unhook(
'AuthManagerLoginAuthenticateAudit' );
766 $this->assertSame(
$user->getName(),
$ret->username );
767 $this->assertSame(
$user->getId(), $this->
request->getSessionData(
'AuthManager:lastAuthId' ) );
769 time(), $this->
request->getSessionData(
'AuthManager:lastAuthTimestamp' ),
772 $this->assertNull( $this->
request->getSession()->getSecret(
'AuthManager::authnState' ) );
773 $this->assertSame(
$user->getId(), $this->
request->getSession()->getUser()->getId() );
775 [ LogLevel::INFO,
'Logging in {user} after account creation' ],
776 ], $this->logger->getBuffer() );
785 $userReq->username =
'UTDummy';
787 $req1->returnToUrl =
'http://localhost/';
788 $req2->returnToUrl =
'http://localhost/';
789 $req3->returnToUrl =
'http://localhost/';
790 $req3->username =
'UTDummy';
791 $userReq->returnToUrl =
'http://localhost/';
795 $this->primaryauthMocks = [ $primary ];
798 $res->createRequest = $req1;
799 $primary->expects( $this->
any() )->method(
'beginPrimaryAuthentication' )
800 ->will( $this->returnValue(
$res ) );
802 null, [ $req2->getUniqueId() => $req2 ]
804 $this->logger->setCollect(
true );
805 $ret = $this->manager->beginAuthentication( [ $createReq ],
'http://localhost/' );
806 $this->logger->setCollect(
false );
809 $this->assertSame( $req1,
$ret->createRequest->createRequest );
810 $this->assertEquals( [ $req2->getUniqueId() => $req2 ],
$ret->createRequest->maybeLink );
814 ->setMethods( [
'continuePrimaryAuthentication' ] )
815 ->getMockForAbstractClass();
816 $this->primaryauthMocks = [ $primary ];
818 $primary->expects( $this->
any() )->method(
'beginPrimaryAuthentication' )
819 ->will( $this->returnValue(
823 $res->createRequest = $req2;
824 $primary->expects( $this->
any() )->method(
'continuePrimaryAuthentication' )
825 ->will( $this->returnValue(
$res ) );
826 $this->logger->setCollect(
true );
827 $ret = $this->manager->beginAuthentication( [],
'http://localhost/' );
829 $ret = $this->manager->continueAuthentication( [] );
830 $this->logger->setCollect(
false );
833 $this->assertSame( $req2,
$ret->createRequest->createRequest );
834 $this->assertEquals( [],
$ret->createRequest->maybeLink );
838 $this->primaryauthMocks = [ $primary ];
841 $createReq->returnToUrl =
'http://localhost/';
842 $createReq->username =
'UTDummy';
844 $primary->expects( $this->
any() )->method(
'beginPrimaryAccountCreation' )
845 ->with( $this->
anything(), $this->
anything(), [ $userReq, $createReq, $req3 ] )
846 ->will( $this->returnValue(
$res ) );
847 $primary->expects( $this->
any() )->method(
'accountCreationType' )
849 $this->logger->setCollect(
true );
850 $ret = $this->manager->beginAccountCreation(
851 $user, [ $userReq, $createReq ],
'http://localhost/'
853 $this->logger->setCollect(
false );
855 $state = $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' );
856 $this->assertNotNull( $state );
857 $this->assertEquals( [ $userReq, $createReq, $req3 ], $state[
'reqs'] );
858 $this->assertEquals( [ $req2 ], $state[
'maybeLink'] );
875 $id =
$user->getId();
880 $req->rememberMe = (bool)rand( 0, 1 );
881 $req->pre = $preResponse;
882 $req->primary = $primaryResponses;
883 $req->secondary = $secondaryResponses;
885 foreach ( [
'pre',
'primary',
'secondary' ]
as $key ) {
886 $class = ucfirst( $key ) .
'AuthenticationProvider';
887 $mocks[$key] = $this->getMockForAbstractClass(
888 "MediaWiki\\Auth\\$class", [],
"Mock$class"
890 $mocks[$key]->expects( $this->
any() )->method(
'getUniqueId' )
891 ->will( $this->returnValue( $key ) );
892 $mocks[$key .
'2'] = $this->getMockForAbstractClass(
"MediaWiki\\Auth\\$class" );
893 $mocks[$key .
'2']->expects( $this->
any() )->method(
'getUniqueId' )
894 ->will( $this->returnValue( $key .
'2' ) );
895 $mocks[$key .
'3'] = $this->getMockForAbstractClass(
"MediaWiki\\Auth\\$class" );
896 $mocks[$key .
'3']->expects( $this->
any() )->method(
'getUniqueId' )
897 ->will( $this->returnValue( $key .
'3' ) );
899 foreach ( $mocks
as $mock ) {
900 $mock->expects( $this->
any() )->method(
'getAuthenticationRequests' )
901 ->will( $this->returnValue( [] ) );
904 $mocks[
'pre']->expects( $this->once() )->method(
'testForAuthentication' )
905 ->will( $this->returnCallback(
function ( $reqs )
use (
$req ) {
906 $this->assertContains(
$req, $reqs );
911 $callback = $this->returnCallback(
function ( $reqs )
use (
$req ) {
912 $this->assertContains(
$req, $reqs );
913 return array_shift(
$req->primary );
915 $mocks[
'primary']->expects( $this->exactly( min( 1, $ct ) ) )
916 ->method(
'beginPrimaryAuthentication' )
918 $mocks[
'primary']->expects( $this->exactly( max( 0, $ct - 1 ) ) )
919 ->method(
'continuePrimaryAuthentication' )
922 $mocks[
'primary']->expects( $this->
any() )->method(
'accountCreationType' )
927 $callback = $this->returnCallback(
function (
$user, $reqs )
use ( $id,
$name,
$req ) {
928 $this->assertSame( $id,
$user->getId() );
930 $this->assertContains(
$req, $reqs );
931 return array_shift(
$req->secondary );
933 $mocks[
'secondary']->expects( $this->exactly( min( 1, $ct ) ) )
934 ->method(
'beginSecondaryAuthentication' )
936 $mocks[
'secondary']->expects( $this->exactly( max( 0, $ct - 1 ) ) )
937 ->method(
'continueSecondaryAuthentication' )
941 $mocks[
'pre2']->expects( $this->atMost( 1 ) )->method(
'testForAuthentication' )
943 $mocks[
'primary2']->expects( $this->atMost( 1 ) )->method(
'beginPrimaryAuthentication' )
944 ->will( $this->returnValue( $abstain ) );
945 $mocks[
'primary2']->expects( $this->never() )->method(
'continuePrimaryAuthentication' );
946 $mocks[
'secondary2']->expects( $this->atMost( 1 ) )->method(
'beginSecondaryAuthentication' )
947 ->will( $this->returnValue( $abstain ) );
948 $mocks[
'secondary2']->expects( $this->never() )->method(
'continueSecondaryAuthentication' );
949 $mocks[
'secondary3']->expects( $this->atMost( 1 ) )->method(
'beginSecondaryAuthentication' )
950 ->will( $this->returnValue( $abstain ) );
951 $mocks[
'secondary3']->expects( $this->never() )->method(
'continueSecondaryAuthentication' );
953 $this->preauthMocks = [ $mocks[
'pre'], $mocks[
'pre2'] ];
954 $this->primaryauthMocks = [ $mocks[
'primary'], $mocks[
'primary2'] ];
955 $this->secondaryauthMocks = [
956 $mocks[
'secondary3'], $mocks[
'secondary'], $mocks[
'secondary2'],
961 $this->logger->setCollect(
true );
963 $constraint = \PHPUnit_Framework_Assert::logicalOr(
967 $providers = array_filter(
969 $this->preauthMocks, $this->primaryauthMocks, $this->secondaryauthMocks
972 return is_callable( [ $p,
'expects' ] );
975 foreach ( $providers
as $p ) {
976 $p->postCalled =
false;
977 $p->expects( $this->atMost( 1 ) )->method(
'postAuthentication' )
979 if (
$user !==
null ) {
981 $this->assertSame(
'UTSysop',
$user->getName() );
984 $this->assertThat(
$response->status, $constraint );
989 $session = $this->
request->getSession();
990 $session->setRememberUser( !
$req->rememberMe );
996 $this->
hook(
'UserLoggedIn', $this->once() )
997 ->with( $this->callback(
function (
$user )
use ( $id,
$name ) {
1001 $this->
hook(
'UserLoggedIn', $this->never() );
1006 $response->message->getKey() !==
'authmanager-authn-not-in-progress' &&
1007 $response->message->getKey() !==
'authmanager-authn-no-primary'
1010 $this->
hook(
'AuthManagerLoginAuthenticateAudit', $this->once() );
1012 $this->
hook(
'AuthManagerLoginAuthenticateAudit', $this->never() );
1018 $ret = $this->manager->beginAuthentication( [
$req ],
'http://localhost/' );
1020 $ret = $this->manager->continueAuthentication( [
$req ] );
1022 if (
$response instanceof \Exception ) {
1023 $this->fail(
'Expected exception not thrown',
"Response $i" );
1025 }
catch ( \Exception $ex ) {
1026 if ( !
$response instanceof \Exception ) {
1029 $this->assertEquals(
$response->getMessage(), $ex->getMessage(),
"Response $i, exception" );
1030 $this->assertNull( $session->getSecret(
'AuthManager::authnState' ),
1031 "Response $i, exception, session state" );
1032 $this->
unhook(
'UserLoggedIn' );
1033 $this->
unhook(
'AuthManagerLoginAuthenticateAudit' );
1037 $this->
unhook(
'UserLoggedIn' );
1038 $this->
unhook(
'AuthManagerLoginAuthenticateAudit' );
1040 $this->assertSame(
'http://localhost/',
$req->returnToUrl );
1045 $this->assertSame( $id, $session->getUser()->getId(),
1046 "Response $i, authn" );
1048 $this->assertSame( 0, $session->getUser()->getId(),
1049 "Response $i, authn" );
1052 $this->assertNull( $session->getSecret(
'AuthManager::authnState' ),
1053 "Response $i, session state" );
1054 foreach ( $providers
as $p ) {
1055 $this->assertSame(
$response->status, $p->postCalled,
1056 "Response $i, post-auth callback called" );
1059 $this->assertNotNull( $session->getSecret(
'AuthManager::authnState' ),
1060 "Response $i, session state" );
1061 foreach (
$ret->neededRequests
as $neededReq ) {
1063 "Response $i, neededRequest action" );
1065 $this->assertEquals(
1066 $ret->neededRequests,
1068 "Response $i, continuation check"
1070 foreach ( $providers
as $p ) {
1071 $this->assertFalse( $p->postCalled,
"Response $i, post-auth callback not called" );
1075 $state = $session->getSecret(
'AuthManager::authnState' );
1076 $maybeLink = $state[
'maybeLink'] ?? [];
1078 $this->assertEquals(
1081 "Response $i, maybeLink"
1084 $this->assertEquals( [], $maybeLink,
"Response $i, maybeLink" );
1089 $this->assertSame(
$req->rememberMe, $session->shouldRememberUser(),
1090 'rememberMe checkbox had effect' );
1092 $this->assertNotSame(
$req->rememberMe, $session->shouldRememberUser(),
1093 'rememberMe checkbox wasn\'t applied' );
1102 $req->foobar =
'baz';
1104 $this->
message(
'authmanager-authn-no-local-user' )
1106 $restartResponse->neededRequests = [ $rememberReq ];
1109 $restartResponse2Pass->linkRequest =
$req;
1111 $this->
message(
'authmanager-authn-no-local-user-link' )
1114 null, [
$req->getUniqueId() =>
$req ]
1117 $restartResponse2->neededRequests = [ $rememberReq, $restartResponse2->createRequest ];
1119 $userName =
'UTSysop';
1122 'Failure in pre-auth' => [
1129 $this->
message(
'authmanager-authn-not-in-progress' )
1133 'Failure in primary' => [
1141 'All primary abstain' => [
1151 'Primary UI, then redirect, then fail' => [
1161 'Primary redirect, then abstain' => [
1165 [
$req ],
'/foo.html', [
'foo' =>
'bar' ]
1172 new \DomainException(
1173 'MockPrimaryAuthenticationProvider::continuePrimaryAuthentication() returned ABSTAIN'
1177 'Primary UI, then pass with no local user' => [
1189 'Primary UI, then pass with no local user (link type)' => [
1193 $restartResponse2Pass,
1202 'Primary pass with invalid username' => [
1209 new \DomainException(
'MockPrimaryAuthenticationProvider returned an invalid username: <>' ),
1212 'Secondary fail' => [
1222 'Secondary UI, then abstain' => [
1236 'Secondary pass' => [
1259 $mock1->expects( $this->
any() )->method(
'getUniqueId' )
1260 ->will( $this->returnValue(
'primary1' ) );
1261 $mock1->expects( $this->
any() )->method(
'testUserExists' )
1262 ->with( $this->equalTo(
'UTSysop' ) )
1263 ->will( $this->returnValue( $primary1Exists ) );
1265 $mock2->expects( $this->
any() )->method(
'getUniqueId' )
1266 ->will( $this->returnValue(
'primary2' ) );
1267 $mock2->expects( $this->
any() )->method(
'testUserExists' )
1268 ->with( $this->equalTo(
'UTSysop' ) )
1269 ->will( $this->returnValue( $primary2Exists ) );
1270 $this->primaryauthMocks = [ $mock1, $mock2 ];
1273 $this->assertSame( $expect, $this->manager->userExists(
'UTSysop' ) );
1278 [
false,
false,
false ],
1279 [
true,
false,
true ],
1280 [
false,
true,
true ],
1281 [
true,
true,
true ],
1295 $mock1->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'1' ) );
1296 $mock1->expects( $this->
any() )->method(
'providerAllowsAuthenticationDataChange' )
1297 ->with( $this->equalTo(
$req ) )
1298 ->will( $this->returnValue( $primaryReturn ) );
1300 $mock2->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'2' ) );
1301 $mock2->expects( $this->
any() )->method(
'providerAllowsAuthenticationDataChange' )
1302 ->with( $this->equalTo(
$req ) )
1303 ->will( $this->returnValue( $secondaryReturn ) );
1305 $this->primaryauthMocks = [ $mock1 ];
1306 $this->secondaryauthMocks = [ $mock2 ];
1308 $this->assertEquals( $expect, $this->manager->allowsAuthenticationDataChange(
$req ) );
1313 $ignored->warning(
'authmanager-change-not-supported' );
1316 $okFromPrimary->warning(
'warning-from-primary' );
1318 $okFromSecondary->warning(
'warning-from-secondary' );
1366 $req->username =
'UTSysop';
1369 $mock1->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'1' ) );
1370 $mock1->expects( $this->once() )->method(
'providerChangeAuthenticationData' )
1371 ->with( $this->equalTo(
$req ) );
1373 $mock2->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'2' ) );
1374 $mock2->expects( $this->once() )->method(
'providerChangeAuthenticationData' )
1375 ->with( $this->equalTo(
$req ) );
1377 $this->primaryauthMocks = [ $mock1, $mock2 ];
1379 $this->logger->setCollect(
true );
1380 $this->manager->changeAuthenticationData(
$req );
1381 $this->assertSame( [
1382 [ LogLevel::INFO,
'Changing authentication data for {user} class {what}' ],
1383 ], $this->logger->getBuffer() );
1393 foreach ( $types
as $type => $can ) {
1395 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
$type ) );
1396 $mock->expects( $this->
any() )->method(
'accountCreationType' )
1397 ->will( $this->returnValue(
$type ) );
1398 $this->primaryauthMocks = [ $mock ];
1400 $this->assertSame( $can, $this->manager->canCreateAccounts(),
$type );
1408 $this->assertEquals(
1410 $this->manager->checkAccountCreatePermissions(
new \
User )
1413 $readOnlyMode = \MediaWiki\MediaWikiServices::getInstance()->getReadOnlyMode();
1414 $readOnlyMode->setReason(
'Because' );
1415 $this->assertEquals(
1417 $this->manager->checkAccountCreatePermissions(
new \
User )
1419 $readOnlyMode->setReason(
false );
1422 $status = $this->manager->checkAccountCreatePermissions(
new \
User );
1423 $this->assertFalse(
$status->isOK() );
1424 $this->assertTrue(
$status->hasMessage(
'badaccess-groups' ) );
1428 if (
$user->getID() == 0 ) {
1429 $user->addToDatabase();
1431 $user->saveSettings();
1436 $oldBlock->delete();
1439 'address' =>
'UTBlockee',
1440 'user' =>
$user->getID(),
1442 'reason' => __METHOD__,
1443 'expiry' => time() + 100500,
1444 'createAccount' =>
true,
1446 $block = new \Block( $blockOptions );
1448 $status = $this->manager->checkAccountCreatePermissions(
$user );
1449 $this->assertFalse(
$status->isOK() );
1450 $this->assertTrue(
$status->hasMessage(
'cantcreateaccount-text' ) );
1453 'address' =>
'127.0.0.0/24',
1455 'reason' => __METHOD__,
1456 'expiry' => time() + 100500,
1457 'createAccount' =>
true,
1459 $block = new \Block( $blockOptions );
1461 $scopeVariable =
new ScopedCallback( [ $block,
'delete' ] );
1462 $status = $this->manager->checkAccountCreatePermissions(
new \
User );
1463 $this->assertFalse(
$status->isOK() );
1464 $this->assertTrue(
$status->hasMessage(
'cantcreateaccount-range-text' ) );
1465 ScopedCallback::consume( $scopeVariable );
1468 'wgEnableDnsBlacklist' =>
true,
1469 'wgDnsBlacklistUrls' => [
1470 'local.wmftest.net',
1472 'wgProxyWhitelist' => [],
1474 $status = $this->manager->checkAccountCreatePermissions(
new \
User );
1475 $this->assertFalse(
$status->isOK() );
1476 $this->assertTrue(
$status->hasMessage(
'sorbs_create_account_reason' ) );
1477 $this->
setMwGlobals(
'wgProxyWhitelist', [
'127.0.0.1' ] );
1478 $status = $this->manager->checkAccountCreatePermissions(
new \
User );
1479 $this->assertTrue(
$status->isGood() );
1489 $username =
"UTAuthManagerTestAccountCreation" . $uniq . ++$i;
1498 $this->assertEquals(
1500 $this->manager->canCreateAccount(
$username )
1504 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
1505 $mock->expects( $this->
any() )->method(
'accountCreationType' )
1507 $mock->expects( $this->
any() )->method(
'testUserExists' )->will( $this->returnValue(
true ) );
1508 $mock->expects( $this->
any() )->method(
'testUserForCreation' )
1510 $this->primaryauthMocks = [ $mock ];
1513 $this->assertEquals(
1515 $this->manager->canCreateAccount(
$username )
1519 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
1520 $mock->expects( $this->
any() )->method(
'accountCreationType' )
1522 $mock->expects( $this->
any() )->method(
'testUserExists' )->will( $this->returnValue(
false ) );
1523 $mock->expects( $this->
any() )->method(
'testUserForCreation' )
1525 $this->primaryauthMocks = [ $mock ];
1528 $this->assertEquals(
1530 $this->manager->canCreateAccount(
$username .
'<>' )
1533 $this->assertEquals(
1535 $this->manager->canCreateAccount(
'UTSysop' )
1538 $this->assertEquals(
1540 $this->manager->canCreateAccount(
$username )
1544 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
1545 $mock->expects( $this->
any() )->method(
'accountCreationType' )
1547 $mock->expects( $this->
any() )->method(
'testUserExists' )->will( $this->returnValue(
false ) );
1548 $mock->expects( $this->
any() )->method(
'testUserForCreation' )
1550 $this->primaryauthMocks = [ $mock ];
1553 $this->assertEquals(
1555 $this->manager->canCreateAccount(
$username )
1562 $this->logger = new \TestLogger(
false,
function ( $message, $level ) {
1563 return $level === LogLevel::DEBUG ?
null : $message;
1567 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState',
'test' );
1568 $this->
hook(
'LocalUserCreated', $this->never() );
1570 $this->manager->beginAccountCreation(
1571 $creator, [],
'http://localhost/'
1573 $this->fail(
'Expected exception not thrown' );
1574 }
catch ( \LogicException $ex ) {
1575 $this->assertEquals(
'Account creation is not possible', $ex->getMessage() );
1577 $this->
unhook(
'LocalUserCreated' );
1579 $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' )
1583 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
1584 $mock->expects( $this->
any() )->method(
'accountCreationType' )
1586 $mock->expects( $this->
any() )->method(
'testUserExists' )->will( $this->returnValue(
true ) );
1587 $mock->expects( $this->
any() )->method(
'testUserForCreation' )
1589 $this->primaryauthMocks = [ $mock ];
1592 $this->
hook(
'LocalUserCreated', $this->never() );
1593 $ret = $this->manager->beginAccountCreation( $creator, [],
'http://localhost/' );
1594 $this->
unhook(
'LocalUserCreated' );
1596 $this->assertSame(
'noname',
$ret->message->getKey() );
1598 $this->
hook(
'LocalUserCreated', $this->never() );
1601 $userReq2->username = $userReq->username .
'X';
1602 $ret = $this->manager->beginAccountCreation(
1603 $creator, [ $userReq, $userReq2 ],
'http://localhost/'
1605 $this->
unhook(
'LocalUserCreated' );
1607 $this->assertSame(
'noname',
$ret->message->getKey() );
1609 $readOnlyMode = \MediaWiki\MediaWikiServices::getInstance()->getReadOnlyMode();
1610 $readOnlyMode->setReason(
'Because' );
1611 $this->
hook(
'LocalUserCreated', $this->never() );
1613 $ret = $this->manager->beginAccountCreation( $creator, [ $userReq ],
'http://localhost/' );
1614 $this->
unhook(
'LocalUserCreated' );
1616 $this->assertSame(
'readonlytext',
$ret->message->getKey() );
1617 $this->assertSame( [
'Because' ],
$ret->message->getParams() );
1618 $readOnlyMode->setReason(
false );
1620 $this->
hook(
'LocalUserCreated', $this->never() );
1622 $ret = $this->manager->beginAccountCreation( $creator, [ $userReq ],
'http://localhost/' );
1623 $this->
unhook(
'LocalUserCreated' );
1625 $this->assertSame(
'userexists',
$ret->message->getKey() );
1628 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
1629 $mock->expects( $this->
any() )->method(
'accountCreationType' )
1631 $mock->expects( $this->
any() )->method(
'testUserExists' )->will( $this->returnValue(
false ) );
1632 $mock->expects( $this->
any() )->method(
'testUserForCreation' )
1634 $this->primaryauthMocks = [ $mock ];
1637 $this->
hook(
'LocalUserCreated', $this->never() );
1639 $ret = $this->manager->beginAccountCreation( $creator, [ $userReq ],
'http://localhost/' );
1640 $this->
unhook(
'LocalUserCreated' );
1642 $this->assertSame(
'fail',
$ret->message->getKey() );
1645 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
1646 $mock->expects( $this->
any() )->method(
'accountCreationType' )
1648 $mock->expects( $this->
any() )->method(
'testUserExists' )->will( $this->returnValue(
false ) );
1649 $mock->expects( $this->
any() )->method(
'testUserForCreation' )
1651 $this->primaryauthMocks = [ $mock ];
1654 $this->
hook(
'LocalUserCreated', $this->never() );
1656 $ret = $this->manager->beginAccountCreation( $creator, [ $userReq ],
'http://localhost/' );
1657 $this->
unhook(
'LocalUserCreated' );
1659 $this->assertSame(
'noname',
$ret->message->getKey() );
1661 $this->
hook(
'LocalUserCreated', $this->never() );
1662 $userReq->username = $creator->getName();
1663 $ret = $this->manager->beginAccountCreation( $creator, [ $userReq ],
'http://localhost/' );
1664 $this->
unhook(
'LocalUserCreated' );
1666 $this->assertSame(
'userexists',
$ret->message->getKey() );
1669 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
1670 $mock->expects( $this->
any() )->method(
'accountCreationType' )
1672 $mock->expects( $this->
any() )->method(
'testUserExists' )->will( $this->returnValue(
false ) );
1673 $mock->expects( $this->
any() )->method(
'testUserForCreation' )
1675 $mock->expects( $this->
any() )->method(
'testForAccountCreation' )
1677 $this->primaryauthMocks = [ $mock ];
1681 ->setMethods( [
'populateUser' ] )
1683 $req->expects( $this->
any() )->method(
'populateUser' )
1686 $ret = $this->manager->beginAccountCreation(
1687 $creator, [ $userReq,
$req ],
'http://localhost/'
1690 $this->assertSame(
'populatefail',
$ret->message->getKey() );
1695 $ret = $this->manager->beginAccountCreation(
1696 $creator, [ $userReq,
$req ],
'http://localhost/'
1699 $this->assertSame(
'fail',
$ret->message->getKey() );
1701 $this->manager->beginAccountCreation(
1705 $this->assertSame(
'fail',
$ret->message->getKey() );
1711 $this->logger = new \TestLogger(
false,
function ( $message, $level ) {
1712 return $level === LogLevel::DEBUG ?
null : $message;
1723 'primaryResponse' =>
null,
1725 'ranPreTests' =>
true,
1728 $this->
hook(
'LocalUserCreated', $this->never() );
1730 $this->manager->continueAccountCreation( [] );
1731 $this->fail(
'Expected exception not thrown' );
1732 }
catch ( \LogicException $ex ) {
1733 $this->assertEquals(
'Account creation is not possible', $ex->getMessage() );
1735 $this->
unhook(
'LocalUserCreated' );
1738 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
1739 $mock->expects( $this->
any() )->method(
'accountCreationType' )
1741 $mock->expects( $this->
any() )->method(
'testUserExists' )->will( $this->returnValue(
false ) );
1742 $mock->expects( $this->
any() )->method(
'beginPrimaryAccountCreation' )->will(
1745 $this->primaryauthMocks = [ $mock ];
1748 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState',
null );
1749 $this->
hook(
'LocalUserCreated', $this->never() );
1750 $ret = $this->manager->continueAccountCreation( [] );
1751 $this->
unhook(
'LocalUserCreated' );
1753 $this->assertSame(
'authmanager-create-not-in-progress',
$ret->message->getKey() );
1755 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState',
1756 [
'username' =>
"$username<>" ] + $session );
1757 $this->
hook(
'LocalUserCreated', $this->never() );
1758 $ret = $this->manager->continueAccountCreation( [] );
1759 $this->
unhook(
'LocalUserCreated' );
1761 $this->assertSame(
'noname',
$ret->message->getKey() );
1763 $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' )
1766 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState', $session );
1767 $this->
hook(
'LocalUserCreated', $this->never() );
1770 $ret = $this->manager->continueAccountCreation( [] );
1772 $this->
unhook(
'LocalUserCreated' );
1774 $this->assertSame(
'usernameinprogress',
$ret->message->getKey() );
1778 $session, $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' )
1781 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState',
1782 [
'username' => $creator->getName() ] + $session );
1783 $readOnlyMode = \MediaWiki\MediaWikiServices::getInstance()->getReadOnlyMode();
1784 $readOnlyMode->setReason(
'Because' );
1785 $this->
hook(
'LocalUserCreated', $this->never() );
1786 $ret = $this->manager->continueAccountCreation( [] );
1787 $this->
unhook(
'LocalUserCreated' );
1789 $this->assertSame(
'readonlytext',
$ret->message->getKey() );
1790 $this->assertSame( [
'Because' ],
$ret->message->getParams() );
1791 $readOnlyMode->setReason(
false );
1793 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState',
1794 [
'username' => $creator->getName() ] + $session );
1795 $this->
hook(
'LocalUserCreated', $this->never() );
1796 $ret = $this->manager->continueAccountCreation( [] );
1797 $this->
unhook(
'LocalUserCreated' );
1799 $this->assertSame(
'userexists',
$ret->message->getKey() );
1801 $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' )
1804 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState',
1805 [
'userid' => $creator->getId() ] + $session );
1806 $this->
hook(
'LocalUserCreated', $this->never() );
1808 $ret = $this->manager->continueAccountCreation( [] );
1809 $this->fail(
'Expected exception not thrown' );
1810 }
catch ( \UnexpectedValueException $ex ) {
1811 $this->assertEquals(
"User \"{$username}\" should exist now, but doesn't!", $ex->getMessage() );
1813 $this->
unhook(
'LocalUserCreated' );
1815 $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' )
1818 $id = $creator->getId();
1819 $name = $creator->getName();
1820 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState',
1821 [
'username' =>
$name,
'userid' => $id + 1 ] + $session );
1822 $this->
hook(
'LocalUserCreated', $this->never() );
1824 $ret = $this->manager->continueAccountCreation( [] );
1825 $this->fail(
'Expected exception not thrown' );
1826 }
catch ( \UnexpectedValueException $ex ) {
1827 $this->assertEquals(
1828 "User \"{$name}\" exists, but ID $id !== " . ( $id + 1 ) .
'!', $ex->getMessage()
1831 $this->
unhook(
'LocalUserCreated' );
1833 $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' )
1837 ->setMethods( [
'populateUser' ] )
1839 $req->expects( $this->
any() )->method(
'populateUser' )
1841 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState',
1842 [
'reqs' => [ $req ] ] + $session );
1843 $ret = $this->manager->continueAccountCreation( [] );
1845 $this->assertSame(
'populatefail',
$ret->message->getKey() );
1847 $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' )
1861 StatusValue $preTest, $primaryTest, $secondaryTest,
1862 array $primaryResponses,
array $secondaryResponses,
array $managerResponses
1871 $req->preTest = $preTest;
1872 $req->primaryTest = $primaryTest;
1873 $req->secondaryTest = $secondaryTest;
1874 $req->primary = $primaryResponses;
1875 $req->secondary = $secondaryResponses;
1877 foreach ( [
'pre',
'primary',
'secondary' ]
as $key ) {
1878 $class = ucfirst( $key ) .
'AuthenticationProvider';
1879 $mocks[$key] = $this->getMockForAbstractClass(
1880 "MediaWiki\\Auth\\$class", [],
"Mock$class"
1882 $mocks[$key]->expects( $this->
any() )->method(
'getUniqueId' )
1883 ->will( $this->returnValue( $key ) );
1884 $mocks[$key]->expects( $this->
any() )->method(
'testUserForCreation' )
1886 $mocks[$key]->expects( $this->
any() )->method(
'testForAccountCreation' )
1887 ->will( $this->returnCallback(
1888 function (
$user, $creatorIn, $reqs )
1892 $this->assertSame( $creator->getId(), $creatorIn->getId() );
1893 $this->assertSame( $creator->getName(), $creatorIn->getName() );
1895 foreach ( $reqs
as $r ) {
1896 $this->assertSame(
$username, $r->username );
1897 $foundReq = $foundReq || get_class( $r ) === get_class(
$req );
1899 $this->assertTrue( $foundReq,
'$reqs contains $req' );
1905 for ( $i = 2; $i <= 3; $i++ ) {
1906 $mocks[$key . $i] = $this->getMockForAbstractClass(
"MediaWiki\\Auth\\$class" );
1907 $mocks[$key . $i]->expects( $this->
any() )->method(
'getUniqueId' )
1908 ->will( $this->returnValue( $key . $i ) );
1909 $mocks[$key . $i]->expects( $this->
any() )->method(
'testUserForCreation' )
1911 $mocks[$key . $i]->expects( $this->atMost( 1 ) )->method(
'testForAccountCreation' )
1916 $mocks[
'primary']->expects( $this->
any() )->method(
'accountCreationType' )
1918 $mocks[
'primary']->expects( $this->
any() )->method(
'testUserExists' )
1919 ->will( $this->returnValue(
false ) );
1923 $this->assertSame(
'UTSysop', $creator->getName() );
1925 foreach ( $reqs
as $r ) {
1926 $this->assertSame(
$username, $r->username );
1927 $foundReq = $foundReq || get_class( $r ) === get_class(
$req );
1929 $this->assertTrue( $foundReq,
'$reqs contains $req' );
1930 return array_shift(
$req->primary );
1932 $mocks[
'primary']->expects( $this->exactly( min( 1, $ct ) ) )
1933 ->method(
'beginPrimaryAccountCreation' )
1934 ->will( $callback );
1935 $mocks[
'primary']->expects( $this->exactly( max( 0, $ct - 1 ) ) )
1936 ->method(
'continuePrimaryAccountCreation' )
1937 ->will( $callback );
1942 $this->assertSame(
'UTSysop', $creator->getName() );
1944 foreach ( $reqs
as $r ) {
1945 $this->assertSame(
$username, $r->username );
1946 $foundReq = $foundReq || get_class( $r ) === get_class(
$req );
1948 $this->assertTrue( $foundReq,
'$reqs contains $req' );
1949 return array_shift(
$req->secondary );
1951 $mocks[
'secondary']->expects( $this->exactly( min( 1, $ct ) ) )
1952 ->method(
'beginSecondaryAccountCreation' )
1953 ->will( $callback );
1954 $mocks[
'secondary']->expects( $this->exactly( max( 0, $ct - 1 ) ) )
1955 ->method(
'continueSecondaryAccountCreation' )
1956 ->will( $callback );
1959 $mocks[
'primary2']->expects( $this->
any() )->method(
'accountCreationType' )
1961 $mocks[
'primary2']->expects( $this->
any() )->method(
'testUserExists' )
1962 ->will( $this->returnValue(
false ) );
1963 $mocks[
'primary2']->expects( $this->atMost( 1 ) )->method(
'beginPrimaryAccountCreation' )
1964 ->will( $this->returnValue( $abstain ) );
1965 $mocks[
'primary2']->expects( $this->never() )->method(
'continuePrimaryAccountCreation' );
1966 $mocks[
'primary3']->expects( $this->
any() )->method(
'accountCreationType' )
1968 $mocks[
'primary3']->expects( $this->
any() )->method(
'testUserExists' )
1969 ->will( $this->returnValue(
false ) );
1970 $mocks[
'primary3']->expects( $this->never() )->method(
'beginPrimaryAccountCreation' );
1971 $mocks[
'primary3']->expects( $this->never() )->method(
'continuePrimaryAccountCreation' );
1972 $mocks[
'secondary2']->expects( $this->atMost( 1 ) )
1973 ->method(
'beginSecondaryAccountCreation' )
1974 ->will( $this->returnValue( $abstain ) );
1975 $mocks[
'secondary2']->expects( $this->never() )->method(
'continueSecondaryAccountCreation' );
1976 $mocks[
'secondary3']->expects( $this->atMost( 1 ) )
1977 ->method(
'beginSecondaryAccountCreation' )
1978 ->will( $this->returnValue( $abstain ) );
1979 $mocks[
'secondary3']->expects( $this->never() )->method(
'continueSecondaryAccountCreation' );
1981 $this->preauthMocks = [ $mocks[
'pre'], $mocks[
'pre2'] ];
1982 $this->primaryauthMocks = [ $mocks[
'primary3'], $mocks[
'primary'], $mocks[
'primary2'] ];
1983 $this->secondaryauthMocks = [
1984 $mocks[
'secondary3'], $mocks[
'secondary'], $mocks[
'secondary2']
1987 $this->logger = new \TestLogger(
true,
function ( $message, $level ) {
1988 return $level === LogLevel::DEBUG ?
null : $message;
1993 $constraint = \PHPUnit_Framework_Assert::logicalOr(
1997 $providers = array_merge(
1998 $this->preauthMocks, $this->primaryauthMocks, $this->secondaryauthMocks
2000 foreach ( $providers
as $p ) {
2001 $p->postCalled =
false;
2002 $p->expects( $this->atMost( 1 ) )->method(
'postAccountCreation' )
2008 $this->assertSame(
'UTSysop', $creator->getName() );
2010 $this->assertThat(
$response->status, $constraint );
2017 $maxLogId = $dbw->selectField(
'logging',
'MAX(log_id)', [
'log_type' =>
'newusers' ] );
2021 foreach ( $managerResponses
as $i =>
$response ) {
2024 if ( $i ===
'created' ) {
2026 $this->
hook(
'LocalUserCreated', $this->once() )
2031 $this->equalTo(
false )
2033 $expectLog[] = [ LogLevel::INFO,
"Creating user {user} during account creation" ];
2035 $this->
hook(
'LocalUserCreated', $this->never() );
2043 $ret = $this->manager->beginAccountCreation(
2044 $creator, [ $userReq,
$req ],
'http://localhost/'
2047 $ret = $this->manager->continueAccountCreation( [
$req ] );
2049 if (
$response instanceof \Exception ) {
2050 $this->fail(
'Expected exception not thrown',
"Response $i" );
2052 }
catch ( \Exception $ex ) {
2053 if ( !
$response instanceof \Exception ) {
2056 $this->assertEquals(
$response->getMessage(), $ex->getMessage(),
"Response $i, exception" );
2058 $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' ),
2059 "Response $i, exception, session state"
2061 $this->
unhook(
'LocalUserCreated' );
2065 $this->
unhook(
'LocalUserCreated' );
2067 $this->assertSame(
'http://localhost/',
$req->returnToUrl );
2070 $this->assertNotNull(
$ret->loginRequest,
"Response $i, login marker" );
2071 $this->assertContains(
2072 $ret->loginRequest, $this->managerPriv->createdAccountAuthenticationRequests,
2073 "Response $i, login marker"
2078 "MediaWiki\Auth\AuthManager::continueAccountCreation: Account creation succeeded for {user}"
2086 $this->assertNull(
$ret->loginRequest,
"Response $i, login marker" );
2087 $this->assertSame( [], $this->managerPriv->createdAccountAuthenticationRequests,
2088 "Response $i, login marker" );
2094 $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' ),
2095 "Response $i, session state"
2097 foreach ( $providers
as $p ) {
2098 $this->assertSame(
$response->status, $p->postCalled,
2099 "Response $i, post-auth callback called" );
2102 $this->assertNotNull(
2103 $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' ),
2104 "Response $i, session state"
2106 foreach (
$ret->neededRequests
as $neededReq ) {
2108 "Response $i, neededRequest action" );
2110 $this->assertEquals(
2111 $ret->neededRequests,
2113 "Response $i, continuation check"
2115 foreach ( $providers
as $p ) {
2116 $this->assertFalse( $p->postCalled,
"Response $i, post-auth callback not called" );
2129 $this->assertSame( $expectLog, $this->logger->getBuffer() );
2133 $dbw->selectField(
'logging',
'MAX(log_id)', [
'log_type' =>
'newusers' ] )
2142 'Pre-creation test fail in pre' => [
2150 'Pre-creation test fail in primary' => [
2158 'Pre-creation test fail in secondary' => [
2166 'Failure in primary' => [
2167 $good, $good, $good,
2174 'All primary abstain' => [
2175 $good, $good, $good,
2184 'Primary UI, then redirect, then fail' => [
2185 $good, $good, $good,
2194 'Primary redirect, then abstain' => [
2195 $good, $good, $good,
2198 [
$req ],
'/foo.html', [
'foo' =>
'bar' ]
2205 new \DomainException(
2206 'MockPrimaryAuthenticationProvider::continuePrimaryAccountCreation() returned ABSTAIN'
2210 'Primary UI, then pass; secondary abstain' => [
2211 $good, $good, $good,
2224 'Primary pass; secondary UI then pass' => [
2225 $good, $good, $good,
2238 'Primary pass; secondary fail' => [
2239 $good, $good, $good,
2247 'created' => new \DomainException(
2248 'MockSecondaryAuthenticationProvider::beginSecondaryAccountCreation() returned FAIL. ' .
2249 'Secondary providers are not allowed to fail account creation, ' .
2250 'that should have been done via testForAccountCreation().'
2269 $mock = $this->getMockForAbstractClass(
2272 $mock->expects( $this->
any() )->method(
'getUniqueId' )
2273 ->will( $this->returnValue(
'primary' ) );
2274 $mock->expects( $this->
any() )->method(
'testUserForCreation' )
2276 $mock->expects( $this->
any() )->method(
'testForAccountCreation' )
2278 $mock->expects( $this->
any() )->method(
'accountCreationType' )
2280 $mock->expects( $this->
any() )->method(
'testUserExists' )
2281 ->will( $this->returnValue(
false ) );
2282 $mock->expects( $this->
any() )->method(
'beginPrimaryAccountCreation' )
2284 $mock->expects( $this->
any() )->method(
'finishAccountCreation' )
2285 ->will( $this->returnValue( $logSubtype ) );
2287 $this->primaryauthMocks = [ $mock ];
2289 $this->logger->setCollect(
true );
2291 $this->config->set(
'NewUserLog',
true );
2294 $maxLogId = $dbw->selectField(
'logging',
'MAX(log_id)', [
'log_type' =>
'newusers' ] );
2299 $reasonReq->reason = $this->toString();
2300 $ret = $this->manager->beginAccountCreation(
2301 $creator, [ $userReq, $reasonReq ],
'http://localhost/'
2307 $this->assertNotEquals( 0,
$user->getId(),
'sanity check' );
2308 $this->assertNotEquals( $creator->getId(),
$user->getId(),
'sanity check' );
2311 $rows = iterator_to_array( $dbw->select(
2315 'log_id > ' . (
int)$maxLogId,
2316 'log_type' =>
'newusers'
2322 $this->assertCount( 1,
$rows );
2325 $this->assertSame( $logSubtype ?: ( $isAnon ?
'create' :
'create2' ), $entry->getSubtype() );
2327 $isAnon ?
$user->getId() : $creator->getId(),
2328 $entry->getPerformer()->getId()
2331 $isAnon ?
$user->getName() : $creator->getName(),
2332 $entry->getPerformer()->getName()
2334 $this->assertSame(
$user->getUserPage()->getFullText(), $entry->getTarget()->getFullText() );
2335 $this->assertSame( [
'4::userid' =>
$user->getId() ], $entry->getParameters() );
2336 $this->assertSame( $this->toString(), $entry->getComment() );
2344 [
false,
'byemail' ],
2353 $workaroundPHPUnitBug =
false;
2363 [ __METHOD__ => [
'class' =>
'HashBagOStuff' ] ] );
2364 $this->
setMwGlobals( [
'wgMainCacheType' => __METHOD__ ] );
2368 foreach ( [
'pre',
'primary',
'secondary' ]
as $key ) {
2369 $class = ucfirst( $key ) .
'AuthenticationProvider';
2370 $mocks[$key] = $this->getMockForAbstractClass(
"MediaWiki\\Auth\\$class" );
2371 $mocks[$key]->expects( $this->
any() )->method(
'getUniqueId' )
2372 ->will( $this->returnValue( $key ) );
2377 $callback = $this->callback(
function (
$user )
use ( &
$username, &$workaroundPHPUnitBug ) {
2380 $callback2 = $this->callback(
2381 function (
$source )
use ( &$expectedSource, &$workaroundPHPUnitBug ) {
2382 return $workaroundPHPUnitBug ||
$source === $expectedSource;
2386 $mocks[
'pre']->expects( $this->exactly( 13 ) )->method(
'testUserForCreation' )
2387 ->with( $callback, $callback2 )
2388 ->will( $this->onConsecutiveCalls(
2398 $mocks[
'primary']->expects( $this->
any() )->method(
'accountCreationType' )
2400 $mocks[
'primary']->expects( $this->
any() )->method(
'testUserExists' )
2401 ->will( $this->returnValue(
true ) );
2402 $mocks[
'primary']->expects( $this->exactly( 9 ) )->method(
'testUserForCreation' )
2403 ->with( $callback, $callback2 )
2404 ->will( $this->onConsecutiveCalls(
2412 $mocks[
'primary']->expects( $this->exactly( 3 ) )->method(
'autoCreatedAccount' )
2413 ->with( $callback, $callback2 );
2415 $mocks[
'secondary']->expects( $this->exactly( 8 ) )->method(
'testUserForCreation' )
2416 ->with( $callback, $callback2 )
2417 ->will( $this->onConsecutiveCalls(
2425 $mocks[
'secondary']->expects( $this->exactly( 3 ) )->method(
'autoCreatedAccount' )
2426 ->with( $callback, $callback2 );
2428 $this->preauthMocks = [ $mocks[
'pre'] ];
2429 $this->primaryauthMocks = [ $mocks[
'primary'] ];
2430 $this->secondaryauthMocks = [ $mocks[
'secondary'] ];
2432 $session = $this->
request->getSession();
2434 $logger = new \TestLogger(
true,
function ( $m ) {
2435 $m = str_replace(
'MediaWiki\\Auth\\AuthManager::autoCreateUser: ',
'', $m );
2438 $this->manager->setLogger(
$logger );
2442 $this->manager->autoCreateUser(
$user,
'InvalidSource',
true );
2443 $this->fail(
'Expected exception not thrown' );
2444 }
catch ( \InvalidArgumentException $ex ) {
2445 $this->assertSame(
'Unknown auto-creation source: InvalidSource', $ex->getMessage() );
2451 $this->
hook(
'LocalUserCreated', $this->never() );
2453 $this->
unhook(
'LocalUserCreated' );
2455 $expect->warning(
'userexists' );
2456 $this->assertEquals( $expect,
$ret );
2457 $this->assertNotEquals( 0,
$user->getId() );
2458 $this->assertSame(
'UTSysop',
$user->getName() );
2459 $this->assertEquals(
$user->getId(), $session->getUser()->getId() );
2460 $this->assertSame( [
2461 [ LogLevel::DEBUG,
'{username} already exists locally' ],
2467 $this->
hook(
'LocalUserCreated', $this->never() );
2469 $this->
unhook(
'LocalUserCreated' );
2471 $expect->warning(
'userexists' );
2472 $this->assertEquals( $expect,
$ret );
2473 $this->assertNotEquals( 0,
$user->getId() );
2474 $this->assertSame(
'UTSysop',
$user->getName() );
2475 $this->assertEquals( 0, $session->getUser()->getId() );
2476 $this->assertSame( [
2477 [ LogLevel::DEBUG,
'{username} already exists locally' ],
2483 $readOnlyMode = \MediaWiki\MediaWikiServices::getInstance()->getReadOnlyMode();
2484 $readOnlyMode->setReason(
'Because' );
2486 $this->
hook(
'LocalUserCreated', $this->never() );
2488 $this->
unhook(
'LocalUserCreated' );
2490 $this->assertEquals( 0,
$user->getId() );
2492 $this->assertEquals( 0, $session->getUser()->getId() );
2493 $this->assertSame( [
2494 [ LogLevel::DEBUG,
'denied by wfReadOnly(): {reason}' ],
2497 $readOnlyMode->setReason(
false );
2501 $session->set(
'AuthManager::AutoCreateBlacklist',
'test' );
2503 $this->
hook(
'LocalUserCreated', $this->never() );
2505 $this->
unhook(
'LocalUserCreated' );
2507 $this->assertEquals( 0,
$user->getId() );
2509 $this->assertEquals( 0, $session->getUser()->getId() );
2510 $this->assertSame( [
2511 [ LogLevel::DEBUG,
'blacklisted in session {sessionid}' ],
2518 $this->
hook(
'LocalUserCreated', $this->never() );
2520 $this->
unhook(
'LocalUserCreated' );
2522 $this->assertEquals( 0,
$user->getId() );
2524 $this->assertEquals( 0, $session->getUser()->getId() );
2525 $this->assertSame( [
2526 [ LogLevel::DEBUG,
'blacklisted in session {sessionid}' ],
2533 $this->
hook(
'LocalUserCreated', $this->never() );
2535 $this->
unhook(
'LocalUserCreated' );
2537 $this->assertEquals( 0,
$user->getId() );
2539 $this->assertEquals( 0, $session->getUser()->getId() );
2540 $this->assertSame( [
2541 [ LogLevel::DEBUG,
'name "{username}" is not creatable' ],
2544 $this->assertSame(
'noname', $session->get(
'AuthManager::AutoCreateBlacklist' ) );
2551 $this->
hook(
'LocalUserCreated', $this->never() );
2553 $this->
unhook(
'LocalUserCreated' );
2555 $this->assertEquals( 0,
$user->getId() );
2557 $this->assertEquals( 0, $session->getUser()->getId() );
2558 $this->assertSame( [
2559 [ LogLevel::DEBUG,
'IP lacks the ability to create or autocreate accounts' ],
2563 'authmanager-autocreate-noperm', $session->get(
'AuthManager::AutoCreateBlacklist' )
2572 $this->
hook(
'LocalUserCreated', $this->never() );
2574 $this->
unhook(
'LocalUserCreated' );
2584 $this->
hook(
'LocalUserCreated', $this->never() );
2586 $this->
unhook(
'LocalUserCreated' );
2593 $this->
hook(
'LocalUserCreated', $this->never() );
2595 $this->
unhook(
'LocalUserCreated' );
2602 $this->
hook(
'LocalUserCreated', $this->never() );
2607 $this->
unhook(
'LocalUserCreated' );
2609 $this->assertEquals( 0,
$user->getId() );
2611 $this->assertEquals( 0, $session->getUser()->getId() );
2612 $this->assertSame( [
2613 [ LogLevel::DEBUG,
'Could not acquire account creation lock' ],
2620 $this->
hook(
'LocalUserCreated', $this->never() );
2622 $this->
unhook(
'LocalUserCreated' );
2624 $this->assertEquals( 0,
$user->getId() );
2626 $this->assertEquals( 0, $session->getUser()->getId() );
2627 $this->assertSame( [
2628 [ LogLevel::DEBUG,
'Provider denied creation of {username}: {reason}' ],
2631 $this->assertEquals(
2637 $this->
hook(
'LocalUserCreated', $this->never() );
2639 $this->
unhook(
'LocalUserCreated' );
2641 $this->assertEquals( 0,
$user->getId() );
2643 $this->assertEquals( 0, $session->getUser()->getId() );
2644 $this->assertSame( [
2645 [ LogLevel::DEBUG,
'Provider denied creation of {username}: {reason}' ],
2648 $this->assertEquals(
2654 $this->
hook(
'LocalUserCreated', $this->never() );
2656 $this->
unhook(
'LocalUserCreated' );
2658 $this->assertEquals( 0,
$user->getId() );
2660 $this->assertEquals( 0, $session->getUser()->getId() );
2661 $this->assertSame( [
2662 [ LogLevel::DEBUG,
'Provider denied creation of {username}: {reason}' ],
2665 $this->assertEquals(
2672 $cache->set( $backoffKey,
true );
2675 $this->
hook(
'LocalUserCreated', $this->never() );
2677 $this->
unhook(
'LocalUserCreated' );
2679 $this->assertEquals( 0,
$user->getId() );
2681 $this->assertEquals( 0, $session->getUser()->getId() );
2682 $this->assertSame( [
2683 [ LogLevel::DEBUG,
'{username} denied by prior creation attempt failures' ],
2686 $this->assertSame(
null, $session->get(
'AuthManager::AutoCreateBlacklist' ) );
2687 $cache->delete( $backoffKey );
2692 ->setMethods( [
'addToDatabase' ] )->getMock();
2693 $user->expects( $this->once() )->method(
'addToDatabase' )
2698 $this->assertEquals( 0,
$user->getId() );
2700 $this->assertEquals( 0, $session->getUser()->getId() );
2701 $this->assertSame( [
2702 [ LogLevel::INFO,
'creating new user ({username}) - from: {from}' ],
2703 [ LogLevel::ERROR,
'{username} failed with message {msg}' ],
2706 $this->assertSame(
null, $session->get(
'AuthManager::AutoCreateBlacklist' ) );
2711 $this->assertFalse(
$cache->get( $backoffKey ),
'sanity check' );
2714 ->setMethods( [
'addToDatabase' ] )->getMock();
2715 $user->expects( $this->once() )->method(
'addToDatabase' )
2716 ->will( $this->throwException(
new \Exception(
'Excepted' ) ) );
2720 $this->fail(
'Expected exception not thrown' );
2721 }
catch ( \Exception $ex ) {
2722 $this->assertSame(
'Excepted', $ex->getMessage() );
2724 $this->assertEquals( 0,
$user->getId() );
2725 $this->assertEquals( 0, $session->getUser()->getId() );
2726 $this->assertSame( [
2727 [ LogLevel::INFO,
'creating new user ({username}) - from: {from}' ],
2728 [ LogLevel::ERROR,
'{username} failed with exception {exception}' ],
2731 $this->assertSame(
null, $session->get(
'AuthManager::AutoCreateBlacklist' ) );
2732 $this->assertNotEquals(
false,
$cache->get( $backoffKey ) );
2733 $cache->delete( $backoffKey );
2738 ->setMethods( [
'addToDatabase' ] )->getMock();
2739 $user->expects( $this->once() )->method(
'addToDatabase' )
2742 $status = $oldUser->addToDatabase();
2743 $this->assertTrue(
$status->isOK(),
'sanity check' );
2744 $user->setId( $oldUser->getId() );
2745 return \Status::newFatal(
'userexists' );
2750 $expect->warning(
'userexists' );
2751 $this->assertEquals( $expect,
$ret );
2752 $this->assertNotEquals( 0,
$user->getId() );
2754 $this->assertEquals(
$user->getId(), $session->getUser()->getId() );
2755 $this->assertSame( [
2756 [ LogLevel::INFO,
'creating new user ({username}) - from: {from}' ],
2757 [ LogLevel::INFO,
'{username} already exists locally (race)' ],
2760 $this->assertSame(
null, $session->get(
'AuthManager::AutoCreateBlacklist' ) );
2766 $this->
hook(
'LocalUserCreated', $this->once() )
2767 ->with( $callback, $this->equalTo(
true ) );
2769 $this->
unhook(
'LocalUserCreated' );
2771 $this->assertNotEquals( 0,
$user->getId() );
2773 $this->assertEquals(
$user->getId(), $session->getUser()->getId() );
2774 $this->assertSame( [
2775 [ LogLevel::INFO,
'creating new user ({username}) - from: {from}' ],
2780 $maxLogId = $dbw->selectField(
'logging',
'MAX(log_id)', [
'log_type' =>
'newusers' ] );
2784 $this->
hook(
'LocalUserCreated', $this->once() )
2785 ->with( $callback, $this->equalTo(
true ) );
2787 $this->
unhook(
'LocalUserCreated' );
2789 $this->assertNotEquals( 0,
$user->getId() );
2791 $this->assertEquals( 0, $session->getUser()->getId() );
2792 $this->assertSame( [
2793 [ LogLevel::INFO,
'creating new user ({username}) - from: {from}' ],
2798 $dbw->selectField(
'logging',
'MAX(log_id)', [
'log_type' =>
'newusers' ] )
2801 $this->config->set(
'NewUserLog',
true );
2810 $rows = iterator_to_array( $dbw->select(
2814 'log_id > ' . (
int)$maxLogId,
2815 'log_type' =>
'newusers'
2821 $this->assertCount( 1,
$rows );
2824 $this->assertSame(
'autocreate', $entry->getSubtype() );
2825 $this->assertSame(
$user->getId(), $entry->getPerformer()->getId() );
2826 $this->assertSame(
$user->getName(), $entry->getPerformer()->getName() );
2827 $this->assertSame(
$user->getUserPage()->getFullText(), $entry->getTarget()->getFullText() );
2828 $this->assertSame( [
'4::userid' =>
$user->getId() ], $entry->getParameters() );
2830 $workaroundPHPUnitBug =
true;
2840 $makeReq =
function ( $key )
use (
$action ) {
2842 $req->expects( $this->
any() )->method(
'getUniqueId' )
2843 ->will( $this->returnValue( $key ) );
2848 $cmpReqs =
function ( $a, $b ) {
2849 $ret = strcmp( get_class( $a ), get_class( $b ) );
2851 $ret = strcmp( $a->key, $b->key );
2859 foreach ( [
'pre',
'primary',
'secondary' ]
as $key ) {
2860 $class = ucfirst( $key ) .
'AuthenticationProvider';
2861 $mocks[$key] = $this->getMockBuilder(
"MediaWiki\\Auth\\$class" )
2863 'getUniqueId',
'getAuthenticationRequests',
'providerAllowsAuthenticationDataChange',
2865 ->getMockForAbstractClass();
2866 $mocks[$key]->expects( $this->
any() )->method(
'getUniqueId' )
2867 ->will( $this->returnValue( $key ) );
2868 $mocks[$key]->expects( $this->
any() )->method(
'getAuthenticationRequests' )
2869 ->will( $this->returnCallback(
function (
$action )
use ( $key, $makeReq ) {
2870 return [ $makeReq(
"$key-$action" ), $makeReq(
'generic' ) ];
2872 $mocks[$key]->expects( $this->
any() )->method(
'providerAllowsAuthenticationDataChange' )
2873 ->will( $this->returnValue( $good ) );
2882 $class =
'PrimaryAuthenticationProvider';
2883 $mocks[
"primary-$type"] = $this->getMockBuilder(
"MediaWiki\\Auth\\$class" )
2885 'getUniqueId',
'accountCreationType',
'getAuthenticationRequests',
2886 'providerAllowsAuthenticationDataChange',
2888 ->getMockForAbstractClass();
2889 $mocks[
"primary-$type"]->expects( $this->
any() )->method(
'getUniqueId' )
2890 ->will( $this->returnValue(
"primary-$type" ) );
2891 $mocks[
"primary-$type"]->expects( $this->
any() )->method(
'accountCreationType' )
2892 ->will( $this->returnValue(
$type ) );
2893 $mocks[
"primary-$type"]->expects( $this->
any() )->method(
'getAuthenticationRequests' )
2894 ->will( $this->returnCallback(
function (
$action )
use (
$type, $makeReq ) {
2895 return [ $makeReq(
"primary-$type-$action" ), $makeReq(
'generic' ) ];
2897 $mocks[
"primary-$type"]->expects( $this->
any() )
2898 ->method(
'providerAllowsAuthenticationDataChange' )
2899 ->will( $this->returnValue( $good ) );
2900 $this->primaryauthMocks[] = $mocks[
"primary-$type"];
2905 'getUniqueId',
'accountCreationType',
'getAuthenticationRequests',
2906 'providerAllowsAuthenticationDataChange',
2908 ->getMockForAbstractClass();
2909 $mocks[
'primary2']->expects( $this->
any() )->method(
'getUniqueId' )
2910 ->will( $this->returnValue(
'primary2' ) );
2911 $mocks[
'primary2']->expects( $this->
any() )->method(
'accountCreationType' )
2913 $mocks[
'primary2']->expects( $this->
any() )->method(
'getAuthenticationRequests' )
2914 ->will( $this->returnValue( [] ) );
2915 $mocks[
'primary2']->expects( $this->
any() )
2916 ->method(
'providerAllowsAuthenticationDataChange' )
2917 ->will( $this->returnCallback(
function (
$req )
use ( $good ) {
2920 $this->primaryauthMocks[] = $mocks[
'primary2'];
2922 $this->preauthMocks = [ $mocks[
'pre'] ];
2923 $this->secondaryauthMocks = [ $mocks[
'secondary'] ];
2927 if ( isset( $state[
'continueRequests'] ) ) {
2928 $state[
'continueRequests'] = array_map( $makeReq, $state[
'continueRequests'] );
2931 $this->
request->getSession()->setSecret(
'AuthManager::authnState', $state );
2933 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState', $state );
2935 $this->
request->getSession()->setSecret(
'AuthManager::accountLinkState', $state );
2939 $expectReqs = array_map( $makeReq, $expect );
2944 $expectReqs[] =
$req;
2948 $expectReqs[] =
$req;
2952 $expectReqs[] =
$req;
2954 usort( $expectReqs, $cmpReqs );
2956 $actual = $this->manager->getAuthenticationRequests(
$action );
2957 foreach ( $actual
as $req ) {
2961 usort( $actual, $cmpReqs );
2963 $this->assertEquals( $expectReqs, $actual );
2970 $expectReqs[] =
$req;
2971 usort( $expectReqs, $cmpReqs );
2974 foreach ( $actual
as $req ) {
2978 usort( $actual, $cmpReqs );
2980 $this->assertEquals( $expectReqs, $actual );
2988 [
'pre-login',
'primary-none-login',
'primary-create-login',
2989 'primary-link-login',
'secondary-login',
'generic' ],
2993 [
'pre-create',
'primary-none-create',
'primary-create-create',
2994 'primary-link-create',
'secondary-create',
'generic' ],
2998 [
'primary-link-link',
'generic' ],
3002 [
'primary-none-change',
'primary-create-change',
'primary-link-change',
3003 'secondary-change' ],
3007 [
'primary-none-remove',
'primary-create-remove',
'primary-link-remove',
3008 'secondary-remove' ],
3012 [
'primary-link-remove' ],
3020 $reqs = [
'continue-login',
'foo',
'bar' ],
3022 'continueRequests' => $reqs,
3031 $reqs = [
'continue-create',
'foo',
'bar' ],
3033 'continueRequests' => $reqs,
3042 $reqs = [
'continue-link',
'foo',
'bar' ],
3044 'continueRequests' => $reqs,
3051 $makeReq =
function ( $key, $required ) {
3053 $req->expects( $this->
any() )->method(
'getUniqueId' )
3054 ->will( $this->returnValue( $key ) );
3057 $req->required = $required;
3060 $cmpReqs =
function ( $a, $b ) {
3061 $ret = strcmp( get_class( $a ), get_class( $b ) );
3063 $ret = strcmp( $a->key, $b->key );
3071 $primary1->expects( $this->
any() )->method(
'getUniqueId' )
3072 ->will( $this->returnValue(
'primary1' ) );
3073 $primary1->expects( $this->
any() )->method(
'accountCreationType' )
3075 $primary1->expects( $this->
any() )->method(
'getAuthenticationRequests' )
3076 ->will( $this->returnCallback(
function (
$action )
use ( $makeReq ) {
3088 $primary2->expects( $this->
any() )->method(
'getUniqueId' )
3089 ->will( $this->returnValue(
'primary2' ) );
3090 $primary2->expects( $this->
any() )->method(
'accountCreationType' )
3092 $primary2->expects( $this->
any() )->method(
'getAuthenticationRequests' )
3093 ->will( $this->returnCallback(
function (
$action )
use ( $makeReq ) {
3102 $secondary->expects( $this->
any() )->method(
'getUniqueId' )
3103 ->will( $this->returnValue(
'secondary' ) );
3104 $secondary->expects( $this->
any() )->method(
'getAuthenticationRequests' )
3105 ->will( $this->returnCallback(
function (
$action )
use ( $makeReq ) {
3116 $this->primaryauthMocks = [ $primary1, $primary2 ];
3117 $this->secondaryauthMocks = [ $secondary ];
3132 usort( $actual, $cmpReqs );
3133 usort( $expected, $cmpReqs );
3134 $this->assertEquals( $expected, $actual );
3136 $this->primaryauthMocks = [ $primary1 ];
3137 $this->secondaryauthMocks = [ $secondary ];
3150 usort( $actual, $cmpReqs );
3151 usort( $expected, $cmpReqs );
3152 $this->assertEquals( $expected, $actual );
3157 foreach ( [
'primary',
'secondary' ]
as $key ) {
3158 $class = ucfirst( $key ) .
'AuthenticationProvider';
3159 $mocks[$key] = $this->getMockForAbstractClass(
"MediaWiki\\Auth\\$class" );
3160 $mocks[$key]->expects( $this->
any() )->method(
'getUniqueId' )
3161 ->will( $this->returnValue( $key ) );
3162 $mocks[$key]->expects( $this->
any() )->method(
'providerAllowsPropertyChange' )
3163 ->will( $this->returnCallback(
function ( $prop )
use ( $key ) {
3164 return $prop !== $key;
3168 $this->primaryauthMocks = [ $mocks[
'primary'] ];
3169 $this->secondaryauthMocks = [ $mocks[
'secondary'] ];
3172 $this->assertTrue( $this->manager->allowsPropertyChange(
'foo' ) );
3173 $this->assertFalse( $this->manager->allowsPropertyChange(
'primary' ) );
3174 $this->assertFalse( $this->manager->allowsPropertyChange(
'secondary' ) );
3183 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'primary' ) );
3184 $mock->expects( $this->
any() )->method(
'beginPrimaryAuthentication' )
3186 $mock->expects( $this->
any() )->method(
'accountCreationType' )
3188 $mock->expects( $this->
any() )->method(
'testUserExists' )->will( $this->returnValue(
true ) );
3189 $mock->expects( $this->
any() )->method(
'testUserForCreation' )
3193 $mock2->expects( $this->
any() )->method(
'getUniqueId' )
3194 ->will( $this->returnValue(
'secondary' ) );
3195 $mock2->expects( $this->
any() )->method(
'beginSecondaryAuthentication' )->will(
3200 $mock2->expects( $this->
any() )->method(
'continueSecondaryAuthentication' )
3202 $mock2->expects( $this->
any() )->method(
'testUserForCreation' )
3205 $this->primaryauthMocks = [ $mock ];
3206 $this->secondaryauthMocks = [ $mock2 ];
3208 $this->manager->setLogger(
new \Psr\Log\NullLogger() );
3209 $session = $this->
request->getSession();
3219 $this->
hook(
'UserLoggedIn', $this->never() );
3220 $this->
hook(
'LocalUserCreated', $this->once() )->with( $callback, $this->equalTo(
true ) );
3221 $ret = $this->manager->beginAuthentication( [],
'http://localhost/' );
3222 $this->
unhook(
'LocalUserCreated' );
3223 $this->
unhook(
'UserLoggedIn' );
3228 $this->assertSame( 0, $session->getUser()->getId() );
3230 $this->
hook(
'UserLoggedIn', $this->once() )->with( $callback );
3231 $this->
hook(
'LocalUserCreated', $this->never() );
3232 $ret = $this->manager->continueAuthentication( [] );
3233 $this->
unhook(
'LocalUserCreated' );
3234 $this->
unhook(
'UserLoggedIn' );
3237 $this->assertSame( $id, $session->getUser()->getId() );
3244 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'primary' ) );
3245 $mock->expects( $this->
any() )->method(
'beginPrimaryAuthentication' )
3247 $mock->expects( $this->
any() )->method(
'accountCreationType' )
3249 $mock->expects( $this->
any() )->method(
'testUserExists' )->will( $this->returnValue(
true ) );
3250 $mock->expects( $this->
any() )->method(
'testUserForCreation' )
3253 $this->primaryauthMocks = [ $mock ];
3255 $this->manager->setLogger(
new \Psr\Log\NullLogger() );
3256 $session = $this->
request->getSession();
3259 $this->assertSame( 0, $session->getUser()->getId(),
3264 $this->
hook(
'UserLoggedIn', $this->never() );
3265 $this->
hook(
'LocalUserCreated', $this->never() );
3266 $ret = $this->manager->beginAuthentication( [],
'http://localhost/' );
3267 $this->
unhook(
'LocalUserCreated' );
3268 $this->
unhook(
'UserLoggedIn' );
3270 $this->assertSame(
'authmanager-authn-autocreate-failed',
$ret->message->getKey() );
3273 $this->assertSame( 0, $session->getUser()->getId() );
3279 $this->assertNull( $this->manager->getAuthenticationSessionData(
'foo' ) );
3280 $this->manager->setAuthenticationSessionData(
'foo',
'foo!' );
3281 $this->manager->setAuthenticationSessionData(
'bar',
'bar!' );
3282 $this->assertSame(
'foo!', $this->manager->getAuthenticationSessionData(
'foo' ) );
3283 $this->assertSame(
'bar!', $this->manager->getAuthenticationSessionData(
'bar' ) );
3284 $this->manager->removeAuthenticationSessionData(
'foo' );
3285 $this->assertNull( $this->manager->getAuthenticationSessionData(
'foo' ) );
3286 $this->assertSame(
'bar!', $this->manager->getAuthenticationSessionData(
'bar' ) );
3287 $this->manager->removeAuthenticationSessionData(
'bar' );
3288 $this->assertNull( $this->manager->getAuthenticationSessionData(
'bar' ) );
3290 $this->manager->setAuthenticationSessionData(
'foo',
'foo!' );
3291 $this->manager->setAuthenticationSessionData(
'bar',
'bar!' );
3292 $this->manager->removeAuthenticationSessionData(
null );
3293 $this->assertNull( $this->manager->getAuthenticationSessionData(
'foo' ) );
3294 $this->assertNull( $this->manager->getAuthenticationSessionData(
'bar' ) );
3304 foreach ( $types
as $type => $can ) {
3306 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
$type ) );
3307 $mock->expects( $this->
any() )->method(
'accountCreationType' )
3308 ->will( $this->returnValue(
$type ) );
3309 $this->primaryauthMocks = [ $mock ];
3311 $this->assertSame( $can, $this->manager->canCreateAccounts(),
$type );
3319 $this->
request->getSession()->setSecret(
'AuthManager::accountLinkState',
'test' );
3321 $this->manager->beginAccountLink(
$user, [],
'http://localhost/' );
3322 $this->fail(
'Expected exception not thrown' );
3323 }
catch ( \LogicException $ex ) {
3324 $this->assertEquals(
'Account linking is not possible', $ex->getMessage() );
3326 $this->assertNull( $this->
request->getSession()->getSecret(
'AuthManager::accountLinkState' ) );
3329 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
3330 $mock->expects( $this->
any() )->method(
'accountCreationType' )
3332 $this->primaryauthMocks = [ $mock ];
3335 $ret = $this->manager->beginAccountLink(
new \
User, [],
'http://localhost/' );
3337 $this->assertSame(
'noname',
$ret->message->getKey() );
3339 $ret = $this->manager->beginAccountLink(
3343 $this->assertSame(
'authmanager-userdoesnotexist',
$ret->message->getKey() );
3351 'userid' =>
$user->getId(),
3352 'username' =>
$user->getName(),
3357 $this->manager->continueAccountLink( [] );
3358 $this->fail(
'Expected exception not thrown' );
3359 }
catch ( \LogicException $ex ) {
3360 $this->assertEquals(
'Account linking is not possible', $ex->getMessage() );
3364 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
3365 $mock->expects( $this->
any() )->method(
'accountCreationType' )
3367 $mock->expects( $this->
any() )->method(
'beginPrimaryAccountLink' )->will(
3370 $this->primaryauthMocks = [ $mock ];
3373 $this->
request->getSession()->setSecret(
'AuthManager::accountLinkState',
null );
3374 $ret = $this->manager->continueAccountLink( [] );
3376 $this->assertSame(
'authmanager-link-not-in-progress',
$ret->message->getKey() );
3378 $this->
request->getSession()->setSecret(
'AuthManager::accountLinkState',
3379 [
'username' =>
$user->getName() .
'<>' ] + $session );
3380 $ret = $this->manager->continueAccountLink( [] );
3382 $this->assertSame(
'noname',
$ret->message->getKey() );
3383 $this->assertNull( $this->
request->getSession()->getSecret(
'AuthManager::accountLinkState' ) );
3385 $id =
$user->getId();
3386 $this->
request->getSession()->setSecret(
'AuthManager::accountLinkState',
3387 [
'userid' => $id + 1 ] + $session );
3389 $ret = $this->manager->continueAccountLink( [] );
3390 $this->fail(
'Expected exception not thrown' );
3391 }
catch ( \UnexpectedValueException $ex ) {
3392 $this->assertEquals(
3393 "User \"{$user->getName()}\" is valid, but ID $id !== " . ( $id + 1 ) .
'!',
3397 $this->assertNull( $this->
request->getSession()->getSecret(
'AuthManager::accountLinkState' ) );
3415 $req->primary = $primaryResponses;
3418 foreach ( [
'pre',
'primary' ]
as $key ) {
3419 $class = ucfirst( $key ) .
'AuthenticationProvider';
3420 $mocks[$key] = $this->getMockForAbstractClass(
3421 "MediaWiki\\Auth\\$class", [],
"Mock$class"
3423 $mocks[$key]->expects( $this->
any() )->method(
'getUniqueId' )
3424 ->will( $this->returnValue( $key ) );
3426 for ( $i = 2; $i <= 3; $i++ ) {
3427 $mocks[$key . $i] = $this->getMockForAbstractClass(
3428 "MediaWiki\\Auth\\$class", [],
"Mock$class"
3430 $mocks[$key . $i]->expects( $this->
any() )->method(
'getUniqueId' )
3431 ->will( $this->returnValue( $key . $i ) );
3435 $mocks[
'pre']->expects( $this->
any() )->method(
'testForAccountLink' )
3436 ->will( $this->returnCallback(
3440 $this->assertSame(
$user->getId(), $u->getId() );
3441 $this->assertSame(
$user->getName(), $u->getName() );
3446 $mocks[
'pre2']->expects( $this->atMost( 1 ) )->method(
'testForAccountLink' )
3449 $mocks[
'primary']->expects( $this->
any() )->method(
'accountCreationType' )
3452 $callback = $this->returnCallback(
function ( $u, $reqs )
use (
$user,
$req ) {
3453 $this->assertSame(
$user->getId(), $u->getId() );
3454 $this->assertSame(
$user->getName(), $u->getName() );
3456 foreach ( $reqs
as $r ) {
3457 $this->assertSame(
$user->getName(), $r->username );
3458 $foundReq = $foundReq || get_class( $r ) === get_class(
$req );
3460 $this->assertTrue( $foundReq,
'$reqs contains $req' );
3461 return array_shift(
$req->primary );
3463 $mocks[
'primary']->expects( $this->exactly( min( 1, $ct ) ) )
3464 ->method(
'beginPrimaryAccountLink' )
3465 ->will( $callback );
3466 $mocks[
'primary']->expects( $this->exactly( max( 0, $ct - 1 ) ) )
3467 ->method(
'continuePrimaryAccountLink' )
3468 ->will( $callback );
3471 $mocks[
'primary2']->expects( $this->
any() )->method(
'accountCreationType' )
3473 $mocks[
'primary2']->expects( $this->atMost( 1 ) )->method(
'beginPrimaryAccountLink' )
3474 ->will( $this->returnValue( $abstain ) );
3475 $mocks[
'primary2']->expects( $this->never() )->method(
'continuePrimaryAccountLink' );
3476 $mocks[
'primary3']->expects( $this->
any() )->method(
'accountCreationType' )
3478 $mocks[
'primary3']->expects( $this->never() )->method(
'beginPrimaryAccountLink' );
3479 $mocks[
'primary3']->expects( $this->never() )->method(
'continuePrimaryAccountLink' );
3481 $this->preauthMocks = [ $mocks[
'pre'], $mocks[
'pre2'] ];
3482 $this->primaryauthMocks = [ $mocks[
'primary3'], $mocks[
'primary2'], $mocks[
'primary'] ];
3483 $this->logger = new \TestLogger(
true,
function ( $message, $level ) {
3484 return $level === LogLevel::DEBUG ?
null : $message;
3488 $constraint = \PHPUnit_Framework_Assert::logicalOr(
3492 $providers = array_merge( $this->preauthMocks, $this->primaryauthMocks );
3493 foreach ( $providers
as $p ) {
3494 $p->postCalled =
false;
3495 $p->expects( $this->atMost( 1 ) )->method(
'postAccountLink' )
3498 $this->assertSame(
'UTSysop',
$user->getName() );
3500 $this->assertThat(
$response->status, $constraint );
3508 foreach ( $managerResponses
as $i =>
$response ) {
3512 $expectLog[] = [ LogLevel::INFO,
'Account linked to {user} by primary' ];
3518 $ret = $this->manager->beginAccountLink(
$user, [
$req ],
'http://localhost/' );
3520 $ret = $this->manager->continueAccountLink( [
$req ] );
3522 if (
$response instanceof \Exception ) {
3523 $this->fail(
'Expected exception not thrown',
"Response $i" );
3525 }
catch ( \Exception $ex ) {
3526 if ( !
$response instanceof \Exception ) {
3529 $this->assertEquals(
$response->getMessage(), $ex->getMessage(),
"Response $i, exception" );
3530 $this->assertNull( $this->
request->getSession()->getSecret(
'AuthManager::accountLinkState' ),
3531 "Response $i, exception, session state" );
3535 $this->assertSame(
'http://localhost/',
$req->returnToUrl );
3542 $this->assertNull( $this->
request->getSession()->getSecret(
'AuthManager::accountLinkState' ),
3543 "Response $i, session state" );
3544 foreach ( $providers
as $p ) {
3545 $this->assertSame(
$response->status, $p->postCalled,
3546 "Response $i, post-auth callback called" );
3549 $this->assertNotNull(
3550 $this->
request->getSession()->getSecret(
'AuthManager::accountLinkState' ),
3551 "Response $i, session state"
3553 foreach (
$ret->neededRequests
as $neededReq ) {
3555 "Response $i, neededRequest action" );
3557 $this->assertEquals(
3558 $ret->neededRequests,
3560 "Response $i, continuation check"
3562 foreach ( $providers
as $p ) {
3563 $this->assertFalse( $p->postCalled,
"Response $i, post-auth callback not called" );
3570 $this->assertSame( $expectLog, $this->logger->getBuffer() );
3578 'Pre-link test fail in pre' => [
3585 'Failure in primary' => [
3592 'All primary abstain' => [
3601 'Primary UI, then redirect, then fail' => [
3610 'Primary redirect, then abstain' => [
3614 [
$req ],
'/foo.html', [
'foo' =>
'bar' ]
3620 new \DomainException(
3621 'MockPrimaryAuthenticationProvider::continuePrimaryAccountLink() returned ABSTAIN'
3625 'Primary UI, then pass' => [