From 08adf46b262d05da6e0082241abd42dd8da68a82 Mon Sep 17 00:00:00 2001 From: Brad Jorsch Date: Mon, 10 Jun 2013 15:30:43 -0400 Subject: [PATCH] Add 'viewmyprivateinfo', 'editmyprivateinfo', and 'editmyoptions' rights These are needed for OAuth grants. Note that we don't bother with a 'viewmyoptions' right, since the majority will be determinable from just observing the interface. Note that the fact of having a confirmed email address cannot be reliably hidden, and if the user has 'sendemail' they may be able to determine the real name and email address by sending an email to another account that they control. Change-Id: I3f03dd010020e8d43cc2d3bca7b3ef7196d1c548 --- RELEASE-NOTES-1.22 | 10 +- includes/DefaultSettings.php | 3 + includes/Metadata.php | 4 +- includes/Preferences.php | 129 +++++++++++------- includes/User.php | 3 + includes/api/ApiOptions.php | 4 + includes/api/ApiQueryUserInfo.php | 15 +- includes/specials/SpecialChangeEmail.php | 9 +- includes/specials/SpecialChangePassword.php | 3 +- includes/specials/SpecialConfirmemail.php | 12 +- includes/specials/SpecialPasswordReset.php | 2 +- includes/specials/SpecialPreferences.php | 8 ++ includes/specials/SpecialWatchlist.php | 8 +- languages/messages/MessagesEn.php | 7 + languages/messages/MessagesQqq.php | 7 + maintenance/dictionary/mediawiki.dic | 3 + maintenance/language/messages.inc | 7 + tests/phpunit/includes/UserTest.php | 5 + tests/phpunit/includes/api/ApiOptionsTest.php | 32 +++-- 19 files changed, 188 insertions(+), 83 deletions(-) diff --git a/RELEASE-NOTES-1.22 b/RELEASE-NOTES-1.22 index 8bf680b276..6008800a76 100644 --- a/RELEASE-NOTES-1.22 +++ b/RELEASE-NOTES-1.22 @@ -29,8 +29,9 @@ production. default for $wgLogAutopatrol is true. * The 'edit' right no longer allows for editing a user's own CSS and JS. * New rights 'editmyusercss', 'editmyuserjs', 'viewmywatchlist', - and 'editmywatchlist' restrict actions that were formerly allowed by default. - They have been added to the default for $wgGroupPermissions['*']. + 'editmywatchlist', 'viewmyprivateinfo', 'editmyprivateinfo', and + 'editmyoptions' restrict actions that were formerly allowed by default. They + have been added to the default for $wgGroupPermissions['*']. === New features in 1.22 === * (bug 44525) mediawiki.jqueryMsg can now parse (whitelisted) HTML elements and attributes. @@ -112,6 +113,11 @@ production. ** editmyuserjs controls whether a user may edit their own JS subpages. ** viewmywatchlist controls whether a user may view their watchlist. ** editmywatchlist controls whether a user may edit their watchlist. +** viewmyprivateinfo controls whether a user may access their private + information (e.g. registered email address, real name). +** editmyprivateinfo controls whether a user may change their private + information. +** editmyoptions controls whether a user may change their preferences. * Add new hook AbortTalkPageEmailNotification, this will be used to determine whether to send the regular talk page email notification * (bug 46513) Vector: Add the collapsibleTabs script from the Vector extension. diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 31659f3295..f30a8b6d5d 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -3875,6 +3875,9 @@ $wgGroupPermissions['*']['editmyusercss'] = true; $wgGroupPermissions['*']['editmyuserjs'] = true; $wgGroupPermissions['*']['viewmywatchlist'] = true; $wgGroupPermissions['*']['editmywatchlist'] = true; +$wgGroupPermissions['*']['viewmyprivateinfo'] = true; +$wgGroupPermissions['*']['editmyprivateinfo'] = true; +$wgGroupPermissions['*']['editmyoptions'] = true; #$wgGroupPermissions['*']['patrolmarks'] = false; // let anons see what was patrolled // Implicit group for all logged-in accounts diff --git a/includes/Metadata.php b/includes/Metadata.php index cd77ef8779..37df489c9d 100644 --- a/includes/Metadata.php +++ b/includes/Metadata.php @@ -116,11 +116,13 @@ abstract class RdfMetaData { } protected function person( $name, User $user ) { + global $wgHiddenPrefs; + if ( $user->isAnon() ) { $this->element( $name, wfMessage( 'anonymous' )->numParams( 1 )->text() ); } else { $real = $user->getRealName(); - if ( $real ) { + if ( $real && !in_array( 'realname', $wgHiddenPrefs ) ) { $this->element( $name, $real ); } else { $userName = $user->getName(); diff --git a/includes/Preferences.php b/includes/Preferences.php index 8f784bb507..6b5b5ebaa9 100644 --- a/includes/Preferences.php +++ b/includes/Preferences.php @@ -56,6 +56,12 @@ class Preferences { 'searchlimit' => array( 'Preferences', 'filterIntval' ), ); + // Stuff that shouldn't be saved as a preference. + private static $saveBlacklist = array( + 'realname', + 'emailaddress', + ); + /** * @throws MWException * @param $user User @@ -93,9 +99,14 @@ class Preferences { ## Make sure that form fields have their parent set. See bug 41337. $dummyForm = new HTMLForm( array(), $context ); + $disable = !$user->isAllowed( 'editmyoptions' ); + ## Prod in defaults from the user foreach ( $defaultPreferences as $name => &$info ) { $prefFromUser = self::getOptionFromUser( $name, $info, $user ); + if ( $disable && !in_array( $name, self::$saveBlacklist ) ) { + $info['disabled'] = 'disabled'; + } $field = HTMLForm::loadInputFromParameters( $name, $info ); // For validation $field->mParent = $dummyForm; $defaultOptions = User::getDefaultOptions(); @@ -256,9 +267,13 @@ class Preferences { ); } + $canViewPrivateInfo = $user->isAllowed( 'viewmyprivateinfo' ); + $canEditPrivateInfo = $user->isAllowed( 'editmyprivateinfo' ); + // Actually changeable stuff $defaultPreferences['realname'] = array( - 'type' => $wgAuth->allowPropChange( 'realname' ) ? 'text' : 'info', + // (not really "private", but still shouldn't be edited without permission) + 'type' => $canEditPrivateInfo && $wgAuth->allowPropChange( 'realname' ) ? 'text' : 'info', 'default' => $user->getRealName(), 'section' => 'personal/info', 'label-message' => 'yourrealname', @@ -277,7 +292,7 @@ class Preferences { 'help-message' => 'prefs-help-gender', ); - if ( $wgAuth->allowPasswordChange() ) { + if ( $canEditPrivateInfo && $wgAuth->allowPasswordChange() ) { $link = Linker::link( SpecialPage::getTitleFor( 'ChangePassword' ), $context->msg( 'prefs-resetpass' )->escaped(), array(), array( 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ) ); @@ -398,22 +413,24 @@ class Preferences { array( 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ) ); $emailAddress = $user->getEmail() ? htmlspecialchars( $user->getEmail() ) : ''; - if ( $wgAuth->allowPropChange( 'emailaddress' ) ) { + if ( $canEditPrivateInfo && $wgAuth->allowPropChange( 'emailaddress' ) ) { $emailAddress .= $emailAddress == '' ? $link : ( $context->msg( 'word-separator' )->plain() . $context->msg( 'parentheses' )->rawParams( $link )->plain() ); } - $defaultPreferences['emailaddress'] = array( - 'type' => 'info', - 'raw' => true, - 'default' => $emailAddress, - 'label-message' => 'youremail', - 'section' => 'personal/email', - 'help-messages' => $helpMessages, - # 'cssclass' chosen below - ); + if ( $canViewPrivateInfo ) { + $defaultPreferences['emailaddress'] = array( + 'type' => 'info', + 'raw' => true, + 'default' => $emailAddress, + 'label-message' => 'youremail', + 'section' => 'personal/email', + 'help-messages' => $helpMessages, + # 'cssclass' chosen below + ); + } $disableEmailPrefs = false; @@ -448,16 +465,18 @@ class Preferences { $emailauthenticationclass = 'mw-email-none'; } - $defaultPreferences['emailauthentication'] = array( - 'type' => 'info', - 'raw' => true, - 'section' => 'personal/email', - 'label-message' => 'prefs-emailconfirm-label', - 'default' => $emailauthenticated, - # Apply the same CSS class used on the input to the message: - 'cssclass' => $emailauthenticationclass, - ); - $defaultPreferences['emailaddress']['cssclass'] = $emailauthenticationclass; + if ( $canViewPrivateInfo ) { + $defaultPreferences['emailauthentication'] = array( + 'type' => 'info', + 'raw' => true, + 'section' => 'personal/email', + 'label-message' => 'prefs-emailconfirm-label', + 'default' => $emailauthenticated, + # Apply the same CSS class used on the input to the message: + 'cssclass' => $emailauthenticationclass, + ); + $defaultPreferences['emailaddress']['cssclass'] = $emailauthenticationclass; + } } if ( $wgEnableUserEmail && $user->isAllowed( 'sendemail' ) ) { @@ -1385,6 +1404,10 @@ class Preferences { $user = $form->getModifiedUser(); $result = true; + if ( !$user->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' ) ) { + return Status::newFatal( 'mypreferencesprotected' ); + } + // Filter input foreach ( array_keys( $formData ) as $name ) { if ( isset( self::$saveFilters[$name] ) ) { @@ -1393,40 +1416,37 @@ class Preferences { } } - // Stuff that shouldn't be saved as a preference. - $saveBlacklist = array( - 'realname', - 'emailaddress', - ); - // Fortunately, the realname field is MUCH simpler - if ( !in_array( 'realname', $wgHiddenPrefs ) ) { + // (not really "private", but still shouldn't be edited without permission) + if ( !in_array( 'realname', $wgHiddenPrefs ) && $user->isAllowed( 'editmyprivateinfo' ) ) { $realName = $formData['realname']; $user->setRealName( $realName ); } - foreach ( $saveBlacklist as $b ) { - unset( $formData[$b] ); - } + if ( $user->isAllowed( 'editmyoptions' ) ) { + foreach ( self::$saveBlacklist as $b ) { + unset( $formData[$b] ); + } - # If users have saved a value for a preference which has subsequently been disabled - # via $wgHiddenPrefs, we don't want to destroy that setting in case the preference - # is subsequently re-enabled - # TODO: maintenance script to actually delete these - foreach ( $wgHiddenPrefs as $pref ) { - # If the user has not set a non-default value here, the default will be returned - # and subsequently discarded - $formData[$pref] = $user->getOption( $pref, null, true ); - } + # If users have saved a value for a preference which has subsequently been disabled + # via $wgHiddenPrefs, we don't want to destroy that setting in case the preference + # is subsequently re-enabled + # TODO: maintenance script to actually delete these + foreach ( $wgHiddenPrefs as $pref ) { + # If the user has not set a non-default value here, the default will be returned + # and subsequently discarded + $formData[$pref] = $user->getOption( $pref, null, true ); + } - // Keep old preferences from interfering due to back-compat code, etc. - $user->resetOptions( 'unused', $form->getContext() ); + // Keep old preferences from interfering due to back-compat code, etc. + $user->resetOptions( 'unused', $form->getContext() ); - foreach ( $formData as $key => $value ) { - $user->setOption( $key, $value ); - } + foreach ( $formData as $key => $value ) { + $user->setOption( $key, $value ); + } - $user->saveSettings(); + $user->saveSettings(); + } $wgAuth->updateExternalDB( $user ); @@ -1461,7 +1481,8 @@ class Preferences { /** * Try to set a user's email address. * This does *not* try to validate the address. - * Caller is responsible for checking $wgAuth. + * Caller is responsible for checking $wgAuth and 'editmyprivateinfo' + * right. * * @deprecated in 1.20; use User::setEmailWithConfirmation() instead. * @param $user User @@ -1550,13 +1571,19 @@ class PreferencesForm extends HTMLForm { * @return String */ function getButtons() { + if ( !$this->getModifiedUser()->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' ) ) { + return ''; + } + $html = parent::getButtons(); - $t = SpecialPage::getTitleFor( 'Preferences', 'reset' ); + if ( $this->getModifiedUser()->isAllowed( 'editmyoptions' ) ) { + $t = SpecialPage::getTitleFor( 'Preferences', 'reset' ); - $html .= "\n" . Linker::link( $t, $this->msg( 'restoreprefs' )->escaped() ); + $html .= "\n" . Linker::link( $t, $this->msg( 'restoreprefs' )->escaped() ); - $html = Xml::tags( 'div', array( 'class' => 'mw-prefs-buttons' ), $html ); + $html = Xml::tags( 'div', array( 'class' => 'mw-prefs-buttons' ), $html ); + } return $html; } diff --git a/includes/User.php b/includes/User.php index 685fe9613f..3ee32c3ddf 100644 --- a/includes/User.php +++ b/includes/User.php @@ -124,6 +124,8 @@ class User { 'edit', 'editinterface', 'editprotected', + 'editmyoptions', + 'editmyprivateinfo', 'editmyusercss', 'editmyuserjs', 'editmywatchlist', @@ -167,6 +169,7 @@ class User { 'upload_by_url', 'userrights', 'userrights-interwiki', + 'viewmyprivateinfo', 'viewmywatchlist', 'writeapi', ); diff --git a/includes/api/ApiOptions.php b/includes/api/ApiOptions.php index 8c996a267e..720025f8dc 100644 --- a/includes/api/ApiOptions.php +++ b/includes/api/ApiOptions.php @@ -42,6 +42,10 @@ class ApiOptions extends ApiBase { $this->dieUsage( 'Anonymous users cannot change preferences', 'notloggedin' ); } + if ( !$user->isAllowed( 'editmyoptions' ) ) { + $this->dieUsage( 'You don\'t have permission to edit your options', 'permissiondenied' ); + } + $params = $this->extractRequestParams(); $changed = false; diff --git a/includes/api/ApiQueryUserInfo.php b/includes/api/ApiQueryUserInfo.php index 8e65b407bf..2b7e7cc962 100644 --- a/includes/api/ApiQueryUserInfo.php +++ b/includes/api/ApiQueryUserInfo.php @@ -104,7 +104,8 @@ class ApiQueryUserInfo extends ApiQueryBase { } if ( isset( $this->prop['preferencestoken'] ) && - is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) + is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) && + $user->isAllowed( 'editmyoptions' ) ) { $vals['preferencestoken'] = $user->getEditToken( '', $this->getMain()->getRequest() ); } @@ -121,11 +122,13 @@ class ApiQueryUserInfo extends ApiQueryBase { $vals['realname'] = $user->getRealName(); } - if ( isset( $this->prop['email'] ) ) { - $vals['email'] = $user->getEmail(); - $auth = $user->getEmailAuthenticationTimestamp(); - if ( !is_null( $auth ) ) { - $vals['emailauthenticated'] = wfTimestamp( TS_ISO_8601, $auth ); + if ( $user->isAllowed( 'viewmyprivateinfo' ) ) { + if ( isset( $this->prop['email'] ) ) { + $vals['email'] = $user->getEmail(); + $auth = $user->getEmailAuthenticationTimestamp(); + if ( !is_null( $auth ) ) { + $vals['emailauthenticated'] = wfTimestamp( TS_ISO_8601, $auth ); + } } } diff --git a/includes/specials/SpecialChangeEmail.php b/includes/specials/SpecialChangeEmail.php index 9e435fb374..d8a01cd1de 100644 --- a/includes/specials/SpecialChangeEmail.php +++ b/includes/specials/SpecialChangeEmail.php @@ -41,7 +41,7 @@ class SpecialChangeEmail extends UnlistedSpecialPage { protected $mNewEmail; public function __construct() { - parent::__construct( 'ChangeEmail' ); + parent::__construct( 'ChangeEmail', 'editmyprivateinfo' ); } /** @@ -88,6 +88,13 @@ class SpecialChangeEmail extends UnlistedSpecialPage { } $this->checkReadOnly(); + $this->checkPermissions(); + + // This could also let someone check the current email address, so + // require both permissions. + if ( !$this->getUser()->isAllowed( 'viewmyprivateinfo' ) ) { + throw new PerissionsError( 'viewmyprivateinfo' ); + } $this->mPassword = $request->getVal( 'wpPassword' ); $this->mNewEmail = $request->getVal( 'wpNewEmail' ); diff --git a/includes/specials/SpecialChangePassword.php b/includes/specials/SpecialChangePassword.php index b53a46a0f5..d297a2fc2c 100644 --- a/includes/specials/SpecialChangePassword.php +++ b/includes/specials/SpecialChangePassword.php @@ -31,7 +31,7 @@ class SpecialChangePassword extends UnlistedSpecialPage { protected $mUserName, $mOldpass, $mNewpass, $mRetype, $mDomain; public function __construct() { - parent::__construct( 'ChangePassword' ); + parent::__construct( 'ChangePassword', 'editmyprivateinfo' ); } /** @@ -65,6 +65,7 @@ class SpecialChangePassword extends UnlistedSpecialPage { } $this->checkReadOnly(); + $this->checkPermissions(); if ( $request->wasPosted() && $user->matchEditToken( $request->getVal( 'token' ) ) ) { try { diff --git a/includes/specials/SpecialConfirmemail.php b/includes/specials/SpecialConfirmemail.php index 47808d1112..c34b96806a 100644 --- a/includes/specials/SpecialConfirmemail.php +++ b/includes/specials/SpecialConfirmemail.php @@ -31,7 +31,7 @@ */ class EmailConfirmation extends UnlistedSpecialPage { public function __construct() { - parent::__construct( 'Confirmemail' ); + parent::__construct( 'Confirmemail', 'editmyprivateinfo' ); } /** @@ -43,6 +43,13 @@ class EmailConfirmation extends UnlistedSpecialPage { $this->setHeaders(); $this->checkReadOnly(); + $this->checkPermissions(); + + // This could also let someone check the current email address, so + // require both permissions. + if ( !$this->getUser()->isAllowed( 'viewmyprivateinfo' ) ) { + throw new PerissionsError( 'viewmyprivateinfo' ); + } if ( $code === null || $code === '' ) { if ( $this->getUser()->isLoggedIn() ) { @@ -149,12 +156,13 @@ class EmailConfirmation extends UnlistedSpecialPage { */ class EmailInvalidation extends UnlistedSpecialPage { public function __construct() { - parent::__construct( 'Invalidateemail' ); + parent::__construct( 'Invalidateemail', 'editmyprivateinfo' ); } function execute( $code ) { $this->setHeaders(); $this->checkReadOnly(); + $this->checkPermissions(); $this->attemptInvalidate( $code ); } diff --git a/includes/specials/SpecialPasswordReset.php b/includes/specials/SpecialPasswordReset.php index 3b675544a3..69c40569d0 100644 --- a/includes/specials/SpecialPasswordReset.php +++ b/includes/specials/SpecialPasswordReset.php @@ -43,7 +43,7 @@ class SpecialPasswordReset extends FormSpecialPage { private $result; public function __construct() { - parent::__construct( 'PasswordReset' ); + parent::__construct( 'PasswordReset', 'editmyprivateinfo' ); } public function userCanExecute( User $user ) { diff --git a/includes/specials/SpecialPreferences.php b/includes/specials/SpecialPreferences.php index bb6bc959a3..fe91adace3 100644 --- a/includes/specials/SpecialPreferences.php +++ b/includes/specials/SpecialPreferences.php @@ -69,6 +69,10 @@ class SpecialPreferences extends SpecialPage { } private function showResetForm() { + if ( !$this->getUser()->isAllowed( 'editmyoptions' ) ) { + throw new PermissionsError( 'editmyoptions' ); + } + $this->getOutput()->addWikiMsg( 'prefs-reset-intro' ); $htmlForm = new HTMLForm( array(), $this->getContext(), 'prefs-restore' ); @@ -82,6 +86,10 @@ class SpecialPreferences extends SpecialPage { } public function submitReset( $formData ) { + if ( !$this->getUser()->isAllowed( 'editmyoptions' ) ) { + throw new PermissionsError( 'editmyoptions' ); + } + $user = $this->getUser(); $user->resetOptions( 'all' ); $user->saveSettings(); diff --git a/includes/specials/SpecialWatchlist.php b/includes/specials/SpecialWatchlist.php index d61e1ee767..b4bea60301 100644 --- a/includes/specials/SpecialWatchlist.php +++ b/includes/specials/SpecialWatchlist.php @@ -59,14 +59,16 @@ class SpecialWatchlist extends SpecialPage { // Add feed links $wlToken = $user->getOption( 'watchlisttoken' ); - if ( !$wlToken ) { + if ( !$wlToken && $user->isAllowed( 'editmyoptions' ) ) { $wlToken = MWCryptRand::generateHex( 40 ); $user->setOption( 'watchlisttoken', $wlToken ); $user->saveSettings(); } - $this->addFeedLinks( array( 'action' => 'feedwatchlist', 'allrev' => 'allrev', - 'wlowner' => $user->getName(), 'wltoken' => $wlToken ) ); + if ( $wlToken ) { + $this->addFeedLinks( array( 'action' => 'feedwatchlist', 'allrev' => 'allrev', + 'wlowner' => $user->getName(), 'wltoken' => $wlToken ) ); + } $this->setHeaders(); $this->outputHeader(); diff --git a/languages/messages/MessagesEn.php b/languages/messages/MessagesEn.php index 10fe5ea9b8..0fabd9293b 100644 --- a/languages/messages/MessagesEn.php +++ b/languages/messages/MessagesEn.php @@ -1070,6 +1070,8 @@ $2', 'customjsprotected' => "You do not have permission to edit this JavaScript page because it contains another user's personal settings.", 'mycustomcssprotected' => 'You do not have permission to edit this CSS page.', 'mycustomjsprotected' => 'You do not have permission to edit this JavaScript page.', +'myprivateinfoprotected' => 'You do not have permission to edit your private information.', +'mypreferencesprotected' => 'You do not have permission to edit your preferences.', 'ns-specialprotected' => 'Special pages cannot be edited.', 'titleprotected' => 'This title has been protected from creation by [[User:$1|$1]]. The reason given is "\'\'$2\'\'".', @@ -2096,6 +2098,9 @@ Your email address is not revealed when other users contact you.', 'right-editmyuserjs' => 'Edit your own user JavaScript files', 'right-viewmywatchlist' => 'View your own watchlist', 'right-editmywatchlist' => 'Edit your own watchlist. Note some actions will still add pages even without this right.', +'right-viewmyprivateinfo' => 'View your own private data (e.g. email address, real name)', +'right-editmyprivateinfo' => 'Edit your own private data (e.g. email address, real name)', +'right-editmyoptions' => 'Edit your own preferences', 'right-rollback' => 'Quickly rollback the edits of the last user who edited a particular page', 'right-markbotedits' => 'Mark rolled-back edits as bot edits', 'right-noratelimit' => 'Not be affected by rate limits', @@ -2159,6 +2164,8 @@ Your email address is not revealed when other users contact you.', 'action-sendemail' => 'send emails', 'action-viewmywatchlist' => 'view your watchlist', 'action-editmywatchlist' => 'edit your watchlist', +'action-viewmyprivateinfo' => 'view your private information', +'action-editmyprivateinfo' => 'edit your private information', # Recent changes 'nchanges' => '$1 {{PLURAL:$1|change|changes}}', diff --git a/languages/messages/MessagesQqq.php b/languages/messages/MessagesQqq.php index 827f20e938..84f6a6c15f 100644 --- a/languages/messages/MessagesQqq.php +++ b/languages/messages/MessagesQqq.php @@ -1029,6 +1029,8 @@ See also {{msg-mw|protectedinterface}}.', 'customjsprotected' => 'Used as error message.', 'mycustomcssprotected' => 'Used as error message.', 'mycustomjsprotected' => 'Used as error message.', +'myprivateinfoprotected' => 'Used as error message.', +'mypreferencesprotected' => 'Used as error message.', 'ns-specialprotected' => 'Error message displayed when trying to edit a page in the Special namespace', 'titleprotected' => 'Use $1 for GENDER.', 'filereadonlyerror' => 'Parameters: @@ -2938,6 +2940,9 @@ See also: * {{msg-mw|Right-edituserjs}}', 'right-viewmywatchlist' => '{{doc-right|viewmywatchlist}}', 'right-editmywatchlist' => '{{doc-right|editmywatchlist}}', +'right-viewmyprivateinfo' => '{{doc-right|viewmyprivateinfo}}', +'right-editmyprivateinfo' => '{{doc-right|editmyprivateinfo}}', +'right-editmyoptions' => '{{doc-right|editmyoptions}}', 'right-rollback' => '{{doc-right|rollback}} {{Identical|Rollback}}', 'right-markbotedits' => '{{doc-right|markbotedits}} @@ -3012,6 +3017,8 @@ This action allows editing of all of the "user rights", not just the rights of t 'action-sendemail' => '{{doc-action|sendemail}}', 'action-editmywatchlist' => '{{doc-action|editmywatchlist}}', 'action-viewmywatchlist' => '{{doc-action|viewmywatchlist}}', +'action-viewmyprivateinfo' => '{{doc-action|viewmyprivateinfo}}', +'action-editmyprivateinfo' => '{{doc-action|editmyprivateinfo}}', # Recent changes 'nchanges' => 'Appears on the [[Special:RecentChanges]] special page in brackets after pages having more than one change on that date. $1 is the number of changes on that day.', diff --git a/maintenance/dictionary/mediawiki.dic b/maintenance/dictionary/mediawiki.dic index f73dfc1dea..656dc33171 100644 --- a/maintenance/dictionary/mediawiki.dic +++ b/maintenance/dictionary/mediawiki.dic @@ -1284,6 +1284,8 @@ editinterface editintro edititis editlink +editmyoptions +editmyprivateinfo editmyusercss editmyuserjs editmywatchlist @@ -4343,6 +4345,7 @@ view viewcount viewdeleted viewhelppage +viewmyprivateinfo viewmywatchlist viewprevnext viewsource diff --git a/maintenance/language/messages.inc b/maintenance/language/messages.inc index 2712fae61b..00462d5e9e 100644 --- a/maintenance/language/messages.inc +++ b/maintenance/language/messages.inc @@ -425,6 +425,8 @@ $wgMessageStructure = array( 'customjsprotected', 'mycustomcssprotected', 'mycustomjsprotected', + 'myprivateinfoprotected', + 'mypreferencesprotected', 'ns-specialprotected', 'titleprotected', 'filereadonlyerror', @@ -1224,6 +1226,9 @@ $wgMessageStructure = array( 'right-editmyuserjs', 'right-viewmywatchlist', 'right-editmywatchlist', + 'right-viewmyprivateinfo', + 'right-editmyprivateinfo', + 'right-editmyoptions', 'right-rollback', 'right-markbotedits', 'right-noratelimit', @@ -1287,6 +1292,8 @@ $wgMessageStructure = array( 'action-sendemail', 'action-editmywatchlist', 'action-viewmywatchlist', + 'action-viewmyprivateinfo', + 'action-editmyprivateinfo', ), 'recentchanges' => array( 'nchanges', diff --git a/tests/phpunit/includes/UserTest.php b/tests/phpunit/includes/UserTest.php index e777179a24..0113cab8de 100644 --- a/tests/phpunit/includes/UserTest.php +++ b/tests/phpunit/includes/UserTest.php @@ -46,6 +46,11 @@ class UserTest extends MediaWikiTestCase { $wgRevokePermissions['formertesters'] = array( 'runtest' => true, ); + + # For the options test + $wgGroupPermissions['*'] = array( + 'editmyoptions' => true, + ); } public function testGroupPermissions() { diff --git a/tests/phpunit/includes/api/ApiOptionsTest.php b/tests/phpunit/includes/api/ApiOptionsTest.php index ae74e384a1..ad1e73ab4a 100644 --- a/tests/phpunit/includes/api/ApiOptionsTest.php +++ b/tests/phpunit/includes/api/ApiOptionsTest.php @@ -20,9 +20,11 @@ class ApiOptionsTest extends MediaWikiLangTestCase { ->disableOriginalConstructor() ->getMock(); - // Set up groups + // Set up groups and rights $this->mUserMock->expects( $this->any() ) ->method( 'getEffectiveGroups' )->will( $this->returnValue( array( '*', 'user' ) ) ); + $this->mUserMock->expects( $this->any() ) + ->method( 'isAllowed' )->will( $this->returnValue( true ) ); // Set up callback for User::getOptionKinds $this->mUserMock->expects( $this->any() ) @@ -280,21 +282,21 @@ class ApiOptionsTest extends MediaWikiLangTestCase { $this->mUserMock->expects( $this->at( 2 ) ) ->method( 'getOptions' ); - $this->mUserMock->expects( $this->at( 3 ) ) + $this->mUserMock->expects( $this->at( 4 ) ) ->method( 'setOption' ) ->with( $this->equalTo( 'willBeNull' ), $this->identicalTo( null ) ); - $this->mUserMock->expects( $this->at( 4 ) ) + $this->mUserMock->expects( $this->at( 5 ) ) ->method( 'getOptions' ); - $this->mUserMock->expects( $this->at( 5 ) ) + $this->mUserMock->expects( $this->at( 6 ) ) ->method( 'setOption' ) ->with( $this->equalTo( 'willBeEmpty' ), $this->equalTo( '' ) ); - $this->mUserMock->expects( $this->at( 6 ) ) + $this->mUserMock->expects( $this->at( 7 ) ) ->method( 'getOptions' ); - $this->mUserMock->expects( $this->at( 7 ) ) + $this->mUserMock->expects( $this->at( 8 ) ) ->method( 'setOption' ) ->with( $this->equalTo( 'willBeHappy' ), $this->equalTo( 'Happy' ) ); @@ -312,17 +314,17 @@ class ApiOptionsTest extends MediaWikiLangTestCase { $this->mUserMock->expects( $this->once() ) ->method( 'resetOptions' ); - $this->mUserMock->expects( $this->at( 3 ) ) + $this->mUserMock->expects( $this->at( 4 ) ) ->method( 'getOptions' ); - $this->mUserMock->expects( $this->at( 4 ) ) + $this->mUserMock->expects( $this->at( 5 ) ) ->method( 'setOption' ) ->with( $this->equalTo( 'willBeHappy' ), $this->equalTo( 'Happy' ) ); - $this->mUserMock->expects( $this->at( 5 ) ) + $this->mUserMock->expects( $this->at( 6 ) ) ->method( 'getOptions' ); - $this->mUserMock->expects( $this->at( 6 ) ) + $this->mUserMock->expects( $this->at( 7 ) ) ->method( 'setOption' ) ->with( $this->equalTo( 'name' ), $this->equalTo( 'value' ) ); @@ -345,19 +347,19 @@ class ApiOptionsTest extends MediaWikiLangTestCase { $this->mUserMock->expects( $this->never() ) ->method( 'resetOptions' ); - $this->mUserMock->expects( $this->at( 2 ) ) + $this->mUserMock->expects( $this->at( 3 ) ) ->method( 'setOption' ) ->with( $this->equalTo( 'testmultiselect-opt1' ), $this->identicalTo( true ) ); - $this->mUserMock->expects( $this->at( 3 ) ) + $this->mUserMock->expects( $this->at( 4 ) ) ->method( 'setOption' ) ->with( $this->equalTo( 'testmultiselect-opt2' ), $this->identicalTo( null ) ); - $this->mUserMock->expects( $this->at( 4 ) ) + $this->mUserMock->expects( $this->at( 5 ) ) ->method( 'setOption' ) ->with( $this->equalTo( 'testmultiselect-opt3' ), $this->identicalTo( false ) ); - $this->mUserMock->expects( $this->at( 5 ) ) + $this->mUserMock->expects( $this->at( 6 ) ) ->method( 'setOption' ) ->with( $this->equalTo( 'testmultiselect-opt4' ), $this->identicalTo( false ) ); @@ -400,7 +402,7 @@ class ApiOptionsTest extends MediaWikiLangTestCase { $this->mUserMock->expects( $this->never() ) ->method( 'resetOptions' ); - $this->mUserMock->expects( $this->at( 2 ) ) + $this->mUserMock->expects( $this->at( 3 ) ) ->method( 'setOption' ) ->with( $this->equalTo( 'userjs-option' ), $this->equalTo( '1' ) ); -- 2.20.1