X-Git-Url: https://scripts.mit.edu/gitweb/autoinstallsdev/mediawiki.git/blobdiff_plain/e9f06ed19f56c6b855e47d4610f23d08cf94c99a..728655992158793110a6eb14793e94edd0863582:/includes/specials/SpecialUserlogin.php diff --git a/includes/specials/SpecialUserlogin.php b/includes/specials/SpecialUserlogin.php index b065bdd6..0f5f69b7 100644 --- a/includes/specials/SpecialUserlogin.php +++ b/includes/specials/SpecialUserlogin.php @@ -34,10 +34,14 @@ class LoginForm { const ABORTED = 8; const CREATE_BLOCKED = 9; const THROTTLED = 10; + const USER_BLOCKED = 11; + const NEED_TOKEN = 12; + const WRONG_TOKEN = 13; var $mName, $mPassword, $mRetype, $mReturnTo, $mCookieCheck, $mPosted; var $mAction, $mCreateaccount, $mCreateaccountMail, $mMailmypassword; var $mLoginattempt, $mRemember, $mEmail, $mDomain, $mLanguage, $mSkipCookieCheck; + var $mToken; /** * Constructor @@ -65,6 +69,7 @@ class LoginForm { $this->mRemember = $request->getCheck( 'wpRemember' ); $this->mLanguage = $request->getText( 'uselang' ); $this->mSkipCookieCheck = $request->getCheck( 'wpSkipCookieCheck' ); + $this->mToken = $request->getVal( 'wpLoginToken' ); if ( $wgRedirectOnLogin ) { $this->mReturnTo = $wgRedirectOnLogin; @@ -382,7 +387,22 @@ class LoginForm { if ( '' == $this->mName ) { return self::NO_NAME; } + + // We require a login token to prevent login CSRF + // Handle part of this before incrementing the throttle so + // token-less login attempts don't count towards the throttle + // but wrong-token attempts do. + // If the user doesn't have a login token yet, set one. + if ( !self::getLoginToken() ) { + self::setLoginToken(); + return self::NEED_TOKEN; + } + // If the user didn't pass a login token, tell them we need one + if ( !$this->mToken ) { + return self::NEED_TOKEN; + } + global $wgPasswordAttemptThrottle; $throttleCount=0; @@ -401,6 +421,11 @@ class LoginForm { return self::THROTTLED; } } + + // Validate the login token + if ( $this->mToken !== self::getLoginToken() ) { + return self::WRONG_TOKEN; + } // Load $wgUser now, and check to see if we're logging in as the same // name. This is necessary because loading $wgUser (say by calling @@ -532,6 +557,7 @@ class LoginForm { $wgUser->invalidateCache(); } $wgUser->setCookies(); + self::clearLoginToken(); // Reset the throttle $key = wfMemcKey( 'password-throttle', wfGetIP(), md5( $this->mName ) ); @@ -550,7 +576,11 @@ class LoginForm { return $this->cookieRedirectCheck( 'login' ); } break; - + + case self::NEED_TOKEN: + case self::WRONG_TOKEN: + $this->mainLoginForm( wfMsg( 'sessionfailure' ) ); + break; case self::NO_NAME: case self::ILLEGAL: $this->mainLoginForm( wfMsg( 'noname' ) ); @@ -881,6 +911,11 @@ class LoginForm { $template->set( 'canremember', ( $wgCookieExpiration > 0 ) ); $template->set( 'remember', $wgUser->getOption( 'rememberpassword' ) or $this->mRemember ); + if ( !self::getLoginToken() ) { + self::setLoginToken(); + } + $template->set( 'token', self::getLoginToken() ); + # Prepare language selection links as needed if( $wgLoginLanguageSelector ) { $template->set( 'languages', $this->makeLanguageSelector() ); @@ -929,6 +964,32 @@ class LoginForm { global $wgDisableCookieCheck, $wgRequest; return $wgDisableCookieCheck ? true : $wgRequest->checkSessionCookie(); } + + /** + * Get the login token from the current session + */ + public static function getLoginToken() { + global $wgRequest; + return $wgRequest->getSessionData( 'wsLoginToken' ); + } + + /** + * Generate a new login token and attach it to the current session + */ + public static function setLoginToken() { + global $wgRequest; + // Use User::generateToken() instead of $user->editToken() + // because the latter reuses $_SESSION['wsEditToken'] + $wgRequest->setSessionData( 'wsLoginToken', User::generateToken() ); + } + + /** + * Remove any login token attached to the current session + */ + public static function clearLoginToken() { + global $wgRequest; + $wgRequest->setSessionData( 'wsLoginToken', null ); + } /** * @private