mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/AbuseFilter.git
synced 2024-11-27 15:30:42 +00:00
maintenance/SearchFilters: Allow searching by privacy level
Why: * Filters should only be protected if they contain protected variables, or have done in the past. * Before T377765, it was possible to protect any filter, and at least one filter was mistakenly protected. * To check whether any other filters have been mistakenly protected, it is helpful to run a query on all databases for protected filters. What: * Add an option to maintenance/SearchFilters to allow searching by privacy level. Bug: T380290 Change-Id: I40837de7c63fb8001734df80524a0bf79ff50135
This commit is contained in:
parent
0ea7944ea3
commit
47fb507e28
|
@ -19,10 +19,17 @@ class SearchFilters extends Maintenance {
|
|||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->addDescription(
|
||||
'Find all filters matching a regular expression pattern and/or that have given consequence'
|
||||
'Find all filters matching a regular expression pattern and/or that have a given ' .
|
||||
'consequence and/or privacy level'
|
||||
);
|
||||
$this->addOption( 'pattern', 'Regular expression pattern', false, true );
|
||||
$this->addOption( 'consequence', 'The consequence that the filter should have', false, true );
|
||||
$this->addOption(
|
||||
'privacy',
|
||||
'The privacy level that the filter should include (a constant from Flags)',
|
||||
false,
|
||||
true
|
||||
);
|
||||
|
||||
$this->requireExtension( 'Abuse Filter' );
|
||||
}
|
||||
|
@ -37,8 +44,12 @@ class SearchFilters extends Maintenance {
|
|||
$this->fatalError( 'This maintenance script only works with MySQL databases' );
|
||||
}
|
||||
|
||||
if ( !$this->getOption( 'pattern' ) && !$this->getOption( 'consequence' ) ) {
|
||||
$this->fatalError( 'One of --consequence or --pattern should be specified.' );
|
||||
if (
|
||||
!$this->getOption( 'pattern' ) &&
|
||||
!$this->getOption( 'consequence' ) &&
|
||||
$this->getOption( 'privacy' ) === null
|
||||
) {
|
||||
$this->fatalError( 'One of --consequence, --pattern or --privacy should be specified.' );
|
||||
}
|
||||
|
||||
$this->output( "wiki\tfilter\n" );
|
||||
|
@ -59,6 +70,7 @@ class SearchFilters extends Maintenance {
|
|||
$dbr = $this->getDB( DB_REPLICA, [], $dbname );
|
||||
$pattern = $dbr->addQuotes( $this->getOption( 'pattern' ) );
|
||||
$consequence = $this->getOption( 'consequence' );
|
||||
$privacy = $this->getOption( 'privacy' );
|
||||
|
||||
if ( $dbr->tableExists( 'abuse_filter', __METHOD__ ) ) {
|
||||
$queryBuilder = $dbr->newSelectQueryBuilder()
|
||||
|
@ -74,6 +86,21 @@ class SearchFilters extends Maintenance {
|
|||
new LikeValue( $dbr->anyString(), $consequence, $dbr->anyString() )
|
||||
) );
|
||||
}
|
||||
if ( $privacy !== '' ) {
|
||||
if ( $privacy === '0' ) {
|
||||
$queryBuilder->where( $dbr->expr(
|
||||
'af_hidden',
|
||||
'=',
|
||||
0
|
||||
) );
|
||||
} else {
|
||||
$privacy = (int)$privacy;
|
||||
$queryBuilder->where( $dbr->bitAnd(
|
||||
'af_hidden',
|
||||
$privacy
|
||||
) . " = $privacy" );
|
||||
}
|
||||
}
|
||||
$rows = $queryBuilder->caller( __METHOD__ )->fetchResultSet();
|
||||
|
||||
foreach ( $rows as $row ) {
|
||||
|
|
|
@ -32,7 +32,6 @@ class SearchFiltersTest extends MaintenanceBaseTestCase {
|
|||
'af_enabled' => 1,
|
||||
'af_comments' => '',
|
||||
'af_public_comments' => 'Test filter',
|
||||
'af_hidden' => Flags::FILTER_PUBLIC,
|
||||
'af_hit_count' => 0,
|
||||
'af_throttled' => 0,
|
||||
'af_deleted' => 0,
|
||||
|
@ -40,11 +39,29 @@ class SearchFiltersTest extends MaintenanceBaseTestCase {
|
|||
'af_group' => 'default'
|
||||
];
|
||||
$rows = [
|
||||
[ 'af_id' => 1, 'af_pattern' => '', 'af_actions' => '' ] + $defaultRow,
|
||||
[ 'af_id' => 2, 'af_pattern' => 'rmspecials(page_title) === "foo"', 'af_actions' => 'warn' ] + $defaultRow,
|
||||
[ 'af_id' => 3, 'af_pattern' => 'user_editcount % 3 !== 1', 'af_actions' => 'warn,block' ] + $defaultRow,
|
||||
[
|
||||
'af_id' => 4, 'af_pattern' => 'rmspecials(added_lines_pst) !== ""', 'af_actions' => 'block',
|
||||
'af_id' => 1,
|
||||
'af_pattern' => '',
|
||||
'af_actions' => '',
|
||||
'af_hidden' => Flags::FILTER_PUBLIC,
|
||||
] + $defaultRow,
|
||||
[
|
||||
'af_id' => 2,
|
||||
'af_pattern' => 'rmspecials(page_title) === "foo"',
|
||||
'af_actions' => 'warn',
|
||||
'af_hidden' => Flags::FILTER_PUBLIC,
|
||||
] + $defaultRow,
|
||||
[
|
||||
'af_id' => 3,
|
||||
'af_pattern' => 'user_editcount % 3 !== 1',
|
||||
'af_actions' => 'warn,block',
|
||||
'af_hidden' => Flags::FILTER_USES_PROTECTED_VARS,
|
||||
] + $defaultRow,
|
||||
[
|
||||
'af_id' => 4,
|
||||
'af_pattern' => 'rmspecials(added_lines_pst) !== ""',
|
||||
'af_actions' => 'block',
|
||||
'af_hidden' => Flags::FILTER_HIDDEN | Flags::FILTER_USES_PROTECTED_VARS,
|
||||
] + $defaultRow
|
||||
];
|
||||
$this->getDb()->newInsertQueryBuilder()
|
||||
|
@ -69,11 +86,11 @@ class SearchFiltersTest extends MaintenanceBaseTestCase {
|
|||
];
|
||||
}
|
||||
|
||||
public function testExecuteWhenNeitherPatternOrConsequenceProvided() {
|
||||
public function testExecuteWhenNoArgumentsProvided() {
|
||||
// It is safe to mock the DB type here, as the script should exit before any queries are made
|
||||
$this->overrideConfigValue( MainConfigNames::DBtype, 'mysql' );
|
||||
$this->expectCallToFatalError();
|
||||
$this->expectOutputString( "One of --consequence or --pattern should be specified.\n" );
|
||||
$this->expectOutputString( "One of --consequence, --pattern or --privacy should be specified.\n" );
|
||||
$this->maintenance->execute();
|
||||
}
|
||||
|
||||
|
@ -87,24 +104,38 @@ class SearchFiltersTest extends MaintenanceBaseTestCase {
|
|||
}
|
||||
|
||||
public static function provideSearches(): Generator {
|
||||
yield 'single filter for pattern search' => [ 'page_title', '', [ 2 ] ];
|
||||
yield 'multiple filters for pattern search' => [ 'rmspecials', '', [ 2, 4 ] ];
|
||||
yield 'single filter when consequence specified' => [ 'rmspecials', 'block', [ 4 ] ];
|
||||
yield 'regex for pattern' => [ '[a-z]\(', '', [ 2, 4 ] ];
|
||||
yield 'single filter for pattern search' => [ 'page_title', '', '', [ 2 ] ];
|
||||
yield 'multiple filters for pattern search' => [ 'rmspecials', '', '', [ 2, 4 ] ];
|
||||
yield 'single filter when consequence specified' => [ 'rmspecials', 'block', '', [ 4 ] ];
|
||||
yield 'regex for pattern' => [ '[a-z]\(', '', '', [ 2, 4 ] ];
|
||||
yield 'single filter for privacy level search' => [ '', '', '1', [ 4 ] ];
|
||||
yield 'multiple filters for privacy level search' => [ '', '', '2', [ 3, 4 ] ];
|
||||
yield 'search for multiple privacy levels' => [ '', '', '3', [ 4 ] ];
|
||||
yield 'search for public filters (handle zero)' => [ '', '', '0', [ 1, 2 ] ];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $pattern
|
||||
* @param string $consequence
|
||||
* @param string $privacy
|
||||
* @param array $expectedIDs
|
||||
* @dataProvider provideSearches
|
||||
*/
|
||||
public function testExecute_singleWiki( string $pattern, string $consequence, array $expectedIDs ) {
|
||||
public function testExecute_singleWiki(
|
||||
string $pattern,
|
||||
string $consequence,
|
||||
string $privacy,
|
||||
array $expectedIDs
|
||||
) {
|
||||
if ( $this->getDb()->getType() !== 'mysql' ) {
|
||||
$this->markTestSkipped( 'The script only works on MySQL' );
|
||||
}
|
||||
$this->setMwGlobals( [ 'wgConf' => (object)[ 'wikis' => [] ] ] );
|
||||
$this->maintenance->loadParamsAndArgs( null, [ 'pattern' => $pattern, 'consequence' => $consequence ] );
|
||||
$this->maintenance->loadParamsAndArgs( null, [
|
||||
'pattern' => $pattern,
|
||||
'consequence' => $consequence,
|
||||
'privacy' => $privacy,
|
||||
] );
|
||||
$this->expectOutputString( $this->getExpectedOutput( $expectedIDs ) );
|
||||
$this->maintenance->execute();
|
||||
}
|
||||
|
@ -112,16 +143,26 @@ class SearchFiltersTest extends MaintenanceBaseTestCase {
|
|||
/**
|
||||
* @param string $pattern
|
||||
* @param string $consequence
|
||||
* @param string $privacy
|
||||
* @param array $expectedIDs
|
||||
* @dataProvider provideSearches
|
||||
*/
|
||||
public function testExecute_multipleWikis( string $pattern, string $consequence, array $expectedIDs ) {
|
||||
public function testExecute_multipleWikis(
|
||||
string $pattern,
|
||||
string $consequence,
|
||||
string $privacy,
|
||||
array $expectedIDs
|
||||
) {
|
||||
if ( $this->getDb()->getType() !== 'mysql' ) {
|
||||
$this->markTestSkipped( 'The script only works on MySQL' );
|
||||
}
|
||||
global $wgDBname;
|
||||
$this->setMwGlobals( [ 'wgConf' => (object)[ 'wikis' => [ $wgDBname, $wgDBname ] ] ] );
|
||||
$this->maintenance->loadParamsAndArgs( null, [ 'pattern' => $pattern, 'consequence' => $consequence ] );
|
||||
$this->maintenance->loadParamsAndArgs( null, [
|
||||
'pattern' => $pattern,
|
||||
'consequence' => $consequence,
|
||||
'privacy' => $privacy,
|
||||
] );
|
||||
$expectedText = $this->getExpectedOutput( $expectedIDs ) . $this->getExpectedOutput( $expectedIDs, false );
|
||||
$this->expectOutputString( $expectedText );
|
||||
$this->maintenance->execute();
|
||||
|
|
Loading…
Reference in a new issue