Merge "Add $wgRateLimits types ip-all and subnet-all"
[lhc/web/wiklou.git] / includes / user / User.php
index b1f79ce..01afd97 100644 (file)
@@ -309,6 +309,15 @@ class User implements IDBAccessObject {
                return $this->getName();
        }
 
+       /**
+        * Test if it's safe to load this User object
+        * @return bool
+        */
+       public function isSafeToLoad() {
+               global $wgFullyInitialised;
+               return $wgFullyInitialised || $this->mLoadedItems === true || $this->mFrom !== 'session';
+       }
+
        /**
         * Load the user table data for this object from the source given by mFrom.
         *
@@ -327,7 +336,7 @@ class User implements IDBAccessObject {
                $this->queryFlagsUsed = $flags;
 
                // If this is called too early, things are likely to break.
-               if ( $this->mFrom === 'session' && empty( $wgFullyInitialised ) ) {
+               if ( !$wgFullyInitialised && $this->mFrom === 'session' ) {
                        \MediaWiki\Logger\LoggerFactory::getInstance( 'session' )
                                ->warning( 'User::loadFromSession called before the end of Setup.php', array(
                                        'exception' => new Exception( 'User::loadFromSession called before the end of Setup.php' ),
@@ -1156,7 +1165,7 @@ class User implements IDBAccessObject {
                        // Other code expects these to be set in the session, so set them.
                        $session->set( 'wsUserID', $this->getId() );
                        $session->set( 'wsUserName', $this->getName() );
-                       $session->set( 'wsToken', $this->mToken );
+                       $session->set( 'wsToken', $this->getToken() );
                        return true;
                }
 
@@ -1526,10 +1535,16 @@ class User implements IDBAccessObject {
                # 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
-               if ( !$this->isAllowed( 'ipblock-exempt' ) && $this->equals( $wgUser ) ) {
-                       $ip = $this->getRequest()->getIP();
-               } else {
-                       $ip = null;
+               $ip = null;
+               if ( !$this->isAllowed( 'ipblock-exempt' ) ) {
+                       // $wgUser->getName() only works after the end of Setup.php. Until
+                       // then, assume it's a logged-out user.
+                       $globalUserName = $wgUser->isSafeToLoad()
+                               ? $wgUser->getName()
+                               : IP::sanitizeIP( $wgUser->getRequest()->getIP() );
+                       if ( $this->getName() === $globalUserName ) {
+                               $ip = $this->getRequest()->getIP();
+                       }
                }
 
                // User/IP blocking
@@ -1738,40 +1753,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'];
                        }
+                       // 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'];
                                }
                        }
                }
+
                // 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
@@ -1781,6 +1799,7 @@ class User implements IDBAccessObject {
                                }
                        }
                }
+
                // Set the user limit key
                if ( $userLimit !== false ) {
                        list( $max, $period ) = $userLimit;
@@ -1788,6 +1807,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;
@@ -2429,14 +2472,33 @@ class User implements IDBAccessObject {
         * Get the user's current token.
         * @param bool $forceCreation Force the generation of a new token if the
         *   user doesn't have one (default=true for backwards compatibility).
-        * @return string Token
+        * @return string|null Token
         */
        public function getToken( $forceCreation = true ) {
+               global $wgAuthenticationTokenVersion;
+
                $this->load();
                if ( !$this->mToken && $forceCreation ) {
                        $this->setToken();
                }
-               return $this->mToken;
+
+               // 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 ) {
+                       return null;
+               } elseif ( $wgAuthenticationTokenVersion === null ) {
+                       return $this->mToken;
+               } else {
+                       $ret = MWCryptHash::hmac( $wgAuthenticationTokenVersion, $this->mToken, false );
+
+                       // The raw hash can be overly long. Shorten it up.
+                       $len = max( 32, self::TOKEN_LENGTH );
+                       if ( strlen( $ret ) < $len ) {
+                               // Should never happen, even md5 is 128 bits
+                               throw new \UnexpectedValueException( 'Hmac returned less than 128 bits' );
+                       }
+                       return substr( $ret, -$len );
+               }
        }
 
        /**