Added a per-user limit on password reminder emails. Presumably if you just had a...
authorTim Starling <tstarling@users.mediawiki.org>
Mon, 23 Oct 2006 09:35:30 +0000 (09:35 +0000)
committerTim Starling <tstarling@users.mediawiki.org>
Mon, 23 Oct 2006 09:35:30 +0000 (09:35 +0000)
includes/DefaultSettings.php
includes/SpecialUserlogin.php
includes/User.php
languages/messages/MessagesEn.php
maintenance/archives/patch-user_newpass_time.sql [new file with mode: 0644]
maintenance/mysql5/tables.sql
maintenance/postgres/tables.sql
maintenance/tables.sql
maintenance/updaters.inc

index 20f4aa7..21a1cfb 100644 (file)
@@ -424,6 +424,12 @@ $wgEnableEmail = true;
  */
 $wgEnableUserEmail = true;
 
+/**
+ * Minimum time, in hours, which must elapse between password reminder
+ * emails for a given account. This is to prevent abuse by mail flooding.
+ */
+$wgPasswordReminderResendTime = 24;
+
 /**
  * SMTP Mode
  * For using a direct (authenticated) SMTP server connection.
index 1f56141..2cd1468 100644 (file)
@@ -123,7 +123,7 @@ class LoginForm {
                }
 
                $u->saveSettings();
-               $result = $this->mailPasswordInternal($u);
+               $result = $this->mailPasswordInternal( $u, false );
 
                wfRunHooks( 'AddNewAccount', array( $u ) );
 
@@ -343,7 +343,7 @@ class LoginForm {
                                return self::NOT_EXISTS;
                        }
                } else {
-                       $u->loadFromDatabase();
+                       $u->load();
                }
 
                if (!$u->checkPassword( $this->mPassword )) {
@@ -419,7 +419,7 @@ class LoginForm {
                        $wgOut->rateLimited();
                        return;
                }
-       
+
                if ( '' == $this->mName ) {
                        $this->mainLoginForm( wfMsg( 'noname' ) );
                        return;
@@ -434,9 +434,16 @@ class LoginForm {
                        return;
                }
 
-               $u->loadFromDatabase();
+               # Check against password throttle
+               if ( $u->isPasswordReminderThrottled() ) {
+                       global $wgPasswordReminderResendTime;
+                       # Round the time in hours to 3 d.p., in case someone is specifying minutes or seconds.
+                       $this->mainLoginForm( wfMsg( 'throttled-mailpassword', 
+                               round( $wgPasswordReminderResendTime, 3 ) ) );
+                       return;
+               }
 
-               $result = $this->mailPasswordInternal( $u );
+               $result = $this->mailPasswordInternal( $u, true );
                if( WikiError::isError( $result ) ) {
                        $this->mainLoginForm( wfMsg( 'mailerror', $result->getMessage() ) );
                } else {
@@ -449,7 +456,7 @@ class LoginForm {
         * @return mixed true on success, WikiError on failure
         * @private
         */
-       function mailPasswordInternal( $u ) {
+       function mailPasswordInternal( $u, $throttle = true ) {
                global $wgCookiePath, $wgCookieDomain, $wgCookiePrefix, $wgCookieSecure;
                global $wgServer, $wgScript;
 
@@ -458,7 +465,7 @@ class LoginForm {
                }
 
                $np = $u->randomPassword();
-               $u->setNewpassword( $np );
+               $u->setNewpassword( $np, $throttle );
 
                setcookie( "{$wgCookiePrefix}Token", '', time() - 3600, $wgCookiePath, $wgCookieDomain, $wgCookieSecure );
 
index 4fac12a..5d169e5 100644 (file)
@@ -70,6 +70,7 @@ class User {
                'mRealName',
                'mPassword',
                'mNewpassword',
+               'mNewpassTime',
                'mEmail',
                'mOptions',
                'mTouched',
@@ -86,9 +87,9 @@ class User {
        /**
         * The cache variable declarations
         */
-       var $mId, $mName, $mRealName, $mPassword, $mNewpassword, $mEmail, $mOptions
-               $mTouched, $mToken, $mEmailAuthenticated, $mEmailToken, $mEmailTokenExpires,
-               $mRegistration, $mGroups;
+       var $mId, $mName, $mRealName, $mPassword, $mNewpassword, $mNewpassTime
+               $mEmail, $mOptions, $mTouched, $mToken, $mEmailAuthenticated, 
+               $mEmailToken, $mEmailTokenExpires, $mRegistration, $mGroups;
 
        /**
         * Whether the cache variables have been loaded
@@ -574,6 +575,7 @@ class User {
                $this->mName = $name;
                $this->mRealName = '';
                $this->mPassword = $this->mNewpassword = '';
+               $this->mNewpassTime = null;
                $this->mEmail = '';
                $this->mOptions = null; # Defer init
 
@@ -691,6 +693,7 @@ class User {
                        $this->mRealName = $s->user_real_name;
                        $this->mPassword = $s->user_password;
                        $this->mNewpassword = $s->user_newpassword;
+                       $this->mNewpassTime = wfTimestampOrNull( TS_MW, $s->user_newpass_time );
                        $this->mEmail = $s->user_email;
                        $this->decodeOptions( $s->user_options );
                        $this->mTouched = wfTimestamp(TS_MW,$s->user_touched);
@@ -1285,6 +1288,7 @@ class User {
                $this->setToken();
                $this->mPassword = $this->encryptPassword( $str );
                $this->mNewpassword = '';
+               $this->mNewpassTime = NULL;
        }
 
        /**
@@ -1314,11 +1318,32 @@ class User {
                $this->mCookiePassword = md5( $str );
        }
 
-       function setNewpassword( $str ) {
+       /**
+        * Set the password for a password reminder or new account email
+        * Sets the user_newpass_time field if $throttle is true
+        */
+       function setNewpassword( $str, $throttle = true ) {
                $this->load();
                $this->mNewpassword = $this->encryptPassword( $str );
+               if ( $throttle ) {
+                       $this->mNewpassTime = wfTimestampNow();
+               }
        }
 
+       /**
+        * Returns true if a password reminder email has already been sent within
+        * the last $wgPasswordReminderResendTime hours
+        */
+       function isPasswordReminderThrottled() {
+               global $wgPasswordReminderResendTime;
+               $this->load();
+               if ( !$this->mNewpassTime || !$wgPasswordReminderResendTime ) {
+                       return false;
+               }
+               $expiry = wfTimestamp( TS_UNIX, $this->mNewpassTime ) + $wgPasswordReminderResendTime * 3600;
+               return time() < $expiry;
+       }
+       
        function getEmail() {
                $this->load();
                return $this->mEmail;
@@ -1774,6 +1799,7 @@ class User {
                                'user_name' => $this->mName,
                                'user_password' => $this->mPassword,
                                'user_newpassword' => $this->mNewpassword,
+                               'user_newpass_time' => $dbw->timestampOrNull( $this->mNewpassTime ),
                                'user_real_name' => $this->mRealName,
                                'user_email' => $this->mEmail,
                                'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
@@ -1834,6 +1860,7 @@ class User {
                        'user_name' => $name,
                        'user_password' => $user->mPassword,
                        'user_newpassword' => $user->mNewpassword,
+                       'user_newpass_time' => $dbw->timestamp( $user->mNewpassTime ),
                        'user_email' => $user->mEmail,
                        'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
                        'user_real_name' => $user->mRealName,
@@ -1866,6 +1893,7 @@ class User {
                                'user_name' => $this->mName,
                                'user_password' => $this->mPassword,
                                'user_newpassword' => $this->mNewpassword,
+                               'user_newpass_time' => $dbw->timestamp( $this->mNewpassTime ),
                                'user_email' => $this->mEmail,
                                'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
                                'user_real_name' => $this->mRealName,
index a457dab..0f1d71d 100644 (file)
@@ -144,8 +144,18 @@ $datePreferences = array(
        'ISO 8601',
 );
 
+/**
+ * The date format to use for generated dates in the user interface.
+ * This may be one of the above date preferences, or the special value 
+ * "dmy or mdy", which uses mdy if $wgAmericanDates is true, and dmy 
+ * if $wgAmericanDates is false.
+ */
 $defaultDateFormat = 'dmy or mdy';
 
+/**
+ * Associative array mapping old numeric date formats, which may still be 
+ * stored in user preferences, to the new string formats.
+ */
 $datePreferenceMigrationMap = array(
        'default',
        'mdy',
@@ -179,6 +189,9 @@ $dateFormats = array(
        'ISO 8601 both' => 'xnY-xnm-xnd"T"xnH:xni:xns',
 );
 
+/**
+ * Default list of book sources
+ */
 $bookstoreList = array(
        'AddALL' => 'http://www.addall.com/New/Partner.cgi?query=$1&type=ISBN',
        'PriceSCAN' => 'http://www.pricescan.com/books/bookDetail.asp?isbn=$1',
@@ -186,10 +199,14 @@ $bookstoreList = array(
        'Amazon.com' => 'http://www.amazon.com/exec/obidos/ISBN=$1'
 );
 
-# Note to translators:
-#   Please include the English words as synonyms.  This allows people
-#   from other wikis to contribute more easily.
-#
+/**
+ * Magic words
+ * Customisable syntax for wikitext and elsewhere
+ *
+ * Note to translators:
+ *   Please include the English words as synonyms.  This allows people
+ *   from other wikis to contribute more easily.
+ */
 $magicWords = array(
 #   ID                                 CASE  SYNONYMS
        'redirect'               => array( 0,    '#REDIRECT'              ),
@@ -299,9 +316,12 @@ $magicWords = array(
        'formatnum'              => array( 0,    'FORMATNUM'              ),
        'padleft'                => array( 0,    'PADLEFT'                ),
        'padright'               => array( 0,    'PADRIGHT'               ),
-
 );
 
+/**
+ * Regular expression matching the "link trail", e.g. "ed" in [[Toast]]ed, as 
+ * the first group, and the remainder of the string as the second group.
+ */
 $linkTrail = '/^([a-z]+)(.*)$/sD';
 
 #-------------------------------------------------------------------
@@ -736,6 +756,9 @@ is not allowed to use the password recovery function to prevent abuse.',
 'eauthentsent' =>  'A confirmation e-mail has been sent to the nominated e-mail address.
 Before any other mail is sent to the account, you will have to follow the instructions in the e-mail,
 to confirm that the account is actually yours.',
+'throttled-mailpassword' => 'A password reminder has already been sent, within the
+last $1 hours. To prevent abuse, only one password reminder will be sent per
+$1 hours.',
 'loginend'                         => '',
 'signupend'                        => '{{int:loginend}}',
 'mailerror'                 => 'Error sending mail: $1',
diff --git a/maintenance/archives/patch-user_newpass_time.sql b/maintenance/archives/patch-user_newpass_time.sql
new file mode 100644 (file)
index 0000000..befbf0e
--- /dev/null
@@ -0,0 +1,4 @@
+-- Timestamp of the last time when a new password was
+-- sent, for throttling purposes
+ALTER TABLE user ADD user_newpass_time char(14) binary;
+
index 81a4690..9682ecd 100644 (file)
@@ -86,6 +86,10 @@ CREATE TABLE /*$wgDBprefix*/user (
   -- at which point the hash is moved to user_password
   -- and the old password is invalidated.
   user_newpassword tinyblob NOT NULL default '',
+
+  -- Timestamp of the last time when a new password was
+  -- sent, for throttling purposes
+  user_newpass_time char(14) binary,
   
   -- Note: email should be restricted, not public info.
   -- Same with passwords.
index 8ee8720..8534a1b 100644 (file)
@@ -17,6 +17,7 @@ CREATE TABLE mwuser ( -- replace reserved word 'user'
   user_real_name            TEXT,
   user_password             TEXT,
   user_newpassword          TEXT,
+  user_newpass_time         TIMESTAMPTZ,
   user_token                CHAR(32),
   user_email                TEXT,
   user_email_token          CHAR(32),
index 3ffa5e5..9b2955d 100644 (file)
@@ -74,6 +74,10 @@ CREATE TABLE /*$wgDBprefix*/user (
   -- and the old password is invalidated.
   user_newpassword tinyblob NOT NULL default '',
   
+  -- Timestamp of the last time when a new password was
+  -- sent, for throttling purposes
+  user_newpass_time char(14) binary,
+
   -- Note: email should be restricted, not public info.
   -- Same with passwords.
   user_email tinytext NOT NULL default '',
index d334660..a63307b 100644 (file)
@@ -57,6 +57,7 @@ $wgNewFields = array(
        array( 'ipblocks',      'ipb_range_start',  'patch-ipb_range_start.sql' ),
        array( 'site_stats',    'ss_images',        'patch-ss_images.sql' ),
        array( 'ipblocks',      'ipb_anon_only',    'patch-ipb_anon_only.sql' ),
+       array( 'user',          'user_newpass_time','patch-user_newpass_time.sql' ),
 );
 
 function rename_table( $from, $to, $patch ) {