Localisation updates from http://translatewiki.net.
[lhc/web/wiklou.git] / includes / User.php
index c7c6496..5de4b2c 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 );
@@ -1273,10 +1273,6 @@ class User {
                // overwriting mBlockedby, surely?
                $this->load();
 
-               $this->mBlockedby = 0;
-               $this->mHideName = 0;
-               $this->mAllowUsertalk = 0;
-
                # We only need to worry about passing the IP address to the Block generator if the
                # user is not immune to autoblocks/hardblocks, and they are the current user so we
                # know which IP address they're actually coming from
@@ -1287,30 +1283,37 @@ class User {
                }
 
                # User/IP blocking
-               $this->mBlock = Block::newFromTarget( $this->getName(), $ip, !$bFromSlave );
-               if ( $this->mBlock instanceof Block ) {
-                       wfDebug( __METHOD__ . ": Found block.\n" );
-                       $this->mBlockedby = $this->mBlock->getByName();
-                       $this->mBlockreason = $this->mBlock->mReason;
-                       $this->mHideName = $this->mBlock->mHideName;
-                       $this->mAllowUsertalk = !$this->mBlock->prevents( 'editownusertalk' );
-               }
+               $block = Block::newFromTarget( $this, $ip, !$bFromSlave );
 
                # Proxy blocking
-               if ( $ip !== null && !$this->isAllowed( 'proxyunbannable' ) && !in_array( $ip, $wgProxyWhitelist ) ) {
+               if ( !$block instanceof Block && $ip !== null && !$this->isAllowed( 'proxyunbannable' )
+                       && !in_array( $ip, $wgProxyWhitelist ) )
+               {
                        # Local list
                        if ( self::isLocallyBlockedProxy( $ip ) ) {
-                               $this->mBlockedby = wfMsg( 'proxyblocker' );
-                               $this->mBlockreason = wfMsg( 'proxyblockreason' );
+                               $block = new Block;
+                               $block->setBlocker( wfMsg( 'proxyblocker' ) );
+                               $block->mReason = wfMsg( 'proxyblockreason' );
+                               $block->setTarget( $ip );
+                       } elseif ( $this->isAnon() && $this->isDnsBlacklisted( $ip ) ) {
+                               $block = new Block;
+                               $block->setBlocker( wfMsg( 'sorbs' ) );
+                               $block->mReason = wfMsg( 'sorbsreason' );
+                               $block->setTarget( $ip );
                        }
+               }
 
-                       # DNSBL
-                       if ( !$this->mBlockedby && !$this->getID() ) {
-                               if ( $this->isDnsBlacklisted( $ip ) ) {
-                                       $this->mBlockedby = wfMsg( 'sorbs' );
-                                       $this->mBlockreason = wfMsg( 'sorbsreason' );
-                               }
-                       }
+               if ( $block instanceof Block ) {
+                       wfDebug( __METHOD__ . ": Found block.\n" );
+                       $this->mBlock = $block;
+                       $this->mBlockedby = $block->getByName();
+                       $this->mBlockreason = $block->mReason;
+                       $this->mHideName = $block->mHideName;
+                       $this->mAllowUsertalk = !$block->prevents( 'editownusertalk' );
+               } else {
+                       $this->mBlockedby = '';
+                       $this->mHideName = 0;
+                       $this->mAllowUsertalk = false;
                }
 
                # Extensions
@@ -1514,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();
@@ -1923,10 +1926,19 @@ class User {
                        $this->mTouched = self::newTouchedTimestamp();
 
                        $dbw = wfGetDB( DB_MASTER );
-                       $dbw->update( 'user',
-                               array( 'user_touched' => $dbw->timestamp( $this->mTouched ) ),
-                               array( 'user_id' => $this->mId ),
-                               __METHOD__ );
+
+                       // Prevent contention slams by checking user_touched first
+                       $now = $dbw->timestamp( $this->mTouched );
+                       $needsPurge = $dbw->selectField( 'user', '1',
+                               array( 'user_id' => $this->mId, 'user_touched < ' . $dbw->addQuotes( $now ) )
+                       );
+                       if ( $needsPurge ) {
+                               $dbw->update( 'user',
+                                       array( 'user_touched' => $now ),
+                                       array( 'user_id' => $this->mId, 'user_touched < ' . $dbw->addQuotes( $now ) ),
+                                       __METHOD__
+                               );
+                       }
 
                        $this->clearSharedCache();
                }
@@ -2020,10 +2032,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;
        }
 
@@ -2034,17 +2050,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;
                }
@@ -2113,6 +2121,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
@@ -2246,7 +2290,10 @@ class User {
         * Reset all options to the site defaults
         */
        public function resetOptions() {
+               $this->load();
+
                $this->mOptions = self::getDefaultOptions();
+               $this->mOptionsLoaded = true;
        }
 
        /**
@@ -2811,7 +2858,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 */
@@ -2877,7 +2924,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() ),
@@ -2914,7 +2961,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 ),
@@ -2976,7 +3023,7 @@ class User {
         */
        public function getPageRenderingHash() {
                wfDeprecated( __METHOD__, '1.17' );
-               
+
                global $wgUseDynamicDates, $wgRenderHashAppend, $wgLang, $wgContLang;
                if( $this->mHash ){
                        return $this->mHash;
@@ -3179,7 +3226,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 ) ) {
@@ -3194,10 +3241,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 );
        }
 
        /**
@@ -3304,9 +3351,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;
@@ -3327,7 +3374,7 @@ class User {
         * @return String New token URL
         */
        private function invalidationTokenUrl( $token ) {
-               return $this->getTokenUrl( 'Invalidateemail', $token );
+               return $this->getTokenUrl( 'InvalidateEmail', $token );
        }
 
        /**
@@ -3857,7 +3904,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 {
@@ -3889,7 +3936,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;
@@ -3944,7 +3991,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;
        }