1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
|
<?php
namespace MediaWiki\Extension\AbuseFilter;
use ManualLogEntry;
use MediaWiki\MediaWikiServices;
use MediaWiki\Title\Title;
use MediaWiki\User\ActorStore;
use MediaWiki\User\UserIdentity;
use Psr\Log\LoggerInterface;
use Wikimedia\Assert\Assert;
use Wikimedia\Rdbms\DBError;
use Wikimedia\Rdbms\IConnectionProvider;
/**
* Defines the API for the component responsible for logging the following interactions:
*
* - A user enables protected variable viewing
* - A user disables protected variable viewing
*/
class ProtectedVarsAccessLogger {
/**
* Represents a user enabling their own access to view protected variables
*
* @var string
*/
public const ACTION_CHANGE_ACCESS_ENABLED = 'change-access-enable';
/**
* Represents a user disabling their own access to view protected variables
*
* @var string
*/
public const ACTION_CHANGE_ACCESS_DISABLED = 'change-access-disable';
/**
* Represents a user viewing the value of a protected variable
*
* @var string
*/
public const ACTION_VIEW_PROTECTED_VARIABLE_VALUE = 'view-protected-var-value';
/**
* @var string
*/
public const LOG_TYPE = 'abusefilter-protected-vars';
private LoggerInterface $logger;
private IConnectionProvider $lbFactory;
private ActorStore $actorStore;
private int $delay;
/**
* @param LoggerInterface $logger
* @param IConnectionProvider $lbFactory
* @param ActorStore $actorStore
* @param int $delay The number of seconds after which a duplicate log entry can be
* created for a debounced log
*/
public function __construct(
LoggerInterface $logger,
IConnectionProvider $lbFactory,
ActorStore $actorStore,
int $delay
) {
Assert::parameter( $delay > 0, 'delay', 'delay must be positive' );
$this->logger = $logger;
$this->lbFactory = $lbFactory;
$this->actorStore = $actorStore;
$this->delay = $delay;
}
/**
* Log when the user enables their own access
*
* @param UserIdentity $performer
*/
public function logAccessEnabled( UserIdentity $performer ): void {
$this->log( $performer, $performer->getName(), self::ACTION_CHANGE_ACCESS_ENABLED, false );
}
/**
* Log when the user disables their own access
*
* @param UserIdentity $performer
*/
public function logAccessDisabled( UserIdentity $performer ): void {
$this->log( $performer, $performer->getName(), self::ACTION_CHANGE_ACCESS_DISABLED, false );
}
/**
* Log when the user views the values of protected variables
*
* @param UserIdentity $performer
* @param string $target
* @param int|null $timestamp
*/
public function logViewProtectedVariableValue(
UserIdentity $performer,
string $target,
?int $timestamp = null
): void {
if ( !$timestamp ) {
$timestamp = (int)wfTimestamp();
}
$this->log(
$performer,
$target,
self::ACTION_VIEW_PROTECTED_VARIABLE_VALUE,
true,
$timestamp
);
}
/**
* @param UserIdentity $performer
* @param string $target
* @param string $action
* @param bool $shouldDebounce
* @param int|null $timestamp
* @param array|null $params
*/
private function log(
UserIdentity $performer,
string $target,
string $action,
bool $shouldDebounce,
?int $timestamp = null,
?array $params = []
): void {
if ( !$timestamp ) {
$timestamp = (int)wfTimestamp();
}
// Log to CheckUser's temporary accounts log if CU is installed
if ( MediaWikiServices::getInstance()->getExtensionRegistry()->isLoaded( 'CheckUser' ) ) {
// Add the extension name to the action so that CheckUser has a clearer
// reference to the source in the message key
$action = 'af-' . $action;
$logger = MediaWikiServices::getInstance()
->getService( 'CheckUserTemporaryAccountLoggerFactory' )
->getLogger();
$logger->logFromExternal(
$performer,
$target,
$action,
$params,
$shouldDebounce,
$timestamp
);
} else {
$dbw = $this->lbFactory->getPrimaryDatabase();
$shouldLog = false;
// If the log is debounced, check against the logging table before logging
if ( $shouldDebounce ) {
$timestampMinusDelay = $timestamp - $this->delay;
$actorId = $this->actorStore->findActorId( $performer, $dbw );
if ( !$actorId ) {
$shouldLog = true;
} else {
$logline = $dbw->newSelectQueryBuilder()
->select( '*' )
->from( 'logging' )
->where( [
'log_type' => self::LOG_TYPE,
'log_action' => $action,
'log_actor' => $actorId,
'log_namespace' => NS_USER,
'log_title' => $target,
$dbw->expr( 'log_timestamp', '>', $dbw->timestamp( $timestampMinusDelay ) ),
] )
->caller( __METHOD__ )
->fetchRow();
if ( !$logline ) {
$shouldLog = true;
}
}
} else {
// If the log isn't debounced then it should always be logged
$shouldLog = true;
}
// Actually write to logging table
if ( $shouldLog ) {
$logEntry = $this->createManualLogEntry( $action );
$logEntry->setPerformer( $performer );
$logEntry->setTarget( Title::makeTitle( NS_USER, $target ) );
$logEntry->setParameters( $params );
$logEntry->setTimestamp( wfTimestamp( TS_MW, $timestamp ) );
try {
$logEntry->insert( $dbw );
} catch ( DBError $e ) {
$this->logger->critical(
'AbuseFilter proctected variable log entry was not recorded. ' .
'This means access to IPs can occur without being auditable. ' .
'Immediate fix required.'
);
throw $e;
}
}
}
}
/**
* @internal
*
* @param string $subtype
* @return ManualLogEntry
*/
protected function createManualLogEntry( string $subtype ): ManualLogEntry {
return new ManualLogEntry( self::LOG_TYPE, $subtype );
}
}
|