Add 'viewmyprivateinfo', 'editmyprivateinfo', and 'editmyoptions' rights
authorBrad Jorsch <bjorsch@wikimedia.org>
Mon, 10 Jun 2013 19:30:43 +0000 (15:30 -0400)
committerBrad Jorsch <bjorsch@wikimedia.org>
Wed, 3 Jul 2013 17:40:04 +0000 (13:40 -0400)
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

19 files changed:
RELEASE-NOTES-1.22
includes/DefaultSettings.php
includes/Metadata.php
includes/Preferences.php
includes/User.php
includes/api/ApiOptions.php
includes/api/ApiQueryUserInfo.php
includes/specials/SpecialChangeEmail.php
includes/specials/SpecialChangePassword.php
includes/specials/SpecialConfirmemail.php
includes/specials/SpecialPasswordReset.php
includes/specials/SpecialPreferences.php
includes/specials/SpecialWatchlist.php
languages/messages/MessagesEn.php
languages/messages/MessagesQqq.php
maintenance/dictionary/mediawiki.dic
maintenance/language/messages.inc
tests/phpunit/includes/UserTest.php
tests/phpunit/includes/api/ApiOptionsTest.php

index 8bf680b..6008800 100644 (file)
@@ -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.
index 31659f3..f30a8b6 100644 (file)
@@ -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
index cd77ef8..37df489 100644 (file)
@@ -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();
index 8f784bb..6b5b5eb 100644 (file)
@@ -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;
        }
index 685fe96..3ee32c3 100644 (file)
@@ -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',
        );
index 8c996a2..720025f 100644 (file)
@@ -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;
 
index 8e65b40..2b7e7cc 100644 (file)
@@ -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 );
+                               }
                        }
                }
 
index 9e435fb..d8a01cd 100644 (file)
@@ -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' );
index b53a46a..d297a2f 100644 (file)
@@ -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 {
index 47808d1..c34b968 100644 (file)
@@ -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 );
        }
 
index 3b67554..69c4056 100644 (file)
@@ -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 ) {
index bb6bc95..fe91ada 100644 (file)
@@ -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();
index d61e1ee..b4bea60 100644 (file)
@@ -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();
index 10fe5ea..0fabd92 100644 (file)
@@ -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}}',
index 827f20e..84f6a6c 100644 (file)
@@ -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.',
index f73dfc1..656dc33 100644 (file)
@@ -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
index 2712fae..00462d5 100644 (file)
@@ -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',
index e777179..0113cab 100644 (file)
@@ -46,6 +46,11 @@ class UserTest extends MediaWikiTestCase {
                $wgRevokePermissions['formertesters'] = array(
                        'runtest' => true,
                );
+
+               # For the options test
+               $wgGroupPermissions['*'] = array(
+                       'editmyoptions' => true,
+               );
        }
 
        public function testGroupPermissions() {
index ae74e38..ad1e73a 100644 (file)
@@ -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' ) );