Page MenuHomeWickedGov Phorge

TemporaryAccountLogger.php
No OneTemporary

Size
10 KB
Referenced Files
None
Subscribers
None

TemporaryAccountLogger.php

<?php
namespace MediaWiki\CheckUser\Logging;
use MediaWiki\Logging\DatabaseLogEntry;
use MediaWiki\Logging\ManualLogEntry;
use MediaWiki\Title\TitleFactory;
use MediaWiki\User\ActorStore;
use MediaWiki\User\UserIdentity;
use Psr\Log\LoggerInterface;
use Wikimedia\Assert\Assert;
use Wikimedia\Assert\ParameterAssertionException;
use Wikimedia\IPUtils;
use Wikimedia\Rdbms\DBError;
use Wikimedia\Rdbms\IConnectionProvider;
/**
* Defines the API for the component responsible for logging the following interactions:
*
* - A user views IP addresses for a temporary account
* - A user views temporary accounts on a given IP address or range
* - A user enables temporary account IP viewing
* - A user disables temporary account IP viewing
*
* All the above interactions will be logged to the `logging` table with a log type
* `checkuser-temporary-account`.
*/
class TemporaryAccountLogger {
/**
* Represents a user (the performer) viewing IP addresses for a temporary account.
*
* @var string
*/
public const ACTION_VIEW_IPS = 'view-ips';
/** @var string Represents a user viewing the temporary accounts on a specific IP address */
public const ACTION_VIEW_TEMPORARY_ACCOUNTS_ON_IP = 'view-temporary-accounts-on-ip';
/** @var string Represents a user globally viewing the temporary accounts on a specific IP address */
public const ACTION_VIEW_TEMPORARY_ACCOUNTS_ON_IP_GLOBAL = 'view-temp-accounts-on-ip-global';
/**
* Represents a user enabling or disabling their own access to view IPs
*
* @var string
*/
public const ACTION_CHANGE_ACCESS = 'change-access';
public const ACTION_ACCESS_ENABLED = 'enable';
public const ACTION_ACCESS_DISABLED = 'disable';
public const ACTION_GLOBAL_ACCESS_ENABLED = 'enable-globally';
public const ACTION_GLOBAL_ACCESS_DISABLED = 'disable-globally';
/**
* Represents a user enabling or disabling IP auto-reveal mode
*
* @var string
*/
public const ACTION_CHANGE_AUTO_REVEAL = 'change-auto-reveal';
public const ACTION_AUTO_REVEAL_ENABLED = 'enable-auto-reveal';
public const ACTION_AUTO_REVEAL_DISABLED = 'disable-auto-reveal';
/**
* @var string
*/
public const LOG_TYPE = 'checkuser-temporary-account';
private ActorStore $actorStore;
private LoggerInterface $logger;
private IConnectionProvider $dbProvider;
private TitleFactory $titleFactory;
private int $delay;
/**
* @param ActorStore $actorStore
* @param LoggerInterface $logger
* @param IConnectionProvider $dbProvider
* @param TitleFactory $titleFactory
* @param int $delay The number of seconds after which a duplicate log entry can be
* created for a debounced log
* @throws ParameterAssertionException
*/
public function __construct(
ActorStore $actorStore,
LoggerInterface $logger,
IConnectionProvider $dbProvider,
TitleFactory $titleFactory,
int $delay
) {
Assert::parameter( $delay > 0, 'delay', 'delay must be positive' );
$this->actorStore = $actorStore;
$this->logger = $logger;
$this->dbProvider = $dbProvider;
$this->titleFactory = $titleFactory;
$this->delay = $delay;
}
/**
* Logs the user (the performer) viewing temporary accounts on a given IP address or range.
* This action can be performed locally or globally.
*
* @param UserIdentity $performer
* @param string $ip IP address or range
* @param int $timestamp
*/
public function logViewTemporaryAccountsOnIP(
UserIdentity $performer,
string $ip,
int $timestamp,
bool $global = false
): void {
$action = $global ?
self::ACTION_VIEW_TEMPORARY_ACCOUNTS_ON_IP_GLOBAL : self::ACTION_VIEW_TEMPORARY_ACCOUNTS_ON_IP;
$this->debouncedLog(
$performer, IPUtils::prettifyIP( $ip ), $action, $timestamp
);
}
/**
* Logs the user (the performer) viewing IP addresses for a temporary account.
*
* @param UserIdentity $performer
* @param string $tempUser
* @param int $timestamp
*/
public function logViewIPs( UserIdentity $performer, string $tempUser, int $timestamp ): void {
$this->debouncedLog( $performer, $tempUser, self::ACTION_VIEW_IPS, $timestamp );
}
/**
* Logs the user (the performer) viewing IP addresses for a temporary account, while
* auto-reveal mode is enabled. These actions are logged temporarily rather than permanently,
* to avoid flooding the on-wiki logs.
*
* @param string $username The name of the performer who viewed the IPs
* @param string $tempUser
*/
public function logViewIPsWithAutoReveal( string $username, string $tempUser ): void {
$this->logger->info(
'{username} viewed IP addresses for {target}',
[
'username' => $username,
'target' => $tempUser,
]
);
}
/**
* Log when the user enables their own access locally.
*
* @param UserIdentity $performer
*/
public function logAccessEnabled( UserIdentity $performer ): void {
$this->logAccessChanged( $performer, self::ACTION_ACCESS_ENABLED );
}
/**
* Log when the user disables their own access locally.
*
* @param UserIdentity $performer
*/
public function logAccessDisabled( UserIdentity $performer ): void {
$this->logAccessChanged( $performer, self::ACTION_ACCESS_DISABLED );
}
/**
* Log when the user enables their own access globally.
*
* @param UserIdentity $performer
*/
public function logGlobalAccessEnabled( UserIdentity $performer ): void {
$this->logAccessChanged( $performer, self::ACTION_GLOBAL_ACCESS_ENABLED );
}
/**
* Log when the user disables their own access globally.
*
* @param UserIdentity $performer
*/
public function logGlobalAccessDisabled( UserIdentity $performer ): void {
$this->logAccessChanged( $performer, self::ACTION_GLOBAL_ACCESS_DISABLED );
}
/**
* @param UserIdentity $performer
* @param string $action One of the following action constants from this class:
* - ACTION_ACCESS_ENABLED
* - ACTION_ACCESS_DISABLED
* - ACTION_GLOBAL_ACCESS_ENABLED
* - ACTION_GLOBAL_ACCESS_DISABLED
*/
private function logAccessChanged( UserIdentity $performer, string $action ) {
$params = [ '4::changeType' => $action ];
$this->log( $performer, $performer->getName(), self::ACTION_CHANGE_ACCESS, $params );
}
/**
* Log when the user enables IP auto-reveal.
*
* @param UserIdentity $performer
* @param int $expiry
*/
public function logAutoRevealAccessEnabled( UserIdentity $performer, int $expiry ): void {
$params = [
'4::changeType' => self::ACTION_AUTO_REVEAL_ENABLED,
'5::expiry' => $expiry,
];
$this->logAutoRevealChanged( $performer, $params );
}
/**
* Log when the user disables IP auto-reveal.
*
* @param UserIdentity $performer
*/
public function logAutoRevealAccessDisabled( UserIdentity $performer ): void {
$params = [ '4::changeType' => self::ACTION_AUTO_REVEAL_DISABLED ];
$this->logAutoRevealChanged( $performer, $params );
}
/**
* @param UserIdentity $performer
* @param array $params
*/
private function logAutoRevealChanged( UserIdentity $performer, array $params ) {
$this->log( $performer, $performer->getName(), self::ACTION_CHANGE_AUTO_REVEAL, $params );
}
/**
* @param UserIdentity $performer
* @param string $target
* @param string $action
* @param int $timestamp
* @param array|null $params
*/
private function debouncedLog(
UserIdentity $performer,
string $target,
string $action,
int $timestamp,
?array $params = []
): void {
$dbw = $this->dbProvider->getPrimaryDatabase();
$timestampMinusDelay = $timestamp - $this->delay;
$actorId = $this->actorStore->findActorId( $performer, $dbw );
if ( !$actorId ) {
$this->log( $performer, $target, $action, $params, $timestamp );
return;
}
$targetAsTitle = $this->titleFactory->makeTitle( NS_USER, $target );
$logRows = DatabaseLogEntry::newSelectQueryBuilder( $dbw )
->where( [
'log_type' => self::LOG_TYPE,
'log_action' => $action,
'log_actor' => $actorId,
'log_namespace' => $targetAsTitle->getNamespace(),
'log_title' => $targetAsTitle->getDBkey(),
$dbw->expr( 'log_timestamp', '>', $dbw->timestamp( $timestampMinusDelay ) ),
] )
->caller( __METHOD__ )
->fetchResultSet();
$shouldLog = true;
foreach ( $logRows as $logRow ) {
$logEntry = DatabaseLogEntry::newFromRow( $logRow );
$logParams = $logEntry->getParameters();
if ( $logParams == $params ) {
$shouldLog = false;
break;
}
}
if ( $shouldLog ) {
$this->log( $performer, $target, $action, $params, $timestamp );
}
}
/**
* @param UserIdentity $performer
* @param string $target
* @param string $action
* @param array $params
* @param int|null $timestamp
*/
private function log(
UserIdentity $performer,
string $target,
string $action,
array $params,
?int $timestamp = null
): void {
$logEntry = $this->createManualLogEntry( $action );
$logEntry->setPerformer( $performer );
$logEntry->setTarget( $this->titleFactory->makeTitle( NS_USER, $target ) );
$logEntry->setParameters( $params );
if ( $timestamp ) {
$logEntry->setTimestamp( wfTimestamp( TS_MW, $timestamp ) );
}
try {
$logEntry->insert( $this->dbProvider->getPrimaryDatabase() );
} catch ( DBError ) {
$this->logger->critical(
'CheckUser temporary account log entry was not recorded. ' .
'This means checks can occur without being auditable. ' .
'Immediate fix required.'
);
}
}
/**
* Allow other extensions to write relevant logs to the temporary accounts log
*
* Sources:
* - AbuseFilter
* + af-view-protected-var-value
*/
public function logFromExternal(
UserIdentity $performer,
string $target,
string $action,
array $params = [],
bool $debounce = false,
?int $timestamp = null
) {
if ( !$timestamp ) {
$timestamp = (int)wfTimestamp();
}
if ( $debounce ) {
$this->debouncedLog(
$performer, $target, $action, $timestamp, $params
);
} else {
$this->log(
$performer, $target, $action, $params, $timestamp
);
}
}
/**
* There is no `LogEntryFactory` (or `Logger::insert()` method) in MediaWiki Core to inject
* via the constructor so use this method to isolate the creation of `LogEntry` objects during
* testing.
*
* @private
*
* @param string $subtype
* @return ManualLogEntry
*/
protected function createManualLogEntry( string $subtype ): ManualLogEntry {
return new ManualLogEntry( self::LOG_TYPE, $subtype );
}
}

File Metadata

Mime Type
text/x-php
Expires
Aug 16 2026, 07:28 (12 w, 2 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
b4/e9/1dfa9aca706a81cba325f52f8e97
Default Alt Text
TemporaryAccountLogger.php (10 KB)

Event Timeline