in the history or when previewing changes while editing).
* New hook 'IsUploadAllowedFromUrl' is added which can be used to intercept uploads by
URL, useful for blacklisting specific URLs
+* (bug 21912) Watchlist token implementation has been refactored and
+ Special:ResetTokens was added to allow users to reset their tokens
+ instead of presenting them in Preferences.
=== Bug fixes in 1.22 ===
* Disable Special:PasswordReset when $wgEnableEmail is false. Previously one
&$query_options: array of options for the database request
&$select: Array of columns to select
+'SpecialResetTokensTokens': Called when building token list for
+SpecialResetTokens.
+&$tokens: array of token information arrays in the format of
+ array( 'preference' => '<preference-name>', 'label-message' => '<message-key>' )
+
'SpecialSearchCreateLink': Called when making the message to create a page or
go to the existing page.
$t: title object searched for
'SpecialRecentChanges' => 'includes/specials/SpecialRecentchanges.php',
'SpecialRecentchangeslinked' => 'includes/specials/SpecialRecentchangeslinked.php',
'SpecialRedirect' => 'includes/specials/SpecialRedirect.php',
+ 'SpecialResetTokens' => 'includes/specials/SpecialResetTokens.php',
'SpecialRevisionDelete' => 'includes/specials/SpecialRevisiondelete.php',
'SpecialSearch' => 'includes/specials/SpecialSearch.php',
'SpecialSpecialpages' => 'includes/specials/SpecialSpecialpages.php',
);
}
- if ( $wgEnableAPI ) {
- # Some random gibberish as a proposed default
- // @todo Fixme: this should use CryptRand but we may not want to read urandom on every view
- $hash = sha1( mt_rand() . microtime( true ) );
-
- $defaultPreferences['watchlisttoken'] = array(
- 'type' => 'text',
- 'section' => 'watchlist/advancedwatchlist',
- 'label-message' => 'prefs-watchlist-token',
- 'help' => $context->msg( 'prefs-help-watchlist-token', $hash )->escaped()
- );
- }
-
$watchTypes = array(
'edit' => 'watchdefault',
'move' => 'watchmoves',
);
}
}
+
+ if ( $wgEnableAPI ) {
+ $defaultPreferences['watchlisttoken'] = array(
+ 'type' => 'api',
+ );
+ $defaultPreferences['watchlisttoken-info'] = array(
+ 'type' => 'info',
+ 'section' => 'watchlist/tokenwatchlist',
+ 'label-message' => 'prefs-watchlist-token',
+ 'default' => $user->getTokenFromOption( 'watchlisttoken' ),
+ 'help-message' => 'prefs-help-watchlist-token2',
+ );
+ }
}
/**
'PasswordReset' => 'SpecialPasswordReset',
'DeletedContributions' => 'DeletedContributionsPage',
'Preferences' => 'SpecialPreferences',
+ 'ResetTokens' => 'SpecialResetTokens',
'Contributions' => 'SpecialContributions',
'Listgrouprights' => 'SpecialListGroupRights',
'Listusers' => 'SpecialListUsers',
$this->mOptions[$oname] = $val;
}
+ /**
+ * Get a token stored in the preferences (like the watchlist one),
+ * resetting it if it's empty (and saving changes).
+ *
+ * @param string $oname The option name to retrieve the token from
+ * @return string|bool User's current value for the option, or false if this option is disabled.
+ * @see resetTokenFromOption()
+ * @see getOption()
+ */
+ public function getTokenFromOption( $oname ) {
+ global $wgHiddenPrefs;
+ if ( in_array( $oname, $wgHiddenPrefs ) ) {
+ return false;
+ }
+
+ $token = $this->getOption( $oname );
+ if ( !$token ) {
+ $token = $this->resetTokenFromOption( $oname );
+ $this->saveSettings();
+ }
+ return $token;
+ }
+
+ /**
+ * Reset a token stored in the preferences (like the watchlist one).
+ * *Does not* save user's preferences (similarly to setOption()).
+ *
+ * @param string $oname The option name to reset the token in
+ * @return string|bool New token value, or false if this option is disabled.
+ * @see getTokenFromOption()
+ * @see setOption()
+ */
+ public function resetTokenFromOption( $oname ) {
+ global $wgHiddenPrefs;
+ if ( in_array( $oname, $wgHiddenPrefs ) ) {
+ return false;
+ }
+
+ $token = MWCryptRand::generateHex( 40 );
+ $this->setOption( $oname, $token );
+ return $token;
+ }
+
/**
* Return a list of the types of user options currently returned by
* User::getOptionKinds().
--- /dev/null
+<?php
+/**
+ * Implements Special:ResetTokens
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+
+/**
+ * Let users reset tokens like the watchlist token.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialResetTokens extends FormSpecialPage {
+ private $tokensList;
+
+ public function __construct() {
+ parent::__construct( 'ResetTokens' );
+ }
+
+ /**
+ * Returns the token information list for this page after running
+ * the hook and filtering out disabled preferences.
+ *
+ * @return array
+ */
+ protected function getTokensList() {
+ global $wgHiddenPrefs;
+
+ if ( !isset( $this->tokensList ) ) {
+ $tokens = array(
+ array( 'preference' => 'watchlisttoken', 'label-message' => 'resettokens-watchlist-token' ),
+ );
+ wfRunHooks( 'SpecialResetTokensTokens', array( &$tokens ) );
+
+ $tokens = array_filter( $tokens, function ( $tok ) use ( $wgHiddenPrefs ) {
+ return !in_array( $tok['preference'], $wgHiddenPrefs );
+ } );
+
+ $this->tokensList = $tokens;
+ }
+
+ return $this->tokensList;
+ }
+
+ public function execute( $par ) {
+ // This is a preferences page, so no user JS for y'all.
+ $this->getOutput()->disallowUserJs();
+
+ parent::execute( $par );
+
+ $this->getOutput()->addReturnTo( SpecialPage::getTitleFor( 'Preferences' ) );
+ }
+
+ public function onSuccess() {
+ $this->getOutput()->wrapWikiMsg(
+ "<div class='successbox'>\n$1\n</div>",
+ 'resettokens-done'
+ );
+ }
+
+ /**
+ * Display appropriate message if there's nothing to do.
+ * The submit button is also suppressed in this case (see alterForm()).
+ */
+ protected function getFormFields() {
+ $user = $this->getUser();
+ $tokens = $this->getTokensList();
+
+ if ( $tokens ) {
+ $tokensForForm = array();
+ foreach ( $tokens as $tok ) {
+ $label = $this->msg( 'resettokens-token-label' )
+ ->rawParams( $this->msg( $tok['label-message'] )->escaped() )
+ ->params( $user->getTokenFromOption( $tok['preference'] ) )
+ ->escaped();
+ $tokensForForm[ $label ] = $tok['preference'];
+ }
+
+ $desc = array(
+ 'label-message' => 'resettokens-tokens',
+ 'type' => 'multiselect',
+ 'options' => $tokensForForm,
+ );
+ } else {
+ $desc = array(
+ 'label-message' => 'resettokens-no-tokens',
+ 'type' => 'info',
+ );
+ }
+
+ return array(
+ 'tokens' => $desc,
+ );
+ }
+
+ /**
+ * Suppress the submit button if there's nothing to do;
+ * provide additional message on it otherwise.
+ */
+ protected function alterForm( HTMLForm $form ) {
+ if ( $this->getTokensList() ) {
+ $form->setSubmitTextMsg( 'resettokens-resetbutton' );
+ } else {
+ $form->suppressDefaultSubmit();
+ }
+ }
+
+ public function onSubmit( array $formData ) {
+ if ( $formData['tokens'] ) {
+ $user = $this->getUser();
+ foreach ( $formData['tokens'] as $tokenPref ) {
+ $user->resetTokenFromOption( $tokenPref );
+ }
+ $user->saveSettings();
+
+ return true;
+ }
+
+ return false;
+ }
+
+ protected function getGroupName() {
+ return 'users';
+ }
+
+ public function isListed() {
+ return (bool)$this->getTokensList();
+ }
+}
$this->checkPermissions();
// Add feed links
- $wlToken = $user->getOption( 'watchlisttoken' );
- if ( !$wlToken && $user->isAllowed( 'editmyoptions' ) ) {
- $wlToken = MWCryptRand::generateHex( 40 );
- $user->setOption( 'watchlisttoken', $wlToken );
- $user->saveSettings();
- }
-
+ $wlToken = $user->getTokenFromOption( 'watchlisttoken' );
if ( $wlToken ) {
$this->addFeedLinks( array( 'action' => 'feedwatchlist', 'allrev' => 'allrev',
'wlowner' => $user->getName(), 'wltoken' => $wlToken ) );
'Recentchanges' => array( 'RecentChanges' ),
'Recentchangeslinked' => array( 'RecentChangesLinked', 'RelatedChanges' ),
'Redirect' => array( 'Redirect' ),
+ 'ResetTokens' => array( 'ResetTokens' ),
'Revisiondelete' => array( 'RevisionDelete' ),
'Search' => array( 'Search' ),
'Shortpages' => array( 'ShortPages' ),
'changeemail-submit' => 'Change email',
'changeemail-cancel' => 'Cancel',
+# Special:ResetTokens
+'resettokens' => 'Reset tokens',
+'resettokens-summary' => '', # do not translate or duplicate this message to other languages
+'resettokens-text' => 'You can reset tokens which allow access to certain private data associated with your account here.
+
+You should do it if you accidentally shared them with someone or if your account has been compromised.',
+'resettokens-no-tokens' => 'There are no tokens to reset.',
+'resettokens-legend' => 'Reset tokens',
+'resettokens-tokens' => 'Tokens:',
+'resettokens-token-label' => '$1 (current value: $2)',
+'resettokens-watchlist-token' => 'Watchlist web feed token',
+'resettokens-done' => 'Tokens reset.',
+'resettokens-resetbutton' => 'Reset selected tokens',
+
# Edit page toolbar
'bold_sample' => 'Bold text',
'bold_tip' => 'Bold text',
'recentchangesdays-max' => 'Maximum $1 {{PLURAL:$1|day|days}}',
'recentchangescount' => 'Number of edits to show by default:',
'prefs-help-recentchangescount' => 'This includes recent changes, page histories, and logs.',
-'prefs-help-watchlist-token' => "Filling in this field with a secret key will generate an RSS feed for your watchlist.
-Anyone who knows the key in this field will be able to read your watchlist, so choose a secure value.
-Here's a randomly-generated value you can use: $1",
+'prefs-help-watchlist-token2' => "This is the secret key to the web feed of your watchlist.
+Anyone who knows it will be able to read your watchlist, so do not share it.
+[[Special:ResetTokens|Click here if you need to reset it]].",
'savedprefs' => 'Your preferences have been saved.',
'timezonelegend' => 'Time zone:',
'localtime' => 'Local time:',
'prefs-displayrc' => 'Display options',
'prefs-displaysearchoptions' => 'Display options',
'prefs-displaywatchlist' => 'Display options',
+'prefs-tokenwatchlist' => 'Token',
'prefs-diffs' => 'Diffs',
# User preference: email validation using jQuery
{{Identical|Cancel}}',
+# Special:ResetTokens
+'resettokens' => 'Title of [[Special:ResetTokens|special page]].',
+'resettokens-text' => 'Text on [[Special:ResetTokens]].',
+'resettokens-no-tokens' => 'Additional text on [[Special:ResetTokens]] if the user has no tokens.',
+'resettokens-legend' => 'Form legend on [[Special:ResetTokens]].',
+'resettokens-tokens' => 'Form label on [[Special:ResetTokens]].',
+'resettokens-token-label' => 'Label for each checkbox on [[Special:ResetTokens]].
+$1 is short information about the token (for example {{msg-mw|resettokens-watchlist-token}}), $2 is its current value.',
+'resettokens-watchlist-token' => 'Label for watchlist token checkbox on [[Special:ResetTokens]].',
+'resettokens-done' => 'Message shown on [[Special:ResetTokens]] after the tokens are reset.',
+'resettokens-resetbutton' => 'Form submit button on [[Special:ResetTokens]].',
+
# Edit page toolbar
'bold_sample' => 'This is the sample text that you get when you press the first button on the left on the edit toolbar.
'recentchangesdays-max' => 'Shown as hint in [[Special:Preferences]], tab "Recent changes"',
'recentchangescount' => 'Used in [[Special:Preferences]], tab "Recent changes".',
'prefs-help-recentchangescount' => 'Used in [[Special:Preferences]], tab "Recent changes".',
-'prefs-help-watchlist-token' => 'Used in [[Special:Preferences]], tab Watchlist.',
+'prefs-help-watchlist-token2' => 'Used in [[Special:Preferences]], tab Watchlist.',
'savedprefs' => 'This message appears after saving changes to your user preferences.',
'timezonelegend' => '{{Identical|Time zone}}',
'localtime' => 'Used as label in [[Special:Preferences#mw-prefsection-datetime|preferences]].',
Used in [[Special:Preferences]], tab "Search options". The display options refer to:
* {{msg-mw|Vector-simplesearch-preference}}',
-'prefs-displaywatchlist' => '"Display" is a noun that specifies the kind of "options". So translate as "options about display", not as "display the options".
-
-Used in [[Special:Preferences]], tab "Watchlist". The display options refer to:
-* {{msg-mw|Prefs-watchlist-days}}
-* {{msg-mw|Prefs-watchlist-edits}}',
+'prefs-tokenwatchlist' => 'Section heading.
+Used in [[Special:Preferences]], tab "Watchlist".',
'prefs-diffs' => 'Used in [[Special:Preferences]], tab "Misc".',
# User preference: email validation using jQuery
'version-summary',
'tags-summary',
'comparepages-summary',
+ 'resettokens-summary',
'version-entrypoints-index-php',
'version-entrypoints-api-php',
'version-entrypoints-load-php',
'changeemail-submit',
'changeemail-cancel',
),
+ 'resettokens' => array(
+ 'resettokens',
+ 'resettokens-summary',
+ 'resettokens-text',
+ 'resettokens-no-tokens',
+ 'resettokens-legend',
+ 'resettokens-tokens',
+ 'resettokens-token-label',
+ 'resettokens-watchlist-token',
+ 'resettokens-done',
+ 'resettokens-resetbutton',
+ ),
'toolbar' => array(
'bold_sample',
'bold_tip',
'recentchangesdays-max',
'recentchangescount',
'prefs-help-recentchangescount',
- 'prefs-help-watchlist-token',
+ 'prefs-help-watchlist-token2',
'savedprefs',
'timezonelegend',
'localtime',
'prefs-displayrc',
'prefs-displaysearchoptions',
'prefs-displaywatchlist',
+ 'prefs-tokenwatchlist',
'prefs-diffs',
),
'preferences-email' => array(