From 183372c99526201aa783f1ceb00a223065f1711b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20Tisza?= Date: Sat, 22 Apr 2023 15:33:41 +0200 Subject: [PATCH] authz: Group grants by riskiness Bug: T290790 Change-Id: Ib7a195c167f82e686c4ede45388957f9988bf75d --- RELEASE-NOTES-1.42 | 2 + docs/config-schema.yaml | 36 ++++++++++++++ docs/config-vars.php | 6 +++ docs/extension.schema.v1.json | 9 ++++ docs/extension.schema.v2.json | 9 ++++ includes/MainConfigNames.php | 6 +++ includes/MainConfigSchema.php | 45 ++++++++++++++++- includes/Permissions/GrantsInfo.php | 49 +++++++++++++++++++ includes/config-schema.php | 29 +++++++++++ includes/registration/ExtensionProcessor.php | 1 + .../includes/Permissions/GrantsInfoTest.php | 23 +++++++++ 11 files changed, 214 insertions(+), 1 deletion(-) diff --git a/RELEASE-NOTES-1.42 b/RELEASE-NOTES-1.42 index 935b425b0af..1694d17086a 100644 --- a/RELEASE-NOTES-1.42 +++ b/RELEASE-NOTES-1.42 @@ -31,6 +31,8 @@ For notes on 1.41.x and older releases, see HISTORY. * $wgConditionalUserOptions: Makes it possible to define user properties with defaults varying by user, without growing the user_properties table. Typical use-case is to enable a feature only for users created after a certain date. +* $wgGrantRiskGroups: Grant risk levels, used to indicate on various UIs which + grants should be considered risky. (T290790) * … ==== Changed configuration ==== diff --git a/docs/config-schema.yaml b/docs/config-schema.yaml index ad0ee4ecb60..4a96bc1a330 100644 --- a/docs/config-schema.yaml +++ b/docs/config-schema.yaml @@ -5693,6 +5693,42 @@ config-schema: site administrators. @see self::GrantPermissions @since 1.27 + GrantRiskGroups: + default: + basic: low + editpage: low + createeditmovepage: low + editprotected: vandalism + patrol: low + uploadfile: low + uploadeditmovefile: low + sendemail: security + viewmywatchlist: low + editviewmywatchlist: low + editmycssjs: security + editmyoptions: security + editinterface: vandalism + editsiteconfig: security + rollback: low + blockusers: vandalism + delete: vandalism + viewdeleted: vandalism + viewrestrictedlogs: security + protect: vandalism + oversight: security + createaccount: low + mergehistory: vandalism + import: security + highvolume: low + privateinfo: security + type: object + description: |- + Group grants by risk level. Keys are grant names (i.e. keys from GrantPermissions), + values are GrantsInfo::RISK_* constants. + Note that this classification is only informative; merely applying 'security' or 'internal' + to a grant won't prevent it from being available. It's used to give guidance to users + in various interfaces about the riskiness of the various grants. + @since 1.42 EnableBotPasswords: default: true type: boolean diff --git a/docs/config-vars.php b/docs/config-vars.php index 550e11cafbc..379b17dd257 100644 --- a/docs/config-vars.php +++ b/docs/config-vars.php @@ -3031,6 +3031,12 @@ $wgGrantPermissions = null; */ $wgGrantPermissionGroups = null; +/** + * Config variable stub for the GrantRiskGroups setting, for use by phpdoc and IDEs. + * @see MediaWiki\MainConfigSchema::GrantRiskGroups + */ +$wgGrantRiskGroups = null; + /** * Config variable stub for the EnableBotPasswords setting, for use by phpdoc and IDEs. * @see MediaWiki\MainConfigSchema::EnableBotPasswords diff --git a/docs/extension.schema.v1.json b/docs/extension.schema.v1.json index cc8ecd24ecb..eeca86325a0 100644 --- a/docs/extension.schema.v1.json +++ b/docs/extension.schema.v1.json @@ -786,6 +786,15 @@ } } }, + "GrantRiskGroups": { + "type": "object", + "description": "Map of grants to their risk category", + "patternProperties": { + "^[a-z]+$": { + "type": "string" + } + } + }, "ImplicitGroups": { "type": "array", "description": "Implicit groups" diff --git a/docs/extension.schema.v2.json b/docs/extension.schema.v2.json index b56ec443441..8b648ebcb0c 100644 --- a/docs/extension.schema.v2.json +++ b/docs/extension.schema.v2.json @@ -949,6 +949,15 @@ } } }, + "GrantRiskGroups": { + "type": "object", + "description": "Map of grants to their risk category", + "patternProperties": { + "^[a-z]+$": { + "type": "string" + } + } + }, "ImplicitGroups": { "type": "array", "description": "Implicit groups" diff --git a/includes/MainConfigNames.php b/includes/MainConfigNames.php index f0cf29f1502..ae4aaf1fc81 100644 --- a/includes/MainConfigNames.php +++ b/includes/MainConfigNames.php @@ -3046,6 +3046,12 @@ class MainConfigNames { */ public const GrantPermissionGroups = 'GrantPermissionGroups'; + /** + * Name constant for the GrantRiskGroups setting, for use with Config::get() + * @see MainConfigSchema::GrantRiskGroups + */ + public const GrantRiskGroups = 'GrantRiskGroups'; + /** * Name constant for the EnableBotPasswords setting, for use with Config::get() * @see MainConfigSchema::EnableBotPasswords diff --git a/includes/MainConfigSchema.php b/includes/MainConfigSchema.php index df4581140ba..7a663e1d1e7 100644 --- a/includes/MainConfigSchema.php +++ b/includes/MainConfigSchema.php @@ -47,6 +47,7 @@ use LocalisationCache; use LocalRepo; use LogFormatter; use MediaWiki\Deferred\SiteStatsUpdate; +use MediaWiki\Permissions\GrantsInfo; use MediaWiki\Request\WebRequest; use MediaWiki\Settings\Source\JsonSchemaTrait; use MediaWiki\Site\MediaWikiSite; @@ -9061,7 +9062,7 @@ class MainConfigSchema { 'default' => [ // Hidden grants are implicitly present - 'basic' => 'hidden', + 'basic' => 'hidden', 'editpage' => 'page-interaction', 'createeditmovepage' => 'page-interaction', @@ -9100,6 +9101,48 @@ class MainConfigSchema { 'additionalProperties' => [ 'type' => 'string', ], ]; + /** + * Group grants by risk level. Keys are grant names (i.e. keys from GrantPermissions), + * values are GrantsInfo::RISK_* constants. + * + * Note that this classification is only informative; merely applying 'security' or 'internal' + * to a grant won't prevent it from being available. It's used to give guidance to users + * in various interfaces about the riskiness of the various grants. + * + * @since 1.42 + */ + public const GrantRiskGroups = [ + 'default' => [ + 'basic' => GrantsInfo::RISK_LOW, + 'editpage' => GrantsInfo::RISK_LOW, + 'createeditmovepage' => GrantsInfo::RISK_LOW, + 'editprotected' => GrantsInfo::RISK_VANDALISM, + 'patrol' => GrantsInfo::RISK_LOW, + 'uploadfile' => GrantsInfo::RISK_LOW, + 'uploadeditmovefile' => GrantsInfo::RISK_LOW, + 'sendemail' => GrantsInfo::RISK_SECURITY, + 'viewmywatchlist' => GrantsInfo::RISK_LOW, + 'editviewmywatchlist' => GrantsInfo::RISK_LOW, + 'editmycssjs' => GrantsInfo::RISK_SECURITY, + 'editmyoptions' => GrantsInfo::RISK_SECURITY, + 'editinterface' => GrantsInfo::RISK_VANDALISM, + 'editsiteconfig' => GrantsInfo::RISK_SECURITY, + 'rollback' => GrantsInfo::RISK_LOW, + 'blockusers' => GrantsInfo::RISK_VANDALISM, + 'delete' => GrantsInfo::RISK_VANDALISM, + 'viewdeleted' => GrantsInfo::RISK_VANDALISM, + 'viewrestrictedlogs' => GrantsInfo::RISK_SECURITY, + 'protect' => GrantsInfo::RISK_VANDALISM, + 'oversight' => GrantsInfo::RISK_SECURITY, + 'createaccount' => GrantsInfo::RISK_LOW, + 'mergehistory' => GrantsInfo::RISK_VANDALISM, + 'import' => GrantsInfo::RISK_SECURITY, + 'highvolume' => GrantsInfo::RISK_LOW, + 'privateinfo' => GrantsInfo::RISK_SECURITY, + ], + 'type' => 'map', + ]; + /** * @since 1.27 */ diff --git a/includes/Permissions/GrantsInfo.php b/includes/Permissions/GrantsInfo.php index c9b1a5a2ba6..591da7ad40f 100644 --- a/includes/Permissions/GrantsInfo.php +++ b/includes/Permissions/GrantsInfo.php @@ -31,12 +31,44 @@ use MediaWiki\MainConfigNames; * @since 1.38 */ class GrantsInfo { + /** + * Risk level classification for grants which aren't particularly risky. These grants might + * be abused, e.g. for vandalism, but the effect is easy to undo and the efficiency of abusing + * them isn't particularly different from registering new user accounts and using those for + * abuse. + * Note that risk levels depend on the use case; the default classification is meant for + * "normal" (public, open registration) wikis. Classification for e.g. a private wiki holding + * confidential information could be quite different. + */ + public const RISK_LOW = 'low'; + + /** + * Risk level classification for grants which can be used for disruptive vandalism or other + * kinds of abuse that couldn't be achieved just by registering new accounts, such as main + * page vandalism, vandalism of popular templates, page merge vandalism, or blocks. + */ + public const RISK_VANDALISM = 'vandalism'; + + /** + * Risk level classification for grants which can be used to cause damage that is hard or + * impossible to undo, such as exfiltrating sensitive private data or creating security + * vulnerabilities. + */ + public const RISK_SECURITY = 'security'; + + /** + * Risk level classification for grants which are used for internal purposes and should not + * be handed out. + */ + public const RISK_INTERNAL = 'internal'; + /** * @internal For use by ServiceWiring */ public const CONSTRUCTOR_OPTIONS = [ MainConfigNames::GrantPermissions, MainConfigNames::GrantPermissionGroups, + MainConfigNames::GrantRiskGroups, ]; /** @var ServiceOptions */ @@ -137,4 +169,21 @@ class GrantsInfo { } return $grants; } + + /** + * Returns a map of grant name => risk group. The risk groups are the GrantsInfo::RISK_* + * constants, plus $default for grants where the risk level is not defined. + * @param string $default Default risk group to assign to grants for which no risk group + * is configured. $default does not have to be one of the RISK_* constants. + * @return string[] + * @since 1.42 + */ + public function getRiskGroupsByGrant( string $default = 'unknown' ): array { + $res = []; + $grantRiskGroups = $this->options->get( MainConfigNames::GrantRiskGroups ); + foreach ( $this->options->get( MainConfigNames::GrantPermissions ) as $grant => $_ ) { + $res[$grant] = $grantRiskGroups[$grant] ?? $default; + } + return $res; + } } diff --git a/includes/config-schema.php b/includes/config-schema.php index b08876b451d..4e7cd775b8e 100644 --- a/includes/config-schema.php +++ b/includes/config-schema.php @@ -1753,6 +1753,34 @@ return [ 'highvolume' => 'high-volume', 'privateinfo' => 'private-information', ], + 'GrantRiskGroups' => [ + 'basic' => 'low', + 'editpage' => 'low', + 'createeditmovepage' => 'low', + 'editprotected' => 'vandalism', + 'patrol' => 'low', + 'uploadfile' => 'low', + 'uploadeditmovefile' => 'low', + 'sendemail' => 'security', + 'viewmywatchlist' => 'low', + 'editviewmywatchlist' => 'low', + 'editmycssjs' => 'security', + 'editmyoptions' => 'security', + 'editinterface' => 'vandalism', + 'editsiteconfig' => 'security', + 'rollback' => 'low', + 'blockusers' => 'vandalism', + 'delete' => 'vandalism', + 'viewdeleted' => 'vandalism', + 'viewrestrictedlogs' => 'security', + 'protect' => 'vandalism', + 'oversight' => 'security', + 'createaccount' => 'low', + 'mergehistory' => 'vandalism', + 'import' => 'security', + 'highvolume' => 'low', + 'privateinfo' => 'security', + ], 'EnableBotPasswords' => true, 'BotPasswordsCluster' => false, 'BotPasswordsDatabase' => false, @@ -2812,6 +2840,7 @@ return [ 'PasswordAttemptThrottle' => 'array', 'GrantPermissions' => 'object', 'GrantPermissionGroups' => 'object', + 'GrantRiskGroups' => 'object', 'EnableBotPasswords' => 'boolean', 'BotPasswordsCluster' => [ 0 => 'string', diff --git a/includes/registration/ExtensionProcessor.php b/includes/registration/ExtensionProcessor.php index d6d5c79cbcf..1767fc06786 100644 --- a/includes/registration/ExtensionProcessor.php +++ b/includes/registration/ExtensionProcessor.php @@ -40,6 +40,7 @@ class ExtensionProcessor implements Processor { MainConfigNames::FilterLogTypes, MainConfigNames::GrantPermissionGroups, MainConfigNames::GrantPermissions, + MainConfigNames::GrantRiskGroups, MainConfigNames::GroupPermissions, MainConfigNames::GroupsAddToSelf, MainConfigNames::GroupsRemoveFromSelf, diff --git a/tests/phpunit/unit/includes/Permissions/GrantsInfoTest.php b/tests/phpunit/unit/includes/Permissions/GrantsInfoTest.php index e8a8b22737d..6f379a7f5b3 100644 --- a/tests/phpunit/unit/includes/Permissions/GrantsInfoTest.php +++ b/tests/phpunit/unit/includes/Permissions/GrantsInfoTest.php @@ -32,6 +32,13 @@ class GrantsInfoTest extends MediaWikiUnitTestCase { 'normal' => 'normal-group', 'admin' => 'admin', ], + MainConfigNames::GrantRiskGroups => [ + 'hidden1' => 'low', + 'hidden2' => 'low', + 'normal' => 'low', + 'normal2' => 'vandalism', + 'admin' => 'security', + ], ]; $this->grantsInfo = new GrantsInfo( @@ -138,4 +145,20 @@ class GrantsInfoTest extends MediaWikiUnitTestCase { ); } + /** + * @covers ::getRiskGroupsByGrant + */ + public function testGetRiskGroupsByGrant() { + $this->assertSame( + [ + 'hidden1' => 'low', + 'hidden2' => 'low', + 'normal' => 'low', + 'normal2' => 'vandalism', + 'admin' => 'security', + ], + $this->grantsInfo->getRiskGroupsByGrant() + ); + } + }