%PDF- %PDF-
| Direktori : /www/varak.net/wiki.varak.net/tests/phpunit/includes/auth/ |
| Current File : //www/varak.net/wiki.varak.net/tests/phpunit/includes/auth/AuthManagerTest.php |
<?php
namespace MediaWiki\Auth;
use Config;
use MediaWiki\Session\SessionInfo;
use MediaWiki\Session\UserInfo;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use StatusValue;
use WebRequest;
use Wikimedia\ScopedCallback;
use Wikimedia\TestingAccessWrapper;
/**
* @group AuthManager
* @group Database
* @covers MediaWiki\Auth\AuthManager
*/
class AuthManagerTest extends \MediaWikiTestCase {
/** @var WebRequest */
protected $request;
/** @var Config */
protected $config;
/** @var LoggerInterface */
protected $logger;
protected $preauthMocks = [];
protected $primaryauthMocks = [];
protected $secondaryauthMocks = [];
/** @var AuthManager */
protected $manager;
/** @var TestingAccessWrapper */
protected $managerPriv;
protected function setUp() {
parent::setUp();
$this->setMwGlobals( [ 'wgAuth' => null ] );
}
/**
* Sets a mock on a hook
* @param string $hook
* @param object $expect From $this->once(), $this->never(), etc.
* @return object $mock->expects( $expect )->method( ... ).
*/
protected function hook( $hook, $expect ) {
$mock = $this->getMockBuilder( __CLASS__ )
->setMethods( [ "on$hook" ] )
->getMock();
$this->setTemporaryHook( $hook, $mock );
return $mock->expects( $expect )->method( "on$hook" );
}
/**
* Unsets a hook
* @param string $hook
*/
protected function unhook( $hook ) {
global $wgHooks;
$wgHooks[$hook] = [];
}
/**
* Ensure a value is a clean Message object
* @param string|Message $key
* @param array $params
* @return Message
*/
protected function message( $key, $params = [] ) {
if ( $key === null ) {
return null;
}
if ( $key instanceof \MessageSpecifier ) {
$params = $key->getParams();
$key = $key->getKey();
}
return new \Message( $key, $params, \Language::factory( 'en' ) );
}
/**
* Test two AuthenticationResponses for equality. We don't want to use regular assertEquals
* because that recursively compares members, which leads to false negatives if e.g. Language
* caches are reset.
*
* @param AuthenticationResponse $response1
* @param AuthenticationResponse $response2
* @param string $msg
* @return bool
*/
private function assertResponseEquals(
AuthenticationResponse $expected, AuthenticationResponse $actual, $msg = ''
) {
foreach ( ( new \ReflectionClass( $expected ) )->getProperties() as $prop ) {
$name = $prop->getName();
$usedMsg = ltrim( "$msg ($name)" );
if ( $name === 'message' && $expected->message ) {
$this->assertSame( $expected->message->serialize(), $actual->message->serialize(),
$usedMsg );
} else {
$this->assertEquals( $expected->$name, $actual->$name, $usedMsg );
}
}
}
/**
* Initialize the AuthManagerConfig variable in $this->config
*
* Uses data from the various 'mocks' fields.
*/
protected function initializeConfig() {
$config = [
'preauth' => [
],
'primaryauth' => [
],
'secondaryauth' => [
],
];
foreach ( [ 'preauth', 'primaryauth', 'secondaryauth' ] as $type ) {
$key = $type . 'Mocks';
foreach ( $this->$key as $mock ) {
$config[$type][$mock->getUniqueId()] = [ 'factory' => function () use ( $mock ) {
return $mock;
} ];
}
}
$this->config->set( 'AuthManagerConfig', $config );
$this->config->set( 'LanguageCode', 'en' );
$this->config->set( 'NewUserLog', false );
}
/**
* Initialize $this->manager
* @param bool $regen Force a call to $this->initializeConfig()
*/
protected function initializeManager( $regen = false ) {
if ( $regen || !$this->config ) {
$this->config = new \HashConfig();
}
if ( $regen || !$this->request ) {
$this->request = new \FauxRequest();
}
if ( !$this->logger ) {
$this->logger = new \TestLogger();
}
if ( $regen || !$this->config->has( 'AuthManagerConfig' ) ) {
$this->initializeConfig();
}
$this->manager = new AuthManager( $this->request, $this->config );
$this->manager->setLogger( $this->logger );
$this->managerPriv = TestingAccessWrapper::newFromObject( $this->manager );
}
/**
* Setup SessionManager with a mock session provider
* @param bool|null $canChangeUser If non-null, canChangeUser will be mocked to return this
* @param array $methods Additional methods to mock
* @return array (MediaWiki\Session\SessionProvider, ScopedCallback)
*/
protected function getMockSessionProvider( $canChangeUser = null, array $methods = [] ) {
if ( !$this->config ) {
$this->config = new \HashConfig();
$this->initializeConfig();
}
$this->config->set( 'ObjectCacheSessionExpiry', 100 );
$methods[] = '__toString';
$methods[] = 'describe';
if ( $canChangeUser !== null ) {
$methods[] = 'canChangeUser';
}
$provider = $this->getMockBuilder( \DummySessionProvider::class )
->setMethods( $methods )
->getMock();
$provider->expects( $this->any() )->method( '__toString' )
->will( $this->returnValue( 'MockSessionProvider' ) );
$provider->expects( $this->any() )->method( 'describe' )
->will( $this->returnValue( 'MockSessionProvider sessions' ) );
if ( $canChangeUser !== null ) {
$provider->expects( $this->any() )->method( 'canChangeUser' )
->will( $this->returnValue( $canChangeUser ) );
}
$this->config->set( 'SessionProviders', [
[ 'factory' => function () use ( $provider ) {
return $provider;
} ],
] );
$manager = new \MediaWiki\Session\SessionManager( [
'config' => $this->config,
'logger' => new \Psr\Log\NullLogger(),
'store' => new \HashBagOStuff(),
] );
TestingAccessWrapper::newFromObject( $manager )->getProvider( (string)$provider );
$reset = \MediaWiki\Session\TestUtils::setSessionManagerSingleton( $manager );
if ( $this->request ) {
$manager->getSessionForRequest( $this->request );
}
return [ $provider, $reset ];
}
public function testSingleton() {
// Temporarily clear out the global singleton, if any, to test creating
// one.
$rProp = new \ReflectionProperty( AuthManager::class, 'instance' );
$rProp->setAccessible( true );
$old = $rProp->getValue();
$cb = new ScopedCallback( [ $rProp, 'setValue' ], [ $old ] );
$rProp->setValue( null );
$singleton = AuthManager::singleton();
$this->assertInstanceOf( AuthManager::class, AuthManager::singleton() );
$this->assertSame( $singleton, AuthManager::singleton() );
$this->assertSame( \RequestContext::getMain()->getRequest(), $singleton->getRequest() );
$this->assertSame(
\RequestContext::getMain()->getConfig(),
TestingAccessWrapper::newFromObject( $singleton )->config
);
}
public function testCanAuthenticateNow() {
$this->initializeManager();
list( $provider, $reset ) = $this->getMockSessionProvider( false );
$this->assertFalse( $this->manager->canAuthenticateNow() );
ScopedCallback::consume( $reset );
list( $provider, $reset ) = $this->getMockSessionProvider( true );
$this->assertTrue( $this->manager->canAuthenticateNow() );
ScopedCallback::consume( $reset );
}
public function testNormalizeUsername() {
$mocks = [
$this->getMockForAbstractClass( PrimaryAuthenticationProvider::class ),
$this->getMockForAbstractClass( PrimaryAuthenticationProvider::class ),
$this->getMockForAbstractClass( PrimaryAuthenticationProvider::class ),
$this->getMockForAbstractClass( PrimaryAuthenticationProvider::class ),
];
foreach ( $mocks as $key => $mock ) {
$mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( $key ) );
}
$mocks[0]->expects( $this->once() )->method( 'providerNormalizeUsername' )
->with( $this->identicalTo( 'XYZ' ) )
->willReturn( 'Foo' );
$mocks[1]->expects( $this->once() )->method( 'providerNormalizeUsername' )
->with( $this->identicalTo( 'XYZ' ) )
->willReturn( 'Foo' );
$mocks[2]->expects( $this->once() )->method( 'providerNormalizeUsername' )
->with( $this->identicalTo( 'XYZ' ) )
->willReturn( null );
$mocks[3]->expects( $this->once() )->method( 'providerNormalizeUsername' )
->with( $this->identicalTo( 'XYZ' ) )
->willReturn( 'Bar!' );
$this->primaryauthMocks = $mocks;
$this->initializeManager();
$this->assertSame( [ 'Foo', 'Bar!' ], $this->manager->normalizeUsername( 'XYZ' ) );
}
/**
* @dataProvider provideSecuritySensitiveOperationStatus
* @param bool $mutableSession
*/
public function testSecuritySensitiveOperationStatus( $mutableSession ) {
$this->logger = new \Psr\Log\NullLogger();
$user = \User::newFromName( 'UTSysop' );
$provideUser = null;
$reauth = $mutableSession ? AuthManager::SEC_REAUTH : AuthManager::SEC_FAIL;
list( $provider, $reset ) = $this->getMockSessionProvider(
$mutableSession, [ 'provideSessionInfo' ]
);
$provider->expects( $this->any() )->method( 'provideSessionInfo' )
->will( $this->returnCallback( function () use ( $provider, &$provideUser ) {
return new SessionInfo( SessionInfo::MIN_PRIORITY, [
'provider' => $provider,
'id' => \DummySessionProvider::ID,
'persisted' => true,
'userInfo' => UserInfo::newFromUser( $provideUser, true )
] );
} ) );
$this->initializeManager();
$this->config->set( 'ReauthenticateTime', [] );
$this->config->set( 'AllowSecuritySensitiveOperationIfCannotReauthenticate', [] );
$provideUser = new \User;
$session = $provider->getManager()->getSessionForRequest( $this->request );
$this->assertSame( 0, $session->getUser()->getId(), 'sanity check' );
// Anonymous user => reauth
$session->set( 'AuthManager:lastAuthId', 0 );
$session->set( 'AuthManager:lastAuthTimestamp', time() - 5 );
$this->assertSame( $reauth, $this->manager->securitySensitiveOperationStatus( 'foo' ) );
$provideUser = $user;
$session = $provider->getManager()->getSessionForRequest( $this->request );
$this->assertSame( $user->getId(), $session->getUser()->getId(), 'sanity check' );
// Error for no default (only gets thrown for non-anonymous user)
$session->set( 'AuthManager:lastAuthId', $user->getId() + 1 );
$session->set( 'AuthManager:lastAuthTimestamp', time() - 5 );
try {
$this->manager->securitySensitiveOperationStatus( 'foo' );
$this->fail( 'Expected exception not thrown' );
} catch ( \UnexpectedValueException $ex ) {
$this->assertSame(
$mutableSession
? '$wgReauthenticateTime lacks a default'
: '$wgAllowSecuritySensitiveOperationIfCannotReauthenticate lacks a default',
$ex->getMessage()
);
}
if ( $mutableSession ) {
$this->config->set( 'ReauthenticateTime', [
'test' => 100,
'test2' => -1,
'default' => 10,
] );
// Mismatched user ID
$session->set( 'AuthManager:lastAuthId', $user->getId() + 1 );
$session->set( 'AuthManager:lastAuthTimestamp', time() - 5 );
$this->assertSame(
AuthManager::SEC_REAUTH, $this->manager->securitySensitiveOperationStatus( 'foo' )
);
$this->assertSame(
AuthManager::SEC_REAUTH, $this->manager->securitySensitiveOperationStatus( 'test' )
);
$this->assertSame(
AuthManager::SEC_OK, $this->manager->securitySensitiveOperationStatus( 'test2' )
);
// Missing time
$session->set( 'AuthManager:lastAuthId', $user->getId() );
$session->set( 'AuthManager:lastAuthTimestamp', null );
$this->assertSame(
AuthManager::SEC_REAUTH, $this->manager->securitySensitiveOperationStatus( 'foo' )
);
$this->assertSame(
AuthManager::SEC_REAUTH, $this->manager->securitySensitiveOperationStatus( 'test' )
);
$this->assertSame(
AuthManager::SEC_OK, $this->manager->securitySensitiveOperationStatus( 'test2' )
);
// Recent enough to pass
$session->set( 'AuthManager:lastAuthTimestamp', time() - 5 );
$this->assertSame(
AuthManager::SEC_OK, $this->manager->securitySensitiveOperationStatus( 'foo' )
);
// Not recent enough to pass
$session->set( 'AuthManager:lastAuthTimestamp', time() - 20 );
$this->assertSame(
AuthManager::SEC_REAUTH, $this->manager->securitySensitiveOperationStatus( 'foo' )
);
// But recent enough for the 'test' operation
$this->assertSame(
AuthManager::SEC_OK, $this->manager->securitySensitiveOperationStatus( 'test' )
);
} else {
$this->config->set( 'AllowSecuritySensitiveOperationIfCannotReauthenticate', [
'test' => false,
'default' => true,
] );
$this->assertEquals(
AuthManager::SEC_OK, $this->manager->securitySensitiveOperationStatus( 'foo' )
);
$this->assertEquals(
AuthManager::SEC_FAIL, $this->manager->securitySensitiveOperationStatus( 'test' )
);
}
// Test hook, all three possible values
foreach ( [
AuthManager::SEC_OK => AuthManager::SEC_OK,
AuthManager::SEC_REAUTH => $reauth,
AuthManager::SEC_FAIL => AuthManager::SEC_FAIL,
] as $hook => $expect ) {
$this->hook( 'SecuritySensitiveOperationStatus', $this->exactly( 2 ) )
->with(
$this->anything(),
$this->anything(),
$this->callback( function ( $s ) use ( $session ) {
return $s->getId() === $session->getId();
} ),
$mutableSession ? $this->equalTo( 500, 1 ) : $this->equalTo( -1 )
)
->will( $this->returnCallback( function ( &$v ) use ( $hook ) {
$v = $hook;
return true;
} ) );
$session->set( 'AuthManager:lastAuthTimestamp', time() - 500 );
$this->assertEquals(
$expect, $this->manager->securitySensitiveOperationStatus( 'test' ), "hook $hook"
);
$this->assertEquals(
$expect, $this->manager->securitySensitiveOperationStatus( 'test2' ), "hook $hook"
);
$this->unhook( 'SecuritySensitiveOperationStatus' );
}
ScopedCallback::consume( $reset );
}
public function onSecuritySensitiveOperationStatus( &$status, $operation, $session, $time ) {
}
public static function provideSecuritySensitiveOperationStatus() {
return [
[ true ],
[ false ],
];
}
/**
* @dataProvider provideUserCanAuthenticate
* @param bool $primary1Can
* @param bool $primary2Can
* @param bool $expect
*/
public function testUserCanAuthenticate( $primary1Can, $primary2Can, $expect ) {
$mock1 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$mock1->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( 'primary1' ) );
$mock1->expects( $this->any() )->method( 'testUserCanAuthenticate' )
->with( $this->equalTo( 'UTSysop' ) )
->will( $this->returnValue( $primary1Can ) );
$mock2 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$mock2->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( 'primary2' ) );
$mock2->expects( $this->any() )->method( 'testUserCanAuthenticate' )
->with( $this->equalTo( 'UTSysop' ) )
->will( $this->returnValue( $primary2Can ) );
$this->primaryauthMocks = [ $mock1, $mock2 ];
$this->initializeManager( true );
$this->assertSame( $expect, $this->manager->userCanAuthenticate( 'UTSysop' ) );
}
public static function provideUserCanAuthenticate() {
return [
[ false, false, false ],
[ true, false, true ],
[ false, true, true ],
[ true, true, true ],
];
}
public function testRevokeAccessForUser() {
$this->initializeManager();
$mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$mock->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( 'primary' ) );
$mock->expects( $this->once() )->method( 'providerRevokeAccessForUser' )
->with( $this->equalTo( 'UTSysop' ) );
$this->primaryauthMocks = [ $mock ];
$this->initializeManager( true );
$this->logger->setCollect( true );
$this->manager->revokeAccessForUser( 'UTSysop' );
$this->assertSame( [
[ LogLevel::INFO, 'Revoking access for {user}' ],
], $this->logger->getBuffer() );
}
public function testProviderCreation() {
$mocks = [
'pre' => $this->getMockForAbstractClass( PreAuthenticationProvider::class ),
'primary' => $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class ),
'secondary' => $this->getMockForAbstractClass( SecondaryAuthenticationProvider::class ),
];
foreach ( $mocks as $key => $mock ) {
$mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( $key ) );
$mock->expects( $this->once() )->method( 'setLogger' );
$mock->expects( $this->once() )->method( 'setManager' );
$mock->expects( $this->once() )->method( 'setConfig' );
}
$this->preauthMocks = [ $mocks['pre'] ];
$this->primaryauthMocks = [ $mocks['primary'] ];
$this->secondaryauthMocks = [ $mocks['secondary'] ];
// Normal operation
$this->initializeManager();
$this->assertSame(
$mocks['primary'],
$this->managerPriv->getAuthenticationProvider( 'primary' )
);
$this->assertSame(
$mocks['secondary'],
$this->managerPriv->getAuthenticationProvider( 'secondary' )
);
$this->assertSame(
$mocks['pre'],
$this->managerPriv->getAuthenticationProvider( 'pre' )
);
$this->assertSame(
[ 'pre' => $mocks['pre'] ],
$this->managerPriv->getPreAuthenticationProviders()
);
$this->assertSame(
[ 'primary' => $mocks['primary'] ],
$this->managerPriv->getPrimaryAuthenticationProviders()
);
$this->assertSame(
[ 'secondary' => $mocks['secondary'] ],
$this->managerPriv->getSecondaryAuthenticationProviders()
);
// Duplicate IDs
$mock1 = $this->getMockForAbstractClass( PreAuthenticationProvider::class );
$mock2 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$mock1->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
$mock2->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
$this->preauthMocks = [ $mock1 ];
$this->primaryauthMocks = [ $mock2 ];
$this->secondaryauthMocks = [];
$this->initializeManager( true );
try {
$this->managerPriv->getAuthenticationProvider( 'Y' );
$this->fail( 'Expected exception not thrown' );
} catch ( \RuntimeException $ex ) {
$class1 = get_class( $mock1 );
$class2 = get_class( $mock2 );
$this->assertSame(
"Duplicate specifications for id X (classes $class1 and $class2)", $ex->getMessage()
);
}
// Wrong classes
$mock = $this->getMockForAbstractClass( AuthenticationProvider::class );
$mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
$class = get_class( $mock );
$this->preauthMocks = [ $mock ];
$this->primaryauthMocks = [ $mock ];
$this->secondaryauthMocks = [ $mock ];
$this->initializeManager( true );
try {
$this->managerPriv->getPreAuthenticationProviders();
$this->fail( 'Expected exception not thrown' );
} catch ( \RuntimeException $ex ) {
$this->assertSame(
"Expected instance of MediaWiki\\Auth\\PreAuthenticationProvider, got $class",
$ex->getMessage()
);
}
try {
$this->managerPriv->getPrimaryAuthenticationProviders();
$this->fail( 'Expected exception not thrown' );
} catch ( \RuntimeException $ex ) {
$this->assertSame(
"Expected instance of MediaWiki\\Auth\\PrimaryAuthenticationProvider, got $class",
$ex->getMessage()
);
}
try {
$this->managerPriv->getSecondaryAuthenticationProviders();
$this->fail( 'Expected exception not thrown' );
} catch ( \RuntimeException $ex ) {
$this->assertSame(
"Expected instance of MediaWiki\\Auth\\SecondaryAuthenticationProvider, got $class",
$ex->getMessage()
);
}
// Sorting
$mock1 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$mock2 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$mock3 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$mock1->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'A' ) );
$mock2->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'B' ) );
$mock3->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'C' ) );
$this->preauthMocks = [];
$this->primaryauthMocks = [ $mock1, $mock2, $mock3 ];
$this->secondaryauthMocks = [];
$this->initializeConfig();
$config = $this->config->get( 'AuthManagerConfig' );
$this->initializeManager( false );
$this->assertSame(
[ 'A' => $mock1, 'B' => $mock2, 'C' => $mock3 ],
$this->managerPriv->getPrimaryAuthenticationProviders(),
'sanity check'
);
$config['primaryauth']['A']['sort'] = 100;
$config['primaryauth']['C']['sort'] = -1;
$this->config->set( 'AuthManagerConfig', $config );
$this->initializeManager( false );
$this->assertSame(
[ 'C' => $mock3, 'B' => $mock2, 'A' => $mock1 ],
$this->managerPriv->getPrimaryAuthenticationProviders()
);
}
/**
* @dataProvider provideSetDefaultUserOptions
*/
public function testSetDefaultUserOptions(
$contLang, $useContextLang, $expectedLang, $expectedVariant
) {
$this->initializeManager();
$this->setContentLang( $contLang );
$context = \RequestContext::getMain();
$reset = new ScopedCallback( [ $context, 'setLanguage' ], [ $context->getLanguage() ] );
$context->setLanguage( 'de' );
$user = \User::newFromName( self::usernameForCreation() );
$user->addToDatabase();
$oldToken = $user->getToken();
$this->managerPriv->setDefaultUserOptions( $user, $useContextLang );
$user->saveSettings();
$this->assertNotEquals( $oldToken, $user->getToken() );
$this->assertSame( $expectedLang, $user->getOption( 'language' ) );
$this->assertSame( $expectedVariant, $user->getOption( 'variant' ) );
}
public function provideSetDefaultUserOptions() {
return [
[ 'zh', false, 'zh', 'zh' ],
[ 'zh', true, 'de', 'zh' ],
[ 'fr', true, 'de', null ],
];
}
public function testForcePrimaryAuthenticationProviders() {
$mockA = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$mockB = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$mockB2 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$mockA->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'A' ) );
$mockB->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'B' ) );
$mockB2->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'B' ) );
$this->primaryauthMocks = [ $mockA ];
$this->logger = new \TestLogger( true );
// Test without first initializing the configured providers
$this->initializeManager();
$this->manager->forcePrimaryAuthenticationProviders( [ $mockB ], 'testing' );
$this->assertSame(
[ 'B' => $mockB ], $this->managerPriv->getPrimaryAuthenticationProviders()
);
$this->assertSame( null, $this->managerPriv->getAuthenticationProvider( 'A' ) );
$this->assertSame( $mockB, $this->managerPriv->getAuthenticationProvider( 'B' ) );
$this->assertSame( [
[ LogLevel::WARNING, 'Overriding AuthManager primary authn because testing' ],
], $this->logger->getBuffer() );
$this->logger->clearBuffer();
// Test with first initializing the configured providers
$this->initializeManager();
$this->assertSame( $mockA, $this->managerPriv->getAuthenticationProvider( 'A' ) );
$this->assertSame( null, $this->managerPriv->getAuthenticationProvider( 'B' ) );
$this->request->getSession()->setSecret( 'AuthManager::authnState', 'test' );
$this->request->getSession()->setSecret( 'AuthManager::accountCreationState', 'test' );
$this->manager->forcePrimaryAuthenticationProviders( [ $mockB ], 'testing' );
$this->assertSame(
[ 'B' => $mockB ], $this->managerPriv->getPrimaryAuthenticationProviders()
);
$this->assertSame( null, $this->managerPriv->getAuthenticationProvider( 'A' ) );
$this->assertSame( $mockB, $this->managerPriv->getAuthenticationProvider( 'B' ) );
$this->assertNull( $this->request->getSession()->getSecret( 'AuthManager::authnState' ) );
$this->assertNull(
$this->request->getSession()->getSecret( 'AuthManager::accountCreationState' )
);
$this->assertSame( [
[ LogLevel::WARNING, 'Overriding AuthManager primary authn because testing' ],
[
LogLevel::WARNING,
'PrimaryAuthenticationProviders have already been accessed! I hope nothing breaks.'
],
], $this->logger->getBuffer() );
$this->logger->clearBuffer();
// Test duplicate IDs
$this->initializeManager();
try {
$this->manager->forcePrimaryAuthenticationProviders( [ $mockB, $mockB2 ], 'testing' );
$this->fail( 'Expected exception not thrown' );
} catch ( \RuntimeException $ex ) {
$class1 = get_class( $mockB );
$class2 = get_class( $mockB2 );
$this->assertSame(
"Duplicate specifications for id B (classes $class2 and $class1)", $ex->getMessage()
);
}
// Wrong classes
$mock = $this->getMockForAbstractClass( AuthenticationProvider::class );
$mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
$class = get_class( $mock );
try {
$this->manager->forcePrimaryAuthenticationProviders( [ $mock ], 'testing' );
$this->fail( 'Expected exception not thrown' );
} catch ( \RuntimeException $ex ) {
$this->assertSame(
"Expected instance of MediaWiki\\Auth\\PrimaryAuthenticationProvider, got $class",
$ex->getMessage()
);
}
}
public function testBeginAuthentication() {
$this->initializeManager();
// Immutable session
list( $provider, $reset ) = $this->getMockSessionProvider( false );
$this->hook( 'UserLoggedIn', $this->never() );
$this->request->getSession()->setSecret( 'AuthManager::authnState', 'test' );
try {
$this->manager->beginAuthentication( [], 'http://localhost/' );
$this->fail( 'Expected exception not thrown' );
} catch ( \LogicException $ex ) {
$this->assertSame( 'Authentication is not possible now', $ex->getMessage() );
}
$this->unhook( 'UserLoggedIn' );
$this->assertNull( $this->request->getSession()->getSecret( 'AuthManager::authnState' ) );
ScopedCallback::consume( $reset );
$this->initializeManager( true );
// CreatedAccountAuthenticationRequest
$user = \User::newFromName( 'UTSysop' );
$reqs = [
new CreatedAccountAuthenticationRequest( $user->getId(), $user->getName() )
];
$this->hook( 'UserLoggedIn', $this->never() );
try {
$this->manager->beginAuthentication( $reqs, 'http://localhost/' );
$this->fail( 'Expected exception not thrown' );
} catch ( \LogicException $ex ) {
$this->assertSame(
'CreatedAccountAuthenticationRequests are only valid on the same AuthManager ' .
'that created the account',
$ex->getMessage()
);
}
$this->unhook( 'UserLoggedIn' );
$this->request->getSession()->clear();
$this->request->getSession()->setSecret( 'AuthManager::authnState', 'test' );
$this->managerPriv->createdAccountAuthenticationRequests = [ $reqs[0] ];
$this->hook( 'UserLoggedIn', $this->once() )
->with( $this->callback( function ( $u ) use ( $user ) {
return $user->getId() === $u->getId() && $user->getName() === $u->getName();
} ) );
$this->hook( 'AuthManagerLoginAuthenticateAudit', $this->once() );
$this->logger->setCollect( true );
$ret = $this->manager->beginAuthentication( $reqs, 'http://localhost/' );
$this->logger->setCollect( false );
$this->unhook( 'UserLoggedIn' );
$this->unhook( 'AuthManagerLoginAuthenticateAudit' );
$this->assertSame( AuthenticationResponse::PASS, $ret->status );
$this->assertSame( $user->getName(), $ret->username );
$this->assertSame( $user->getId(), $this->request->getSessionData( 'AuthManager:lastAuthId' ) );
$this->assertEquals(
time(), $this->request->getSessionData( 'AuthManager:lastAuthTimestamp' ),
'timestamp ±1', 1
);
$this->assertNull( $this->request->getSession()->getSecret( 'AuthManager::authnState' ) );
$this->assertSame( $user->getId(), $this->request->getSession()->getUser()->getId() );
$this->assertSame( [
[ LogLevel::INFO, 'Logging in {user} after account creation' ],
], $this->logger->getBuffer() );
}
public function testCreateFromLogin() {
$user = \User::newFromName( 'UTSysop' );
$req1 = $this->createMock( AuthenticationRequest::class );
$req2 = $this->createMock( AuthenticationRequest::class );
$req3 = $this->createMock( AuthenticationRequest::class );
$userReq = new UsernameAuthenticationRequest;
$userReq->username = 'UTDummy';
$req1->returnToUrl = 'http://localhost/';
$req2->returnToUrl = 'http://localhost/';
$req3->returnToUrl = 'http://localhost/';
$req3->username = 'UTDummy';
$userReq->returnToUrl = 'http://localhost/';
// Passing one into beginAuthentication(), and an immediate FAIL
$primary = $this->getMockForAbstractClass( AbstractPrimaryAuthenticationProvider::class );
$this->primaryauthMocks = [ $primary ];
$this->initializeManager( true );
$res = AuthenticationResponse::newFail( wfMessage( 'foo' ) );
$res->createRequest = $req1;
$primary->expects( $this->any() )->method( 'beginPrimaryAuthentication' )
->will( $this->returnValue( $res ) );
$createReq = new CreateFromLoginAuthenticationRequest(
null, [ $req2->getUniqueId() => $req2 ]
);
$this->logger->setCollect( true );
$ret = $this->manager->beginAuthentication( [ $createReq ], 'http://localhost/' );
$this->logger->setCollect( false );
$this->assertSame( AuthenticationResponse::FAIL, $ret->status );
$this->assertInstanceOf( CreateFromLoginAuthenticationRequest::class, $ret->createRequest );
$this->assertSame( $req1, $ret->createRequest->createRequest );
$this->assertEquals( [ $req2->getUniqueId() => $req2 ], $ret->createRequest->maybeLink );
// UI, then FAIL in beginAuthentication()
$primary = $this->getMockBuilder( AbstractPrimaryAuthenticationProvider::class )
->setMethods( [ 'continuePrimaryAuthentication' ] )
->getMockForAbstractClass();
$this->primaryauthMocks = [ $primary ];
$this->initializeManager( true );
$primary->expects( $this->any() )->method( 'beginPrimaryAuthentication' )
->will( $this->returnValue(
AuthenticationResponse::newUI( [ $req1 ], wfMessage( 'foo' ) )
) );
$res = AuthenticationResponse::newFail( wfMessage( 'foo' ) );
$res->createRequest = $req2;
$primary->expects( $this->any() )->method( 'continuePrimaryAuthentication' )
->will( $this->returnValue( $res ) );
$this->logger->setCollect( true );
$ret = $this->manager->beginAuthentication( [], 'http://localhost/' );
$this->assertSame( AuthenticationResponse::UI, $ret->status, 'sanity check' );
$ret = $this->manager->continueAuthentication( [] );
$this->logger->setCollect( false );
$this->assertSame( AuthenticationResponse::FAIL, $ret->status );
$this->assertInstanceOf( CreateFromLoginAuthenticationRequest::class, $ret->createRequest );
$this->assertSame( $req2, $ret->createRequest->createRequest );
$this->assertEquals( [], $ret->createRequest->maybeLink );
// Pass into beginAccountCreation(), see that maybeLink and createRequest get copied
$primary = $this->getMockForAbstractClass( AbstractPrimaryAuthenticationProvider::class );
$this->primaryauthMocks = [ $primary ];
$this->initializeManager( true );
$createReq = new CreateFromLoginAuthenticationRequest( $req3, [ $req2 ] );
$createReq->returnToUrl = 'http://localhost/';
$createReq->username = 'UTDummy';
$res = AuthenticationResponse::newUI( [ $req1 ], wfMessage( 'foo' ) );
$primary->expects( $this->any() )->method( 'beginPrimaryAccountCreation' )
->with( $this->anything(), $this->anything(), [ $userReq, $createReq, $req3 ] )
->will( $this->returnValue( $res ) );
$primary->expects( $this->any() )->method( 'accountCreationType' )
->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
$this->logger->setCollect( true );
$ret = $this->manager->beginAccountCreation(
$user, [ $userReq, $createReq ], 'http://localhost/'
);
$this->logger->setCollect( false );
$this->assertSame( AuthenticationResponse::UI, $ret->status );
$state = $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' );
$this->assertNotNull( $state );
$this->assertEquals( [ $userReq, $createReq, $req3 ], $state['reqs'] );
$this->assertEquals( [ $req2 ], $state['maybeLink'] );
}
/**
* @dataProvider provideAuthentication
* @param StatusValue $preResponse
* @param array $primaryResponses
* @param array $secondaryResponses
* @param array $managerResponses
* @param bool $link Whether the primary authentication provider is a "link" provider
*/
public function testAuthentication(
StatusValue $preResponse, array $primaryResponses, array $secondaryResponses,
array $managerResponses, $link = false
) {
$this->initializeManager();
$user = \User::newFromName( 'UTSysop' );
$id = $user->getId();
$name = $user->getName();
// Set up lots of mocks...
$req = new RememberMeAuthenticationRequest;
$req->rememberMe = (bool)rand( 0, 1 );
$req->pre = $preResponse;
$req->primary = $primaryResponses;
$req->secondary = $secondaryResponses;
$mocks = [];
foreach ( [ 'pre', 'primary', 'secondary' ] as $key ) {
$class = ucfirst( $key ) . 'AuthenticationProvider';
$mocks[$key] = $this->getMockForAbstractClass(
"MediaWiki\\Auth\\$class", [], "Mock$class"
);
$mocks[$key]->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( $key ) );
$mocks[$key . '2'] = $this->getMockForAbstractClass( "MediaWiki\\Auth\\$class" );
$mocks[$key . '2']->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( $key . '2' ) );
$mocks[$key . '3'] = $this->getMockForAbstractClass( "MediaWiki\\Auth\\$class" );
$mocks[$key . '3']->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( $key . '3' ) );
}
foreach ( $mocks as $mock ) {
$mock->expects( $this->any() )->method( 'getAuthenticationRequests' )
->will( $this->returnValue( [] ) );
}
$mocks['pre']->expects( $this->once() )->method( 'testForAuthentication' )
->will( $this->returnCallback( function ( $reqs ) use ( $req ) {
$this->assertContains( $req, $reqs );
return $req->pre;
} ) );
$ct = count( $req->primary );
$callback = $this->returnCallback( function ( $reqs ) use ( $req ) {
$this->assertContains( $req, $reqs );
return array_shift( $req->primary );
} );
$mocks['primary']->expects( $this->exactly( min( 1, $ct ) ) )
->method( 'beginPrimaryAuthentication' )
->will( $callback );
$mocks['primary']->expects( $this->exactly( max( 0, $ct - 1 ) ) )
->method( 'continuePrimaryAuthentication' )
->will( $callback );
if ( $link ) {
$mocks['primary']->expects( $this->any() )->method( 'accountCreationType' )
->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_LINK ) );
}
$ct = count( $req->secondary );
$callback = $this->returnCallback( function ( $user, $reqs ) use ( $id, $name, $req ) {
$this->assertSame( $id, $user->getId() );
$this->assertSame( $name, $user->getName() );
$this->assertContains( $req, $reqs );
return array_shift( $req->secondary );
} );
$mocks['secondary']->expects( $this->exactly( min( 1, $ct ) ) )
->method( 'beginSecondaryAuthentication' )
->will( $callback );
$mocks['secondary']->expects( $this->exactly( max( 0, $ct - 1 ) ) )
->method( 'continueSecondaryAuthentication' )
->will( $callback );
$abstain = AuthenticationResponse::newAbstain();
$mocks['pre2']->expects( $this->atMost( 1 ) )->method( 'testForAuthentication' )
->will( $this->returnValue( StatusValue::newGood() ) );
$mocks['primary2']->expects( $this->atMost( 1 ) )->method( 'beginPrimaryAuthentication' )
->will( $this->returnValue( $abstain ) );
$mocks['primary2']->expects( $this->never() )->method( 'continuePrimaryAuthentication' );
$mocks['secondary2']->expects( $this->atMost( 1 ) )->method( 'beginSecondaryAuthentication' )
->will( $this->returnValue( $abstain ) );
$mocks['secondary2']->expects( $this->never() )->method( 'continueSecondaryAuthentication' );
$mocks['secondary3']->expects( $this->atMost( 1 ) )->method( 'beginSecondaryAuthentication' )
->will( $this->returnValue( $abstain ) );
$mocks['secondary3']->expects( $this->never() )->method( 'continueSecondaryAuthentication' );
$this->preauthMocks = [ $mocks['pre'], $mocks['pre2'] ];
$this->primaryauthMocks = [ $mocks['primary'], $mocks['primary2'] ];
$this->secondaryauthMocks = [
$mocks['secondary3'], $mocks['secondary'], $mocks['secondary2'],
// So linking happens
new ConfirmLinkSecondaryAuthenticationProvider,
];
$this->initializeManager( true );
$this->logger->setCollect( true );
$constraint = \PHPUnit_Framework_Assert::logicalOr(
$this->equalTo( AuthenticationResponse::PASS ),
$this->equalTo( AuthenticationResponse::FAIL )
);
$providers = array_filter(
array_merge(
$this->preauthMocks, $this->primaryauthMocks, $this->secondaryauthMocks
),
function ( $p ) {
return is_callable( [ $p, 'expects' ] );
}
);
foreach ( $providers as $p ) {
$p->postCalled = false;
$p->expects( $this->atMost( 1 ) )->method( 'postAuthentication' )
->willReturnCallback( function ( $user, $response ) use ( $constraint, $p ) {
if ( $user !== null ) {
$this->assertInstanceOf( \User::class, $user );
$this->assertSame( 'UTSysop', $user->getName() );
}
$this->assertInstanceOf( AuthenticationResponse::class, $response );
$this->assertThat( $response->status, $constraint );
$p->postCalled = $response->status;
} );
}
$session = $this->request->getSession();
$session->setRememberUser( !$req->rememberMe );
foreach ( $managerResponses as $i => $response ) {
$success = $response instanceof AuthenticationResponse &&
$response->status === AuthenticationResponse::PASS;
if ( $success ) {
$this->hook( 'UserLoggedIn', $this->once() )
->with( $this->callback( function ( $user ) use ( $id, $name ) {
return $user->getId() === $id && $user->getName() === $name;
} ) );
} else {
$this->hook( 'UserLoggedIn', $this->never() );
}
if ( $success || (
$response instanceof AuthenticationResponse &&
$response->status === AuthenticationResponse::FAIL &&
$response->message->getKey() !== 'authmanager-authn-not-in-progress' &&
$response->message->getKey() !== 'authmanager-authn-no-primary'
)
) {
$this->hook( 'AuthManagerLoginAuthenticateAudit', $this->once() );
} else {
$this->hook( 'AuthManagerLoginAuthenticateAudit', $this->never() );
}
$ex = null;
try {
if ( !$i ) {
$ret = $this->manager->beginAuthentication( [ $req ], 'http://localhost/' );
} else {
$ret = $this->manager->continueAuthentication( [ $req ] );
}
if ( $response instanceof \Exception ) {
$this->fail( 'Expected exception not thrown', "Response $i" );
}
} catch ( \Exception $ex ) {
if ( !$response instanceof \Exception ) {
throw $ex;
}
$this->assertEquals( $response->getMessage(), $ex->getMessage(), "Response $i, exception" );
$this->assertNull( $session->getSecret( 'AuthManager::authnState' ),
"Response $i, exception, session state" );
$this->unhook( 'UserLoggedIn' );
$this->unhook( 'AuthManagerLoginAuthenticateAudit' );
return;
}
$this->unhook( 'UserLoggedIn' );
$this->unhook( 'AuthManagerLoginAuthenticateAudit' );
$this->assertSame( 'http://localhost/', $req->returnToUrl );
$ret->message = $this->message( $ret->message );
$this->assertResponseEquals( $response, $ret, "Response $i, response" );
if ( $success ) {
$this->assertSame( $id, $session->getUser()->getId(),
"Response $i, authn" );
} else {
$this->assertSame( 0, $session->getUser()->getId(),
"Response $i, authn" );
}
if ( $success || $response->status === AuthenticationResponse::FAIL ) {
$this->assertNull( $session->getSecret( 'AuthManager::authnState' ),
"Response $i, session state" );
foreach ( $providers as $p ) {
$this->assertSame( $response->status, $p->postCalled,
"Response $i, post-auth callback called" );
}
} else {
$this->assertNotNull( $session->getSecret( 'AuthManager::authnState' ),
"Response $i, session state" );
foreach ( $ret->neededRequests as $neededReq ) {
$this->assertEquals( AuthManager::ACTION_LOGIN, $neededReq->action,
"Response $i, neededRequest action" );
}
$this->assertEquals(
$ret->neededRequests,
$this->manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN_CONTINUE ),
"Response $i, continuation check"
);
foreach ( $providers as $p ) {
$this->assertFalse( $p->postCalled, "Response $i, post-auth callback not called" );
}
}
$state = $session->getSecret( 'AuthManager::authnState' );
$maybeLink = $state['maybeLink'] ?? [];
if ( $link && $response->status === AuthenticationResponse::RESTART ) {
$this->assertEquals(
$response->createRequest->maybeLink,
$maybeLink,
"Response $i, maybeLink"
);
} else {
$this->assertEquals( [], $maybeLink, "Response $i, maybeLink" );
}
}
if ( $success ) {
$this->assertSame( $req->rememberMe, $session->shouldRememberUser(),
'rememberMe checkbox had effect' );
} else {
$this->assertNotSame( $req->rememberMe, $session->shouldRememberUser(),
'rememberMe checkbox wasn\'t applied' );
}
}
public function provideAuthentication() {
$rememberReq = new RememberMeAuthenticationRequest;
$rememberReq->action = AuthManager::ACTION_LOGIN;
$req = $this->getMockForAbstractClass( AuthenticationRequest::class );
$req->foobar = 'baz';
$restartResponse = AuthenticationResponse::newRestart(
$this->message( 'authmanager-authn-no-local-user' )
);
$restartResponse->neededRequests = [ $rememberReq ];
$restartResponse2Pass = AuthenticationResponse::newPass( null );
$restartResponse2Pass->linkRequest = $req;
$restartResponse2 = AuthenticationResponse::newRestart(
$this->message( 'authmanager-authn-no-local-user-link' )
);
$restartResponse2->createRequest = new CreateFromLoginAuthenticationRequest(
null, [ $req->getUniqueId() => $req ]
);
$restartResponse2->createRequest->action = AuthManager::ACTION_LOGIN;
$restartResponse2->neededRequests = [ $rememberReq, $restartResponse2->createRequest ];
$userName = 'UTSysop';
return [
'Failure in pre-auth' => [
StatusValue::newFatal( 'fail-from-pre' ),
[],
[],
[
AuthenticationResponse::newFail( $this->message( 'fail-from-pre' ) ),
AuthenticationResponse::newFail(
$this->message( 'authmanager-authn-not-in-progress' )
),
]
],
'Failure in primary' => [
StatusValue::newGood(),
$tmp = [
AuthenticationResponse::newFail( $this->message( 'fail-from-primary' ) ),
],
[],
$tmp
],
'All primary abstain' => [
StatusValue::newGood(),
[
AuthenticationResponse::newAbstain(),
],
[],
[
AuthenticationResponse::newFail( $this->message( 'authmanager-authn-no-primary' ) )
]
],
'Primary UI, then redirect, then fail' => [
StatusValue::newGood(),
$tmp = [
AuthenticationResponse::newUI( [ $req ], $this->message( '...' ) ),
AuthenticationResponse::newRedirect( [ $req ], '/foo.html', [ 'foo' => 'bar' ] ),
AuthenticationResponse::newFail( $this->message( 'fail-in-primary-continue' ) ),
],
[],
$tmp
],
'Primary redirect, then abstain' => [
StatusValue::newGood(),
[
$tmp = AuthenticationResponse::newRedirect(
[ $req ], '/foo.html', [ 'foo' => 'bar' ]
),
AuthenticationResponse::newAbstain(),
],
[],
[
$tmp,
new \DomainException(
'MockPrimaryAuthenticationProvider::continuePrimaryAuthentication() returned ABSTAIN'
)
]
],
'Primary UI, then pass with no local user' => [
StatusValue::newGood(),
[
$tmp = AuthenticationResponse::newUI( [ $req ], $this->message( '...' ) ),
AuthenticationResponse::newPass( null ),
],
[],
[
$tmp,
$restartResponse,
]
],
'Primary UI, then pass with no local user (link type)' => [
StatusValue::newGood(),
[
$tmp = AuthenticationResponse::newUI( [ $req ], $this->message( '...' ) ),
$restartResponse2Pass,
],
[],
[
$tmp,
$restartResponse2,
],
true
],
'Primary pass with invalid username' => [
StatusValue::newGood(),
[
AuthenticationResponse::newPass( '<>' ),
],
[],
[
new \DomainException( 'MockPrimaryAuthenticationProvider returned an invalid username: <>' ),
]
],
'Secondary fail' => [
StatusValue::newGood(),
[
AuthenticationResponse::newPass( $userName ),
],
$tmp = [
AuthenticationResponse::newFail( $this->message( 'fail-in-secondary' ) ),
],
$tmp
],
'Secondary UI, then abstain' => [
StatusValue::newGood(),
[
AuthenticationResponse::newPass( $userName ),
],
[
$tmp = AuthenticationResponse::newUI( [ $req ], $this->message( '...' ) ),
AuthenticationResponse::newAbstain()
],
[
$tmp,
AuthenticationResponse::newPass( $userName ),
]
],
'Secondary pass' => [
StatusValue::newGood(),
[
AuthenticationResponse::newPass( $userName ),
],
[
AuthenticationResponse::newPass()
],
[
AuthenticationResponse::newPass( $userName ),
]
],
];
}
/**
* @dataProvider provideUserExists
* @param bool $primary1Exists
* @param bool $primary2Exists
* @param bool $expect
*/
public function testUserExists( $primary1Exists, $primary2Exists, $expect ) {
$mock1 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$mock1->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( 'primary1' ) );
$mock1->expects( $this->any() )->method( 'testUserExists' )
->with( $this->equalTo( 'UTSysop' ) )
->will( $this->returnValue( $primary1Exists ) );
$mock2 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$mock2->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( 'primary2' ) );
$mock2->expects( $this->any() )->method( 'testUserExists' )
->with( $this->equalTo( 'UTSysop' ) )
->will( $this->returnValue( $primary2Exists ) );
$this->primaryauthMocks = [ $mock1, $mock2 ];
$this->initializeManager( true );
$this->assertSame( $expect, $this->manager->userExists( 'UTSysop' ) );
}
public static function provideUserExists() {
return [
[ false, false, false ],
[ true, false, true ],
[ false, true, true ],
[ true, true, true ],
];
}
/**
* @dataProvider provideAllowsAuthenticationDataChange
* @param StatusValue $primaryReturn
* @param StatusValue $secondaryReturn
* @param Status $expect
*/
public function testAllowsAuthenticationDataChange( $primaryReturn, $secondaryReturn, $expect ) {
$req = $this->getMockForAbstractClass( AuthenticationRequest::class );
$mock1 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$mock1->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( '1' ) );
$mock1->expects( $this->any() )->method( 'providerAllowsAuthenticationDataChange' )
->with( $this->equalTo( $req ) )
->will( $this->returnValue( $primaryReturn ) );
$mock2 = $this->getMockForAbstractClass( SecondaryAuthenticationProvider::class );
$mock2->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( '2' ) );
$mock2->expects( $this->any() )->method( 'providerAllowsAuthenticationDataChange' )
->with( $this->equalTo( $req ) )
->will( $this->returnValue( $secondaryReturn ) );
$this->primaryauthMocks = [ $mock1 ];
$this->secondaryauthMocks = [ $mock2 ];
$this->initializeManager( true );
$this->assertEquals( $expect, $this->manager->allowsAuthenticationDataChange( $req ) );
}
public static function provideAllowsAuthenticationDataChange() {
$ignored = \Status::newGood( 'ignored' );
$ignored->warning( 'authmanager-change-not-supported' );
$okFromPrimary = StatusValue::newGood();
$okFromPrimary->warning( 'warning-from-primary' );
$okFromSecondary = StatusValue::newGood();
$okFromSecondary->warning( 'warning-from-secondary' );
return [
[
StatusValue::newGood(),
StatusValue::newGood(),
\Status::newGood(),
],
[
StatusValue::newGood(),
StatusValue::newGood( 'ignore' ),
\Status::newGood(),
],
[
StatusValue::newGood( 'ignored' ),
StatusValue::newGood(),
\Status::newGood(),
],
[
StatusValue::newGood( 'ignored' ),
StatusValue::newGood( 'ignored' ),
$ignored,
],
[
StatusValue::newFatal( 'fail from primary' ),
StatusValue::newGood(),
\Status::newFatal( 'fail from primary' ),
],
[
$okFromPrimary,
StatusValue::newGood(),
\Status::wrap( $okFromPrimary ),
],
[
StatusValue::newGood(),
StatusValue::newFatal( 'fail from secondary' ),
\Status::newFatal( 'fail from secondary' ),
],
[
StatusValue::newGood(),
$okFromSecondary,
\Status::wrap( $okFromSecondary ),
],
];
}
public function testChangeAuthenticationData() {
$req = $this->getMockForAbstractClass( AuthenticationRequest::class );
$req->username = 'UTSysop';
$mock1 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$mock1->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( '1' ) );
$mock1->expects( $this->once() )->method( 'providerChangeAuthenticationData' )
->with( $this->equalTo( $req ) );
$mock2 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$mock2->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( '2' ) );
$mock2->expects( $this->once() )->method( 'providerChangeAuthenticationData' )
->with( $this->equalTo( $req ) );
$this->primaryauthMocks = [ $mock1, $mock2 ];
$this->initializeManager( true );
$this->logger->setCollect( true );
$this->manager->changeAuthenticationData( $req );
$this->assertSame( [
[ LogLevel::INFO, 'Changing authentication data for {user} class {what}' ],
], $this->logger->getBuffer() );
}
public function testCanCreateAccounts() {
$types = [
PrimaryAuthenticationProvider::TYPE_CREATE => true,
PrimaryAuthenticationProvider::TYPE_LINK => true,
PrimaryAuthenticationProvider::TYPE_NONE => false,
];
foreach ( $types as $type => $can ) {
$mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( $type ) );
$mock->expects( $this->any() )->method( 'accountCreationType' )
->will( $this->returnValue( $type ) );
$this->primaryauthMocks = [ $mock ];
$this->initializeManager( true );
$this->assertSame( $can, $this->manager->canCreateAccounts(), $type );
}
}
public function testCheckAccountCreatePermissions() {
$this->initializeManager( true );
$this->setGroupPermissions( '*', 'createaccount', true );
$this->assertEquals(
\Status::newGood(),
$this->manager->checkAccountCreatePermissions( new \User )
);
$readOnlyMode = \MediaWiki\MediaWikiServices::getInstance()->getReadOnlyMode();
$readOnlyMode->setReason( 'Because' );
$this->assertEquals(
\Status::newFatal( wfMessage( 'readonlytext', 'Because' ) ),
$this->manager->checkAccountCreatePermissions( new \User )
);
$readOnlyMode->setReason( false );
$this->setGroupPermissions( '*', 'createaccount', false );
$status = $this->manager->checkAccountCreatePermissions( new \User );
$this->assertFalse( $status->isOK() );
$this->assertTrue( $status->hasMessage( 'badaccess-groups' ) );
$this->setGroupPermissions( '*', 'createaccount', true );
$user = \User::newFromName( 'UTBlockee' );
if ( $user->getID() == 0 ) {
$user->addToDatabase();
\TestUser::setPasswordForUser( $user, 'UTBlockeePassword' );
$user->saveSettings();
}
$oldBlock = \Block::newFromTarget( 'UTBlockee' );
if ( $oldBlock ) {
// An old block will prevent our new one from saving.
$oldBlock->delete();
}
$blockOptions = [
'address' => 'UTBlockee',
'user' => $user->getID(),
'by' => $this->getTestSysop()->getUser()->getId(),
'reason' => __METHOD__,
'expiry' => time() + 100500,
'createAccount' => true,
];
$block = new \Block( $blockOptions );
$block->insert();
$status = $this->manager->checkAccountCreatePermissions( $user );
$this->assertFalse( $status->isOK() );
$this->assertTrue( $status->hasMessage( 'cantcreateaccount-text' ) );
$blockOptions = [
'address' => '127.0.0.0/24',
'by' => $this->getTestSysop()->getUser()->getId(),
'reason' => __METHOD__,
'expiry' => time() + 100500,
'createAccount' => true,
];
$block = new \Block( $blockOptions );
$block->insert();
$scopeVariable = new ScopedCallback( [ $block, 'delete' ] );
$status = $this->manager->checkAccountCreatePermissions( new \User );
$this->assertFalse( $status->isOK() );
$this->assertTrue( $status->hasMessage( 'cantcreateaccount-range-text' ) );
ScopedCallback::consume( $scopeVariable );
$this->setMwGlobals( [
'wgEnableDnsBlacklist' => true,
'wgDnsBlacklistUrls' => [
'local.wmftest.net', // This will resolve for every subdomain, which works to test "listed?"
],
'wgProxyWhitelist' => [],
] );
$status = $this->manager->checkAccountCreatePermissions( new \User );
$this->assertFalse( $status->isOK() );
$this->assertTrue( $status->hasMessage( 'sorbs_create_account_reason' ) );
$this->setMwGlobals( 'wgProxyWhitelist', [ '127.0.0.1' ] );
$status = $this->manager->checkAccountCreatePermissions( new \User );
$this->assertTrue( $status->isGood() );
}
/**
* @param string $uniq
* @return string
*/
private static function usernameForCreation( $uniq = '' ) {
$i = 0;
do {
$username = "UTAuthManagerTestAccountCreation" . $uniq . ++$i;
} while ( \User::newFromName( $username )->getId() !== 0 );
return $username;
}
public function testCanCreateAccount() {
$username = self::usernameForCreation();
$this->initializeManager();
$this->assertEquals(
\Status::newFatal( 'authmanager-create-disabled' ),
$this->manager->canCreateAccount( $username )
);
$mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
$mock->expects( $this->any() )->method( 'accountCreationType' )
->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
$mock->expects( $this->any() )->method( 'testUserExists' )->will( $this->returnValue( true ) );
$mock->expects( $this->any() )->method( 'testUserForCreation' )
->will( $this->returnValue( StatusValue::newGood() ) );
$this->primaryauthMocks = [ $mock ];
$this->initializeManager( true );
$this->assertEquals(
\Status::newFatal( 'userexists' ),
$this->manager->canCreateAccount( $username )
);
$mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
$mock->expects( $this->any() )->method( 'accountCreationType' )
->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
$mock->expects( $this->any() )->method( 'testUserExists' )->will( $this->returnValue( false ) );
$mock->expects( $this->any() )->method( 'testUserForCreation' )
->will( $this->returnValue( StatusValue::newGood() ) );
$this->primaryauthMocks = [ $mock ];
$this->initializeManager( true );
$this->assertEquals(
\Status::newFatal( 'noname' ),
$this->manager->canCreateAccount( $username . '<>' )
);
$this->assertEquals(
\Status::newFatal( 'userexists' ),
$this->manager->canCreateAccount( 'UTSysop' )
);
$this->assertEquals(
\Status::newGood(),
$this->manager->canCreateAccount( $username )
);
$mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
$mock->expects( $this->any() )->method( 'accountCreationType' )
->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
$mock->expects( $this->any() )->method( 'testUserExists' )->will( $this->returnValue( false ) );
$mock->expects( $this->any() )->method( 'testUserForCreation' )
->will( $this->returnValue( StatusValue::newFatal( 'fail' ) ) );
$this->primaryauthMocks = [ $mock ];
$this->initializeManager( true );
$this->assertEquals(
\Status::newFatal( 'fail' ),
$this->manager->canCreateAccount( $username )
);
}
public function testBeginAccountCreation() {
$creator = \User::newFromName( 'UTSysop' );
$userReq = new UsernameAuthenticationRequest;
$this->logger = new \TestLogger( false, function ( $message, $level ) {
return $level === LogLevel::DEBUG ? null : $message;
} );
$this->initializeManager();
$this->request->getSession()->setSecret( 'AuthManager::accountCreationState', 'test' );
$this->hook( 'LocalUserCreated', $this->never() );
try {
$this->manager->beginAccountCreation(
$creator, [], 'http://localhost/'
);
$this->fail( 'Expected exception not thrown' );
} catch ( \LogicException $ex ) {
$this->assertEquals( 'Account creation is not possible', $ex->getMessage() );
}
$this->unhook( 'LocalUserCreated' );
$this->assertNull(
$this->request->getSession()->getSecret( 'AuthManager::accountCreationState' )
);
$mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
$mock->expects( $this->any() )->method( 'accountCreationType' )
->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
$mock->expects( $this->any() )->method( 'testUserExists' )->will( $this->returnValue( true ) );
$mock->expects( $this->any() )->method( 'testUserForCreation' )
->will( $this->returnValue( StatusValue::newGood() ) );
$this->primaryauthMocks = [ $mock ];
$this->initializeManager( true );
$this->hook( 'LocalUserCreated', $this->never() );
$ret = $this->manager->beginAccountCreation( $creator, [], 'http://localhost/' );
$this->unhook( 'LocalUserCreated' );
$this->assertSame( AuthenticationResponse::FAIL, $ret->status );
$this->assertSame( 'noname', $ret->message->getKey() );
$this->hook( 'LocalUserCreated', $this->never() );
$userReq->username = self::usernameForCreation();
$userReq2 = new UsernameAuthenticationRequest;
$userReq2->username = $userReq->username . 'X';
$ret = $this->manager->beginAccountCreation(
$creator, [ $userReq, $userReq2 ], 'http://localhost/'
);
$this->unhook( 'LocalUserCreated' );
$this->assertSame( AuthenticationResponse::FAIL, $ret->status );
$this->assertSame( 'noname', $ret->message->getKey() );
$readOnlyMode = \MediaWiki\MediaWikiServices::getInstance()->getReadOnlyMode();
$readOnlyMode->setReason( 'Because' );
$this->hook( 'LocalUserCreated', $this->never() );
$userReq->username = self::usernameForCreation();
$ret = $this->manager->beginAccountCreation( $creator, [ $userReq ], 'http://localhost/' );
$this->unhook( 'LocalUserCreated' );
$this->assertSame( AuthenticationResponse::FAIL, $ret->status );
$this->assertSame( 'readonlytext', $ret->message->getKey() );
$this->assertSame( [ 'Because' ], $ret->message->getParams() );
$readOnlyMode->setReason( false );
$this->hook( 'LocalUserCreated', $this->never() );
$userReq->username = self::usernameForCreation();
$ret = $this->manager->beginAccountCreation( $creator, [ $userReq ], 'http://localhost/' );
$this->unhook( 'LocalUserCreated' );
$this->assertSame( AuthenticationResponse::FAIL, $ret->status );
$this->assertSame( 'userexists', $ret->message->getKey() );
$mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
$mock->expects( $this->any() )->method( 'accountCreationType' )
->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
$mock->expects( $this->any() )->method( 'testUserExists' )->will( $this->returnValue( false ) );
$mock->expects( $this->any() )->method( 'testUserForCreation' )
->will( $this->returnValue( StatusValue::newFatal( 'fail' ) ) );
$this->primaryauthMocks = [ $mock ];
$this->initializeManager( true );
$this->hook( 'LocalUserCreated', $this->never() );
$userReq->username = self::usernameForCreation();
$ret = $this->manager->beginAccountCreation( $creator, [ $userReq ], 'http://localhost/' );
$this->unhook( 'LocalUserCreated' );
$this->assertSame( AuthenticationResponse::FAIL, $ret->status );
$this->assertSame( 'fail', $ret->message->getKey() );
$mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
$mock->expects( $this->any() )->method( 'accountCreationType' )
->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
$mock->expects( $this->any() )->method( 'testUserExists' )->will( $this->returnValue( false ) );
$mock->expects( $this->any() )->method( 'testUserForCreation' )
->will( $this->returnValue( StatusValue::newGood() ) );
$this->primaryauthMocks = [ $mock ];
$this->initializeManager( true );
$this->hook( 'LocalUserCreated', $this->never() );
$userReq->username = self::usernameForCreation() . '<>';
$ret = $this->manager->beginAccountCreation( $creator, [ $userReq ], 'http://localhost/' );
$this->unhook( 'LocalUserCreated' );
$this->assertSame( AuthenticationResponse::FAIL, $ret->status );
$this->assertSame( 'noname', $ret->message->getKey() );
$this->hook( 'LocalUserCreated', $this->never() );
$userReq->username = $creator->getName();
$ret = $this->manager->beginAccountCreation( $creator, [ $userReq ], 'http://localhost/' );
$this->unhook( 'LocalUserCreated' );
$this->assertSame( AuthenticationResponse::FAIL, $ret->status );
$this->assertSame( 'userexists', $ret->message->getKey() );
$mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
$mock->expects( $this->any() )->method( 'accountCreationType' )
->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
$mock->expects( $this->any() )->method( 'testUserExists' )->will( $this->returnValue( false ) );
$mock->expects( $this->any() )->method( 'testUserForCreation' )
->will( $this->returnValue( StatusValue::newGood() ) );
$mock->expects( $this->any() )->method( 'testForAccountCreation' )
->will( $this->returnValue( StatusValue::newFatal( 'fail' ) ) );
$this->primaryauthMocks = [ $mock ];
$this->initializeManager( true );
$req = $this->getMockBuilder( UserDataAuthenticationRequest::class )
->setMethods( [ 'populateUser' ] )
->getMock();
$req->expects( $this->any() )->method( 'populateUser' )
->willReturn( \StatusValue::newFatal( 'populatefail' ) );
$userReq->username = self::usernameForCreation();
$ret = $this->manager->beginAccountCreation(
$creator, [ $userReq, $req ], 'http://localhost/'
);
$this->assertSame( AuthenticationResponse::FAIL, $ret->status );
$this->assertSame( 'populatefail', $ret->message->getKey() );
$req = new UserDataAuthenticationRequest;
$userReq->username = self::usernameForCreation();
$ret = $this->manager->beginAccountCreation(
$creator, [ $userReq, $req ], 'http://localhost/'
);
$this->assertSame( AuthenticationResponse::FAIL, $ret->status );
$this->assertSame( 'fail', $ret->message->getKey() );
$this->manager->beginAccountCreation(
\User::newFromName( $userReq->username ), [ $userReq, $req ], 'http://localhost/'
);
$this->assertSame( AuthenticationResponse::FAIL, $ret->status );
$this->assertSame( 'fail', $ret->message->getKey() );
}
public function testContinueAccountCreation() {
$creator = \User::newFromName( 'UTSysop' );
$username = self::usernameForCreation();
$this->logger = new \TestLogger( false, function ( $message, $level ) {
return $level === LogLevel::DEBUG ? null : $message;
} );
$this->initializeManager();
$session = [
'userid' => 0,
'username' => $username,
'creatorid' => 0,
'creatorname' => $username,
'reqs' => [],
'primary' => null,
'primaryResponse' => null,
'secondary' => [],
'ranPreTests' => true,
];
$this->hook( 'LocalUserCreated', $this->never() );
try {
$this->manager->continueAccountCreation( [] );
$this->fail( 'Expected exception not thrown' );
} catch ( \LogicException $ex ) {
$this->assertEquals( 'Account creation is not possible', $ex->getMessage() );
}
$this->unhook( 'LocalUserCreated' );
$mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
$mock->expects( $this->any() )->method( 'accountCreationType' )
->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
$mock->expects( $this->any() )->method( 'testUserExists' )->will( $this->returnValue( false ) );
$mock->expects( $this->any() )->method( 'beginPrimaryAccountCreation' )->will(
$this->returnValue( AuthenticationResponse::newFail( $this->message( 'fail' ) ) )
);
$this->primaryauthMocks = [ $mock ];
$this->initializeManager( true );
$this->request->getSession()->setSecret( 'AuthManager::accountCreationState', null );
$this->hook( 'LocalUserCreated', $this->never() );
$ret = $this->manager->continueAccountCreation( [] );
$this->unhook( 'LocalUserCreated' );
$this->assertSame( AuthenticationResponse::FAIL, $ret->status );
$this->assertSame( 'authmanager-create-not-in-progress', $ret->message->getKey() );
$this->request->getSession()->setSecret( 'AuthManager::accountCreationState',
[ 'username' => "$username<>" ] + $session );
$this->hook( 'LocalUserCreated', $this->never() );
$ret = $this->manager->continueAccountCreation( [] );
$this->unhook( 'LocalUserCreated' );
$this->assertSame( AuthenticationResponse::FAIL, $ret->status );
$this->assertSame( 'noname', $ret->message->getKey() );
$this->assertNull(
$this->request->getSession()->getSecret( 'AuthManager::accountCreationState' )
);
$this->request->getSession()->setSecret( 'AuthManager::accountCreationState', $session );
$this->hook( 'LocalUserCreated', $this->never() );
$cache = \ObjectCache::getLocalClusterInstance();
$lock = $cache->getScopedLock( $cache->makeGlobalKey( 'account', md5( $username ) ) );
$ret = $this->manager->continueAccountCreation( [] );
unset( $lock );
$this->unhook( 'LocalUserCreated' );
$this->assertSame( AuthenticationResponse::FAIL, $ret->status );
$this->assertSame( 'usernameinprogress', $ret->message->getKey() );
// This error shouldn't remove the existing session, because the
// raced-with process "owns" it.
$this->assertSame(
$session, $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' )
);
$this->request->getSession()->setSecret( 'AuthManager::accountCreationState',
[ 'username' => $creator->getName() ] + $session );
$readOnlyMode = \MediaWiki\MediaWikiServices::getInstance()->getReadOnlyMode();
$readOnlyMode->setReason( 'Because' );
$this->hook( 'LocalUserCreated', $this->never() );
$ret = $this->manager->continueAccountCreation( [] );
$this->unhook( 'LocalUserCreated' );
$this->assertSame( AuthenticationResponse::FAIL, $ret->status );
$this->assertSame( 'readonlytext', $ret->message->getKey() );
$this->assertSame( [ 'Because' ], $ret->message->getParams() );
$readOnlyMode->setReason( false );
$this->request->getSession()->setSecret( 'AuthManager::accountCreationState',
[ 'username' => $creator->getName() ] + $session );
$this->hook( 'LocalUserCreated', $this->never() );
$ret = $this->manager->continueAccountCreation( [] );
$this->unhook( 'LocalUserCreated' );
$this->assertSame( AuthenticationResponse::FAIL, $ret->status );
$this->assertSame( 'userexists', $ret->message->getKey() );
$this->assertNull(
$this->request->getSession()->getSecret( 'AuthManager::accountCreationState' )
);
$this->request->getSession()->setSecret( 'AuthManager::accountCreationState',
[ 'userid' => $creator->getId() ] + $session );
$this->hook( 'LocalUserCreated', $this->never() );
try {
$ret = $this->manager->continueAccountCreation( [] );
$this->fail( 'Expected exception not thrown' );
} catch ( \UnexpectedValueException $ex ) {
$this->assertEquals( "User \"{$username}\" should exist now, but doesn't!", $ex->getMessage() );
}
$this->unhook( 'LocalUserCreated' );
$this->assertNull(
$this->request->getSession()->getSecret( 'AuthManager::accountCreationState' )
);
$id = $creator->getId();
$name = $creator->getName();
$this->request->getSession()->setSecret( 'AuthManager::accountCreationState',
[ 'username' => $name, 'userid' => $id + 1 ] + $session );
$this->hook( 'LocalUserCreated', $this->never() );
try {
$ret = $this->manager->continueAccountCreation( [] );
$this->fail( 'Expected exception not thrown' );
} catch ( \UnexpectedValueException $ex ) {
$this->assertEquals(
"User \"{$name}\" exists, but ID $id != " . ( $id + 1 ) . '!', $ex->getMessage()
);
}
$this->unhook( 'LocalUserCreated' );
$this->assertNull(
$this->request->getSession()->getSecret( 'AuthManager::accountCreationState' )
);
$req = $this->getMockBuilder( UserDataAuthenticationRequest::class )
->setMethods( [ 'populateUser' ] )
->getMock();
$req->expects( $this->any() )->method( 'populateUser' )
->willReturn( \StatusValue::newFatal( 'populatefail' ) );
$this->request->getSession()->setSecret( 'AuthManager::accountCreationState',
[ 'reqs' => [ $req ] ] + $session );
$ret = $this->manager->continueAccountCreation( [] );
$this->assertSame( AuthenticationResponse::FAIL, $ret->status );
$this->assertSame( 'populatefail', $ret->message->getKey() );
$this->assertNull(
$this->request->getSession()->getSecret( 'AuthManager::accountCreationState' )
);
}
/**
* @dataProvider provideAccountCreation
* @param StatusValue $preTest
* @param StatusValue $primaryTest
* @param StatusValue $secondaryTest
* @param array $primaryResponses
* @param array $secondaryResponses
* @param array $managerResponses
*/
public function testAccountCreation(
StatusValue $preTest, $primaryTest, $secondaryTest,
array $primaryResponses, array $secondaryResponses, array $managerResponses
) {
$creator = \User::newFromName( 'UTSysop' );
$username = self::usernameForCreation();
$this->initializeManager();
// Set up lots of mocks...
$req = $this->getMockForAbstractClass( AuthenticationRequest::class );
$req->preTest = $preTest;
$req->primaryTest = $primaryTest;
$req->secondaryTest = $secondaryTest;
$req->primary = $primaryResponses;
$req->secondary = $secondaryResponses;
$mocks = [];
foreach ( [ 'pre', 'primary', 'secondary' ] as $key ) {
$class = ucfirst( $key ) . 'AuthenticationProvider';
$mocks[$key] = $this->getMockForAbstractClass(
"MediaWiki\\Auth\\$class", [], "Mock$class"
);
$mocks[$key]->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( $key ) );
$mocks[$key]->expects( $this->any() )->method( 'testUserForCreation' )
->will( $this->returnValue( StatusValue::newGood() ) );
$mocks[$key]->expects( $this->any() )->method( 'testForAccountCreation' )
->will( $this->returnCallback(
function ( $user, $creatorIn, $reqs )
use ( $username, $creator, $req, $key )
{
$this->assertSame( $username, $user->getName() );
$this->assertSame( $creator->getId(), $creatorIn->getId() );
$this->assertSame( $creator->getName(), $creatorIn->getName() );
$foundReq = false;
foreach ( $reqs as $r ) {
$this->assertSame( $username, $r->username );
$foundReq = $foundReq || get_class( $r ) === get_class( $req );
}
$this->assertTrue( $foundReq, '$reqs contains $req' );
$k = $key . 'Test';
return $req->$k;
}
) );
for ( $i = 2; $i <= 3; $i++ ) {
$mocks[$key . $i] = $this->getMockForAbstractClass( "MediaWiki\\Auth\\$class" );
$mocks[$key . $i]->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( $key . $i ) );
$mocks[$key . $i]->expects( $this->any() )->method( 'testUserForCreation' )
->will( $this->returnValue( StatusValue::newGood() ) );
$mocks[$key . $i]->expects( $this->atMost( 1 ) )->method( 'testForAccountCreation' )
->will( $this->returnValue( StatusValue::newGood() ) );
}
}
$mocks['primary']->expects( $this->any() )->method( 'accountCreationType' )
->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
$mocks['primary']->expects( $this->any() )->method( 'testUserExists' )
->will( $this->returnValue( false ) );
$ct = count( $req->primary );
$callback = $this->returnCallback( function ( $user, $creator, $reqs ) use ( $username, $req ) {
$this->assertSame( $username, $user->getName() );
$this->assertSame( 'UTSysop', $creator->getName() );
$foundReq = false;
foreach ( $reqs as $r ) {
$this->assertSame( $username, $r->username );
$foundReq = $foundReq || get_class( $r ) === get_class( $req );
}
$this->assertTrue( $foundReq, '$reqs contains $req' );
return array_shift( $req->primary );
} );
$mocks['primary']->expects( $this->exactly( min( 1, $ct ) ) )
->method( 'beginPrimaryAccountCreation' )
->will( $callback );
$mocks['primary']->expects( $this->exactly( max( 0, $ct - 1 ) ) )
->method( 'continuePrimaryAccountCreation' )
->will( $callback );
$ct = count( $req->secondary );
$callback = $this->returnCallback( function ( $user, $creator, $reqs ) use ( $username, $req ) {
$this->assertSame( $username, $user->getName() );
$this->assertSame( 'UTSysop', $creator->getName() );
$foundReq = false;
foreach ( $reqs as $r ) {
$this->assertSame( $username, $r->username );
$foundReq = $foundReq || get_class( $r ) === get_class( $req );
}
$this->assertTrue( $foundReq, '$reqs contains $req' );
return array_shift( $req->secondary );
} );
$mocks['secondary']->expects( $this->exactly( min( 1, $ct ) ) )
->method( 'beginSecondaryAccountCreation' )
->will( $callback );
$mocks['secondary']->expects( $this->exactly( max( 0, $ct - 1 ) ) )
->method( 'continueSecondaryAccountCreation' )
->will( $callback );
$abstain = AuthenticationResponse::newAbstain();
$mocks['primary2']->expects( $this->any() )->method( 'accountCreationType' )
->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_LINK ) );
$mocks['primary2']->expects( $this->any() )->method( 'testUserExists' )
->will( $this->returnValue( false ) );
$mocks['primary2']->expects( $this->atMost( 1 ) )->method( 'beginPrimaryAccountCreation' )
->will( $this->returnValue( $abstain ) );
$mocks['primary2']->expects( $this->never() )->method( 'continuePrimaryAccountCreation' );
$mocks['primary3']->expects( $this->any() )->method( 'accountCreationType' )
->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_NONE ) );
$mocks['primary3']->expects( $this->any() )->method( 'testUserExists' )
->will( $this->returnValue( false ) );
$mocks['primary3']->expects( $this->never() )->method( 'beginPrimaryAccountCreation' );
$mocks['primary3']->expects( $this->never() )->method( 'continuePrimaryAccountCreation' );
$mocks['secondary2']->expects( $this->atMost( 1 ) )
->method( 'beginSecondaryAccountCreation' )
->will( $this->returnValue( $abstain ) );
$mocks['secondary2']->expects( $this->never() )->method( 'continueSecondaryAccountCreation' );
$mocks['secondary3']->expects( $this->atMost( 1 ) )
->method( 'beginSecondaryAccountCreation' )
->will( $this->returnValue( $abstain ) );
$mocks['secondary3']->expects( $this->never() )->method( 'continueSecondaryAccountCreation' );
$this->preauthMocks = [ $mocks['pre'], $mocks['pre2'] ];
$this->primaryauthMocks = [ $mocks['primary3'], $mocks['primary'], $mocks['primary2'] ];
$this->secondaryauthMocks = [
$mocks['secondary3'], $mocks['secondary'], $mocks['secondary2']
];
$this->logger = new \TestLogger( true, function ( $message, $level ) {
return $level === LogLevel::DEBUG ? null : $message;
} );
$expectLog = [];
$this->initializeManager( true );
$constraint = \PHPUnit_Framework_Assert::logicalOr(
$this->equalTo( AuthenticationResponse::PASS ),
$this->equalTo( AuthenticationResponse::FAIL )
);
$providers = array_merge(
$this->preauthMocks, $this->primaryauthMocks, $this->secondaryauthMocks
);
foreach ( $providers as $p ) {
$p->postCalled = false;
$p->expects( $this->atMost( 1 ) )->method( 'postAccountCreation' )
->willReturnCallback( function ( $user, $creator, $response )
use ( $constraint, $p, $username )
{
$this->assertInstanceOf( \User::class, $user );
$this->assertSame( $username, $user->getName() );
$this->assertSame( 'UTSysop', $creator->getName() );
$this->assertInstanceOf( AuthenticationResponse::class, $response );
$this->assertThat( $response->status, $constraint );
$p->postCalled = $response->status;
} );
}
// We're testing with $wgNewUserLog = false, so assert that it worked
$dbw = wfGetDB( DB_MASTER );
$maxLogId = $dbw->selectField( 'logging', 'MAX(log_id)', [ 'log_type' => 'newusers' ] );
$first = true;
$created = false;
foreach ( $managerResponses as $i => $response ) {
$success = $response instanceof AuthenticationResponse &&
$response->status === AuthenticationResponse::PASS;
if ( $i === 'created' ) {
$created = true;
$this->hook( 'LocalUserCreated', $this->once() )
->with(
$this->callback( function ( $user ) use ( $username ) {
return $user->getName() === $username;
} ),
$this->equalTo( false )
);
$expectLog[] = [ LogLevel::INFO, "Creating user {user} during account creation" ];
} else {
$this->hook( 'LocalUserCreated', $this->never() );
}
$ex = null;
try {
if ( $first ) {
$userReq = new UsernameAuthenticationRequest;
$userReq->username = $username;
$ret = $this->manager->beginAccountCreation(
$creator, [ $userReq, $req ], 'http://localhost/'
);
} else {
$ret = $this->manager->continueAccountCreation( [ $req ] );
}
if ( $response instanceof \Exception ) {
$this->fail( 'Expected exception not thrown', "Response $i" );
}
} catch ( \Exception $ex ) {
if ( !$response instanceof \Exception ) {
throw $ex;
}
$this->assertEquals( $response->getMessage(), $ex->getMessage(), "Response $i, exception" );
$this->assertNull(
$this->request->getSession()->getSecret( 'AuthManager::accountCreationState' ),
"Response $i, exception, session state"
);
$this->unhook( 'LocalUserCreated' );
return;
}
$this->unhook( 'LocalUserCreated' );
$this->assertSame( 'http://localhost/', $req->returnToUrl );
if ( $success ) {
$this->assertNotNull( $ret->loginRequest, "Response $i, login marker" );
$this->assertContains(
$ret->loginRequest, $this->managerPriv->createdAccountAuthenticationRequests,
"Response $i, login marker"
);
$expectLog[] = [
LogLevel::INFO,
"MediaWiki\Auth\AuthManager::continueAccountCreation: Account creation succeeded for {user}"
];
// Set some fields in the expected $response that we couldn't
// know in provideAccountCreation().
$response->username = $username;
$response->loginRequest = $ret->loginRequest;
} else {
$this->assertNull( $ret->loginRequest, "Response $i, login marker" );
$this->assertSame( [], $this->managerPriv->createdAccountAuthenticationRequests,
"Response $i, login marker" );
}
$ret->message = $this->message( $ret->message );
$this->assertResponseEquals( $response, $ret, "Response $i, response" );
if ( $success || $response->status === AuthenticationResponse::FAIL ) {
$this->assertNull(
$this->request->getSession()->getSecret( 'AuthManager::accountCreationState' ),
"Response $i, session state"
);
foreach ( $providers as $p ) {
$this->assertSame( $response->status, $p->postCalled,
"Response $i, post-auth callback called" );
}
} else {
$this->assertNotNull(
$this->request->getSession()->getSecret( 'AuthManager::accountCreationState' ),
"Response $i, session state"
);
foreach ( $ret->neededRequests as $neededReq ) {
$this->assertEquals( AuthManager::ACTION_CREATE, $neededReq->action,
"Response $i, neededRequest action" );
}
$this->assertEquals(
$ret->neededRequests,
$this->manager->getAuthenticationRequests( AuthManager::ACTION_CREATE_CONTINUE ),
"Response $i, continuation check"
);
foreach ( $providers as $p ) {
$this->assertFalse( $p->postCalled, "Response $i, post-auth callback not called" );
}
}
if ( $created ) {
$this->assertNotEquals( 0, \User::idFromName( $username ) );
} else {
$this->assertEquals( 0, \User::idFromName( $username ) );
}
$first = false;
}
$this->assertSame( $expectLog, $this->logger->getBuffer() );
$this->assertSame(
$maxLogId,
$dbw->selectField( 'logging', 'MAX(log_id)', [ 'log_type' => 'newusers' ] )
);
}
public function provideAccountCreation() {
$req = $this->getMockForAbstractClass( AuthenticationRequest::class );
$good = StatusValue::newGood();
return [
'Pre-creation test fail in pre' => [
StatusValue::newFatal( 'fail-from-pre' ), $good, $good,
[],
[],
[
AuthenticationResponse::newFail( $this->message( 'fail-from-pre' ) ),
]
],
'Pre-creation test fail in primary' => [
$good, StatusValue::newFatal( 'fail-from-primary' ), $good,
[],
[],
[
AuthenticationResponse::newFail( $this->message( 'fail-from-primary' ) ),
]
],
'Pre-creation test fail in secondary' => [
$good, $good, StatusValue::newFatal( 'fail-from-secondary' ),
[],
[],
[
AuthenticationResponse::newFail( $this->message( 'fail-from-secondary' ) ),
]
],
'Failure in primary' => [
$good, $good, $good,
$tmp = [
AuthenticationResponse::newFail( $this->message( 'fail-from-primary' ) ),
],
[],
$tmp
],
'All primary abstain' => [
$good, $good, $good,
[
AuthenticationResponse::newAbstain(),
],
[],
[
AuthenticationResponse::newFail( $this->message( 'authmanager-create-no-primary' ) )
]
],
'Primary UI, then redirect, then fail' => [
$good, $good, $good,
$tmp = [
AuthenticationResponse::newUI( [ $req ], $this->message( '...' ) ),
AuthenticationResponse::newRedirect( [ $req ], '/foo.html', [ 'foo' => 'bar' ] ),
AuthenticationResponse::newFail( $this->message( 'fail-in-primary-continue' ) ),
],
[],
$tmp
],
'Primary redirect, then abstain' => [
$good, $good, $good,
[
$tmp = AuthenticationResponse::newRedirect(
[ $req ], '/foo.html', [ 'foo' => 'bar' ]
),
AuthenticationResponse::newAbstain(),
],
[],
[
$tmp,
new \DomainException(
'MockPrimaryAuthenticationProvider::continuePrimaryAccountCreation() returned ABSTAIN'
)
]
],
'Primary UI, then pass; secondary abstain' => [
$good, $good, $good,
[
$tmp1 = AuthenticationResponse::newUI( [ $req ], $this->message( '...' ) ),
AuthenticationResponse::newPass(),
],
[
AuthenticationResponse::newAbstain(),
],
[
$tmp1,
'created' => AuthenticationResponse::newPass( '' ),
]
],
'Primary pass; secondary UI then pass' => [
$good, $good, $good,
[
AuthenticationResponse::newPass( '' ),
],
[
$tmp1 = AuthenticationResponse::newUI( [ $req ], $this->message( '...' ) ),
AuthenticationResponse::newPass( '' ),
],
[
'created' => $tmp1,
AuthenticationResponse::newPass( '' ),
]
],
'Primary pass; secondary fail' => [
$good, $good, $good,
[
AuthenticationResponse::newPass(),
],
[
AuthenticationResponse::newFail( $this->message( '...' ) ),
],
[
'created' => new \DomainException(
'MockSecondaryAuthenticationProvider::beginSecondaryAccountCreation() returned FAIL. ' .
'Secondary providers are not allowed to fail account creation, ' .
'that should have been done via testForAccountCreation().'
)
]
],
];
}
/**
* @dataProvider provideAccountCreationLogging
* @param bool $isAnon
* @param string|null $logSubtype
*/
public function testAccountCreationLogging( $isAnon, $logSubtype ) {
$creator = $isAnon ? new \User : \User::newFromName( 'UTSysop' );
$username = self::usernameForCreation();
$this->initializeManager();
// Set up lots of mocks...
$mock = $this->getMockForAbstractClass(
\MediaWiki\Auth\PrimaryAuthenticationProvider::class, []
);
$mock->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( 'primary' ) );
$mock->expects( $this->any() )->method( 'testUserForCreation' )
->will( $this->returnValue( StatusValue::newGood() ) );
$mock->expects( $this->any() )->method( 'testForAccountCreation' )
->will( $this->returnValue( StatusValue::newGood() ) );
$mock->expects( $this->any() )->method( 'accountCreationType' )
->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
$mock->expects( $this->any() )->method( 'testUserExists' )
->will( $this->returnValue( false ) );
$mock->expects( $this->any() )->method( 'beginPrimaryAccountCreation' )
->will( $this->returnValue( AuthenticationResponse::newPass( $username ) ) );
$mock->expects( $this->any() )->method( 'finishAccountCreation' )
->will( $this->returnValue( $logSubtype ) );
$this->primaryauthMocks = [ $mock ];
$this->initializeManager( true );
$this->logger->setCollect( true );
$this->config->set( 'NewUserLog', true );
$dbw = wfGetDB( DB_MASTER );
$maxLogId = $dbw->selectField( 'logging', 'MAX(log_id)', [ 'log_type' => 'newusers' ] );
$userReq = new UsernameAuthenticationRequest;
$userReq->username = $username;
$reasonReq = new CreationReasonAuthenticationRequest;
$reasonReq->reason = $this->toString();
$ret = $this->manager->beginAccountCreation(
$creator, [ $userReq, $reasonReq ], 'http://localhost/'
);
$this->assertSame( AuthenticationResponse::PASS, $ret->status );
$user = \User::newFromName( $username );
$this->assertNotEquals( 0, $user->getId(), 'sanity check' );
$this->assertNotEquals( $creator->getId(), $user->getId(), 'sanity check' );
$data = \DatabaseLogEntry::getSelectQueryData();
$rows = iterator_to_array( $dbw->select(
$data['tables'],
$data['fields'],
[
'log_id > ' . (int)$maxLogId,
'log_type' => 'newusers'
] + $data['conds'],
__METHOD__,
$data['options'],
$data['join_conds']
) );
$this->assertCount( 1, $rows );
$entry = \DatabaseLogEntry::newFromRow( reset( $rows ) );
$this->assertSame( $logSubtype ?: ( $isAnon ? 'create' : 'create2' ), $entry->getSubtype() );
$this->assertSame(
$isAnon ? $user->getId() : $creator->getId(),
$entry->getPerformer()->getId()
);
$this->assertSame(
$isAnon ? $user->getName() : $creator->getName(),
$entry->getPerformer()->getName()
);
$this->assertSame( $user->getUserPage()->getFullText(), $entry->getTarget()->getFullText() );
$this->assertSame( [ '4::userid' => $user->getId() ], $entry->getParameters() );
$this->assertSame( $this->toString(), $entry->getComment() );
}
public static function provideAccountCreationLogging() {
return [
[ true, null ],
[ true, 'foobar' ],
[ false, null ],
[ false, 'byemail' ],
];
}
public function testAutoAccountCreation() {
global $wgHooks;
// PHPUnit seems to have a bug where it will call the ->with()
// callbacks for our hooks again after the test is run (WTF?), which
// breaks here because $username no longer matches $user by the end of
// the testing.
$workaroundPHPUnitBug = false;
$username = self::usernameForCreation();
$this->initializeManager();
$this->setGroupPermissions( '*', 'createaccount', true );
$this->setGroupPermissions( '*', 'autocreateaccount', false );
$this->mergeMwGlobalArrayValue( 'wgObjectCaches',
[ __METHOD__ => [ 'class' => 'HashBagOStuff' ] ] );
$this->setMwGlobals( [ 'wgMainCacheType' => __METHOD__ ] );
// Set up lots of mocks...
$mocks = [];
foreach ( [ 'pre', 'primary', 'secondary' ] as $key ) {
$class = ucfirst( $key ) . 'AuthenticationProvider';
$mocks[$key] = $this->getMockForAbstractClass( "MediaWiki\\Auth\\$class" );
$mocks[$key]->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( $key ) );
}
$good = StatusValue::newGood();
$callback = $this->callback( function ( $user ) use ( &$username, &$workaroundPHPUnitBug ) {
return $workaroundPHPUnitBug || $user->getName() === $username;
} );
$mocks['pre']->expects( $this->exactly( 12 ) )->method( 'testUserForCreation' )
->with( $callback, $this->identicalTo( AuthManager::AUTOCREATE_SOURCE_SESSION ) )
->will( $this->onConsecutiveCalls(
StatusValue::newFatal( 'ok' ), StatusValue::newFatal( 'ok' ), // For testing permissions
StatusValue::newFatal( 'fail-in-pre' ), $good, $good,
$good, // backoff test
$good, // addToDatabase fails test
$good, // addToDatabase throws test
$good, // addToDatabase exists test
$good, $good, $good // success
) );
$mocks['primary']->expects( $this->any() )->method( 'accountCreationType' )
->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
$mocks['primary']->expects( $this->any() )->method( 'testUserExists' )
->will( $this->returnValue( true ) );
$mocks['primary']->expects( $this->exactly( 9 ) )->method( 'testUserForCreation' )
->with( $callback, $this->identicalTo( AuthManager::AUTOCREATE_SOURCE_SESSION ) )
->will( $this->onConsecutiveCalls(
StatusValue::newFatal( 'fail-in-primary' ), $good,
$good, // backoff test
$good, // addToDatabase fails test
$good, // addToDatabase throws test
$good, // addToDatabase exists test
$good, $good, $good
) );
$mocks['primary']->expects( $this->exactly( 3 ) )->method( 'autoCreatedAccount' )
->with( $callback, $this->identicalTo( AuthManager::AUTOCREATE_SOURCE_SESSION ) );
$mocks['secondary']->expects( $this->exactly( 8 ) )->method( 'testUserForCreation' )
->with( $callback, $this->identicalTo( AuthManager::AUTOCREATE_SOURCE_SESSION ) )
->will( $this->onConsecutiveCalls(
StatusValue::newFatal( 'fail-in-secondary' ),
$good, // backoff test
$good, // addToDatabase fails test
$good, // addToDatabase throws test
$good, // addToDatabase exists test
$good, $good, $good
) );
$mocks['secondary']->expects( $this->exactly( 3 ) )->method( 'autoCreatedAccount' )
->with( $callback, $this->identicalTo( AuthManager::AUTOCREATE_SOURCE_SESSION ) );
$this->preauthMocks = [ $mocks['pre'] ];
$this->primaryauthMocks = [ $mocks['primary'] ];
$this->secondaryauthMocks = [ $mocks['secondary'] ];
$this->initializeManager( true );
$session = $this->request->getSession();
$logger = new \TestLogger( true, function ( $m ) {
$m = str_replace( 'MediaWiki\\Auth\\AuthManager::autoCreateUser: ', '', $m );
return $m;
} );
$this->manager->setLogger( $logger );
try {
$user = \User::newFromName( 'UTSysop' );
$this->manager->autoCreateUser( $user, 'InvalidSource', true );
$this->fail( 'Expected exception not thrown' );
} catch ( \InvalidArgumentException $ex ) {
$this->assertSame( 'Unknown auto-creation source: InvalidSource', $ex->getMessage() );
}
// First, check an existing user
$session->clear();
$user = \User::newFromName( 'UTSysop' );
$this->hook( 'LocalUserCreated', $this->never() );
$ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
$this->unhook( 'LocalUserCreated' );
$expect = \Status::newGood();
$expect->warning( 'userexists' );
$this->assertEquals( $expect, $ret );
$this->assertNotEquals( 0, $user->getId() );
$this->assertSame( 'UTSysop', $user->getName() );
$this->assertEquals( $user->getId(), $session->getUser()->getId() );
$this->assertSame( [
[ LogLevel::DEBUG, '{username} already exists locally' ],
], $logger->getBuffer() );
$logger->clearBuffer();
$session->clear();
$user = \User::newFromName( 'UTSysop' );
$this->hook( 'LocalUserCreated', $this->never() );
$ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, false );
$this->unhook( 'LocalUserCreated' );
$expect = \Status::newGood();
$expect->warning( 'userexists' );
$this->assertEquals( $expect, $ret );
$this->assertNotEquals( 0, $user->getId() );
$this->assertSame( 'UTSysop', $user->getName() );
$this->assertEquals( 0, $session->getUser()->getId() );
$this->assertSame( [
[ LogLevel::DEBUG, '{username} already exists locally' ],
], $logger->getBuffer() );
$logger->clearBuffer();
// Wiki is read-only
$session->clear();
$readOnlyMode = \MediaWiki\MediaWikiServices::getInstance()->getReadOnlyMode();
$readOnlyMode->setReason( 'Because' );
$user = \User::newFromName( $username );
$this->hook( 'LocalUserCreated', $this->never() );
$ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
$this->unhook( 'LocalUserCreated' );
$this->assertEquals( \Status::newFatal( wfMessage( 'readonlytext', 'Because' ) ), $ret );
$this->assertEquals( 0, $user->getId() );
$this->assertNotEquals( $username, $user->getName() );
$this->assertEquals( 0, $session->getUser()->getId() );
$this->assertSame( [
[ LogLevel::DEBUG, 'denied by wfReadOnly(): {reason}' ],
], $logger->getBuffer() );
$logger->clearBuffer();
$readOnlyMode->setReason( false );
// Session blacklisted
$session->clear();
$session->set( 'AuthManager::AutoCreateBlacklist', 'test' );
$user = \User::newFromName( $username );
$this->hook( 'LocalUserCreated', $this->never() );
$ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
$this->unhook( 'LocalUserCreated' );
$this->assertEquals( \Status::newFatal( 'test' ), $ret );
$this->assertEquals( 0, $user->getId() );
$this->assertNotEquals( $username, $user->getName() );
$this->assertEquals( 0, $session->getUser()->getId() );
$this->assertSame( [
[ LogLevel::DEBUG, 'blacklisted in session {sessionid}' ],
], $logger->getBuffer() );
$logger->clearBuffer();
$session->clear();
$session->set( 'AuthManager::AutoCreateBlacklist', StatusValue::newFatal( 'test2' ) );
$user = \User::newFromName( $username );
$this->hook( 'LocalUserCreated', $this->never() );
$ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
$this->unhook( 'LocalUserCreated' );
$this->assertEquals( \Status::newFatal( 'test2' ), $ret );
$this->assertEquals( 0, $user->getId() );
$this->assertNotEquals( $username, $user->getName() );
$this->assertEquals( 0, $session->getUser()->getId() );
$this->assertSame( [
[ LogLevel::DEBUG, 'blacklisted in session {sessionid}' ],
], $logger->getBuffer() );
$logger->clearBuffer();
// Uncreatable name
$session->clear();
$user = \User::newFromName( $username . '@' );
$this->hook( 'LocalUserCreated', $this->never() );
$ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
$this->unhook( 'LocalUserCreated' );
$this->assertEquals( \Status::newFatal( 'noname' ), $ret );
$this->assertEquals( 0, $user->getId() );
$this->assertNotEquals( $username . '@', $user->getId() );
$this->assertEquals( 0, $session->getUser()->getId() );
$this->assertSame( [
[ LogLevel::DEBUG, 'name "{username}" is not creatable' ],
], $logger->getBuffer() );
$logger->clearBuffer();
$this->assertSame( 'noname', $session->get( 'AuthManager::AutoCreateBlacklist' ) );
// IP unable to create accounts
$this->setGroupPermissions( '*', 'createaccount', false );
$this->setGroupPermissions( '*', 'autocreateaccount', false );
$session->clear();
$user = \User::newFromName( $username );
$this->hook( 'LocalUserCreated', $this->never() );
$ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
$this->unhook( 'LocalUserCreated' );
$this->assertEquals( \Status::newFatal( 'authmanager-autocreate-noperm' ), $ret );
$this->assertEquals( 0, $user->getId() );
$this->assertNotEquals( $username, $user->getName() );
$this->assertEquals( 0, $session->getUser()->getId() );
$this->assertSame( [
[ LogLevel::DEBUG, 'IP lacks the ability to create or autocreate accounts' ],
], $logger->getBuffer() );
$logger->clearBuffer();
$this->assertSame(
'authmanager-autocreate-noperm', $session->get( 'AuthManager::AutoCreateBlacklist' )
);
// Test that both permutations of permissions are allowed
// (this hits the two "ok" entries in $mocks['pre'])
$this->setGroupPermissions( '*', 'createaccount', false );
$this->setGroupPermissions( '*', 'autocreateaccount', true );
$session->clear();
$user = \User::newFromName( $username );
$this->hook( 'LocalUserCreated', $this->never() );
$ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
$this->unhook( 'LocalUserCreated' );
$this->assertEquals( \Status::newFatal( 'ok' ), $ret );
$this->setGroupPermissions( '*', 'createaccount', true );
$this->setGroupPermissions( '*', 'autocreateaccount', false );
$session->clear();
$user = \User::newFromName( $username );
$this->hook( 'LocalUserCreated', $this->never() );
$ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
$this->unhook( 'LocalUserCreated' );
$this->assertEquals( \Status::newFatal( 'ok' ), $ret );
$logger->clearBuffer();
// Test lock fail
$session->clear();
$user = \User::newFromName( $username );
$this->hook( 'LocalUserCreated', $this->never() );
$cache = \ObjectCache::getLocalClusterInstance();
$lock = $cache->getScopedLock( $cache->makeGlobalKey( 'account', md5( $username ) ) );
$ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
unset( $lock );
$this->unhook( 'LocalUserCreated' );
$this->assertEquals( \Status::newFatal( 'usernameinprogress' ), $ret );
$this->assertEquals( 0, $user->getId() );
$this->assertNotEquals( $username, $user->getName() );
$this->assertEquals( 0, $session->getUser()->getId() );
$this->assertSame( [
[ LogLevel::DEBUG, 'Could not acquire account creation lock' ],
], $logger->getBuffer() );
$logger->clearBuffer();
// Test pre-authentication provider fail
$session->clear();
$user = \User::newFromName( $username );
$this->hook( 'LocalUserCreated', $this->never() );
$ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
$this->unhook( 'LocalUserCreated' );
$this->assertEquals( \Status::newFatal( 'fail-in-pre' ), $ret );
$this->assertEquals( 0, $user->getId() );
$this->assertNotEquals( $username, $user->getName() );
$this->assertEquals( 0, $session->getUser()->getId() );
$this->assertSame( [
[ LogLevel::DEBUG, 'Provider denied creation of {username}: {reason}' ],
], $logger->getBuffer() );
$logger->clearBuffer();
$this->assertEquals(
StatusValue::newFatal( 'fail-in-pre' ), $session->get( 'AuthManager::AutoCreateBlacklist' )
);
$session->clear();
$user = \User::newFromName( $username );
$this->hook( 'LocalUserCreated', $this->never() );
$ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
$this->unhook( 'LocalUserCreated' );
$this->assertEquals( \Status::newFatal( 'fail-in-primary' ), $ret );
$this->assertEquals( 0, $user->getId() );
$this->assertNotEquals( $username, $user->getName() );
$this->assertEquals( 0, $session->getUser()->getId() );
$this->assertSame( [
[ LogLevel::DEBUG, 'Provider denied creation of {username}: {reason}' ],
], $logger->getBuffer() );
$logger->clearBuffer();
$this->assertEquals(
StatusValue::newFatal( 'fail-in-primary' ), $session->get( 'AuthManager::AutoCreateBlacklist' )
);
$session->clear();
$user = \User::newFromName( $username );
$this->hook( 'LocalUserCreated', $this->never() );
$ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
$this->unhook( 'LocalUserCreated' );
$this->assertEquals( \Status::newFatal( 'fail-in-secondary' ), $ret );
$this->assertEquals( 0, $user->getId() );
$this->assertNotEquals( $username, $user->getName() );
$this->assertEquals( 0, $session->getUser()->getId() );
$this->assertSame( [
[ LogLevel::DEBUG, 'Provider denied creation of {username}: {reason}' ],
], $logger->getBuffer() );
$logger->clearBuffer();
$this->assertEquals(
StatusValue::newFatal( 'fail-in-secondary' ), $session->get( 'AuthManager::AutoCreateBlacklist' )
);
// Test backoff
$cache = \ObjectCache::getLocalClusterInstance();
$backoffKey = wfMemcKey( 'AuthManager', 'autocreate-failed', md5( $username ) );
$cache->set( $backoffKey, true );
$session->clear();
$user = \User::newFromName( $username );
$this->hook( 'LocalUserCreated', $this->never() );
$ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
$this->unhook( 'LocalUserCreated' );
$this->assertEquals( \Status::newFatal( 'authmanager-autocreate-exception' ), $ret );
$this->assertEquals( 0, $user->getId() );
$this->assertNotEquals( $username, $user->getName() );
$this->assertEquals( 0, $session->getUser()->getId() );
$this->assertSame( [
[ LogLevel::DEBUG, '{username} denied by prior creation attempt failures' ],
], $logger->getBuffer() );
$logger->clearBuffer();
$this->assertSame( null, $session->get( 'AuthManager::AutoCreateBlacklist' ) );
$cache->delete( $backoffKey );
// Test addToDatabase fails
$session->clear();
$user = $this->getMockBuilder( \User::class )
->setMethods( [ 'addToDatabase' ] )->getMock();
$user->expects( $this->once() )->method( 'addToDatabase' )
->will( $this->returnValue( \Status::newFatal( 'because' ) ) );
$user->setName( $username );
$ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
$this->assertEquals( \Status::newFatal( 'because' ), $ret );
$this->assertEquals( 0, $user->getId() );
$this->assertNotEquals( $username, $user->getName() );
$this->assertEquals( 0, $session->getUser()->getId() );
$this->assertSame( [
[ LogLevel::INFO, 'creating new user ({username}) - from: {from}' ],
[ LogLevel::ERROR, '{username} failed with message {msg}' ],
], $logger->getBuffer() );
$logger->clearBuffer();
$this->assertSame( null, $session->get( 'AuthManager::AutoCreateBlacklist' ) );
// Test addToDatabase throws an exception
$cache = \ObjectCache::getLocalClusterInstance();
$backoffKey = wfMemcKey( 'AuthManager', 'autocreate-failed', md5( $username ) );
$this->assertFalse( $cache->get( $backoffKey ), 'sanity check' );
$session->clear();
$user = $this->getMockBuilder( \User::class )
->setMethods( [ 'addToDatabase' ] )->getMock();
$user->expects( $this->once() )->method( 'addToDatabase' )
->will( $this->throwException( new \Exception( 'Excepted' ) ) );
$user->setName( $username );
try {
$this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
$this->fail( 'Expected exception not thrown' );
} catch ( \Exception $ex ) {
$this->assertSame( 'Excepted', $ex->getMessage() );
}
$this->assertEquals( 0, $user->getId() );
$this->assertEquals( 0, $session->getUser()->getId() );
$this->assertSame( [
[ LogLevel::INFO, 'creating new user ({username}) - from: {from}' ],
[ LogLevel::ERROR, '{username} failed with exception {exception}' ],
], $logger->getBuffer() );
$logger->clearBuffer();
$this->assertSame( null, $session->get( 'AuthManager::AutoCreateBlacklist' ) );
$this->assertNotEquals( false, $cache->get( $backoffKey ) );
$cache->delete( $backoffKey );
// Test addToDatabase fails because the user already exists.
$session->clear();
$user = $this->getMockBuilder( \User::class )
->setMethods( [ 'addToDatabase' ] )->getMock();
$user->expects( $this->once() )->method( 'addToDatabase' )
->will( $this->returnCallback( function () use ( $username, &$user ) {
$oldUser = \User::newFromName( $username );
$status = $oldUser->addToDatabase();
$this->assertTrue( $status->isOK(), 'sanity check' );
$user->setId( $oldUser->getId() );
return \Status::newFatal( 'userexists' );
} ) );
$user->setName( $username );
$ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
$expect = \Status::newGood();
$expect->warning( 'userexists' );
$this->assertEquals( $expect, $ret );
$this->assertNotEquals( 0, $user->getId() );
$this->assertEquals( $username, $user->getName() );
$this->assertEquals( $user->getId(), $session->getUser()->getId() );
$this->assertSame( [
[ LogLevel::INFO, 'creating new user ({username}) - from: {from}' ],
[ LogLevel::INFO, '{username} already exists locally (race)' ],
], $logger->getBuffer() );
$logger->clearBuffer();
$this->assertSame( null, $session->get( 'AuthManager::AutoCreateBlacklist' ) );
// Success!
$session->clear();
$username = self::usernameForCreation();
$user = \User::newFromName( $username );
$this->hook( 'AuthPluginAutoCreate', $this->once() )
->with( $callback );
$this->hideDeprecated( 'AuthPluginAutoCreate hook (used in ' .
get_class( $wgHooks['AuthPluginAutoCreate'][0] ) . '::onAuthPluginAutoCreate)' );
$this->hook( 'LocalUserCreated', $this->once() )
->with( $callback, $this->equalTo( true ) );
$ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, true );
$this->unhook( 'LocalUserCreated' );
$this->unhook( 'AuthPluginAutoCreate' );
$this->assertEquals( \Status::newGood(), $ret );
$this->assertNotEquals( 0, $user->getId() );
$this->assertEquals( $username, $user->getName() );
$this->assertEquals( $user->getId(), $session->getUser()->getId() );
$this->assertSame( [
[ LogLevel::INFO, 'creating new user ({username}) - from: {from}' ],
], $logger->getBuffer() );
$logger->clearBuffer();
$dbw = wfGetDB( DB_MASTER );
$maxLogId = $dbw->selectField( 'logging', 'MAX(log_id)', [ 'log_type' => 'newusers' ] );
$session->clear();
$username = self::usernameForCreation();
$user = \User::newFromName( $username );
$this->hook( 'LocalUserCreated', $this->once() )
->with( $callback, $this->equalTo( true ) );
$ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, false );
$this->unhook( 'LocalUserCreated' );
$this->assertEquals( \Status::newGood(), $ret );
$this->assertNotEquals( 0, $user->getId() );
$this->assertEquals( $username, $user->getName() );
$this->assertEquals( 0, $session->getUser()->getId() );
$this->assertSame( [
[ LogLevel::INFO, 'creating new user ({username}) - from: {from}' ],
], $logger->getBuffer() );
$logger->clearBuffer();
$this->assertSame(
$maxLogId,
$dbw->selectField( 'logging', 'MAX(log_id)', [ 'log_type' => 'newusers' ] )
);
$this->config->set( 'NewUserLog', true );
$session->clear();
$username = self::usernameForCreation();
$user = \User::newFromName( $username );
$ret = $this->manager->autoCreateUser( $user, AuthManager::AUTOCREATE_SOURCE_SESSION, false );
$this->assertEquals( \Status::newGood(), $ret );
$logger->clearBuffer();
$data = \DatabaseLogEntry::getSelectQueryData();
$rows = iterator_to_array( $dbw->select(
$data['tables'],
$data['fields'],
[
'log_id > ' . (int)$maxLogId,
'log_type' => 'newusers'
] + $data['conds'],
__METHOD__,
$data['options'],
$data['join_conds']
) );
$this->assertCount( 1, $rows );
$entry = \DatabaseLogEntry::newFromRow( reset( $rows ) );
$this->assertSame( 'autocreate', $entry->getSubtype() );
$this->assertSame( $user->getId(), $entry->getPerformer()->getId() );
$this->assertSame( $user->getName(), $entry->getPerformer()->getName() );
$this->assertSame( $user->getUserPage()->getFullText(), $entry->getTarget()->getFullText() );
$this->assertSame( [ '4::userid' => $user->getId() ], $entry->getParameters() );
$workaroundPHPUnitBug = true;
}
/**
* @dataProvider provideGetAuthenticationRequests
* @param string $action
* @param array $expect
* @param array $state
*/
public function testGetAuthenticationRequests( $action, $expect, $state = [] ) {
$makeReq = function ( $key ) use ( $action ) {
$req = $this->createMock( AuthenticationRequest::class );
$req->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( $key ) );
$req->action = $action === AuthManager::ACTION_UNLINK ? AuthManager::ACTION_REMOVE : $action;
$req->key = $key;
return $req;
};
$cmpReqs = function ( $a, $b ) {
$ret = strcmp( get_class( $a ), get_class( $b ) );
if ( !$ret ) {
$ret = strcmp( $a->key, $b->key );
}
return $ret;
};
$good = StatusValue::newGood();
$mocks = [];
foreach ( [ 'pre', 'primary', 'secondary' ] as $key ) {
$class = ucfirst( $key ) . 'AuthenticationProvider';
$mocks[$key] = $this->getMockBuilder( "MediaWiki\\Auth\\$class" )
->setMethods( [
'getUniqueId', 'getAuthenticationRequests', 'providerAllowsAuthenticationDataChange',
] )
->getMockForAbstractClass();
$mocks[$key]->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( $key ) );
$mocks[$key]->expects( $this->any() )->method( 'getAuthenticationRequests' )
->will( $this->returnCallback( function ( $action ) use ( $key, $makeReq ) {
return [ $makeReq( "$key-$action" ), $makeReq( 'generic' ) ];
} ) );
$mocks[$key]->expects( $this->any() )->method( 'providerAllowsAuthenticationDataChange' )
->will( $this->returnValue( $good ) );
}
$primaries = [];
foreach ( [
PrimaryAuthenticationProvider::TYPE_NONE,
PrimaryAuthenticationProvider::TYPE_CREATE,
PrimaryAuthenticationProvider::TYPE_LINK
] as $type ) {
$class = 'PrimaryAuthenticationProvider';
$mocks["primary-$type"] = $this->getMockBuilder( "MediaWiki\\Auth\\$class" )
->setMethods( [
'getUniqueId', 'accountCreationType', 'getAuthenticationRequests',
'providerAllowsAuthenticationDataChange',
] )
->getMockForAbstractClass();
$mocks["primary-$type"]->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( "primary-$type" ) );
$mocks["primary-$type"]->expects( $this->any() )->method( 'accountCreationType' )
->will( $this->returnValue( $type ) );
$mocks["primary-$type"]->expects( $this->any() )->method( 'getAuthenticationRequests' )
->will( $this->returnCallback( function ( $action ) use ( $type, $makeReq ) {
return [ $makeReq( "primary-$type-$action" ), $makeReq( 'generic' ) ];
} ) );
$mocks["primary-$type"]->expects( $this->any() )
->method( 'providerAllowsAuthenticationDataChange' )
->will( $this->returnValue( $good ) );
$this->primaryauthMocks[] = $mocks["primary-$type"];
}
$mocks['primary2'] = $this->getMockBuilder( PrimaryAuthenticationProvider::class )
->setMethods( [
'getUniqueId', 'accountCreationType', 'getAuthenticationRequests',
'providerAllowsAuthenticationDataChange',
] )
->getMockForAbstractClass();
$mocks['primary2']->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( 'primary2' ) );
$mocks['primary2']->expects( $this->any() )->method( 'accountCreationType' )
->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_LINK ) );
$mocks['primary2']->expects( $this->any() )->method( 'getAuthenticationRequests' )
->will( $this->returnValue( [] ) );
$mocks['primary2']->expects( $this->any() )
->method( 'providerAllowsAuthenticationDataChange' )
->will( $this->returnCallback( function ( $req ) use ( $good ) {
return $req->key === 'generic' ? StatusValue::newFatal( 'no' ) : $good;
} ) );
$this->primaryauthMocks[] = $mocks['primary2'];
$this->preauthMocks = [ $mocks['pre'] ];
$this->secondaryauthMocks = [ $mocks['secondary'] ];
$this->initializeManager( true );
if ( $state ) {
if ( isset( $state['continueRequests'] ) ) {
$state['continueRequests'] = array_map( $makeReq, $state['continueRequests'] );
}
if ( $action === AuthManager::ACTION_LOGIN_CONTINUE ) {
$this->request->getSession()->setSecret( 'AuthManager::authnState', $state );
} elseif ( $action === AuthManager::ACTION_CREATE_CONTINUE ) {
$this->request->getSession()->setSecret( 'AuthManager::accountCreationState', $state );
} elseif ( $action === AuthManager::ACTION_LINK_CONTINUE ) {
$this->request->getSession()->setSecret( 'AuthManager::accountLinkState', $state );
}
}
$expectReqs = array_map( $makeReq, $expect );
if ( $action === AuthManager::ACTION_LOGIN ) {
$req = new RememberMeAuthenticationRequest;
$req->action = $action;
$req->required = AuthenticationRequest::REQUIRED;
$expectReqs[] = $req;
} elseif ( $action === AuthManager::ACTION_CREATE ) {
$req = new UsernameAuthenticationRequest;
$req->action = $action;
$expectReqs[] = $req;
$req = new UserDataAuthenticationRequest;
$req->action = $action;
$req->required = AuthenticationRequest::REQUIRED;
$expectReqs[] = $req;
}
usort( $expectReqs, $cmpReqs );
$actual = $this->manager->getAuthenticationRequests( $action );
foreach ( $actual as $req ) {
// Don't test this here.
$req->required = AuthenticationRequest::REQUIRED;
}
usort( $actual, $cmpReqs );
$this->assertEquals( $expectReqs, $actual );
// Test CreationReasonAuthenticationRequest gets returned
if ( $action === AuthManager::ACTION_CREATE ) {
$req = new CreationReasonAuthenticationRequest;
$req->action = $action;
$req->required = AuthenticationRequest::REQUIRED;
$expectReqs[] = $req;
usort( $expectReqs, $cmpReqs );
$actual = $this->manager->getAuthenticationRequests( $action, \User::newFromName( 'UTSysop' ) );
foreach ( $actual as $req ) {
// Don't test this here.
$req->required = AuthenticationRequest::REQUIRED;
}
usort( $actual, $cmpReqs );
$this->assertEquals( $expectReqs, $actual );
}
}
public static function provideGetAuthenticationRequests() {
return [
[
AuthManager::ACTION_LOGIN,
[ 'pre-login', 'primary-none-login', 'primary-create-login',
'primary-link-login', 'secondary-login', 'generic' ],
],
[
AuthManager::ACTION_CREATE,
[ 'pre-create', 'primary-none-create', 'primary-create-create',
'primary-link-create', 'secondary-create', 'generic' ],
],
[
AuthManager::ACTION_LINK,
[ 'primary-link-link', 'generic' ],
],
[
AuthManager::ACTION_CHANGE,
[ 'primary-none-change', 'primary-create-change', 'primary-link-change',
'secondary-change' ],
],
[
AuthManager::ACTION_REMOVE,
[ 'primary-none-remove', 'primary-create-remove', 'primary-link-remove',
'secondary-remove' ],
],
[
AuthManager::ACTION_UNLINK,
[ 'primary-link-remove' ],
],
[
AuthManager::ACTION_LOGIN_CONTINUE,
[],
],
[
AuthManager::ACTION_LOGIN_CONTINUE,
$reqs = [ 'continue-login', 'foo', 'bar' ],
[
'continueRequests' => $reqs,
],
],
[
AuthManager::ACTION_CREATE_CONTINUE,
[],
],
[
AuthManager::ACTION_CREATE_CONTINUE,
$reqs = [ 'continue-create', 'foo', 'bar' ],
[
'continueRequests' => $reqs,
],
],
[
AuthManager::ACTION_LINK_CONTINUE,
[],
],
[
AuthManager::ACTION_LINK_CONTINUE,
$reqs = [ 'continue-link', 'foo', 'bar' ],
[
'continueRequests' => $reqs,
],
],
];
}
public function testGetAuthenticationRequestsRequired() {
$makeReq = function ( $key, $required ) {
$req = $this->createMock( AuthenticationRequest::class );
$req->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( $key ) );
$req->action = AuthManager::ACTION_LOGIN;
$req->key = $key;
$req->required = $required;
return $req;
};
$cmpReqs = function ( $a, $b ) {
$ret = strcmp( get_class( $a ), get_class( $b ) );
if ( !$ret ) {
$ret = strcmp( $a->key, $b->key );
}
return $ret;
};
$good = StatusValue::newGood();
$primary1 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$primary1->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( 'primary1' ) );
$primary1->expects( $this->any() )->method( 'accountCreationType' )
->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
$primary1->expects( $this->any() )->method( 'getAuthenticationRequests' )
->will( $this->returnCallback( function ( $action ) use ( $makeReq ) {
return [
$makeReq( "primary-shared", AuthenticationRequest::REQUIRED ),
$makeReq( "required", AuthenticationRequest::REQUIRED ),
$makeReq( "optional", AuthenticationRequest::OPTIONAL ),
$makeReq( "foo", AuthenticationRequest::REQUIRED ),
$makeReq( "bar", AuthenticationRequest::REQUIRED ),
$makeReq( "baz", AuthenticationRequest::OPTIONAL ),
];
} ) );
$primary2 = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$primary2->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( 'primary2' ) );
$primary2->expects( $this->any() )->method( 'accountCreationType' )
->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
$primary2->expects( $this->any() )->method( 'getAuthenticationRequests' )
->will( $this->returnCallback( function ( $action ) use ( $makeReq ) {
return [
$makeReq( "primary-shared", AuthenticationRequest::REQUIRED ),
$makeReq( "required2", AuthenticationRequest::REQUIRED ),
$makeReq( "optional2", AuthenticationRequest::OPTIONAL ),
];
} ) );
$secondary = $this->getMockForAbstractClass( SecondaryAuthenticationProvider::class );
$secondary->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( 'secondary' ) );
$secondary->expects( $this->any() )->method( 'getAuthenticationRequests' )
->will( $this->returnCallback( function ( $action ) use ( $makeReq ) {
return [
$makeReq( "foo", AuthenticationRequest::OPTIONAL ),
$makeReq( "bar", AuthenticationRequest::REQUIRED ),
$makeReq( "baz", AuthenticationRequest::REQUIRED ),
];
} ) );
$rememberReq = new RememberMeAuthenticationRequest;
$rememberReq->action = AuthManager::ACTION_LOGIN;
$this->primaryauthMocks = [ $primary1, $primary2 ];
$this->secondaryauthMocks = [ $secondary ];
$this->initializeManager( true );
$actual = $this->manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN );
$expected = [
$rememberReq,
$makeReq( "primary-shared", AuthenticationRequest::PRIMARY_REQUIRED ),
$makeReq( "required", AuthenticationRequest::PRIMARY_REQUIRED ),
$makeReq( "required2", AuthenticationRequest::PRIMARY_REQUIRED ),
$makeReq( "optional", AuthenticationRequest::OPTIONAL ),
$makeReq( "optional2", AuthenticationRequest::OPTIONAL ),
$makeReq( "foo", AuthenticationRequest::PRIMARY_REQUIRED ),
$makeReq( "bar", AuthenticationRequest::REQUIRED ),
$makeReq( "baz", AuthenticationRequest::REQUIRED ),
];
usort( $actual, $cmpReqs );
usort( $expected, $cmpReqs );
$this->assertEquals( $expected, $actual );
$this->primaryauthMocks = [ $primary1 ];
$this->secondaryauthMocks = [ $secondary ];
$this->initializeManager( true );
$actual = $this->manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN );
$expected = [
$rememberReq,
$makeReq( "primary-shared", AuthenticationRequest::PRIMARY_REQUIRED ),
$makeReq( "required", AuthenticationRequest::PRIMARY_REQUIRED ),
$makeReq( "optional", AuthenticationRequest::OPTIONAL ),
$makeReq( "foo", AuthenticationRequest::PRIMARY_REQUIRED ),
$makeReq( "bar", AuthenticationRequest::REQUIRED ),
$makeReq( "baz", AuthenticationRequest::REQUIRED ),
];
usort( $actual, $cmpReqs );
usort( $expected, $cmpReqs );
$this->assertEquals( $expected, $actual );
}
public function testAllowsPropertyChange() {
$mocks = [];
foreach ( [ 'primary', 'secondary' ] as $key ) {
$class = ucfirst( $key ) . 'AuthenticationProvider';
$mocks[$key] = $this->getMockForAbstractClass( "MediaWiki\\Auth\\$class" );
$mocks[$key]->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( $key ) );
$mocks[$key]->expects( $this->any() )->method( 'providerAllowsPropertyChange' )
->will( $this->returnCallback( function ( $prop ) use ( $key ) {
return $prop !== $key;
} ) );
}
$this->primaryauthMocks = [ $mocks['primary'] ];
$this->secondaryauthMocks = [ $mocks['secondary'] ];
$this->initializeManager( true );
$this->assertTrue( $this->manager->allowsPropertyChange( 'foo' ) );
$this->assertFalse( $this->manager->allowsPropertyChange( 'primary' ) );
$this->assertFalse( $this->manager->allowsPropertyChange( 'secondary' ) );
}
public function testAutoCreateOnLogin() {
$username = self::usernameForCreation();
$req = $this->createMock( AuthenticationRequest::class );
$mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'primary' ) );
$mock->expects( $this->any() )->method( 'beginPrimaryAuthentication' )
->will( $this->returnValue( AuthenticationResponse::newPass( $username ) ) );
$mock->expects( $this->any() )->method( 'accountCreationType' )
->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
$mock->expects( $this->any() )->method( 'testUserExists' )->will( $this->returnValue( true ) );
$mock->expects( $this->any() )->method( 'testUserForCreation' )
->will( $this->returnValue( StatusValue::newGood() ) );
$mock2 = $this->getMockForAbstractClass( SecondaryAuthenticationProvider::class );
$mock2->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( 'secondary' ) );
$mock2->expects( $this->any() )->method( 'beginSecondaryAuthentication' )->will(
$this->returnValue(
AuthenticationResponse::newUI( [ $req ], $this->message( '...' ) )
)
);
$mock2->expects( $this->any() )->method( 'continueSecondaryAuthentication' )
->will( $this->returnValue( AuthenticationResponse::newAbstain() ) );
$mock2->expects( $this->any() )->method( 'testUserForCreation' )
->will( $this->returnValue( StatusValue::newGood() ) );
$this->primaryauthMocks = [ $mock ];
$this->secondaryauthMocks = [ $mock2 ];
$this->initializeManager( true );
$this->manager->setLogger( new \Psr\Log\NullLogger() );
$session = $this->request->getSession();
$session->clear();
$this->assertSame( 0, \User::newFromName( $username )->getId(),
'sanity check' );
$callback = $this->callback( function ( $user ) use ( $username ) {
return $user->getName() === $username;
} );
$this->hook( 'UserLoggedIn', $this->never() );
$this->hook( 'LocalUserCreated', $this->once() )->with( $callback, $this->equalTo( true ) );
$ret = $this->manager->beginAuthentication( [], 'http://localhost/' );
$this->unhook( 'LocalUserCreated' );
$this->unhook( 'UserLoggedIn' );
$this->assertSame( AuthenticationResponse::UI, $ret->status );
$id = (int)\User::newFromName( $username )->getId();
$this->assertNotSame( 0, \User::newFromName( $username )->getId() );
$this->assertSame( 0, $session->getUser()->getId() );
$this->hook( 'UserLoggedIn', $this->once() )->with( $callback );
$this->hook( 'LocalUserCreated', $this->never() );
$ret = $this->manager->continueAuthentication( [] );
$this->unhook( 'LocalUserCreated' );
$this->unhook( 'UserLoggedIn' );
$this->assertSame( AuthenticationResponse::PASS, $ret->status );
$this->assertSame( $username, $ret->username );
$this->assertSame( $id, $session->getUser()->getId() );
}
public function testAutoCreateFailOnLogin() {
$username = self::usernameForCreation();
$mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'primary' ) );
$mock->expects( $this->any() )->method( 'beginPrimaryAuthentication' )
->will( $this->returnValue( AuthenticationResponse::newPass( $username ) ) );
$mock->expects( $this->any() )->method( 'accountCreationType' )
->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
$mock->expects( $this->any() )->method( 'testUserExists' )->will( $this->returnValue( true ) );
$mock->expects( $this->any() )->method( 'testUserForCreation' )
->will( $this->returnValue( StatusValue::newFatal( 'fail-from-primary' ) ) );
$this->primaryauthMocks = [ $mock ];
$this->initializeManager( true );
$this->manager->setLogger( new \Psr\Log\NullLogger() );
$session = $this->request->getSession();
$session->clear();
$this->assertSame( 0, $session->getUser()->getId(),
'sanity check' );
$this->assertSame( 0, \User::newFromName( $username )->getId(),
'sanity check' );
$this->hook( 'UserLoggedIn', $this->never() );
$this->hook( 'LocalUserCreated', $this->never() );
$ret = $this->manager->beginAuthentication( [], 'http://localhost/' );
$this->unhook( 'LocalUserCreated' );
$this->unhook( 'UserLoggedIn' );
$this->assertSame( AuthenticationResponse::FAIL, $ret->status );
$this->assertSame( 'authmanager-authn-autocreate-failed', $ret->message->getKey() );
$this->assertSame( 0, \User::newFromName( $username )->getId() );
$this->assertSame( 0, $session->getUser()->getId() );
}
public function testAuthenticationSessionData() {
$this->initializeManager( true );
$this->assertNull( $this->manager->getAuthenticationSessionData( 'foo' ) );
$this->manager->setAuthenticationSessionData( 'foo', 'foo!' );
$this->manager->setAuthenticationSessionData( 'bar', 'bar!' );
$this->assertSame( 'foo!', $this->manager->getAuthenticationSessionData( 'foo' ) );
$this->assertSame( 'bar!', $this->manager->getAuthenticationSessionData( 'bar' ) );
$this->manager->removeAuthenticationSessionData( 'foo' );
$this->assertNull( $this->manager->getAuthenticationSessionData( 'foo' ) );
$this->assertSame( 'bar!', $this->manager->getAuthenticationSessionData( 'bar' ) );
$this->manager->removeAuthenticationSessionData( 'bar' );
$this->assertNull( $this->manager->getAuthenticationSessionData( 'bar' ) );
$this->manager->setAuthenticationSessionData( 'foo', 'foo!' );
$this->manager->setAuthenticationSessionData( 'bar', 'bar!' );
$this->manager->removeAuthenticationSessionData( null );
$this->assertNull( $this->manager->getAuthenticationSessionData( 'foo' ) );
$this->assertNull( $this->manager->getAuthenticationSessionData( 'bar' ) );
}
public function testCanLinkAccounts() {
$types = [
PrimaryAuthenticationProvider::TYPE_CREATE => true,
PrimaryAuthenticationProvider::TYPE_LINK => true,
PrimaryAuthenticationProvider::TYPE_NONE => false,
];
foreach ( $types as $type => $can ) {
$mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( $type ) );
$mock->expects( $this->any() )->method( 'accountCreationType' )
->will( $this->returnValue( $type ) );
$this->primaryauthMocks = [ $mock ];
$this->initializeManager( true );
$this->assertSame( $can, $this->manager->canCreateAccounts(), $type );
}
}
public function testBeginAccountLink() {
$user = \User::newFromName( 'UTSysop' );
$this->initializeManager();
$this->request->getSession()->setSecret( 'AuthManager::accountLinkState', 'test' );
try {
$this->manager->beginAccountLink( $user, [], 'http://localhost/' );
$this->fail( 'Expected exception not thrown' );
} catch ( \LogicException $ex ) {
$this->assertEquals( 'Account linking is not possible', $ex->getMessage() );
}
$this->assertNull( $this->request->getSession()->getSecret( 'AuthManager::accountLinkState' ) );
$mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
$mock->expects( $this->any() )->method( 'accountCreationType' )
->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_LINK ) );
$this->primaryauthMocks = [ $mock ];
$this->initializeManager( true );
$ret = $this->manager->beginAccountLink( new \User, [], 'http://localhost/' );
$this->assertSame( AuthenticationResponse::FAIL, $ret->status );
$this->assertSame( 'noname', $ret->message->getKey() );
$ret = $this->manager->beginAccountLink(
\User::newFromName( 'UTDoesNotExist' ), [], 'http://localhost/'
);
$this->assertSame( AuthenticationResponse::FAIL, $ret->status );
$this->assertSame( 'authmanager-userdoesnotexist', $ret->message->getKey() );
}
public function testContinueAccountLink() {
$user = \User::newFromName( 'UTSysop' );
$this->initializeManager();
$session = [
'userid' => $user->getId(),
'username' => $user->getName(),
'primary' => 'X',
];
try {
$this->manager->continueAccountLink( [] );
$this->fail( 'Expected exception not thrown' );
} catch ( \LogicException $ex ) {
$this->assertEquals( 'Account linking is not possible', $ex->getMessage() );
}
$mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'X' ) );
$mock->expects( $this->any() )->method( 'accountCreationType' )
->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_LINK ) );
$mock->expects( $this->any() )->method( 'beginPrimaryAccountLink' )->will(
$this->returnValue( AuthenticationResponse::newFail( $this->message( 'fail' ) ) )
);
$this->primaryauthMocks = [ $mock ];
$this->initializeManager( true );
$this->request->getSession()->setSecret( 'AuthManager::accountLinkState', null );
$ret = $this->manager->continueAccountLink( [] );
$this->assertSame( AuthenticationResponse::FAIL, $ret->status );
$this->assertSame( 'authmanager-link-not-in-progress', $ret->message->getKey() );
$this->request->getSession()->setSecret( 'AuthManager::accountLinkState',
[ 'username' => $user->getName() . '<>' ] + $session );
$ret = $this->manager->continueAccountLink( [] );
$this->assertSame( AuthenticationResponse::FAIL, $ret->status );
$this->assertSame( 'noname', $ret->message->getKey() );
$this->assertNull( $this->request->getSession()->getSecret( 'AuthManager::accountLinkState' ) );
$id = $user->getId();
$this->request->getSession()->setSecret( 'AuthManager::accountLinkState',
[ 'userid' => $id + 1 ] + $session );
try {
$ret = $this->manager->continueAccountLink( [] );
$this->fail( 'Expected exception not thrown' );
} catch ( \UnexpectedValueException $ex ) {
$this->assertEquals(
"User \"{$user->getName()}\" is valid, but ID $id != " . ( $id + 1 ) . '!',
$ex->getMessage()
);
}
$this->assertNull( $this->request->getSession()->getSecret( 'AuthManager::accountLinkState' ) );
}
/**
* @dataProvider provideAccountLink
* @param StatusValue $preTest
* @param array $primaryResponses
* @param array $managerResponses
*/
public function testAccountLink(
StatusValue $preTest, array $primaryResponses, array $managerResponses
) {
$user = \User::newFromName( 'UTSysop' );
$this->initializeManager();
// Set up lots of mocks...
$req = $this->getMockForAbstractClass( AuthenticationRequest::class );
$req->primary = $primaryResponses;
$mocks = [];
foreach ( [ 'pre', 'primary' ] as $key ) {
$class = ucfirst( $key ) . 'AuthenticationProvider';
$mocks[$key] = $this->getMockForAbstractClass(
"MediaWiki\\Auth\\$class", [], "Mock$class"
);
$mocks[$key]->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( $key ) );
for ( $i = 2; $i <= 3; $i++ ) {
$mocks[$key . $i] = $this->getMockForAbstractClass(
"MediaWiki\\Auth\\$class", [], "Mock$class"
);
$mocks[$key . $i]->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( $key . $i ) );
}
}
$mocks['pre']->expects( $this->any() )->method( 'testForAccountLink' )
->will( $this->returnCallback(
function ( $u )
use ( $user, $preTest )
{
$this->assertSame( $user->getId(), $u->getId() );
$this->assertSame( $user->getName(), $u->getName() );
return $preTest;
}
) );
$mocks['pre2']->expects( $this->atMost( 1 ) )->method( 'testForAccountLink' )
->will( $this->returnValue( StatusValue::newGood() ) );
$mocks['primary']->expects( $this->any() )->method( 'accountCreationType' )
->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_LINK ) );
$ct = count( $req->primary );
$callback = $this->returnCallback( function ( $u, $reqs ) use ( $user, $req ) {
$this->assertSame( $user->getId(), $u->getId() );
$this->assertSame( $user->getName(), $u->getName() );
$foundReq = false;
foreach ( $reqs as $r ) {
$this->assertSame( $user->getName(), $r->username );
$foundReq = $foundReq || get_class( $r ) === get_class( $req );
}
$this->assertTrue( $foundReq, '$reqs contains $req' );
return array_shift( $req->primary );
} );
$mocks['primary']->expects( $this->exactly( min( 1, $ct ) ) )
->method( 'beginPrimaryAccountLink' )
->will( $callback );
$mocks['primary']->expects( $this->exactly( max( 0, $ct - 1 ) ) )
->method( 'continuePrimaryAccountLink' )
->will( $callback );
$abstain = AuthenticationResponse::newAbstain();
$mocks['primary2']->expects( $this->any() )->method( 'accountCreationType' )
->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_LINK ) );
$mocks['primary2']->expects( $this->atMost( 1 ) )->method( 'beginPrimaryAccountLink' )
->will( $this->returnValue( $abstain ) );
$mocks['primary2']->expects( $this->never() )->method( 'continuePrimaryAccountLink' );
$mocks['primary3']->expects( $this->any() )->method( 'accountCreationType' )
->will( $this->returnValue( PrimaryAuthenticationProvider::TYPE_CREATE ) );
$mocks['primary3']->expects( $this->never() )->method( 'beginPrimaryAccountLink' );
$mocks['primary3']->expects( $this->never() )->method( 'continuePrimaryAccountLink' );
$this->preauthMocks = [ $mocks['pre'], $mocks['pre2'] ];
$this->primaryauthMocks = [ $mocks['primary3'], $mocks['primary2'], $mocks['primary'] ];
$this->logger = new \TestLogger( true, function ( $message, $level ) {
return $level === LogLevel::DEBUG ? null : $message;
} );
$this->initializeManager( true );
$constraint = \PHPUnit_Framework_Assert::logicalOr(
$this->equalTo( AuthenticationResponse::PASS ),
$this->equalTo( AuthenticationResponse::FAIL )
);
$providers = array_merge( $this->preauthMocks, $this->primaryauthMocks );
foreach ( $providers as $p ) {
$p->postCalled = false;
$p->expects( $this->atMost( 1 ) )->method( 'postAccountLink' )
->willReturnCallback( function ( $user, $response ) use ( $constraint, $p ) {
$this->assertInstanceOf( \User::class, $user );
$this->assertSame( 'UTSysop', $user->getName() );
$this->assertInstanceOf( AuthenticationResponse::class, $response );
$this->assertThat( $response->status, $constraint );
$p->postCalled = $response->status;
} );
}
$first = true;
$created = false;
$expectLog = [];
foreach ( $managerResponses as $i => $response ) {
if ( $response instanceof AuthenticationResponse &&
$response->status === AuthenticationResponse::PASS
) {
$expectLog[] = [ LogLevel::INFO, 'Account linked to {user} by primary' ];
}
$ex = null;
try {
if ( $first ) {
$ret = $this->manager->beginAccountLink( $user, [ $req ], 'http://localhost/' );
} else {
$ret = $this->manager->continueAccountLink( [ $req ] );
}
if ( $response instanceof \Exception ) {
$this->fail( 'Expected exception not thrown', "Response $i" );
}
} catch ( \Exception $ex ) {
if ( !$response instanceof \Exception ) {
throw $ex;
}
$this->assertEquals( $response->getMessage(), $ex->getMessage(), "Response $i, exception" );
$this->assertNull( $this->request->getSession()->getSecret( 'AuthManager::accountLinkState' ),
"Response $i, exception, session state" );
return;
}
$this->assertSame( 'http://localhost/', $req->returnToUrl );
$ret->message = $this->message( $ret->message );
$this->assertResponseEquals( $response, $ret, "Response $i, response" );
if ( $response->status === AuthenticationResponse::PASS ||
$response->status === AuthenticationResponse::FAIL
) {
$this->assertNull( $this->request->getSession()->getSecret( 'AuthManager::accountLinkState' ),
"Response $i, session state" );
foreach ( $providers as $p ) {
$this->assertSame( $response->status, $p->postCalled,
"Response $i, post-auth callback called" );
}
} else {
$this->assertNotNull(
$this->request->getSession()->getSecret( 'AuthManager::accountLinkState' ),
"Response $i, session state"
);
foreach ( $ret->neededRequests as $neededReq ) {
$this->assertEquals( AuthManager::ACTION_LINK, $neededReq->action,
"Response $i, neededRequest action" );
}
$this->assertEquals(
$ret->neededRequests,
$this->manager->getAuthenticationRequests( AuthManager::ACTION_LINK_CONTINUE ),
"Response $i, continuation check"
);
foreach ( $providers as $p ) {
$this->assertFalse( $p->postCalled, "Response $i, post-auth callback not called" );
}
}
$first = false;
}
$this->assertSame( $expectLog, $this->logger->getBuffer() );
}
public function provideAccountLink() {
$req = $this->getMockForAbstractClass( AuthenticationRequest::class );
$good = StatusValue::newGood();
return [
'Pre-link test fail in pre' => [
StatusValue::newFatal( 'fail-from-pre' ),
[],
[
AuthenticationResponse::newFail( $this->message( 'fail-from-pre' ) ),
]
],
'Failure in primary' => [
$good,
$tmp = [
AuthenticationResponse::newFail( $this->message( 'fail-from-primary' ) ),
],
$tmp
],
'All primary abstain' => [
$good,
[
AuthenticationResponse::newAbstain(),
],
[
AuthenticationResponse::newFail( $this->message( 'authmanager-link-no-primary' ) )
]
],
'Primary UI, then redirect, then fail' => [
$good,
$tmp = [
AuthenticationResponse::newUI( [ $req ], $this->message( '...' ) ),
AuthenticationResponse::newRedirect( [ $req ], '/foo.html', [ 'foo' => 'bar' ] ),
AuthenticationResponse::newFail( $this->message( 'fail-in-primary-continue' ) ),
],
$tmp
],
'Primary redirect, then abstain' => [
$good,
[
$tmp = AuthenticationResponse::newRedirect(
[ $req ], '/foo.html', [ 'foo' => 'bar' ]
),
AuthenticationResponse::newAbstain(),
],
[
$tmp,
new \DomainException(
'MockPrimaryAuthenticationProvider::continuePrimaryAccountLink() returned ABSTAIN'
)
]
],
'Primary UI, then pass' => [
$good,
[
$tmp1 = AuthenticationResponse::newUI( [ $req ], $this->message( '...' ) ),
AuthenticationResponse::newPass(),
],
[
$tmp1,
AuthenticationResponse::newPass( '' ),
]
],
'Primary pass' => [
$good,
[
AuthenticationResponse::newPass( '' ),
],
[
AuthenticationResponse::newPass( '' ),
]
],
];
}
}