resourceloader: Remove obsolete '$that = $this' closure pattern
[lhc/web/wiklou.git] / includes / user / User.php
index 3635f5c..6638fb7 100644 (file)
@@ -45,6 +45,11 @@ class User implements IDBAccessObject {
         */
        const TOKEN_LENGTH = 32;
 
+       /**
+        * @const string An invalid value for user_token
+        */
+       const INVALID_TOKEN = '*** INVALID ***';
+
        /**
         * Global constant made accessible as class constants so that autoloader
         * magic can be used.
@@ -310,7 +315,14 @@ class User implements IDBAccessObject {
        }
 
        /**
-        * Test if it's safe to load this User object
+        * Test if it's safe to load this User object. You should typically check this before using
+        * $wgUser or RequestContext::getUser in a method that might be called before the system has
+        * been fully initialized. If the object is unsafe, you should use an anonymous user:
+        * \code
+        * $user = $wgUser->isSafeToLoad() ? $wgUser : new User;
+        * \endcode
+        *
+        * @since 1.27
         * @return bool
         */
        public function isSafeToLoad() {
@@ -650,7 +662,8 @@ class User implements IDBAccessObject {
                $user = self::newFromRow( $row );
 
                // A user is considered to exist as a non-system user if it has a
-               // password set, or a temporary password set, or an email set.
+               // password set, or a temporary password set, or an email set, or a
+               // non-invalid token.
                $passwordFactory = new PasswordFactory();
                $passwordFactory->init( RequestContext::getMain()->getConfig() );
                try {
@@ -666,7 +679,7 @@ class User implements IDBAccessObject {
                        $newpassword = PasswordFactory::newInvalidPassword();
                }
                if ( !$password instanceof InvalidPassword || !$newpassword instanceof InvalidPassword
-                       || $user->mEmail
+                       || $user->mEmail || $user->mToken !== self::INVALID_TOKEN
                ) {
                        // User exists. Steal it?
                        if ( !$options['steal'] ) {
@@ -686,11 +699,11 @@ class User implements IDBAccessObject {
                                __METHOD__
                        );
                        $user->invalidateEmail();
+                       $user->mToken = self::INVALID_TOKEN;
                        $user->saveSettings();
+                       SessionManager::singleton()->preventSessionsForUser( $user->getName() );
                }
 
-               SessionManager::singleton()->preventSessionsForUser( $user->getName() );
-
                return $user;
        }
 
@@ -1753,40 +1766,43 @@ class User implements IDBAccessObject {
                $keys = array();
                $id = $this->getId();
                $userLimit = false;
+               $isNewbie = $this->isNewbie();
 
-               if ( isset( $limits['anon'] ) && $id == 0 ) {
-                       $keys[wfMemcKey( 'limiter', $action, 'anon' )] = $limits['anon'];
-               }
-
-               if ( isset( $limits['user'] ) && $id != 0 ) {
-                       $userLimit = $limits['user'];
-               }
-               if ( $this->isNewbie() ) {
-                       if ( isset( $limits['newbie'] ) && $id != 0 ) {
+               if ( $id == 0 ) {
+                       // limits for anons
+                       if ( isset( $limits['anon'] ) ) {
+                               $keys[wfMemcKey( 'limiter', $action, 'anon' )] = $limits['anon'];
+                       }
+               } else {
+                       // limits for logged-in users
+                       if ( isset( $limits['user'] ) ) {
+                               $userLimit = $limits['user'];
+                       }
+                       // limits for newbie logged-in users
+                       if ( $isNewbie && isset( $limits['newbie'] ) ) {
                                $keys[wfMemcKey( 'limiter', $action, 'user', $id )] = $limits['newbie'];
                        }
+               }
+
+               // limits for anons and for newbie logged-in users
+               if ( $isNewbie ) {
+                       // ip-based limits
                        if ( isset( $limits['ip'] ) ) {
                                $ip = $this->getRequest()->getIP();
-                               $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
+                               $keys[wfMemcKey( 'limiter', $action, 'ip', $ip )] = $limits['ip'];
                        }
+                       // subnet-based limits
                        if ( isset( $limits['subnet'] ) ) {
                                $ip = $this->getRequest()->getIP();
-                               $matches = array();
-                               $subnet = false;
-                               if ( IP::isIPv6( $ip ) ) {
-                                       $parts = IP::parseRange( "$ip/64" );
-                                       $subnet = $parts[0];
-                               } elseif ( preg_match( '/^(\d+\.\d+\.\d+)\.\d+$/', $ip, $matches ) ) {
-                                       // IPv4
-                                       $subnet = $matches[1];
-                               }
+                               $subnet = IP::getSubnet( $ip );
                                if ( $subnet !== false ) {
-                                       $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
+                                       $keys[wfMemcKey( 'limiter', $action, 'subnet', $subnet )] = $limits['subnet'];
                                }
                        }
                }
+
                // Check for group-specific permissions
-               // If more than one group applies, use the group with the highest limit
+               // If more than one group applies, use the group with the highest limit ratio (max/period)
                foreach ( $this->getGroups() as $group ) {
                        if ( isset( $limits[$group] ) ) {
                                if ( $userLimit === false
@@ -1796,6 +1812,7 @@ class User implements IDBAccessObject {
                                }
                        }
                }
+
                // Set the user limit key
                if ( $userLimit !== false ) {
                        list( $max, $period ) = $userLimit;
@@ -1803,6 +1820,30 @@ class User implements IDBAccessObject {
                        $keys[wfMemcKey( 'limiter', $action, 'user', $id )] = $userLimit;
                }
 
+               // ip-based limits for all ping-limitable users
+               if ( isset( $limits['ip-all'] ) ) {
+                       $ip = $this->getRequest()->getIP();
+                       // ignore if user limit is more permissive
+                       if ( $isNewbie || $userLimit === false
+                               || $limits['ip-all'][0] / $limits['ip-all'][1] > $userLimit[0] / $userLimit[1] ) {
+                               $keys["mediawiki:limiter:$action:ip-all:$ip"] = $limits['ip-all'];
+                       }
+               }
+
+               // subnet-based limits for all ping-limitable users
+               if ( isset( $limits['subnet-all'] ) ) {
+                       $ip = $this->getRequest()->getIP();
+                       $subnet = IP::getSubnet( $ip );
+                       if ( $subnet !== false ) {
+                               // ignore if user limit is more permissive
+                               if ( $isNewbie || $userLimit === false
+                                       || $limits['ip-all'][0] / $limits['ip-all'][1]
+                                       > $userLimit[0] / $userLimit[1] ) {
+                                       $keys["mediawiki:limiter:$action:subnet-all:$subnet"] = $limits['subnet-all'];
+                               }
+                       }
+               }
+
                $cache = ObjectCache::getLocalClusterInstance();
 
                $triggered = false;
@@ -2454,13 +2495,18 @@ class User implements IDBAccessObject {
                        $this->setToken();
                }
 
-               // If the user doesn't have a token, return null to indicate that.
-               // Otherwise, hmac the version with the secret if we have a version.
                if ( !$this->mToken ) {
+                       // The user doesn't have a token, return null to indicate that.
                        return null;
+               } elseif ( $this->mToken === self::INVALID_TOKEN ) {
+                       // We return a random value here so existing token checks are very
+                       // likely to fail.
+                       return MWCryptRand::generateHex( self::TOKEN_LENGTH );
                } elseif ( $wgAuthenticationTokenVersion === null ) {
+                       // $wgAuthenticationTokenVersion not in use, so return the raw secret
                        return $this->mToken;
                } else {
+                       // $wgAuthenticationTokenVersion in use, so hmac it.
                        $ret = MWCryptHash::hmac( $wgAuthenticationTokenVersion, $this->mToken, false );
 
                        // The raw hash can be overly long. Shorten it up.
@@ -2481,7 +2527,10 @@ class User implements IDBAccessObject {
         */
        public function setToken( $token = false ) {
                $this->load();
-               if ( !$token ) {
+               if ( $this->mToken === self::INVALID_TOKEN ) {
+                       \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
+                               ->debug( __METHOD__ . ": Ignoring attempt to set token for system user \"$this\"" );
+               } elseif ( !$token ) {
                        $this->mToken = MWCryptRand::generateHex( self::TOKEN_LENGTH );
                } else {
                        $this->mToken = $token;