Merge "mediawiki.diff: Replace pixel font-size value for relative one"
[mediawiki.git] / maintenance / expireTemporaryAccounts.php
blob939105e46b9a2b9074abcd60142d298df7777597
1 <?php
3 use MediaWiki\Auth\AuthManager;
4 use MediaWiki\Maintenance\Maintenance;
5 use MediaWiki\Session\SessionManager;
6 use MediaWiki\User\TempUser\TempUserConfig;
7 use MediaWiki\User\UserFactory;
8 use MediaWiki\User\UserIdentity;
9 use MediaWiki\User\UserIdentityLookup;
10 use MediaWiki\User\UserIdentityUtils;
11 use MediaWiki\User\UserSelectQueryBuilder;
12 use Wikimedia\Rdbms\SelectQueryBuilder;
14 // @codeCoverageIgnoreStart
15 require_once __DIR__ . '/Maintenance.php';
16 // @codeCoverageIgnoreEnd
18 /**
19 * Expire temporary accounts that are registered for longer than `expiryAfterDays` days
20 * (defined in $wgAutoCreateTempUser) by forcefully logging them out.
22 * Extensions can extend this class to provide their own logic of determining a list
23 * of temporary accounts to expire.
25 * @stable to extend
26 * @since 1.42
28 class ExpireTemporaryAccounts extends Maintenance {
30 protected UserIdentityLookup $userIdentityLookup;
31 protected UserFactory $userFactory;
32 protected AuthManager $authManager;
33 protected TempUserConfig $tempUserConfig;
34 protected UserIdentityUtils $userIdentityUtils;
36 public function __construct() {
37 parent::__construct();
39 $this->addDescription( 'Expire temporary accounts that exist for more than N days' );
40 $this->addOption( 'frequency', 'How frequently the script runs [days]', true, true );
41 $this->addOption(
42 'expiry',
43 'Expire accounts older than this number of days. Use 0 to expire all temporary accounts',
44 false,
45 true
47 $this->addOption( 'verbose', 'Verbose logging output' );
50 /**
51 * Construct services the script needs to use
53 * @stable to override
55 protected function initServices(): void {
56 $services = $this->getServiceContainer();
58 $this->userIdentityLookup = $services->getUserIdentityLookup();
59 $this->userFactory = $services->getUserFactory();
60 $this->authManager = $services->getAuthManager();
61 $this->tempUserConfig = $services->getTempUserConfig();
62 $this->userIdentityUtils = $services->getUserIdentityUtils();
65 /**
66 * If --verbose is passed, log to output
68 * @param string $log
69 * @return void
71 protected function verboseLog( string $log ) {
72 if ( $this->hasOption( 'verbose' ) ) {
73 $this->output( $log );
77 /**
78 * Return a SelectQueryBuilder that returns temp accounts to invalidate
80 * This method should return temporary accounts that registered before $registeredBeforeUnix.
81 * To avoid returning an ever-growing set of accounts, the method should skip users that were
82 * supposedly invalidated by a previous script run (script runs each $frequencyDays days).
84 * If you override this method, you probably also want to override
85 * queryBuilderToUserIdentities().
87 * @stable to override
88 * @param int $registeredBeforeUnix Cutoff Unix timestamp
89 * @param int $frequencyDays Script runs each $frequencyDays days
90 * @return SelectQueryBuilder
92 protected function getTempAccountsToExpireQueryBuilder(
93 int $registeredBeforeUnix,
94 int $frequencyDays
95 ): SelectQueryBuilder {
96 return $this->userIdentityLookup->newSelectQueryBuilder()
97 ->temp()
98 ->whereRegisteredTimestamp( wfTimestamp(
99 TS_MW,
100 $registeredBeforeUnix
101 ), true )
102 ->whereRegisteredTimestamp( wfTimestamp(
103 TS_MW,
104 $registeredBeforeUnix - ( 86_400 * $frequencyDays )
105 ), false );
109 * Convert a SelectQueryBuilder into a list of user identities
111 * Default implementation expects $queryBuilder is an instance of UserSelectQueryBuilder. If
112 * you override getTempAccountsToExpireQueryBuilder() to work with a different query builder,
113 * this method should be overriden to properly convert the query builder into user identities.
115 * @throws LogicException if $queryBuilder is not UserSelectQueryBuilder
116 * @stable to override
117 * @param SelectQueryBuilder $queryBuilder
118 * @return Iterator<UserIdentity>
120 protected function queryBuilderToUserIdentities( SelectQueryBuilder $queryBuilder ): Iterator {
121 if ( $queryBuilder instanceof UserSelectQueryBuilder ) {
122 return $queryBuilder->fetchUserIdentities();
125 throw new LogicException(
126 '$queryBuilder is not UserSelectQueryBuilder. Did you forget to override ' .
127 __METHOD__ . '?'
132 * Expire a temporary account
134 * Default implementation calls AuthManager::revokeAccessForUser and
135 * SessionManager::invalidateSessionsForUser.
137 * @stable to override
138 * @param UserIdentity $tempAccountUserIdentity
140 protected function expireTemporaryAccount( UserIdentity $tempAccountUserIdentity ): void {
141 $this->authManager->revokeAccessForUser( $tempAccountUserIdentity->getName() );
142 SessionManager::singleton()->invalidateSessionsForUser(
143 $this->userFactory->newFromUserIdentity( $tempAccountUserIdentity )
148 * @inheritDoc
150 public function execute() {
151 $this->initServices();
153 if ( !$this->tempUserConfig->isKnown() ) {
154 $this->output( 'Temporary accounts are disabled' . PHP_EOL );
155 return;
158 $frequencyDays = (int)$this->getOption( 'frequency' );
159 if ( $this->getOption( 'expiry' ) !== null ) {
160 $expiryAfterDays = (int)$this->getOption( 'expiry' );
161 } else {
162 $expiryAfterDays = $this->tempUserConfig->getExpireAfterDays();
164 if ( $expiryAfterDays === null ) {
165 $this->output( 'Temporary account expiry is not enabled' . PHP_EOL );
166 return;
168 $registeredBeforeUnix = (int)wfTimestamp( TS_UNIX ) - ( 86_400 * $expiryAfterDays );
170 $tempAccounts = $this->queryBuilderToUserIdentities( $this->getTempAccountsToExpireQueryBuilder(
171 $registeredBeforeUnix,
172 $frequencyDays
173 )->caller( __METHOD__ ) );
175 $revokedUsers = 0;
176 foreach ( $tempAccounts as $tempAccountUserIdentity ) {
177 if ( !$this->userIdentityUtils->isTemp( $tempAccountUserIdentity ) ) {
178 // Not a temporary account, skip it.
179 continue;
182 $this->expireTemporaryAccount( $tempAccountUserIdentity );
184 $this->verboseLog(
185 'Revoking access for ' . $tempAccountUserIdentity->getName() . PHP_EOL
187 $revokedUsers++;
190 $this->output( "Revoked access for $revokedUsers temporary users." . PHP_EOL );
194 // @codeCoverageIgnoreStart
195 $maintClass = ExpireTemporaryAccounts::class;
196 require_once RUN_MAINTENANCE_IF_MAIN;
197 // @codeCoverageIgnoreEnd