diff options
Diffstat (limited to 'includes/specials/SpecialUserlogin.php')
-rw-r--r-- | includes/specials/SpecialUserlogin.php | 609 |
1 files changed, 349 insertions, 260 deletions
diff --git a/includes/specials/SpecialUserlogin.php b/includes/specials/SpecialUserlogin.php index 58da77da..5ac3e654 100644 --- a/includes/specials/SpecialUserlogin.php +++ b/includes/specials/SpecialUserlogin.php @@ -48,18 +48,21 @@ class LoginForm extends SpecialPage { var $mLoginattempt, $mRemember, $mEmail, $mDomain, $mLanguage; var $mSkipCookieCheck, $mReturnToQuery, $mToken, $mStickHTTPS; var $mType, $mReason, $mRealName; - var $mAbortLoginErrorMsg = 'login-abort-generic'; + var $mAbortLoginErrorMsg = null; private $mLoaded = false; + private $mSecureLoginUrl; /** - * @var ExternalUser + * @ var WebRequest */ - private $mExtUser = null; + private $mOverrideRequest = null; /** - * @ var WebRequest + * Effective request; set at the beginning of load + * + * @var WebRequest $mRequest */ - private $mOverrideRequest = null; + private $mRequest = null; /** * @param WebRequest $request @@ -86,6 +89,7 @@ class LoginForm extends SpecialPage { } else { $request = $this->mOverrideRequest; } + $this->mRequest = $request; $this->mType = $request->getText( 'type' ); $this->mUsername = $request->getText( 'wpName' ); @@ -95,31 +99,32 @@ class LoginForm extends SpecialPage { $this->mReason = $request->getText( 'wpReason' ); $this->mCookieCheck = $request->getVal( 'wpCookieCheck' ); $this->mPosted = $request->wasPosted(); - $this->mCreateaccount = $request->getCheck( 'wpCreateaccount' ); $this->mCreateaccountMail = $request->getCheck( 'wpCreateaccountMail' ) && $wgEnableEmail; + $this->mCreateaccount = $request->getCheck( 'wpCreateaccount' ) && !$this->mCreateaccountMail; $this->mLoginattempt = $request->getCheck( 'wpLoginattempt' ); $this->mAction = $request->getVal( 'action' ); $this->mRemember = $request->getCheck( 'wpRemember' ); - $this->mStickHTTPS = $request->getCheck( 'wpStickHTTPS' ); + $this->mFromHTTP = $request->getBool( 'fromhttp', false ); + $this->mStickHTTPS = ( !$this->mFromHTTP && $request->detectProtocol() === 'https' ) || $request->getBool( 'wpForceHttps', false ); $this->mLanguage = $request->getText( 'uselang' ); $this->mSkipCookieCheck = $request->getCheck( 'wpSkipCookieCheck' ); $this->mToken = ( $this->mType == 'signup' ) ? $request->getVal( 'wpCreateaccountToken' ) : $request->getVal( 'wpLoginToken' ); $this->mReturnTo = $request->getVal( 'returnto', '' ); $this->mReturnToQuery = $request->getVal( 'returntoquery', '' ); - if( $wgEnableEmail ) { + if ( $wgEnableEmail ) { $this->mEmail = $request->getText( 'wpEmail' ); } else { $this->mEmail = ''; } - if( !in_array( 'realname', $wgHiddenPrefs ) ) { + if ( !in_array( 'realname', $wgHiddenPrefs ) ) { $this->mRealName = $request->getText( 'wpRealName' ); } else { $this->mRealName = ''; } - if( !$wgAuth->validDomain( $this->mDomain ) ) { + if ( !$wgAuth->validDomain( $this->mDomain ) ) { $this->mDomain = $wgAuth->getDomain(); } $wgAuth->setDomain( $this->mDomain ); @@ -128,7 +133,7 @@ class LoginForm extends SpecialPage { # 2. Do not return to PasswordReset after a successful password change # but goto Wiki start page (Main_Page) instead ( bug 33997 ) $returnToTitle = Title::newFromText( $this->mReturnTo ); - if( is_object( $returnToTitle ) && ( + if ( is_object( $returnToTitle ) && ( $returnToTitle->isSpecial( 'Userlogout' ) || $returnToTitle->isSpecial( 'PasswordReset' ) ) ) { $this->mReturnTo = ''; @@ -137,27 +142,61 @@ class LoginForm extends SpecialPage { } function getDescription() { - return $this->msg( $this->getUser()->isAllowed( 'createaccount' ) ? - 'userlogin' : 'userloginnocreate' )->text(); + if ( $this->mType === 'signup' ) { + return $this->msg( 'createaccount' )->text(); + } else { + return $this->msg( 'login' )->text(); + } } - public function execute( $par ) { + /* + * @param $subPage string|null + */ + public function execute( $subPage ) { if ( session_id() == '' ) { wfSetupSession(); } $this->load(); - $this->setHeaders(); - if ( $par == 'signup' ) { # Check for [[Special:Userlogin/signup]] + // Check for [[Special:Userlogin/signup]]. This affects form display and + // page title. + if ( $subPage == 'signup' ) { $this->mType = 'signup'; } + $this->setHeaders(); + + // If logging in and not on HTTPS, either redirect to it or offer a link. + global $wgSecureLogin; + if ( WebRequest::detectProtocol() !== 'https' ) { + $title = $this->getFullTitle(); + $query = array( + 'returnto' => $this->mReturnTo, + 'returntoquery' => $this->mReturnToQuery, + 'title' => null, + ) + $this->mRequest->getQueryValues(); + $url = $title->getFullURL( $query, false, PROTO_HTTPS ); + if ( $wgSecureLogin && wfCanIPUseHTTPS( $this->getRequest()->getIP() ) ) { + $url = wfAppendQuery( $url, 'fromhttp=1' ); + $this->getOutput()->redirect( $url ); + // Since we only do this redir to change proto, always vary + $this->getOutput()->addVaryHeader( 'X-Forwarded-Proto' ); + return; + } else { + // A wiki without HTTPS login support should set $wgServer to + // http://somehost, in which case the secure URL generated + // above won't actually start with https:// + if ( substr( $url, 0, 8 ) === 'https://' ) { + $this->mSecureLoginUrl = $url; + } + } + } if ( !is_null( $this->mCookieCheck ) ) { $this->onCookieRedirectCheck( $this->mCookieCheck ); return; - } elseif( $this->mPosted ) { - if( $this->mCreateaccount ) { + } elseif ( $this->mPosted ) { + if ( $this->mCreateaccount ) { $this->addNewAccount(); return; } elseif ( $this->mCreateaccountMail ) { @@ -180,24 +219,27 @@ class LoginForm extends SpecialPage { return; } - $u = $this->addNewaccountInternal(); - - if ( $u == null ) { + $status = $this->addNewaccountInternal(); + if ( !$status->isGood() ) { + $error = $status->getMessage(); + $this->mainLoginForm( $error->toString() ); return; } + $u = $status->getValue(); + // Wipe the initial password and mail a temporary one $u->setPassword( null ); $u->saveSettings(); $result = $this->mailPasswordInternal( $u, false, 'createaccount-title', 'createaccount-text' ); wfRunHooks( 'AddNewAccount', array( $u, true ) ); - $u->addNewUserLogEntry( true, $this->mReason ); + $u->addNewUserLogEntry( 'byemail', $this->mReason ); $out = $this->getOutput(); $out->setPageTitle( $this->msg( 'accmailtitle' ) ); - if( !$result->isGood() ) { + if ( !$result->isGood() ) { $this->mainLoginForm( $this->msg( 'mailerror', $result->getWikiText() )->text() ); } else { $out->addWikiMsg( 'accmailtext', $u->getName(), $u->getEmail() ); @@ -210,26 +252,42 @@ class LoginForm extends SpecialPage { * @return bool */ function addNewAccount() { - global $wgUser, $wgEmailAuthentication, $wgLoginLanguageSelector; + global $wgContLang, $wgUser, $wgEmailAuthentication, $wgLoginLanguageSelector; # Create the account and abort if there's a problem doing so - $u = $this->addNewAccountInternal(); - if( $u == null ) { + $status = $this->addNewAccountInternal(); + if ( !$status->isGood() ) { + $error = $status->getMessage(); + $this->mainLoginForm( $error->toString() ); return false; } - # If we showed up language selection links, and one was in use, be - # smart (and sensible) and save that language as the user's preference - if( $wgLoginLanguageSelector && $this->mLanguage ) { - $u->setOption( 'language', $this->mLanguage ); + $u = $status->getValue(); + + # Only save preferences if the user is not creating an account for someone else. + if ( $this->getUser()->isAnon() ) { + # If we showed up language selection links, and one was in use, be + # smart (and sensible) and save that language as the user's preference + if ( $wgLoginLanguageSelector && $this->mLanguage ) { + $u->setOption( 'language', $this->mLanguage ); + } else { + + # Otherwise the user's language preference defaults to $wgContLang, + # but it may be better to set it to their preferred $wgContLang variant, + # based on browser preferences or URL parameters. + $u->setOption( 'language', $wgContLang->getPreferredVariant() ); + } + if ( $wgContLang->hasVariants() ) { + $u->setOption( 'variant', $wgContLang->getPreferredVariant() ); + } } $out = $this->getOutput(); # Send out an email authentication message if needed - if( $wgEmailAuthentication && Sanitizer::validateEmail( $u->getEmail() ) ) { + if ( $wgEmailAuthentication && Sanitizer::validateEmail( $u->getEmail() ) ) { $status = $u->sendConfirmationMail(); - if( $status->isGood() ) { + if ( $status->isGood() ) { $out->addWikiMsg( 'confirmemail_oncreate' ); } else { $out->addWikiText( $status->getWikiText( 'confirmemail_sendfailed' ) ); @@ -242,7 +300,7 @@ class LoginForm extends SpecialPage { # If not logged in, assume the new account as the current one and set # session cookies then show a "welcome" message or a "need cookies" # message as needed - if( $this->getUser()->isAnon() ) { + if ( $this->getUser()->isAnon() ) { $u->setCookies(); $wgUser = $u; // This should set it for OutputPage and the Skin @@ -250,8 +308,8 @@ class LoginForm extends SpecialPage { // wrong. $this->getContext()->setUser( $u ); wfRunHooks( 'AddNewAccount', array( $u, false ) ); - $u->addNewUserLogEntry(); - if( $this->hasSessionCookie() ) { + $u->addNewUserLogEntry( 'create' ); + if ( $this->hasSessionCookie() ) { $this->successfulCreation(); } else { $this->cookieRedirectCheck( 'new' ); @@ -262,23 +320,24 @@ class LoginForm extends SpecialPage { $out->addWikiMsg( 'accountcreatedtext', $u->getName() ); $out->addReturnTo( $this->getTitle() ); wfRunHooks( 'AddNewAccount', array( $u, false ) ); - $u->addNewUserLogEntry( false, $this->mReason ); + $u->addNewUserLogEntry( 'create2', $this->mReason ); } return true; } /** + * Make a new user account using the loaded data. * @private - * @return bool|User + * @throws PermissionsError|ReadOnlyError + * @return Status */ - function addNewAccountInternal() { + public function addNewAccountInternal() { global $wgAuth, $wgMemc, $wgAccountCreationThrottle, $wgMinimalPasswordLength, $wgEmailConfirmToEdit; // If the user passes an invalid domain, something is fishy - if( !$wgAuth->validDomain( $this->mDomain ) ) { - $this->mainLoginForm( $this->msg( 'wrongpassword' )->text() ); - return false; + if ( !$wgAuth->validDomain( $this->mDomain ) ) { + return Status::newFatal( 'wrongpassword' ); } // If we are not allowing users to login locally, we should be checking @@ -286,11 +345,15 @@ class LoginForm extends SpecialPage { // cation server before they create an account (otherwise, they can // create a local account and login as any domain user). We only need // to check this for domains that aren't local. - if( 'local' != $this->mDomain && $this->mDomain != '' ) { - if( !$wgAuth->canCreateAccounts() && ( !$wgAuth->userExists( $this->mUsername ) - || !$wgAuth->authenticate( $this->mUsername, $this->mPassword ) ) ) { - $this->mainLoginForm( $this->msg( 'wrongpassword' )->text() ); - return false; + if ( 'local' != $this->mDomain && $this->mDomain != '' ) { + if ( + !$wgAuth->canCreateAccounts() && + ( + !$wgAuth->userExists( $this->mUsername ) || + !$wgAuth->authenticate( $this->mUsername, $this->mPassword ) + ) + ) { + return Status::newFatal( 'wrongpassword' ); } } @@ -301,28 +364,28 @@ class LoginForm extends SpecialPage { # Request forgery checks. if ( !self::getCreateaccountToken() ) { self::setCreateaccountToken(); - $this->mainLoginForm( $this->msg( 'nocookiesfornew' )->parse() ); - return false; + return Status::newFatal( 'nocookiesfornew' ); } # The user didn't pass a createaccount token if ( !$this->mToken ) { - $this->mainLoginForm( $this->msg( 'sessionfailure' )->text() ); - return false; + return Status::newFatal( 'sessionfailure' ); } # Validate the createaccount token if ( $this->mToken !== self::getCreateaccountToken() ) { - $this->mainLoginForm( $this->msg( 'sessionfailure' )->text() ); - return false; + return Status::newFatal( 'sessionfailure' ); } # Check permissions $currentUser = $this->getUser(); + $creationBlock = $currentUser->isBlockedFromCreateAccount(); if ( !$currentUser->isAllowed( 'createaccount' ) ) { throw new PermissionsError( 'createaccount' ); - } elseif ( $currentUser->isBlockedFromCreateAccount() ) { - $this->userBlockedMessage( $currentUser->isBlockedFromCreateAccount() ); + } elseif ( $creationBlock instanceof Block ) { + // Throws an ErrorPageError. + $this->userBlockedMessage( $creationBlock ); + // This should never be reached. return false; } @@ -334,58 +397,45 @@ class LoginForm extends SpecialPage { $ip = $this->getRequest()->getIP(); if ( $currentUser->isDnsBlacklisted( $ip, true /* check $wgProxyWhitelist */ ) ) { - $this->mainLoginForm( $this->msg( 'sorbs_create_account_reason' )->text() . ' ' . $this->msg( 'parentheses', $ip )->escaped() ); - return false; + return Status::newFatal( 'sorbs_create_account_reason' ); } # Now create a dummy user ($u) and check if it is valid $name = trim( $this->mUsername ); $u = User::newFromName( $name, 'creatable' ); if ( !is_object( $u ) ) { - $this->mainLoginForm( $this->msg( 'noname' )->text() ); - return false; + return Status::newFatal( 'noname' ); + } elseif ( 0 != $u->idForName() ) { + return Status::newFatal( 'userexists' ); } - if ( 0 != $u->idForName() ) { - $this->mainLoginForm( $this->msg( 'userexists' )->text() ); - return false; - } - - if ( 0 != strcmp( $this->mPassword, $this->mRetype ) ) { - $this->mainLoginForm( $this->msg( 'badretype' )->text() ); - return false; - } + if ( $this->mCreateaccountMail ) { + # do not force a password for account creation by email + # set invalid password, it will be replaced later by a random generated password + $this->mPassword = null; + } else { + if ( $this->mPassword !== $this->mRetype ) { + return Status::newFatal( 'badretype' ); + } - # check for minimal password length - $valid = $u->getPasswordValidity( $this->mPassword ); - if ( $valid !== true ) { - if ( !$this->mCreateaccountMail ) { - if ( is_array( $valid ) ) { - $message = array_shift( $valid ); - $params = $valid; - } else { - $message = $valid; - $params = array( $wgMinimalPasswordLength ); + # check for minimal password length + $valid = $u->getPasswordValidity( $this->mPassword ); + if ( $valid !== true ) { + if ( !is_array( $valid ) ) { + $valid = array( $valid, $wgMinimalPasswordLength ); } - $this->mainLoginForm( $this->msg( $message, $params )->text() ); - return false; - } else { - # do not force a password for account creation by email - # set invalid password, it will be replaced later by a random generated password - $this->mPassword = null; + return call_user_func_array( 'Status::newFatal', $valid ); } } # if you need a confirmed email address to edit, then obviously you # need an email address. - if ( $wgEmailConfirmToEdit && empty( $this->mEmail ) ) { - $this->mainLoginForm( $this->msg( 'noemailtitle' )->text() ); - return false; + if ( $wgEmailConfirmToEdit && strval( $this->mEmail ) === '' ) { + return Status::newFatal( 'noemailtitle' ); } - if( !empty( $this->mEmail ) && !Sanitizer::validateEmail( $this->mEmail ) ) { - $this->mainLoginForm( $this->msg( 'invalidemailaddress' )->text() ); - return false; + if ( strval( $this->mEmail ) !== '' && !Sanitizer::validateEmail( $this->mEmail ) ) { + return Status::newFatal( 'invalidemailaddress' ); } # Set some additional data so the AbortNewAccount hook can be used for @@ -394,11 +444,12 @@ class LoginForm extends SpecialPage { $u->setRealName( $this->mRealName ); $abortError = ''; - if( !wfRunHooks( 'AbortNewAccount', array( $u, &$abortError ) ) ) { + if ( !wfRunHooks( 'AbortNewAccount', array( $u, &$abortError ) ) ) { // Hook point to add extra creation throttles and blocks wfDebug( "LoginForm::addNewAccountInternal: a hook blocked creation\n" ); - $this->mainLoginForm( $abortError ); - return false; + $abortError = new RawMessage( $abortError ); + $abortError->text(); + return Status::newFatal( $abortError ); } // Hook point to check for exempt from account creation throttle @@ -412,16 +463,14 @@ class LoginForm extends SpecialPage { $wgMemc->set( $key, 0, 86400 ); } if ( $value >= $wgAccountCreationThrottle ) { - $this->throttleHit( $wgAccountCreationThrottle ); - return false; + return Status::newFatal( 'acct_creation_throttle_hit', $wgAccountCreationThrottle ); } $wgMemc->incr( $key ); } } - if( !$wgAuth->addUser( $u, $this->mPassword, $this->mEmail, $this->mRealName ) ) { - $this->mainLoginForm( $this->msg( 'externaldberror' )->text() ); - return false; + if ( !$wgAuth->addUser( $u, $this->mPassword, $this->mEmail, $this->mRealName ) ) { + return Status::newFatal( 'externaldberror' ); } self::clearCreateaccountToken(); @@ -434,13 +483,16 @@ class LoginForm extends SpecialPage { * * @param $u User object. * @param $autocreate boolean -- true if this is an autocreation via auth plugin - * @return User object. + * @return Status object, with the User object in the value member on success * @private */ function initUser( $u, $autocreate ) { global $wgAuth; - $u->addToDatabase(); + $status = $u->addToDatabase(); + if ( !$status->isOK() ) { + return $status; + } if ( $wgAuth->allowPasswordChange() ) { $u->setPassword( $this->mPassword ); @@ -452,22 +504,13 @@ class LoginForm extends SpecialPage { $wgAuth->initUser( $u, $autocreate ); - if ( $this->mExtUser ) { - $this->mExtUser->linkToLocal( $u->getId() ); - $email = $this->mExtUser->getPref( 'emailaddress' ); - if ( $email && !$this->mEmail ) { - $u->setEmail( $email ); - } - } - $u->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 ); $u->saveSettings(); # Update user count - $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 ); - $ssUpdate->doUpdate(); + DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 0, 0, 0, 1 ) ); - return $u; + return Status::newGood( $u ); } /** @@ -523,17 +566,13 @@ class LoginForm extends SpecialPage { return self::SUCCESS; } - $this->mExtUser = ExternalUser::newFromName( $this->mUsername ); - - # TODO: Allow some magic here for invalid external names, e.g., let the - # user choose a different wiki name. $u = User::newFromName( $this->mUsername ); - if( !( $u instanceof User ) || !User::isUsableName( $u->getName() ) ) { + if ( !( $u instanceof User ) || !User::isUsableName( $u->getName() ) ) { return self::ILLEGAL; } $isAutoCreated = false; - if ( 0 == $u->getID() ) { + if ( $u->getID() == 0 ) { $status = $this->attemptAutoCreate( $u ); if ( $status !== self::SUCCESS ) { return $status; @@ -541,27 +580,20 @@ class LoginForm extends SpecialPage { $isAutoCreated = true; } } else { - global $wgExternalAuthType, $wgAutocreatePolicy; - if ( $wgExternalAuthType && $wgAutocreatePolicy != 'never' - && is_object( $this->mExtUser ) - && $this->mExtUser->authenticate( $this->mPassword ) ) { - # The external user and local user have the same name and - # password, so we assume they're the same. - $this->mExtUser->linkToLocal( $u->getID() ); - } - $u->load(); } // Give general extensions, such as a captcha, a chance to abort logins $abort = self::ABORTED; - if( !wfRunHooks( 'AbortLogin', array( $u, $this->mPassword, &$abort, &$this->mAbortLoginErrorMsg ) ) ) { + $msg = null; + if ( !wfRunHooks( 'AbortLogin', array( $u, $this->mPassword, &$abort, &$msg ) ) ) { + $this->mAbortLoginErrorMsg = $msg; return $abort; } global $wgBlockDisablesLogin; if ( !$u->checkPassword( $this->mPassword ) ) { - if( $u->checkTemporaryPassword( $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- @@ -578,7 +610,7 @@ class LoginForm extends SpecialPage { // 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() ) { + if ( !$u->isEmailConfirmed() ) { $u->confirmEmail(); $u->saveSettings(); } @@ -588,7 +620,7 @@ class LoginForm extends SpecialPage { // faces etc will probably just fail cleanly here. $retval = self::RESET_PASS; } else { - $retval = ( $this->mPassword == '' ) ? self::EMPTY_PASS : self::WRONG_PASS; + $retval = ( $this->mPassword == '' ) ? self::EMPTY_PASS : self::WRONG_PASS; } } elseif ( $wgBlockDisablesLogin && $u->isBlocked() ) { // If we've enabled it, make it so that a blocked user cannot login @@ -620,7 +652,7 @@ class LoginForm extends SpecialPage { /** * Increment the login attempt throttle hit count for the (username,current IP) * tuple unless the throttle was already reached. - * @param $username string The user name + * @param string $username The user name * @return Bool|Integer The integer hit count or True if it is already at the limit */ public static function incLoginThrottle( $username ) { @@ -648,7 +680,7 @@ class LoginForm extends SpecialPage { /** * Clear the login attempt throttle hit count for the (username,current IP) tuple. - * @param $username string The user name + * @param string $username The user name * @return void */ public static function clearLoginThrottle( $username ) { @@ -668,44 +700,26 @@ class LoginForm extends SpecialPage { * @return integer Status code */ function attemptAutoCreate( $user ) { - global $wgAuth, $wgAutocreatePolicy; + global $wgAuth; if ( $this->getUser()->isBlockedFromCreateAccount() ) { wfDebug( __METHOD__ . ": user is blocked from account creation\n" ); return self::CREATE_BLOCKED; } - - /** - * If the external authentication plugin allows it, automatically cre- - * ate a new account for users that are externally defined but have not - * yet logged in. - */ - if ( $this->mExtUser ) { - # mExtUser is neither null nor false, so use the new ExternalAuth - # system. - if ( $wgAutocreatePolicy == 'never' ) { - return self::NOT_EXISTS; - } - if ( !$this->mExtUser->authenticate( $this->mPassword ) ) { - return self::WRONG_PLUGIN_PASS; - } - } else { - # Old AuthPlugin. - if ( !$wgAuth->autoCreate() ) { - return self::NOT_EXISTS; - } - if ( !$wgAuth->userExists( $user->getName() ) ) { - wfDebug( __METHOD__ . ": user does not exist\n" ); - return self::NOT_EXISTS; - } - if ( !$wgAuth->authenticate( $user->getName(), $this->mPassword ) ) { - wfDebug( __METHOD__ . ": \$wgAuth->authenticate() returned false, aborting\n" ); - return self::WRONG_PLUGIN_PASS; - } + if ( !$wgAuth->autoCreate() ) { + return self::NOT_EXISTS; + } + if ( !$wgAuth->userExists( $user->getName() ) ) { + wfDebug( __METHOD__ . ": user does not exist\n" ); + return self::NOT_EXISTS; + } + if ( !$wgAuth->authenticate( $user->getName(), $this->mPassword ) ) { + wfDebug( __METHOD__ . ": \$wgAuth->authenticate() returned false, aborting\n" ); + return self::WRONG_PLUGIN_PASS; } $abortError = ''; - if( !wfRunHooks( 'AbortAutoAccount', array( $user, &$abortError ) ) ) { + if ( !wfRunHooks( 'AbortAutoAccount', array( $user, &$abortError ) ) ) { // Hook point to add extra creation throttles and blocks wfDebug( "LoginForm::attemptAutoCreate: a hook blocked creation: $abortError\n" ); $this->mAbortLoginErrorMsg = $abortError; @@ -713,24 +727,40 @@ class LoginForm extends SpecialPage { } wfDebug( __METHOD__ . ": creating account\n" ); - $this->initUser( $user, true ); + $status = $this->initUser( $user, true ); + + if ( !$status->isOK() ) { + $errors = $status->getErrorsByType( 'error' ); + $this->mAbortLoginErrorMsg = $errors[0]['message']; + return self::ABORTED; + } + return self::SUCCESS; } function processLogin() { - global $wgMemc, $wgLang; + global $wgMemc, $wgLang, $wgSecureLogin, $wgPasswordAttemptThrottle; switch ( $this->authenticateUserData() ) { case self::SUCCESS: # We've verified now, update the real record $user = $this->getUser(); - if( (bool)$this->mRemember != (bool)$user->getOption( 'rememberpassword' ) ) { + if ( (bool)$this->mRemember != $user->getBoolOption( 'rememberpassword' ) ) { $user->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 ); $user->saveSettings(); } else { $user->invalidateCache(); } - $user->setCookies(); + + if ( $user->requiresHTTPS() ) { + $this->mStickHTTPS = true; + } + + if ( $wgSecureLogin && !$this->mStickHTTPS ) { + $user->setCookies( null, false ); + } else { + $user->setCookies(); + } self::clearLoginToken(); // Reset the throttle @@ -738,7 +768,7 @@ class LoginForm extends SpecialPage { $key = wfMemcKey( 'password-throttle', $request->getIP(), md5( $this->mUsername ) ); $wgMemc->delete( $key ); - if( $this->hasSessionCookie() || $this->mSkipCookieCheck ) { + if ( $this->hasSessionCookie() || $this->mSkipCookieCheck ) { /* Replace the language object to provide user interface in * correct language immediately on this first page load. */ @@ -755,56 +785,73 @@ class LoginForm extends SpecialPage { break; case self::NEED_TOKEN: - $this->mainLoginForm( $this->msg( 'nocookiesforlogin' )->parse() ); + $error = $this->mAbortLoginErrorMsg ?: 'nocookiesforlogin'; + $this->mainLoginForm( $this->msg( $error )->parse() ); break; case self::WRONG_TOKEN: - $this->mainLoginForm( $this->msg( 'sessionfailure' )->text() ); + $error = $this->mAbortLoginErrorMsg ?: 'sessionfailure'; + $this->mainLoginForm( $this->msg( $error )->text() ); break; case self::NO_NAME: case self::ILLEGAL: - $this->mainLoginForm( $this->msg( 'noname' )->text() ); + $error = $this->mAbortLoginErrorMsg ?: 'noname'; + $this->mainLoginForm( $this->msg( $error )->text() ); break; case self::WRONG_PLUGIN_PASS: - $this->mainLoginForm( $this->msg( 'wrongpassword' )->text() ); + $error = $this->mAbortLoginErrorMsg ?: 'wrongpassword'; + $this->mainLoginForm( $this->msg( $error )->text() ); break; case self::NOT_EXISTS: - if( $this->getUser()->isAllowed( 'createaccount' ) ) { - $this->mainLoginForm( $this->msg( 'nosuchuser', + if ( $this->getUser()->isAllowed( 'createaccount' ) ) { + $error = $this->mAbortLoginErrorMsg ?: 'nosuchuser'; + $this->mainLoginForm( $this->msg( $error, wfEscapeWikiText( $this->mUsername ) )->parse() ); } else { - $this->mainLoginForm( $this->msg( 'nosuchusershort', + $error = $this->mAbortLoginErrorMsg ?: 'nosuchusershort'; + $this->mainLoginForm( $this->msg( $error, wfEscapeWikiText( $this->mUsername ) )->text() ); } break; case self::WRONG_PASS: - $this->mainLoginForm( $this->msg( 'wrongpassword' )->text() ); + $error = $this->mAbortLoginErrorMsg ?: 'wrongpassword'; + $this->mainLoginForm( $this->msg( $error )->text() ); break; case self::EMPTY_PASS: - $this->mainLoginForm( $this->msg( 'wrongpasswordempty' )->text() ); + $error = $this->mAbortLoginErrorMsg ?: 'wrongpasswordempty'; + $this->mainLoginForm( $this->msg( $error )->text() ); break; case self::RESET_PASS: - $this->resetLoginForm( $this->msg( 'resetpass_announce' )->text() ); + $error = $this->mAbortLoginErrorMsg ?: 'resetpass_announce'; + $this->resetLoginForm( $this->msg( $error )->text() ); break; case self::CREATE_BLOCKED: - $this->userBlockedMessage( $this->getUser()->mBlock ); + $this->userBlockedMessage( $this->getUser()->isBlockedFromCreateAccount() ); break; case self::THROTTLED: - $this->mainLoginForm( $this->msg( 'login-throttled' )->text() ); + $error = $this->mAbortLoginErrorMsg ?: 'login-throttled'; + $this->mainLoginForm( $this->msg( $error ) + ->params ( $this->getLanguage()->formatDuration( $wgPasswordAttemptThrottle['seconds'] ) ) + ->text() + ); break; case self::USER_BLOCKED: - $this->mainLoginForm( $this->msg( 'login-userblocked', - $this->mUsername )->escaped() ); + $error = $this->mAbortLoginErrorMsg ?: 'login-userblocked'; + $this->mainLoginForm( $this->msg( $error, $this->mUsername )->escaped() ); break; case self::ABORTED: - $this->mainLoginForm( $this->msg( $this->mAbortLoginErrorMsg )->text() ); + $error = $this->mAbortLoginErrorMsg ?: 'login-abort-generic'; + $this->mainLoginForm( $this->msg( $error )->text() ); break; default: throw new MWException( 'Unhandled case value' ); } } + /** + * @param $error string + */ function resetLoginForm( $error ) { - $this->getOutput()->addHTML( Xml::element('p', array( 'class' => 'error' ), $error ) ); + $this->getOutput()->addHTML( Xml::element( 'p', array( 'class' => 'error' ), $error ) ); $reset = new SpecialChangePassword(); $reset->setContext( $this->getContext() ); $reset->execute( null ); @@ -813,18 +860,18 @@ class LoginForm extends SpecialPage { /** * @param $u User object * @param $throttle Boolean - * @param $emailTitle String: message name of email title - * @param $emailText String: message name of email text + * @param string $emailTitle message name of email title + * @param string $emailText message name of email text * @return Status object */ function mailPasswordInternal( $u, $throttle = true, $emailTitle = 'passwordremindertitle', $emailText = 'passwordremindertext' ) { - global $wgServer, $wgScript, $wgNewPasswordExpiry; + global $wgCanonicalServer, $wgScript, $wgNewPasswordExpiry; if ( $u->getEmail() == '' ) { return Status::newFatal( 'noemail', $u->getName() ); } $ip = $this->getRequest()->getIP(); - if( !$ip ) { + if ( !$ip ) { return Status::newFatal( 'badipaddress' ); } @@ -835,14 +882,13 @@ class LoginForm extends SpecialPage { $u->setNewpassword( $np, $throttle ); $u->saveSettings(); $userLanguage = $u->getOption( 'language' ); - $m = $this->msg( $emailText, $ip, $u->getName(), $np, $wgServer . $wgScript, + $m = $this->msg( $emailText, $ip, $u->getName(), $np, '<' . $wgCanonicalServer . $wgScript . '>', round( $wgNewPasswordExpiry / 86400 ) )->inLanguage( $userLanguage )->text(); $result = $u->sendMail( $this->msg( $emailTitle )->inLanguage( $userLanguage )->text(), $m ); return $result; } - /** * Run any hooks registered for logins, then HTTP redirect to * $this->mReturnTo (or Main Page if that's undefined). Formerly we had a @@ -859,8 +905,9 @@ class LoginForm extends SpecialPage { $injected_html = ''; wfRunHooks( 'UserLoginComplete', array( &$currentUser, &$injected_html ) ); - if( $injected_html !== '' ) { - $this->displaySuccessfulLogin( 'loginsuccess', $injected_html ); + if ( $injected_html !== '' ) { + $this->displaySuccessfulAction( $this->msg( 'loginsuccesstitle' ), + 'loginsuccess', $injected_html ); } else { $this->executeReturnTo( 'successredirect' ); } @@ -876,7 +923,7 @@ class LoginForm extends SpecialPage { # Run any hooks; display injected HTML $currentUser = $this->getUser(); $injected_html = ''; - $welcome_creation_msg = 'welcomecreation'; + $welcome_creation_msg = 'welcomecreation-msg'; wfRunHooks( 'UserLoginComplete', array( &$currentUser, &$injected_html ) ); @@ -887,18 +934,21 @@ class LoginForm extends SpecialPage { */ wfRunHooks( 'BeforeWelcomeCreation', array( &$welcome_creation_msg, &$injected_html ) ); - $this->displaySuccessfulLogin( $welcome_creation_msg, $injected_html ); + $this->displaySuccessfulAction( $this->msg( 'welcomeuser', $this->getUser()->getName() ), + $welcome_creation_msg, $injected_html ); } /** - * Display a "login successful" page. + * Display an "successful action" page. + * + * @param string|Message $title page's title * @param $msgname string * @param $injected_html string */ - private function displaySuccessfulLogin( $msgname, $injected_html ) { + private function displaySuccessfulAction( $title, $msgname, $injected_html ) { $out = $this->getOutput(); - $out->setPageTitle( $this->msg( 'loginsuccesstitle' ) ); - if( $msgname ){ + $out->setPageTitle( $title ); + if ( $msgname ) { $out->addWikiMsg( $msgname, wfEscapeWikiText( $this->getUser()->getName() ) ); } @@ -913,6 +963,7 @@ class LoginForm extends SpecialPage { * User::isBlockedFromCreateAccount(), which gets this block, ignores the 'hardblock' * setting on blocks (bug 13611). * @param $block Block the block causing this error + * @throws ErrorPageError */ function userBlockedMessage( Block $block ) { # Let's be nice about this, it's likely that this feature will be used @@ -922,23 +973,38 @@ class LoginForm extends SpecialPage { # haven't bothered to log out before trying to create an account to # evade it, but we'll leave that to their guilty conscience to figure # out. - - $out = $this->getOutput(); - $out->setPageTitle( $this->msg( 'cantcreateaccounttitle' ) ); - - $block_reason = $block->mReason; - if ( strval( $block_reason ) === '' ) { - $block_reason = $this->msg( 'blockednoreason' )->text(); - } - - $out->addWikiMsg( + throw new ErrorPageError( + 'cantcreateaccounttitle', 'cantcreateaccount-text', - $block->getTarget(), - $block_reason, - $block->getByName() + array( + $block->getTarget(), + $block->mReason ? $block->mReason : $this->msg( 'blockednoreason' )->text(), + $block->getByName() + ) ); + } - $this->executeReturnTo( 'error' ); + /** + * Add a "return to" link or redirect to it. + * Extensions can use this to reuse the "return to" logic after + * inject steps (such as redirection) into the login process. + * + * @param $type string, one of the following: + * - error: display a return to link ignoring $wgRedirectOnLogin + * - success: display a return to link using $wgRedirectOnLogin if needed + * - successredirect: send an HTTP redirect using $wgRedirectOnLogin if needed + * @param string $returnTo + * @param array|string $returnToQuery + * @param bool $stickHTTPs Keep redirect link on HTTPs + * @since 1.22 + */ + public function showReturnToPage( + $type, $returnTo = '', $returnToQuery = '', $stickHTTPs = false + ) { + $this->mReturnTo = $returnTo; + $this->mReturnToQuery = $returnToQuery; + $this->mStickHTTPS = $stickHTTPs; + $this->executeReturnTo( $type ); } /** @@ -965,14 +1031,22 @@ class LoginForm extends SpecialPage { $returnToTitle = Title::newMainPage(); } + if ( $wgSecureLogin && !$this->mStickHTTPS ) { + $options = array( 'http' ); + $proto = PROTO_HTTP; + } elseif ( $wgSecureLogin ) { + $options = array( 'https' ); + $proto = PROTO_HTTPS; + } else { + $options = array(); + $proto = PROTO_RELATIVE; + } + if ( $type == 'successredirect' ) { - $redirectUrl = $returnToTitle->getFullURL( $returnToQuery ); - if( $wgSecureLogin && !$this->mStickHTTPS ) { - $redirectUrl = preg_replace( '/^https:/', 'http:', $redirectUrl ); - } + $redirectUrl = $returnToTitle->getFullURL( $returnToQuery, false, $proto ); $this->getOutput()->redirect( $redirectUrl ); } else { - $this->getOutput()->addReturnTo( $returnToTitle, $returnToQuery ); + $this->getOutput()->addReturnTo( $returnToTitle, $returnToQuery, null, $options ); } } @@ -987,6 +1061,7 @@ class LoginForm extends SpecialPage { $titleObj = $this->getTitle(); $user = $this->getUser(); + $out = $this->getOutput(); if ( $this->mType == 'signup' ) { // Block signup here if in readonly. Keeps user from @@ -1003,7 +1078,8 @@ class LoginForm extends SpecialPage { } } - if ( $this->mUsername == '' ) { + // Pre-fill username (if not creating an account, bug 44775). + if ( $this->mUsername == '' && $this->mType != 'signup' ) { if ( $user->isLoggedIn() ) { $this->mUsername = $user->getName(); } else { @@ -1013,14 +1089,33 @@ class LoginForm extends SpecialPage { if ( $this->mType == 'signup' ) { $template = new UsercreateTemplate(); + + $out->addModuleStyles( array( + 'mediawiki.ui', + 'mediawiki.special.createaccount' + ) ); + // XXX hack pending RL or JS parse() support for complex content messages + // https://bugzilla.wikimedia.org/show_bug.cgi?id=25349 + $out->addJsConfigVars( 'wgCreateacctImgcaptchaHelp', + $this->msg( 'createacct-imgcaptcha-help' )->parse() ); + $out->addModules( array( + 'mediawiki.special.createaccount.js' + ) ); + // Must match number of benefits defined in messages + $template->set( 'benefitCount', 3 ); + $q = 'action=submitlogin&type=signup'; $linkq = 'type=login'; - $linkmsg = 'gotaccount'; } else { $template = new UserloginTemplate(); + + $out->addModuleStyles( array( + 'mediawiki.ui', + 'mediawiki.special.userlogin' + ) ); + $q = 'action=submitlogin&type=login'; $linkq = 'type=signup'; - $linkmsg = 'nologin'; } if ( $this->mReturnTo !== '' ) { @@ -1033,16 +1128,14 @@ class LoginForm extends SpecialPage { $linkq .= $returnto; } - # Don't show a "create account" link if the user can't - if( $this->showCreateOrLoginLink( $user ) ) { + # Don't show a "create account" link if the user can't. + if ( $this->showCreateOrLoginLink( $user ) ) { # Pass any language selection on to the mode switch link - if( $wgLoginLanguageSelector && $this->mLanguage ) { + if ( $wgLoginLanguageSelector && $this->mLanguage ) { $linkq .= '&uselang=' . $this->mLanguage; } - $link = Html::element( 'a', array( 'href' => $titleObj->getLocalURL( $linkq ) ), - $this->msg( $linkmsg . 'link' )->text() ); # Calling either 'gotaccountlink' or 'nologinlink' - - $template->set( 'link', $this->msg( $linkmsg )->rawParams( $link )->parse() ); + // Supply URL, login template creates the button. + $template->set( 'createOrLoginHref', $titleObj->getLocalURL( $linkq ) ); } else { $template->set( 'link', '' ); } @@ -1052,9 +1145,11 @@ class LoginForm extends SpecialPage { : is_array( $wgPasswordResetRoutes ) && in_array( true, array_values( $wgPasswordResetRoutes ) ); $template->set( 'header', '' ); + $template->set( 'skin', $this->getSkin() ); $template->set( 'name', $this->mUsername ); $template->set( 'password', $this->mPassword ); $template->set( 'retype', $this->mRetype ); + $template->set( 'createemailset', $this->mCreateaccountMail ); $template->set( 'email', $this->mEmail ); $template->set( 'realname', $this->mRealName ); $template->set( 'domain', $this->mDomain ); @@ -1074,7 +1169,9 @@ class LoginForm extends SpecialPage { $template->set( 'usereason', $user->isLoggedIn() ); $template->set( 'remember', $user->getOption( 'rememberpassword' ) || $this->mRemember ); $template->set( 'cansecurelogin', ( $wgSecureLogin === true ) ); - $template->set( 'stickHTTPS', $this->mStickHTTPS ); + $template->set( 'stickhttps', (int)$this->mStickHTTPS ); + $template->set( 'loggedin', $user->isLoggedIn() ); + $template->set( 'loggedinuser', $user->getName() ); if ( $this->mType == 'signup' ) { if ( !self::getCreateaccountToken() ) { @@ -1089,15 +1186,16 @@ class LoginForm extends SpecialPage { } # Prepare language selection links as needed - if( $wgLoginLanguageSelector ) { + if ( $wgLoginLanguageSelector ) { $template->set( 'languages', $this->makeLanguageSelector() ); - if( $this->mLanguage ) { + if ( $this->mLanguage ) { $template->set( 'uselang', $this->mLanguage ); } } + $template->set( 'secureLoginUrl', $this->mSecureLoginUrl ); // Use loginend-https for HTTPS requests if it's not blank, loginend otherwise - // Ditto for signupend + // Ditto for signupend. New forms use neither. $usingHTTPS = WebRequest::detectProtocol() == 'https'; $loginendHTTPS = $this->msg( 'loginend-https' ); $signupendHTTPS = $this->msg( 'signupend-https' ); @@ -1120,22 +1218,21 @@ class LoginForm extends SpecialPage { wfRunHooks( 'UserLoginForm', array( &$template ) ); } - $out = $this->getOutput(); $out->disallowUserJs(); // just in case... $out->addTemplate( $template ); } /** - * @private + * Whether the login/create account form should display a link to the + * other form (in addition to whatever the skin provides). * * @param $user User - * - * @return Boolean + * @return bool */ - function showCreateOrLoginLink( &$user ) { - if( $this->mType == 'signup' ) { + private function showCreateOrLoginLink( &$user ) { + if ( $this->mType == 'signup' ) { return true; - } elseif( $user->isAllowed( 'createaccount' ) ) { + } elseif ( $user->isAllowed( 'createaccount' ) ) { return true; } else { return false; @@ -1213,17 +1310,12 @@ class LoginForm extends SpecialPage { * Renew the user's session id, using strong entropy */ private function renewSessionId() { - if ( wfCheckEntropy() ) { - session_regenerate_id( false ); - } else { - //If we don't trust PHP's entropy, we have to replace the session manually - $tmp = $_SESSION; - session_unset(); - session_write_close(); - session_id( MWCryptRand::generateHex( 32 ) ); - session_start(); - $_SESSION = $tmp; + global $wgSecureLogin, $wgCookieSecure; + if ( $wgSecureLogin && !$this->mStickHTTPS ) { + $wgCookieSecure = false; } + + wfResetSessionID(); } /** @@ -1260,13 +1352,6 @@ class LoginForm extends SpecialPage { } /** - * @private - */ - function throttleHit( $limit ) { - $this->mainLoginForm( $this->msg( 'acct_creation_throttle_hit' )->numParams( $limit )->parse() ); - } - - /** * Produce a bar of links which allow the user to select another language * during login/registration but retain "returnto" * @@ -1274,10 +1359,10 @@ class LoginForm extends SpecialPage { */ function makeLanguageSelector() { $msg = $this->msg( 'loginlanguagelinks' )->inContentLanguage(); - if( !$msg->isBlank() ) { + if ( !$msg->isBlank() ) { $langs = explode( "\n", $msg->text() ); $links = array(); - foreach( $langs as $lang ) { + foreach ( $langs as $lang ) { $lang = trim( $lang, '* ' ); $parts = explode( '|', $lang ); if ( count( $parts ) >= 2 ) { @@ -1295,20 +1380,20 @@ class LoginForm extends SpecialPage { * Create a language selector link for a particular language * Links back to this page preserving type and returnto * - * @param $text Link text - * @param $lang Language code + * @param string $text Link text + * @param string $lang Language code * @return string */ function makeLanguageSelectorLink( $text, $lang ) { - if( $this->getLanguage()->getCode() == $lang ) { + if ( $this->getLanguage()->getCode() == $lang ) { // no link for currently used language return htmlspecialchars( $text ); } $query = array( 'uselang' => $lang ); - if( $this->mType == 'signup' ) { + if ( $this->mType == 'signup' ) { $query['type'] = 'signup'; } - if( $this->mReturnTo !== '' ) { + if ( $this->mReturnTo !== '' ) { $query['returnto'] = $this->mReturnTo; $query['returntoquery'] = $this->mReturnToQuery; } @@ -1324,4 +1409,8 @@ class LoginForm extends SpecialPage { $query ); } + + protected function getGroupName() { + return 'login'; + } } |