Skip to content

Commit

Permalink
Merge "SECURITY: Run edit filters on submit (undo/restore)"
Browse files Browse the repository at this point in the history
  • Loading branch information
jenkins-bot authored and Gerrit Code Review committed Jun 28, 2023
2 parents e44bbc0 + aa17394 commit c51ac87
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 15 deletions.
19 changes: 12 additions & 7 deletions repo/includes/Actions/SubmitEntityAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace Wikibase\Repo\Actions;

use Article;
use Content;
use IContextSource;
use LogicException;
use MediaWiki\MediaWikiServices;
Expand All @@ -14,6 +13,7 @@
use Title;
use Wikibase\Lib\Summary;
use Wikibase\Repo\Content\EntityContent;
use Wikibase\Repo\EditEntity\EditFilterHookRunner;
use Wikibase\Repo\SummaryFormatter;
use Wikibase\Repo\WikibaseRepo;

Expand All @@ -29,10 +29,8 @@
*/
class SubmitEntityAction extends EditEntityAction {

/**
* @var SummaryFormatter
*/
private $summaryFormatter;
private EditFilterHookRunner $editFilterHookRunner;
private SummaryFormatter $summaryFormatter;

/**
* @see EditEntityAction::__construct
Expand All @@ -43,6 +41,7 @@ class SubmitEntityAction extends EditEntityAction {
public function __construct( Article $article, IContextSource $context ) {
parent::__construct( $article, $context );

$this->editFilterHookRunner = WikibaseRepo::getEditFilterHookRunner();
$this->summaryFormatter = WikibaseRepo::getSummaryFormatter();
}

Expand Down Expand Up @@ -213,7 +212,7 @@ public function execute() {

/**
* @param Title $title
* @param Content $content
* @param EntityContent $content
* @param string $summary
* @param int $undidRevId
* @param int $originalRevId
Expand All @@ -222,7 +221,7 @@ public function execute() {
* @return Status
*/
private function attemptSave(
Title $title, Content $content, $summary, $undidRevId, $originalRevId, $editToken
Title $title, EntityContent $content, $summary, $undidRevId, $originalRevId, $editToken
) {
$status = $this->getEditTokenStatus( $editToken );

Expand All @@ -236,6 +235,12 @@ private function attemptSave(
return $status;
}

$status = $this->editFilterHookRunner->run( $content, $this->getContext(), $summary );

if ( !$status->isOK() ) {
return $status;
}

// save edit
$page = MediaWikiServices::getInstance()->getWikiPageFactory()
->newFromTitle( $title );
Expand Down
3 changes: 2 additions & 1 deletion repo/includes/EditEntity/EditFilterHookRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Status;
use Wikibase\DataModel\Entity\EntityDocument;
use Wikibase\DataModel\Entity\EntityRedirect;
use Wikibase\Repo\Content\EntityContent;

/**
* Interface to run a hook before and edit is saved.
Expand All @@ -20,7 +21,7 @@ interface EditFilterHookRunner {
/**
* Call EditFilterMergedContent hook, if registered.
*
* @param EntityDocument|EntityRedirect|null $new The entity or redirect we are trying to save
* @param EntityDocument|EntityRedirect|EntityContent|null $new The entity or redirect (content) we are trying to save
* @param IContextSource $context The request context for the edit
* @param string $summary The edit summary
*
Expand Down
15 changes: 13 additions & 2 deletions repo/includes/EditEntity/MediaWikiEditFilterHookRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Wikibase\DataModel\Entity\EntityId;
use Wikibase\DataModel\Entity\EntityRedirect;
use Wikibase\Lib\Store\EntityNamespaceLookup;
use Wikibase\Repo\Content\EntityContent;
use Wikibase\Repo\Content\EntityContentFactory;
use Wikibase\Repo\Store\EntityTitleStoreLookup;

Expand Down Expand Up @@ -59,7 +60,7 @@ public function __construct(
/**
* Call EditFilterMergedContent hook, if registered.
*
* @param EntityDocument|EntityRedirect|null $new The entity or redirect we are trying to save
* @param EntityDocument|EntityRedirect|EntityContent|null $new The entity or redirect (content) we are trying to save
* @param IContextSource $context The request context for the edit
* @param string $summary The edit summary
*
Expand Down Expand Up @@ -95,13 +96,23 @@ public function run( $new, IContextSource $context, string $summary ) {
$entityId = $new->getEntityId();
$entityType = $entityId->getEntityType();

$context = $this->getContextForEditFilter(
$context,
$entityId,
$entityType
);
} elseif ( $new instanceof EntityContent ) {
$entityContent = $new;
$entityId = $entityContent->getEntityId();
$entityType = $entityId->getEntityType();

$context = $this->getContextForEditFilter(
$context,
$entityId,
$entityType
);
} else {
throw new InvalidArgumentException( '$new must be instance of EntityDocument or EntityRedirect' );
throw new InvalidArgumentException( '$new must be instance of EntityDocument, EntityRedirect or EntityContent' );
}

$slotRole = $this->namespaceLookup->getEntitySlotRole( $entityType );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Status;
use Wikibase\DataModel\Entity\EntityDocument;
use Wikibase\DataModel\Entity\EntityRedirect;
use Wikibase\Repo\Content\EntityContent;

/**
* EditFilterHookRunning that collects stats for edits.
Expand Down Expand Up @@ -37,7 +38,7 @@ public function __construct(
}

/**
* @param null|EntityDocument|EntityRedirect $new
* @param null|EntityDocument|EntityRedirect|EntityContent $new
* @param IContextSource $context
* @param string $summary
* @return Status
Expand All @@ -47,10 +48,21 @@ public function run( $new, IContextSource $context, $summary ) {
$hookStatus = $this->hookRunner->run( $new, $context, $summary );
$attemptSaveFilterEnd = microtime( true );

$this->stats->timing(
"{$this->timingPrefix}.run.{$new->getType()}",
( $attemptSaveFilterEnd - $attemptSaveFilterStart ) * 1000
);
if ( $new !== null ) {
if ( $new instanceof EntityDocument ) {
$entityType = $new->getType();
} elseif ( $new instanceof EntityRedirect ) {
$entityType = $new->getEntityId()->getEntityType();
} elseif ( $new instanceof EntityContent ) {
$entityType = $new->getEntityId()->getEntityType();
} else {
$entityType = 'UNKNOWN';
}
$this->stats->timing(
"{$this->timingPrefix}.run.{$entityType}",
( $attemptSaveFilterEnd - $attemptSaveFilterStart ) * 1000
);
}

return $hookStatus;
}
Expand Down
4 changes: 4 additions & 0 deletions repo/tests/phpunit/includes/Actions/ActionTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ private function makeTestItemData() {
$item->setLabel( 'de', 'London' );
$items['London'][] = $item;

$item = new Item();
$item->setLabel( 'en', 'Oslo' );
$items['Oslo'][] = $item;

$item = new Item();
$item->setLabel( 'de', 'Oslo' );
$item->setLabel( 'en', 'Oslo' );
Expand Down
39 changes: 39 additions & 0 deletions repo/tests/phpunit/includes/Actions/EditEntityActionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

namespace Wikibase\Repo\Tests\Actions;

use IContextSource;
use MediaWiki\MediaWikiServices;
use RuntimeException;
use Status;
use Title;
use User;
use Wikibase\DataModel\Term\LabelsProvider;
use Wikibase\Repo\Actions\EditEntityAction;
use Wikibase\Repo\Actions\SubmitEntityAction;
use Wikibase\Repo\Content\EntityContent;
use Wikibase\Repo\WikibaseRepo;
use WikiPage;

Expand All @@ -32,6 +36,27 @@ protected function setUp(): void {

// Remove handlers for the "OutputPageParserOutput" hook
$this->clearHook( 'OutputPageParserOutput' );

// Install EditFilterMergedContent hook blocking any edits to "Oslo"@en
$method = __METHOD__;
$this->setTemporaryHook(
'EditFilterMergedContent',
function ( IContextSource $context, EntityContent $entityContent, Status $status ) use ( $method ) {
if ( !$entityContent->isRedirect() ) {
$entity = $entityContent->getEntity();
if ( $entity instanceof LabelsProvider ) {
$labels = $entity->getLabels();
if ( $labels->hasTermForLanguage( 'en' )
&& $labels->getByLanguage( 'en' )->getText() === 'Oslo'
) {
$status->fatal( $method . ': block "Oslo"@en edit' );
return false;
}
}
}
return true;
}
);
}

public function testActionForPage() {
Expand Down Expand Up @@ -845,6 +870,20 @@ public function provideUndoSubmit() {
null, // user
'/wikibase-undo-redirect-latestnoredirect/', // htmlPattern: should contain error
];

// blocked by edit filter (e.g. AbuseFilter, T250720), see setUp()
yield 'edit blocked by edit filter' => [
'submit', // action
'Oslo', // handle
[ // params
'wpSave' => 1,
'wpEditToken' => true, // automatic token
'undo' => 0, // current revision
],
true, // post
null, // user
'/block "Oslo"@en edit/', // htmlPattern: should contain filter error message
];
}

/**
Expand Down

0 comments on commit c51ac87

Please sign in to comment.