BlockedExternalDomains: validate JSON structure before save

This makes raw page editing safer, and potentially enables opening up
access to less restricted user groups.

Bug: T337431
Change-Id: I14f21003a551f34b6e524e9b229613e79b0e5a70
This commit is contained in:
Siddharth VP 2023-06-06 01:19:47 +05:30 committed by Amir Sarabadani
parent 1298c9243b
commit 8a22007034
4 changed files with 66 additions and 2 deletions

View file

@ -411,7 +411,8 @@
"BeforeCreateEchoEvent": "MediaWiki\\Extension\\AbuseFilter\\Hooks\\Handlers\\EchoHandler::onBeforeCreateEchoEvent",
"ParserOutputStashForEdit": "FilteredActions",
"UnitTestsAfterDatabaseSetup": "Tests",
"UnitTestsBeforeDatabaseTeardown": "Tests"
"UnitTestsBeforeDatabaseTeardown": "Tests",
"JsonValidateSave": "EditPermission"
},
"ServiceWiringFiles": [
"includes/ServiceWiring.php"

View file

@ -583,6 +583,8 @@
"abusefilter-blocked-domains-remove-reason": "Reason",
"abusefilter-blocked-domains-remove-submit": "Remove",
"abusefilter-blocked-domains-attempted": "The text you wanted to publish was blocked by our filter. The following domain is blocked from being added: $1",
"abusefilter-blocked-domains-json-error": "JSON should be an array",
"abusefilter-blocked-domains-invalid-entry": "Entry $1 in JSON is invalid - it should be an object with 'domain' and 'notes' fields only, both being strings",
"log-description-abusefilterprivatedetails": "This log shows a list of times when a user accessed the private details of an abuse log.",
"abusefilter-noreason": "Warning: To see the private details of this log, you must provide a reason.",
"abusefilter-log-ip-not-available": "Not Available",

View file

@ -625,6 +625,8 @@
"abusefilter-blocked-domains-remove-reason": "Reason given for removal",
"abusefilter-blocked-domains-remove-submit": "Submit button",
"abusefilter-blocked-domains-attempted": "The error message when adding a blocked domain has been attempted.",
"abusefilter-blocked-domains-json-error": "The error message when editing the JSON page to no longer be an array",
"abusefilter-blocked-domains-invalid-entry": "The error message when adding an invalid entry to the JSON array. Parameters:\n* $1 - the serial number of the entry which is invalid.",
"log-description-abusefilterprivatedetails": "The description of the abuse filter private details access log.",
"abusefilter-noreason": "Warning message shown when no reasons is given to access the private details of an abuse log.",
"abusefilter-log-ip-not-available": "Text shown when IP address is not available.\n{{Identical|Not available}}",

View file

@ -3,18 +3,29 @@
namespace MediaWiki\Extension\AbuseFilter\Hooks\Handlers;
use JsonContent;
use MediaWiki\Content\Hook\JsonValidateSaveHook;
use MediaWiki\Extension\AbuseFilter\BlockedDomainStorage;
use MediaWiki\MediaWikiServices;
use MediaWiki\Page\PageIdentity;
use MediaWiki\Permissions\Hook\GetUserPermissionsErrorsHook;
use MessageSpecifier;
use StatusValue;
use Title;
use TitleValue;
use User;
/**
* This hook handler is for very simple checks, rather than the much more advanced ones
* undertaken by the FilteredActionsHandler.
*/
class EditPermissionHandler implements GetUserPermissionsErrorsHook {
class EditPermissionHandler implements GetUserPermissionsErrorsHook, JsonValidateSaveHook {
/** @var string[] */
private const JSON_OBJECT_FIELDS = [
'domain',
'notes'
];
/**
* @see https://www.mediawiki.org/wiki/Manual:Hooks/getUserPermissionsErrors
@ -48,4 +59,52 @@ class EditPermissionHandler implements GetUserPermissionsErrorsHook {
return false;
}
/**
* @param JsonContent $content
* @param PageIdentity $pageIdentity
* @param StatusValue $status
* @return bool|void
*/
public function onJsonValidateSave( JsonContent $content, PageIdentity $pageIdentity, StatusValue $status ) {
$services = MediaWikiServices::getInstance();
// Only do anything if we're enabled on this wiki.
if ( !$services->getMainConfig()->get( 'AbuseFilterEnableBlockedExternalDomain' ) ) {
return;
}
$title = TitleValue::newFromPage( $pageIdentity );
if ( !$title->inNamespace( NS_MEDIAWIKI ) || $title->getText() !== BlockedDomainStorage::TARGET_PAGE ) {
return;
}
$data = $content->getData()->getValue();
if ( !is_array( $data ) ) {
$status->fatal( 'abusefilter-blocked-domains-json-error' );
return;
}
$isValid = true;
$entryNumber = 0;
foreach ( $data as $element ) {
$entryNumber++;
// Check if each element is an object with all known fields, and no other fields
if ( is_object( $element ) && count( get_object_vars( $element ) ) === count( self::JSON_OBJECT_FIELDS ) ) {
foreach ( self::JSON_OBJECT_FIELDS as $field ) {
if ( !property_exists( $element, $field ) || !is_string( $element->{$field} ) ) {
$isValid = false;
break 2;
}
}
} else {
$isValid = false;
break;
}
}
if ( !$isValid ) {
$status->fatal( 'abusefilter-blocked-domains-invalid-entry', $entryNumber );
}
}
}