8 use Psr\Log\LoggerInterface;
12 use Wikimedia\ScopedCallback;
13 use Wikimedia\TestingAccessWrapper;
50 protected function hook( $hook, $expect ) {
52 $mock = $this->getMockBuilder( __CLASS__ )
53 ->setMethods( [
"on$hook" ] )
56 return $mock->expects( $expect )->method(
"on$hook" );
75 if ( $key ===
null ) {
80 $key = $key->getKey();
100 foreach ( [
'preauth',
'primaryauth',
'secondaryauth' ]
as $type ) {
101 $key =
$type .
'Mocks';
102 foreach ( $this->$key
as $mock ) {
103 $config[
$type][$mock->getUniqueId()] = [
'factory' =>
function ()
use ( $mock ) {
109 $this->config->set(
'AuthManagerConfig',
$config );
110 $this->config->set(
'LanguageCode',
'en' );
111 $this->config->set(
'NewUserLog',
false );
119 if ( $regen || !$this->config ) {
120 $this->config = new \HashConfig();
122 if ( $regen || !$this->
request ) {
123 $this->
request = new \FauxRequest();
125 if ( !$this->logger ) {
126 $this->logger = new \TestLogger();
129 if ( $regen || !$this->config->has(
'AuthManagerConfig' ) ) {
133 $this->manager->setLogger( $this->logger );
134 $this->managerPriv = TestingAccessWrapper::newFromObject( $this->manager );
144 if ( !$this->config ) {
145 $this->config = new \HashConfig();
148 $this->config->set(
'ObjectCacheSessionExpiry', 100 );
150 $methods[] =
'__toString';
151 $methods[] =
'describe';
152 if ( $canChangeUser !==
null ) {
153 $methods[] =
'canChangeUser';
156 ->setMethods( $methods )
158 $provider->expects( $this->
any() )->method(
'__toString' )
159 ->will( $this->returnValue(
'MockSessionProvider' ) );
160 $provider->expects( $this->
any() )->method(
'describe' )
161 ->will( $this->returnValue(
'MockSessionProvider sessions' ) );
162 if ( $canChangeUser !==
null ) {
163 $provider->expects( $this->
any() )->method(
'canChangeUser' )
164 ->will( $this->returnValue( $canChangeUser ) );
166 $this->config->set(
'SessionProviders', [
167 [
'factory' =>
function ()
use ( $provider ) {
172 $manager = new \MediaWiki\Session\SessionManager( [
173 'config' => $this->config,
174 'logger' =>
new \Psr\Log\NullLogger(),
177 TestingAccessWrapper::newFromObject(
$manager )->getProvider( (
string)$provider );
185 return [ $provider, $reset ];
192 $rProp->setAccessible(
true );
193 $old = $rProp->getValue();
194 $cb =
new ScopedCallback( [ $rProp,
'setValue' ], [ $old ] );
195 $rProp->setValue(
null );
203 TestingAccessWrapper::newFromObject( $singleton )->config
211 $this->assertFalse( $this->manager->canAuthenticateNow() );
212 ScopedCallback::consume( $reset );
215 $this->assertTrue( $this->manager->canAuthenticateNow() );
216 ScopedCallback::consume( $reset );
226 foreach ( $mocks
as $key => $mock ) {
227 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue( $key ) );
229 $mocks[0]->expects( $this->once() )->method(
'providerNormalizeUsername' )
230 ->with( $this->identicalTo(
'XYZ' ) )
231 ->willReturn(
'Foo' );
232 $mocks[1]->expects( $this->once() )->method(
'providerNormalizeUsername' )
233 ->with( $this->identicalTo(
'XYZ' ) )
234 ->willReturn(
'Foo' );
235 $mocks[2]->expects( $this->once() )->method(
'providerNormalizeUsername' )
236 ->with( $this->identicalTo(
'XYZ' ) )
237 ->willReturn(
null );
238 $mocks[3]->expects( $this->once() )->method(
'providerNormalizeUsername' )
239 ->with( $this->identicalTo(
'XYZ' ) )
240 ->willReturn(
'Bar!' );
242 $this->primaryauthMocks = $mocks;
246 $this->assertSame( [
'Foo',
'Bar!' ], $this->manager->normalizeUsername(
'XYZ' ) );
254 $this->logger = new \Psr\Log\NullLogger();
260 $mutableSession, [
'provideSessionInfo' ]
262 $provider->expects( $this->
any() )->method(
'provideSessionInfo' )
263 ->will( $this->returnCallback(
function ()
use ( $provider, &$provideUser ) {
265 'provider' => $provider,
273 $this->config->set(
'ReauthenticateTime', [] );
274 $this->config->set(
'AllowSecuritySensitiveOperationIfCannotReauthenticate', [] );
276 $session = $provider->getManager()->getSessionForRequest( $this->
request );
277 $this->assertSame( 0, $session->getUser()->getId(),
'sanity check' );
280 $session->set(
'AuthManager:lastAuthId', 0 );
281 $session->set(
'AuthManager:lastAuthTimestamp', time() - 5 );
282 $this->assertSame( $reauth, $this->manager->securitySensitiveOperationStatus(
'foo' ) );
284 $provideUser =
$user;
285 $session = $provider->getManager()->getSessionForRequest( $this->
request );
286 $this->assertSame(
$user->getId(), $session->getUser()->getId(),
'sanity check' );
289 $session->set(
'AuthManager:lastAuthId',
$user->getId() + 1 );
290 $session->set(
'AuthManager:lastAuthTimestamp', time() - 5 );
292 $this->manager->securitySensitiveOperationStatus(
'foo' );
293 $this->fail(
'Expected exception not thrown' );
294 }
catch ( \UnexpectedValueException $ex ) {
297 ?
'$wgReauthenticateTime lacks a default'
298 :
'$wgAllowSecuritySensitiveOperationIfCannotReauthenticate lacks a default',
303 if ( $mutableSession ) {
304 $this->config->set(
'ReauthenticateTime', [
311 $session->set(
'AuthManager:lastAuthId',
$user->getId() + 1 );
312 $session->set(
'AuthManager:lastAuthTimestamp', time() - 5 );
324 $session->set(
'AuthManager:lastAuthId',
$user->getId() );
325 $session->set(
'AuthManager:lastAuthTimestamp',
null );
337 $session->set(
'AuthManager:lastAuthTimestamp', time() - 5 );
343 $session->set(
'AuthManager:lastAuthTimestamp', time() - 20 );
352 $this->config->set(
'AllowSecuritySensitiveOperationIfCannotReauthenticate', [
371 ]
as $hook => $expect ) {
372 $this->
hook(
'SecuritySensitiveOperationStatus', $this->exactly( 2 ) )
376 $this->callback(
function (
$s )
use ( $session ) {
377 return $s->getId() === $session->getId();
379 $mutableSession ? $this->equalTo( 500, 1 ) : $this->equalTo( -1 )
381 ->
will( $this->returnCallback(
function ( &$v )
use ( $hook ) {
385 $session->set(
'AuthManager:lastAuthTimestamp', time() - 500 );
387 $expect, $this->manager->securitySensitiveOperationStatus(
'test' ),
"hook $hook"
390 $expect, $this->manager->securitySensitiveOperationStatus(
'test2' ),
"hook $hook"
392 $this->
unhook(
'SecuritySensitiveOperationStatus' );
395 ScopedCallback::consume( $reset );
416 $mock1->expects( $this->
any() )->method(
'getUniqueId' )
417 ->will( $this->returnValue(
'primary1' ) );
418 $mock1->expects( $this->
any() )->method(
'testUserCanAuthenticate' )
419 ->with( $this->equalTo(
'UTSysop' ) )
420 ->will( $this->returnValue( $primary1Can ) );
422 $mock2->expects( $this->
any() )->method(
'getUniqueId' )
423 ->will( $this->returnValue(
'primary2' ) );
424 $mock2->expects( $this->
any() )->method(
'testUserCanAuthenticate' )
425 ->with( $this->equalTo(
'UTSysop' ) )
426 ->will( $this->returnValue( $primary2Can ) );
427 $this->primaryauthMocks = [ $mock1, $mock2 ];
430 $this->assertSame( $expect, $this->manager->userCanAuthenticate(
'UTSysop' ) );
435 [
false,
false,
false ],
436 [
true,
false,
true ],
437 [
false,
true,
true ],
438 [
true,
true,
true ],
446 $mock->expects( $this->
any() )->method(
'getUniqueId' )
447 ->will( $this->returnValue(
'primary' ) );
448 $mock->expects( $this->once() )->method(
'providerRevokeAccessForUser' )
449 ->with( $this->equalTo(
'UTSysop' ) );
450 $this->primaryauthMocks = [ $mock ];
453 $this->logger->setCollect(
true );
455 $this->manager->revokeAccessForUser(
'UTSysop' );
458 [ LogLevel::INFO,
'Revoking access for {user}' ],
459 ], $this->logger->getBuffer() );
468 foreach ( $mocks
as $key => $mock ) {
469 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue( $key ) );
470 $mock->expects( $this->once() )->method(
'setLogger' );
471 $mock->expects( $this->once() )->method(
'setManager' );
472 $mock->expects( $this->once() )->method(
'setConfig' );
474 $this->preauthMocks = [ $mocks[
'pre'] ];
475 $this->primaryauthMocks = [ $mocks[
'primary'] ];
476 $this->secondaryauthMocks = [ $mocks[
'secondary'] ];
482 $this->managerPriv->getAuthenticationProvider(
'primary' )
486 $this->managerPriv->getAuthenticationProvider(
'secondary' )
490 $this->managerPriv->getAuthenticationProvider(
'pre' )
493 [
'pre' => $mocks[
'pre'] ],
494 $this->managerPriv->getPreAuthenticationProviders()
497 [
'primary' => $mocks[
'primary'] ],
498 $this->managerPriv->getPrimaryAuthenticationProviders()
501 [
'secondary' => $mocks[
'secondary'] ],
502 $this->managerPriv->getSecondaryAuthenticationProviders()
508 $mock1->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
509 $mock2->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
510 $this->preauthMocks = [ $mock1 ];
511 $this->primaryauthMocks = [ $mock2 ];
512 $this->secondaryauthMocks = [];
515 $this->managerPriv->getAuthenticationProvider(
'Y' );
516 $this->fail(
'Expected exception not thrown' );
517 }
catch ( \RuntimeException $ex ) {
518 $class1 = get_class( $mock1 );
519 $class2 = get_class( $mock2 );
521 "Duplicate specifications for id X (classes $class1 and $class2)", $ex->getMessage()
527 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
528 $class = get_class( $mock );
529 $this->preauthMocks = [ $mock ];
530 $this->primaryauthMocks = [ $mock ];
531 $this->secondaryauthMocks = [ $mock ];
534 $this->managerPriv->getPreAuthenticationProviders();
535 $this->fail(
'Expected exception not thrown' );
536 }
catch ( \RuntimeException $ex ) {
538 "Expected instance of MediaWiki\\Auth\\PreAuthenticationProvider, got $class",
543 $this->managerPriv->getPrimaryAuthenticationProviders();
544 $this->fail(
'Expected exception not thrown' );
545 }
catch ( \RuntimeException $ex ) {
547 "Expected instance of MediaWiki\\Auth\\PrimaryAuthenticationProvider, got $class",
552 $this->managerPriv->getSecondaryAuthenticationProviders();
553 $this->fail(
'Expected exception not thrown' );
554 }
catch ( \RuntimeException $ex ) {
556 "Expected instance of MediaWiki\\Auth\\SecondaryAuthenticationProvider, got $class",
565 $mock1->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'A' ) );
566 $mock2->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'B' ) );
567 $mock3->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'C' ) );
568 $this->preauthMocks = [];
569 $this->primaryauthMocks = [ $mock1, $mock2, $mock3 ];
570 $this->secondaryauthMocks = [];
572 $config = $this->config->
get(
'AuthManagerConfig' );
576 [
'A' => $mock1,
'B' => $mock2,
'C' => $mock3 ],
577 $this->managerPriv->getPrimaryAuthenticationProviders(),
581 $config[
'primaryauth'][
'A'][
'sort'] = 100;
582 $config[
'primaryauth'][
'C'][
'sort'] = -1;
583 $this->config->set(
'AuthManagerConfig',
$config );
586 [
'C' => $mock3,
'B' => $mock2,
'A' => $mock1 ],
587 $this->managerPriv->getPrimaryAuthenticationProviders()
600 $user->addToDatabase();
601 $oldToken =
$user->getToken();
602 $this->managerPriv->setDefaultUserOptions(
$user,
false );
603 $user->saveSettings();
604 $this->assertNotEquals( $oldToken,
$user->getToken() );
605 $this->assertSame(
'zh',
$user->getOption(
'language' ) );
606 $this->assertSame(
'zh',
$user->getOption(
'variant' ) );
609 $user->addToDatabase();
610 $oldToken =
$user->getToken();
611 $this->managerPriv->setDefaultUserOptions(
$user,
true );
612 $user->saveSettings();
613 $this->assertNotEquals( $oldToken,
$user->getToken() );
614 $this->assertSame(
'de',
$user->getOption(
'language' ) );
615 $this->assertSame(
'zh',
$user->getOption(
'variant' ) );
620 $user->addToDatabase();
621 $oldToken =
$user->getToken();
622 $this->managerPriv->setDefaultUserOptions(
$user,
true );
623 $user->saveSettings();
624 $this->assertNotEquals( $oldToken,
$user->getToken() );
625 $this->assertSame(
'de',
$user->getOption(
'language' ) );
626 $this->assertSame(
null,
$user->getOption(
'variant' ) );
633 $mockA->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'A' ) );
634 $mockB->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'B' ) );
635 $mockB2->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'B' ) );
636 $this->primaryauthMocks = [ $mockA ];
638 $this->logger = new \TestLogger(
true );
642 $this->manager->forcePrimaryAuthenticationProviders( [ $mockB ],
'testing' );
644 [
'B' => $mockB ], $this->managerPriv->getPrimaryAuthenticationProviders()
646 $this->assertSame(
null, $this->managerPriv->getAuthenticationProvider(
'A' ) );
647 $this->assertSame( $mockB, $this->managerPriv->getAuthenticationProvider(
'B' ) );
649 [ LogLevel::WARNING,
'Overriding AuthManager primary authn because testing' ],
650 ], $this->logger->getBuffer() );
651 $this->logger->clearBuffer();
655 $this->assertSame( $mockA, $this->managerPriv->getAuthenticationProvider(
'A' ) );
656 $this->assertSame(
null, $this->managerPriv->getAuthenticationProvider(
'B' ) );
657 $this->
request->getSession()->setSecret(
'AuthManager::authnState',
'test' );
658 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState',
'test' );
659 $this->manager->forcePrimaryAuthenticationProviders( [ $mockB ],
'testing' );
661 [
'B' => $mockB ], $this->managerPriv->getPrimaryAuthenticationProviders()
663 $this->assertSame(
null, $this->managerPriv->getAuthenticationProvider(
'A' ) );
664 $this->assertSame( $mockB, $this->managerPriv->getAuthenticationProvider(
'B' ) );
665 $this->assertNull( $this->
request->getSession()->getSecret(
'AuthManager::authnState' ) );
667 $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' )
670 [ LogLevel::WARNING,
'Overriding AuthManager primary authn because testing' ],
673 'PrimaryAuthenticationProviders have already been accessed! I hope nothing breaks.'
675 ], $this->logger->getBuffer() );
676 $this->logger->clearBuffer();
681 $this->manager->forcePrimaryAuthenticationProviders( [ $mockB, $mockB2 ],
'testing' );
682 $this->fail(
'Expected exception not thrown' );
683 }
catch ( \RuntimeException $ex ) {
684 $class1 = get_class( $mockB );
685 $class2 = get_class( $mockB2 );
687 "Duplicate specifications for id B (classes $class2 and $class1)", $ex->getMessage()
693 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
694 $class = get_class( $mock );
696 $this->manager->forcePrimaryAuthenticationProviders( [ $mock ],
'testing' );
697 $this->fail(
'Expected exception not thrown' );
698 }
catch ( \RuntimeException $ex ) {
700 "Expected instance of MediaWiki\\Auth\\PrimaryAuthenticationProvider, got $class",
711 $this->
hook(
'UserLoggedIn', $this->never() );
712 $this->
request->getSession()->setSecret(
'AuthManager::authnState',
'test' );
714 $this->manager->beginAuthentication( [],
'http://localhost/' );
715 $this->fail(
'Expected exception not thrown' );
716 }
catch ( \LogicException $ex ) {
717 $this->assertSame(
'Authentication is not possible now', $ex->getMessage() );
719 $this->
unhook(
'UserLoggedIn' );
720 $this->assertNull( $this->
request->getSession()->getSecret(
'AuthManager::authnState' ) );
721 ScopedCallback::consume( $reset );
729 $this->
hook(
'UserLoggedIn', $this->never() );
731 $this->manager->beginAuthentication( $reqs,
'http://localhost/' );
732 $this->fail(
'Expected exception not thrown' );
733 }
catch ( \LogicException $ex ) {
735 'CreatedAccountAuthenticationRequests are only valid on the same AuthManager ' .
736 'that created the account',
740 $this->
unhook(
'UserLoggedIn' );
742 $this->
request->getSession()->clear();
743 $this->
request->getSession()->setSecret(
'AuthManager::authnState',
'test' );
744 $this->managerPriv->createdAccountAuthenticationRequests = [ $reqs[0] ];
745 $this->
hook(
'UserLoggedIn', $this->once() )
746 ->with( $this->callback(
function ( $u )
use (
$user ) {
747 return $user->getId() === $u->getId() &&
$user->getName() === $u->getName();
749 $this->
hook(
'AuthManagerLoginAuthenticateAudit', $this->once() );
750 $this->logger->setCollect(
true );
751 $ret = $this->manager->beginAuthentication( $reqs,
'http://localhost/' );
752 $this->logger->setCollect(
false );
753 $this->
unhook(
'UserLoggedIn' );
754 $this->
unhook(
'AuthManagerLoginAuthenticateAudit' );
756 $this->assertSame(
$user->getName(),
$ret->username );
757 $this->assertSame(
$user->getId(), $this->
request->getSessionData(
'AuthManager:lastAuthId' ) );
759 time(), $this->
request->getSessionData(
'AuthManager:lastAuthTimestamp' ),
762 $this->assertNull( $this->
request->getSession()->getSecret(
'AuthManager::authnState' ) );
763 $this->assertSame(
$user->getId(), $this->
request->getSession()->getUser()->getId() );
765 [ LogLevel::INFO,
'Logging in {user} after account creation' ],
766 ], $this->logger->getBuffer() );
775 $userReq->username =
'UTDummy';
777 $req1->returnToUrl =
'http://localhost/';
778 $req2->returnToUrl =
'http://localhost/';
779 $req3->returnToUrl =
'http://localhost/';
780 $req3->username =
'UTDummy';
781 $userReq->returnToUrl =
'http://localhost/';
785 $this->primaryauthMocks = [ $primary ];
788 $res->createRequest = $req1;
789 $primary->expects( $this->
any() )->method(
'beginPrimaryAuthentication' )
790 ->will( $this->returnValue(
$res ) );
792 null, [ $req2->getUniqueId() => $req2 ]
794 $this->logger->setCollect(
true );
795 $ret = $this->manager->beginAuthentication( [ $createReq ],
'http://localhost/' );
796 $this->logger->setCollect(
false );
799 $this->assertSame( $req1,
$ret->createRequest->createRequest );
800 $this->assertEquals( [ $req2->getUniqueId() => $req2 ],
$ret->createRequest->maybeLink );
804 ->setMethods( [
'continuePrimaryAuthentication' ] )
805 ->getMockForAbstractClass();
806 $this->primaryauthMocks = [ $primary ];
808 $primary->expects( $this->
any() )->method(
'beginPrimaryAuthentication' )
809 ->will( $this->returnValue(
813 $res->createRequest = $req2;
814 $primary->expects( $this->
any() )->method(
'continuePrimaryAuthentication' )
815 ->will( $this->returnValue(
$res ) );
816 $this->logger->setCollect(
true );
817 $ret = $this->manager->beginAuthentication( [],
'http://localhost/' );
819 $ret = $this->manager->continueAuthentication( [] );
820 $this->logger->setCollect(
false );
823 $this->assertSame( $req2,
$ret->createRequest->createRequest );
824 $this->assertEquals( [],
$ret->createRequest->maybeLink );
828 $this->primaryauthMocks = [ $primary ];
831 $createReq->returnToUrl =
'http://localhost/';
832 $createReq->username =
'UTDummy';
834 $primary->expects( $this->
any() )->method(
'beginPrimaryAccountCreation' )
835 ->with( $this->
anything(), $this->
anything(), [ $userReq, $createReq, $req3 ] )
836 ->will( $this->returnValue(
$res ) );
837 $primary->expects( $this->
any() )->method(
'accountCreationType' )
839 $this->logger->setCollect(
true );
840 $ret = $this->manager->beginAccountCreation(
841 $user, [ $userReq, $createReq ],
'http://localhost/'
843 $this->logger->setCollect(
false );
845 $state = $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' );
846 $this->assertNotNull( $state );
847 $this->assertEquals( [ $userReq, $createReq, $req3 ], $state[
'reqs'] );
848 $this->assertEquals( [ $req2 ], $state[
'maybeLink'] );
865 $id =
$user->getId();
870 $req->rememberMe = (bool)rand( 0, 1 );
871 $req->pre = $preResponse;
872 $req->primary = $primaryResponses;
873 $req->secondary = $secondaryResponses;
875 foreach ( [
'pre',
'primary',
'secondary' ]
as $key ) {
876 $class = ucfirst( $key ) .
'AuthenticationProvider';
877 $mocks[$key] = $this->getMockForAbstractClass(
878 "MediaWiki\\Auth\\$class", [],
"Mock$class"
880 $mocks[$key]->expects( $this->
any() )->method(
'getUniqueId' )
881 ->will( $this->returnValue( $key ) );
882 $mocks[$key .
'2'] = $this->getMockForAbstractClass(
"MediaWiki\\Auth\\$class" );
883 $mocks[$key .
'2']->expects( $this->
any() )->method(
'getUniqueId' )
884 ->will( $this->returnValue( $key .
'2' ) );
885 $mocks[$key .
'3'] = $this->getMockForAbstractClass(
"MediaWiki\\Auth\\$class" );
886 $mocks[$key .
'3']->expects( $this->
any() )->method(
'getUniqueId' )
887 ->will( $this->returnValue( $key .
'3' ) );
889 foreach ( $mocks
as $mock ) {
890 $mock->expects( $this->
any() )->method(
'getAuthenticationRequests' )
891 ->will( $this->returnValue( [] ) );
894 $mocks[
'pre']->expects( $this->once() )->method(
'testForAuthentication' )
895 ->will( $this->returnCallback(
function ( $reqs )
use (
$req ) {
896 $this->assertContains(
$req, $reqs );
901 $callback = $this->returnCallback(
function ( $reqs )
use (
$req ) {
902 $this->assertContains(
$req, $reqs );
903 return array_shift(
$req->primary );
905 $mocks[
'primary']->expects( $this->exactly( min( 1, $ct ) ) )
906 ->method(
'beginPrimaryAuthentication' )
908 $mocks[
'primary']->expects( $this->exactly( max( 0, $ct - 1 ) ) )
909 ->method(
'continuePrimaryAuthentication' )
912 $mocks[
'primary']->expects( $this->
any() )->method(
'accountCreationType' )
917 $callback = $this->returnCallback(
function (
$user, $reqs )
use ( $id,
$name,
$req ) {
918 $this->assertSame( $id,
$user->getId() );
920 $this->assertContains(
$req, $reqs );
921 return array_shift(
$req->secondary );
923 $mocks[
'secondary']->expects( $this->exactly( min( 1, $ct ) ) )
924 ->method(
'beginSecondaryAuthentication' )
926 $mocks[
'secondary']->expects( $this->exactly( max( 0, $ct - 1 ) ) )
927 ->method(
'continueSecondaryAuthentication' )
931 $mocks[
'pre2']->expects( $this->atMost( 1 ) )->method(
'testForAuthentication' )
933 $mocks[
'primary2']->expects( $this->atMost( 1 ) )->method(
'beginPrimaryAuthentication' )
934 ->will( $this->returnValue( $abstain ) );
935 $mocks[
'primary2']->expects( $this->never() )->method(
'continuePrimaryAuthentication' );
936 $mocks[
'secondary2']->expects( $this->atMost( 1 ) )->method(
'beginSecondaryAuthentication' )
937 ->will( $this->returnValue( $abstain ) );
938 $mocks[
'secondary2']->expects( $this->never() )->method(
'continueSecondaryAuthentication' );
939 $mocks[
'secondary3']->expects( $this->atMost( 1 ) )->method(
'beginSecondaryAuthentication' )
940 ->will( $this->returnValue( $abstain ) );
941 $mocks[
'secondary3']->expects( $this->never() )->method(
'continueSecondaryAuthentication' );
943 $this->preauthMocks = [ $mocks[
'pre'], $mocks[
'pre2'] ];
944 $this->primaryauthMocks = [ $mocks[
'primary'], $mocks[
'primary2'] ];
945 $this->secondaryauthMocks = [
946 $mocks[
'secondary3'], $mocks[
'secondary'], $mocks[
'secondary2'],
951 $this->logger->setCollect(
true );
953 $constraint = \PHPUnit_Framework_Assert::logicalOr(
957 $providers = array_filter(
959 $this->preauthMocks, $this->primaryauthMocks, $this->secondaryauthMocks
962 return is_callable( [ $p,
'expects' ] );
965 foreach ( $providers
as $p ) {
966 $p->postCalled =
false;
967 $p->expects( $this->atMost( 1 ) )->method(
'postAuthentication' )
969 if (
$user !==
null ) {
971 $this->assertSame(
'UTSysop',
$user->getName() );
974 $this->assertThat(
$response->status, $constraint );
979 $session = $this->
request->getSession();
980 $session->setRememberUser( !
$req->rememberMe );
986 $this->
hook(
'UserLoggedIn', $this->once() )
987 ->with( $this->callback(
function (
$user )
use ( $id,
$name ) {
991 $this->
hook(
'UserLoggedIn', $this->never() );
996 $response->message->getKey() !==
'authmanager-authn-not-in-progress' &&
997 $response->message->getKey() !==
'authmanager-authn-no-primary'
1000 $this->
hook(
'AuthManagerLoginAuthenticateAudit', $this->once() );
1002 $this->
hook(
'AuthManagerLoginAuthenticateAudit', $this->never() );
1008 $ret = $this->manager->beginAuthentication( [
$req ],
'http://localhost/' );
1010 $ret = $this->manager->continueAuthentication( [
$req ] );
1012 if (
$response instanceof \Exception ) {
1013 $this->fail(
'Expected exception not thrown',
"Response $i" );
1015 }
catch ( \Exception $ex ) {
1016 if ( !
$response instanceof \Exception ) {
1019 $this->assertEquals(
$response->getMessage(), $ex->getMessage(),
"Response $i, exception" );
1020 $this->assertNull( $session->getSecret(
'AuthManager::authnState' ),
1021 "Response $i, exception, session state" );
1022 $this->
unhook(
'UserLoggedIn' );
1023 $this->
unhook(
'AuthManagerLoginAuthenticateAudit' );
1027 $this->
unhook(
'UserLoggedIn' );
1028 $this->
unhook(
'AuthManagerLoginAuthenticateAudit' );
1030 $this->assertSame(
'http://localhost/',
$req->returnToUrl );
1033 $this->assertEquals(
$response,
$ret,
"Response $i, response" );
1035 $this->assertSame( $id, $session->getUser()->getId(),
1036 "Response $i, authn" );
1038 $this->assertSame( 0, $session->getUser()->getId(),
1039 "Response $i, authn" );
1042 $this->assertNull( $session->getSecret(
'AuthManager::authnState' ),
1043 "Response $i, session state" );
1044 foreach ( $providers
as $p ) {
1045 $this->assertSame(
$response->status, $p->postCalled,
1046 "Response $i, post-auth callback called" );
1049 $this->assertNotNull( $session->getSecret(
'AuthManager::authnState' ),
1050 "Response $i, session state" );
1051 foreach (
$ret->neededRequests
as $neededReq ) {
1053 "Response $i, neededRequest action" );
1055 $this->assertEquals(
1056 $ret->neededRequests,
1058 "Response $i, continuation check"
1060 foreach ( $providers
as $p ) {
1061 $this->assertFalse( $p->postCalled,
"Response $i, post-auth callback not called" );
1065 $state = $session->getSecret(
'AuthManager::authnState' );
1066 $maybeLink = isset( $state[
'maybeLink'] ) ? $state[
'maybeLink'] : [];
1068 $this->assertEquals(
1071 "Response $i, maybeLink"
1074 $this->assertEquals( [], $maybeLink,
"Response $i, maybeLink" );
1079 $this->assertSame(
$req->rememberMe, $session->shouldRememberUser(),
1080 'rememberMe checkbox had effect' );
1082 $this->assertNotSame(
$req->rememberMe, $session->shouldRememberUser(),
1083 'rememberMe checkbox wasn\'t applied' );
1092 $req->foobar =
'baz';
1094 $this->
message(
'authmanager-authn-no-local-user' )
1096 $restartResponse->neededRequests = [ $rememberReq ];
1099 $restartResponse2Pass->linkRequest =
$req;
1101 $this->
message(
'authmanager-authn-no-local-user-link' )
1104 null, [
$req->getUniqueId() =>
$req ]
1107 $restartResponse2->neededRequests = [ $rememberReq, $restartResponse2->createRequest ];
1109 $userName =
'UTSysop';
1112 'Failure in pre-auth' => [
1119 $this->
message(
'authmanager-authn-not-in-progress' )
1123 'Failure in primary' => [
1131 'All primary abstain' => [
1141 'Primary UI, then redirect, then fail' => [
1151 'Primary redirect, then abstain' => [
1155 [
$req ],
'/foo.html', [
'foo' =>
'bar' ]
1162 new \DomainException(
1163 'MockPrimaryAuthenticationProvider::continuePrimaryAuthentication() returned ABSTAIN'
1167 'Primary UI, then pass with no local user' => [
1179 'Primary UI, then pass with no local user (link type)' => [
1183 $restartResponse2Pass,
1192 'Primary pass with invalid username' => [
1199 new \DomainException(
'MockPrimaryAuthenticationProvider returned an invalid username: <>' ),
1202 'Secondary fail' => [
1212 'Secondary UI, then abstain' => [
1226 'Secondary pass' => [
1249 $mock1->expects( $this->
any() )->method(
'getUniqueId' )
1250 ->will( $this->returnValue(
'primary1' ) );
1251 $mock1->expects( $this->
any() )->method(
'testUserExists' )
1252 ->with( $this->equalTo(
'UTSysop' ) )
1253 ->will( $this->returnValue( $primary1Exists ) );
1255 $mock2->expects( $this->
any() )->method(
'getUniqueId' )
1256 ->will( $this->returnValue(
'primary2' ) );
1257 $mock2->expects( $this->
any() )->method(
'testUserExists' )
1258 ->with( $this->equalTo(
'UTSysop' ) )
1259 ->will( $this->returnValue( $primary2Exists ) );
1260 $this->primaryauthMocks = [ $mock1, $mock2 ];
1263 $this->assertSame( $expect, $this->manager->userExists(
'UTSysop' ) );
1268 [
false,
false,
false ],
1269 [
true,
false,
true ],
1270 [
false,
true,
true ],
1271 [
true,
true,
true ],
1285 $mock1->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'1' ) );
1286 $mock1->expects( $this->
any() )->method(
'providerAllowsAuthenticationDataChange' )
1287 ->with( $this->equalTo(
$req ) )
1288 ->will( $this->returnValue( $primaryReturn ) );
1290 $mock2->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'2' ) );
1291 $mock2->expects( $this->
any() )->method(
'providerAllowsAuthenticationDataChange' )
1292 ->with( $this->equalTo(
$req ) )
1293 ->will( $this->returnValue( $secondaryReturn ) );
1295 $this->primaryauthMocks = [ $mock1 ];
1296 $this->secondaryauthMocks = [ $mock2 ];
1298 $this->assertEquals( $expect, $this->manager->allowsAuthenticationDataChange(
$req ) );
1303 $ignored->warning(
'authmanager-change-not-supported' );
1306 $okFromPrimary->warning(
'warning-from-primary' );
1308 $okFromSecondary->warning(
'warning-from-secondary' );
1356 $req->username =
'UTSysop';
1359 $mock1->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'1' ) );
1360 $mock1->expects( $this->once() )->method(
'providerChangeAuthenticationData' )
1361 ->with( $this->equalTo(
$req ) );
1363 $mock2->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'2' ) );
1364 $mock2->expects( $this->once() )->method(
'providerChangeAuthenticationData' )
1365 ->with( $this->equalTo(
$req ) );
1367 $this->primaryauthMocks = [ $mock1, $mock2 ];
1369 $this->logger->setCollect(
true );
1370 $this->manager->changeAuthenticationData(
$req );
1371 $this->assertSame( [
1372 [ LogLevel::INFO,
'Changing authentication data for {user} class {what}' ],
1373 ], $this->logger->getBuffer() );
1383 foreach ( $types
as $type => $can ) {
1385 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
$type ) );
1386 $mock->expects( $this->
any() )->method(
'accountCreationType' )
1387 ->will( $this->returnValue(
$type ) );
1388 $this->primaryauthMocks = [ $mock ];
1390 $this->assertSame( $can, $this->manager->canCreateAccounts(),
$type );
1401 $wgGroupPermissions[
'*'][
'createaccount'] =
true;
1402 $this->assertEquals(
1404 $this->manager->checkAccountCreatePermissions(
new \
User )
1407 $readOnlyMode = \MediaWiki\MediaWikiServices::getInstance()->getReadOnlyMode();
1408 $readOnlyMode->setReason(
'Because' );
1409 $this->assertEquals(
1411 $this->manager->checkAccountCreatePermissions(
new \
User )
1413 $readOnlyMode->setReason(
false );
1416 $status = $this->manager->checkAccountCreatePermissions(
new \
User );
1417 $this->assertFalse(
$status->isOK() );
1418 $this->assertTrue(
$status->hasMessage(
'badaccess-groups' ) );
1422 if (
$user->getID() == 0 ) {
1423 $user->addToDatabase();
1425 $user->saveSettings();
1430 $oldBlock->delete();
1433 'address' =>
'UTBlockee',
1434 'user' =>
$user->getID(),
1436 'reason' => __METHOD__,
1437 'expiry' => time() + 100500,
1438 'createAccount' =>
true,
1440 $block = new \Block( $blockOptions );
1442 $status = $this->manager->checkAccountCreatePermissions(
$user );
1443 $this->assertFalse(
$status->isOK() );
1444 $this->assertTrue(
$status->hasMessage(
'cantcreateaccount-text' ) );
1447 'address' =>
'127.0.0.0/24',
1449 'reason' => __METHOD__,
1450 'expiry' => time() + 100500,
1451 'createAccount' =>
true,
1453 $block = new \Block( $blockOptions );
1455 $scopeVariable =
new ScopedCallback( [ $block,
'delete' ] );
1456 $status = $this->manager->checkAccountCreatePermissions(
new \
User );
1457 $this->assertFalse(
$status->isOK() );
1458 $this->assertTrue(
$status->hasMessage(
'cantcreateaccount-range-text' ) );
1459 ScopedCallback::consume( $scopeVariable );
1462 'wgEnableDnsBlacklist' =>
true,
1463 'wgDnsBlacklistUrls' => [
1464 'local.wmftest.net',
1466 'wgProxyWhitelist' => [],
1468 $status = $this->manager->checkAccountCreatePermissions(
new \
User );
1469 $this->assertFalse(
$status->isOK() );
1470 $this->assertTrue(
$status->hasMessage(
'sorbs_create_account_reason' ) );
1471 $this->
setMwGlobals(
'wgProxyWhitelist', [
'127.0.0.1' ] );
1472 $status = $this->manager->checkAccountCreatePermissions(
new \
User );
1473 $this->assertTrue(
$status->isGood() );
1483 $username =
"UTAuthManagerTestAccountCreation" . $uniq . ++$i;
1492 $this->assertEquals(
1494 $this->manager->canCreateAccount(
$username )
1498 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
1499 $mock->expects( $this->
any() )->method(
'accountCreationType' )
1501 $mock->expects( $this->
any() )->method(
'testUserExists' )->will( $this->returnValue(
true ) );
1502 $mock->expects( $this->
any() )->method(
'testUserForCreation' )
1504 $this->primaryauthMocks = [ $mock ];
1507 $this->assertEquals(
1509 $this->manager->canCreateAccount(
$username )
1513 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
1514 $mock->expects( $this->
any() )->method(
'accountCreationType' )
1516 $mock->expects( $this->
any() )->method(
'testUserExists' )->will( $this->returnValue(
false ) );
1517 $mock->expects( $this->
any() )->method(
'testUserForCreation' )
1519 $this->primaryauthMocks = [ $mock ];
1522 $this->assertEquals(
1524 $this->manager->canCreateAccount(
$username .
'<>' )
1527 $this->assertEquals(
1529 $this->manager->canCreateAccount(
'UTSysop' )
1532 $this->assertEquals(
1534 $this->manager->canCreateAccount(
$username )
1538 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
1539 $mock->expects( $this->
any() )->method(
'accountCreationType' )
1541 $mock->expects( $this->
any() )->method(
'testUserExists' )->will( $this->returnValue(
false ) );
1542 $mock->expects( $this->
any() )->method(
'testUserForCreation' )
1544 $this->primaryauthMocks = [ $mock ];
1547 $this->assertEquals(
1549 $this->manager->canCreateAccount(
$username )
1556 $this->logger = new \TestLogger(
false,
function ( $message, $level ) {
1557 return $level === LogLevel::DEBUG ? null : $message;
1561 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState',
'test' );
1562 $this->
hook(
'LocalUserCreated', $this->never() );
1564 $this->manager->beginAccountCreation(
1565 $creator, [],
'http://localhost/'
1567 $this->fail(
'Expected exception not thrown' );
1568 }
catch ( \LogicException $ex ) {
1569 $this->assertEquals(
'Account creation is not possible', $ex->getMessage() );
1571 $this->
unhook(
'LocalUserCreated' );
1573 $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' )
1577 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
1578 $mock->expects( $this->
any() )->method(
'accountCreationType' )
1580 $mock->expects( $this->
any() )->method(
'testUserExists' )->will( $this->returnValue(
true ) );
1581 $mock->expects( $this->
any() )->method(
'testUserForCreation' )
1583 $this->primaryauthMocks = [ $mock ];
1586 $this->
hook(
'LocalUserCreated', $this->never() );
1587 $ret = $this->manager->beginAccountCreation( $creator, [],
'http://localhost/' );
1588 $this->
unhook(
'LocalUserCreated' );
1590 $this->assertSame(
'noname',
$ret->message->getKey() );
1592 $this->
hook(
'LocalUserCreated', $this->never() );
1595 $userReq2->username = $userReq->username .
'X';
1596 $ret = $this->manager->beginAccountCreation(
1597 $creator, [ $userReq, $userReq2 ],
'http://localhost/'
1599 $this->
unhook(
'LocalUserCreated' );
1601 $this->assertSame(
'noname',
$ret->message->getKey() );
1603 $readOnlyMode = \MediaWiki\MediaWikiServices::getInstance()->getReadOnlyMode();
1604 $readOnlyMode->setReason(
'Because' );
1605 $this->
hook(
'LocalUserCreated', $this->never() );
1607 $ret = $this->manager->beginAccountCreation( $creator, [ $userReq ],
'http://localhost/' );
1608 $this->
unhook(
'LocalUserCreated' );
1610 $this->assertSame(
'readonlytext',
$ret->message->getKey() );
1611 $this->assertSame( [
'Because' ],
$ret->message->getParams() );
1612 $readOnlyMode->setReason(
false );
1614 $this->
hook(
'LocalUserCreated', $this->never() );
1616 $ret = $this->manager->beginAccountCreation( $creator, [ $userReq ],
'http://localhost/' );
1617 $this->
unhook(
'LocalUserCreated' );
1619 $this->assertSame(
'userexists',
$ret->message->getKey() );
1622 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
1623 $mock->expects( $this->
any() )->method(
'accountCreationType' )
1625 $mock->expects( $this->
any() )->method(
'testUserExists' )->will( $this->returnValue(
false ) );
1626 $mock->expects( $this->
any() )->method(
'testUserForCreation' )
1628 $this->primaryauthMocks = [ $mock ];
1631 $this->
hook(
'LocalUserCreated', $this->never() );
1633 $ret = $this->manager->beginAccountCreation( $creator, [ $userReq ],
'http://localhost/' );
1634 $this->
unhook(
'LocalUserCreated' );
1636 $this->assertSame(
'fail',
$ret->message->getKey() );
1639 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
1640 $mock->expects( $this->
any() )->method(
'accountCreationType' )
1642 $mock->expects( $this->
any() )->method(
'testUserExists' )->will( $this->returnValue(
false ) );
1643 $mock->expects( $this->
any() )->method(
'testUserForCreation' )
1645 $this->primaryauthMocks = [ $mock ];
1648 $this->
hook(
'LocalUserCreated', $this->never() );
1650 $ret = $this->manager->beginAccountCreation( $creator, [ $userReq ],
'http://localhost/' );
1651 $this->
unhook(
'LocalUserCreated' );
1653 $this->assertSame(
'noname',
$ret->message->getKey() );
1655 $this->
hook(
'LocalUserCreated', $this->never() );
1656 $userReq->username = $creator->getName();
1657 $ret = $this->manager->beginAccountCreation( $creator, [ $userReq ],
'http://localhost/' );
1658 $this->
unhook(
'LocalUserCreated' );
1660 $this->assertSame(
'userexists',
$ret->message->getKey() );
1663 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
1664 $mock->expects( $this->
any() )->method(
'accountCreationType' )
1666 $mock->expects( $this->
any() )->method(
'testUserExists' )->will( $this->returnValue(
false ) );
1667 $mock->expects( $this->
any() )->method(
'testUserForCreation' )
1669 $mock->expects( $this->
any() )->method(
'testForAccountCreation' )
1671 $this->primaryauthMocks = [ $mock ];
1675 ->setMethods( [
'populateUser' ] )
1677 $req->expects( $this->
any() )->method(
'populateUser' )
1680 $ret = $this->manager->beginAccountCreation(
1681 $creator, [ $userReq,
$req ],
'http://localhost/'
1684 $this->assertSame(
'populatefail',
$ret->message->getKey() );
1689 $ret = $this->manager->beginAccountCreation(
1690 $creator, [ $userReq,
$req ],
'http://localhost/'
1693 $this->assertSame(
'fail',
$ret->message->getKey() );
1695 $this->manager->beginAccountCreation(
1699 $this->assertSame(
'fail',
$ret->message->getKey() );
1705 $this->logger = new \TestLogger(
false,
function ( $message, $level ) {
1706 return $level === LogLevel::DEBUG ? null : $message;
1717 'primaryResponse' =>
null,
1719 'ranPreTests' =>
true,
1722 $this->
hook(
'LocalUserCreated', $this->never() );
1724 $this->manager->continueAccountCreation( [] );
1725 $this->fail(
'Expected exception not thrown' );
1726 }
catch ( \LogicException $ex ) {
1727 $this->assertEquals(
'Account creation is not possible', $ex->getMessage() );
1729 $this->
unhook(
'LocalUserCreated' );
1732 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
1733 $mock->expects( $this->
any() )->method(
'accountCreationType' )
1735 $mock->expects( $this->
any() )->method(
'testUserExists' )->will( $this->returnValue(
false ) );
1736 $mock->expects( $this->
any() )->method(
'beginPrimaryAccountCreation' )->will(
1739 $this->primaryauthMocks = [ $mock ];
1742 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState',
null );
1743 $this->
hook(
'LocalUserCreated', $this->never() );
1744 $ret = $this->manager->continueAccountCreation( [] );
1745 $this->
unhook(
'LocalUserCreated' );
1747 $this->assertSame(
'authmanager-create-not-in-progress',
$ret->message->getKey() );
1749 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState',
1750 [
'username' =>
"$username<>" ] + $session );
1751 $this->
hook(
'LocalUserCreated', $this->never() );
1752 $ret = $this->manager->continueAccountCreation( [] );
1753 $this->
unhook(
'LocalUserCreated' );
1755 $this->assertSame(
'noname',
$ret->message->getKey() );
1757 $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' )
1760 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState', $session );
1761 $this->
hook(
'LocalUserCreated', $this->never() );
1764 $ret = $this->manager->continueAccountCreation( [] );
1766 $this->
unhook(
'LocalUserCreated' );
1768 $this->assertSame(
'usernameinprogress',
$ret->message->getKey() );
1772 $session, $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' )
1775 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState',
1776 [
'username' => $creator->getName() ] + $session );
1777 $readOnlyMode = \MediaWiki\MediaWikiServices::getInstance()->getReadOnlyMode();
1778 $readOnlyMode->setReason(
'Because' );
1779 $this->
hook(
'LocalUserCreated', $this->never() );
1780 $ret = $this->manager->continueAccountCreation( [] );
1781 $this->
unhook(
'LocalUserCreated' );
1783 $this->assertSame(
'readonlytext',
$ret->message->getKey() );
1784 $this->assertSame( [
'Because' ],
$ret->message->getParams() );
1785 $readOnlyMode->setReason(
false );
1787 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState',
1788 [
'username' => $creator->getName() ] + $session );
1789 $this->
hook(
'LocalUserCreated', $this->never() );
1790 $ret = $this->manager->continueAccountCreation( [] );
1791 $this->
unhook(
'LocalUserCreated' );
1793 $this->assertSame(
'userexists',
$ret->message->getKey() );
1795 $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' )
1798 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState',
1799 [
'userid' => $creator->getId() ] + $session );
1800 $this->
hook(
'LocalUserCreated', $this->never() );
1802 $ret = $this->manager->continueAccountCreation( [] );
1803 $this->fail(
'Expected exception not thrown' );
1804 }
catch ( \UnexpectedValueException $ex ) {
1805 $this->assertEquals(
"User \"{$username}\" should exist now, but doesn't!", $ex->getMessage() );
1807 $this->
unhook(
'LocalUserCreated' );
1809 $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' )
1812 $id = $creator->getId();
1813 $name = $creator->getName();
1814 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState',
1815 [
'username' =>
$name,
'userid' => $id + 1 ] + $session );
1816 $this->
hook(
'LocalUserCreated', $this->never() );
1818 $ret = $this->manager->continueAccountCreation( [] );
1819 $this->fail(
'Expected exception not thrown' );
1820 }
catch ( \UnexpectedValueException $ex ) {
1821 $this->assertEquals(
1822 "User \"{$name}\" exists, but ID $id != " . ( $id + 1 ) .
'!', $ex->getMessage()
1825 $this->
unhook(
'LocalUserCreated' );
1827 $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' )
1831 ->setMethods( [
'populateUser' ] )
1833 $req->expects( $this->
any() )->method(
'populateUser' )
1835 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState',
1836 [
'reqs' => [ $req ] ] + $session );
1837 $ret = $this->manager->continueAccountCreation( [] );
1839 $this->assertSame(
'populatefail',
$ret->message->getKey() );
1841 $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' )
1855 StatusValue $preTest, $primaryTest, $secondaryTest,
1856 array $primaryResponses,
array $secondaryResponses,
array $managerResponses
1865 $req->preTest = $preTest;
1866 $req->primaryTest = $primaryTest;
1867 $req->secondaryTest = $secondaryTest;
1868 $req->primary = $primaryResponses;
1869 $req->secondary = $secondaryResponses;
1871 foreach ( [
'pre',
'primary',
'secondary' ]
as $key ) {
1872 $class = ucfirst( $key ) .
'AuthenticationProvider';
1873 $mocks[$key] = $this->getMockForAbstractClass(
1874 "MediaWiki\\Auth\\$class", [],
"Mock$class"
1876 $mocks[$key]->expects( $this->
any() )->method(
'getUniqueId' )
1877 ->will( $this->returnValue( $key ) );
1878 $mocks[$key]->expects( $this->
any() )->method(
'testUserForCreation' )
1880 $mocks[$key]->expects( $this->
any() )->method(
'testForAccountCreation' )
1881 ->will( $this->returnCallback(
1882 function (
$user, $creatorIn, $reqs )
1886 $this->assertSame( $creator->getId(), $creatorIn->getId() );
1887 $this->assertSame( $creator->getName(), $creatorIn->getName() );
1889 foreach ( $reqs
as $r ) {
1890 $this->assertSame(
$username, $r->username );
1891 $foundReq = $foundReq || get_class( $r ) === get_class(
$req );
1893 $this->assertTrue( $foundReq,
'$reqs contains $req' );
1899 for ( $i = 2; $i <= 3; $i++ ) {
1900 $mocks[$key . $i] = $this->getMockForAbstractClass(
"MediaWiki\\Auth\\$class" );
1901 $mocks[$key . $i]->expects( $this->
any() )->method(
'getUniqueId' )
1902 ->will( $this->returnValue( $key . $i ) );
1903 $mocks[$key . $i]->expects( $this->
any() )->method(
'testUserForCreation' )
1905 $mocks[$key . $i]->expects( $this->atMost( 1 ) )->method(
'testForAccountCreation' )
1910 $mocks[
'primary']->expects( $this->
any() )->method(
'accountCreationType' )
1912 $mocks[
'primary']->expects( $this->
any() )->method(
'testUserExists' )
1913 ->will( $this->returnValue(
false ) );
1917 $this->assertSame(
'UTSysop', $creator->getName() );
1919 foreach ( $reqs
as $r ) {
1920 $this->assertSame(
$username, $r->username );
1921 $foundReq = $foundReq || get_class( $r ) === get_class(
$req );
1923 $this->assertTrue( $foundReq,
'$reqs contains $req' );
1924 return array_shift(
$req->primary );
1926 $mocks[
'primary']->expects( $this->exactly( min( 1, $ct ) ) )
1927 ->method(
'beginPrimaryAccountCreation' )
1928 ->will( $callback );
1929 $mocks[
'primary']->expects( $this->exactly( max( 0, $ct - 1 ) ) )
1930 ->method(
'continuePrimaryAccountCreation' )
1931 ->will( $callback );
1936 $this->assertSame(
'UTSysop', $creator->getName() );
1938 foreach ( $reqs
as $r ) {
1939 $this->assertSame(
$username, $r->username );
1940 $foundReq = $foundReq || get_class( $r ) === get_class(
$req );
1942 $this->assertTrue( $foundReq,
'$reqs contains $req' );
1943 return array_shift(
$req->secondary );
1945 $mocks[
'secondary']->expects( $this->exactly( min( 1, $ct ) ) )
1946 ->method(
'beginSecondaryAccountCreation' )
1947 ->will( $callback );
1948 $mocks[
'secondary']->expects( $this->exactly( max( 0, $ct - 1 ) ) )
1949 ->method(
'continueSecondaryAccountCreation' )
1950 ->will( $callback );
1953 $mocks[
'primary2']->expects( $this->
any() )->method(
'accountCreationType' )
1955 $mocks[
'primary2']->expects( $this->
any() )->method(
'testUserExists' )
1956 ->will( $this->returnValue(
false ) );
1957 $mocks[
'primary2']->expects( $this->atMost( 1 ) )->method(
'beginPrimaryAccountCreation' )
1958 ->will( $this->returnValue( $abstain ) );
1959 $mocks[
'primary2']->expects( $this->never() )->method(
'continuePrimaryAccountCreation' );
1960 $mocks[
'primary3']->expects( $this->
any() )->method(
'accountCreationType' )
1962 $mocks[
'primary3']->expects( $this->
any() )->method(
'testUserExists' )
1963 ->will( $this->returnValue(
false ) );
1964 $mocks[
'primary3']->expects( $this->never() )->method(
'beginPrimaryAccountCreation' );
1965 $mocks[
'primary3']->expects( $this->never() )->method(
'continuePrimaryAccountCreation' );
1966 $mocks[
'secondary2']->expects( $this->atMost( 1 ) )
1967 ->method(
'beginSecondaryAccountCreation' )
1968 ->will( $this->returnValue( $abstain ) );
1969 $mocks[
'secondary2']->expects( $this->never() )->method(
'continueSecondaryAccountCreation' );
1970 $mocks[
'secondary3']->expects( $this->atMost( 1 ) )
1971 ->method(
'beginSecondaryAccountCreation' )
1972 ->will( $this->returnValue( $abstain ) );
1973 $mocks[
'secondary3']->expects( $this->never() )->method(
'continueSecondaryAccountCreation' );
1975 $this->preauthMocks = [ $mocks[
'pre'], $mocks[
'pre2'] ];
1976 $this->primaryauthMocks = [ $mocks[
'primary3'], $mocks[
'primary'], $mocks[
'primary2'] ];
1977 $this->secondaryauthMocks = [
1978 $mocks[
'secondary3'], $mocks[
'secondary'], $mocks[
'secondary2']
1981 $this->logger = new \TestLogger(
true,
function ( $message, $level ) {
1982 return $level === LogLevel::DEBUG ? null : $message;
1987 $constraint = \PHPUnit_Framework_Assert::logicalOr(
1991 $providers = array_merge(
1992 $this->preauthMocks, $this->primaryauthMocks, $this->secondaryauthMocks
1994 foreach ( $providers
as $p ) {
1995 $p->postCalled =
false;
1996 $p->expects( $this->atMost( 1 ) )->method(
'postAccountCreation' )
2002 $this->assertSame(
'UTSysop', $creator->getName() );
2004 $this->assertThat(
$response->status, $constraint );
2011 $maxLogId = $dbw->selectField(
'logging',
'MAX(log_id)', [
'log_type' =>
'newusers' ] );
2015 foreach ( $managerResponses
as $i =>
$response ) {
2018 if ( $i ===
'created' ) {
2020 $this->
hook(
'LocalUserCreated', $this->once() )
2025 $this->equalTo(
false )
2027 $expectLog[] = [ LogLevel::INFO,
"Creating user {user} during account creation" ];
2029 $this->
hook(
'LocalUserCreated', $this->never() );
2037 $ret = $this->manager->beginAccountCreation(
2038 $creator, [ $userReq,
$req ],
'http://localhost/'
2041 $ret = $this->manager->continueAccountCreation( [
$req ] );
2043 if (
$response instanceof \Exception ) {
2044 $this->fail(
'Expected exception not thrown',
"Response $i" );
2046 }
catch ( \Exception $ex ) {
2047 if ( !
$response instanceof \Exception ) {
2050 $this->assertEquals(
$response->getMessage(), $ex->getMessage(),
"Response $i, exception" );
2052 $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' ),
2053 "Response $i, exception, session state"
2055 $this->
unhook(
'LocalUserCreated' );
2059 $this->
unhook(
'LocalUserCreated' );
2061 $this->assertSame(
'http://localhost/',
$req->returnToUrl );
2064 $this->assertNotNull(
$ret->loginRequest,
"Response $i, login marker" );
2065 $this->assertContains(
2066 $ret->loginRequest, $this->managerPriv->createdAccountAuthenticationRequests,
2067 "Response $i, login marker"
2072 "MediaWiki\Auth\AuthManager::continueAccountCreation: Account creation succeeded for {user}"
2080 $this->assertNull(
$ret->loginRequest,
"Response $i, login marker" );
2081 $this->assertSame( [], $this->managerPriv->createdAccountAuthenticationRequests,
2082 "Response $i, login marker" );
2085 $this->assertEquals(
$response,
$ret,
"Response $i, response" );
2088 $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' ),
2089 "Response $i, session state"
2091 foreach ( $providers
as $p ) {
2092 $this->assertSame(
$response->status, $p->postCalled,
2093 "Response $i, post-auth callback called" );
2096 $this->assertNotNull(
2097 $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' ),
2098 "Response $i, session state"
2100 foreach (
$ret->neededRequests
as $neededReq ) {
2102 "Response $i, neededRequest action" );
2104 $this->assertEquals(
2105 $ret->neededRequests,
2107 "Response $i, continuation check"
2109 foreach ( $providers
as $p ) {
2110 $this->assertFalse( $p->postCalled,
"Response $i, post-auth callback not called" );
2123 $this->assertSame( $expectLog, $this->logger->getBuffer() );
2127 $dbw->selectField(
'logging',
'MAX(log_id)', [
'log_type' =>
'newusers' ] )
2136 'Pre-creation test fail in pre' => [
2144 'Pre-creation test fail in primary' => [
2152 'Pre-creation test fail in secondary' => [
2160 'Failure in primary' => [
2161 $good, $good, $good,
2168 'All primary abstain' => [
2169 $good, $good, $good,
2178 'Primary UI, then redirect, then fail' => [
2179 $good, $good, $good,
2188 'Primary redirect, then abstain' => [
2189 $good, $good, $good,
2192 [
$req ],
'/foo.html', [
'foo' =>
'bar' ]
2199 new \DomainException(
2200 'MockPrimaryAuthenticationProvider::continuePrimaryAccountCreation() returned ABSTAIN'
2204 'Primary UI, then pass; secondary abstain' => [
2205 $good, $good, $good,
2218 'Primary pass; secondary UI then pass' => [
2219 $good, $good, $good,
2232 'Primary pass; secondary fail' => [
2233 $good, $good, $good,
2241 'created' => new \DomainException(
2242 'MockSecondaryAuthenticationProvider::beginSecondaryAccountCreation() returned FAIL. ' .
2243 'Secondary providers are not allowed to fail account creation, ' .
2244 'that should have been done via testForAccountCreation().'
2263 $mock = $this->getMockForAbstractClass(
2266 $mock->expects( $this->
any() )->method(
'getUniqueId' )
2267 ->will( $this->returnValue(
'primary' ) );
2268 $mock->expects( $this->
any() )->method(
'testUserForCreation' )
2270 $mock->expects( $this->
any() )->method(
'testForAccountCreation' )
2272 $mock->expects( $this->
any() )->method(
'accountCreationType' )
2274 $mock->expects( $this->
any() )->method(
'testUserExists' )
2275 ->will( $this->returnValue(
false ) );
2276 $mock->expects( $this->
any() )->method(
'beginPrimaryAccountCreation' )
2278 $mock->expects( $this->
any() )->method(
'finishAccountCreation' )
2279 ->will( $this->returnValue( $logSubtype ) );
2281 $this->primaryauthMocks = [ $mock ];
2283 $this->logger->setCollect(
true );
2285 $this->config->set(
'NewUserLog',
true );
2288 $maxLogId = $dbw->selectField(
'logging',
'MAX(log_id)', [
'log_type' =>
'newusers' ] );
2293 $reasonReq->reason = $this->toString();
2294 $ret = $this->manager->beginAccountCreation(
2295 $creator, [ $userReq, $reasonReq ],
'http://localhost/'
2301 $this->assertNotEquals( 0,
$user->getId(),
'sanity check' );
2302 $this->assertNotEquals( $creator->getId(),
$user->getId(),
'sanity check' );
2305 $rows = iterator_to_array( $dbw->select(
2309 'log_id > ' . (
int)$maxLogId,
2310 'log_type' =>
'newusers'
2316 $this->assertCount( 1,
$rows );
2319 $this->assertSame( $logSubtype ?: ( $isAnon ?
'create' :
'create2' ), $entry->getSubtype() );
2321 $isAnon ?
$user->getId() : $creator->getId(),
2322 $entry->getPerformer()->getId()
2325 $isAnon ?
$user->getName() : $creator->getName(),
2326 $entry->getPerformer()->getName()
2328 $this->assertSame(
$user->getUserPage()->getFullText(), $entry->getTarget()->getFullText() );
2329 $this->assertSame( [
'4::userid' =>
$user->getId() ], $entry->getParameters() );
2330 $this->assertSame( $this->toString(), $entry->getComment() );
2338 [
false,
'byemail' ],
2349 $workaroundPHPUnitBug =
false;
2355 $wgGroupPermissions[
'*'][
'createaccount'] =
true;
2359 $this->
setMwGlobals( [
'wgMainCacheType' => __METHOD__ ] );
2363 foreach ( [
'pre',
'primary',
'secondary' ]
as $key ) {
2364 $class = ucfirst( $key ) .
'AuthenticationProvider';
2365 $mocks[$key] = $this->getMockForAbstractClass(
"MediaWiki\\Auth\\$class" );
2366 $mocks[$key]->expects( $this->
any() )->method(
'getUniqueId' )
2367 ->will( $this->returnValue( $key ) );
2371 $callback = $this->callback(
function (
$user )
use ( &
$username, &$workaroundPHPUnitBug ) {
2375 $mocks[
'pre']->expects( $this->exactly( 12 ) )->method(
'testUserForCreation' )
2377 ->will( $this->onConsecutiveCalls(
2387 $mocks[
'primary']->expects( $this->
any() )->method(
'accountCreationType' )
2389 $mocks[
'primary']->expects( $this->
any() )->method(
'testUserExists' )
2390 ->will( $this->returnValue(
true ) );
2391 $mocks[
'primary']->expects( $this->exactly( 9 ) )->method(
'testUserForCreation' )
2393 ->will( $this->onConsecutiveCalls(
2401 $mocks[
'primary']->expects( $this->exactly( 3 ) )->method(
'autoCreatedAccount' )
2404 $mocks[
'secondary']->expects( $this->exactly( 8 ) )->method(
'testUserForCreation' )
2406 ->will( $this->onConsecutiveCalls(
2414 $mocks[
'secondary']->expects( $this->exactly( 3 ) )->method(
'autoCreatedAccount' )
2417 $this->preauthMocks = [ $mocks[
'pre'] ];
2418 $this->primaryauthMocks = [ $mocks[
'primary'] ];
2419 $this->secondaryauthMocks = [ $mocks[
'secondary'] ];
2421 $session = $this->
request->getSession();
2423 $logger = new \TestLogger(
true,
function ( $m ) {
2424 $m = str_replace(
'MediaWiki\\Auth\\AuthManager::autoCreateUser: ',
'', $m );
2427 $this->manager->setLogger(
$logger );
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() );
2440 $this->
hook(
'LocalUserCreated', $this->never() );
2442 $this->
unhook(
'LocalUserCreated' );
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' ],
2456 $this->
hook(
'LocalUserCreated', $this->never() );
2458 $this->
unhook(
'LocalUserCreated' );
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' ],
2472 $readOnlyMode = \MediaWiki\MediaWikiServices::getInstance()->getReadOnlyMode();
2473 $readOnlyMode->setReason(
'Because' );
2475 $this->
hook(
'LocalUserCreated', $this->never() );
2477 $this->
unhook(
'LocalUserCreated' );
2479 $this->assertEquals( 0,
$user->getId() );
2481 $this->assertEquals( 0, $session->getUser()->getId() );
2482 $this->assertSame( [
2483 [ LogLevel::DEBUG,
'denied by wfReadOnly(): {reason}' ],
2486 $readOnlyMode->setReason(
false );
2490 $session->set(
'AuthManager::AutoCreateBlacklist',
'test' );
2492 $this->
hook(
'LocalUserCreated', $this->never() );
2494 $this->
unhook(
'LocalUserCreated' );
2496 $this->assertEquals( 0,
$user->getId() );
2498 $this->assertEquals( 0, $session->getUser()->getId() );
2499 $this->assertSame( [
2500 [ LogLevel::DEBUG,
'blacklisted in session {sessionid}' ],
2507 $this->
hook(
'LocalUserCreated', $this->never() );
2509 $this->
unhook(
'LocalUserCreated' );
2511 $this->assertEquals( 0,
$user->getId() );
2513 $this->assertEquals( 0, $session->getUser()->getId() );
2514 $this->assertSame( [
2515 [ LogLevel::DEBUG,
'blacklisted in session {sessionid}' ],
2522 $this->
hook(
'LocalUserCreated', $this->never() );
2524 $this->
unhook(
'LocalUserCreated' );
2526 $this->assertEquals( 0,
$user->getId() );
2528 $this->assertEquals( 0, $session->getUser()->getId() );
2529 $this->assertSame( [
2530 [ LogLevel::DEBUG,
'name "{username}" is not creatable' ],
2533 $this->assertSame(
'noname', $session->get(
'AuthManager::AutoCreateBlacklist' ) );
2540 $this->
hook(
'LocalUserCreated', $this->never() );
2542 $this->
unhook(
'LocalUserCreated' );
2544 $this->assertEquals( 0,
$user->getId() );
2546 $this->assertEquals( 0, $session->getUser()->getId() );
2547 $this->assertSame( [
2548 [ LogLevel::DEBUG,
'IP lacks the ability to create or autocreate accounts' ],
2552 'authmanager-autocreate-noperm', $session->get(
'AuthManager::AutoCreateBlacklist' )
2561 $this->
hook(
'LocalUserCreated', $this->never() );
2563 $this->
unhook(
'LocalUserCreated' );
2570 $this->
hook(
'LocalUserCreated', $this->never() );
2572 $this->
unhook(
'LocalUserCreated' );
2579 $this->
hook(
'LocalUserCreated', $this->never() );
2584 $this->
unhook(
'LocalUserCreated' );
2586 $this->assertEquals( 0,
$user->getId() );
2588 $this->assertEquals( 0, $session->getUser()->getId() );
2589 $this->assertSame( [
2590 [ LogLevel::DEBUG,
'Could not acquire account creation lock' ],
2597 $this->
hook(
'LocalUserCreated', $this->never() );
2599 $this->
unhook(
'LocalUserCreated' );
2601 $this->assertEquals( 0,
$user->getId() );
2603 $this->assertEquals( 0, $session->getUser()->getId() );
2604 $this->assertSame( [
2605 [ LogLevel::DEBUG,
'Provider denied creation of {username}: {reason}' ],
2608 $this->assertEquals(
2614 $this->
hook(
'LocalUserCreated', $this->never() );
2616 $this->
unhook(
'LocalUserCreated' );
2618 $this->assertEquals( 0,
$user->getId() );
2620 $this->assertEquals( 0, $session->getUser()->getId() );
2621 $this->assertSame( [
2622 [ LogLevel::DEBUG,
'Provider denied creation of {username}: {reason}' ],
2625 $this->assertEquals(
2631 $this->
hook(
'LocalUserCreated', $this->never() );
2633 $this->
unhook(
'LocalUserCreated' );
2635 $this->assertEquals( 0,
$user->getId() );
2637 $this->assertEquals( 0, $session->getUser()->getId() );
2638 $this->assertSame( [
2639 [ LogLevel::DEBUG,
'Provider denied creation of {username}: {reason}' ],
2642 $this->assertEquals(
2649 $cache->set( $backoffKey,
true );
2652 $this->
hook(
'LocalUserCreated', $this->never() );
2654 $this->
unhook(
'LocalUserCreated' );
2656 $this->assertEquals( 0,
$user->getId() );
2658 $this->assertEquals( 0, $session->getUser()->getId() );
2659 $this->assertSame( [
2660 [ LogLevel::DEBUG,
'{username} denied by prior creation attempt failures' ],
2663 $this->assertSame(
null, $session->get(
'AuthManager::AutoCreateBlacklist' ) );
2664 $cache->delete( $backoffKey );
2669 ->setMethods( [
'addToDatabase' ] )->getMock();
2670 $user->expects( $this->once() )->method(
'addToDatabase' )
2675 $this->assertEquals( 0,
$user->getId() );
2677 $this->assertEquals( 0, $session->getUser()->getId() );
2678 $this->assertSame( [
2679 [ LogLevel::INFO,
'creating new user ({username}) - from: {from}' ],
2680 [ LogLevel::ERROR,
'{username} failed with message {msg}' ],
2683 $this->assertSame(
null, $session->get(
'AuthManager::AutoCreateBlacklist' ) );
2688 $this->assertFalse(
$cache->get( $backoffKey ),
'sanity check' );
2691 ->setMethods( [
'addToDatabase' ] )->getMock();
2692 $user->expects( $this->once() )->method(
'addToDatabase' )
2693 ->will( $this->throwException(
new \Exception(
'Excepted' ) ) );
2697 $this->fail(
'Expected exception not thrown' );
2698 }
catch ( \Exception $ex ) {
2699 $this->assertSame(
'Excepted', $ex->getMessage() );
2701 $this->assertEquals( 0,
$user->getId() );
2702 $this->assertEquals( 0, $session->getUser()->getId() );
2703 $this->assertSame( [
2704 [ LogLevel::INFO,
'creating new user ({username}) - from: {from}' ],
2705 [ LogLevel::ERROR,
'{username} failed with exception {exception}' ],
2708 $this->assertSame(
null, $session->get(
'AuthManager::AutoCreateBlacklist' ) );
2709 $this->assertNotEquals(
false,
$cache->get( $backoffKey ) );
2710 $cache->delete( $backoffKey );
2715 ->setMethods( [
'addToDatabase' ] )->getMock();
2716 $user->expects( $this->once() )->method(
'addToDatabase' )
2719 $status = $oldUser->addToDatabase();
2720 $this->assertTrue(
$status->isOK(),
'sanity check' );
2721 $user->setId( $oldUser->getId() );
2722 return \Status::newFatal(
'userexists' );
2727 $expect->warning(
'userexists' );
2728 $this->assertEquals( $expect,
$ret );
2729 $this->assertNotEquals( 0,
$user->getId() );
2731 $this->assertEquals(
$user->getId(), $session->getUser()->getId() );
2732 $this->assertSame( [
2733 [ LogLevel::INFO,
'creating new user ({username}) - from: {from}' ],
2734 [ LogLevel::INFO,
'{username} already exists locally (race)' ],
2737 $this->assertSame(
null, $session->get(
'AuthManager::AutoCreateBlacklist' ) );
2743 $this->
hook(
'AuthPluginAutoCreate', $this->once() )
2744 ->with( $callback );
2746 get_class(
$wgHooks[
'AuthPluginAutoCreate'][0] ) .
'::onAuthPluginAutoCreate)' );
2747 $this->
hook(
'LocalUserCreated', $this->once() )
2748 ->with( $callback, $this->equalTo(
true ) );
2750 $this->
unhook(
'LocalUserCreated' );
2751 $this->
unhook(
'AuthPluginAutoCreate' );
2753 $this->assertNotEquals( 0,
$user->getId() );
2755 $this->assertEquals(
$user->getId(), $session->getUser()->getId() );
2756 $this->assertSame( [
2757 [ LogLevel::INFO,
'creating new user ({username}) - from: {from}' ],
2762 $maxLogId = $dbw->selectField(
'logging',
'MAX(log_id)', [
'log_type' =>
'newusers' ] );
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( 0, $session->getUser()->getId() );
2774 $this->assertSame( [
2775 [ LogLevel::INFO,
'creating new user ({username}) - from: {from}' ],
2780 $dbw->selectField(
'logging',
'MAX(log_id)', [
'log_type' =>
'newusers' ] )
2783 $this->config->set(
'NewUserLog',
true );
2792 $rows = iterator_to_array( $dbw->select(
2796 'log_id > ' . (
int)$maxLogId,
2797 'log_type' =>
'newusers'
2803 $this->assertCount( 1,
$rows );
2806 $this->assertSame(
'autocreate', $entry->getSubtype() );
2807 $this->assertSame(
$user->getId(), $entry->getPerformer()->getId() );
2808 $this->assertSame(
$user->getName(), $entry->getPerformer()->getName() );
2809 $this->assertSame(
$user->getUserPage()->getFullText(), $entry->getTarget()->getFullText() );
2810 $this->assertSame( [
'4::userid' =>
$user->getId() ], $entry->getParameters() );
2812 $workaroundPHPUnitBug =
true;
2822 $makeReq =
function ( $key )
use (
$action ) {
2824 $req->expects( $this->
any() )->method(
'getUniqueId' )
2825 ->will( $this->returnValue( $key ) );
2830 $cmpReqs =
function ( $a, $b ) {
2831 $ret = strcmp( get_class( $a ), get_class( $b ) );
2833 $ret = strcmp( $a->key, $b->key );
2841 foreach ( [
'pre',
'primary',
'secondary' ]
as $key ) {
2842 $class = ucfirst( $key ) .
'AuthenticationProvider';
2843 $mocks[$key] = $this->getMockBuilder(
"MediaWiki\\Auth\\$class" )
2845 'getUniqueId',
'getAuthenticationRequests',
'providerAllowsAuthenticationDataChange',
2847 ->getMockForAbstractClass();
2848 $mocks[$key]->expects( $this->
any() )->method(
'getUniqueId' )
2849 ->will( $this->returnValue( $key ) );
2850 $mocks[$key]->expects( $this->
any() )->method(
'getAuthenticationRequests' )
2851 ->will( $this->returnCallback(
function (
$action )
use ( $key, $makeReq ) {
2852 return [ $makeReq(
"$key-$action" ), $makeReq(
'generic' ) ];
2854 $mocks[$key]->expects( $this->
any() )->method(
'providerAllowsAuthenticationDataChange' )
2855 ->will( $this->returnValue( $good ) );
2864 $class =
'PrimaryAuthenticationProvider';
2865 $mocks[
"primary-$type"] = $this->getMockBuilder(
"MediaWiki\\Auth\\$class" )
2867 'getUniqueId',
'accountCreationType',
'getAuthenticationRequests',
2868 'providerAllowsAuthenticationDataChange',
2870 ->getMockForAbstractClass();
2871 $mocks[
"primary-$type"]->expects( $this->
any() )->method(
'getUniqueId' )
2872 ->will( $this->returnValue(
"primary-$type" ) );
2873 $mocks[
"primary-$type"]->expects( $this->
any() )->method(
'accountCreationType' )
2874 ->will( $this->returnValue(
$type ) );
2875 $mocks[
"primary-$type"]->expects( $this->
any() )->method(
'getAuthenticationRequests' )
2876 ->will( $this->returnCallback(
function (
$action )
use (
$type, $makeReq ) {
2877 return [ $makeReq(
"primary-$type-$action" ), $makeReq(
'generic' ) ];
2879 $mocks[
"primary-$type"]->expects( $this->
any() )
2880 ->method(
'providerAllowsAuthenticationDataChange' )
2881 ->will( $this->returnValue( $good ) );
2882 $this->primaryauthMocks[] = $mocks[
"primary-$type"];
2887 'getUniqueId',
'accountCreationType',
'getAuthenticationRequests',
2888 'providerAllowsAuthenticationDataChange',
2890 ->getMockForAbstractClass();
2891 $mocks[
'primary2']->expects( $this->
any() )->method(
'getUniqueId' )
2892 ->will( $this->returnValue(
'primary2' ) );
2893 $mocks[
'primary2']->expects( $this->
any() )->method(
'accountCreationType' )
2895 $mocks[
'primary2']->expects( $this->
any() )->method(
'getAuthenticationRequests' )
2896 ->will( $this->returnValue( [] ) );
2897 $mocks[
'primary2']->expects( $this->
any() )
2898 ->method(
'providerAllowsAuthenticationDataChange' )
2899 ->will( $this->returnCallback(
function (
$req )
use ( $good ) {
2902 $this->primaryauthMocks[] = $mocks[
'primary2'];
2904 $this->preauthMocks = [ $mocks[
'pre'] ];
2905 $this->secondaryauthMocks = [ $mocks[
'secondary'] ];
2909 if ( isset( $state[
'continueRequests'] ) ) {
2910 $state[
'continueRequests'] = array_map( $makeReq, $state[
'continueRequests'] );
2913 $this->
request->getSession()->setSecret(
'AuthManager::authnState', $state );
2915 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState', $state );
2917 $this->
request->getSession()->setSecret(
'AuthManager::accountLinkState', $state );
2921 $expectReqs = array_map( $makeReq, $expect );
2926 $expectReqs[] =
$req;
2930 $expectReqs[] =
$req;
2934 $expectReqs[] =
$req;
2936 usort( $expectReqs, $cmpReqs );
2938 $actual = $this->manager->getAuthenticationRequests(
$action );
2939 foreach ( $actual
as $req ) {
2943 usort( $actual, $cmpReqs );
2945 $this->assertEquals( $expectReqs, $actual );
2952 $expectReqs[] =
$req;
2953 usort( $expectReqs, $cmpReqs );
2956 foreach ( $actual
as $req ) {
2960 usort( $actual, $cmpReqs );
2962 $this->assertEquals( $expectReqs, $actual );
2970 [
'pre-login',
'primary-none-login',
'primary-create-login',
2971 'primary-link-login',
'secondary-login',
'generic' ],
2975 [
'pre-create',
'primary-none-create',
'primary-create-create',
2976 'primary-link-create',
'secondary-create',
'generic' ],
2980 [
'primary-link-link',
'generic' ],
2984 [
'primary-none-change',
'primary-create-change',
'primary-link-change',
2985 'secondary-change' ],
2989 [
'primary-none-remove',
'primary-create-remove',
'primary-link-remove',
2990 'secondary-remove' ],
2994 [
'primary-link-remove' ],
3002 $reqs = [
'continue-login',
'foo',
'bar' ],
3004 'continueRequests' => $reqs,
3013 $reqs = [
'continue-create',
'foo',
'bar' ],
3015 'continueRequests' => $reqs,
3024 $reqs = [
'continue-link',
'foo',
'bar' ],
3026 'continueRequests' => $reqs,
3033 $makeReq =
function ( $key, $required ) {
3035 $req->expects( $this->
any() )->method(
'getUniqueId' )
3036 ->will( $this->returnValue( $key ) );
3039 $req->required = $required;
3042 $cmpReqs =
function ( $a, $b ) {
3043 $ret = strcmp( get_class( $a ), get_class( $b ) );
3045 $ret = strcmp( $a->key, $b->key );
3053 $primary1->expects( $this->
any() )->method(
'getUniqueId' )
3054 ->will( $this->returnValue(
'primary1' ) );
3055 $primary1->expects( $this->
any() )->method(
'accountCreationType' )
3057 $primary1->expects( $this->
any() )->method(
'getAuthenticationRequests' )
3058 ->will( $this->returnCallback(
function (
$action )
use ( $makeReq ) {
3070 $primary2->expects( $this->
any() )->method(
'getUniqueId' )
3071 ->will( $this->returnValue(
'primary2' ) );
3072 $primary2->expects( $this->
any() )->method(
'accountCreationType' )
3074 $primary2->expects( $this->
any() )->method(
'getAuthenticationRequests' )
3075 ->will( $this->returnCallback(
function (
$action )
use ( $makeReq ) {
3084 $secondary->expects( $this->
any() )->method(
'getUniqueId' )
3085 ->will( $this->returnValue(
'secondary' ) );
3086 $secondary->expects( $this->
any() )->method(
'getAuthenticationRequests' )
3087 ->will( $this->returnCallback(
function (
$action )
use ( $makeReq ) {
3098 $this->primaryauthMocks = [ $primary1, $primary2 ];
3099 $this->secondaryauthMocks = [ $secondary ];
3114 usort( $actual, $cmpReqs );
3115 usort( $expected, $cmpReqs );
3116 $this->assertEquals( $expected, $actual );
3118 $this->primaryauthMocks = [ $primary1 ];
3119 $this->secondaryauthMocks = [ $secondary ];
3132 usort( $actual, $cmpReqs );
3133 usort( $expected, $cmpReqs );
3134 $this->assertEquals( $expected, $actual );
3139 foreach ( [
'primary',
'secondary' ]
as $key ) {
3140 $class = ucfirst( $key ) .
'AuthenticationProvider';
3141 $mocks[$key] = $this->getMockForAbstractClass(
"MediaWiki\\Auth\\$class" );
3142 $mocks[$key]->expects( $this->
any() )->method(
'getUniqueId' )
3143 ->will( $this->returnValue( $key ) );
3144 $mocks[$key]->expects( $this->
any() )->method(
'providerAllowsPropertyChange' )
3145 ->will( $this->returnCallback(
function ( $prop )
use ( $key ) {
3146 return $prop !== $key;
3150 $this->primaryauthMocks = [ $mocks[
'primary'] ];
3151 $this->secondaryauthMocks = [ $mocks[
'secondary'] ];
3154 $this->assertTrue( $this->manager->allowsPropertyChange(
'foo' ) );
3155 $this->assertFalse( $this->manager->allowsPropertyChange(
'primary' ) );
3156 $this->assertFalse( $this->manager->allowsPropertyChange(
'secondary' ) );
3165 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'primary' ) );
3166 $mock->expects( $this->
any() )->method(
'beginPrimaryAuthentication' )
3168 $mock->expects( $this->
any() )->method(
'accountCreationType' )
3170 $mock->expects( $this->
any() )->method(
'testUserExists' )->will( $this->returnValue(
true ) );
3171 $mock->expects( $this->
any() )->method(
'testUserForCreation' )
3175 $mock2->expects( $this->
any() )->method(
'getUniqueId' )
3176 ->will( $this->returnValue(
'secondary' ) );
3177 $mock2->expects( $this->
any() )->method(
'beginSecondaryAuthentication' )->will(
3182 $mock2->expects( $this->
any() )->method(
'continueSecondaryAuthentication' )
3184 $mock2->expects( $this->
any() )->method(
'testUserForCreation' )
3187 $this->primaryauthMocks = [ $mock ];
3188 $this->secondaryauthMocks = [ $mock2 ];
3190 $this->manager->setLogger(
new \Psr\Log\NullLogger() );
3191 $session = $this->
request->getSession();
3201 $this->
hook(
'UserLoggedIn', $this->never() );
3202 $this->
hook(
'LocalUserCreated', $this->once() )->with( $callback, $this->equalTo(
true ) );
3203 $ret = $this->manager->beginAuthentication( [],
'http://localhost/' );
3204 $this->
unhook(
'LocalUserCreated' );
3205 $this->
unhook(
'UserLoggedIn' );
3210 $this->assertSame( 0, $session->getUser()->getId() );
3212 $this->
hook(
'UserLoggedIn', $this->once() )->with( $callback );
3213 $this->
hook(
'LocalUserCreated', $this->never() );
3214 $ret = $this->manager->continueAuthentication( [] );
3215 $this->
unhook(
'LocalUserCreated' );
3216 $this->
unhook(
'UserLoggedIn' );
3219 $this->assertSame( $id, $session->getUser()->getId() );
3226 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'primary' ) );
3227 $mock->expects( $this->
any() )->method(
'beginPrimaryAuthentication' )
3229 $mock->expects( $this->
any() )->method(
'accountCreationType' )
3231 $mock->expects( $this->
any() )->method(
'testUserExists' )->will( $this->returnValue(
true ) );
3232 $mock->expects( $this->
any() )->method(
'testUserForCreation' )
3235 $this->primaryauthMocks = [ $mock ];
3237 $this->manager->setLogger(
new \Psr\Log\NullLogger() );
3238 $session = $this->
request->getSession();
3241 $this->assertSame( 0, $session->getUser()->getId(),
3246 $this->
hook(
'UserLoggedIn', $this->never() );
3247 $this->
hook(
'LocalUserCreated', $this->never() );
3248 $ret = $this->manager->beginAuthentication( [],
'http://localhost/' );
3249 $this->
unhook(
'LocalUserCreated' );
3250 $this->
unhook(
'UserLoggedIn' );
3252 $this->assertSame(
'authmanager-authn-autocreate-failed',
$ret->message->getKey() );
3255 $this->assertSame( 0, $session->getUser()->getId() );
3261 $this->assertNull( $this->manager->getAuthenticationSessionData(
'foo' ) );
3262 $this->manager->setAuthenticationSessionData(
'foo',
'foo!' );
3263 $this->manager->setAuthenticationSessionData(
'bar',
'bar!' );
3264 $this->assertSame(
'foo!', $this->manager->getAuthenticationSessionData(
'foo' ) );
3265 $this->assertSame(
'bar!', $this->manager->getAuthenticationSessionData(
'bar' ) );
3266 $this->manager->removeAuthenticationSessionData(
'foo' );
3267 $this->assertNull( $this->manager->getAuthenticationSessionData(
'foo' ) );
3268 $this->assertSame(
'bar!', $this->manager->getAuthenticationSessionData(
'bar' ) );
3269 $this->manager->removeAuthenticationSessionData(
'bar' );
3270 $this->assertNull( $this->manager->getAuthenticationSessionData(
'bar' ) );
3272 $this->manager->setAuthenticationSessionData(
'foo',
'foo!' );
3273 $this->manager->setAuthenticationSessionData(
'bar',
'bar!' );
3274 $this->manager->removeAuthenticationSessionData(
null );
3275 $this->assertNull( $this->manager->getAuthenticationSessionData(
'foo' ) );
3276 $this->assertNull( $this->manager->getAuthenticationSessionData(
'bar' ) );
3286 foreach ( $types
as $type => $can ) {
3288 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
$type ) );
3289 $mock->expects( $this->
any() )->method(
'accountCreationType' )
3290 ->will( $this->returnValue(
$type ) );
3291 $this->primaryauthMocks = [ $mock ];
3293 $this->assertSame( $can, $this->manager->canCreateAccounts(),
$type );
3301 $this->
request->getSession()->setSecret(
'AuthManager::accountLinkState',
'test' );
3303 $this->manager->beginAccountLink(
$user, [],
'http://localhost/' );
3304 $this->fail(
'Expected exception not thrown' );
3305 }
catch ( \LogicException $ex ) {
3306 $this->assertEquals(
'Account linking is not possible', $ex->getMessage() );
3308 $this->assertNull( $this->
request->getSession()->getSecret(
'AuthManager::accountLinkState' ) );
3311 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
3312 $mock->expects( $this->
any() )->method(
'accountCreationType' )
3314 $this->primaryauthMocks = [ $mock ];
3317 $ret = $this->manager->beginAccountLink(
new \
User, [],
'http://localhost/' );
3319 $this->assertSame(
'noname',
$ret->message->getKey() );
3321 $ret = $this->manager->beginAccountLink(
3325 $this->assertSame(
'authmanager-userdoesnotexist',
$ret->message->getKey() );
3333 'userid' =>
$user->getId(),
3334 'username' =>
$user->getName(),
3339 $this->manager->continueAccountLink( [] );
3340 $this->fail(
'Expected exception not thrown' );
3341 }
catch ( \LogicException $ex ) {
3342 $this->assertEquals(
'Account linking is not possible', $ex->getMessage() );
3346 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
3347 $mock->expects( $this->
any() )->method(
'accountCreationType' )
3349 $mock->expects( $this->
any() )->method(
'beginPrimaryAccountLink' )->will(
3352 $this->primaryauthMocks = [ $mock ];
3355 $this->
request->getSession()->setSecret(
'AuthManager::accountLinkState',
null );
3356 $ret = $this->manager->continueAccountLink( [] );
3358 $this->assertSame(
'authmanager-link-not-in-progress',
$ret->message->getKey() );
3360 $this->
request->getSession()->setSecret(
'AuthManager::accountLinkState',
3361 [
'username' =>
$user->getName() .
'<>' ] + $session );
3362 $ret = $this->manager->continueAccountLink( [] );
3364 $this->assertSame(
'noname',
$ret->message->getKey() );
3365 $this->assertNull( $this->
request->getSession()->getSecret(
'AuthManager::accountLinkState' ) );
3367 $id =
$user->getId();
3368 $this->
request->getSession()->setSecret(
'AuthManager::accountLinkState',
3369 [
'userid' => $id + 1 ] + $session );
3371 $ret = $this->manager->continueAccountLink( [] );
3372 $this->fail(
'Expected exception not thrown' );
3373 }
catch ( \UnexpectedValueException $ex ) {
3374 $this->assertEquals(
3375 "User \"{$user->getName()}\" is valid, but ID $id != " . ( $id + 1 ) .
'!',
3379 $this->assertNull( $this->
request->getSession()->getSecret(
'AuthManager::accountLinkState' ) );
3397 $req->primary = $primaryResponses;
3400 foreach ( [
'pre',
'primary' ]
as $key ) {
3401 $class = ucfirst( $key ) .
'AuthenticationProvider';
3402 $mocks[$key] = $this->getMockForAbstractClass(
3403 "MediaWiki\\Auth\\$class", [],
"Mock$class"
3405 $mocks[$key]->expects( $this->
any() )->method(
'getUniqueId' )
3406 ->will( $this->returnValue( $key ) );
3408 for ( $i = 2; $i <= 3; $i++ ) {
3409 $mocks[$key . $i] = $this->getMockForAbstractClass(
3410 "MediaWiki\\Auth\\$class", [],
"Mock$class"
3412 $mocks[$key . $i]->expects( $this->
any() )->method(
'getUniqueId' )
3413 ->will( $this->returnValue( $key . $i ) );
3417 $mocks[
'pre']->expects( $this->
any() )->method(
'testForAccountLink' )
3418 ->will( $this->returnCallback(
3422 $this->assertSame(
$user->getId(), $u->getId() );
3423 $this->assertSame(
$user->getName(), $u->getName() );
3428 $mocks[
'pre2']->expects( $this->atMost( 1 ) )->method(
'testForAccountLink' )
3431 $mocks[
'primary']->expects( $this->
any() )->method(
'accountCreationType' )
3434 $callback = $this->returnCallback(
function ( $u, $reqs )
use (
$user,
$req ) {
3435 $this->assertSame(
$user->getId(), $u->getId() );
3436 $this->assertSame(
$user->getName(), $u->getName() );
3438 foreach ( $reqs
as $r ) {
3439 $this->assertSame(
$user->getName(), $r->username );
3440 $foundReq = $foundReq || get_class( $r ) === get_class(
$req );
3442 $this->assertTrue( $foundReq,
'$reqs contains $req' );
3443 return array_shift(
$req->primary );
3445 $mocks[
'primary']->expects( $this->exactly( min( 1, $ct ) ) )
3446 ->method(
'beginPrimaryAccountLink' )
3447 ->will( $callback );
3448 $mocks[
'primary']->expects( $this->exactly( max( 0, $ct - 1 ) ) )
3449 ->method(
'continuePrimaryAccountLink' )
3450 ->will( $callback );
3453 $mocks[
'primary2']->expects( $this->
any() )->method(
'accountCreationType' )
3455 $mocks[
'primary2']->expects( $this->atMost( 1 ) )->method(
'beginPrimaryAccountLink' )
3456 ->will( $this->returnValue( $abstain ) );
3457 $mocks[
'primary2']->expects( $this->never() )->method(
'continuePrimaryAccountLink' );
3458 $mocks[
'primary3']->expects( $this->
any() )->method(
'accountCreationType' )
3460 $mocks[
'primary3']->expects( $this->never() )->method(
'beginPrimaryAccountLink' );
3461 $mocks[
'primary3']->expects( $this->never() )->method(
'continuePrimaryAccountLink' );
3463 $this->preauthMocks = [ $mocks[
'pre'], $mocks[
'pre2'] ];
3464 $this->primaryauthMocks = [ $mocks[
'primary3'], $mocks[
'primary2'], $mocks[
'primary'] ];
3465 $this->logger = new \TestLogger(
true,
function ( $message, $level ) {
3466 return $level === LogLevel::DEBUG ? null : $message;
3470 $constraint = \PHPUnit_Framework_Assert::logicalOr(
3474 $providers = array_merge( $this->preauthMocks, $this->primaryauthMocks );
3475 foreach ( $providers
as $p ) {
3476 $p->postCalled =
false;
3477 $p->expects( $this->atMost( 1 ) )->method(
'postAccountLink' )
3480 $this->assertSame(
'UTSysop',
$user->getName() );
3482 $this->assertThat(
$response->status, $constraint );
3490 foreach ( $managerResponses
as $i =>
$response ) {
3494 $expectLog[] = [ LogLevel::INFO,
'Account linked to {user} by primary' ];
3500 $ret = $this->manager->beginAccountLink(
$user, [
$req ],
'http://localhost/' );
3502 $ret = $this->manager->continueAccountLink( [
$req ] );
3504 if (
$response instanceof \Exception ) {
3505 $this->fail(
'Expected exception not thrown',
"Response $i" );
3507 }
catch ( \Exception $ex ) {
3508 if ( !
$response instanceof \Exception ) {
3511 $this->assertEquals(
$response->getMessage(), $ex->getMessage(),
"Response $i, exception" );
3512 $this->assertNull( $this->
request->getSession()->getSecret(
'AuthManager::accountLinkState' ),
3513 "Response $i, exception, session state" );
3517 $this->assertSame(
'http://localhost/',
$req->returnToUrl );
3520 $this->assertEquals(
$response,
$ret,
"Response $i, response" );
3524 $this->assertNull( $this->
request->getSession()->getSecret(
'AuthManager::accountLinkState' ),
3525 "Response $i, session state" );
3526 foreach ( $providers
as $p ) {
3527 $this->assertSame(
$response->status, $p->postCalled,
3528 "Response $i, post-auth callback called" );
3531 $this->assertNotNull(
3532 $this->
request->getSession()->getSecret(
'AuthManager::accountLinkState' ),
3533 "Response $i, session state"
3535 foreach (
$ret->neededRequests
as $neededReq ) {
3537 "Response $i, neededRequest action" );
3539 $this->assertEquals(
3540 $ret->neededRequests,
3542 "Response $i, continuation check"
3544 foreach ( $providers
as $p ) {
3545 $this->assertFalse( $p->postCalled,
"Response $i, post-auth callback not called" );
3552 $this->assertSame( $expectLog, $this->logger->getBuffer() );
3560 'Pre-link test fail in pre' => [
3567 'Failure in primary' => [
3574 'All primary abstain' => [
3583 'Primary UI, then redirect, then fail' => [
3592 'Primary redirect, then abstain' => [
3596 [
$req ],
'/foo.html', [
'foo' =>
'bar' ]
3602 new \DomainException(
3603 'MockPrimaryAuthenticationProvider::continuePrimaryAccountLink() returned ABSTAIN'
3607 'Primary UI, then pass' => [