Add a central ID lookup service

Anything that wants to be "central" right now has to depend on
CentralAuth, and then either can't work without CentralAuth or has to
branch all over the place based on whether CentralAuth is present. Most
of the time all it really needs is a mapping from local users to central
user IDs and back or the ability to query whether the local user is
attached on some other wiki, so let's make an interface for that in
core.

See I52aa0460 for an example implementation (CentralAuth), and Ibd192e29
for an example use (OAuth).

Bug: T111302
Change-Id: I49568358ec35fdfd0b9e53e441adabded5c7b80f
This commit is contained in:
Brad Jorsch 2015-11-12 18:21:19 -05:00 committed by Bryan Davis
parent a9521635bf
commit d032bb52cd
13 changed files with 683 additions and 6 deletions

View file

@ -84,6 +84,9 @@ production.
* Added MWTimestamp::getTimezoneString() which returns the localized timezone
string, if available. To localize this string, see the comments of
$wgLocaltimezone in includes/DefaultSettings.php.
* Added CentralIdLookup, a service that allows extensions needing a concept of
"central" users to get that without having to know about specific central
authentication extensions.
=== External library changes in 1.27 ===
==== Upgraded external libraries ====

View file

@ -198,6 +198,7 @@ $wgAutoloadLocalClasses = array(
'CdbException' => __DIR__ . '/includes/compat/CdbCompat.php',
'CdbReader' => __DIR__ . '/includes/compat/CdbCompat.php',
'CdbWriter' => __DIR__ . '/includes/compat/CdbCompat.php',
'CentralIdLookup' => __DIR__ . '/includes/user/CentralIdLookup.php',
'CgzCopyTransaction' => __DIR__ . '/maintenance/storage/recompressTracked.php',
'ChangePassword' => __DIR__ . '/maintenance/changePassword.php',
'ChangeTags' => __DIR__ . '/includes/changetags/ChangeTags.php',
@ -693,6 +694,7 @@ $wgAutoloadLocalClasses = array(
'LocalFileDeleteBatch' => __DIR__ . '/includes/filerepo/file/LocalFile.php',
'LocalFileMoveBatch' => __DIR__ . '/includes/filerepo/file/LocalFile.php',
'LocalFileRestoreBatch' => __DIR__ . '/includes/filerepo/file/LocalFile.php',
'LocalIdLookup' => __DIR__ . '/includes/user/LocalIdLookup.php',
'LocalRepo' => __DIR__ . '/includes/filerepo/LocalRepo.php',
'LocalSettingsGenerator' => __DIR__ . '/includes/installer/LocalSettingsGenerator.php',
'LocalisationCache' => __DIR__ . '/includes/cache/LocalisationCache.php',
@ -1320,9 +1322,9 @@ $wgAutoloadLocalClasses = array(
'UploadStashZeroLengthFileException' => __DIR__ . '/includes/upload/UploadStash.php',
'UppercaseCollation' => __DIR__ . '/includes/Collation.php',
'UsageException' => __DIR__ . '/includes/api/ApiMain.php',
'User' => __DIR__ . '/includes/User.php',
'UserArray' => __DIR__ . '/includes/UserArray.php',
'UserArrayFromResult' => __DIR__ . '/includes/UserArrayFromResult.php',
'User' => __DIR__ . '/includes/user/User.php',
'UserArray' => __DIR__ . '/includes/user/UserArray.php',
'UserArrayFromResult' => __DIR__ . '/includes/user/UserArrayFromResult.php',
'UserBlockedError' => __DIR__ . '/includes/exception/UserBlockedError.php',
'UserCache' => __DIR__ . '/includes/cache/UserCache.php',
'UserDupes' => __DIR__ . '/maintenance/userDupes.inc',
@ -1330,7 +1332,7 @@ $wgAutoloadLocalClasses = array(
'UserNotLoggedIn' => __DIR__ . '/includes/exception/UserNotLoggedIn.php',
'UserOptions' => __DIR__ . '/maintenance/userOptions.inc',
'UserPasswordPolicy' => __DIR__ . '/includes/password/UserPasswordPolicy.php',
'UserRightsProxy' => __DIR__ . '/includes/UserRightsProxy.php',
'UserRightsProxy' => __DIR__ . '/includes/user/UserRightsProxy.php',
'UsercreateTemplate' => __DIR__ . '/includes/templates/Usercreate.php',
'UserloginTemplate' => __DIR__ . '/includes/templates/Userlogin.php',
'UserrightsPage' => __DIR__ . '/includes/specials/SpecialUserrights.php',

View file

@ -4333,6 +4333,21 @@ $wgActiveUserDays = 30;
* @{
*/
/**
* Central ID lookup providers
* Key is the provider ID, value is a specification for ObjectFactory
* @since 1.27
*/
$wgCentralIdLookupProviders = array(
'local' => array( 'class' => 'LocalIdLookup' ),
);
/**
* Central ID lookup provider to use by default
* @var string
*/
$wgCentralIdLookupProvider = 'local';
/**
* Password policy for local wiki users. A user's effective policy
* is the superset of all policy statements from the policies for the

View file

@ -0,0 +1,208 @@
<?php
/**
* A central user id lookup service
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
/**
* The CentralIdLookup service allows for connecting local users with
* cluster-wide IDs.
*/
abstract class CentralIdLookup implements IDBAccessObject {
// Audience options for accessors
const AUDIENCE_PUBLIC = 1;
const AUDIENCE_RAW = 2;
/** @var CentralIdLookup[][] */
private static $instances = array();
/** @var string */
private $providerId;
/**
* Fetch a CentralIdLookup
* @param string|null $providerId Provider ID from $wgCentralIdLookupProviders
* @return CentralIdLookup|null
*/
public static function factory( $providerId = null ) {
global $wgCentralIdLookupProviders, $wgCentralIdLookupProvider;
if ( $providerId === null ) {
$providerId = $wgCentralIdLookupProvider;
}
if ( !array_key_exists( $providerId, self::$instances ) ) {
self::$instances[$providerId] = null;
if ( isset( $wgCentralIdLookupProviders[$providerId] ) ) {
$provider = ObjectFactory::getObjectFromSpec( $wgCentralIdLookupProviders[$providerId] );
if ( $provider instanceof CentralIdLookup ) {
$provider->providerId = $providerId;
self::$instances[$providerId] = $provider;
}
}
}
return self::$instances[$providerId];
}
final public function getProviderId() {
return $this->providerId;
}
/**
* Check that the "audience" parameter is valid
* @param int|User $audience One of the audience constants, or a specific user
* @return User|null User to check against, or null if no checks are needed
* @throws InvalidArgumentException
*/
protected function checkAudience( $audience ) {
if ( $audience instanceof User ) {
return $audience;
}
if ( $audience === self::AUDIENCE_PUBLIC ) {
return new User;
}
if ( $audience === self::AUDIENCE_RAW ) {
return null;
}
throw new InvalidArgumentException( 'Invalid audience' );
}
/**
* Check that a User is attached on the specified wiki.
*
* If unattached local accounts don't exist in your extension, this comes
* down to a check whether the central account exists at all and that
* $wikiId is using the same central database.
*
* @param User $user
* @param string|null $wikiId Wiki to check attachment status. If null, check the current wiki.
* @return bool
*/
abstract public function isAttached( User $user, $wikiId = null );
/**
* Given central user IDs, return the (local) user names
* @note There's no requirement that the user names actually exist locally,
* or if they do that they're actually attached to the central account.
* @param array $idToName Array with keys being central user IDs
* @param int|User $audience One of the audience constants, or a specific user
* @param int $flags IDBAccessObject read flags
* @return array Copy of $idToName with values set to user names (or
* empty-string if the user exists but $audience lacks the rights needed
* to see it). IDs not corresponding to a user are unchanged.
*/
abstract public function lookupCentralIds(
array $idToName, $audience = self::AUDIENCE_PUBLIC, $flags = self::READ_NORMAL
);
/**
* Given (local) user names, return the central IDs
* @note There's no requirement that the user names actually exist locally,
* or if they do that they're actually attached to the central account.
* @param array $nameToId Array with keys being canonicalized user names
* @param int|User $audience One of the audience constants, or a specific user
* @param int $flags IDBAccessObject read flags
* @return array Copy of $nameToId with values set to central IDs.
* Names not corresponding to a user (or $audience lacks the rights needed
* to see it) are unchanged.
*/
abstract public function lookupUserNames(
array $nameToId, $audience = self::AUDIENCE_PUBLIC, $flags = self::READ_NORMAL
);
/**
* Given a central user ID, return the (local) user name
* @note There's no requirement that the user name actually exists locally,
* or if it does that it's actually attached to the central account.
* @param int $id Central user ID
* @param int|User $audience One of the audience constants, or a specific user
* @param int $flags IDBAccessObject read flags
* @return string|null User name, or empty string if $audience lacks the
* rights needed to see it, or null if $id doesn't correspond to a user
*/
public function nameFromCentralId(
$id, $audience = self::AUDIENCE_PUBLIC, $flags = self::READ_NORMAL
) {
$idToName = $this->lookupCentralIds( array( $id => null ), $audience, $flags );
return $idToName[$id];
}
/**
* Given a (local) user name, return the central ID
* @note There's no requirement that the user name actually exists locally,
* or if it does that it's actually attached to the central account.
* @param string $name Canonicalized user name
* @param int|User $audience One of the audience constants, or a specific user
* @param int $flags IDBAccessObject read flags
* @return int User ID; 0 if the name does not correspond to a user or
* $audience lacks the rights needed to see it.
*/
public function centralIdFromName(
$name, $audience = self::AUDIENCE_PUBLIC, $flags = self::READ_NORMAL
) {
$nameToId = $this->lookupUserNames( array( $name => 0 ), $audience, $flags );
return $nameToId[$name];
}
/**
* Given a central user ID, return a local User object
* @note Unlike nameFromCentralId(), this does guarantee that the local
* user exists and is attached to the central account.
* @param int $id Central user ID
* @param int|User $audience One of the audience constants, or a specific user
* @param int $flags IDBAccessObject read flags
* @return User|null Local user, or null if: $id doesn't correspond to a
* user, $audience lacks the rights needed to see the user, the user
* doesn't exist locally, or the user isn't locally attached.
*/
public function localUserFromCentralId(
$id, $audience = self::AUDIENCE_PUBLIC, $flags = self::READ_NORMAL
) {
$name = $this->nameFromCentralId( $id, $audience, $flags );
if ( $name !== null && $name !== '' ) {
$user = User::newFromName( $name );
if ( $user && $user->getId() && $this->isAttached( $user ) ) {
return $user;
}
}
return null;
}
/**
* Given a local User object, return the central ID
* @note Unlike centralIdFromName(), this does guarantee that the local
* user is attached to the central account.
* @param User $user Local user
* @param int|User $audience One of the audience constants, or a specific user
* @param int $flags IDBAccessObject read flags
* @return int User ID; 0 if the local user does not correspond to a
* central user, $audience lacks the rights needed to see it, or the
* central user isn't locally attached.
*/
public function centralIdFromLocalUser(
User $user, $audience = self::AUDIENCE_PUBLIC, $flags = self::READ_NORMAL
) {
return $this->isAttached( $user )
? $this->centralIdFromName( $user->getName(), $audience, $flags )
: 0;
}
}

View file

@ -0,0 +1,119 @@
<?php
/**
* A central user id lookup service implementation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
/**
* A CentralIdLookup provider that just uses local IDs. Useful if the wiki
* isn't part of a cluster or you're using shared user tables.
*
* @note Shared user table support expects that all wikis involved have
* $wgSharedDB and $wgSharedTables set, and that all wikis involved in the
* sharing are listed in $wgLocalDatabases, and that no wikis not involved in
* the sharing are listed in $wgLocalDatabases.
*/
class LocalIdLookup extends CentralIdLookup {
public function isAttached( User $user, $wikiId = null ) {
global $wgSharedDB, $wgSharedTables, $wgLocalDatabases;
// If the user has no ID, it can't be attached
if ( !$user->getId() ) {
return false;
}
// Easy case, we're checking locally
if ( $wikiId === null || $wikiId === wfWikiId() ) {
return true;
}
// Assume that shared user tables are set up as described above, if
// they're being used at all.
return $wgSharedDB !== null &&
in_array( 'user', $wgSharedTables, true ) &&
in_array( $wikiId, $wgLocalDatabases, true );
}
public function lookupCentralIds(
array $idToName, $audience = self::AUDIENCE_PUBLIC, $flags = self::READ_NORMAL
) {
if ( !$idToName ) {
return array();
}
$audience = $this->checkAudience( $audience );
$db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_SLAVE );
$options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
? array( 'LOCK IN SHARE MODE' )
: array();
$tables = array( 'user' );
$fields = array( 'user_id', 'user_name' );
$where = array(
'user_id' => array_map( 'intval', array_keys( $idToName ) ),
);
$join = array();
if ( $audience && !$audience->isAllowed( 'hideuser' ) ) {
$tables[] = 'ipblocks';
$join['ipblocks'] = array( 'LEFT JOIN', 'ipb_user=user_id' );
$fields[] = 'ipb_deleted';
}
$res = $db->select( $tables, $fields, $where, __METHOD__, $options, $join );
foreach ( $res as $row ) {
$idToName[$row->user_id] = empty( $row->ipb_deleted ) ? $row->user_name : '';
}
return $idToName;
}
public function lookupUserNames(
array $nameToId, $audience = self::AUDIENCE_PUBLIC, $flags = self::READ_NORMAL
) {
if ( !$nameToId ) {
return array();
}
$audience = $this->checkAudience( $audience );
$db = wfGetDB( ( $flags & self::READ_LATEST ) ? DB_MASTER : DB_SLAVE );
$options = ( ( $flags & self::READ_LOCKING ) == self::READ_LOCKING )
? array( 'LOCK IN SHARE MODE' )
: array();
$tables = array( 'user' );
$fields = array( 'user_id', 'user_name' );
$where = array(
'user_name' => array_map( 'strval', array_keys( $nameToId ) ),
);
$join = array();
if ( $audience && !$audience->isAllowed( 'hideuser' ) ) {
$tables[] = 'ipblocks';
$join['ipblocks'] = array( 'LEFT JOIN', 'ipb_user=user_id' );
$where[] = 'ipb_deleted = 0 OR ipb_deleted IS NULL';
}
$res = $db->select( $tables, $fields, $where, __METHOD__, $options, $join );
foreach ( $res as $row ) {
$nameToId[$row->user_name] = (int)$row->user_id;
}
return $nameToId;
}
}

View file

@ -0,0 +1,181 @@
<?php
/**
* @covers CentralIdLookup
* @group Database
*/
class CentralIdLookupTest extends MediaWikiTestCase {
public function testFactory() {
$mock = $this->getMockForAbstractClass( 'CentralIdLookup' );
$this->setMwGlobals( array(
'wgCentralIdLookupProviders' => array(
'local' => array( 'class' => 'LocalIdLookup' ),
'local2' => array( 'class' => 'LocalIdLookup' ),
'mock' => array( 'factory' => function () use ( $mock ) {
return $mock;
} ),
'bad' => array( 'class' => 'stdClass' ),
),
'wgCentralIdLookupProvider' => 'mock',
) );
$this->assertSame( $mock, CentralIdLookup::factory() );
$this->assertSame( $mock, CentralIdLookup::factory( 'mock' ) );
$this->assertSame( 'mock', $mock->getProviderId() );
$local = CentralIdLookup::factory( 'local' );
$this->assertNotSame( $mock, $local );
$this->assertInstanceOf( 'LocalIdLookup', $local );
$this->assertSame( $local, CentralIdLookup::factory( 'local' ) );
$this->assertSame( 'local', $local->getProviderId() );
$local2 = CentralIdLookup::factory( 'local2' );
$this->assertNotSame( $local, $local2 );
$this->assertInstanceOf( 'LocalIdLookup', $local2 );
$this->assertSame( 'local2', $local2->getProviderId() );
$this->assertNull( CentralIdLookup::factory( 'unconfigured' ) );
$this->assertNull( CentralIdLookup::factory( 'bad' ) );
}
public function testCheckAudience() {
$mock = TestingAccessWrapper::newFromObject(
$this->getMockForAbstractClass( 'CentralIdLookup' )
);
$user = User::newFromName( 'UTSysop' );
$this->assertSame( $user, $mock->checkAudience( $user ) );
$user = $mock->checkAudience( CentralIdLookup::AUDIENCE_PUBLIC );
$this->assertInstanceOf( 'User', $user );
$this->assertSame( 0, $user->getId() );
$this->assertNull( $mock->checkAudience( CentralIdLookup::AUDIENCE_RAW ) );
try {
$mock->checkAudience( 100 );
$this->fail( 'Expected exception not thrown' );
} catch ( InvalidArgumentException $ex ) {
$this->assertSame( 'Invalid audience', $ex->getMessage() );
}
}
public function testNameFromCentralId() {
$mock = $this->getMockForAbstractClass( 'CentralIdLookup' );
$mock->expects( $this->once() )->method( 'lookupCentralIds' )
->with(
$this->equalTo( array( 15 => null ) ),
$this->equalTo( CentralIdLookup::AUDIENCE_RAW ),
$this->equalTo( CentralIdLookup::READ_LATEST )
)
->will( $this->returnValue( array( 15 => 'FooBar' ) ) );
$this->assertSame(
'FooBar',
$mock->nameFromCentralId( 15, CentralIdLookup::AUDIENCE_RAW, CentralIdLookup::READ_LATEST )
);
}
/**
* @dataProvider provideLocalUserFromCentralId
* @param string $name
* @param bool $succeeds
*/
public function testLocalUserFromCentralId( $name, $succeeds ) {
$mock = $this->getMockForAbstractClass( 'CentralIdLookup' );
$mock->expects( $this->any() )->method( 'isAttached' )
->will( $this->returnValue( true ) );
$mock->expects( $this->once() )->method( 'lookupCentralIds' )
->with(
$this->equalTo( array( 42 => null ) ),
$this->equalTo( CentralIdLookup::AUDIENCE_RAW ),
$this->equalTo( CentralIdLookup::READ_LATEST )
)
->will( $this->returnValue( array( 42 => $name ) ) );
$user = $mock->localUserFromCentralId(
42, CentralIdLookup::AUDIENCE_RAW, CentralIdLookup::READ_LATEST
);
if ( $succeeds ) {
$this->assertInstanceOf( 'User', $user );
$this->assertSame( $name, $user->getName() );
} else {
$this->assertNull( $user );
}
$mock = $this->getMockForAbstractClass( 'CentralIdLookup' );
$mock->expects( $this->any() )->method( 'isAttached' )
->will( $this->returnValue( false ) );
$mock->expects( $this->once() )->method( 'lookupCentralIds' )
->with(
$this->equalTo( array( 42 => null ) ),
$this->equalTo( CentralIdLookup::AUDIENCE_RAW ),
$this->equalTo( CentralIdLookup::READ_LATEST )
)
->will( $this->returnValue( array( 42 => $name ) ) );
$this->assertNull(
$mock->localUserFromCentralId( 42, CentralIdLookup::AUDIENCE_RAW, CentralIdLookup::READ_LATEST )
);
}
public static function provideLocalUserFromCentralId() {
return array(
array( 'UTSysop', true ),
array( 'UTDoesNotExist', false ),
array( null, false ),
array( '', false ),
array( '<X>', false ),
);
}
public function testCentralIdFromName() {
$mock = $this->getMockForAbstractClass( 'CentralIdLookup' );
$mock->expects( $this->once() )->method( 'lookupUserNames' )
->with(
$this->equalTo( array( 'FooBar' => 0 ) ),
$this->equalTo( CentralIdLookup::AUDIENCE_RAW ),
$this->equalTo( CentralIdLookup::READ_LATEST )
)
->will( $this->returnValue( array( 'FooBar' => 23 ) ) );
$this->assertSame(
23,
$mock->centralIdFromName( 'FooBar', CentralIdLookup::AUDIENCE_RAW, CentralIdLookup::READ_LATEST )
);
}
public function testCentralIdFromLocalUser() {
$mock = $this->getMockForAbstractClass( 'CentralIdLookup' );
$mock->expects( $this->any() )->method( 'isAttached' )
->will( $this->returnValue( true ) );
$mock->expects( $this->once() )->method( 'lookupUserNames' )
->with(
$this->equalTo( array( 'FooBar' => 0 ) ),
$this->equalTo( CentralIdLookup::AUDIENCE_RAW ),
$this->equalTo( CentralIdLookup::READ_LATEST )
)
->will( $this->returnValue( array( 'FooBar' => 23 ) ) );
$this->assertSame(
23,
$mock->centralIdFromLocalUser(
User::newFromName( 'FooBar' ), CentralIdLookup::AUDIENCE_RAW, CentralIdLookup::READ_LATEST
)
);
$mock = $this->getMockForAbstractClass( 'CentralIdLookup' );
$mock->expects( $this->any() )->method( 'isAttached' )
->will( $this->returnValue( false ) );
$mock->expects( $this->never() )->method( 'lookupUserNames' );
$this->assertSame(
0,
$mock->centralIdFromLocalUser(
User::newFromName( 'FooBar' ), CentralIdLookup::AUDIENCE_RAW, CentralIdLookup::READ_LATEST
)
);
}
}

View file

@ -0,0 +1,149 @@
<?php
/**
* @covers LocalIdLookup
* @group Database
*/
class LocalIdLookupTest extends MediaWikiTestCase {
private $localUsers = array();
protected function setUp() {
global $wgGroupPermissions;
parent::setUp();
$this->stashMwGlobals( array( 'wgGroupPermissions' ) );
$wgGroupPermissions['local-id-lookup-test']['hideuser'] = true;
}
public function addDBData() {
for ( $i = 1; $i <= 4; $i++ ) {
$user = User::newFromName( "UTLocalIdLookup$i" );
if ( $user->getId() == 0 ) {
$user->addToDatabase();
}
$this->localUsers["UTLocalIdLookup$i"] = $user->getId();
}
User::newFromName( 'UTLocalIdLookup1' )->addGroup( 'local-id-lookup-test' );
$block = new Block( array(
'address' => 'UTLocalIdLookup3',
'by' => User::idFromName( 'UTSysop' ),
'reason' => __METHOD__,
'expiry' => '1 day',
'hideName' => false,
) );
$block->insert();
$block = new Block( array(
'address' => 'UTLocalIdLookup4',
'by' => User::idFromName( 'UTSysop' ),
'reason' => __METHOD__,
'expiry' => '1 day',
'hideName' => true,
) );
$block->insert();
}
public function testLookupCentralIds() {
$lookup = new LocalIdLookup();
$user1 = User::newFromName( 'UTLocalIdLookup1' );
$user2 = User::newFromName( 'UTLocalIdLookup2' );
$this->assertTrue( $user1->isAllowed( 'hideuser' ), 'sanity check' );
$this->assertFalse( $user2->isAllowed( 'hideuser' ), 'sanity check' );
$this->assertSame( array(), $lookup->lookupCentralIds( array() ) );
$expect = array_flip( $this->localUsers );
$expect[123] = 'X';
ksort( $expect );
$expect2 = $expect;
$expect2[$this->localUsers['UTLocalIdLookup4']] = '';
$arg = array_fill_keys( array_keys( $expect ), 'X' );
$this->assertSame( $expect2, $lookup->lookupCentralIds( $arg ) );
$this->assertSame( $expect, $lookup->lookupCentralIds( $arg, CentralIdLookup::AUDIENCE_RAW ) );
$this->assertSame( $expect, $lookup->lookupCentralIds( $arg, $user1 ) );
$this->assertSame( $expect2, $lookup->lookupCentralIds( $arg, $user2 ) );
}
public function testLookupUserNames() {
$lookup = new LocalIdLookup();
$user1 = User::newFromName( 'UTLocalIdLookup1' );
$user2 = User::newFromName( 'UTLocalIdLookup2' );
$this->assertTrue( $user1->isAllowed( 'hideuser' ), 'sanity check' );
$this->assertFalse( $user2->isAllowed( 'hideuser' ), 'sanity check' );
$this->assertSame( array(), $lookup->lookupUserNames( array() ) );
$expect = $this->localUsers;
$expect['UTDoesNotExist'] = 'X';
ksort( $expect );
$expect2 = $expect;
$expect2['UTLocalIdLookup4'] = 'X';
$arg = array_fill_keys( array_keys( $expect ), 'X' );
$this->assertSame( $expect2, $lookup->lookupUserNames( $arg ) );
$this->assertSame( $expect, $lookup->lookupUserNames( $arg, CentralIdLookup::AUDIENCE_RAW ) );
$this->assertSame( $expect, $lookup->lookupUserNames( $arg, $user1 ) );
$this->assertSame( $expect2, $lookup->lookupUserNames( $arg, $user2 ) );
}
public function testIsAttached() {
$lookup = new LocalIdLookup();
$user1 = User::newFromName( 'UTLocalIdLookup1' );
$user2 = User::newFromName( 'DoesNotExist' );
$this->assertTrue( $lookup->isAttached( $user1 ) );
$this->assertFalse( $lookup->isAttached( $user2 ) );
$wiki = wfWikiId();
$this->assertTrue( $lookup->isAttached( $user1, $wiki ) );
$this->assertFalse( $lookup->isAttached( $user2, $wiki ) );
$wiki = 'not-' . wfWikiId();
$this->assertFalse( $lookup->isAttached( $user1, $wiki ) );
$this->assertFalse( $lookup->isAttached( $user2, $wiki ) );
}
/**
* @dataProvider provideIsAttachedShared
* @param bool $sharedDB $wgSharedDB is set
* @param bool $sharedTable $wgSharedTables contains 'user'
* @param bool $localDBSet $wgLocalDatabases contains the shared DB
*/
public function testIsAttachedShared( $sharedDB, $sharedTable, $localDBSet ) {
global $wgDBName;
$this->setMwGlobals( array(
'wgSharedDB' => $sharedDB ? $wgDBName : null,
'wgSharedTables' => $sharedTable ? array( 'user' ) : array(),
'wgLocalDatabases' => $localDBSet ? array( 'shared' ) : array(),
) );
$lookup = new LocalIdLookup();
$this->assertSame(
$sharedDB && $sharedTable && $localDBSet,
$lookup->isAttached( User::newFromName( 'UTLocalIdLookup1' ), 'shared' )
);
}
public static function provideIsAttachedShared() {
$ret = array();
for ( $i = 0; $i < 7; $i++ ) {
$ret[] = array(
(bool)( $i & 1 ),
(bool)( $i & 2 ),
(bool)( $i & 4 ),
);
}
return $ret;
}
}

View file

@ -509,11 +509,11 @@ class UserTest extends MediaWikiTestCase {
$setcookieInvocation = end( $setcookieInvocations );
$actualExpiry = $setcookieInvocation->parameters[2];
// TODO: ± 300 seconds compensates for
// TODO: ± 600 seconds compensates for
// slow-running tests. However, the dependency on the time
// function should be removed. This requires some way
// to mock/isolate User->setExtendedLoginCookie's call to time()
$this->assertEquals( $expectedExpiry, $actualExpiry, '', 300 );
$this->assertEquals( $expectedExpiry, $actualExpiry, '', 600 );
}
}