2020-10-15 22:00:32 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace MediaWiki\Extension\AbuseFilter;
|
|
|
|
|
|
|
|
use LogicException;
|
|
|
|
use MediaWiki\Config\ServiceOptions;
|
2020-12-18 14:05:33 +00:00
|
|
|
use MediaWiki\Extension\AbuseFilter\Consequences\ConsequencesRegistry;
|
2020-10-15 22:00:32 +00:00
|
|
|
use MediaWiki\Extension\AbuseFilter\Filter\Filter;
|
|
|
|
use MediaWiki\Extension\AbuseFilter\Filter\Flags;
|
|
|
|
use MediaWiki\Extension\AbuseFilter\Filter\LastEditInfo;
|
|
|
|
use MediaWiki\Extension\AbuseFilter\Filter\MutableFilter;
|
|
|
|
use MediaWiki\Extension\AbuseFilter\Filter\Specs;
|
2024-06-12 18:01:35 +00:00
|
|
|
use MediaWiki\Json\FormatJson;
|
2020-10-15 22:00:32 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* This class allows encoding filters to (and decoding from) a string format that can be used
|
|
|
|
* to export them to another wiki.
|
2022-09-29 16:54:36 +00:00
|
|
|
*
|
2020-10-15 22:00:32 +00:00
|
|
|
* @internal
|
|
|
|
* @note Callers should NOT rely on the output format, as it may vary
|
|
|
|
*/
|
|
|
|
class FilterImporter {
|
|
|
|
public const SERVICE_NAME = 'AbuseFilterFilterImporter';
|
|
|
|
|
|
|
|
public const CONSTRUCTOR_OPTIONS = [
|
|
|
|
'AbuseFilterValidGroups',
|
|
|
|
'AbuseFilterIsCentral',
|
|
|
|
];
|
|
|
|
|
|
|
|
private const TEMPLATE_KEYS = [
|
|
|
|
'rules',
|
|
|
|
'name',
|
|
|
|
'comments',
|
|
|
|
'group',
|
|
|
|
'actions',
|
|
|
|
'enabled',
|
|
|
|
'deleted',
|
2024-05-16 10:40:53 +00:00
|
|
|
'privacylevel',
|
2020-10-15 22:00:32 +00:00
|
|
|
'global'
|
|
|
|
];
|
|
|
|
|
|
|
|
/** @var ServiceOptions */
|
|
|
|
private $options;
|
|
|
|
|
2020-11-29 23:10:14 +00:00
|
|
|
/** @var ConsequencesRegistry */
|
|
|
|
private $consequencesRegistry;
|
|
|
|
|
2020-10-15 22:00:32 +00:00
|
|
|
/**
|
|
|
|
* @param ServiceOptions $options
|
2020-11-29 23:10:14 +00:00
|
|
|
* @param ConsequencesRegistry $consequencesRegistry
|
2020-10-15 22:00:32 +00:00
|
|
|
*/
|
2020-11-29 23:10:14 +00:00
|
|
|
public function __construct( ServiceOptions $options, ConsequencesRegistry $consequencesRegistry ) {
|
2020-10-15 22:00:32 +00:00
|
|
|
$options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
|
|
|
|
$this->options = $options;
|
2020-11-29 23:10:14 +00:00
|
|
|
$this->consequencesRegistry = $consequencesRegistry;
|
2020-10-15 22:00:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Filter $filter
|
|
|
|
* @param array $actions
|
|
|
|
* @return string
|
|
|
|
*/
|
2021-07-21 18:51:12 +00:00
|
|
|
public function encodeData( Filter $filter, array $actions ): string {
|
2020-10-15 22:00:32 +00:00
|
|
|
$data = [
|
|
|
|
'rules' => $filter->getRules(),
|
|
|
|
'name' => $filter->getName(),
|
|
|
|
'comments' => $filter->getComments(),
|
|
|
|
'group' => $filter->getGroup(),
|
|
|
|
'actions' => $filter->getActions(),
|
|
|
|
'enabled' => $filter->isEnabled(),
|
|
|
|
'deleted' => $filter->isDeleted(),
|
2024-05-16 10:40:53 +00:00
|
|
|
'privacylevel' => $filter->getPrivacyLevel(),
|
2020-10-15 22:00:32 +00:00
|
|
|
'global' => $filter->isGlobal()
|
|
|
|
];
|
|
|
|
// @codeCoverageIgnoreStart
|
|
|
|
if ( array_keys( $data ) !== self::TEMPLATE_KEYS ) {
|
|
|
|
// Sanity
|
|
|
|
throw new LogicException( 'Bad keys' );
|
|
|
|
}
|
|
|
|
// @codeCoverageIgnoreEnd
|
|
|
|
return FormatJson::encode( [ 'data' => $data, 'actions' => $actions ] );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $rawData
|
|
|
|
* @return Filter
|
|
|
|
* @throws InvalidImportDataException
|
|
|
|
*/
|
2021-07-21 18:51:12 +00:00
|
|
|
public function decodeData( string $rawData ): Filter {
|
2020-10-15 22:00:32 +00:00
|
|
|
$validGroups = $this->options->get( 'AbuseFilterValidGroups' );
|
|
|
|
$globalFiltersEnabled = $this->options->get( 'AbuseFilterIsCentral' );
|
|
|
|
|
|
|
|
$data = FormatJson::decode( $rawData );
|
|
|
|
if ( !$this->isValidImportData( $data ) ) {
|
|
|
|
throw new InvalidImportDataException( $rawData );
|
|
|
|
}
|
|
|
|
[ 'data' => $filterData, 'actions' => $actions ] = wfObjectToArray( $data );
|
|
|
|
|
|
|
|
return new MutableFilter(
|
|
|
|
new Specs(
|
|
|
|
$filterData['rules'],
|
|
|
|
$filterData['comments'],
|
|
|
|
$filterData['name'],
|
|
|
|
array_keys( $actions ),
|
|
|
|
// Keep the group only if it exists on this wiki
|
|
|
|
in_array( $filterData['group'], $validGroups, true ) ? $filterData['group'] : 'default'
|
|
|
|
),
|
|
|
|
new Flags(
|
|
|
|
(bool)$filterData['enabled'],
|
|
|
|
(bool)$filterData['deleted'],
|
2024-05-16 10:40:53 +00:00
|
|
|
(int)$filterData['privacylevel'],
|
2020-10-15 22:00:32 +00:00
|
|
|
// And also make it global only if global filters are enabled here
|
|
|
|
$filterData['global'] && $globalFiltersEnabled
|
|
|
|
),
|
|
|
|
$actions,
|
|
|
|
new LastEditInfo(
|
|
|
|
0,
|
|
|
|
'',
|
|
|
|
''
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Note: this doesn't check if parameters are valid etc., but only if the shape of the object is right.
|
|
|
|
*
|
|
|
|
* @param mixed $data Already decoded
|
|
|
|
* @return bool
|
|
|
|
*/
|
2021-07-21 18:51:12 +00:00
|
|
|
private function isValidImportData( $data ): bool {
|
2020-10-15 22:00:32 +00:00
|
|
|
if ( !is_object( $data ) ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$arr = get_object_vars( $data );
|
|
|
|
|
|
|
|
$expectedKeys = [ 'data' => true, 'actions' => true ];
|
|
|
|
if ( count( $arr ) !== count( $expectedKeys ) || array_diff_key( $arr, $expectedKeys ) ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !is_object( $arr['data'] ) || !( is_object( $arr['actions'] ) || $arr['actions'] === [] ) ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( array_keys( get_object_vars( $arr['data'] ) ) !== self::TEMPLATE_KEYS ) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-11-29 23:10:14 +00:00
|
|
|
$allActions = $this->consequencesRegistry->getAllActionNames();
|
2020-10-15 22:00:32 +00:00
|
|
|
foreach ( $arr['actions'] as $action => $params ) {
|
2020-11-29 23:10:14 +00:00
|
|
|
if ( !in_array( $action, $allActions, true ) || !is_array( $params ) ) {
|
2020-10-15 22:00:32 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|