mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Nuke
synced 2024-11-23 15:57:03 +00:00
Use revision table instead of recentchanges
This switches Nuke over to using the `revision` table instead of `recentchanges`, allowing a wider timespan of pages to be deleted. As a DoS prevention measure, this adds the `$wgNukeMaxAge` config option (defaulted to the value of `$wgRCMaxAge`) and a max execution time for the SELECT (set to `$wgMaxExecutionTimeForExpensiveQueries`). This also adds a relevant test. Partially based off of I6c2b7e6b695d58a7dcba93ccaeba9ed35d81cf80. Bug: T379147 Co-Authored-by: Kgraessle <kgraessle@wikimedia.org> Change-Id: I5d68d2663751783bcc773799e951f74866ceb722
This commit is contained in:
parent
10f5a9b1bc
commit
41d31c3076
|
@ -29,6 +29,12 @@
|
||||||
"ListDefinedTags": "MediaWiki\\Extension\\Nuke\\Hooks::onRegisterTags",
|
"ListDefinedTags": "MediaWiki\\Extension\\Nuke\\Hooks::onRegisterTags",
|
||||||
"ChangeTagsListActive": "MediaWiki\\Extension\\Nuke\\Hooks::onRegisterTags"
|
"ChangeTagsListActive": "MediaWiki\\Extension\\Nuke\\Hooks::onRegisterTags"
|
||||||
},
|
},
|
||||||
|
"config": {
|
||||||
|
"NukeMaxAge": {
|
||||||
|
"value": 0,
|
||||||
|
"description": "The maximum age of a new page creation or file upload before it becomes ineligible for mass deletion. Defaults to the value of $wgRCMaxAge."
|
||||||
|
}
|
||||||
|
},
|
||||||
"ResourceModules": {
|
"ResourceModules": {
|
||||||
"ext.nuke.confirm": {
|
"ext.nuke.confirm": {
|
||||||
"scripts": [
|
"scripts": [
|
||||||
|
|
|
@ -12,6 +12,7 @@ use MediaWiki\Html\Html;
|
||||||
use MediaWiki\Html\ListToggle;
|
use MediaWiki\Html\ListToggle;
|
||||||
use MediaWiki\HTMLForm\HTMLForm;
|
use MediaWiki\HTMLForm\HTMLForm;
|
||||||
use MediaWiki\Language\Language;
|
use MediaWiki\Language\Language;
|
||||||
|
use MediaWiki\MainConfigNames;
|
||||||
use MediaWiki\Page\File\FileDeleteForm;
|
use MediaWiki\Page\File\FileDeleteForm;
|
||||||
use MediaWiki\Permissions\PermissionManager;
|
use MediaWiki\Permissions\PermissionManager;
|
||||||
use MediaWiki\Request\WebRequest;
|
use MediaWiki\Request\WebRequest;
|
||||||
|
@ -437,21 +438,32 @@ class SpecialNuke extends SpecialPage {
|
||||||
*/
|
*/
|
||||||
protected function getNewPages( $username, $limit, $namespace = null, $tempnames = [] ): array {
|
protected function getNewPages( $username, $limit, $namespace = null, $tempnames = [] ): array {
|
||||||
$dbr = $this->dbProvider->getReplicaDatabase();
|
$dbr = $this->dbProvider->getReplicaDatabase();
|
||||||
|
|
||||||
|
$maxAge = $this->getConfig()->get( "NukeMaxAge" );
|
||||||
|
// If no Nuke-specific max age was set, this should match the value of `$wgRCMaxAge`.
|
||||||
|
if ( !$maxAge ) {
|
||||||
|
$maxAge = $this->getConfig()->get( MainConfigNames::RCMaxAge );
|
||||||
|
}
|
||||||
|
|
||||||
$queryBuilder = $dbr->newSelectQueryBuilder()
|
$queryBuilder = $dbr->newSelectQueryBuilder()
|
||||||
->select( [ 'page_title', 'page_namespace' ] )
|
->select( [ 'page_title', 'page_namespace' ] )
|
||||||
->from( 'recentchanges' )
|
->from( 'revision' )
|
||||||
->join( 'actor', null, 'actor_id=rc_actor' )
|
->join( 'actor', null, 'actor_id=rev_actor' )
|
||||||
->join( 'page', null, 'page_id=rc_cur_id' )
|
->join( 'page', null, 'page_id=rev_page' )
|
||||||
->where(
|
->where( [
|
||||||
$dbr->expr( 'rc_source', '=', 'mw.new' )->orExpr(
|
$dbr->expr( 'rev_parent_id', '=', 0 ),
|
||||||
$dbr->expr( 'rc_log_type', '=', 'upload' )
|
$dbr->expr( 'rev_timestamp', '>', $dbr->timestamp(
|
||||||
->and( 'rc_log_action', '=', 'upload' )
|
time() - $maxAge
|
||||||
)
|
) )
|
||||||
)
|
] )
|
||||||
->orderBy( 'rc_timestamp', SelectQueryBuilder::SORT_DESC )
|
->orderBy( 'rev_timestamp', SelectQueryBuilder::SORT_DESC )
|
||||||
->limit( $limit );
|
->distinct()
|
||||||
|
->limit( $limit )
|
||||||
|
->setMaxExecutionTime(
|
||||||
|
$this->getConfig()->get( MainConfigNames::MaxExecutionTimeForExpensiveQueries )
|
||||||
|
);
|
||||||
|
|
||||||
$queryBuilder->field( 'actor_name', 'rc_user_text' );
|
$queryBuilder->field( 'actor_name' );
|
||||||
$actornames = array_filter( [ $username, ...$tempnames ] );
|
$actornames = array_filter( [ $username, ...$tempnames ] );
|
||||||
if ( $actornames ) {
|
if ( $actornames ) {
|
||||||
$queryBuilder->andWhere( [ 'actor_name' => $actornames ] );
|
$queryBuilder->andWhere( [ 'actor_name' => $actornames ] );
|
||||||
|
@ -556,7 +568,7 @@ class SpecialNuke extends SpecialPage {
|
||||||
foreach ( $result as $row ) {
|
foreach ( $result as $row ) {
|
||||||
$pages[] = [
|
$pages[] = [
|
||||||
Title::makeTitle( $row->page_namespace, $row->page_title ),
|
Title::makeTitle( $row->page_namespace, $row->page_title ),
|
||||||
$row->rc_user_text
|
$row->actor_name
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -560,6 +560,77 @@ class SpecialNukeTest extends SpecialPageTestBase {
|
||||||
$this->assertEquals( 2, substr_count( $html, '<li>' ) );
|
$this->assertEquals( 2, substr_count( $html, '<li>' ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testListMaxAge() {
|
||||||
|
// 1 day
|
||||||
|
$maxAge = 86400;
|
||||||
|
$this->overrideConfigValues( [
|
||||||
|
'NukeMaxAge' => $maxAge,
|
||||||
|
'RCMaxAge' => $maxAge
|
||||||
|
] );
|
||||||
|
|
||||||
|
// Will never show up. If it does, the max age isn't being applied at all.
|
||||||
|
$page1Status = $this->editPage( 'Page1', 'test' );
|
||||||
|
// Will show up conditionally (see below).
|
||||||
|
$page2Status = $this->editPage( 'Page2', 'test' );
|
||||||
|
// Will always show up.
|
||||||
|
$this->editPage( 'Page3', 'test' );
|
||||||
|
|
||||||
|
// Prepare database connection and update query builder
|
||||||
|
$dbw = $this->getServiceContainer()->getConnectionProvider()->getPrimaryDatabase();
|
||||||
|
$uqb = $dbw->newUpdateQueryBuilder()
|
||||||
|
->update( 'revision' )
|
||||||
|
->caller( __METHOD__ );
|
||||||
|
|
||||||
|
// Manually change the rev_timestamp of Page1's creation in the database.
|
||||||
|
( clone $uqb )
|
||||||
|
->set( [ 'rev_timestamp' => $dbw->timestamp( time() - ( $maxAge * 3 ) ) ] )
|
||||||
|
->where( [
|
||||||
|
'rev_id' => $page1Status->getNewRevision()->getId()
|
||||||
|
] )->execute();
|
||||||
|
// Manually change the rev_timestamp of Page2's creation in the database.
|
||||||
|
( clone $uqb )
|
||||||
|
->set( [ 'rev_timestamp' => $dbw->timestamp( time() - $maxAge - 60 ) ] )
|
||||||
|
->where( [
|
||||||
|
'rev_id' => $page2Status->getNewRevision()->getId()
|
||||||
|
] )->execute();
|
||||||
|
|
||||||
|
$admin = $this->getTestSysop()->getUser();
|
||||||
|
$request = new FauxRequest( [
|
||||||
|
'action' => 'submit',
|
||||||
|
'pattern' => 'Page%',
|
||||||
|
'limit' => 2
|
||||||
|
], true );
|
||||||
|
$performer = new UltimateAuthority( $admin );
|
||||||
|
|
||||||
|
[ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer );
|
||||||
|
$this->assertStringNotContainsString( 'Page1', $html );
|
||||||
|
$this->assertStringNotContainsString( 'Page2', $html );
|
||||||
|
$this->assertStringContainsString( 'Page3', $html );
|
||||||
|
|
||||||
|
// Now check if resetting $wgNukeMaxAge will show Page2.
|
||||||
|
// This should follow $wgRCMaxAge, which will include the page.
|
||||||
|
$this->overrideConfigValues( [
|
||||||
|
'RCMaxAge' => $maxAge * 2,
|
||||||
|
'NukeMaxAge' => 0
|
||||||
|
] );
|
||||||
|
|
||||||
|
[ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer );
|
||||||
|
$this->assertStringNotContainsString( 'Page1', $html );
|
||||||
|
$this->assertStringContainsString( 'Page2', $html );
|
||||||
|
$this->assertStringContainsString( 'Page3', $html );
|
||||||
|
|
||||||
|
// Now check if expanding just $wgNukeMaxAge will show Page2.
|
||||||
|
$this->overrideConfigValues( [
|
||||||
|
'RCMaxAge' => $maxAge,
|
||||||
|
'NukeMaxAge' => $maxAge * 2
|
||||||
|
] );
|
||||||
|
|
||||||
|
[ $html ] = $this->executeSpecialPage( '', $request, 'qqx', $performer );
|
||||||
|
$this->assertStringNotContainsString( 'Page1', $html );
|
||||||
|
$this->assertStringContainsString( 'Page2', $html );
|
||||||
|
$this->assertStringContainsString( 'Page3', $html );
|
||||||
|
}
|
||||||
|
|
||||||
public function testExecutePattern() {
|
public function testExecutePattern() {
|
||||||
// Test that matching wildcards works, and that escaping wildcards works as documented
|
// Test that matching wildcards works, and that escaping wildcards works as documented
|
||||||
// at https://www.mediawiki.org/wiki/Help:Extension:Nuke
|
// at https://www.mediawiki.org/wiki/Help:Extension:Nuke
|
||||||
|
|
Loading…
Reference in a new issue