Merge "(bug 18195) Allow changing preferences via API"
[lhc/web/wiklou.git] / includes / User.php
index e3e2c9a..793714d 100644 (file)
@@ -115,7 +115,6 @@ class User {
                'deletedhistory',
                'deletedtext',
                'deleterevision',
-               'disableaccount',
                'edit',
                'editinterface',
                'editusercssjs', #deprecated
@@ -143,13 +142,11 @@ class User {
                'reupload',
                'reupload-shared',
                'rollback',
-               'selenium',
                'sendemail',
                'siteadmin',
                'suppressionlog',
                'suppressredirect',
                'suppressrevision',
-               'trackback',
                'unblockself',
                'undelete',
                'unwatchedpages',
@@ -168,8 +165,8 @@ class User {
        //@{
        var $mId, $mName, $mRealName, $mPassword, $mNewpassword, $mNewpassTime,
                $mEmail, $mTouched, $mToken, $mEmailAuthenticated,
-               $mEmailToken, $mEmailTokenExpires, $mRegistration, $mGroups, $mOptionOverrides,
-               $mCookiePassword, $mEditCount, $mAllowUsertalk;
+               $mEmailToken, $mEmailTokenExpires, $mRegistration, $mEditCount,
+               $mGroups, $mOptionOverrides;
        //@}
 
        /**
@@ -212,6 +209,11 @@ class User {
         */
        var $mBlock;
 
+       /**
+        * @var bool
+        */
+       var $mAllowUsertalk;
+
        /**
         * @var Block
         */
@@ -394,7 +396,7 @@ class User {
         * If the code is invalid or has expired, returns NULL.
         *
         * @param $code String Confirmation code
-        * @return User
+        * @return User object, or null
         */
        public static function newFromConfirmationCode( $code ) {
                $dbr = wfGetDB( DB_SLAVE );
@@ -415,7 +417,7 @@ class User {
         *
         * @param $request WebRequest object to use; $wgRequest will be used if
         *        ommited.
-        * @return User
+        * @return User object
         */
        public static function newFromSession( WebRequest $request = null ) {
                $user = new User;
@@ -448,7 +450,7 @@ class User {
        /**
         * Get the username corresponding to a given user ID
         * @param $id Int User ID
-        * @return String|false The corresponding username
+        * @return String|bool The corresponding username
         */
        public static function whoIs( $id ) {
                $dbr = wfGetDB( DB_SLAVE );
@@ -459,7 +461,7 @@ class User {
         * Get the real name of a user given their user ID
         *
         * @param $id Int User ID
-        * @return String|false The corresponding user's real name
+        * @return String|bool The corresponding user's real name
         */
        public static function whoIsReal( $id ) {
                $dbr = wfGetDB( DB_SLAVE );
@@ -551,6 +553,7 @@ class User {
                        return false;
                }
 
+
                // Ensure that the name can't be misresolved as a different title,
                // such as with extra namespace keys at the start.
                $parsed = Title::newFromText( $name );
@@ -732,6 +735,7 @@ class User {
         * @deprecated since 1.18 call Sanitizer::isValidEmail() directly
         */
        public static function isValidEmailAddr( $addr ) {
+               wfDeprecated( __METHOD__, '1.18' );
                return Sanitizer::validateEmail( $addr );
        }
 
@@ -832,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 );
        }
 
        /**
@@ -878,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;
@@ -985,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
@@ -1094,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 );
@@ -1218,6 +1222,12 @@ class User {
                }
                $defOpt['skin'] = $wgDefaultSkin;
 
+               // FIXME: Ideally we'd cache the results of this function so the hook is only run once,
+               // but that breaks the parser tests because they rely on being able to change $wgContLang
+               // mid-request and see that change reflected in the return value of this function.
+               // Which is insane and would never happen during normal MW operation, but is also not
+               // likely to get fixed unless and until we context-ify everything.
+               // See also https://www.mediawiki.org/wiki/Special:Code/MediaWiki/101488#c25275
                wfRunHooks( 'UserGetDefaultOptions', array( &$defOpt ) );
 
                return $defOpt;
@@ -1263,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
@@ -1277,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->getName(), $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
@@ -1504,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();
@@ -2010,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;
        }
 
@@ -2024,32 +2041,14 @@ 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;
                }
        }
 
-       /**
-        * Set the cookie password
-        *
-        * @param $str String New cookie password
-        */
-       private function setCookiePassword( $str ) {
-               $this->load();
-               $this->mCookiePassword = md5( $str );
-       }
-
        /**
         * Set the password for a password reminder or new account email
         *
@@ -2105,10 +2104,50 @@ class User {
         */
        public function setEmail( $str ) {
                $this->load();
+               if( $str == $this->mEmail ) {
+                       return;
+               }
                $this->mEmail = $str;
+               $this->invalidateEmail();
                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
@@ -2242,7 +2281,10 @@ class User {
         * Reset all options to the site defaults
         */
        public function resetOptions() {
+               $this->load();
+
                $this->mOptions = self::getDefaultOptions();
+               $this->mOptionsLoaded = true;
        }
 
        /**
@@ -2281,29 +2323,16 @@ class User {
 
        /**
         * Get the permissions this user has.
-        * @param $ns int If numeric, get permissions for this namespace
         * @return Array of String permission names
         */
-       public function getRights( $ns = null ) {
-               $key = is_null( $ns ) ? '*' : intval( $ns );
-
+       public function getRights() {
                if ( is_null( $this->mRights ) ) {
-                       $this->mRights = array();
-               }
-
-               if ( !isset( $this->mRights[$key] ) ) {
-                       $this->mRights[$key] = self::getGroupPermissions( $this->getEffectiveGroups(), $ns );
-                       wfRunHooks( 'UserGetRights', array( $this, &$this->mRights[$key], $ns ) );
+                       $this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() );
+                       wfRunHooks( 'UserGetRights', array( $this, &$this->mRights ) );
                        // Force reindexation of rights when a hook has unset one of them
-                       $this->mRights[$key] = array_values( $this->mRights[$key] );
-               }
-               if ( is_null( $ns ) ) {
-                       return $this->mRights[$key];
-               } else {
-                       // Merge non namespace specific rights
-                       return array_merge( $this->mRights[$key], $this->getRights() );
+                       $this->mRights = array_values( $this->mRights );
                }
-
+               return $this->mRights;
        }
 
        /**
@@ -2428,7 +2457,7 @@ class User {
                }
                $this->loadGroups();
                $this->mGroups[] = $group;
-               $this->mRights = null;
+               $this->mRights = User::getGroupPermissions( $this->getEffectiveGroups( true ) );
 
                $this->invalidateCache();
        }
@@ -2458,7 +2487,7 @@ class User {
                }
                $this->loadGroups();
                $this->mGroups = array_diff( $this->mGroups, array( $group ) );
-               $this->mRights = null;
+               $this->mRights = User::getGroupPermissions( $this->getEffectiveGroups( true ) );
 
                $this->invalidateCache();
        }
@@ -2515,10 +2544,9 @@ class User {
        /**
         * Internal mechanics of testing a permission
         * @param $action String
-        * @param $ns int|null Namespace optional
         * @return bool
         */
-       public function isAllowed( $action = '', $ns = null ) {
+       public function isAllowed( $action = '' ) {
                if ( $action === '' ) {
                        return true; // In the spirit of DWIM
                }
@@ -2530,7 +2558,7 @@ class User {
                }
                # Use strict parameter to avoid matching numeric 0 accidentally inserted
                # by misconfiguration: 0 == 'foo'
-               return in_array( $action, $this->getRights( $ns ), true );
+               return in_array( $action, $this->getRights(), true );
        }
 
        /**
@@ -2572,6 +2600,7 @@ class User {
         * @deprecated since 1.18 Use ->getSkin() in the most relevant outputting context you have
         */
        public function getSkin() {
+               wfDeprecated( __METHOD__, '1.18' );
                return RequestContext::getMain()->getSkin();
        }
 
@@ -2605,14 +2634,6 @@ class User {
                $this->invalidateCache();
        }
 
-       /**
-        * Cleans up watchlist by removing invalid entries from it
-        */
-       public function cleanupWatchlist() {
-               $dbw = wfGetDB( DB_MASTER );
-               $dbw->delete( 'watchlist', array( 'wl_namespace < 0', 'wl_user' => $this->getId() ), __METHOD__ );
-       }
-
        /**
         * Clear the user's notification timestamp for the given title.
         * If e-notif e-mails are on, they will receive notification mails on
@@ -2647,28 +2668,15 @@ class User {
                // The query to find out if it is watched is cached both in memcached and per-invocation,
                // and when it does have to be executed, it can be on a slave
                // If this is the user's newtalk page, we always update the timestamp
-               if( $title->getNamespace() == NS_USER_TALK &&
+               $force = '';
+               if ( $title->getNamespace() == NS_USER_TALK &&
                        $title->getText() == $this->getName() )
                {
-                       $watched = true;
-               } else {
-                       $watched = $this->isWatched( $title );
+                       $force = 'force';
                }
 
-               // If the page is watched by the user (or may be watched), update the timestamp on any
-               // any matching rows
-               if ( $watched ) {
-                       $dbw = wfGetDB( DB_MASTER );
-                       $dbw->update( 'watchlist',
-                                       array( /* SET */
-                                               'wl_notificationtimestamp' => null
-                                       ), array( /* WHERE */
-                                               'wl_title' => $title->getDBkey(),
-                                               'wl_namespace' => $title->getNamespace(),
-                                               'wl_user' => $this->getID()
-                                       ), __METHOD__
-                       );
-               }
+               $wi = WatchedItem::fromUserTitle( $this, $title );
+               $wi->resetNotificationTimestamp( $force );
        }
 
        /**
@@ -2704,6 +2712,7 @@ class User {
         * @deprecated in 1.19 due to removal of user_options from the user table
         */
        private function decodeOptions( $str ) {
+               wfDeprecated( __METHOD__, '1.19' );
                if( !$str )
                        return;
 
@@ -2756,6 +2765,14 @@ class User {
 
                $this->load();
                if ( 0 == $this->mId ) return;
+               if ( !$this->mToken ) {
+                       // When token is empty or NULL generate a new one and then save it to the database
+                       // This allows a wiki to re-secure itself after a leak of it's user table or $wgSecretKey
+                       // Simply by setting every cell in the user_token column to NULL and letting them be
+                       // regenerated as users log back into the wiki.
+                       $this->setToken();
+                       $this->saveSettings();
+               }
                $session = array(
                        'wsUserID' => $this->mId,
                        'wsToken' => $this->mToken,
@@ -2832,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 */
@@ -2898,9 +2915,10 @@ 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() ),
                );
                foreach ( $params as $name => $value ) {
                        $fields["user_$name"] = $value;
@@ -2919,6 +2937,9 @@ class User {
         */
        public function addToDatabase() {
                $this->load();
+
+               $this->mTouched = self::newTouchedTimestamp();
+
                $dbw = wfGetDB( DB_MASTER );
                $seqVal = $dbw->nextSequenceValue( 'user_user_id_seq' );
                $dbw->insert( 'user',
@@ -2931,9 +2952,10 @@ 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 ),
                        ), __METHOD__
                );
                $this->mId = $dbw->insertId();
@@ -2991,11 +3013,12 @@ class User {
         * @return String Page rendering hash
         */
        public function getPageRenderingHash() {
+               wfDeprecated( __METHOD__, '1.17' );
+               
                global $wgUseDynamicDates, $wgRenderHashAppend, $wgLang, $wgContLang;
                if( $this->mHash ){
                        return $this->mHash;
                }
-               wfDeprecated( __METHOD__ );
 
                // stubthreshold is only included below for completeness,
                // since it disables the parser cache, its value will always
@@ -3161,16 +3184,17 @@ class User {
 
        /**
         * Alias for getEditToken.
-        * @deprecated since 1.19, use getEditToken instead. 
-        * 
+        * @deprecated since 1.19, use getEditToken instead.
+        *
         * @param $salt String|Array of Strings Optional function-specific data for hashing
         * @param $request WebRequest object to use or null to use $wgRequest
         * @return String The new edit token
         */
        public function editToken( $salt = '', $request = null ) {
+               wfDeprecated( __METHOD__, '1.19' );
                return $this->getEditToken( $salt, $request );
        }
-       
+
        /**
         * Initialize (if necessary) and return a session token value
         * which can be used in edit forms to show that the user's
@@ -3193,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 ) ) {
@@ -3208,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 );
        }
 
        /**
@@ -3226,7 +3250,7 @@ class User {
         * @return Boolean: Whether the token matches
         */
        public function matchEditToken( $val, $salt = '', $request = null ) {
-               $sessionToken = $this->editToken( $salt, $request );
+               $sessionToken = $this->getEditToken( $salt, $request );
                if ( $val != $sessionToken ) {
                        wfDebug( "User::matchEditToken: broken session data\n" );
                }
@@ -3243,7 +3267,7 @@ class User {
         * @return Boolean: Whether the token matches
         */
        public function matchEditTokenNoSuffix( $val, $salt = '', $request = null ) {
-               $sessionToken = $this->editToken( $salt, $request );
+               $sessionToken = $this->getEditToken( $salt, $request );
                return substr( $sessionToken, 0, 32 ) == substr( $val, 0, 32 );
        }
 
@@ -3318,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;
@@ -3369,7 +3393,7 @@ class User {
         *
         * @note Call saveSettings() after calling this function to commit the change.
         *
-        * @return true
+        * @return bool
         */
        public function confirmEmail() {
                $this->setEmailAuthenticationTimestamp( wfTimestampNow() );
@@ -3382,7 +3406,7 @@ class User {
         * address if it was already confirmed.
         *
         * @note Call saveSettings() after calling this function to commit the change.
-        * @return true
+        * @return bool Returns true
         */
        function invalidateEmail() {
                $this->load();
@@ -3509,70 +3533,40 @@ class User {
         * Get the permissions associated with a given list of groups
         *
         * @param $groups Array of Strings List of internal group names
-        * @param $ns int
-        *
         * @return Array of Strings List of permission key names for given groups combined
         */
-       public static function getGroupPermissions( array $groups, $ns = null ) {
+       public static function getGroupPermissions( $groups ) {
                global $wgGroupPermissions, $wgRevokePermissions;
                $rights = array();
-
-               // Grant every granted permission first
+               // grant every granted permission first
                foreach( $groups as $group ) {
                        if( isset( $wgGroupPermissions[$group] ) ) {
-                               $rights = array_merge( $rights, self::extractRights(
-                                       $wgGroupPermissions[$group], $ns ) );
+                               $rights = array_merge( $rights,
+                                       // array_filter removes empty items
+                                       array_keys( array_filter( $wgGroupPermissions[$group] ) ) );
                        }
                }
-
-               // Revoke the revoked permissions
+               // now revoke the revoked permissions
                foreach( $groups as $group ) {
                        if( isset( $wgRevokePermissions[$group] ) ) {
-                               $rights = array_diff( $rights, self::extractRights(
-                                       $wgRevokePermissions[$group], $ns ) );
+                               $rights = array_diff( $rights,
+                                       array_keys( array_filter( $wgRevokePermissions[$group] ) ) );
                        }
                }
                return array_unique( $rights );
        }
 
-       /**
-        * Helper for User::getGroupPermissions
-        * @param $list array
-        * @param $ns int
-        * @return array
-        */
-       private static function extractRights( $list, $ns ) {
-               $rights = array();
-               foreach( $list as $right => $value ) {
-                       if ( is_array( $value ) ) {
-                               # This is a list of namespaces where the permission applies
-                               if ( !is_null( $ns ) && !empty( $value[$ns] ) ) {
-                                       $rights[] = $right;
-                               }
-                       } else {
-                               # This is a boolean indicating that the permission applies
-                               if ( $value ) {
-                                       $rights[] = $right;
-                               }
-                       }
-               }
-               return $rights;
-       }
-
        /**
         * Get all the groups who have a given permission
         *
         * @param $role String Role to check
-        * @param $ns int
-        *
-        *
         * @return Array of Strings List of internal group names with the given permission
         */
-       public static function getGroupsWithPermission( $role, $ns = null ) {
+       public static function getGroupsWithPermission( $role ) {
                global $wgGroupPermissions;
                $allowedGroups = array();
                foreach ( $wgGroupPermissions as $group => $rights ) {
-                       if ( in_array( $role, self::getGroupPermissions( array( $group ), $ns ), true ) ) {
+                       if ( isset( $rights[$role] ) && $rights[$role] ) {
                                $allowedGroups[] = $group;
                        }
                }
@@ -3901,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 {
@@ -3933,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;
@@ -3941,7 +3935,7 @@ class User {
        }
 
        /**
-        * Add a newuser log entry for this user
+        * Add a newuser log entry for this user. Before 1.19 the return value was always true.
         *
         * @param $byEmail Boolean: account made by email?
         * @param $reason String: user supplied reason
@@ -3980,7 +3974,7 @@ class User {
         * Add an autocreate newuser log entry for this user
         * Used by things like CentralAuth and perhaps other authplugins.
         *
-        * @return true
+        * @return bool
         */
        public function addNewUserLogEntryAutoCreate() {
                global $wgNewUserLog;
@@ -3988,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;
        }
 
@@ -4020,6 +4014,7 @@ class User {
                                __METHOD__
                        );
 
+                       $this->mOptionOverrides = array();
                        foreach ( $res as $row ) {
                                $this->mOptionOverrides[$row->up_property] = $row->up_value;
                                $this->mOptions[$row->up_property] = $row->up_value;
@@ -4075,10 +4070,8 @@ class User {
                        }
                }
 
-               $dbw->begin();
                $dbw->delete( 'user_properties', array( 'up_user' => $this->getId() ), __METHOD__ );
                $dbw->insert( 'user_properties', $insert_rows, __METHOD__ );
-               $dbw->commit();
        }
 
        /**