2021-02-10 13:13:03 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace MediaWiki\Extension\AbuseFilter;
|
|
|
|
|
2024-10-19 19:55:40 +00:00
|
|
|
use Wikimedia\ObjectCache\BagOStuff;
|
2021-02-10 13:13:03 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper class for EmergencyWatcher. Wrapper around cache which tracks hits of recently
|
|
|
|
* modified filters.
|
|
|
|
*/
|
|
|
|
class EmergencyCache {
|
|
|
|
|
|
|
|
public const SERVICE_NAME = 'AbuseFilterEmergencyCache';
|
|
|
|
|
|
|
|
/** @var BagOStuff */
|
|
|
|
private $stash;
|
|
|
|
|
|
|
|
/** @var int[] */
|
|
|
|
private $ttlPerGroup;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param BagOStuff $stash
|
|
|
|
* @param int[] $ttlPerGroup
|
|
|
|
*/
|
|
|
|
public function __construct( BagOStuff $stash, array $ttlPerGroup ) {
|
|
|
|
$this->stash = $stash;
|
|
|
|
$this->ttlPerGroup = $ttlPerGroup;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get recently modified filters in the group. Thanks to this, performance can be improved,
|
|
|
|
* because only a small subset of filters will need an update.
|
|
|
|
*
|
|
|
|
* @param string $group
|
|
|
|
* @return int[]
|
|
|
|
*/
|
2021-07-21 18:51:12 +00:00
|
|
|
public function getFiltersToCheckInGroup( string $group ): array {
|
2021-02-10 13:13:03 +00:00
|
|
|
$filterToExpiry = $this->stash->get( $this->createGroupKey( $group ) );
|
|
|
|
if ( $filterToExpiry === false ) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
$time = (int)round( $this->stash->getCurrentTime() );
|
|
|
|
return array_keys( array_filter(
|
|
|
|
$filterToExpiry,
|
|
|
|
static function ( $exp ) use ( $time ) {
|
|
|
|
return $exp > $time;
|
|
|
|
}
|
|
|
|
) );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a new entry in cache for a filter and update the entry for the group.
|
|
|
|
* This method is usually called after the filter has been updated.
|
|
|
|
*
|
|
|
|
* @param int $filter
|
|
|
|
* @param string $group
|
|
|
|
* @return bool
|
|
|
|
*/
|
2021-07-21 18:51:12 +00:00
|
|
|
public function setNewForFilter( int $filter, string $group ): bool {
|
2021-02-10 13:13:03 +00:00
|
|
|
$ttl = $this->ttlPerGroup[$group] ?? $this->ttlPerGroup['default'];
|
|
|
|
$expiry = (int)round( $this->stash->getCurrentTime() + $ttl );
|
|
|
|
$this->stash->merge(
|
|
|
|
$this->createGroupKey( $group ),
|
2021-04-30 18:54:17 +00:00
|
|
|
static function ( $cache, $key, $value ) use ( $filter, $expiry ) {
|
2021-02-10 13:13:03 +00:00
|
|
|
if ( $value === false ) {
|
|
|
|
$value = [];
|
|
|
|
}
|
|
|
|
// note that some filters may have already had their keys expired
|
|
|
|
// we are currently filtering them out in getFiltersToCheckInGroup
|
|
|
|
// but if necessary, it can be done here
|
|
|
|
$value[$filter] = $expiry;
|
|
|
|
return $value;
|
|
|
|
},
|
|
|
|
$expiry
|
|
|
|
);
|
|
|
|
return $this->stash->set(
|
|
|
|
$this->createFilterKey( $filter ),
|
|
|
|
[ 'total' => 0, 'matches' => 0, 'expiry' => $expiry ],
|
|
|
|
$expiry
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Increase the filter's 'total' value by one and possibly also the 'matched' value.
|
|
|
|
*
|
|
|
|
* @param int $filter
|
|
|
|
* @param bool $matched Whether the filter matched the action
|
|
|
|
* @return bool
|
|
|
|
*/
|
2021-07-21 18:51:12 +00:00
|
|
|
public function incrementForFilter( int $filter, bool $matched ): bool {
|
2021-02-10 13:13:03 +00:00
|
|
|
return $this->stash->merge(
|
|
|
|
$this->createFilterKey( $filter ),
|
|
|
|
static function ( $cache, $key, $value, &$expiry ) use ( $matched ) {
|
|
|
|
if ( $value === false ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
$value['total']++;
|
|
|
|
if ( $matched ) {
|
|
|
|
$value['matches']++;
|
|
|
|
}
|
|
|
|
// enforce the prior TTL
|
|
|
|
$expiry = $value['expiry'];
|
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the cache entry for the filter. Returns false when the key has already expired.
|
|
|
|
* Otherwise it returns the entry formatted as [ 'total' => number of actions,
|
|
|
|
* 'matches' => number of hits ] (since the last filter modification).
|
|
|
|
*
|
|
|
|
* @param int $filter
|
|
|
|
* @return array|false
|
|
|
|
*/
|
|
|
|
public function getForFilter( int $filter ) {
|
|
|
|
$value = $this->stash->get( $this->createFilterKey( $filter ) );
|
|
|
|
if ( $value !== false ) {
|
|
|
|
unset( $value['expiry'] );
|
|
|
|
}
|
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $group
|
|
|
|
* @return string
|
|
|
|
*/
|
2021-07-21 18:51:12 +00:00
|
|
|
private function createGroupKey( string $group ): string {
|
2021-02-10 13:13:03 +00:00
|
|
|
return $this->stash->makeKey( 'abusefilter', 'emergency', 'group', $group );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param int $filter
|
|
|
|
* @return string
|
|
|
|
*/
|
2021-07-21 18:51:12 +00:00
|
|
|
private function createFilterKey( int $filter ): string {
|
2021-02-10 13:13:03 +00:00
|
|
|
return $this->stash->makeKey( 'abusefilter', 'emergency', 'filter', $filter );
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|