TempUserConfig: Add getMatchPattern

Why:
There are extensions that need to check a handful of
users for temp-ness at once (such as GrowthExperiments).
This is not really possible as of now (it would be necessary
to call UserIdentityUtils::isTemp several times, which can get
slow for large bulks of users).

What:
Add TempUserConfig::getMatchPattern() that can be used
to generate a LIKE database condition.

While at it, this patch also adds named() and temp() to
UserSelectQueryBuilder.

Bug: T341389
Change-Id: I90b5c59462c5c98bf5dcf9fa15d20553ef6599a5
This commit is contained in:
Martin Urbanec 2023-08-02 15:27:41 +02:00
parent 7f22129e60
commit 0a2b654e55
9 changed files with 85 additions and 2 deletions

View file

@ -255,6 +255,7 @@ return [
new ServiceOptions( ActorStoreFactory::CONSTRUCTOR_OPTIONS, $services->getMainConfig() ),
$services->getDBLoadBalancerFactory(),
$services->getUserNameUtils(),
$services->getTempUserConfig(),
LoggerFactory::getInstance( 'ActorStore' )
);
},

View file

@ -25,6 +25,7 @@ use DBAccessObjectUtils;
use ExternalUserNames;
use InvalidArgumentException;
use MediaWiki\DAO\WikiAwareEntity;
use MediaWiki\User\TempUser\TempUserConfig;
use Psr\Log\LoggerInterface;
use stdClass;
use User;
@ -52,6 +53,7 @@ class ActorStore implements UserIdentityLookup, ActorNormalization {
/** @var UserNameUtils */
private $userNameUtils;
private TempUserConfig $tempUserConfig;
/** @var LoggerInterface */
private $logger;
@ -65,12 +67,14 @@ class ActorStore implements UserIdentityLookup, ActorNormalization {
/**
* @param ILoadBalancer $loadBalancer
* @param UserNameUtils $userNameUtils
* @param TempUserConfig $tempUserConfig
* @param LoggerInterface $logger
* @param string|false $wikiId
*/
public function __construct(
ILoadBalancer $loadBalancer,
UserNameUtils $userNameUtils,
TempUserConfig $tempUserConfig,
LoggerInterface $logger,
$wikiId = WikiAwareEntity::LOCAL
) {
@ -78,6 +82,7 @@ class ActorStore implements UserIdentityLookup, ActorNormalization {
$this->loadBalancer = $loadBalancer;
$this->userNameUtils = $userNameUtils;
$this->tempUserConfig = $tempUserConfig;
$this->logger = $logger;
$this->wikiId = $wikiId;
@ -718,7 +723,7 @@ class ActorStore implements UserIdentityLookup, ActorNormalization {
[ $db, $options ] = $this->getDBConnectionRefForQueryFlags( $dbOrQueryFlags );
}
return ( new UserSelectQueryBuilder( $db, $this ) )->options( $options );
return ( new UserSelectQueryBuilder( $db, $this, $this->tempUserConfig ) )->options( $options );
}
/**

View file

@ -23,6 +23,7 @@ namespace MediaWiki\User;
use MediaWiki\Config\ServiceOptions;
use MediaWiki\DAO\WikiAwareEntity;
use MediaWiki\MainConfigNames;
use MediaWiki\User\TempUser\TempUserConfig;
use Psr\Log\LoggerInterface;
use Wikimedia\Rdbms\ILBFactory;
use Wikimedia\Rdbms\ILoadBalancer;
@ -46,6 +47,7 @@ class ActorStoreFactory {
/** @var UserNameUtils */
private $userNameUtils;
private TempUserConfig $tempUserConfig;
/** @var LoggerInterface */
private $logger;
@ -63,12 +65,14 @@ class ActorStoreFactory {
* @param ServiceOptions $options
* @param ILBFactory $loadBalancerFactory
* @param UserNameUtils $userNameUtils
* @param TempUserConfig $tempUserConfig
* @param LoggerInterface $logger
*/
public function __construct(
ServiceOptions $options,
ILBFactory $loadBalancerFactory,
UserNameUtils $userNameUtils,
TempUserConfig $tempUserConfig,
LoggerInterface $logger
) {
$options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
@ -76,6 +80,7 @@ class ActorStoreFactory {
$this->sharedDB = $options->get( MainConfigNames::SharedDB );
$this->sharedTables = $options->get( MainConfigNames::SharedTables );
$this->userNameUtils = $userNameUtils;
$this->tempUserConfig = $tempUserConfig;
$this->logger = $logger;
}
@ -104,6 +109,7 @@ class ActorStoreFactory {
$this->storeCache[$storeCacheKey] = new ActorStore(
$this->getLoadBalancerForTable( 'actor', $wikiId ),
$this->userNameUtils,
$this->tempUserConfig,
$this->logger,
$wikiId
);

View file

@ -96,6 +96,14 @@ class RealTempUserConfig implements TempUserConfig {
}
}
public function getMatchPattern(): Pattern {
if ( $this->enabled ) {
return $this->matchPattern;
} else {
throw new BadMethodCallException( __METHOD__ . ' is disabled' );
}
}
/**
* @internal For TempUserCreator only
* @return Pattern

View file

@ -65,4 +65,13 @@ interface TempUserConfig {
* @return string
*/
public function getPlaceholderName(): string;
/**
* Get a Pattern indicating how temporary account can be detected
*
* Used to avoid selecting a temp account via select queries.
*
* @return Pattern
*/
public function getMatchPattern(): Pattern;
}

View file

@ -167,6 +167,10 @@ class TempUserCreator implements TempUserConfig {
return $this->config->getPlaceholderName();
}
public function getMatchPattern(): Pattern {
return $this->config->getMatchPattern();
}
/**
* Acquire a new username and return it. Permanently reserve the ID in
* the database.

View file

@ -21,6 +21,7 @@
namespace MediaWiki\User;
use Iterator;
use MediaWiki\User\TempUser\TempUserConfig;
use Wikimedia\Assert\Assert;
use Wikimedia\Assert\PreconditionException;
use Wikimedia\Rdbms\IReadableDatabase;
@ -30,15 +31,23 @@ class UserSelectQueryBuilder extends SelectQueryBuilder {
/** @var ActorStore */
private $actorStore;
private TempUserConfig $tempUserConfig;
/**
* @internal
* @param IReadableDatabase $db
* @param ActorStore $actorStore
* @param TempUserConfig $tempUserConfig
*/
public function __construct( IReadableDatabase $db, ActorStore $actorStore ) {
public function __construct(
IReadableDatabase $db,
ActorStore $actorStore,
TempUserConfig $tempUserConfig
) {
parent::__construct( $db );
$this->actorStore = $actorStore;
$this->tempUserConfig = $tempUserConfig;
$this->table( 'actor' );
}
@ -161,6 +170,42 @@ class UserSelectQueryBuilder extends SelectQueryBuilder {
return $this;
}
/**
* Only return named users.
*
* @return UserSelectQueryBuilder
*/
public function named(): self {
if ( !$this->tempUserConfig->isEnabled() ) {
// nothing to do: getMatchPattern throws if temp accounts aren't enabled
return $this;
}
$this->conds( [
'actor_name NOT ' . $this->tempUserConfig->getMatchPattern()
->buildLike( $this->db )
] );
return $this;
}
/**
* Only return temp users
*
* @return UserSelectQueryBuilder
*/
public function temp(): self {
if ( !$this->tempUserConfig->isEnabled() ) {
// nothing to do: getMatchPattern throws if temp accounts aren't enabled
return $this;
}
$this->conds( [
'actor_name ' . $this->tempUserConfig->getMatchPattern()
->buildLike( $this->db )
] );
return $this;
}
/**
* Filter based on user hidden status
*

View file

@ -64,6 +64,7 @@ abstract class ActorStoreTestBase extends MediaWikiIntegrationTestCase {
$store = new ActorStore(
$dbLoadBalancer,
$this->getServiceContainer()->getUserNameUtils(),
$this->getServiceContainer()->getTempUserConfig(),
new NullLogger(),
$wikiId
);

View file

@ -7,6 +7,7 @@ use MediaWiki\MainConfigNames;
use MediaWiki\User\ActorNormalization;
use MediaWiki\User\ActorStore;
use MediaWiki\User\ActorStoreFactory;
use MediaWiki\User\TempUser\TempUserConfig;
use MediaWiki\User\UserIdentityLookup;
use MediaWiki\User\UserNameUtils;
use MediaWikiUnitTestCase;
@ -65,6 +66,7 @@ class ActorStoreFactoryTest extends MediaWikiUnitTestCase {
new ServiceOptions( ActorStoreFactory::CONSTRUCTOR_OPTIONS, $config ),
$this->getMockLoadBalancerFactory( $expectedDomain ),
$this->createNoOpMock( UserNameUtils::class ),
$this->createNoOpMock( TempUserConfig::class ),
new NullLogger()
);
$notFromCache = $factory->getActorStore( $domain );
@ -80,6 +82,7 @@ class ActorStoreFactoryTest extends MediaWikiUnitTestCase {
new ServiceOptions( ActorStoreFactory::CONSTRUCTOR_OPTIONS, $config ),
$this->getMockLoadBalancerFactory( $expectedDomain ),
$this->createNoOpMock( UserNameUtils::class ),
$this->createNoOpMock( TempUserConfig::class ),
new NullLogger()
);
$notFromCache = $factory->getActorNormalization( $domain );
@ -95,6 +98,7 @@ class ActorStoreFactoryTest extends MediaWikiUnitTestCase {
new ServiceOptions( ActorStoreFactory::CONSTRUCTOR_OPTIONS, $config ),
$this->getMockLoadBalancerFactory( $expectedDomain ),
$this->createNoOpMock( UserNameUtils::class ),
$this->createNoOpMock( TempUserConfig::class ),
new NullLogger()
);
$notFromCache = $factory->getUserIdentityLookup( $domain );