Merge "Try to fix some other broken-looking legacy maintenance script options"
[lhc/web/wiklou.git] / includes / specials / SpecialUserlogin.php
index 442eee4..1ed8407 100644 (file)
@@ -21,6 +21,7 @@
  * @ingroup SpecialPage
  */
 use MediaWiki\Logger\LoggerFactory;
+use Psr\Log\LogLevel;
 use MediaWiki\Session\SessionManager;
 
 /**
@@ -86,6 +87,11 @@ class LoginForm extends SpecialPage {
        ];
 
        public $mAbortLoginErrorMsg = null;
+       /**
+        * @var int How many seconds user is throttled for
+        * @since 1.27
+        */
+       public $mThrottleWait = '?';
 
        protected $mUsername;
        protected $mPassword;
@@ -269,7 +275,7 @@ class LoginForm extends SpecialPage {
         */
        public function execute( $subPage ) {
                // Make sure session is persisted
-               $session = MediaWiki\Session\SessionManager::getGlobalSession();
+               $session = SessionManager::getGlobalSession();
                $session->persist();
 
                $this->load();
@@ -708,7 +714,7 @@ class LoginForm extends SpecialPage {
                DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 0, 0, 0, 1 ) );
 
                // Watch user's userpage and talk page
-               $u->addWatch( $u->getUserPage(), WatchedItem::IGNORE_USER_RIGHTS );
+               $u->addWatch( $u->getUserPage(), User::IGNORE_USER_RIGHTS );
 
                return Status::newGood( $u );
        }
@@ -745,8 +751,9 @@ class LoginForm extends SpecialPage {
                        return self::NEED_TOKEN;
                }
 
-               $throttleCount = self::incLoginThrottle( $this->mUsername );
-               if ( $throttleCount === true ) {
+               $throttleCount = self::incrementLoginThrottle( $this->mUsername );
+               if ( $throttleCount ) {
+                       $this->mThrottleWait = $throttleCount['wait'];
                        return self::THROTTLED;
                }
 
@@ -862,9 +869,7 @@ class LoginForm extends SpecialPage {
                        $this->getContext()->setUser( $u );
 
                        // Please reset throttle for successful logins, thanks!
-                       if ( $throttleCount ) {
-                               self::clearLoginThrottle( $this->mUsername );
-                       }
+                       self::clearLoginThrottle( $this->mUsername );
 
                        if ( $isAutoCreated ) {
                                // Must be run after $wgUser is set, for correct new user log
@@ -881,31 +886,90 @@ class LoginForm extends SpecialPage {
        /**
         * Increment the login attempt throttle hit count for the (username,current IP)
         * tuple unless the throttle was already reached.
+        *
+        * @since 1.27 Return value changed.
         * @param string $username The user name
-        * @return bool|int The integer hit count or True if it is already at the limit
+        * @return bool|array false if below limit or an array if above limit
+        *   Array contains keys wait, count, and throttleIndex
         */
-       public static function incLoginThrottle( $username ) {
+       public static function incrementLoginThrottle( $username ) {
                global $wgPasswordAttemptThrottle, $wgRequest;
-               $username = trim( $username ); // sanity
+               $username = User::getCanonicalName( $username, 'usable' ) ?: $username;
 
                $throttleCount = 0;
                if ( is_array( $wgPasswordAttemptThrottle ) ) {
-                       $throttleKey = wfGlobalCacheKey( 'password-throttle', $wgRequest->getIP(), md5( $username ) );
-                       $count = $wgPasswordAttemptThrottle['count'];
-                       $period = $wgPasswordAttemptThrottle['seconds'];
-
-                       $cache = ObjectCache::getLocalClusterInstance();
-                       $throttleCount = $cache->get( $throttleKey );
-                       if ( !$throttleCount ) {
-                               $cache->add( $throttleKey, 1, $period ); // start counter
-                       } elseif ( $throttleCount < $count ) {
-                               $cache->incr( $throttleKey );
-                       } elseif ( $throttleCount >= $count ) {
-                               return true;
+                       $throttleConfig = $wgPasswordAttemptThrottle;
+                       if ( isset( $wgPasswordAttemptThrottle['count'] ) ) {
+                               // old style. Convert for backwards compat.
+                               $throttleConfig = [ $wgPasswordAttemptThrottle ];
+                       }
+                       foreach ( $throttleConfig as $index => $specificThrottle ) {
+                               if ( isset( $specificThrottle['allIPs'] ) ) {
+                                       $ip = 'All';
+                               } else {
+                                       $ip = $wgRequest->getIP();
+                               }
+                               $throttleKey = wfGlobalCacheKey( 'password-throttle',
+                                       $index, $ip, md5( $username )
+                               );
+                               $count = $specificThrottle['count'];
+                               $period = $specificThrottle['seconds'];
+
+                               $cache = ObjectCache::getLocalClusterInstance();
+                               $throttleCount = $cache->get( $throttleKey );
+                               if ( !$throttleCount ) {
+                                       $cache->add( $throttleKey, 1, $period ); // start counter
+                               } elseif ( $throttleCount < $count ) {
+                                       $cache->incr( $throttleKey );
+                               } elseif ( $throttleCount >= $count ) {
+                                       $logMsg = 'Login attempt rejected because logins to '
+                                               . '{acct} from IP {ip} have been throttled for '
+                                               . '{period} seconds due to {count} failed attempts';
+                                       // If we are hitting a throttle for >= 50 attempts,
+                                       // it is much more likely to be an attack than someone
+                                       // simply forgetting their password, so log it at a
+                                       // higher level.
+                                       $level = $count >= 50 ? LogLevel::WARNING : LogLevel::INFO;
+                                       // It should be noted that once the throttle is hit,
+                                       // every attempt to login will generate the log message
+                                       // until the throttle expires, not just the attempt that
+                                       // puts the throttle over the top.
+                                       LoggerFactory::getInstance( 'password-throttle' )->log(
+                                               $level,
+                                               $logMsg,
+                                               [
+                                                       'ip' => $ip,
+                                                       'period' => $period,
+                                                       'acct' => $username,
+                                                       'count' => $count,
+                                                       'throttleIdentifier' => $index,
+                                                       'method' => __METHOD__
+                                               ]
+                                       );
+
+                                       return [
+                                               'throttleIndex' => $index,
+                                               'wait' => $period,
+                                               'count' => $count
+                                       ];
+                               }
                        }
                }
+               return false;
+       }
 
-               return $throttleCount;
+       /**
+        * Increment the login attempt throttle hit count for the (username,current IP)
+        * tuple unless the throttle was already reached.
+        *
+        * @deprecated Use LoginForm::incrementLoginThrottle instead
+        * @param string $username The user name
+        * @return bool|int true if above throttle, or 0 (prior to 1.27, returned current count)
+        */
+       public static function incLoginThrottle( $username ) {
+               wfDeprecated( __METHOD__, "1.27" );
+               $res = self::incrementLoginThrottle( $username );
+               return is_array( $res ) ? true : 0;
        }
 
        /**
@@ -914,11 +978,27 @@ class LoginForm extends SpecialPage {
         * @return void
         */
        public static function clearLoginThrottle( $username ) {
-               global $wgRequest;
-               $username = trim( $username ); // sanity
+               global $wgRequest, $wgPasswordAttemptThrottle;
+               $username = User::getCanonicalName( $username, 'usable' ) ?: $username;
 
-               $throttleKey = wfGlobalCacheKey( 'password-throttle', $wgRequest->getIP(), md5( $username ) );
-               ObjectCache::getLocalClusterInstance()->delete( $throttleKey );
+               if ( is_array( $wgPasswordAttemptThrottle ) ) {
+                       $throttleConfig = $wgPasswordAttemptThrottle;
+                       if ( isset( $wgPasswordAttemptThrottle['count'] ) ) {
+                               // old style. Convert for backwards compat.
+                               $throttleConfig = [ $wgPasswordAttemptThrottle ];
+                       }
+                       foreach ( $throttleConfig as $index => $specificThrottle ) {
+                               if ( isset( $specificThrottle['allIPs'] ) ) {
+                                       $ip = 'All';
+                               } else {
+                                       $ip = $wgRequest->getIP();
+                               }
+                               $throttleKey = wfGlobalCacheKey( 'password-throttle', $index,
+                                       $ip, md5( $username )
+                               );
+                               ObjectCache::getLocalClusterInstance()->delete( $throttleKey );
+                       }
+               }
        }
 
        /**
@@ -977,7 +1057,7 @@ class LoginForm extends SpecialPage {
        }
 
        function processLogin() {
-               global $wgLang, $wgSecureLogin, $wgPasswordAttemptThrottle, $wgInvalidPasswordReset;
+               global $wgLang, $wgSecureLogin, $wgInvalidPasswordReset;
 
                $cache = ObjectCache::getLocalClusterInstance();
                $authRes = $this->authenticateUserData();
@@ -999,10 +1079,9 @@ class LoginForm extends SpecialPage {
                                self::clearLoginToken();
 
                                // Reset the throttle
-                               $request = $this->getRequest();
-                               $key = wfGlobalCacheKey( 'password-throttle', $request->getIP(), md5( $this->mUsername ) );
-                               $cache->delete( $key );
+                               self::clearLoginThrottle( $this->mUsername );
 
+                               $request = $this->getRequest();
                                if ( $this->hasSessionCookie() || $this->mSkipCookieCheck ) {
                                        /* Replace the language object to provide user interface in
                                         * correct language immediately on this first page load.
@@ -1010,6 +1089,7 @@ class LoginForm extends SpecialPage {
                                        $code = $request->getVal( 'uselang', $user->getOption( 'language' ) );
                                        $userLang = Language::factory( $code );
                                        $wgLang = $userLang;
+                                       RequestContext::getMain()->setLanguage( $userLang );
                                        $this->getContext()->setLanguage( $userLang );
                                        // Reset SessionID on Successful login (bug 40995)
                                        $this->renewSessionId();
@@ -1079,8 +1159,7 @@ class LoginForm extends SpecialPage {
                        case self::THROTTLED:
                                $error = $this->mAbortLoginErrorMsg ?: 'login-throttled';
                                $this->mainLoginForm( $this->msg( $error )
-                                       ->params( $this->getLanguage()->formatDuration( $wgPasswordAttemptThrottle['seconds'] ) )
-                                       ->text()
+                                       ->durationParams( $this->mThrottleWait )->text()
                                );
                                break;
                        case self::USER_BLOCKED:
@@ -1637,7 +1716,7 @@ class LoginForm extends SpecialPage {
                        $wgCookieSecure = false;
                }
 
-               MediaWiki\Session\SessionManager::getGlobalSession()->resetId();
+               SessionManager::getGlobalSession()->resetId();
        }
 
        /**