From db888bc5ad43e0471bcb1adcf600acf66a5d8353 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gerg=C5=91=20Tisza?= Date: Tue, 20 Mar 2018 15:44:27 -0700 Subject: [PATCH] Segregate right to edit sitewide CSS/JS To limit the likelihood of incidents like T189665, the ability to edit sitewide CSS/JSON/JS is split out from editinterface, into separate 'editsitecss', 'editsitejson' and 'editsitejs' rights. editsitecss, editsitejs, and the right to edit another (potentially more privileged) user's personal CSS/JS is removed from sysops, and a new user group, interface-admin, is created specifically for that task (along with a new grant group 'editsiteconfig'). interface-admin is granted to the first user of a new wiki, along with sysop and bureaucrat. Bug: T120886 Bug: T190015 Depends-On: Ia9b2ea1450aff6121dc0f3777bf029292c8aaad9 Change-Id: Ifefd872640642441e26f8b2f144ffe4b88d2eb12 --- RELEASE-NOTES-1.32 | 8 ++ includes/DefaultSettings.php | 27 +++++- includes/MWGrants.php | 10 +-- includes/Title.php | 109 ++++++++++++++++++++---- includes/installer/Installer.php | 3 +- includes/installer/WebInstallerName.php | 2 +- includes/user/User.php | 3 + languages/i18n/en.json | 12 ++- languages/i18n/qqq.json | 14 ++- maintenance/createAndPromote.php | 2 +- 10 files changed, 158 insertions(+), 32 deletions(-) diff --git a/RELEASE-NOTES-1.32 b/RELEASE-NOTES-1.32 index 54352138a3..ec25ecd4f5 100644 --- a/RELEASE-NOTES-1.32 +++ b/RELEASE-NOTES-1.32 @@ -29,6 +29,14 @@ production. * The archive table's ar_rev_id field is now unique. * Special:BotPasswords now requires reauthentication. * (T194414) The default watchlist view time has been increased from 3 to 7 days. +* The right to edit sitewide Javascript (e.g. MediaWiki:Common.js), CSS or JSON + was separated from 'editinterface' and is available under + 'editsitejs'/'editsitecss'/'editsitejson'. Having 'editinterface' is still + necessary to edit such pages. +* A new user group, 'interface-admin', is added for controlling access to + sitewide CSS/JS (and editing other users' CSS/JS). No other group has + 'editsitecss', 'editusercss', 'editsitejs' or 'edituserjs' by default. +* A new grant group, 'editsiteconfig', is added for granting the above rights. === New features in 1.32 === * (T112474) Generalized the ResourceLoader mechanism for overriding modules diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 2811613fbc..ebce46416c 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -4548,6 +4548,12 @@ $wgPasswordPolicy = [ 'PasswordCannotMatchUsername' => true, 'PasswordCannotBePopular' => 25, ], + 'interface-admin' => [ + 'MinimalPasswordLength' => 8, + 'MinimumPasswordLengthToLogin' => 1, + 'PasswordCannotMatchUsername' => true, + 'PasswordCannotBePopular' => 25, + ], 'bot' => [ 'MinimalPasswordLength' => 8, 'MinimumPasswordLengthToLogin' => 1, @@ -5217,9 +5223,8 @@ $wgGroupPermissions['sysop']['deletedhistory'] = true; $wgGroupPermissions['sysop']['deletedtext'] = true; $wgGroupPermissions['sysop']['undelete'] = true; $wgGroupPermissions['sysop']['editinterface'] = true; -$wgGroupPermissions['sysop']['editusercss'] = true; +$wgGroupPermissions['sysop']['editsitejson'] = true; $wgGroupPermissions['sysop']['edituserjson'] = true; -$wgGroupPermissions['sysop']['edituserjs'] = true; $wgGroupPermissions['sysop']['import'] = true; $wgGroupPermissions['sysop']['importupload'] = true; $wgGroupPermissions['sysop']['move'] = true; @@ -5252,6 +5257,14 @@ $wgGroupPermissions['sysop']['mergehistory'] = true; $wgGroupPermissions['sysop']['managechangetags'] = true; $wgGroupPermissions['sysop']['deletechangetags'] = true; +$wgGroupPermissions['interface-admin']['editinterface'] = true; +$wgGroupPermissions['interface-admin']['editsitecss'] = true; +$wgGroupPermissions['interface-admin']['editsitejson'] = true; +$wgGroupPermissions['interface-admin']['editsitejs'] = true; +$wgGroupPermissions['interface-admin']['editusercss'] = true; +$wgGroupPermissions['interface-admin']['edituserjson'] = true; +$wgGroupPermissions['interface-admin']['edituserjs'] = true; + // Permission to change users' group assignments $wgGroupPermissions['bureaucrat']['userrights'] = true; $wgGroupPermissions['bureaucrat']['noratelimit'] = true; @@ -5858,9 +5871,14 @@ $wgGrantPermissions['editmyoptions']['editmyoptions'] = true; $wgGrantPermissions['editinterface'] = $wgGrantPermissions['editpage']; $wgGrantPermissions['editinterface']['editinterface'] = true; -$wgGrantPermissions['editinterface']['editusercss'] = true; $wgGrantPermissions['editinterface']['edituserjson'] = true; -$wgGrantPermissions['editinterface']['edituserjs'] = true; +$wgGrantPermissions['editinterface']['editsitejson'] = true; + +$wgGrantPermissions['editsiteconfig'] = $wgGrantPermissions['editinterface']; +$wgGrantPermissions['editsiteconfig']['editusercss'] = true; +$wgGrantPermissions['editsiteconfig']['edituserjs'] = true; +$wgGrantPermissions['editsiteconfig']['editsitecss'] = true; +$wgGrantPermissions['editsiteconfig']['editsitejs'] = true; $wgGrantPermissions['createeditmovepage'] = $wgGrantPermissions['editpage']; $wgGrantPermissions['createeditmovepage']['createpage'] = true; @@ -5939,6 +5957,7 @@ $wgGrantPermissionGroups = [ 'editmyoptions' => 'customization', 'editinterface' => 'administration', + 'editsiteconfig' => 'administration', 'rollback' => 'administration', 'blockusers' => 'administration', 'delete' => 'administration', diff --git a/includes/MWGrants.php b/includes/MWGrants.php index ba22590c92..769e5b4da4 100644 --- a/includes/MWGrants.php +++ b/includes/MWGrants.php @@ -58,11 +58,11 @@ class MWGrants { // Give grep a chance to find the usages: // grant-blockusers, grant-createeditmovepage, grant-delete, // grant-editinterface, grant-editmycssjs, grant-editmywatchlist, - // grant-editpage, grant-editprotected, grant-highvolume, - // grant-oversight, grant-patrol, grant-protect, grant-rollback, - // grant-sendemail, grant-uploadeditmovefile, grant-uploadfile, - // grant-basic, grant-viewdeleted, grant-viewmywatchlist, - // grant-createaccount + // grant-editsiteconfig, grant-editpage, grant-editprotected, + // grant-highvolume, grant-oversight, grant-patrol, grant-protect, + // grant-rollback, grant-sendemail, grant-uploadeditmovefile, + // grant-uploadfile, grant-basic, grant-viewdeleted, + // grant-viewmywatchlist, grant-createaccount $msg = wfMessage( "grant-$grant" ); if ( $lang !== null ) { if ( is_string( $lang ) ) { diff --git a/includes/Title.php b/includes/Title.php index b583554ab4..a364b5b010 100644 --- a/includes/Title.php +++ b/includes/Title.php @@ -1289,12 +1289,9 @@ class Title implements LinkTarget { */ public function isSiteConfigPage() { return ( - NS_MEDIAWIKI == $this->mNamespace - && ( - $this->hasContentModel( CONTENT_MODEL_CSS ) - || $this->hasContentModel( CONTENT_MODEL_JSON ) - || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) - ) + $this->isSiteCssConfigPage() + || $this->isSiteJsonConfigPage() + || $this->isSiteJsConfigPage() ); } @@ -1317,13 +1314,9 @@ class Title implements LinkTarget { */ public function isUserConfigPage() { return ( - NS_USER == $this->mNamespace - && $this->isSubpage() - && ( - $this->hasContentModel( CONTENT_MODEL_CSS ) - || $this->hasContentModel( CONTENT_MODEL_JSON ) - || $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) - ) + $this->isUserCssConfigPage() + || $this->isUserJsonConfigPage() + || $this->isUserJsConfigPage() ); } @@ -1423,6 +1416,60 @@ class Title implements LinkTarget { return $this->isUserJsConfigPage(); } + /** + * Is this a sitewide CSS "config" page? + * + * @return bool + * @since 1.32 + */ + public function isSiteCssConfigPage() { + return ( + NS_MEDIAWIKI == $this->mNamespace + && ( + $this->hasContentModel( CONTENT_MODEL_CSS ) + // paranoia - a MediaWiki: namespace page with mismatching extension and content + // model is probably by mistake and might get handled incorrectly (see e.g. T112937) + || substr( $this->getDBkey(), -4 ) === '.css' + ) + ); + } + + /** + * Is this a sitewide JSON "config" page? + * + * @return bool + * @since 1.32 + */ + public function isSiteJsonConfigPage() { + return ( + NS_MEDIAWIKI == $this->mNamespace + && ( + $this->hasContentModel( CONTENT_MODEL_JSON ) + // paranoia - a MediaWiki: namespace page with mismatching extension and content + // model is probably by mistake and might get handled incorrectly (see e.g. T112937) + || substr( $this->getDBkey(), -5 ) === '.json' + ) + ); + } + + /** + * Is this a sitewide JS "config" page? + * + * @return bool + * @since 1.31 + */ + public function isSiteJsConfigPage() { + return ( + NS_MEDIAWIKI == $this->mNamespace + && ( + $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) + // paranoia - a MediaWiki: namespace page with mismatching extension and content + // model is probably by mistake and might get handled incorrectly (see e.g. T112937) + || substr( $this->getDBkey(), -3 ) === '.js' + ) + ); + } + /** * Is this a talk page of some sort? * @@ -2313,6 +2360,33 @@ class Title implements LinkTarget { return $errors; } + /** + * Check sitewide CSS/JSON/JS permissions + * + * @param string $action The action to check + * @param User $user User to check + * @param array $errors List of current errors + * @param string $rigor Same format as Title::getUserPermissionsErrors() + * @param bool $short Short circuit on first error + * + * @return array List of errors + */ + private function checkSiteConfigPermissions( $action, $user, $errors, $rigor, $short ) { + if ( $action != 'patrol' ) { + // Sitewide CSS/JSON/JS changes, like all NS_MEDIAWIKI changes, also require the + // editinterface right. That's implemented as a restriction so no check needed here. + if ( $this->isSiteCssConfigPage() && !$user->isAllowed( 'editsitecss' ) ) { + $errors[] = [ 'sitecssprotected', $action ]; + } elseif ( $this->isSiteJsonConfigPage() && !$user->isAllowed( 'editsitejson' ) ) { + $errors[] = [ 'sitejsonprotected', $action ]; + } elseif ( $this->isSiteJsConfigPage() && !$user->isAllowed( 'editsitejs' ) ) { + $errors[] = [ 'sitejsprotected', $action ]; + } + } + + return $errors; + } + /** * Check CSS/JSON/JS sub-page permissions * @@ -2701,10 +2775,10 @@ class Title implements LinkTarget { 'checkReadPermissions', 'checkUserBlock', // for wgBlockDisablesLogin ]; - # Don't call checkSpecialsAndNSPermissions or checkUserConfigPermissions - # here as it will lead to duplicate error messages. This is okay to do - # since anywhere that checks for create will also check for edit, and - # those checks are called for edit. + # Don't call checkSpecialsAndNSPermissions, checkSiteConfigPermissions + # or checkUserConfigPermissions here as it will lead to duplicate + # error messages. This is okay to do since anywhere that checks for + # create will also check for edit, and those checks are called for edit. } elseif ( $action == 'create' ) { $checks = [ 'checkQuickPermissions', @@ -2719,6 +2793,7 @@ class Title implements LinkTarget { 'checkQuickPermissions', 'checkPermissionHooks', 'checkSpecialsAndNSPermissions', + 'checkSiteConfigPermissions', 'checkUserConfigPermissions', 'checkPageRestrictions', 'checkCascadingSourcesRestrictions', diff --git a/includes/installer/Installer.php b/includes/installer/Installer.php index 3905ba02ee..0977dfbd50 100644 --- a/includes/installer/Installer.php +++ b/includes/installer/Installer.php @@ -1606,7 +1606,7 @@ abstract class Installer { } /** - * Create the first user account, grant it sysop and bureaucrat rights + * Create the first user account, grant it sysop, bureaucrat and interface-admin rights * * @return Status */ @@ -1630,6 +1630,7 @@ abstract class Installer { $user->addGroup( 'sysop' ); $user->addGroup( 'bureaucrat' ); + $user->addGroup( 'interface-admin' ); if ( $this->getVar( '_AdminEmail' ) ) { $user->setEmail( $this->getVar( '_AdminEmail' ) ); } diff --git a/includes/installer/WebInstallerName.php b/includes/installer/WebInstallerName.php index 50c88aec67..09fc0f37c7 100644 --- a/includes/installer/WebInstallerName.php +++ b/includes/installer/WebInstallerName.php @@ -223,7 +223,7 @@ class WebInstallerName extends WebInstallerPage { $status = $upp->checkUserPasswordForGroups( $user, $pwd, - [ 'bureaucrat', 'sysop' ] // per Installer::createSysop() + [ 'bureaucrat', 'sysop', 'interface-admin' ] // per Installer::createSysop() ); $valid = $status->isGood() ? true : $status->getMessage(); } else { diff --git a/includes/user/User.php b/includes/user/User.php index 2ba01ff67d..8ebd2d27cc 100644 --- a/includes/user/User.php +++ b/includes/user/User.php @@ -147,6 +147,9 @@ class User implements IDBAccessObject, UserIdentity { 'editmyuserjs', 'editmywatchlist', 'editsemiprotected', + 'editsitecss', + 'editsitejson', + 'editsitejs', 'editusercss', 'edituserjson', 'edituserjs', diff --git a/languages/i18n/en.json b/languages/i18n/en.json index bc35c9e02c..75566326e5 100644 --- a/languages/i18n/en.json +++ b/languages/i18n/en.json @@ -365,6 +365,9 @@ "customcssprotected": "You do not have permission to edit this CSS page because it contains another user's personal settings.", "customjsonprotected": "You do not have permission to edit this JSON page because it contains another user's personal settings.", "customjsprotected": "You do not have permission to edit this JavaScript page because it contains another user's personal settings.", + "sitecssprotected": "You do not have permission to edit this CSS page because it may affect all visitors", + "sitejsonprotected": "You do not have permission to edit this JSON page because it may affect all visitors", + "sitejsprotected": "You do not have permission to edit this JavaScript page because it may affect all visitors", "mycustomcssprotected": "You do not have permission to edit this CSS page.", "mycustomjsonprotected": "You do not have permission to edit this JSON page.", "mycustomjsprotected": "You do not have permission to edit this JavaScript page.", @@ -1178,6 +1181,7 @@ "group-autoconfirmed": "Autoconfirmed users", "group-bot": "Bots", "group-sysop": "Administrators", + "group-interface-admin": "Interface administrators", "group-bureaucrat": "Bureaucrats", "group-suppress": "Suppressors", "group-all": "(all)", @@ -1185,12 +1189,14 @@ "group-autoconfirmed-member": "{{GENDER:$1|autoconfirmed user}}", "group-bot-member": "{{GENDER:$1|bot}}", "group-sysop-member": "{{GENDER:$1|administrator}}", + "group-interface-admin-member": "{{GENDER:$1|interface administrator}}", "group-bureaucrat-member": "{{GENDER:$1|bureaucrat}}", "group-suppress-member": "{{GENDER:$1|suppressor}}", "grouppage-user": "{{ns:project}}:Users", "grouppage-autoconfirmed": "{{ns:project}}:Autoconfirmed users", "grouppage-bot": "{{ns:project}}:Bots", "grouppage-sysop": "{{ns:project}}:Administrators", + "grouppage-interface-admin": "{{ns:project}}:Interface administrators", "grouppage-bureaucrat": "{{ns:project}}:Bureaucrats", "grouppage-suppress": "{{ns:project}}:Suppress", "right-read": "Read pages", @@ -1241,6 +1247,9 @@ "right-editusercss": "Edit other users' CSS files", "right-edituserjson": "Edit other users' JSON files", "right-edituserjs": "Edit other users' JavaScript files", + "right-editsitecss": "Edit sitewide CSS", + "right-editsitejson": "Edit sitewide JSON", + "right-editsitejs": "Edit sitewide JavaScript", "right-editmyusercss": "Edit your own user CSS files", "right-editmyuserjson": "Edit your own user JSON files", "right-editmyuserjs": "Edit your own user JavaScript files", @@ -1282,10 +1291,11 @@ "grant-createaccount": "Create accounts", "grant-createeditmovepage": "Create, edit, and move pages", "grant-delete": "Delete pages, revisions, and log entries", - "grant-editinterface": "Edit the MediaWiki namespace and user CSS/JSON/JavaScript", + "grant-editinterface": "Edit the MediaWiki namespace and sitewide/user JSON", "grant-editmycssjs": "Edit your user CSS/JSON/JavaScript", "grant-editmyoptions": "Edit your user preferences", "grant-editmywatchlist": "Edit your watchlist", + "grant-editsiteconfig": "Edit sitewide and user CSS/JS", "grant-editpage": "Edit existing pages", "grant-editprotected": "Edit protected pages", "grant-highvolume": "High-volume editing", diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json index 573c0290fd..51f843baef 100644 --- a/languages/i18n/qqq.json +++ b/languages/i18n/qqq.json @@ -565,6 +565,9 @@ "customcssprotected": "Used as error message. Parameters:\n* $1 - (Unused) the action the user attempted to perform", "customjsonprotected": "Used as error message. Parameters:\n* $1 - (Unused) the action the user attempted to perform", "customjsprotected": "Used as error message. Parameters:\n* $1 - (Unused) the action the user attempted to perform", + "sitecssprotected": "Used as error message. Parameters:\n* $1 - (Unused) the action the user attempted to perform", + "sitejsonprotected": "Used as error message. Parameters:\n* $1 - (Unused) the action the user attempted to perform", + "sitejsprotected": "Used as error message. Parameters:\n* $1 - (Unused) the action the user attempted to perform", "mycustomcssprotected": "Used as error message. Parameters:\n* $1 - (Unused) the action the user attempted to perform", "mycustomjsonprotected": "Used as error message. Parameters:\n* $1 - (Unused) the action the user attempted to perform", "mycustomjsprotected": "Used as error message. Parameters:\n* $1 - (Unused) the action the user attempted to perform", @@ -1378,6 +1381,7 @@ "group-autoconfirmed": "{{doc-group|autoconfirmed}}\nOn Wikimedia sites autoconfirmed users are users which are older than 4 days. After those 4 days, they have more rights.", "group-bot": "{{doc-group|bot}}\n{{Identical|Bot}}", "group-sysop": "{{doc-group|sysop}}\n{{Identical|Administrator}}", + "group-interface-admin": "{{doc-group|interface-admin}}", "group-bureaucrat": "{{doc-group|bureaucrat}}", "group-suppress": "{{doc-group|suppress}}\nThis is an optional (disabled by default) user group, meant for the suppression feature in [[mw:Flow|Flow]]. It is not to be confused with the Oversighters group, which also has access to the [[mw:RevisionDelete|RevisionDelete]] feature, to change the visibility of revisions through [[Special:RevisionDelete]].\n\n{{Identical|Suppress}}", "group-all": "The name of the user group that contains all users, including anonymous users\n\n{{Identical|All}}", @@ -1385,12 +1389,14 @@ "group-autoconfirmed-member": "{{doc-group|autoconfirmed|member}}", "group-bot-member": "{{doc-group|bot|member}}", "group-sysop-member": "{{doc-group|sysop|member}}\n{{Identical|Administrator}}", + "group-interface-admin-member": "{{doc-group|interface-admin|member}}", "group-bureaucrat-member": "{{doc-group|bureaucrat|member}}", "group-suppress-member": "{{doc-group|suppress|member}}\nThis is a member of the optional (disabled by default) user group, meant for the [[mw:RevisionDelete|RevisionDelete]] feature, to change the visibility of revisions through [[Special:RevisionDelete]].\n\n{{Identical|Suppress}}", "grouppage-user": "{{doc-group|user|page}}\n{{Identical|User}}", "grouppage-autoconfirmed": "{{doc-group|autoconfirmed|page}}", "grouppage-bot": "{{doc-group|bot|page}}\n{{Identical|Bot}}", "grouppage-sysop": "{{doc-group|sysop|page}}", + "grouppage-interface-admin": "{{doc-group|interface-admin|page}}", "grouppage-bureaucrat": "{{doc-group|bureaucrat|page}}", "grouppage-suppress": "{{doc-group|suppress|page}}\n{{Identical|Suppress}}", "right-read": "{{doc-right|read}}\nBasic right to read any page.", @@ -1441,6 +1447,9 @@ "right-editusercss": "{{doc-right|editusercss}}\nSee also:\n* {{msg-mw|Right-editmyusercss}}", "right-edituserjson": "{{doc-right|edituserjson}}\nSee also:\n* {{msg-mw|Right-editmyuserjson}}", "right-edituserjs": "{{doc-right|edituserjs}}\nSee also:\n* {{msg-mw|Right-editmyuserjs}}", + "right-editsitecss": "{{doc-right|editsitecss}}", + "right-editsitejson": "{{doc-right|editsitejson}}", + "right-editsitejs": "{{doc-right|editsitejs}}", "right-editmyusercss": "{{doc-right|editmyusercss}}\nSee also:\n* {{msg-mw|Right-editusercss}}", "right-editmyuserjson": "{{doc-right|editmyuserjson}}\nSee also:\n* {{msg-mw|Right-edituserjson}}", "right-editmyuserjs": "{{doc-right|editmyuserjs}}\nSee also:\n* {{msg-mw|Right-edituserjs}}", @@ -1468,7 +1477,7 @@ "right-applychangetags": "{{doc-right|applychangetags}}", "right-changetags": "{{doc-right|changetags}}", "right-deletechangetags": "{{doc-right|deletechangetags}}", - "grant-generic": "Used if the grant name is not defined. Parameters:\n* $1 - grant name\n\nDefined grants (grant name refers: blockusers, createeditmovepage, ...):\n* {{msg-mw|grant-checkuser}}\n* {{msg-mw|grant-blockusers}}\n* {{msg-mw|grant-createaccount}}\n* {{msg-mw|grant-createeditmovepage}}\n* {{msg-mw|grant-delete}}\n* {{msg-mw|grant-editinterface}}\n* {{msg-mw|grant-editmycssjs}}\n* {{msg-mw|grant-editmyoptions}}\n* {{msg-mw|grant-editmywatchlist}}\n* {{msg-mw|grant-editpage}}\n* {{msg-mw|grant-editprotected}}\n* {{msg-mw|grant-highvolume}}\n* {{msg-mw|grant-oversight}}\n* {{msg-mw|grant-patrol}}\n* {{msg-mw|grant-privateinfo}}\n* {{msg-mw|grant-protect}}\n* {{msg-mw|grant-rollback}}\n* {{msg-mw|grant-sendemail}}\n* {{msg-mw|grant-uploadeditmovefile}}\n* {{msg-mw|grant-uploadfile}}\n* {{msg-mw|grant-basic}}\n* {{msg-mw|grant-viewdeleted}}\n* {{msg-mw|grant-viewmywatchlist}}", + "grant-generic": "Used if the grant name is not defined. Parameters:\n* $1 - grant name\n\nDefined grants (grant name refers: blockusers, createeditmovepage, ...):\n* {{msg-mw|grant-checkuser}}\n* {{msg-mw|grant-blockusers}}\n* {{msg-mw|grant-createaccount}}\n* {{msg-mw|grant-createeditmovepage}}\n* {{msg-mw|grant-delete}}\n* {{msg-mw|grant-editinterface}}\n* {{msg-mw|grant-editmycssjs}}\n* {{msg-mw|grant-editmyoptions}}\n* {{msg-mw|grant-editmywatchlist}}\n* {{msg-mw|grant-editsiteconfig}}\n* {{msg-mw|grant-editpage}}\n* {{msg-mw|grant-editprotected}}\n* {{msg-mw|grant-highvolume}}\n* {{msg-mw|grant-oversight}}\n* {{msg-mw|grant-patrol}}\n* {{msg-mw|grant-privateinfo}}\n* {{msg-mw|grant-protect}}\n* {{msg-mw|grant-rollback}}\n* {{msg-mw|grant-sendemail}}\n* {{msg-mw|grant-uploadeditmovefile}}\n* {{msg-mw|grant-uploadfile}}\n* {{msg-mw|grant-basic}}\n* {{msg-mw|grant-viewdeleted}}\n* {{msg-mw|grant-viewmywatchlist}}", "grant-group-page-interaction": "{{Related|Grant-group}}", "grant-group-file-interaction": "{{Related|Grant-group}}", "grant-group-watchlist-interaction": "{{Related|Grant-group}}", @@ -1482,10 +1491,11 @@ "grant-createaccount": "Name for grant \"createaccount\".\n{{Related|Grant}}\n{{Identical|Create account}}", "grant-createeditmovepage": "Name for grant \"createeditmovepage\".\n{{Related|Grant}}", "grant-delete": "Name for grant \"delete\".\n{{Related|Grant}}", - "grant-editinterface": "Name for grant \"editinterface\".\n\n\"JS\" stands for \"JavaScript\".\n{{Related|Grant}}", + "grant-editinterface": "Name for grant \"editinterface\".\n{{Related|Grant}}", "grant-editmycssjs": "Name for grant \"editmycssjs\".\n\n\"JS\" stands for \"JavaScript\".\n{{Related|Grant}}", "grant-editmyoptions": "Name for grant \"editmyoptions\".\n{{Related|Grant}}", "grant-editmywatchlist": "Name for grant \"editmywatchlist\".\n{{Related|Grant}}\n{{Identical|Edit your watchlist}}", + "grant-editsiteconfig": "Name for grant \"editsiteconfig\".\n{{Related|Grant}}", "grant-editpage": "Name for grant \"editpage\".\n{{Related|Grant}}", "grant-editprotected": "Name for grant \"editprotected\".\n{{Related|Grant}}", "grant-highvolume": "Name for grant \"highvolume\".\n{{Related|Grant}}", diff --git a/maintenance/createAndPromote.php b/maintenance/createAndPromote.php index d3efca6f42..2072d7b54d 100644 --- a/maintenance/createAndPromote.php +++ b/maintenance/createAndPromote.php @@ -31,7 +31,7 @@ require_once __DIR__ . '/Maintenance.php'; * @ingroup Maintenance */ class CreateAndPromote extends Maintenance { - private static $permitRoles = [ 'sysop', 'bureaucrat', 'bot' ]; + private static $permitRoles = [ 'sysop', 'bureaucrat', 'interface-admin', 'bot' ]; public function __construct() { parent::__construct(); -- 2.20.1