8 use Psr\Log\LoggerInterface;
12 use Wikimedia\ScopedCallback;
13 use Wikimedia\TestingAccessWrapper;
49 protected function hook( $hook, $expect ) {
50 $mock = $this->getMockBuilder( __CLASS__ )
51 ->setMethods( [
"on$hook" ] )
54 return $mock->expects( $expect )->method(
"on$hook" );
73 if ( $key ===
null ) {
78 $key = $key->getKey();
96 foreach ( (
new \ReflectionClass( $expected ) )->getProperties()
as $prop ) {
97 $name = $prop->getName();
98 $usedMsg = ltrim(
"$msg ($name)" );
99 if (
$name ===
'message' && $expected->message ) {
100 $this->assertSame( $expected->message->serialize(), $actual->message->serialize(),
103 $this->assertEquals( $expected->$name, $actual->$name, $usedMsg );
123 foreach ( [
'preauth',
'primaryauth',
'secondaryauth' ]
as $type ) {
124 $key =
$type .
'Mocks';
125 foreach ( $this->$key
as $mock ) {
126 $config[
$type][$mock->getUniqueId()] = [
'factory' =>
function ()
use ( $mock ) {
132 $this->config->set(
'AuthManagerConfig',
$config );
133 $this->config->set(
'LanguageCode',
'en' );
134 $this->config->set(
'NewUserLog',
false );
142 if ( $regen || !$this->config ) {
143 $this->config = new \HashConfig();
145 if ( $regen || !$this->
request ) {
146 $this->
request = new \FauxRequest();
148 if ( !$this->logger ) {
149 $this->logger = new \TestLogger();
152 if ( $regen || !$this->config->has(
'AuthManagerConfig' ) ) {
156 $this->manager->setLogger( $this->logger );
157 $this->managerPriv = TestingAccessWrapper::newFromObject( $this->manager );
167 if ( !$this->config ) {
168 $this->config = new \HashConfig();
171 $this->config->set(
'ObjectCacheSessionExpiry', 100 );
173 $methods[] =
'__toString';
174 $methods[] =
'describe';
175 if ( $canChangeUser !==
null ) {
176 $methods[] =
'canChangeUser';
179 ->setMethods( $methods )
181 $provider->expects( $this->
any() )->method(
'__toString' )
182 ->will( $this->returnValue(
'MockSessionProvider' ) );
183 $provider->expects( $this->
any() )->method(
'describe' )
184 ->will( $this->returnValue(
'MockSessionProvider sessions' ) );
185 if ( $canChangeUser !==
null ) {
186 $provider->expects( $this->
any() )->method(
'canChangeUser' )
187 ->will( $this->returnValue( $canChangeUser ) );
189 $this->config->set(
'SessionProviders', [
190 [
'factory' =>
function ()
use ( $provider ) {
195 $manager = new \MediaWiki\Session\SessionManager( [
196 'config' => $this->config,
197 'logger' =>
new \Psr\Log\NullLogger(),
200 TestingAccessWrapper::newFromObject(
$manager )->getProvider( (
string)$provider );
208 return [ $provider, $reset ];
215 $rProp->setAccessible(
true );
216 $old = $rProp->getValue();
217 $cb =
new ScopedCallback( [ $rProp,
'setValue' ], [ $old ] );
218 $rProp->setValue(
null );
226 TestingAccessWrapper::newFromObject( $singleton )->config
234 $this->assertFalse( $this->manager->canAuthenticateNow() );
235 ScopedCallback::consume( $reset );
238 $this->assertTrue( $this->manager->canAuthenticateNow() );
239 ScopedCallback::consume( $reset );
249 foreach ( $mocks
as $key => $mock ) {
250 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue( $key ) );
252 $mocks[0]->expects( $this->once() )->method(
'providerNormalizeUsername' )
253 ->with( $this->identicalTo(
'XYZ' ) )
254 ->willReturn(
'Foo' );
255 $mocks[1]->expects( $this->once() )->method(
'providerNormalizeUsername' )
256 ->with( $this->identicalTo(
'XYZ' ) )
257 ->willReturn(
'Foo' );
258 $mocks[2]->expects( $this->once() )->method(
'providerNormalizeUsername' )
259 ->with( $this->identicalTo(
'XYZ' ) )
260 ->willReturn(
null );
261 $mocks[3]->expects( $this->once() )->method(
'providerNormalizeUsername' )
262 ->with( $this->identicalTo(
'XYZ' ) )
263 ->willReturn(
'Bar!' );
265 $this->primaryauthMocks = $mocks;
269 $this->assertSame( [
'Foo',
'Bar!' ], $this->manager->normalizeUsername(
'XYZ' ) );
277 $this->logger = new \Psr\Log\NullLogger();
283 $mutableSession, [
'provideSessionInfo' ]
285 $provider->expects( $this->
any() )->method(
'provideSessionInfo' )
286 ->will( $this->returnCallback(
function ()
use ( $provider, &$provideUser ) {
288 'provider' => $provider,
296 $this->config->set(
'ReauthenticateTime', [] );
297 $this->config->set(
'AllowSecuritySensitiveOperationIfCannotReauthenticate', [] );
299 $session = $provider->getManager()->getSessionForRequest( $this->
request );
300 $this->assertSame( 0, $session->getUser()->getId(),
'sanity check' );
303 $session->set(
'AuthManager:lastAuthId', 0 );
304 $session->set(
'AuthManager:lastAuthTimestamp', time() - 5 );
305 $this->assertSame( $reauth, $this->manager->securitySensitiveOperationStatus(
'foo' ) );
307 $provideUser =
$user;
308 $session = $provider->getManager()->getSessionForRequest( $this->
request );
309 $this->assertSame(
$user->getId(), $session->getUser()->getId(),
'sanity check' );
312 $session->set(
'AuthManager:lastAuthId',
$user->getId() + 1 );
313 $session->set(
'AuthManager:lastAuthTimestamp', time() - 5 );
315 $this->manager->securitySensitiveOperationStatus(
'foo' );
316 $this->fail(
'Expected exception not thrown' );
317 }
catch ( \UnexpectedValueException $ex ) {
320 ?
'$wgReauthenticateTime lacks a default'
321 :
'$wgAllowSecuritySensitiveOperationIfCannotReauthenticate lacks a default',
326 if ( $mutableSession ) {
327 $this->config->set(
'ReauthenticateTime', [
334 $session->set(
'AuthManager:lastAuthId',
$user->getId() + 1 );
335 $session->set(
'AuthManager:lastAuthTimestamp', time() - 5 );
347 $session->set(
'AuthManager:lastAuthId',
$user->getId() );
348 $session->set(
'AuthManager:lastAuthTimestamp',
null );
360 $session->set(
'AuthManager:lastAuthTimestamp', time() - 5 );
366 $session->set(
'AuthManager:lastAuthTimestamp', time() - 20 );
375 $this->config->set(
'AllowSecuritySensitiveOperationIfCannotReauthenticate', [
394 ]
as $hook => $expect ) {
395 $this->
hook(
'SecuritySensitiveOperationStatus', $this->exactly( 2 ) )
399 $this->callback(
function (
$s )
use ( $session ) {
400 return $s->getId() === $session->getId();
402 $mutableSession ? $this->equalTo( 500, 1 ) : $this->equalTo( -1 )
404 ->
will( $this->returnCallback(
function ( &$v )
use ( $hook ) {
408 $session->set(
'AuthManager:lastAuthTimestamp', time() - 500 );
410 $expect, $this->manager->securitySensitiveOperationStatus(
'test' ),
"hook $hook"
413 $expect, $this->manager->securitySensitiveOperationStatus(
'test2' ),
"hook $hook"
415 $this->
unhook(
'SecuritySensitiveOperationStatus' );
418 ScopedCallback::consume( $reset );
439 $mock1->expects( $this->
any() )->method(
'getUniqueId' )
440 ->will( $this->returnValue(
'primary1' ) );
441 $mock1->expects( $this->
any() )->method(
'testUserCanAuthenticate' )
442 ->with( $this->equalTo(
'UTSysop' ) )
443 ->will( $this->returnValue( $primary1Can ) );
445 $mock2->expects( $this->
any() )->method(
'getUniqueId' )
446 ->will( $this->returnValue(
'primary2' ) );
447 $mock2->expects( $this->
any() )->method(
'testUserCanAuthenticate' )
448 ->with( $this->equalTo(
'UTSysop' ) )
449 ->will( $this->returnValue( $primary2Can ) );
450 $this->primaryauthMocks = [ $mock1, $mock2 ];
453 $this->assertSame( $expect, $this->manager->userCanAuthenticate(
'UTSysop' ) );
458 [
false,
false,
false ],
459 [
true,
false,
true ],
460 [
false,
true,
true ],
461 [
true,
true,
true ],
469 $mock->expects( $this->
any() )->method(
'getUniqueId' )
470 ->will( $this->returnValue(
'primary' ) );
471 $mock->expects( $this->once() )->method(
'providerRevokeAccessForUser' )
472 ->with( $this->equalTo(
'UTSysop' ) );
473 $this->primaryauthMocks = [ $mock ];
476 $this->logger->setCollect(
true );
478 $this->manager->revokeAccessForUser(
'UTSysop' );
481 [ LogLevel::INFO,
'Revoking access for {user}' ],
482 ], $this->logger->getBuffer() );
491 foreach ( $mocks
as $key => $mock ) {
492 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue( $key ) );
493 $mock->expects( $this->once() )->method(
'setLogger' );
494 $mock->expects( $this->once() )->method(
'setManager' );
495 $mock->expects( $this->once() )->method(
'setConfig' );
497 $this->preauthMocks = [ $mocks[
'pre'] ];
498 $this->primaryauthMocks = [ $mocks[
'primary'] ];
499 $this->secondaryauthMocks = [ $mocks[
'secondary'] ];
505 $this->managerPriv->getAuthenticationProvider(
'primary' )
509 $this->managerPriv->getAuthenticationProvider(
'secondary' )
513 $this->managerPriv->getAuthenticationProvider(
'pre' )
516 [
'pre' => $mocks[
'pre'] ],
517 $this->managerPriv->getPreAuthenticationProviders()
520 [
'primary' => $mocks[
'primary'] ],
521 $this->managerPriv->getPrimaryAuthenticationProviders()
524 [
'secondary' => $mocks[
'secondary'] ],
525 $this->managerPriv->getSecondaryAuthenticationProviders()
531 $mock1->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
532 $mock2->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
533 $this->preauthMocks = [ $mock1 ];
534 $this->primaryauthMocks = [ $mock2 ];
535 $this->secondaryauthMocks = [];
538 $this->managerPriv->getAuthenticationProvider(
'Y' );
539 $this->fail(
'Expected exception not thrown' );
540 }
catch ( \RuntimeException $ex ) {
541 $class1 = get_class( $mock1 );
542 $class2 = get_class( $mock2 );
544 "Duplicate specifications for id X (classes $class1 and $class2)", $ex->getMessage()
550 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
551 $class = get_class( $mock );
552 $this->preauthMocks = [ $mock ];
553 $this->primaryauthMocks = [ $mock ];
554 $this->secondaryauthMocks = [ $mock ];
557 $this->managerPriv->getPreAuthenticationProviders();
558 $this->fail(
'Expected exception not thrown' );
559 }
catch ( \RuntimeException $ex ) {
561 "Expected instance of MediaWiki\\Auth\\PreAuthenticationProvider, got $class",
566 $this->managerPriv->getPrimaryAuthenticationProviders();
567 $this->fail(
'Expected exception not thrown' );
568 }
catch ( \RuntimeException $ex ) {
570 "Expected instance of MediaWiki\\Auth\\PrimaryAuthenticationProvider, got $class",
575 $this->managerPriv->getSecondaryAuthenticationProviders();
576 $this->fail(
'Expected exception not thrown' );
577 }
catch ( \RuntimeException $ex ) {
579 "Expected instance of MediaWiki\\Auth\\SecondaryAuthenticationProvider, got $class",
588 $mock1->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'A' ) );
589 $mock2->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'B' ) );
590 $mock3->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'C' ) );
591 $this->preauthMocks = [];
592 $this->primaryauthMocks = [ $mock1, $mock2, $mock3 ];
593 $this->secondaryauthMocks = [];
595 $config = $this->config->
get(
'AuthManagerConfig' );
599 [
'A' => $mock1,
'B' => $mock2,
'C' => $mock3 ],
600 $this->managerPriv->getPrimaryAuthenticationProviders(),
604 $config[
'primaryauth'][
'A'][
'sort'] = 100;
605 $config[
'primaryauth'][
'C'][
'sort'] = -1;
606 $this->config->set(
'AuthManagerConfig',
$config );
609 [
'C' => $mock3,
'B' => $mock2,
'A' => $mock1 ],
610 $this->managerPriv->getPrimaryAuthenticationProviders()
618 $contLang, $useContextLang, $expectedLang, $expectedVariant
628 $user->addToDatabase();
629 $oldToken =
$user->getToken();
630 $this->managerPriv->setDefaultUserOptions(
$user, $useContextLang );
631 $user->saveSettings();
632 $this->assertNotEquals( $oldToken,
$user->getToken() );
633 $this->assertSame( $expectedLang,
$user->getOption(
'language' ) );
634 $this->assertSame( $expectedVariant,
$user->getOption(
'variant' ) );
639 [
'zh',
false,
'zh',
'zh' ],
640 [
'zh',
true,
'de',
'zh' ],
641 [
'fr',
true,
'de', null ],
649 $mockA->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'A' ) );
650 $mockB->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'B' ) );
651 $mockB2->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'B' ) );
652 $this->primaryauthMocks = [ $mockA ];
654 $this->logger = new \TestLogger(
true );
658 $this->manager->forcePrimaryAuthenticationProviders( [ $mockB ],
'testing' );
660 [
'B' => $mockB ], $this->managerPriv->getPrimaryAuthenticationProviders()
662 $this->assertSame(
null, $this->managerPriv->getAuthenticationProvider(
'A' ) );
663 $this->assertSame( $mockB, $this->managerPriv->getAuthenticationProvider(
'B' ) );
665 [ LogLevel::WARNING,
'Overriding AuthManager primary authn because testing' ],
666 ], $this->logger->getBuffer() );
667 $this->logger->clearBuffer();
671 $this->assertSame( $mockA, $this->managerPriv->getAuthenticationProvider(
'A' ) );
672 $this->assertSame(
null, $this->managerPriv->getAuthenticationProvider(
'B' ) );
673 $this->
request->getSession()->setSecret(
'AuthManager::authnState',
'test' );
674 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState',
'test' );
675 $this->manager->forcePrimaryAuthenticationProviders( [ $mockB ],
'testing' );
677 [
'B' => $mockB ], $this->managerPriv->getPrimaryAuthenticationProviders()
679 $this->assertSame(
null, $this->managerPriv->getAuthenticationProvider(
'A' ) );
680 $this->assertSame( $mockB, $this->managerPriv->getAuthenticationProvider(
'B' ) );
681 $this->assertNull( $this->
request->getSession()->getSecret(
'AuthManager::authnState' ) );
683 $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' )
686 [ LogLevel::WARNING,
'Overriding AuthManager primary authn because testing' ],
689 'PrimaryAuthenticationProviders have already been accessed! I hope nothing breaks.'
691 ], $this->logger->getBuffer() );
692 $this->logger->clearBuffer();
697 $this->manager->forcePrimaryAuthenticationProviders( [ $mockB, $mockB2 ],
'testing' );
698 $this->fail(
'Expected exception not thrown' );
699 }
catch ( \RuntimeException $ex ) {
700 $class1 = get_class( $mockB );
701 $class2 = get_class( $mockB2 );
703 "Duplicate specifications for id B (classes $class2 and $class1)", $ex->getMessage()
709 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
710 $class = get_class( $mock );
712 $this->manager->forcePrimaryAuthenticationProviders( [ $mock ],
'testing' );
713 $this->fail(
'Expected exception not thrown' );
714 }
catch ( \RuntimeException $ex ) {
716 "Expected instance of MediaWiki\\Auth\\PrimaryAuthenticationProvider, got $class",
727 $this->
hook(
'UserLoggedIn', $this->never() );
728 $this->
request->getSession()->setSecret(
'AuthManager::authnState',
'test' );
730 $this->manager->beginAuthentication( [],
'http://localhost/' );
731 $this->fail(
'Expected exception not thrown' );
732 }
catch ( \LogicException $ex ) {
733 $this->assertSame(
'Authentication is not possible now', $ex->getMessage() );
735 $this->
unhook(
'UserLoggedIn' );
736 $this->assertNull( $this->
request->getSession()->getSecret(
'AuthManager::authnState' ) );
737 ScopedCallback::consume( $reset );
745 $this->
hook(
'UserLoggedIn', $this->never() );
747 $this->manager->beginAuthentication( $reqs,
'http://localhost/' );
748 $this->fail(
'Expected exception not thrown' );
749 }
catch ( \LogicException $ex ) {
751 'CreatedAccountAuthenticationRequests are only valid on the same AuthManager ' .
752 'that created the account',
756 $this->
unhook(
'UserLoggedIn' );
758 $this->
request->getSession()->clear();
759 $this->
request->getSession()->setSecret(
'AuthManager::authnState',
'test' );
760 $this->managerPriv->createdAccountAuthenticationRequests = [ $reqs[0] ];
761 $this->
hook(
'UserLoggedIn', $this->once() )
762 ->with( $this->callback(
function ( $u )
use (
$user ) {
763 return $user->getId() === $u->getId() &&
$user->getName() === $u->getName();
765 $this->
hook(
'AuthManagerLoginAuthenticateAudit', $this->once() );
766 $this->logger->setCollect(
true );
767 $ret = $this->manager->beginAuthentication( $reqs,
'http://localhost/' );
768 $this->logger->setCollect(
false );
769 $this->
unhook(
'UserLoggedIn' );
770 $this->
unhook(
'AuthManagerLoginAuthenticateAudit' );
772 $this->assertSame(
$user->getName(),
$ret->username );
773 $this->assertSame(
$user->getId(), $this->
request->getSessionData(
'AuthManager:lastAuthId' ) );
775 time(), $this->
request->getSessionData(
'AuthManager:lastAuthTimestamp' ),
778 $this->assertNull( $this->
request->getSession()->getSecret(
'AuthManager::authnState' ) );
779 $this->assertSame(
$user->getId(), $this->
request->getSession()->getUser()->getId() );
781 [ LogLevel::INFO,
'Logging in {user} after account creation' ],
782 ], $this->logger->getBuffer() );
791 $userReq->username =
'UTDummy';
793 $req1->returnToUrl =
'http://localhost/';
794 $req2->returnToUrl =
'http://localhost/';
795 $req3->returnToUrl =
'http://localhost/';
796 $req3->username =
'UTDummy';
797 $userReq->returnToUrl =
'http://localhost/';
801 $this->primaryauthMocks = [ $primary ];
804 $res->createRequest = $req1;
805 $primary->expects( $this->
any() )->method(
'beginPrimaryAuthentication' )
806 ->will( $this->returnValue(
$res ) );
808 null, [ $req2->getUniqueId() => $req2 ]
810 $this->logger->setCollect(
true );
811 $ret = $this->manager->beginAuthentication( [ $createReq ],
'http://localhost/' );
812 $this->logger->setCollect(
false );
815 $this->assertSame( $req1,
$ret->createRequest->createRequest );
816 $this->assertEquals( [ $req2->getUniqueId() => $req2 ],
$ret->createRequest->maybeLink );
820 ->setMethods( [
'continuePrimaryAuthentication' ] )
821 ->getMockForAbstractClass();
822 $this->primaryauthMocks = [ $primary ];
824 $primary->expects( $this->
any() )->method(
'beginPrimaryAuthentication' )
825 ->will( $this->returnValue(
829 $res->createRequest = $req2;
830 $primary->expects( $this->
any() )->method(
'continuePrimaryAuthentication' )
831 ->will( $this->returnValue(
$res ) );
832 $this->logger->setCollect(
true );
833 $ret = $this->manager->beginAuthentication( [],
'http://localhost/' );
835 $ret = $this->manager->continueAuthentication( [] );
836 $this->logger->setCollect(
false );
839 $this->assertSame( $req2,
$ret->createRequest->createRequest );
840 $this->assertEquals( [],
$ret->createRequest->maybeLink );
844 $this->primaryauthMocks = [ $primary ];
847 $createReq->returnToUrl =
'http://localhost/';
848 $createReq->username =
'UTDummy';
850 $primary->expects( $this->
any() )->method(
'beginPrimaryAccountCreation' )
851 ->with( $this->
anything(), $this->
anything(), [ $userReq, $createReq, $req3 ] )
852 ->will( $this->returnValue(
$res ) );
853 $primary->expects( $this->
any() )->method(
'accountCreationType' )
855 $this->logger->setCollect(
true );
856 $ret = $this->manager->beginAccountCreation(
857 $user, [ $userReq, $createReq ],
'http://localhost/'
859 $this->logger->setCollect(
false );
861 $state = $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' );
862 $this->assertNotNull( $state );
863 $this->assertEquals( [ $userReq, $createReq, $req3 ], $state[
'reqs'] );
864 $this->assertEquals( [ $req2 ], $state[
'maybeLink'] );
881 $id =
$user->getId();
886 $req->rememberMe = (bool)rand( 0, 1 );
887 $req->pre = $preResponse;
888 $req->primary = $primaryResponses;
889 $req->secondary = $secondaryResponses;
891 foreach ( [
'pre',
'primary',
'secondary' ]
as $key ) {
892 $class = ucfirst( $key ) .
'AuthenticationProvider';
893 $mocks[$key] = $this->getMockForAbstractClass(
894 "MediaWiki\\Auth\\$class", [],
"Mock$class"
896 $mocks[$key]->expects( $this->
any() )->method(
'getUniqueId' )
897 ->will( $this->returnValue( $key ) );
898 $mocks[$key .
'2'] = $this->getMockForAbstractClass(
"MediaWiki\\Auth\\$class" );
899 $mocks[$key .
'2']->expects( $this->
any() )->method(
'getUniqueId' )
900 ->will( $this->returnValue( $key .
'2' ) );
901 $mocks[$key .
'3'] = $this->getMockForAbstractClass(
"MediaWiki\\Auth\\$class" );
902 $mocks[$key .
'3']->expects( $this->
any() )->method(
'getUniqueId' )
903 ->will( $this->returnValue( $key .
'3' ) );
905 foreach ( $mocks
as $mock ) {
906 $mock->expects( $this->
any() )->method(
'getAuthenticationRequests' )
907 ->will( $this->returnValue( [] ) );
910 $mocks[
'pre']->expects( $this->once() )->method(
'testForAuthentication' )
911 ->will( $this->returnCallback(
function ( $reqs )
use (
$req ) {
912 $this->assertContains(
$req, $reqs );
917 $callback = $this->returnCallback(
function ( $reqs )
use (
$req ) {
918 $this->assertContains(
$req, $reqs );
919 return array_shift(
$req->primary );
921 $mocks[
'primary']->expects( $this->exactly( min( 1, $ct ) ) )
922 ->method(
'beginPrimaryAuthentication' )
924 $mocks[
'primary']->expects( $this->exactly( max( 0, $ct - 1 ) ) )
925 ->method(
'continuePrimaryAuthentication' )
928 $mocks[
'primary']->expects( $this->
any() )->method(
'accountCreationType' )
933 $callback = $this->returnCallback(
function (
$user, $reqs )
use ( $id,
$name,
$req ) {
934 $this->assertSame( $id,
$user->getId() );
936 $this->assertContains(
$req, $reqs );
937 return array_shift(
$req->secondary );
939 $mocks[
'secondary']->expects( $this->exactly( min( 1, $ct ) ) )
940 ->method(
'beginSecondaryAuthentication' )
942 $mocks[
'secondary']->expects( $this->exactly( max( 0, $ct - 1 ) ) )
943 ->method(
'continueSecondaryAuthentication' )
947 $mocks[
'pre2']->expects( $this->atMost( 1 ) )->method(
'testForAuthentication' )
949 $mocks[
'primary2']->expects( $this->atMost( 1 ) )->method(
'beginPrimaryAuthentication' )
950 ->will( $this->returnValue( $abstain ) );
951 $mocks[
'primary2']->expects( $this->never() )->method(
'continuePrimaryAuthentication' );
952 $mocks[
'secondary2']->expects( $this->atMost( 1 ) )->method(
'beginSecondaryAuthentication' )
953 ->will( $this->returnValue( $abstain ) );
954 $mocks[
'secondary2']->expects( $this->never() )->method(
'continueSecondaryAuthentication' );
955 $mocks[
'secondary3']->expects( $this->atMost( 1 ) )->method(
'beginSecondaryAuthentication' )
956 ->will( $this->returnValue( $abstain ) );
957 $mocks[
'secondary3']->expects( $this->never() )->method(
'continueSecondaryAuthentication' );
959 $this->preauthMocks = [ $mocks[
'pre'], $mocks[
'pre2'] ];
960 $this->primaryauthMocks = [ $mocks[
'primary'], $mocks[
'primary2'] ];
961 $this->secondaryauthMocks = [
962 $mocks[
'secondary3'], $mocks[
'secondary'], $mocks[
'secondary2'],
967 $this->logger->setCollect(
true );
969 $constraint = \PHPUnit_Framework_Assert::logicalOr(
973 $providers = array_filter(
975 $this->preauthMocks, $this->primaryauthMocks, $this->secondaryauthMocks
978 return is_callable( [ $p,
'expects' ] );
981 foreach ( $providers
as $p ) {
982 $p->postCalled =
false;
983 $p->expects( $this->atMost( 1 ) )->method(
'postAuthentication' )
985 if (
$user !==
null ) {
987 $this->assertSame(
'UTSysop',
$user->getName() );
990 $this->assertThat(
$response->status, $constraint );
995 $session = $this->
request->getSession();
996 $session->setRememberUser( !
$req->rememberMe );
1002 $this->
hook(
'UserLoggedIn', $this->once() )
1003 ->with( $this->callback(
function (
$user )
use ( $id,
$name ) {
1007 $this->
hook(
'UserLoggedIn', $this->never() );
1012 $response->message->getKey() !==
'authmanager-authn-not-in-progress' &&
1013 $response->message->getKey() !==
'authmanager-authn-no-primary'
1016 $this->
hook(
'AuthManagerLoginAuthenticateAudit', $this->once() );
1018 $this->
hook(
'AuthManagerLoginAuthenticateAudit', $this->never() );
1024 $ret = $this->manager->beginAuthentication( [
$req ],
'http://localhost/' );
1026 $ret = $this->manager->continueAuthentication( [
$req ] );
1028 if (
$response instanceof \Exception ) {
1029 $this->fail(
'Expected exception not thrown',
"Response $i" );
1031 }
catch ( \Exception $ex ) {
1032 if ( !
$response instanceof \Exception ) {
1035 $this->assertEquals(
$response->getMessage(), $ex->getMessage(),
"Response $i, exception" );
1036 $this->assertNull( $session->getSecret(
'AuthManager::authnState' ),
1037 "Response $i, exception, session state" );
1038 $this->
unhook(
'UserLoggedIn' );
1039 $this->
unhook(
'AuthManagerLoginAuthenticateAudit' );
1043 $this->
unhook(
'UserLoggedIn' );
1044 $this->
unhook(
'AuthManagerLoginAuthenticateAudit' );
1046 $this->assertSame(
'http://localhost/',
$req->returnToUrl );
1051 $this->assertSame( $id, $session->getUser()->getId(),
1052 "Response $i, authn" );
1054 $this->assertSame( 0, $session->getUser()->getId(),
1055 "Response $i, authn" );
1058 $this->assertNull( $session->getSecret(
'AuthManager::authnState' ),
1059 "Response $i, session state" );
1060 foreach ( $providers
as $p ) {
1061 $this->assertSame(
$response->status, $p->postCalled,
1062 "Response $i, post-auth callback called" );
1065 $this->assertNotNull( $session->getSecret(
'AuthManager::authnState' ),
1066 "Response $i, session state" );
1067 foreach (
$ret->neededRequests
as $neededReq ) {
1069 "Response $i, neededRequest action" );
1071 $this->assertEquals(
1072 $ret->neededRequests,
1074 "Response $i, continuation check"
1076 foreach ( $providers
as $p ) {
1077 $this->assertFalse( $p->postCalled,
"Response $i, post-auth callback not called" );
1081 $state = $session->getSecret(
'AuthManager::authnState' );
1082 $maybeLink = $state[
'maybeLink'] ?? [];
1084 $this->assertEquals(
1087 "Response $i, maybeLink"
1090 $this->assertEquals( [], $maybeLink,
"Response $i, maybeLink" );
1095 $this->assertSame(
$req->rememberMe, $session->shouldRememberUser(),
1096 'rememberMe checkbox had effect' );
1098 $this->assertNotSame(
$req->rememberMe, $session->shouldRememberUser(),
1099 'rememberMe checkbox wasn\'t applied' );
1108 $req->foobar =
'baz';
1110 $this->
message(
'authmanager-authn-no-local-user' )
1112 $restartResponse->neededRequests = [ $rememberReq ];
1115 $restartResponse2Pass->linkRequest =
$req;
1117 $this->
message(
'authmanager-authn-no-local-user-link' )
1120 null, [
$req->getUniqueId() =>
$req ]
1123 $restartResponse2->neededRequests = [ $rememberReq, $restartResponse2->createRequest ];
1125 $userName =
'UTSysop';
1128 'Failure in pre-auth' => [
1135 $this->
message(
'authmanager-authn-not-in-progress' )
1139 'Failure in primary' => [
1147 'All primary abstain' => [
1157 'Primary UI, then redirect, then fail' => [
1167 'Primary redirect, then abstain' => [
1171 [
$req ],
'/foo.html', [
'foo' =>
'bar' ]
1178 new \DomainException(
1179 'MockPrimaryAuthenticationProvider::continuePrimaryAuthentication() returned ABSTAIN'
1183 'Primary UI, then pass with no local user' => [
1195 'Primary UI, then pass with no local user (link type)' => [
1199 $restartResponse2Pass,
1208 'Primary pass with invalid username' => [
1215 new \DomainException(
'MockPrimaryAuthenticationProvider returned an invalid username: <>' ),
1218 'Secondary fail' => [
1228 'Secondary UI, then abstain' => [
1242 'Secondary pass' => [
1265 $mock1->expects( $this->
any() )->method(
'getUniqueId' )
1266 ->will( $this->returnValue(
'primary1' ) );
1267 $mock1->expects( $this->
any() )->method(
'testUserExists' )
1268 ->with( $this->equalTo(
'UTSysop' ) )
1269 ->will( $this->returnValue( $primary1Exists ) );
1271 $mock2->expects( $this->
any() )->method(
'getUniqueId' )
1272 ->will( $this->returnValue(
'primary2' ) );
1273 $mock2->expects( $this->
any() )->method(
'testUserExists' )
1274 ->with( $this->equalTo(
'UTSysop' ) )
1275 ->will( $this->returnValue( $primary2Exists ) );
1276 $this->primaryauthMocks = [ $mock1, $mock2 ];
1279 $this->assertSame( $expect, $this->manager->userExists(
'UTSysop' ) );
1284 [
false,
false,
false ],
1285 [
true,
false,
true ],
1286 [
false,
true,
true ],
1287 [
true,
true,
true ],
1301 $mock1->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'1' ) );
1302 $mock1->expects( $this->
any() )->method(
'providerAllowsAuthenticationDataChange' )
1303 ->with( $this->equalTo(
$req ) )
1304 ->will( $this->returnValue( $primaryReturn ) );
1306 $mock2->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'2' ) );
1307 $mock2->expects( $this->
any() )->method(
'providerAllowsAuthenticationDataChange' )
1308 ->with( $this->equalTo(
$req ) )
1309 ->will( $this->returnValue( $secondaryReturn ) );
1311 $this->primaryauthMocks = [ $mock1 ];
1312 $this->secondaryauthMocks = [ $mock2 ];
1314 $this->assertEquals( $expect, $this->manager->allowsAuthenticationDataChange(
$req ) );
1319 $ignored->warning(
'authmanager-change-not-supported' );
1322 $okFromPrimary->warning(
'warning-from-primary' );
1324 $okFromSecondary->warning(
'warning-from-secondary' );
1372 $req->username =
'UTSysop';
1375 $mock1->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'1' ) );
1376 $mock1->expects( $this->once() )->method(
'providerChangeAuthenticationData' )
1377 ->with( $this->equalTo(
$req ) );
1379 $mock2->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'2' ) );
1380 $mock2->expects( $this->once() )->method(
'providerChangeAuthenticationData' )
1381 ->with( $this->equalTo(
$req ) );
1383 $this->primaryauthMocks = [ $mock1, $mock2 ];
1385 $this->logger->setCollect(
true );
1386 $this->manager->changeAuthenticationData(
$req );
1387 $this->assertSame( [
1388 [ LogLevel::INFO,
'Changing authentication data for {user} class {what}' ],
1389 ], $this->logger->getBuffer() );
1399 foreach ( $types
as $type => $can ) {
1401 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
$type ) );
1402 $mock->expects( $this->
any() )->method(
'accountCreationType' )
1403 ->will( $this->returnValue(
$type ) );
1404 $this->primaryauthMocks = [ $mock ];
1406 $this->assertSame( $can, $this->manager->canCreateAccounts(),
$type );
1414 $this->assertEquals(
1416 $this->manager->checkAccountCreatePermissions(
new \
User )
1419 $readOnlyMode = \MediaWiki\MediaWikiServices::getInstance()->getReadOnlyMode();
1420 $readOnlyMode->setReason(
'Because' );
1421 $this->assertEquals(
1423 $this->manager->checkAccountCreatePermissions(
new \
User )
1425 $readOnlyMode->setReason(
false );
1428 $status = $this->manager->checkAccountCreatePermissions(
new \
User );
1429 $this->assertFalse(
$status->isOK() );
1430 $this->assertTrue(
$status->hasMessage(
'badaccess-groups' ) );
1434 if (
$user->getID() == 0 ) {
1435 $user->addToDatabase();
1437 $user->saveSettings();
1442 $oldBlock->delete();
1445 'address' =>
'UTBlockee',
1446 'user' =>
$user->getID(),
1448 'reason' => __METHOD__,
1449 'expiry' => time() + 100500,
1450 'createAccount' =>
true,
1452 $block = new \Block( $blockOptions );
1454 $status = $this->manager->checkAccountCreatePermissions(
$user );
1455 $this->assertFalse(
$status->isOK() );
1456 $this->assertTrue(
$status->hasMessage(
'cantcreateaccount-text' ) );
1459 'address' =>
'127.0.0.0/24',
1461 'reason' => __METHOD__,
1462 'expiry' => time() + 100500,
1463 'createAccount' =>
true,
1465 $block = new \Block( $blockOptions );
1467 $scopeVariable =
new ScopedCallback( [ $block,
'delete' ] );
1468 $status = $this->manager->checkAccountCreatePermissions(
new \
User );
1469 $this->assertFalse(
$status->isOK() );
1470 $this->assertTrue(
$status->hasMessage(
'cantcreateaccount-range-text' ) );
1471 ScopedCallback::consume( $scopeVariable );
1474 'wgEnableDnsBlacklist' =>
true,
1475 'wgDnsBlacklistUrls' => [
1476 'local.wmftest.net',
1478 'wgProxyWhitelist' => [],
1480 $status = $this->manager->checkAccountCreatePermissions(
new \
User );
1481 $this->assertFalse(
$status->isOK() );
1482 $this->assertTrue(
$status->hasMessage(
'sorbs_create_account_reason' ) );
1483 $this->
setMwGlobals(
'wgProxyWhitelist', [
'127.0.0.1' ] );
1484 $status = $this->manager->checkAccountCreatePermissions(
new \
User );
1485 $this->assertTrue(
$status->isGood() );
1495 $username =
"UTAuthManagerTestAccountCreation" . $uniq . ++$i;
1504 $this->assertEquals(
1506 $this->manager->canCreateAccount(
$username )
1510 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
1511 $mock->expects( $this->
any() )->method(
'accountCreationType' )
1513 $mock->expects( $this->
any() )->method(
'testUserExists' )->will( $this->returnValue(
true ) );
1514 $mock->expects( $this->
any() )->method(
'testUserForCreation' )
1516 $this->primaryauthMocks = [ $mock ];
1519 $this->assertEquals(
1521 $this->manager->canCreateAccount(
$username )
1525 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
1526 $mock->expects( $this->
any() )->method(
'accountCreationType' )
1528 $mock->expects( $this->
any() )->method(
'testUserExists' )->will( $this->returnValue(
false ) );
1529 $mock->expects( $this->
any() )->method(
'testUserForCreation' )
1531 $this->primaryauthMocks = [ $mock ];
1534 $this->assertEquals(
1536 $this->manager->canCreateAccount(
$username .
'<>' )
1539 $this->assertEquals(
1541 $this->manager->canCreateAccount(
'UTSysop' )
1544 $this->assertEquals(
1546 $this->manager->canCreateAccount(
$username )
1550 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
1551 $mock->expects( $this->
any() )->method(
'accountCreationType' )
1553 $mock->expects( $this->
any() )->method(
'testUserExists' )->will( $this->returnValue(
false ) );
1554 $mock->expects( $this->
any() )->method(
'testUserForCreation' )
1556 $this->primaryauthMocks = [ $mock ];
1559 $this->assertEquals(
1561 $this->manager->canCreateAccount(
$username )
1568 $this->logger = new \TestLogger(
false,
function ( $message, $level ) {
1569 return $level === LogLevel::DEBUG ? null : $message;
1573 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState',
'test' );
1574 $this->
hook(
'LocalUserCreated', $this->never() );
1576 $this->manager->beginAccountCreation(
1577 $creator, [],
'http://localhost/'
1579 $this->fail(
'Expected exception not thrown' );
1580 }
catch ( \LogicException $ex ) {
1581 $this->assertEquals(
'Account creation is not possible', $ex->getMessage() );
1583 $this->
unhook(
'LocalUserCreated' );
1585 $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' )
1589 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
1590 $mock->expects( $this->
any() )->method(
'accountCreationType' )
1592 $mock->expects( $this->
any() )->method(
'testUserExists' )->will( $this->returnValue(
true ) );
1593 $mock->expects( $this->
any() )->method(
'testUserForCreation' )
1595 $this->primaryauthMocks = [ $mock ];
1598 $this->
hook(
'LocalUserCreated', $this->never() );
1599 $ret = $this->manager->beginAccountCreation( $creator, [],
'http://localhost/' );
1600 $this->
unhook(
'LocalUserCreated' );
1602 $this->assertSame(
'noname',
$ret->message->getKey() );
1604 $this->
hook(
'LocalUserCreated', $this->never() );
1607 $userReq2->username = $userReq->username .
'X';
1608 $ret = $this->manager->beginAccountCreation(
1609 $creator, [ $userReq, $userReq2 ],
'http://localhost/'
1611 $this->
unhook(
'LocalUserCreated' );
1613 $this->assertSame(
'noname',
$ret->message->getKey() );
1615 $readOnlyMode = \MediaWiki\MediaWikiServices::getInstance()->getReadOnlyMode();
1616 $readOnlyMode->setReason(
'Because' );
1617 $this->
hook(
'LocalUserCreated', $this->never() );
1619 $ret = $this->manager->beginAccountCreation( $creator, [ $userReq ],
'http://localhost/' );
1620 $this->
unhook(
'LocalUserCreated' );
1622 $this->assertSame(
'readonlytext',
$ret->message->getKey() );
1623 $this->assertSame( [
'Because' ],
$ret->message->getParams() );
1624 $readOnlyMode->setReason(
false );
1626 $this->
hook(
'LocalUserCreated', $this->never() );
1628 $ret = $this->manager->beginAccountCreation( $creator, [ $userReq ],
'http://localhost/' );
1629 $this->
unhook(
'LocalUserCreated' );
1631 $this->assertSame(
'userexists',
$ret->message->getKey() );
1634 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
1635 $mock->expects( $this->
any() )->method(
'accountCreationType' )
1637 $mock->expects( $this->
any() )->method(
'testUserExists' )->will( $this->returnValue(
false ) );
1638 $mock->expects( $this->
any() )->method(
'testUserForCreation' )
1640 $this->primaryauthMocks = [ $mock ];
1643 $this->
hook(
'LocalUserCreated', $this->never() );
1645 $ret = $this->manager->beginAccountCreation( $creator, [ $userReq ],
'http://localhost/' );
1646 $this->
unhook(
'LocalUserCreated' );
1648 $this->assertSame(
'fail',
$ret->message->getKey() );
1651 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
1652 $mock->expects( $this->
any() )->method(
'accountCreationType' )
1654 $mock->expects( $this->
any() )->method(
'testUserExists' )->will( $this->returnValue(
false ) );
1655 $mock->expects( $this->
any() )->method(
'testUserForCreation' )
1657 $this->primaryauthMocks = [ $mock ];
1660 $this->
hook(
'LocalUserCreated', $this->never() );
1662 $ret = $this->manager->beginAccountCreation( $creator, [ $userReq ],
'http://localhost/' );
1663 $this->
unhook(
'LocalUserCreated' );
1665 $this->assertSame(
'noname',
$ret->message->getKey() );
1667 $this->
hook(
'LocalUserCreated', $this->never() );
1668 $userReq->username = $creator->getName();
1669 $ret = $this->manager->beginAccountCreation( $creator, [ $userReq ],
'http://localhost/' );
1670 $this->
unhook(
'LocalUserCreated' );
1672 $this->assertSame(
'userexists',
$ret->message->getKey() );
1675 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
1676 $mock->expects( $this->
any() )->method(
'accountCreationType' )
1678 $mock->expects( $this->
any() )->method(
'testUserExists' )->will( $this->returnValue(
false ) );
1679 $mock->expects( $this->
any() )->method(
'testUserForCreation' )
1681 $mock->expects( $this->
any() )->method(
'testForAccountCreation' )
1683 $this->primaryauthMocks = [ $mock ];
1687 ->setMethods( [
'populateUser' ] )
1689 $req->expects( $this->
any() )->method(
'populateUser' )
1692 $ret = $this->manager->beginAccountCreation(
1693 $creator, [ $userReq,
$req ],
'http://localhost/'
1696 $this->assertSame(
'populatefail',
$ret->message->getKey() );
1701 $ret = $this->manager->beginAccountCreation(
1702 $creator, [ $userReq,
$req ],
'http://localhost/'
1705 $this->assertSame(
'fail',
$ret->message->getKey() );
1707 $this->manager->beginAccountCreation(
1711 $this->assertSame(
'fail',
$ret->message->getKey() );
1717 $this->logger = new \TestLogger(
false,
function ( $message, $level ) {
1718 return $level === LogLevel::DEBUG ? null : $message;
1729 'primaryResponse' =>
null,
1731 'ranPreTests' =>
true,
1734 $this->
hook(
'LocalUserCreated', $this->never() );
1736 $this->manager->continueAccountCreation( [] );
1737 $this->fail(
'Expected exception not thrown' );
1738 }
catch ( \LogicException $ex ) {
1739 $this->assertEquals(
'Account creation is not possible', $ex->getMessage() );
1741 $this->
unhook(
'LocalUserCreated' );
1744 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
1745 $mock->expects( $this->
any() )->method(
'accountCreationType' )
1747 $mock->expects( $this->
any() )->method(
'testUserExists' )->will( $this->returnValue(
false ) );
1748 $mock->expects( $this->
any() )->method(
'beginPrimaryAccountCreation' )->will(
1751 $this->primaryauthMocks = [ $mock ];
1754 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState',
null );
1755 $this->
hook(
'LocalUserCreated', $this->never() );
1756 $ret = $this->manager->continueAccountCreation( [] );
1757 $this->
unhook(
'LocalUserCreated' );
1759 $this->assertSame(
'authmanager-create-not-in-progress',
$ret->message->getKey() );
1761 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState',
1762 [
'username' =>
"$username<>" ] + $session );
1763 $this->
hook(
'LocalUserCreated', $this->never() );
1764 $ret = $this->manager->continueAccountCreation( [] );
1765 $this->
unhook(
'LocalUserCreated' );
1767 $this->assertSame(
'noname',
$ret->message->getKey() );
1769 $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' )
1772 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState', $session );
1773 $this->
hook(
'LocalUserCreated', $this->never() );
1776 $ret = $this->manager->continueAccountCreation( [] );
1778 $this->
unhook(
'LocalUserCreated' );
1780 $this->assertSame(
'usernameinprogress',
$ret->message->getKey() );
1784 $session, $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' )
1787 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState',
1788 [
'username' => $creator->getName() ] + $session );
1789 $readOnlyMode = \MediaWiki\MediaWikiServices::getInstance()->getReadOnlyMode();
1790 $readOnlyMode->setReason(
'Because' );
1791 $this->
hook(
'LocalUserCreated', $this->never() );
1792 $ret = $this->manager->continueAccountCreation( [] );
1793 $this->
unhook(
'LocalUserCreated' );
1795 $this->assertSame(
'readonlytext',
$ret->message->getKey() );
1796 $this->assertSame( [
'Because' ],
$ret->message->getParams() );
1797 $readOnlyMode->setReason(
false );
1799 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState',
1800 [
'username' => $creator->getName() ] + $session );
1801 $this->
hook(
'LocalUserCreated', $this->never() );
1802 $ret = $this->manager->continueAccountCreation( [] );
1803 $this->
unhook(
'LocalUserCreated' );
1805 $this->assertSame(
'userexists',
$ret->message->getKey() );
1807 $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' )
1810 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState',
1811 [
'userid' => $creator->getId() ] + $session );
1812 $this->
hook(
'LocalUserCreated', $this->never() );
1814 $ret = $this->manager->continueAccountCreation( [] );
1815 $this->fail(
'Expected exception not thrown' );
1816 }
catch ( \UnexpectedValueException $ex ) {
1817 $this->assertEquals(
"User \"{$username}\" should exist now, but doesn't!", $ex->getMessage() );
1819 $this->
unhook(
'LocalUserCreated' );
1821 $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' )
1824 $id = $creator->getId();
1825 $name = $creator->getName();
1826 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState',
1827 [
'username' =>
$name,
'userid' => $id + 1 ] + $session );
1828 $this->
hook(
'LocalUserCreated', $this->never() );
1830 $ret = $this->manager->continueAccountCreation( [] );
1831 $this->fail(
'Expected exception not thrown' );
1832 }
catch ( \UnexpectedValueException $ex ) {
1833 $this->assertEquals(
1834 "User \"{$name}\" exists, but ID $id != " . ( $id + 1 ) .
'!', $ex->getMessage()
1837 $this->
unhook(
'LocalUserCreated' );
1839 $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' )
1843 ->setMethods( [
'populateUser' ] )
1845 $req->expects( $this->
any() )->method(
'populateUser' )
1847 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState',
1848 [
'reqs' => [ $req ] ] + $session );
1849 $ret = $this->manager->continueAccountCreation( [] );
1851 $this->assertSame(
'populatefail',
$ret->message->getKey() );
1853 $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' )
1867 StatusValue $preTest, $primaryTest, $secondaryTest,
1868 array $primaryResponses,
array $secondaryResponses,
array $managerResponses
1877 $req->preTest = $preTest;
1878 $req->primaryTest = $primaryTest;
1879 $req->secondaryTest = $secondaryTest;
1880 $req->primary = $primaryResponses;
1881 $req->secondary = $secondaryResponses;
1883 foreach ( [
'pre',
'primary',
'secondary' ]
as $key ) {
1884 $class = ucfirst( $key ) .
'AuthenticationProvider';
1885 $mocks[$key] = $this->getMockForAbstractClass(
1886 "MediaWiki\\Auth\\$class", [],
"Mock$class"
1888 $mocks[$key]->expects( $this->
any() )->method(
'getUniqueId' )
1889 ->will( $this->returnValue( $key ) );
1890 $mocks[$key]->expects( $this->
any() )->method(
'testUserForCreation' )
1892 $mocks[$key]->expects( $this->
any() )->method(
'testForAccountCreation' )
1893 ->will( $this->returnCallback(
1894 function (
$user, $creatorIn, $reqs )
1898 $this->assertSame( $creator->getId(), $creatorIn->getId() );
1899 $this->assertSame( $creator->getName(), $creatorIn->getName() );
1901 foreach ( $reqs
as $r ) {
1902 $this->assertSame(
$username, $r->username );
1903 $foundReq = $foundReq || get_class( $r ) === get_class(
$req );
1905 $this->assertTrue( $foundReq,
'$reqs contains $req' );
1911 for ( $i = 2; $i <= 3; $i++ ) {
1912 $mocks[$key . $i] = $this->getMockForAbstractClass(
"MediaWiki\\Auth\\$class" );
1913 $mocks[$key . $i]->expects( $this->
any() )->method(
'getUniqueId' )
1914 ->will( $this->returnValue( $key . $i ) );
1915 $mocks[$key . $i]->expects( $this->
any() )->method(
'testUserForCreation' )
1917 $mocks[$key . $i]->expects( $this->atMost( 1 ) )->method(
'testForAccountCreation' )
1922 $mocks[
'primary']->expects( $this->
any() )->method(
'accountCreationType' )
1924 $mocks[
'primary']->expects( $this->
any() )->method(
'testUserExists' )
1925 ->will( $this->returnValue(
false ) );
1929 $this->assertSame(
'UTSysop', $creator->getName() );
1931 foreach ( $reqs
as $r ) {
1932 $this->assertSame(
$username, $r->username );
1933 $foundReq = $foundReq || get_class( $r ) === get_class(
$req );
1935 $this->assertTrue( $foundReq,
'$reqs contains $req' );
1936 return array_shift(
$req->primary );
1938 $mocks[
'primary']->expects( $this->exactly( min( 1, $ct ) ) )
1939 ->method(
'beginPrimaryAccountCreation' )
1940 ->will( $callback );
1941 $mocks[
'primary']->expects( $this->exactly( max( 0, $ct - 1 ) ) )
1942 ->method(
'continuePrimaryAccountCreation' )
1943 ->will( $callback );
1948 $this->assertSame(
'UTSysop', $creator->getName() );
1950 foreach ( $reqs
as $r ) {
1951 $this->assertSame(
$username, $r->username );
1952 $foundReq = $foundReq || get_class( $r ) === get_class(
$req );
1954 $this->assertTrue( $foundReq,
'$reqs contains $req' );
1955 return array_shift(
$req->secondary );
1957 $mocks[
'secondary']->expects( $this->exactly( min( 1, $ct ) ) )
1958 ->method(
'beginSecondaryAccountCreation' )
1959 ->will( $callback );
1960 $mocks[
'secondary']->expects( $this->exactly( max( 0, $ct - 1 ) ) )
1961 ->method(
'continueSecondaryAccountCreation' )
1962 ->will( $callback );
1965 $mocks[
'primary2']->expects( $this->
any() )->method(
'accountCreationType' )
1967 $mocks[
'primary2']->expects( $this->
any() )->method(
'testUserExists' )
1968 ->will( $this->returnValue(
false ) );
1969 $mocks[
'primary2']->expects( $this->atMost( 1 ) )->method(
'beginPrimaryAccountCreation' )
1970 ->will( $this->returnValue( $abstain ) );
1971 $mocks[
'primary2']->expects( $this->never() )->method(
'continuePrimaryAccountCreation' );
1972 $mocks[
'primary3']->expects( $this->
any() )->method(
'accountCreationType' )
1974 $mocks[
'primary3']->expects( $this->
any() )->method(
'testUserExists' )
1975 ->will( $this->returnValue(
false ) );
1976 $mocks[
'primary3']->expects( $this->never() )->method(
'beginPrimaryAccountCreation' );
1977 $mocks[
'primary3']->expects( $this->never() )->method(
'continuePrimaryAccountCreation' );
1978 $mocks[
'secondary2']->expects( $this->atMost( 1 ) )
1979 ->method(
'beginSecondaryAccountCreation' )
1980 ->will( $this->returnValue( $abstain ) );
1981 $mocks[
'secondary2']->expects( $this->never() )->method(
'continueSecondaryAccountCreation' );
1982 $mocks[
'secondary3']->expects( $this->atMost( 1 ) )
1983 ->method(
'beginSecondaryAccountCreation' )
1984 ->will( $this->returnValue( $abstain ) );
1985 $mocks[
'secondary3']->expects( $this->never() )->method(
'continueSecondaryAccountCreation' );
1987 $this->preauthMocks = [ $mocks[
'pre'], $mocks[
'pre2'] ];
1988 $this->primaryauthMocks = [ $mocks[
'primary3'], $mocks[
'primary'], $mocks[
'primary2'] ];
1989 $this->secondaryauthMocks = [
1990 $mocks[
'secondary3'], $mocks[
'secondary'], $mocks[
'secondary2']
1993 $this->logger = new \TestLogger(
true,
function ( $message, $level ) {
1994 return $level === LogLevel::DEBUG ? null : $message;
1999 $constraint = \PHPUnit_Framework_Assert::logicalOr(
2003 $providers = array_merge(
2004 $this->preauthMocks, $this->primaryauthMocks, $this->secondaryauthMocks
2006 foreach ( $providers
as $p ) {
2007 $p->postCalled =
false;
2008 $p->expects( $this->atMost( 1 ) )->method(
'postAccountCreation' )
2014 $this->assertSame(
'UTSysop', $creator->getName() );
2016 $this->assertThat(
$response->status, $constraint );
2023 $maxLogId = $dbw->selectField(
'logging',
'MAX(log_id)', [
'log_type' =>
'newusers' ] );
2027 foreach ( $managerResponses
as $i =>
$response ) {
2030 if ( $i ===
'created' ) {
2032 $this->
hook(
'LocalUserCreated', $this->once() )
2037 $this->equalTo(
false )
2039 $expectLog[] = [ LogLevel::INFO,
"Creating user {user} during account creation" ];
2041 $this->
hook(
'LocalUserCreated', $this->never() );
2049 $ret = $this->manager->beginAccountCreation(
2050 $creator, [ $userReq,
$req ],
'http://localhost/'
2053 $ret = $this->manager->continueAccountCreation( [
$req ] );
2055 if (
$response instanceof \Exception ) {
2056 $this->fail(
'Expected exception not thrown',
"Response $i" );
2058 }
catch ( \Exception $ex ) {
2059 if ( !
$response instanceof \Exception ) {
2062 $this->assertEquals(
$response->getMessage(), $ex->getMessage(),
"Response $i, exception" );
2064 $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' ),
2065 "Response $i, exception, session state"
2067 $this->
unhook(
'LocalUserCreated' );
2071 $this->
unhook(
'LocalUserCreated' );
2073 $this->assertSame(
'http://localhost/',
$req->returnToUrl );
2076 $this->assertNotNull(
$ret->loginRequest,
"Response $i, login marker" );
2077 $this->assertContains(
2078 $ret->loginRequest, $this->managerPriv->createdAccountAuthenticationRequests,
2079 "Response $i, login marker"
2084 "MediaWiki\Auth\AuthManager::continueAccountCreation: Account creation succeeded for {user}"
2092 $this->assertNull(
$ret->loginRequest,
"Response $i, login marker" );
2093 $this->assertSame( [], $this->managerPriv->createdAccountAuthenticationRequests,
2094 "Response $i, login marker" );
2100 $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' ),
2101 "Response $i, session state"
2103 foreach ( $providers
as $p ) {
2104 $this->assertSame(
$response->status, $p->postCalled,
2105 "Response $i, post-auth callback called" );
2108 $this->assertNotNull(
2109 $this->
request->getSession()->getSecret(
'AuthManager::accountCreationState' ),
2110 "Response $i, session state"
2112 foreach (
$ret->neededRequests
as $neededReq ) {
2114 "Response $i, neededRequest action" );
2116 $this->assertEquals(
2117 $ret->neededRequests,
2119 "Response $i, continuation check"
2121 foreach ( $providers
as $p ) {
2122 $this->assertFalse( $p->postCalled,
"Response $i, post-auth callback not called" );
2135 $this->assertSame( $expectLog, $this->logger->getBuffer() );
2139 $dbw->selectField(
'logging',
'MAX(log_id)', [
'log_type' =>
'newusers' ] )
2148 'Pre-creation test fail in pre' => [
2156 'Pre-creation test fail in primary' => [
2164 'Pre-creation test fail in secondary' => [
2172 'Failure in primary' => [
2173 $good, $good, $good,
2180 'All primary abstain' => [
2181 $good, $good, $good,
2190 'Primary UI, then redirect, then fail' => [
2191 $good, $good, $good,
2200 'Primary redirect, then abstain' => [
2201 $good, $good, $good,
2204 [
$req ],
'/foo.html', [
'foo' =>
'bar' ]
2211 new \DomainException(
2212 'MockPrimaryAuthenticationProvider::continuePrimaryAccountCreation() returned ABSTAIN'
2216 'Primary UI, then pass; secondary abstain' => [
2217 $good, $good, $good,
2230 'Primary pass; secondary UI then pass' => [
2231 $good, $good, $good,
2244 'Primary pass; secondary fail' => [
2245 $good, $good, $good,
2253 'created' => new \DomainException(
2254 'MockSecondaryAuthenticationProvider::beginSecondaryAccountCreation() returned FAIL. ' .
2255 'Secondary providers are not allowed to fail account creation, ' .
2256 'that should have been done via testForAccountCreation().'
2275 $mock = $this->getMockForAbstractClass(
2278 $mock->expects( $this->
any() )->method(
'getUniqueId' )
2279 ->will( $this->returnValue(
'primary' ) );
2280 $mock->expects( $this->
any() )->method(
'testUserForCreation' )
2282 $mock->expects( $this->
any() )->method(
'testForAccountCreation' )
2284 $mock->expects( $this->
any() )->method(
'accountCreationType' )
2286 $mock->expects( $this->
any() )->method(
'testUserExists' )
2287 ->will( $this->returnValue(
false ) );
2288 $mock->expects( $this->
any() )->method(
'beginPrimaryAccountCreation' )
2290 $mock->expects( $this->
any() )->method(
'finishAccountCreation' )
2291 ->will( $this->returnValue( $logSubtype ) );
2293 $this->primaryauthMocks = [ $mock ];
2295 $this->logger->setCollect(
true );
2297 $this->config->set(
'NewUserLog',
true );
2300 $maxLogId = $dbw->selectField(
'logging',
'MAX(log_id)', [
'log_type' =>
'newusers' ] );
2305 $reasonReq->reason = $this->toString();
2306 $ret = $this->manager->beginAccountCreation(
2307 $creator, [ $userReq, $reasonReq ],
'http://localhost/'
2313 $this->assertNotEquals( 0,
$user->getId(),
'sanity check' );
2314 $this->assertNotEquals( $creator->getId(),
$user->getId(),
'sanity check' );
2317 $rows = iterator_to_array( $dbw->select(
2321 'log_id > ' . (
int)$maxLogId,
2322 'log_type' =>
'newusers'
2328 $this->assertCount( 1,
$rows );
2331 $this->assertSame( $logSubtype ?: ( $isAnon ?
'create' :
'create2' ), $entry->getSubtype() );
2333 $isAnon ?
$user->getId() : $creator->getId(),
2334 $entry->getPerformer()->getId()
2337 $isAnon ?
$user->getName() : $creator->getName(),
2338 $entry->getPerformer()->getName()
2340 $this->assertSame(
$user->getUserPage()->getFullText(), $entry->getTarget()->getFullText() );
2341 $this->assertSame( [
'4::userid' =>
$user->getId() ], $entry->getParameters() );
2342 $this->assertSame( $this->toString(), $entry->getComment() );
2350 [
false,
'byemail' ],
2361 $workaroundPHPUnitBug =
false;
2370 [ __METHOD__ => [
'class' =>
'HashBagOStuff' ] ] );
2371 $this->
setMwGlobals( [
'wgMainCacheType' => __METHOD__ ] );
2375 foreach ( [
'pre',
'primary',
'secondary' ]
as $key ) {
2376 $class = ucfirst( $key ) .
'AuthenticationProvider';
2377 $mocks[$key] = $this->getMockForAbstractClass(
"MediaWiki\\Auth\\$class" );
2378 $mocks[$key]->expects( $this->
any() )->method(
'getUniqueId' )
2379 ->will( $this->returnValue( $key ) );
2383 $callback = $this->callback(
function (
$user )
use ( &
$username, &$workaroundPHPUnitBug ) {
2387 $mocks[
'pre']->expects( $this->exactly( 12 ) )->method(
'testUserForCreation' )
2389 ->will( $this->onConsecutiveCalls(
2399 $mocks[
'primary']->expects( $this->
any() )->method(
'accountCreationType' )
2401 $mocks[
'primary']->expects( $this->
any() )->method(
'testUserExists' )
2402 ->will( $this->returnValue(
true ) );
2403 $mocks[
'primary']->expects( $this->exactly( 9 ) )->method(
'testUserForCreation' )
2405 ->will( $this->onConsecutiveCalls(
2413 $mocks[
'primary']->expects( $this->exactly( 3 ) )->method(
'autoCreatedAccount' )
2416 $mocks[
'secondary']->expects( $this->exactly( 8 ) )->method(
'testUserForCreation' )
2418 ->will( $this->onConsecutiveCalls(
2426 $mocks[
'secondary']->expects( $this->exactly( 3 ) )->method(
'autoCreatedAccount' )
2429 $this->preauthMocks = [ $mocks[
'pre'] ];
2430 $this->primaryauthMocks = [ $mocks[
'primary'] ];
2431 $this->secondaryauthMocks = [ $mocks[
'secondary'] ];
2433 $session = $this->
request->getSession();
2435 $logger = new \TestLogger(
true,
function ( $m ) {
2436 $m = str_replace(
'MediaWiki\\Auth\\AuthManager::autoCreateUser: ',
'', $m );
2439 $this->manager->setLogger(
$logger );
2443 $this->manager->autoCreateUser(
$user,
'InvalidSource',
true );
2444 $this->fail(
'Expected exception not thrown' );
2445 }
catch ( \InvalidArgumentException $ex ) {
2446 $this->assertSame(
'Unknown auto-creation source: InvalidSource', $ex->getMessage() );
2452 $this->
hook(
'LocalUserCreated', $this->never() );
2454 $this->
unhook(
'LocalUserCreated' );
2456 $expect->warning(
'userexists' );
2457 $this->assertEquals( $expect,
$ret );
2458 $this->assertNotEquals( 0,
$user->getId() );
2459 $this->assertSame(
'UTSysop',
$user->getName() );
2460 $this->assertEquals(
$user->getId(), $session->getUser()->getId() );
2461 $this->assertSame( [
2462 [ LogLevel::DEBUG,
'{username} already exists locally' ],
2468 $this->
hook(
'LocalUserCreated', $this->never() );
2470 $this->
unhook(
'LocalUserCreated' );
2472 $expect->warning(
'userexists' );
2473 $this->assertEquals( $expect,
$ret );
2474 $this->assertNotEquals( 0,
$user->getId() );
2475 $this->assertSame(
'UTSysop',
$user->getName() );
2476 $this->assertEquals( 0, $session->getUser()->getId() );
2477 $this->assertSame( [
2478 [ LogLevel::DEBUG,
'{username} already exists locally' ],
2484 $readOnlyMode = \MediaWiki\MediaWikiServices::getInstance()->getReadOnlyMode();
2485 $readOnlyMode->setReason(
'Because' );
2487 $this->
hook(
'LocalUserCreated', $this->never() );
2489 $this->
unhook(
'LocalUserCreated' );
2491 $this->assertEquals( 0,
$user->getId() );
2493 $this->assertEquals( 0, $session->getUser()->getId() );
2494 $this->assertSame( [
2495 [ LogLevel::DEBUG,
'denied by wfReadOnly(): {reason}' ],
2498 $readOnlyMode->setReason(
false );
2502 $session->set(
'AuthManager::AutoCreateBlacklist',
'test' );
2504 $this->
hook(
'LocalUserCreated', $this->never() );
2506 $this->
unhook(
'LocalUserCreated' );
2508 $this->assertEquals( 0,
$user->getId() );
2510 $this->assertEquals( 0, $session->getUser()->getId() );
2511 $this->assertSame( [
2512 [ LogLevel::DEBUG,
'blacklisted in session {sessionid}' ],
2519 $this->
hook(
'LocalUserCreated', $this->never() );
2521 $this->
unhook(
'LocalUserCreated' );
2523 $this->assertEquals( 0,
$user->getId() );
2525 $this->assertEquals( 0, $session->getUser()->getId() );
2526 $this->assertSame( [
2527 [ LogLevel::DEBUG,
'blacklisted in session {sessionid}' ],
2534 $this->
hook(
'LocalUserCreated', $this->never() );
2536 $this->
unhook(
'LocalUserCreated' );
2538 $this->assertEquals( 0,
$user->getId() );
2540 $this->assertEquals( 0, $session->getUser()->getId() );
2541 $this->assertSame( [
2542 [ LogLevel::DEBUG,
'name "{username}" is not creatable' ],
2545 $this->assertSame(
'noname', $session->get(
'AuthManager::AutoCreateBlacklist' ) );
2552 $this->
hook(
'LocalUserCreated', $this->never() );
2554 $this->
unhook(
'LocalUserCreated' );
2556 $this->assertEquals( 0,
$user->getId() );
2558 $this->assertEquals( 0, $session->getUser()->getId() );
2559 $this->assertSame( [
2560 [ LogLevel::DEBUG,
'IP lacks the ability to create or autocreate accounts' ],
2564 'authmanager-autocreate-noperm', $session->get(
'AuthManager::AutoCreateBlacklist' )
2573 $this->
hook(
'LocalUserCreated', $this->never() );
2575 $this->
unhook(
'LocalUserCreated' );
2582 $this->
hook(
'LocalUserCreated', $this->never() );
2584 $this->
unhook(
'LocalUserCreated' );
2591 $this->
hook(
'LocalUserCreated', $this->never() );
2596 $this->
unhook(
'LocalUserCreated' );
2598 $this->assertEquals( 0,
$user->getId() );
2600 $this->assertEquals( 0, $session->getUser()->getId() );
2601 $this->assertSame( [
2602 [ LogLevel::DEBUG,
'Could not acquire account creation lock' ],
2609 $this->
hook(
'LocalUserCreated', $this->never() );
2611 $this->
unhook(
'LocalUserCreated' );
2613 $this->assertEquals( 0,
$user->getId() );
2615 $this->assertEquals( 0, $session->getUser()->getId() );
2616 $this->assertSame( [
2617 [ LogLevel::DEBUG,
'Provider denied creation of {username}: {reason}' ],
2620 $this->assertEquals(
2626 $this->
hook(
'LocalUserCreated', $this->never() );
2628 $this->
unhook(
'LocalUserCreated' );
2630 $this->assertEquals( 0,
$user->getId() );
2632 $this->assertEquals( 0, $session->getUser()->getId() );
2633 $this->assertSame( [
2634 [ LogLevel::DEBUG,
'Provider denied creation of {username}: {reason}' ],
2637 $this->assertEquals(
2643 $this->
hook(
'LocalUserCreated', $this->never() );
2645 $this->
unhook(
'LocalUserCreated' );
2647 $this->assertEquals( 0,
$user->getId() );
2649 $this->assertEquals( 0, $session->getUser()->getId() );
2650 $this->assertSame( [
2651 [ LogLevel::DEBUG,
'Provider denied creation of {username}: {reason}' ],
2654 $this->assertEquals(
2661 $cache->set( $backoffKey,
true );
2664 $this->
hook(
'LocalUserCreated', $this->never() );
2666 $this->
unhook(
'LocalUserCreated' );
2668 $this->assertEquals( 0,
$user->getId() );
2670 $this->assertEquals( 0, $session->getUser()->getId() );
2671 $this->assertSame( [
2672 [ LogLevel::DEBUG,
'{username} denied by prior creation attempt failures' ],
2675 $this->assertSame(
null, $session->get(
'AuthManager::AutoCreateBlacklist' ) );
2676 $cache->delete( $backoffKey );
2681 ->setMethods( [
'addToDatabase' ] )->getMock();
2682 $user->expects( $this->once() )->method(
'addToDatabase' )
2687 $this->assertEquals( 0,
$user->getId() );
2689 $this->assertEquals( 0, $session->getUser()->getId() );
2690 $this->assertSame( [
2691 [ LogLevel::INFO,
'creating new user ({username}) - from: {from}' ],
2692 [ LogLevel::ERROR,
'{username} failed with message {msg}' ],
2695 $this->assertSame(
null, $session->get(
'AuthManager::AutoCreateBlacklist' ) );
2700 $this->assertFalse(
$cache->get( $backoffKey ),
'sanity check' );
2703 ->setMethods( [
'addToDatabase' ] )->getMock();
2704 $user->expects( $this->once() )->method(
'addToDatabase' )
2705 ->will( $this->throwException(
new \Exception(
'Excepted' ) ) );
2709 $this->fail(
'Expected exception not thrown' );
2710 }
catch ( \Exception $ex ) {
2711 $this->assertSame(
'Excepted', $ex->getMessage() );
2713 $this->assertEquals( 0,
$user->getId() );
2714 $this->assertEquals( 0, $session->getUser()->getId() );
2715 $this->assertSame( [
2716 [ LogLevel::INFO,
'creating new user ({username}) - from: {from}' ],
2717 [ LogLevel::ERROR,
'{username} failed with exception {exception}' ],
2720 $this->assertSame(
null, $session->get(
'AuthManager::AutoCreateBlacklist' ) );
2721 $this->assertNotEquals(
false,
$cache->get( $backoffKey ) );
2722 $cache->delete( $backoffKey );
2727 ->setMethods( [
'addToDatabase' ] )->getMock();
2728 $user->expects( $this->once() )->method(
'addToDatabase' )
2731 $status = $oldUser->addToDatabase();
2732 $this->assertTrue(
$status->isOK(),
'sanity check' );
2733 $user->setId( $oldUser->getId() );
2734 return \Status::newFatal(
'userexists' );
2739 $expect->warning(
'userexists' );
2740 $this->assertEquals( $expect,
$ret );
2741 $this->assertNotEquals( 0,
$user->getId() );
2743 $this->assertEquals(
$user->getId(), $session->getUser()->getId() );
2744 $this->assertSame( [
2745 [ LogLevel::INFO,
'creating new user ({username}) - from: {from}' ],
2746 [ LogLevel::INFO,
'{username} already exists locally (race)' ],
2749 $this->assertSame(
null, $session->get(
'AuthManager::AutoCreateBlacklist' ) );
2755 $this->
hook(
'AuthPluginAutoCreate', $this->once() )
2756 ->with( $callback );
2758 get_class(
$wgHooks[
'AuthPluginAutoCreate'][0] ) .
'::onAuthPluginAutoCreate)' );
2759 $this->
hook(
'LocalUserCreated', $this->once() )
2760 ->with( $callback, $this->equalTo(
true ) );
2762 $this->
unhook(
'LocalUserCreated' );
2763 $this->
unhook(
'AuthPluginAutoCreate' );
2765 $this->assertNotEquals( 0,
$user->getId() );
2767 $this->assertEquals(
$user->getId(), $session->getUser()->getId() );
2768 $this->assertSame( [
2769 [ LogLevel::INFO,
'creating new user ({username}) - from: {from}' ],
2774 $maxLogId = $dbw->selectField(
'logging',
'MAX(log_id)', [
'log_type' =>
'newusers' ] );
2778 $this->
hook(
'LocalUserCreated', $this->once() )
2779 ->with( $callback, $this->equalTo(
true ) );
2781 $this->
unhook(
'LocalUserCreated' );
2783 $this->assertNotEquals( 0,
$user->getId() );
2785 $this->assertEquals( 0, $session->getUser()->getId() );
2786 $this->assertSame( [
2787 [ LogLevel::INFO,
'creating new user ({username}) - from: {from}' ],
2792 $dbw->selectField(
'logging',
'MAX(log_id)', [
'log_type' =>
'newusers' ] )
2795 $this->config->set(
'NewUserLog',
true );
2804 $rows = iterator_to_array( $dbw->select(
2808 'log_id > ' . (
int)$maxLogId,
2809 'log_type' =>
'newusers'
2815 $this->assertCount( 1,
$rows );
2818 $this->assertSame(
'autocreate', $entry->getSubtype() );
2819 $this->assertSame(
$user->getId(), $entry->getPerformer()->getId() );
2820 $this->assertSame(
$user->getName(), $entry->getPerformer()->getName() );
2821 $this->assertSame(
$user->getUserPage()->getFullText(), $entry->getTarget()->getFullText() );
2822 $this->assertSame( [
'4::userid' =>
$user->getId() ], $entry->getParameters() );
2824 $workaroundPHPUnitBug =
true;
2834 $makeReq =
function ( $key )
use (
$action ) {
2836 $req->expects( $this->
any() )->method(
'getUniqueId' )
2837 ->will( $this->returnValue( $key ) );
2842 $cmpReqs =
function ( $a, $b ) {
2843 $ret = strcmp( get_class( $a ), get_class( $b ) );
2845 $ret = strcmp( $a->key, $b->key );
2853 foreach ( [
'pre',
'primary',
'secondary' ]
as $key ) {
2854 $class = ucfirst( $key ) .
'AuthenticationProvider';
2855 $mocks[$key] = $this->getMockBuilder(
"MediaWiki\\Auth\\$class" )
2857 'getUniqueId',
'getAuthenticationRequests',
'providerAllowsAuthenticationDataChange',
2859 ->getMockForAbstractClass();
2860 $mocks[$key]->expects( $this->
any() )->method(
'getUniqueId' )
2861 ->will( $this->returnValue( $key ) );
2862 $mocks[$key]->expects( $this->
any() )->method(
'getAuthenticationRequests' )
2863 ->will( $this->returnCallback(
function (
$action )
use ( $key, $makeReq ) {
2864 return [ $makeReq(
"$key-$action" ), $makeReq(
'generic' ) ];
2866 $mocks[$key]->expects( $this->
any() )->method(
'providerAllowsAuthenticationDataChange' )
2867 ->will( $this->returnValue( $good ) );
2876 $class =
'PrimaryAuthenticationProvider';
2877 $mocks[
"primary-$type"] = $this->getMockBuilder(
"MediaWiki\\Auth\\$class" )
2879 'getUniqueId',
'accountCreationType',
'getAuthenticationRequests',
2880 'providerAllowsAuthenticationDataChange',
2882 ->getMockForAbstractClass();
2883 $mocks[
"primary-$type"]->expects( $this->
any() )->method(
'getUniqueId' )
2884 ->will( $this->returnValue(
"primary-$type" ) );
2885 $mocks[
"primary-$type"]->expects( $this->
any() )->method(
'accountCreationType' )
2886 ->will( $this->returnValue(
$type ) );
2887 $mocks[
"primary-$type"]->expects( $this->
any() )->method(
'getAuthenticationRequests' )
2888 ->will( $this->returnCallback(
function (
$action )
use (
$type, $makeReq ) {
2889 return [ $makeReq(
"primary-$type-$action" ), $makeReq(
'generic' ) ];
2891 $mocks[
"primary-$type"]->expects( $this->
any() )
2892 ->method(
'providerAllowsAuthenticationDataChange' )
2893 ->will( $this->returnValue( $good ) );
2894 $this->primaryauthMocks[] = $mocks[
"primary-$type"];
2899 'getUniqueId',
'accountCreationType',
'getAuthenticationRequests',
2900 'providerAllowsAuthenticationDataChange',
2902 ->getMockForAbstractClass();
2903 $mocks[
'primary2']->expects( $this->
any() )->method(
'getUniqueId' )
2904 ->will( $this->returnValue(
'primary2' ) );
2905 $mocks[
'primary2']->expects( $this->
any() )->method(
'accountCreationType' )
2907 $mocks[
'primary2']->expects( $this->
any() )->method(
'getAuthenticationRequests' )
2908 ->will( $this->returnValue( [] ) );
2909 $mocks[
'primary2']->expects( $this->
any() )
2910 ->method(
'providerAllowsAuthenticationDataChange' )
2911 ->will( $this->returnCallback(
function (
$req )
use ( $good ) {
2914 $this->primaryauthMocks[] = $mocks[
'primary2'];
2916 $this->preauthMocks = [ $mocks[
'pre'] ];
2917 $this->secondaryauthMocks = [ $mocks[
'secondary'] ];
2921 if ( isset( $state[
'continueRequests'] ) ) {
2922 $state[
'continueRequests'] = array_map( $makeReq, $state[
'continueRequests'] );
2925 $this->
request->getSession()->setSecret(
'AuthManager::authnState', $state );
2927 $this->
request->getSession()->setSecret(
'AuthManager::accountCreationState', $state );
2929 $this->
request->getSession()->setSecret(
'AuthManager::accountLinkState', $state );
2933 $expectReqs = array_map( $makeReq, $expect );
2938 $expectReqs[] =
$req;
2942 $expectReqs[] =
$req;
2946 $expectReqs[] =
$req;
2948 usort( $expectReqs, $cmpReqs );
2950 $actual = $this->manager->getAuthenticationRequests(
$action );
2951 foreach ( $actual
as $req ) {
2955 usort( $actual, $cmpReqs );
2957 $this->assertEquals( $expectReqs, $actual );
2964 $expectReqs[] =
$req;
2965 usort( $expectReqs, $cmpReqs );
2968 foreach ( $actual
as $req ) {
2972 usort( $actual, $cmpReqs );
2974 $this->assertEquals( $expectReqs, $actual );
2982 [
'pre-login',
'primary-none-login',
'primary-create-login',
2983 'primary-link-login',
'secondary-login',
'generic' ],
2987 [
'pre-create',
'primary-none-create',
'primary-create-create',
2988 'primary-link-create',
'secondary-create',
'generic' ],
2992 [
'primary-link-link',
'generic' ],
2996 [
'primary-none-change',
'primary-create-change',
'primary-link-change',
2997 'secondary-change' ],
3001 [
'primary-none-remove',
'primary-create-remove',
'primary-link-remove',
3002 'secondary-remove' ],
3006 [
'primary-link-remove' ],
3014 $reqs = [
'continue-login',
'foo',
'bar' ],
3016 'continueRequests' => $reqs,
3025 $reqs = [
'continue-create',
'foo',
'bar' ],
3027 'continueRequests' => $reqs,
3036 $reqs = [
'continue-link',
'foo',
'bar' ],
3038 'continueRequests' => $reqs,
3045 $makeReq =
function ( $key, $required ) {
3047 $req->expects( $this->
any() )->method(
'getUniqueId' )
3048 ->will( $this->returnValue( $key ) );
3051 $req->required = $required;
3054 $cmpReqs =
function ( $a, $b ) {
3055 $ret = strcmp( get_class( $a ), get_class( $b ) );
3057 $ret = strcmp( $a->key, $b->key );
3065 $primary1->expects( $this->
any() )->method(
'getUniqueId' )
3066 ->will( $this->returnValue(
'primary1' ) );
3067 $primary1->expects( $this->
any() )->method(
'accountCreationType' )
3069 $primary1->expects( $this->
any() )->method(
'getAuthenticationRequests' )
3070 ->will( $this->returnCallback(
function (
$action )
use ( $makeReq ) {
3082 $primary2->expects( $this->
any() )->method(
'getUniqueId' )
3083 ->will( $this->returnValue(
'primary2' ) );
3084 $primary2->expects( $this->
any() )->method(
'accountCreationType' )
3086 $primary2->expects( $this->
any() )->method(
'getAuthenticationRequests' )
3087 ->will( $this->returnCallback(
function (
$action )
use ( $makeReq ) {
3096 $secondary->expects( $this->
any() )->method(
'getUniqueId' )
3097 ->will( $this->returnValue(
'secondary' ) );
3098 $secondary->expects( $this->
any() )->method(
'getAuthenticationRequests' )
3099 ->will( $this->returnCallback(
function (
$action )
use ( $makeReq ) {
3110 $this->primaryauthMocks = [ $primary1, $primary2 ];
3111 $this->secondaryauthMocks = [ $secondary ];
3126 usort( $actual, $cmpReqs );
3127 usort( $expected, $cmpReqs );
3128 $this->assertEquals( $expected, $actual );
3130 $this->primaryauthMocks = [ $primary1 ];
3131 $this->secondaryauthMocks = [ $secondary ];
3144 usort( $actual, $cmpReqs );
3145 usort( $expected, $cmpReqs );
3146 $this->assertEquals( $expected, $actual );
3151 foreach ( [
'primary',
'secondary' ]
as $key ) {
3152 $class = ucfirst( $key ) .
'AuthenticationProvider';
3153 $mocks[$key] = $this->getMockForAbstractClass(
"MediaWiki\\Auth\\$class" );
3154 $mocks[$key]->expects( $this->
any() )->method(
'getUniqueId' )
3155 ->will( $this->returnValue( $key ) );
3156 $mocks[$key]->expects( $this->
any() )->method(
'providerAllowsPropertyChange' )
3157 ->will( $this->returnCallback(
function ( $prop )
use ( $key ) {
3158 return $prop !== $key;
3162 $this->primaryauthMocks = [ $mocks[
'primary'] ];
3163 $this->secondaryauthMocks = [ $mocks[
'secondary'] ];
3166 $this->assertTrue( $this->manager->allowsPropertyChange(
'foo' ) );
3167 $this->assertFalse( $this->manager->allowsPropertyChange(
'primary' ) );
3168 $this->assertFalse( $this->manager->allowsPropertyChange(
'secondary' ) );
3177 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'primary' ) );
3178 $mock->expects( $this->
any() )->method(
'beginPrimaryAuthentication' )
3180 $mock->expects( $this->
any() )->method(
'accountCreationType' )
3182 $mock->expects( $this->
any() )->method(
'testUserExists' )->will( $this->returnValue(
true ) );
3183 $mock->expects( $this->
any() )->method(
'testUserForCreation' )
3187 $mock2->expects( $this->
any() )->method(
'getUniqueId' )
3188 ->will( $this->returnValue(
'secondary' ) );
3189 $mock2->expects( $this->
any() )->method(
'beginSecondaryAuthentication' )->will(
3194 $mock2->expects( $this->
any() )->method(
'continueSecondaryAuthentication' )
3196 $mock2->expects( $this->
any() )->method(
'testUserForCreation' )
3199 $this->primaryauthMocks = [ $mock ];
3200 $this->secondaryauthMocks = [ $mock2 ];
3202 $this->manager->setLogger(
new \Psr\Log\NullLogger() );
3203 $session = $this->
request->getSession();
3213 $this->
hook(
'UserLoggedIn', $this->never() );
3214 $this->
hook(
'LocalUserCreated', $this->once() )->with( $callback, $this->equalTo(
true ) );
3215 $ret = $this->manager->beginAuthentication( [],
'http://localhost/' );
3216 $this->
unhook(
'LocalUserCreated' );
3217 $this->
unhook(
'UserLoggedIn' );
3222 $this->assertSame( 0, $session->getUser()->getId() );
3224 $this->
hook(
'UserLoggedIn', $this->once() )->with( $callback );
3225 $this->
hook(
'LocalUserCreated', $this->never() );
3226 $ret = $this->manager->continueAuthentication( [] );
3227 $this->
unhook(
'LocalUserCreated' );
3228 $this->
unhook(
'UserLoggedIn' );
3231 $this->assertSame( $id, $session->getUser()->getId() );
3238 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'primary' ) );
3239 $mock->expects( $this->
any() )->method(
'beginPrimaryAuthentication' )
3241 $mock->expects( $this->
any() )->method(
'accountCreationType' )
3243 $mock->expects( $this->
any() )->method(
'testUserExists' )->will( $this->returnValue(
true ) );
3244 $mock->expects( $this->
any() )->method(
'testUserForCreation' )
3247 $this->primaryauthMocks = [ $mock ];
3249 $this->manager->setLogger(
new \Psr\Log\NullLogger() );
3250 $session = $this->
request->getSession();
3253 $this->assertSame( 0, $session->getUser()->getId(),
3258 $this->
hook(
'UserLoggedIn', $this->never() );
3259 $this->
hook(
'LocalUserCreated', $this->never() );
3260 $ret = $this->manager->beginAuthentication( [],
'http://localhost/' );
3261 $this->
unhook(
'LocalUserCreated' );
3262 $this->
unhook(
'UserLoggedIn' );
3264 $this->assertSame(
'authmanager-authn-autocreate-failed',
$ret->message->getKey() );
3267 $this->assertSame( 0, $session->getUser()->getId() );
3273 $this->assertNull( $this->manager->getAuthenticationSessionData(
'foo' ) );
3274 $this->manager->setAuthenticationSessionData(
'foo',
'foo!' );
3275 $this->manager->setAuthenticationSessionData(
'bar',
'bar!' );
3276 $this->assertSame(
'foo!', $this->manager->getAuthenticationSessionData(
'foo' ) );
3277 $this->assertSame(
'bar!', $this->manager->getAuthenticationSessionData(
'bar' ) );
3278 $this->manager->removeAuthenticationSessionData(
'foo' );
3279 $this->assertNull( $this->manager->getAuthenticationSessionData(
'foo' ) );
3280 $this->assertSame(
'bar!', $this->manager->getAuthenticationSessionData(
'bar' ) );
3281 $this->manager->removeAuthenticationSessionData(
'bar' );
3282 $this->assertNull( $this->manager->getAuthenticationSessionData(
'bar' ) );
3284 $this->manager->setAuthenticationSessionData(
'foo',
'foo!' );
3285 $this->manager->setAuthenticationSessionData(
'bar',
'bar!' );
3286 $this->manager->removeAuthenticationSessionData(
null );
3287 $this->assertNull( $this->manager->getAuthenticationSessionData(
'foo' ) );
3288 $this->assertNull( $this->manager->getAuthenticationSessionData(
'bar' ) );
3298 foreach ( $types
as $type => $can ) {
3300 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
$type ) );
3301 $mock->expects( $this->
any() )->method(
'accountCreationType' )
3302 ->will( $this->returnValue(
$type ) );
3303 $this->primaryauthMocks = [ $mock ];
3305 $this->assertSame( $can, $this->manager->canCreateAccounts(),
$type );
3313 $this->
request->getSession()->setSecret(
'AuthManager::accountLinkState',
'test' );
3315 $this->manager->beginAccountLink(
$user, [],
'http://localhost/' );
3316 $this->fail(
'Expected exception not thrown' );
3317 }
catch ( \LogicException $ex ) {
3318 $this->assertEquals(
'Account linking is not possible', $ex->getMessage() );
3320 $this->assertNull( $this->
request->getSession()->getSecret(
'AuthManager::accountLinkState' ) );
3323 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
3324 $mock->expects( $this->
any() )->method(
'accountCreationType' )
3326 $this->primaryauthMocks = [ $mock ];
3329 $ret = $this->manager->beginAccountLink(
new \
User, [],
'http://localhost/' );
3331 $this->assertSame(
'noname',
$ret->message->getKey() );
3333 $ret = $this->manager->beginAccountLink(
3337 $this->assertSame(
'authmanager-userdoesnotexist',
$ret->message->getKey() );
3345 'userid' =>
$user->getId(),
3346 'username' =>
$user->getName(),
3351 $this->manager->continueAccountLink( [] );
3352 $this->fail(
'Expected exception not thrown' );
3353 }
catch ( \LogicException $ex ) {
3354 $this->assertEquals(
'Account linking is not possible', $ex->getMessage() );
3358 $mock->expects( $this->
any() )->method(
'getUniqueId' )->will( $this->returnValue(
'X' ) );
3359 $mock->expects( $this->
any() )->method(
'accountCreationType' )
3361 $mock->expects( $this->
any() )->method(
'beginPrimaryAccountLink' )->will(
3364 $this->primaryauthMocks = [ $mock ];
3367 $this->
request->getSession()->setSecret(
'AuthManager::accountLinkState',
null );
3368 $ret = $this->manager->continueAccountLink( [] );
3370 $this->assertSame(
'authmanager-link-not-in-progress',
$ret->message->getKey() );
3372 $this->
request->getSession()->setSecret(
'AuthManager::accountLinkState',
3373 [
'username' =>
$user->getName() .
'<>' ] + $session );
3374 $ret = $this->manager->continueAccountLink( [] );
3376 $this->assertSame(
'noname',
$ret->message->getKey() );
3377 $this->assertNull( $this->
request->getSession()->getSecret(
'AuthManager::accountLinkState' ) );
3379 $id =
$user->getId();
3380 $this->
request->getSession()->setSecret(
'AuthManager::accountLinkState',
3381 [
'userid' => $id + 1 ] + $session );
3383 $ret = $this->manager->continueAccountLink( [] );
3384 $this->fail(
'Expected exception not thrown' );
3385 }
catch ( \UnexpectedValueException $ex ) {
3386 $this->assertEquals(
3387 "User \"{$user->getName()}\" is valid, but ID $id != " . ( $id + 1 ) .
'!',
3391 $this->assertNull( $this->
request->getSession()->getSecret(
'AuthManager::accountLinkState' ) );
3409 $req->primary = $primaryResponses;
3412 foreach ( [
'pre',
'primary' ]
as $key ) {
3413 $class = ucfirst( $key ) .
'AuthenticationProvider';
3414 $mocks[$key] = $this->getMockForAbstractClass(
3415 "MediaWiki\\Auth\\$class", [],
"Mock$class"
3417 $mocks[$key]->expects( $this->
any() )->method(
'getUniqueId' )
3418 ->will( $this->returnValue( $key ) );
3420 for ( $i = 2; $i <= 3; $i++ ) {
3421 $mocks[$key . $i] = $this->getMockForAbstractClass(
3422 "MediaWiki\\Auth\\$class", [],
"Mock$class"
3424 $mocks[$key . $i]->expects( $this->
any() )->method(
'getUniqueId' )
3425 ->will( $this->returnValue( $key . $i ) );
3429 $mocks[
'pre']->expects( $this->
any() )->method(
'testForAccountLink' )
3430 ->will( $this->returnCallback(
3434 $this->assertSame(
$user->getId(), $u->getId() );
3435 $this->assertSame(
$user->getName(), $u->getName() );
3440 $mocks[
'pre2']->expects( $this->atMost( 1 ) )->method(
'testForAccountLink' )
3443 $mocks[
'primary']->expects( $this->
any() )->method(
'accountCreationType' )
3446 $callback = $this->returnCallback(
function ( $u, $reqs )
use (
$user,
$req ) {
3447 $this->assertSame(
$user->getId(), $u->getId() );
3448 $this->assertSame(
$user->getName(), $u->getName() );
3450 foreach ( $reqs
as $r ) {
3451 $this->assertSame(
$user->getName(), $r->username );
3452 $foundReq = $foundReq || get_class( $r ) === get_class(
$req );
3454 $this->assertTrue( $foundReq,
'$reqs contains $req' );
3455 return array_shift(
$req->primary );
3457 $mocks[
'primary']->expects( $this->exactly( min( 1, $ct ) ) )
3458 ->method(
'beginPrimaryAccountLink' )
3459 ->will( $callback );
3460 $mocks[
'primary']->expects( $this->exactly( max( 0, $ct - 1 ) ) )
3461 ->method(
'continuePrimaryAccountLink' )
3462 ->will( $callback );
3465 $mocks[
'primary2']->expects( $this->
any() )->method(
'accountCreationType' )
3467 $mocks[
'primary2']->expects( $this->atMost( 1 ) )->method(
'beginPrimaryAccountLink' )
3468 ->will( $this->returnValue( $abstain ) );
3469 $mocks[
'primary2']->expects( $this->never() )->method(
'continuePrimaryAccountLink' );
3470 $mocks[
'primary3']->expects( $this->
any() )->method(
'accountCreationType' )
3472 $mocks[
'primary3']->expects( $this->never() )->method(
'beginPrimaryAccountLink' );
3473 $mocks[
'primary3']->expects( $this->never() )->method(
'continuePrimaryAccountLink' );
3475 $this->preauthMocks = [ $mocks[
'pre'], $mocks[
'pre2'] ];
3476 $this->primaryauthMocks = [ $mocks[
'primary3'], $mocks[
'primary2'], $mocks[
'primary'] ];
3477 $this->logger = new \TestLogger(
true,
function ( $message, $level ) {
3478 return $level === LogLevel::DEBUG ? null : $message;
3482 $constraint = \PHPUnit_Framework_Assert::logicalOr(
3486 $providers = array_merge( $this->preauthMocks, $this->primaryauthMocks );
3487 foreach ( $providers
as $p ) {
3488 $p->postCalled =
false;
3489 $p->expects( $this->atMost( 1 ) )->method(
'postAccountLink' )
3492 $this->assertSame(
'UTSysop',
$user->getName() );
3494 $this->assertThat(
$response->status, $constraint );
3502 foreach ( $managerResponses
as $i =>
$response ) {
3506 $expectLog[] = [ LogLevel::INFO,
'Account linked to {user} by primary' ];
3512 $ret = $this->manager->beginAccountLink(
$user, [
$req ],
'http://localhost/' );
3514 $ret = $this->manager->continueAccountLink( [
$req ] );
3516 if (
$response instanceof \Exception ) {
3517 $this->fail(
'Expected exception not thrown',
"Response $i" );
3519 }
catch ( \Exception $ex ) {
3520 if ( !
$response instanceof \Exception ) {
3523 $this->assertEquals(
$response->getMessage(), $ex->getMessage(),
"Response $i, exception" );
3524 $this->assertNull( $this->
request->getSession()->getSecret(
'AuthManager::accountLinkState' ),
3525 "Response $i, exception, session state" );
3529 $this->assertSame(
'http://localhost/',
$req->returnToUrl );
3536 $this->assertNull( $this->
request->getSession()->getSecret(
'AuthManager::accountLinkState' ),
3537 "Response $i, session state" );
3538 foreach ( $providers
as $p ) {
3539 $this->assertSame(
$response->status, $p->postCalled,
3540 "Response $i, post-auth callback called" );
3543 $this->assertNotNull(
3544 $this->
request->getSession()->getSecret(
'AuthManager::accountLinkState' ),
3545 "Response $i, session state"
3547 foreach (
$ret->neededRequests
as $neededReq ) {
3549 "Response $i, neededRequest action" );
3551 $this->assertEquals(
3552 $ret->neededRequests,
3554 "Response $i, continuation check"
3556 foreach ( $providers
as $p ) {
3557 $this->assertFalse( $p->postCalled,
"Response $i, post-auth callback not called" );
3564 $this->assertSame( $expectLog, $this->logger->getBuffer() );
3572 'Pre-link test fail in pre' => [
3579 'Failure in primary' => [
3586 'All primary abstain' => [
3595 'Primary UI, then redirect, then fail' => [
3604 'Primary redirect, then abstain' => [
3608 [
$req ],
'/foo.html', [
'foo' =>
'bar' ]
3614 new \DomainException(
3615 'MockPrimaryAuthenticationProvider::continuePrimaryAccountLink() returned ABSTAIN'
3619 'Primary UI, then pass' => [