From 202add59e656f07efe1743c4c7b6b61388393498 Mon Sep 17 00:00:00 2001 From: "H. C. Kruse" Date: Sun, 31 Mar 2024 20:31:55 +0200 Subject: [PATCH] refactor: Change checking of unscoping permissions Instead of checking the page content for a raw `wrapclass` string, look up the current revision's user and check if that user has the permission to enable un-scoping. This _is_ expensive as each time the tag is handled, a lookup will occur. In reference to #18 --- README.md | 2 + extension.json | 9 +--- includes/Hooks/MainHooks.php | 95 ++++++++++++------------------------ 3 files changed, 35 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 6e860e0..1b3bbc1 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,8 @@ Enables or disables css variable support. Default: `false` Allows users with `editinterface` permissions to unscope css by setting a `wrapclass` attribute. +**Note**: This is potentially expensive, as each templatestyles tag with `wrapclass` set, will attempt to look up the user of the current page revision, and check if this user has the permission to activate css un-scoping. + Example: `` results in the css being scoped to `.mediawiki` instead of `.mw-parser-output`. diff --git a/extension.json b/extension.json index a9bd57f..3025182 100644 --- a/extension.json +++ b/extension.json @@ -48,18 +48,13 @@ }, "HookHandlers": { "MainHooks": { - "class": "MediaWiki\\Extension\\TemplateStylesExtender\\Hooks\\MainHooks", - "services": [ - "PermissionManager", - "UserFactory" - ] + "class": "MediaWiki\\Extension\\TemplateStylesExtender\\Hooks\\MainHooks" } }, "Hooks": { "TemplateStylesPropertySanitizer": "MediaWiki\\Extension\\TemplateStylesExtender\\Hooks\\PropertySanitizerHook::onSanitize", "TemplateStylesStylesheetSanitizer": "MediaWiki\\Extension\\TemplateStylesExtender\\Hooks\\StylesheetSanitizerHook::onSanitize", - "ParserFirstCallInit": "MainHooks", - "EditPage::attemptSave": "MainHooks" + "ParserFirstCallInit": "MainHooks" }, "manifest_version": 2 } diff --git a/includes/Hooks/MainHooks.php b/includes/Hooks/MainHooks.php index 2516fe8..1c441b7 100644 --- a/includes/Hooks/MainHooks.php +++ b/includes/Hooks/MainHooks.php @@ -2,42 +2,19 @@ namespace MediaWiki\Extension\TemplateStylesExtender\Hooks; -use MediaWiki\EditPage\EditPage; use MediaWiki\Extension\TemplateStyles\Hooks; use MediaWiki\Extension\TemplateStylesExtender\TemplateStylesExtender; -use MediaWiki\Hook\EditPage__attemptSaveHook; use MediaWiki\Hook\ParserFirstCallInitHook; -use MediaWiki\Permissions\PermissionManager; -use MediaWiki\Revision\SlotRecord; -use MediaWiki\User\UserFactory; +use MediaWiki\MediaWikiServices; +use MediaWiki\User\UserIdentity; use MWException; use Parser; -use PermissionsError; use PPFrame; /** * phpcs:disable MediaWiki.NamingConventions.LowerCamelFunctionsName.FunctionName */ -class MainHooks implements ParserFirstCallInitHook, EditPage__attemptSaveHook { - - /** - * @var PermissionManager - */ - private PermissionManager $manager; - - /** - * @var UserFactory - */ - private UserFactory $factory; - - /** - * @param PermissionManager $manager - * @param UserFactory $factory - */ - public function __construct( PermissionManager $manager, UserFactory $factory ) { - $this->manager = $manager; - $this->factory = $factory; - } +class MainHooks implements ParserFirstCallInitHook { /** * @param Parser $parser @@ -51,6 +28,9 @@ class MainHooks implements ParserFirstCallInitHook, EditPage__attemptSaveHook { * This is a wrapper for tags, * that allows unscoping of css for users with 'editinterface' permissions * + * Note this is a potentially expensive operation, as a lookup for the user of the current revision is done. + * The unscoping will only happen, if the editor of the current revision has the rights to do so + * * @param string $text * @param string[] $params * @param Parser $parser @@ -59,17 +39,40 @@ class MainHooks implements ParserFirstCallInitHook, EditPage__attemptSaveHook { * @see Hooks::handleTag() */ public static function handleTag( $text, $params, $parser, $frame ): string { + $getOutput = static fn() => Hooks::handleTag( $text, $params, $parser, $frame ); + if ( + !isset( $params['wrapclass'] ) || $parser->getOptions() === null || !TemplateStylesExtender::getConfigValue( 'TemplateStylesExtenderEnableUnscopingSupport' ) ) { - return Hooks::handleTag( $text, $params, $parser, $frame ); + return $getOutput(); } + // 'wrapclass' option is set... + + /** @var \ParserOptions $options - Fix typehint */ $options = $parser->getOptions(); $wrapClass = $options->getWrapOutputClass(); - if ( isset( $params['wrapclass'] ) ) { + $permission = TemplateStylesExtender::getConfigValue( 'TemplateStylesExtenderUnscopingPermission' ); + + $rev = MediaWikiServices::getInstance()->getRevisionLookup()->getRevisionByTitle( $frame->getTitle() ); + + if ( $rev === null || $rev->getUser() === null ) { + return $getOutput(); + } + + /** @var UserIdentity $user - Fix typehint */ + $user = $rev->getUser(); + $user = MediaWikiServices::getInstance()->getUserFactory()->newFromUserIdentity( $user ); + $permissionManager = MediaWikiServices::getInstance()->getPermissionManager(); + + $userCan = $permissionManager->userHasRight( $user, $permission ) + || $permissionManager->userCan( $permission, $user, $frame->getTitle() ); + + // If the editor of the last revision is allowed to change the wrapclass, set it + if ( $userCan ) { $options->setOption( 'wrapclass', $params['wrapclass'] ); } @@ -78,40 +81,4 @@ class MainHooks implements ParserFirstCallInitHook, EditPage__attemptSaveHook { return $out; } - - /** - * Check if 'wrapclass' was used in the page, if so only users with 'editinterface' permissions may save the page - * - * @param EditPage $editpage_Obj - * @return true - * @throws PermissionsError - */ - public function onEditPage__attemptSave( $editpage_Obj ): bool { - $revision = $editpage_Obj->getExpectedParentRevision(); - - if ( - $revision === null || - !TemplateStylesExtender::getConfigValue( 'TemplateStylesExtenderEnableUnscopingSupport' ) - ) { - return true; - } - - $content = $revision->getContent( SlotRecord::MAIN ); - if ( $content === null ) { - return true; - } - - $permission = TemplateStylesExtender::getConfigValue( 'TemplateStylesExtenderUnscopingPermission' ); - - $user = $this->factory->newFromUserIdentity( $editpage_Obj->getContext()->getUser() ); - - $userCan = $this->manager->userHasRight( $user, $permission ) || - $this->manager->userCan( $permission, $user, $editpage_Obj->getTitle() ); - - if ( strpos( $content->getText(), 'wrapclass' ) !== false && !$userCan ) { - throw new PermissionsError( $permission, [ 'templatestylesextender-unscope-no-permisson' ] ); - } - - return true; - } }