From: MatmaRex Date: Fri, 14 Jun 2013 16:59:59 +0000 (+0200) Subject: Refactor watchlist token handling X-Git-Tag: 1.31.0-rc.0~19128 X-Git-Url: http://git.cyclocoop.org/data/Fool?a=commitdiff_plain;h=ed7979a9703f8524044b4f9b477736b8c8b91dac;p=lhc%2Fweb%2Fwiklou.git Refactor watchlist token handling Do not allow the user to change it directly; instead create a form where they can reset it. (The token can still be changed via the API.) The token is autogenerated whenever it is shown or otherwise used. This really should have never used the preferences; however, trying to change that now would be lots of work for very little gain, so this keeps using that mechanism, adding a little abstraction over it. It's not unconceivable that similar tokens could be used for other pieces of data, like Echo's notifications; this enables that with one new hook. ---- Things done here: * Add getTokenFromOption() and resetTokenFromOption() methods to User, abstracting out the get-and-generate-if-empty process of handling tokens. Respect $wgHiddenPrefs (Watchlist didn't do that previously). * Create Special:ResetTokens, inspired by Special:Preferences and Special:ChangeEmail, presenting the token resetting interface (HTMLForm-based with CSRF protection). * Create a new hook, SpecialResetTokensTokens, allowing extensions to register tokens to be shown in the resetting form. Each token needs information about the preference it corresponds to and a short description (used for checkbox label). * Hide the preference on Special:Preferences (use type=api to achieve this), display a link to aforementioned special page instead. Move info blurb to its own section at the bottom. Bug: 21912 Change-Id: I0bdd2469972c4af81bfb480e9dde58cdd14c67a8 --- diff --git a/RELEASE-NOTES-1.22 b/RELEASE-NOTES-1.22 index 0b4a6c5b1a..9ba8f5b2b2 100644 --- a/RELEASE-NOTES-1.22 +++ b/RELEASE-NOTES-1.22 @@ -151,6 +151,9 @@ production. 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 diff --git a/docs/hooks.txt b/docs/hooks.txt index 84cc820c30..0137f5b131 100644 --- a/docs/hooks.txt +++ b/docs/hooks.txt @@ -2222,6 +2222,11 @@ $opts: FormOptions for this request &$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' => '', 'label-message' => '' ) + 'SpecialSearchCreateLink': Called when making the message to create a page or go to the existing page. $t: title object searched for diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 0950e5f638..bb8c272985 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -974,6 +974,7 @@ $wgAutoloadLocalClasses = array( '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', diff --git a/includes/Preferences.php b/includes/Preferences.php index d5c0470916..16a7a6cfc9 100644 --- a/includes/Preferences.php +++ b/includes/Preferences.php @@ -961,19 +961,6 @@ class Preferences { ); } - 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', @@ -994,6 +981,19 @@ class Preferences { ); } } + + 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', + ); + } } /** diff --git a/includes/SpecialPageFactory.php b/includes/SpecialPageFactory.php index 4d635536a7..02bd9e82ce 100644 --- a/includes/SpecialPageFactory.php +++ b/includes/SpecialPageFactory.php @@ -94,6 +94,7 @@ class SpecialPageFactory { 'PasswordReset' => 'SpecialPasswordReset', 'DeletedContributions' => 'DeletedContributionsPage', 'Preferences' => 'SpecialPreferences', + 'ResetTokens' => 'SpecialResetTokens', 'Contributions' => 'SpecialContributions', 'Listgrouprights' => 'SpecialListGroupRights', 'Listusers' => 'SpecialListUsers', diff --git a/includes/User.php b/includes/User.php index 30e618a7d9..ab6f3c7c70 100644 --- a/includes/User.php +++ b/includes/User.php @@ -2372,6 +2372,49 @@ class User { $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(). diff --git a/includes/specials/SpecialResetTokens.php b/includes/specials/SpecialResetTokens.php new file mode 100644 index 0000000000..2285b5208a --- /dev/null +++ b/includes/specials/SpecialResetTokens.php @@ -0,0 +1,145 @@ +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( + "
\n$1\n
", + '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(); + } +} diff --git a/includes/specials/SpecialWatchlist.php b/includes/specials/SpecialWatchlist.php index b4bea60301..b3225475e8 100644 --- a/includes/specials/SpecialWatchlist.php +++ b/includes/specials/SpecialWatchlist.php @@ -58,13 +58,7 @@ class SpecialWatchlist extends SpecialPage { $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 ) ); diff --git a/languages/messages/MessagesEn.php b/languages/messages/MessagesEn.php index bb4763be13..a26b7cf9c1 100644 --- a/languages/messages/MessagesEn.php +++ b/languages/messages/MessagesEn.php @@ -450,6 +450,7 @@ $specialPageAliases = array( 'Recentchanges' => array( 'RecentChanges' ), 'Recentchangeslinked' => array( 'RecentChangesLinked', 'RelatedChanges' ), 'Redirect' => array( 'Redirect' ), + 'ResetTokens' => array( 'ResetTokens' ), 'Revisiondelete' => array( 'RevisionDelete' ), 'Search' => array( 'Search' ), 'Shortpages' => array( 'ShortPages' ), @@ -1334,6 +1335,20 @@ Temporary password: $2', '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', @@ -1919,9 +1934,9 @@ Note that their indexes of {{SITENAME}} content may be out of date.', '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:', @@ -1997,6 +2012,7 @@ Your email address is not revealed when other users contact you.', 'prefs-displayrc' => 'Display options', 'prefs-displaysearchoptions' => 'Display options', 'prefs-displaywatchlist' => 'Display options', +'prefs-tokenwatchlist' => 'Token', 'prefs-diffs' => 'Diffs', # User preference: email validation using jQuery diff --git a/languages/messages/MessagesQqq.php b/languages/messages/MessagesQqq.php index f8537fadf9..272dd8a901 100644 --- a/languages/messages/MessagesQqq.php +++ b/languages/messages/MessagesQqq.php @@ -1439,6 +1439,18 @@ See also: {{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. @@ -2587,7 +2599,7 @@ When changing this message, please also update {{msg-mw|vector-editwarning-warni '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]].', @@ -2728,11 +2740,8 @@ Used in [[Special:Preferences]], tab "Recent changes". The display options refer 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 diff --git a/maintenance/language/messageTypes.inc b/maintenance/language/messageTypes.inc index fadf703917..187e16d6ef 100644 --- a/maintenance/language/messageTypes.inc +++ b/maintenance/language/messageTypes.inc @@ -243,6 +243,7 @@ $wgIgnoredMessages = array( 'version-summary', 'tags-summary', 'comparepages-summary', + 'resettokens-summary', 'version-entrypoints-index-php', 'version-entrypoints-api-php', 'version-entrypoints-load-php', diff --git a/maintenance/language/messages.inc b/maintenance/language/messages.inc index 43d05d0b74..c1af4aa89a 100644 --- a/maintenance/language/messages.inc +++ b/maintenance/language/messages.inc @@ -623,6 +623,18 @@ $wgMessageStructure = array( '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', @@ -1052,7 +1064,7 @@ $wgMessageStructure = array( 'recentchangesdays-max', 'recentchangescount', 'prefs-help-recentchangescount', - 'prefs-help-watchlist-token', + 'prefs-help-watchlist-token2', 'savedprefs', 'timezonelegend', 'localtime', @@ -1126,6 +1138,7 @@ $wgMessageStructure = array( 'prefs-displayrc', 'prefs-displaysearchoptions', 'prefs-displaywatchlist', + 'prefs-tokenwatchlist', 'prefs-diffs', ), 'preferences-email' => array(