objectcache: Introduce IExpiringStore for convenient TTL constants
[lhc/web/wiklou.git] / includes / specials / SpecialUserlogin.php
index ee78a61..6c6ba3b 100644 (file)
@@ -276,13 +276,17 @@ class LoginForm extends SpecialPage {
                }
                $this->setHeaders();
 
-               // In the case where the user is already logged in, and was redirected to the login form from a
-               // page that requires login, do not show the login page. The use case scenario for this is when
-               // a user opens a large number of tabs, is redirected to the login page on all of them, and then
-               // logs in on one, expecting all the others to work properly.
-               //
-               // However, do show the form if it was visited intentionally (no 'returnto' is present). People
-               // who often switch between several accounts have grown accustomed to this behavior.
+               /**
+                * In the case where the user is already logged in, and was redirected to
+                * the login form from a page that requires login, do not show the login
+                * page. The use case scenario for this is when a user opens a large number
+                * of tabs, is redirected to the login page on all of them, and then logs
+                * in on one, expecting all the others to work properly.
+                *
+                * However, do show the form if it was visited intentionally (no 'returnto'
+                * is present). People who often switch between several accounts have grown
+                * accustomed to this behavior.
+                */
                if (
                        $this->mType !== 'signup' &&
                        !$this->mPosted &&
@@ -357,10 +361,10 @@ class LoginForm extends SpecialPage {
                }
 
                $status = $this->addNewAccountInternal();
-               LoggerFactory::getInstance( 'authmanager' )->info( 'Account creation attempt with mailed password', array(
-                       'event' => 'accountcreation',
-                       'status' => $status,
-               ) );
+               LoggerFactory::getInstance( 'authmanager' )->info(
+                       'Account creation attempt with mailed password',
+                       array( 'event' => 'accountcreation', 'status' => $status )
+               );
                if ( !$status->isGood() ) {
                        $error = $status->getMessage();
                        $this->mainLoginForm( $error->toString() );
@@ -481,7 +485,7 @@ class LoginForm extends SpecialPage {
         * @return Status
         */
        public function addNewAccountInternal() {
-               global $wgAuth, $wgMemc, $wgAccountCreationThrottle, $wgEmailConfirmToEdit;
+               global $wgAuth, $wgAccountCreationThrottle, $wgEmailConfirmToEdit;
 
                // If the user passes an invalid domain, something is fishy
                if ( !$wgAuth->validDomain( $this->mDomain ) ) {
@@ -561,8 +565,9 @@ class LoginForm extends SpecialPage {
                        return Status::newFatal( 'noname' );
                }
 
+               $cache = ObjectCache::getLocalClusterInstance();
                # Make sure the user does not exist already
-               $lock = $wgMemc->getScopedLock( wfGlobalCacheKey( 'account', md5( $this->mUsername ) ) );
+               $lock = $cache->getScopedLock( wfGlobalCacheKey( 'account', md5( $this->mUsername ) ) );
                if ( !$lock ) {
                        return Status::newFatal( 'usernameinprogress' );
                } elseif ( $u->idForName( User::READ_LOCKING ) ) {
@@ -629,14 +634,14 @@ class LoginForm extends SpecialPage {
                } else {
                        if ( ( $wgAccountCreationThrottle && $currentUser->isPingLimitable() ) ) {
                                $key = wfMemcKey( 'acctcreate', 'ip', $ip );
-                               $value = $wgMemc->get( $key );
+                               $value = $cache->get( $key );
                                if ( !$value ) {
-                                       $wgMemc->set( $key, 0, 86400 );
+                                       $cache->set( $key, 0, $cache::TTL_DAY );
                                }
                                if ( $value >= $wgAccountCreationThrottle ) {
                                        return Status::newFatal( 'acct_creation_throttle_hit', $wgAccountCreationThrottle );
                                }
-                               $wgMemc->incr( $key );
+                               $cache->incr( $key );
                        }
                }
 
@@ -779,30 +784,34 @@ class LoginForm extends SpecialPage {
                // Give general extensions, such as a captcha, a chance to abort logins
                $abort = self::ABORTED;
                if ( !Hooks::run( 'AbortLogin', array( $u, $this->mPassword, &$abort, &$msg ) ) ) {
+                       if ( !in_array( $abort, self::$statusCodes, true ) ) {
+                               throw new Exception( 'Invalid status code returned from AbortLogin hook: ' . $abort );
+                       }
                        $this->mAbortLoginErrorMsg = $msg;
-
                        return $abort;
                }
 
                global $wgBlockDisablesLogin;
                if ( !$u->checkPassword( $this->mPassword ) ) {
                        if ( $u->checkTemporaryPassword( $this->mPassword ) ) {
-                               // The e-mailed temporary password should not be used for actu-
-                               // al logins; that's a very sloppy habit, and insecure if an
-                               // attacker has a few seconds to click "search" on someone's o-
-                               // pen mail reader.
-                               //
-                               // Allow it to be used only to reset the password a single time
-                               // to a new value, which won't be in the user's e-mail ar-
-                               // chives.
-                               //
-                               // For backwards compatibility, we'll still recognize it at the
-                               // login form to minimize surprises for people who have been
-                               // logging in with a temporary password for some time.
-                               //
-                               // As a side-effect, we can authenticate the user's e-mail ad-
-                               // dress if it's not already done, since the temporary password
-                               // was sent via e-mail.
+                               /**
+                                * The e-mailed temporary password should not be used for actu-
+                                * al logins; that's a very sloppy habit, and insecure if an
+                                * attacker has a few seconds to click "search" on someone's
+                                * open mail reader.
+                                *
+                                * Allow it to be used only to reset the password a single time
+                                * to a new value, which won't be in the user's e-mail ar-
+                                * chives.
+                                *
+                                * For backwards compatibility, we'll still recognize it at the
+                                * login form to minimize surprises for people who have been
+                                * logging in with a temporary password for some time.
+                                *
+                                * As a side-effect, we can authenticate the user's e-mail ad-
+                                * dress if it's not already done, since the temporary password
+                                * was sent via e-mail.
+                                */
                                if ( !$u->isEmailConfirmed() && !wfReadOnly() ) {
                                        $u->confirmEmail();
                                        $u->saveSettings();
@@ -820,7 +829,7 @@ class LoginForm extends SpecialPage {
                } elseif ( $wgBlockDisablesLogin && $u->isBlocked() ) {
                        // If we've enabled it, make it so that a blocked user cannot login
                        $retval = self::USER_BLOCKED;
-               } elseif ( $u->getPasswordExpired() == 'hard' ) {
+               } elseif ( $this->checkUserPasswordExpired( $u ) == 'hard' ) {
                        // Force reset now, without logging in
                        $retval = self::RESET_PASS;
                        $this->mAbortLoginErrorMsg = 'resetpass-expired';
@@ -861,7 +870,7 @@ class LoginForm extends SpecialPage {
         * @return bool|int The integer hit count or True if it is already at the limit
         */
        public static function incLoginThrottle( $username ) {
-               global $wgPasswordAttemptThrottle, $wgMemc, $wgRequest;
+               global $wgPasswordAttemptThrottle, $wgRequest;
                $username = trim( $username ); // sanity
 
                $throttleCount = 0;
@@ -870,11 +879,12 @@ class LoginForm extends SpecialPage {
                        $count = $wgPasswordAttemptThrottle['count'];
                        $period = $wgPasswordAttemptThrottle['seconds'];
 
-                       $throttleCount = $wgMemc->get( $throttleKey );
+                       $cache = ObjectCache::getLocalClusterInstance();
+                       $throttleCount = $cache->get( $throttleKey );
                        if ( !$throttleCount ) {
-                               $wgMemc->add( $throttleKey, 1, $period ); // start counter
+                               $cache->add( $throttleKey, 1, $period ); // start counter
                        } elseif ( $throttleCount < $count ) {
-                               $wgMemc->incr( $throttleKey );
+                               $cache->incr( $throttleKey );
                        } elseif ( $throttleCount >= $count ) {
                                return true;
                        }
@@ -889,11 +899,11 @@ class LoginForm extends SpecialPage {
         * @return void
         */
        public static function clearLoginThrottle( $username ) {
-               global $wgMemc, $wgRequest;
+               global $wgRequest;
                $username = trim( $username ); // sanity
 
                $throttleKey = wfMemcKey( 'password-throttle', $wgRequest->getIP(), md5( $username ) );
-               $wgMemc->delete( $throttleKey );
+               ObjectCache::getLocalClusterInstance()->delete( $throttleKey );
        }
 
        /**
@@ -952,9 +962,9 @@ class LoginForm extends SpecialPage {
        }
 
        function processLogin() {
-               global $wgMemc, $wgLang, $wgSecureLogin, $wgPasswordAttemptThrottle,
-                       $wgInvalidPasswordReset;
+               global $wgLang, $wgSecureLogin, $wgPasswordAttemptThrottle, $wgInvalidPasswordReset;
 
+               $cache = ObjectCache::getLocalClusterInstance();
                $authRes = $this->authenticateUserData();
                switch ( $authRes ) {
                        case self::SUCCESS:
@@ -976,7 +986,7 @@ class LoginForm extends SpecialPage {
                                // Reset the throttle
                                $request = $this->getRequest();
                                $key = wfMemcKey( 'password-throttle', $request->getIP(), md5( $this->mUsername ) );
-                               $wgMemc->delete( $key );
+                               $cache->delete( $key );
 
                                if ( $this->hasSessionCookie() || $this->mSkipCookieCheck ) {
                                        /* Replace the language object to provide user interface in
@@ -988,7 +998,7 @@ class LoginForm extends SpecialPage {
                                        $this->getContext()->setLanguage( $userLang );
                                        // Reset SessionID on Successful login (bug 40995)
                                        $this->renewSessionId();
-                                       if ( $this->getUser()->getPasswordExpired() == 'soft' ) {
+                                       if ( $this->checkUserPasswordExpired( $this->getUser() ) == 'soft' ) {
                                                $this->resetLoginForm( $this->msg( 'resetpass-expired-soft' ) );
                                        } elseif ( $wgInvalidPasswordReset
                                                && !$user->isValidPassword( $this->mPassword )
@@ -1115,7 +1125,7 @@ class LoginForm extends SpecialPage {
        function mailPasswordInternal( $u, $throttle = true, $emailTitle = 'passwordremindertitle',
                $emailText = 'passwordremindertext'
        ) {
-               global $wgNewPasswordExpiry;
+               global $wgNewPasswordExpiry, $wgMinimalPasswordLength;
 
                if ( $u->getEmail() == '' ) {
                        return Status::newFatal( 'noemail', $u->getName() );
@@ -1128,7 +1138,7 @@ class LoginForm extends SpecialPage {
                $currentUser = $this->getUser();
                Hooks::run( 'User::mailPasswordInternal', array( &$currentUser, &$ip, &$u ) );
 
-               $np = $u->randomPassword();
+               $np = PasswordFactory::generateRandomPasswordString( $wgMinimalPasswordLength );
                $u->setNewpassword( $np, $throttle );
                $u->saveSettings();
                $userLanguage = $u->getOption( 'language' );
@@ -1379,11 +1389,6 @@ class LoginForm extends SpecialPage {
                ) );
 
                if ( $this->mType == 'signup' ) {
-                       // XXX hack pending RL or JS parse() support for complex content messages
-                       // https://phabricator.wikimedia.org/T27349
-                       $out->addJsConfigVars( 'wgCreateacctImgcaptchaHelp',
-                               $this->msg( 'createacct-imgcaptcha-help' )->parse() );
-
                        // Additional styles and scripts for signup form
                        $out->addModules( array(
                                'mediawiki.special.userlogin.signup.js'
@@ -1459,7 +1464,9 @@ class LoginForm extends SpecialPage {
                $template->set( 'emailothers', $wgEnableUserEmail );
                $template->set( 'canreset', $wgAuth->allowPasswordChange() );
                $template->set( 'resetlink', $resetLink );
-               $template->set( 'canremember', $wgExtendedLoginCookieExpiration === null ? ( $wgCookieExpiration > 0 ) : ( $wgExtendedLoginCookieExpiration > 0 ) );
+               $template->set( 'canremember', $wgExtendedLoginCookieExpiration === null ?
+                       ( $wgCookieExpiration > 0 ) :
+                       ( $wgExtendedLoginCookieExpiration > 0 ) );
                $template->set( 'usereason', $user->isLoggedIn() );
                $template->set( 'remember', $this->mRemember );
                $template->set( 'cansecurelogin', ( $wgSecureLogin === true ) );
@@ -1709,4 +1716,25 @@ class LoginForm extends SpecialPage {
        protected function getGroupName() {
                return 'login';
        }
+
+       /**
+        * Private function to check password expiration, until AuthManager comes
+        * along to handle that.
+        * @param User $user
+        * @return string|bool
+        */
+       private function checkUserPasswordExpired( User $user ) {
+               global $wgPasswordExpireGrace;
+               $dbr = wfGetDB( DB_SLAVE );
+               $ts = $dbr->selectField( 'user', 'user_password_expires', array( 'user_id' => $user->getId() ) );
+
+               $expired = false;
+               $now = wfTimestamp();
+               $expUnix = wfTimestamp( TS_UNIX, $ts );
+               if ( $ts !== null && $expUnix < $now ) {
+                       $expired = ( $expUnix + $wgPasswordExpireGrace < $now ) ? 'hard' : 'soft';
+               }
+               return $expired;
+       }
+
 }