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
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.
** 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.
$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
}
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();
'searchlimit' => array( 'Preferences', 'filterIntval' ),
);
+ // Stuff that shouldn't be saved as a preference.
+ private static $saveBlacklist = array(
+ 'realname',
+ 'emailaddress',
+ );
+
/**
* @throws MWException
* @param $user User
## 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();
);
}
+ $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',
'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() ) );
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;
$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' ) ) {
$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] ) ) {
}
}
- // 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 );
/**
* 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
* @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;
}
'edit',
'editinterface',
'editprotected',
+ 'editmyoptions',
+ 'editmyprivateinfo',
'editmyusercss',
'editmyuserjs',
'editmywatchlist',
'upload_by_url',
'userrights',
'userrights-interwiki',
+ 'viewmyprivateinfo',
'viewmywatchlist',
'writeapi',
);
$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;
}
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() );
}
$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 );
+ }
}
}
protected $mNewEmail;
public function __construct() {
- parent::__construct( 'ChangeEmail' );
+ parent::__construct( 'ChangeEmail', 'editmyprivateinfo' );
}
/**
}
$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' );
protected $mUserName, $mOldpass, $mNewpass, $mRetype, $mDomain;
public function __construct() {
- parent::__construct( 'ChangePassword' );
+ parent::__construct( 'ChangePassword', 'editmyprivateinfo' );
}
/**
}
$this->checkReadOnly();
+ $this->checkPermissions();
if ( $request->wasPosted() && $user->matchEditToken( $request->getVal( 'token' ) ) ) {
try {
*/
class EmailConfirmation extends UnlistedSpecialPage {
public function __construct() {
- parent::__construct( 'Confirmemail' );
+ parent::__construct( 'Confirmemail', 'editmyprivateinfo' );
}
/**
$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() ) {
*/
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 );
}
private $result;
public function __construct() {
- parent::__construct( 'PasswordReset' );
+ parent::__construct( 'PasswordReset', 'editmyprivateinfo' );
}
public function userCanExecute( User $user ) {
}
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' );
}
public function submitReset( $formData ) {
+ if ( !$this->getUser()->isAllowed( 'editmyoptions' ) ) {
+ throw new PermissionsError( 'editmyoptions' );
+ }
+
$user = $this->getUser();
$user->resetOptions( 'all' );
$user->saveSettings();
// 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();
'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\'\'".',
'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',
'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}}',
'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:
* {{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}}
'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.',
editintro
edititis
editlink
+editmyoptions
+editmyprivateinfo
editmyusercss
editmyuserjs
editmywatchlist
viewcount
viewdeleted
viewhelppage
+viewmyprivateinfo
viewmywatchlist
viewprevnext
viewsource
'customjsprotected',
'mycustomcssprotected',
'mycustomjsprotected',
+ 'myprivateinfoprotected',
+ 'mypreferencesprotected',
'ns-specialprotected',
'titleprotected',
'filereadonlyerror',
'right-editmyuserjs',
'right-viewmywatchlist',
'right-editmywatchlist',
+ 'right-viewmyprivateinfo',
+ 'right-editmyprivateinfo',
+ 'right-editmyoptions',
'right-rollback',
'right-markbotedits',
'right-noratelimit',
'action-sendemail',
'action-editmywatchlist',
'action-viewmywatchlist',
+ 'action-viewmyprivateinfo',
+ 'action-editmyprivateinfo',
),
'recentchanges' => array(
'nchanges',
$wgRevokePermissions['formertesters'] = array(
'runtest' => true,
);
+
+ # For the options test
+ $wgGroupPermissions['*'] = array(
+ 'editmyoptions' => true,
+ );
}
public function testGroupPermissions() {
->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() )
$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' ) );
$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' ) );
$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 ) );
$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' ) );