Merge "(bug 18195) Allow changing preferences via API"
[lhc/web/wiklou.git] / includes / User.php
index 684a05c..793714d 100644 (file)
@@ -836,23 +836,20 @@ class User {
        }
 
        /**
-        * Return a random password. Sourced from mt_rand, so it's not particularly secure.
-        * @todo hash random numbers to improve security, like generateToken()
+        * Return a random password.
         *
         * @return String new random password
         */
        public static function randomPassword() {
                global $wgMinimalPasswordLength;
-               $pwchars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz';
-               $l = strlen( $pwchars ) - 1;
-
-               $pwlength = max( 7, $wgMinimalPasswordLength );
-               $digit = mt_rand( 0, $pwlength - 1 );
-               $np = '';
-               for ( $i = 0; $i < $pwlength; $i++ ) {
-                       $np .= $i == $digit ? chr( mt_rand( 48, 57 ) ) : $pwchars[ mt_rand( 0, $l ) ];
-               }
-               return $np;
+               // Decide the final password length based on our min password length, stopping at a minimum of 10 chars
+               $length = max( 10, $wgMinimalPasswordLength );
+               // Multiply by 1.25 to get the number of hex characters we need
+               $length = $length * 1.25;
+               // Generate random hex chars
+               $hex = MWCryptRand::generateHex( $length );
+               // Convert from base 16 to base 32 to get a proper password like string
+               return wfBaseConvert( $hex, 16, 32 );
        }
 
        /**
@@ -882,7 +879,7 @@ class User {
                        $this->mTouched = '0'; # Allow any pages to be cached
                }
 
-               $this->setToken(); # Random
+               $this->mToken = null; // Don't run cryptographic functions till we need a token
                $this->mEmailAuthenticated = null;
                $this->mEmailToken = '';
                $this->mEmailTokenExpires = null;
@@ -989,11 +986,11 @@ class User {
                        return false;
                }
 
-               if ( $request->getSessionData( 'wsToken' ) !== null ) {
-                       $passwordCorrect = $proposedUser->getToken() === $request->getSessionData( 'wsToken' );
+               if ( $request->getSessionData( 'wsToken' ) ) {
+                       $passwordCorrect = $proposedUser->getToken( false ) === $request->getSessionData( 'wsToken' );
                        $from = 'session';
-               } elseif ( $request->getCookie( 'Token' ) !== null ) {
-                       $passwordCorrect = $proposedUser->getToken() === $request->getCookie( 'Token' );
+               } elseif ( $request->getCookie( 'Token' ) ) {
+                       $passwordCorrect = $proposedUser->getToken( false ) === $request->getCookie( 'Token' );
                        $from = 'cookie';
                } else {
                        # No session or persistent login cookie
@@ -1098,6 +1095,9 @@ class User {
                        }
                        $this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
                        $this->mToken = $row->user_token;
+                       if ( $this->mToken == '' ) {
+                               $this->mToken = null;
+                       }
                        $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
                        $this->mEmailToken = $row->user_email_token;
                        $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
@@ -1517,7 +1517,7 @@ class User {
                        $count = $wgMemc->get( $key );
                        // Already pinged?
                        if( $count ) {
-                               if( $count > $max ) {
+                               if( $count >= $max ) {
                                        wfDebug( __METHOD__ . ": tripped! $key at $count $summary\n" );
                                        if( $wgRateLimitLog ) {
                                                wfSuppressWarnings();
@@ -2023,10 +2023,14 @@ class User {
 
        /**
         * Get the user's current token.
+        * @param $forceCreation Force the generation of a new token if the user doesn't have one (default=true for backwards compatibility)
         * @return String Token
         */
-       public function getToken() {
+       public function getToken( $forceCreation = true ) {
                $this->load();
+               if ( !$this->mToken && $forceCreation ) {
+                       $this->setToken();
+               }
                return $this->mToken;
        }
 
@@ -2037,17 +2041,9 @@ class User {
         * @param $token String|bool If specified, set the token to this value
         */
        public function setToken( $token = false ) {
-               global $wgSecretKey, $wgProxyKey;
                $this->load();
                if ( !$token ) {
-                       if ( $wgSecretKey ) {
-                               $key = $wgSecretKey;
-                       } elseif ( $wgProxyKey ) {
-                               $key = $wgProxyKey;
-                       } else {
-                               $key = microtime();
-                       }
-                       $this->mToken = md5( $key . mt_rand( 0, 0x7fffffff ) . wfWikiID() . $this->mId );
+                       $this->mToken = MWCryptRand::generateHex( USER_TOKEN_LENGTH );
                } else {
                        $this->mToken = $token;
                }
@@ -2116,6 +2112,42 @@ class User {
                wfRunHooks( 'UserSetEmail', array( $this, &$this->mEmail ) );
        }
 
+       /**
+        * Set the user's e-mail address and a confirmation mail if needed.
+        *
+        * @since 1.20
+        * @param $str String New e-mail address
+        * @return Status
+        */
+       public function setEmailWithConfirmation( $str ) {
+               global $wgEnableEmail, $wgEmailAuthentication;
+
+               if ( !$wgEnableEmail ) {
+                       return Status::newFatal( 'emaildisabled' );
+               }
+
+               $oldaddr = $this->getEmail();
+               if ( $str === $oldaddr ) {
+                       return Status::newGood( true );
+               }
+
+               $this->setEmail( $str );
+
+               if ( $str !== '' && $wgEmailAuthentication ) {
+                       # Send a confirmation request to the new address if needed
+                       $type = $oldaddr != '' ? 'changed' : 'set';
+                       $result = $this->sendConfirmationMail( $type );
+                       if ( $result->isGood() ) {
+                               # Say the the caller that a confirmation mail has been sent
+                               $result->value = 'eauth';
+                       }
+               } else {
+                       $result = Status::newGood( true );
+               }
+
+               return $result;
+       }
+
        /**
         * Get the user's real name
         * @return String User's real name
@@ -2249,7 +2281,10 @@ class User {
         * Reset all options to the site defaults
         */
        public function resetOptions() {
+               $this->load();
+
                $this->mOptions = self::getDefaultOptions();
+               $this->mOptionsLoaded = true;
        }
 
        /**
@@ -2814,7 +2849,7 @@ class User {
                                'user_email' => $this->mEmail,
                                'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
                                'user_touched' => $dbw->timestamp( $this->mTouched ),
-                               'user_token' => $this->mToken,
+                               'user_token' => strval( $this->mToken ),
                                'user_email_token' => $this->mEmailToken,
                                'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
                        ), array( /* WHERE */
@@ -2880,7 +2915,7 @@ class User {
                        'user_email' => $user->mEmail,
                        'user_email_authenticated' => $dbw->timestampOrNull( $user->mEmailAuthenticated ),
                        'user_real_name' => $user->mRealName,
-                       'user_token' => $user->mToken,
+                       'user_token' => strval( $user->mToken ),
                        'user_registration' => $dbw->timestamp( $user->mRegistration ),
                        'user_editcount' => 0,
                        'user_touched' => $dbw->timestamp( self::newTouchedTimestamp() ),
@@ -2917,7 +2952,7 @@ class User {
                                'user_email' => $this->mEmail,
                                'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
                                'user_real_name' => $this->mRealName,
-                               'user_token' => $this->mToken,
+                               'user_token' => strval( $this->mToken ),
                                'user_registration' => $dbw->timestamp( $this->mRegistration ),
                                'user_editcount' => 0,
                                'user_touched' => $dbw->timestamp( $this->mTouched ),
@@ -3182,7 +3217,7 @@ class User {
                } else {
                        $token = $request->getSessionData( 'wsEditToken' );
                        if ( $token === null ) {
-                               $token = self::generateToken();
+                               $token = MWCryptRand::generateHex( 32 );
                                $request->setSessionData( 'wsEditToken', $token );
                        }
                        if( is_array( $salt ) ) {
@@ -3197,10 +3232,10 @@ class User {
         *
         * @param $salt String Optional salt value
         * @return String The new random token
+        * @deprecated since 1.20; Use MWCryptRand for secure purposes or wfRandomString for pesudo-randomness
         */
        public static function generateToken( $salt = '' ) {
-               $token = dechex( mt_rand() ) . dechex( mt_rand() );
-               return md5( $token . $salt );
+               return MWCryptRand::generateHex( 32 );
        }
 
        /**
@@ -3307,9 +3342,9 @@ class User {
                $now = time();
                $expires = $now + $wgUserEmailConfirmationTokenExpiry;
                $expiration = wfTimestamp( TS_MW, $expires );
-               $token = self::generateToken( $this->mId . $this->mEmail . $expires );
-               $hash = md5( $token );
                $this->load();
+               $token = MWCryptRand::generateHex( 32 );
+               $hash = md5( $token );
                $this->mEmailToken = $hash;
                $this->mEmailTokenExpires = $expiration;
                return $token;
@@ -3860,7 +3895,7 @@ class User {
 
                if( $wgPasswordSalt ) {
                        if ( $salt === false ) {
-                               $salt = substr( wfGenerateToken(), 0, 8 );
+                               $salt = MWCryptRand::generateHex( 8 );
                        }
                        return ':B:' . $salt . ':' . md5( $salt . '-' . md5( $password ) );
                } else {
@@ -3892,7 +3927,7 @@ class User {
                } elseif ( $type == ':B:' ) {
                        # Salted
                        list( $salt, $realHash ) = explode( ':', substr( $hash, 3 ), 2 );
-                       return md5( $salt.'-'.md5( $password ) ) == $realHash;
+                       return md5( $salt.'-'.md5( $password ) ) === $realHash;
                } else {
                        # Old-style
                        return self::oldCrypt( $password, $userId ) === $hash;
@@ -3947,7 +3982,7 @@ class User {
                        return true; // disabled
                }
                $log = new LogPage( 'newusers', false );
-               $log->addEntry( 'autocreate', $this->getUserPage(), '', array( $this->getId() ) );
+               $log->addEntry( 'autocreate', $this->getUserPage(), '', array( $this->getId() ), $this );
                return true;
        }