MediaWiki  REL1_24
SpecialUserlogin.php
Go to the documentation of this file.
00001 <?php
00029 class LoginForm extends SpecialPage {
00030     const SUCCESS = 0;
00031     const NO_NAME = 1;
00032     const ILLEGAL = 2;
00033     const WRONG_PLUGIN_PASS = 3;
00034     const NOT_EXISTS = 4;
00035     const WRONG_PASS = 5;
00036     const EMPTY_PASS = 6;
00037     const RESET_PASS = 7;
00038     const ABORTED = 8;
00039     const CREATE_BLOCKED = 9;
00040     const THROTTLED = 10;
00041     const USER_BLOCKED = 11;
00042     const NEED_TOKEN = 12;
00043     const WRONG_TOKEN = 13;
00044     const USER_MIGRATED = 14;
00045 
00059     public static $validErrorMessages = array(
00060         'exception-nologin-text',
00061         'watchlistanontext',
00062         'changeemail-no-info',
00063         'resetpass-no-info',
00064         'confirmemail_needlogin',
00065         'prefsnologintext2',
00066     );
00067 
00068     public $mAbortLoginErrorMsg = null;
00069 
00070     protected $mUsername;
00071     protected $mPassword;
00072     protected $mRetype;
00073     protected $mReturnTo;
00074     protected $mCookieCheck;
00075     protected $mPosted;
00076     protected $mAction;
00077     protected $mCreateaccount;
00078     protected $mCreateaccountMail;
00079     protected $mLoginattempt;
00080     protected $mRemember;
00081     protected $mEmail;
00082     protected $mDomain;
00083     protected $mLanguage;
00084     protected $mSkipCookieCheck;
00085     protected $mReturnToQuery;
00086     protected $mToken;
00087     protected $mStickHTTPS;
00088     protected $mType;
00089     protected $mReason;
00090     protected $mRealName;
00091     protected $mEntryError = '';
00092     protected $mEntryErrorType = 'error';
00093 
00094     private $mTempPasswordUsed;
00095     private $mLoaded = false;
00096     private $mSecureLoginUrl;
00097 
00099     private $mOverrideRequest = null;
00100 
00102     private $mRequest = null;
00103 
00107     public function __construct( $request = null ) {
00108         parent::__construct( 'Userlogin' );
00109 
00110         $this->mOverrideRequest = $request;
00111     }
00112 
00116     function load() {
00117         global $wgAuth, $wgHiddenPrefs, $wgEnableEmail;
00118 
00119         if ( $this->mLoaded ) {
00120             return;
00121         }
00122         $this->mLoaded = true;
00123 
00124         if ( $this->mOverrideRequest === null ) {
00125             $request = $this->getRequest();
00126         } else {
00127             $request = $this->mOverrideRequest;
00128         }
00129         $this->mRequest = $request;
00130 
00131         $this->mType = $request->getText( 'type' );
00132         $this->mUsername = $request->getText( 'wpName' );
00133         $this->mPassword = $request->getText( 'wpPassword' );
00134         $this->mRetype = $request->getText( 'wpRetype' );
00135         $this->mDomain = $request->getText( 'wpDomain' );
00136         $this->mReason = $request->getText( 'wpReason' );
00137         $this->mCookieCheck = $request->getVal( 'wpCookieCheck' );
00138         $this->mPosted = $request->wasPosted();
00139         $this->mCreateaccountMail = $request->getCheck( 'wpCreateaccountMail' )
00140             && $wgEnableEmail;
00141         $this->mCreateaccount = $request->getCheck( 'wpCreateaccount' ) && !$this->mCreateaccountMail;
00142         $this->mLoginattempt = $request->getCheck( 'wpLoginattempt' );
00143         $this->mAction = $request->getVal( 'action' );
00144         $this->mRemember = $request->getCheck( 'wpRemember' );
00145         $this->mFromHTTP = $request->getBool( 'fromhttp', false );
00146         $this->mStickHTTPS = ( !$this->mFromHTTP && $request->getProtocol() === 'https' )
00147             || $request->getBool( 'wpForceHttps', false );
00148         $this->mLanguage = $request->getText( 'uselang' );
00149         $this->mSkipCookieCheck = $request->getCheck( 'wpSkipCookieCheck' );
00150         $this->mToken = $this->mType == 'signup'
00151             ? $request->getVal( 'wpCreateaccountToken' )
00152             : $request->getVal( 'wpLoginToken' );
00153         $this->mReturnTo = $request->getVal( 'returnto', '' );
00154         $this->mReturnToQuery = $request->getVal( 'returntoquery', '' );
00155 
00156         // Show an error or warning passed on from a previous page
00157         $entryError = $this->msg( $request->getVal( 'error', '' ) );
00158         $entryWarning = $this->msg( $request->getVal( 'warning', '' ) );
00159         // bc: provide login link as a parameter for messages where the translation
00160         // was not updated
00161         $loginreqlink = Linker::linkKnown(
00162             $this->getPageTitle(),
00163             $this->msg( 'loginreqlink' )->escaped(),
00164             array(),
00165             array(
00166                 'returnto' => $this->mReturnTo,
00167                 'returntoquery' => $this->mReturnToQuery,
00168                 'uselang' => $this->mLanguage,
00169                 'fromhttp' => $this->mFromHTTP ? '1' : '0',
00170             )
00171         );
00172 
00173         // Only show valid error or warning messages.
00174         if ( $entryError->exists()
00175             && in_array( $entryError->getKey(), self::$validErrorMessages )
00176         ) {
00177             $this->mEntryErrorType = 'error';
00178             $this->mEntryError = $entryError->rawParams( $loginreqlink )->escaped();
00179 
00180         } elseif ( $entryWarning->exists()
00181             && in_array( $entryWarning->getKey(), self::$validErrorMessages )
00182         ) {
00183             $this->mEntryErrorType = 'warning';
00184             $this->mEntryError = $entryWarning->rawParams( $loginreqlink )->escaped();
00185         }
00186 
00187         if ( $wgEnableEmail ) {
00188             $this->mEmail = $request->getText( 'wpEmail' );
00189         } else {
00190             $this->mEmail = '';
00191         }
00192         if ( !in_array( 'realname', $wgHiddenPrefs ) ) {
00193             $this->mRealName = $request->getText( 'wpRealName' );
00194         } else {
00195             $this->mRealName = '';
00196         }
00197 
00198         if ( !$wgAuth->validDomain( $this->mDomain ) ) {
00199             $this->mDomain = $wgAuth->getDomain();
00200         }
00201         $wgAuth->setDomain( $this->mDomain );
00202 
00203         # 1. When switching accounts, it sucks to get automatically logged out
00204         # 2. Do not return to PasswordReset after a successful password change
00205         #    but goto Wiki start page (Main_Page) instead ( bug 33997 )
00206         $returnToTitle = Title::newFromText( $this->mReturnTo );
00207         if ( is_object( $returnToTitle )
00208             && ( $returnToTitle->isSpecial( 'Userlogout' )
00209                 || $returnToTitle->isSpecial( 'PasswordReset' ) )
00210         ) {
00211             $this->mReturnTo = '';
00212             $this->mReturnToQuery = '';
00213         }
00214     }
00215 
00216     function getDescription() {
00217         if ( $this->mType === 'signup' ) {
00218             return $this->msg( 'createaccount' )->text();
00219         } else {
00220             return $this->msg( 'login' )->text();
00221         }
00222     }
00223 
00227     public function execute( $subPage ) {
00228         if ( session_id() == '' ) {
00229             wfSetupSession();
00230         }
00231 
00232         $this->load();
00233 
00234         // Check for [[Special:Userlogin/signup]]. This affects form display and
00235         // page title.
00236         if ( $subPage == 'signup' ) {
00237             $this->mType = 'signup';
00238         }
00239         $this->setHeaders();
00240 
00241         // In the case where the user is already logged in, and was redirected to the login form from a
00242         // page that requires login, do not show the login page. The use case scenario for this is when
00243         // a user opens a large number of tabs, is redirected to the login page on all of them, and then
00244         // logs in on one, expecting all the others to work properly.
00245         //
00246         // However, do show the form if it was visited intentionally (no 'returnto' is present). People
00247         // who often switch between several accounts have grown accustomed to this behavior.
00248         if (
00249             $this->mType !== 'signup' &&
00250             !$this->mPosted &&
00251             $this->getUser()->isLoggedIn() &&
00252             ( $this->mReturnTo !== '' || $this->mReturnToQuery !== '' )
00253         ) {
00254             $this->successfulLogin();
00255         }
00256 
00257         // If logging in and not on HTTPS, either redirect to it or offer a link.
00258         global $wgSecureLogin;
00259         if ( $this->mRequest->getProtocol() !== 'https' ) {
00260             $title = $this->getFullTitle();
00261             $query = array(
00262                 'returnto' => $this->mReturnTo !== '' ? $this->mReturnTo : null,
00263                 'returntoquery' => $this->mReturnToQuery !== '' ?
00264                     $this->mReturnToQuery : null,
00265                 'title' => null,
00266                 ( $this->mEntryErrorType === 'error' ? 'error' : 'warning' ) => $this->mEntryError,
00267             ) + $this->mRequest->getQueryValues();
00268             $url = $title->getFullURL( $query, false, PROTO_HTTPS );
00269             if ( $wgSecureLogin
00270                 && wfCanIPUseHTTPS( $this->getRequest()->getIP() )
00271                 && !$this->mFromHTTP ) // Avoid infinite redirect
00272             {
00273                 $url = wfAppendQuery( $url, 'fromhttp=1' );
00274                 $this->getOutput()->redirect( $url );
00275                 // Since we only do this redir to change proto, always vary
00276                 $this->getOutput()->addVaryHeader( 'X-Forwarded-Proto' );
00277 
00278                 return;
00279             } else {
00280                 // A wiki without HTTPS login support should set $wgServer to
00281                 // http://somehost, in which case the secure URL generated
00282                 // above won't actually start with https://
00283                 if ( substr( $url, 0, 8 ) === 'https://' ) {
00284                     $this->mSecureLoginUrl = $url;
00285                 }
00286             }
00287         }
00288 
00289         if ( !is_null( $this->mCookieCheck ) ) {
00290             $this->onCookieRedirectCheck( $this->mCookieCheck );
00291 
00292             return;
00293         } elseif ( $this->mPosted ) {
00294             if ( $this->mCreateaccount ) {
00295                 $this->addNewAccount();
00296 
00297                 return;
00298             } elseif ( $this->mCreateaccountMail ) {
00299                 $this->addNewAccountMailPassword();
00300 
00301                 return;
00302             } elseif ( ( 'submitlogin' == $this->mAction ) || $this->mLoginattempt ) {
00303                 $this->processLogin();
00304 
00305                 return;
00306             }
00307         }
00308         $this->mainLoginForm( $this->mEntryError, $this->mEntryErrorType );
00309     }
00310 
00314     function addNewAccountMailPassword() {
00315         if ( $this->mEmail == '' ) {
00316             $this->mainLoginForm( $this->msg( 'noemailcreate' )->escaped() );
00317 
00318             return;
00319         }
00320 
00321         $status = $this->addNewAccountInternal();
00322         if ( !$status->isGood() ) {
00323             $error = $status->getMessage();
00324             $this->mainLoginForm( $error->toString() );
00325 
00326             return;
00327         }
00328 
00329         $u = $status->getValue();
00330 
00331         // Wipe the initial password and mail a temporary one
00332         $u->setPassword( null );
00333         $u->saveSettings();
00334         $result = $this->mailPasswordInternal( $u, false, 'createaccount-title', 'createaccount-text' );
00335 
00336         wfRunHooks( 'AddNewAccount', array( $u, true ) );
00337         $u->addNewUserLogEntry( 'byemail', $this->mReason );
00338 
00339         $out = $this->getOutput();
00340         $out->setPageTitle( $this->msg( 'accmailtitle' ) );
00341 
00342         if ( !$result->isGood() ) {
00343             $this->mainLoginForm( $this->msg( 'mailerror', $result->getWikiText() )->text() );
00344         } else {
00345             $out->addWikiMsg( 'accmailtext', $u->getName(), $u->getEmail() );
00346             $this->executeReturnTo( 'success' );
00347         }
00348     }
00349 
00354     function addNewAccount() {
00355         global $wgContLang, $wgUser, $wgEmailAuthentication, $wgLoginLanguageSelector;
00356 
00357         # Create the account and abort if there's a problem doing so
00358         $status = $this->addNewAccountInternal();
00359         if ( !$status->isGood() ) {
00360             $error = $status->getMessage();
00361             $this->mainLoginForm( $error->toString() );
00362 
00363             return false;
00364         }
00365 
00366         $u = $status->getValue();
00367 
00368         # Only save preferences if the user is not creating an account for someone else.
00369         if ( $this->getUser()->isAnon() ) {
00370             # If we showed up language selection links, and one was in use, be
00371             # smart (and sensible) and save that language as the user's preference
00372             if ( $wgLoginLanguageSelector && $this->mLanguage ) {
00373                 $u->setOption( 'language', $this->mLanguage );
00374             } else {
00375 
00376                 # Otherwise the user's language preference defaults to $wgContLang,
00377                 # but it may be better to set it to their preferred $wgContLang variant,
00378                 # based on browser preferences or URL parameters.
00379                 $u->setOption( 'language', $wgContLang->getPreferredVariant() );
00380             }
00381             if ( $wgContLang->hasVariants() ) {
00382                 $u->setOption( 'variant', $wgContLang->getPreferredVariant() );
00383             }
00384         }
00385 
00386         $out = $this->getOutput();
00387 
00388         # Send out an email authentication message if needed
00389         if ( $wgEmailAuthentication && Sanitizer::validateEmail( $u->getEmail() ) ) {
00390             $status = $u->sendConfirmationMail();
00391             if ( $status->isGood() ) {
00392                 $out->addWikiMsg( 'confirmemail_oncreate' );
00393             } else {
00394                 $out->addWikiText( $status->getWikiText( 'confirmemail_sendfailed' ) );
00395             }
00396         }
00397 
00398         # Save settings (including confirmation token)
00399         $u->saveSettings();
00400 
00401         # If not logged in, assume the new account as the current one and set
00402         # session cookies then show a "welcome" message or a "need cookies"
00403         # message as needed
00404         if ( $this->getUser()->isAnon() ) {
00405             $u->setCookies();
00406             $wgUser = $u;
00407             // This should set it for OutputPage and the Skin
00408             // which is needed or the personal links will be
00409             // wrong.
00410             $this->getContext()->setUser( $u );
00411             wfRunHooks( 'AddNewAccount', array( $u, false ) );
00412             $u->addNewUserLogEntry( 'create' );
00413             if ( $this->hasSessionCookie() ) {
00414                 $this->successfulCreation();
00415             } else {
00416                 $this->cookieRedirectCheck( 'new' );
00417             }
00418         } else {
00419             # Confirm that the account was created
00420             $out->setPageTitle( $this->msg( 'accountcreated' ) );
00421             $out->addWikiMsg( 'accountcreatedtext', $u->getName() );
00422             $out->addReturnTo( $this->getPageTitle() );
00423             wfRunHooks( 'AddNewAccount', array( $u, false ) );
00424             $u->addNewUserLogEntry( 'create2', $this->mReason );
00425         }
00426 
00427         return true;
00428     }
00429 
00436     public function addNewAccountInternal() {
00437         global $wgAuth, $wgMemc, $wgAccountCreationThrottle,
00438             $wgMinimalPasswordLength, $wgEmailConfirmToEdit;
00439 
00440         // If the user passes an invalid domain, something is fishy
00441         if ( !$wgAuth->validDomain( $this->mDomain ) ) {
00442             return Status::newFatal( 'wrongpassword' );
00443         }
00444 
00445         // If we are not allowing users to login locally, we should be checking
00446         // to see if the user is actually able to authenticate to the authenti-
00447         // cation server before they create an account (otherwise, they can
00448         // create a local account and login as any domain user). We only need
00449         // to check this for domains that aren't local.
00450         if ( 'local' != $this->mDomain && $this->mDomain != '' ) {
00451             if (
00452                 !$wgAuth->canCreateAccounts() &&
00453                 (
00454                     !$wgAuth->userExists( $this->mUsername ) ||
00455                     !$wgAuth->authenticate( $this->mUsername, $this->mPassword )
00456                 )
00457             ) {
00458                 return Status::newFatal( 'wrongpassword' );
00459             }
00460         }
00461 
00462         if ( wfReadOnly() ) {
00463             throw new ReadOnlyError;
00464         }
00465 
00466         # Request forgery checks.
00467         if ( !self::getCreateaccountToken() ) {
00468             self::setCreateaccountToken();
00469 
00470             return Status::newFatal( 'nocookiesfornew' );
00471         }
00472 
00473         # The user didn't pass a createaccount token
00474         if ( !$this->mToken ) {
00475             return Status::newFatal( 'sessionfailure' );
00476         }
00477 
00478         # Validate the createaccount token
00479         if ( $this->mToken !== self::getCreateaccountToken() ) {
00480             return Status::newFatal( 'sessionfailure' );
00481         }
00482 
00483         # Check permissions
00484         $currentUser = $this->getUser();
00485         $creationBlock = $currentUser->isBlockedFromCreateAccount();
00486         if ( !$currentUser->isAllowed( 'createaccount' ) ) {
00487             throw new PermissionsError( 'createaccount' );
00488         } elseif ( $creationBlock instanceof Block ) {
00489             // Throws an ErrorPageError.
00490             $this->userBlockedMessage( $creationBlock );
00491 
00492             // This should never be reached.
00493             return false;
00494         }
00495 
00496         # Include checks that will include GlobalBlocking (Bug 38333)
00497         $permErrors = $this->getPageTitle()->getUserPermissionsErrors(
00498             'createaccount',
00499             $currentUser,
00500             true
00501         );
00502 
00503         if ( count( $permErrors ) ) {
00504             throw new PermissionsError( 'createaccount', $permErrors );
00505         }
00506 
00507         $ip = $this->getRequest()->getIP();
00508         if ( $currentUser->isDnsBlacklisted( $ip, true /* check $wgProxyWhitelist */ ) ) {
00509             return Status::newFatal( 'sorbs_create_account_reason' );
00510         }
00511 
00512         // Normalize the name so that silly things don't cause "invalid username"
00513         // errors. User::newFromName does some rather strict checking, rejecting
00514         // e.g. leading/trailing/multiple spaces. But first we need to reject
00515         // usernames that would be treated as titles with a fragment part.
00516         if ( strpos( $this->mUsername, '#' ) !== false ) {
00517             return Status::newFatal( 'noname' );
00518         }
00519         $title = Title::makeTitleSafe( NS_USER, $this->mUsername );
00520         if ( !is_object( $title ) ) {
00521             return Status::newFatal( 'noname' );
00522         }
00523 
00524         # Now create a dummy user ($u) and check if it is valid
00525         $u = User::newFromName( $title->getText(), 'creatable' );
00526         if ( !is_object( $u ) ) {
00527             return Status::newFatal( 'noname' );
00528         } elseif ( 0 != $u->idForName() ) {
00529             return Status::newFatal( 'userexists' );
00530         }
00531 
00532         if ( $this->mCreateaccountMail ) {
00533             # do not force a password for account creation by email
00534             # set invalid password, it will be replaced later by a random generated password
00535             $this->mPassword = null;
00536         } else {
00537             if ( $this->mPassword !== $this->mRetype ) {
00538                 return Status::newFatal( 'badretype' );
00539             }
00540 
00541             # check for minimal password length
00542             $valid = $u->getPasswordValidity( $this->mPassword );
00543             if ( $valid !== true ) {
00544                 if ( !is_array( $valid ) ) {
00545                     $valid = array( $valid, $wgMinimalPasswordLength );
00546                 }
00547 
00548                 return call_user_func_array( 'Status::newFatal', $valid );
00549             }
00550         }
00551 
00552         # if you need a confirmed email address to edit, then obviously you
00553         # need an email address.
00554         if ( $wgEmailConfirmToEdit && strval( $this->mEmail ) === '' ) {
00555             return Status::newFatal( 'noemailtitle' );
00556         }
00557 
00558         if ( strval( $this->mEmail ) !== '' && !Sanitizer::validateEmail( $this->mEmail ) ) {
00559             return Status::newFatal( 'invalidemailaddress' );
00560         }
00561 
00562         # Set some additional data so the AbortNewAccount hook can be used for
00563         # more than just username validation
00564         $u->setEmail( $this->mEmail );
00565         $u->setRealName( $this->mRealName );
00566 
00567         $abortError = '';
00568         $abortStatus = null;
00569         if ( !wfRunHooks( 'AbortNewAccount', array( $u, &$abortError, &$abortStatus ) ) ) {
00570             // Hook point to add extra creation throttles and blocks
00571             wfDebug( "LoginForm::addNewAccountInternal: a hook blocked creation\n" );
00572             if ( $abortStatus === null ) {
00573                 // Report back the old string as a raw message status.
00574                 // This will report the error back as 'createaccount-hook-aborted'
00575                 // with the given string as the message.
00576                 // To return a different error code, return a Status object.
00577                 $abortError = new Message( 'createaccount-hook-aborted', array( $abortError ) );
00578                 $abortError->text();
00579 
00580                 return Status::newFatal( $abortError );
00581             } else {
00582                 // For MediaWiki 1.23+ and updated hooks, return the Status object
00583                 // returned from the hook.
00584                 return $abortStatus;
00585             }
00586         }
00587 
00588         // Hook point to check for exempt from account creation throttle
00589         if ( !wfRunHooks( 'ExemptFromAccountCreationThrottle', array( $ip ) ) ) {
00590             wfDebug( "LoginForm::exemptFromAccountCreationThrottle: a hook " .
00591                 "allowed account creation w/o throttle\n" );
00592         } else {
00593             if ( ( $wgAccountCreationThrottle && $currentUser->isPingLimitable() ) ) {
00594                 $key = wfMemcKey( 'acctcreate', 'ip', $ip );
00595                 $value = $wgMemc->get( $key );
00596                 if ( !$value ) {
00597                     $wgMemc->set( $key, 0, 86400 );
00598                 }
00599                 if ( $value >= $wgAccountCreationThrottle ) {
00600                     return Status::newFatal( 'acct_creation_throttle_hit', $wgAccountCreationThrottle );
00601                 }
00602                 $wgMemc->incr( $key );
00603             }
00604         }
00605 
00606         if ( !$wgAuth->addUser( $u, $this->mPassword, $this->mEmail, $this->mRealName ) ) {
00607             return Status::newFatal( 'externaldberror' );
00608         }
00609 
00610         self::clearCreateaccountToken();
00611 
00612         return $this->initUser( $u, false );
00613     }
00614 
00624     function initUser( $u, $autocreate ) {
00625         global $wgAuth;
00626 
00627         $status = $u->addToDatabase();
00628         if ( !$status->isOK() ) {
00629             return $status;
00630         }
00631 
00632         if ( $wgAuth->allowPasswordChange() ) {
00633             $u->setPassword( $this->mPassword );
00634         }
00635 
00636         $u->setEmail( $this->mEmail );
00637         $u->setRealName( $this->mRealName );
00638         $u->setToken();
00639 
00640         $wgAuth->initUser( $u, $autocreate );
00641 
00642         $u->saveSettings();
00643 
00644         // Update user count
00645         DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 0, 0, 0, 1 ) );
00646 
00647         // Watch user's userpage and talk page
00648         $u->addWatch( $u->getUserPage(), WatchedItem::IGNORE_USER_RIGHTS );
00649 
00650         return Status::newGood( $u );
00651     }
00652 
00661     public function authenticateUserData() {
00662         global $wgUser, $wgAuth;
00663 
00664         $this->load();
00665 
00666         if ( $this->mUsername == '' ) {
00667             return self::NO_NAME;
00668         }
00669 
00670         // We require a login token to prevent login CSRF
00671         // Handle part of this before incrementing the throttle so
00672         // token-less login attempts don't count towards the throttle
00673         // but wrong-token attempts do.
00674 
00675         // If the user doesn't have a login token yet, set one.
00676         if ( !self::getLoginToken() ) {
00677             self::setLoginToken();
00678 
00679             return self::NEED_TOKEN;
00680         }
00681         // If the user didn't pass a login token, tell them we need one
00682         if ( !$this->mToken ) {
00683             return self::NEED_TOKEN;
00684         }
00685 
00686         $throttleCount = self::incLoginThrottle( $this->mUsername );
00687         if ( $throttleCount === true ) {
00688             return self::THROTTLED;
00689         }
00690 
00691         // Validate the login token
00692         if ( $this->mToken !== self::getLoginToken() ) {
00693             return self::WRONG_TOKEN;
00694         }
00695 
00696         // Load the current user now, and check to see if we're logging in as
00697         // the same name. This is necessary because loading the current user
00698         // (say by calling getName()) calls the UserLoadFromSession hook, which
00699         // potentially creates the user in the database. Until we load $wgUser,
00700         // checking for user existence using User::newFromName($name)->getId() below
00701         // will effectively be using stale data.
00702         if ( $this->getUser()->getName() === $this->mUsername ) {
00703             wfDebug( __METHOD__ . ": already logged in as {$this->mUsername}\n" );
00704 
00705             return self::SUCCESS;
00706         }
00707 
00708         $u = User::newFromName( $this->mUsername );
00709 
00710         // Give extensions a way to indicate the username has been updated,
00711         // rather than telling the user the account doesn't exist.
00712         if ( !wfRunHooks( 'LoginUserMigrated', array( $u, &$msg ) ) ) {
00713             $this->mAbortLoginErrorMsg = $msg;
00714             return self::USER_MIGRATED;
00715         }
00716 
00717         if ( !( $u instanceof User ) || !User::isUsableName( $u->getName() ) ) {
00718             return self::ILLEGAL;
00719         }
00720 
00721         $isAutoCreated = false;
00722         if ( $u->getID() == 0 ) {
00723             $status = $this->attemptAutoCreate( $u );
00724             if ( $status !== self::SUCCESS ) {
00725                 return $status;
00726             } else {
00727                 $isAutoCreated = true;
00728             }
00729         } else {
00730             $u->load();
00731         }
00732 
00733         // Give general extensions, such as a captcha, a chance to abort logins
00734         $abort = self::ABORTED;
00735         $msg = null;
00736         if ( !wfRunHooks( 'AbortLogin', array( $u, $this->mPassword, &$abort, &$msg ) ) ) {
00737             $this->mAbortLoginErrorMsg = $msg;
00738 
00739             return $abort;
00740         }
00741 
00742         global $wgBlockDisablesLogin;
00743         if ( !$u->checkPassword( $this->mPassword ) ) {
00744             if ( $u->checkTemporaryPassword( $this->mPassword ) ) {
00745                 // The e-mailed temporary password should not be used for actu-
00746                 // al logins; that's a very sloppy habit, and insecure if an
00747                 // attacker has a few seconds to click "search" on someone's o-
00748                 // pen mail reader.
00749                 //
00750                 // Allow it to be used only to reset the password a single time
00751                 // to a new value, which won't be in the user's e-mail ar-
00752                 // chives.
00753                 //
00754                 // For backwards compatibility, we'll still recognize it at the
00755                 // login form to minimize surprises for people who have been
00756                 // logging in with a temporary password for some time.
00757                 //
00758                 // As a side-effect, we can authenticate the user's e-mail ad-
00759                 // dress if it's not already done, since the temporary password
00760                 // was sent via e-mail.
00761                 if ( !$u->isEmailConfirmed() ) {
00762                     $u->confirmEmail();
00763                     $u->saveSettings();
00764                 }
00765 
00766                 // At this point we just return an appropriate code/ indicating
00767                 // that the UI should show a password reset form; bot inter-
00768                 // faces etc will probably just fail cleanly here.
00769                 $this->mAbortLoginErrorMsg = 'resetpass-temp-emailed';
00770                 $this->mTempPasswordUsed = true;
00771                 $retval = self::RESET_PASS;
00772             } else {
00773                 $retval = ( $this->mPassword == '' ) ? self::EMPTY_PASS : self::WRONG_PASS;
00774             }
00775         } elseif ( $wgBlockDisablesLogin && $u->isBlocked() ) {
00776             // If we've enabled it, make it so that a blocked user cannot login
00777             $retval = self::USER_BLOCKED;
00778         } elseif ( $u->getPasswordExpired() == 'hard' ) {
00779             // Force reset now, without logging in
00780             $retval = self::RESET_PASS;
00781             $this->mAbortLoginErrorMsg = 'resetpass-expired';
00782         } else {
00783             $wgAuth->updateUser( $u );
00784             $wgUser = $u;
00785             // This should set it for OutputPage and the Skin
00786             // which is needed or the personal links will be
00787             // wrong.
00788             $this->getContext()->setUser( $u );
00789 
00790             // Please reset throttle for successful logins, thanks!
00791             if ( $throttleCount ) {
00792                 self::clearLoginThrottle( $this->mUsername );
00793             }
00794 
00795             if ( $isAutoCreated ) {
00796                 // Must be run after $wgUser is set, for correct new user log
00797                 wfRunHooks( 'AuthPluginAutoCreate', array( $u ) );
00798             }
00799 
00800             $retval = self::SUCCESS;
00801         }
00802         wfRunHooks( 'LoginAuthenticateAudit', array( $u, $this->mPassword, $retval ) );
00803 
00804         return $retval;
00805     }
00806 
00813     public static function incLoginThrottle( $username ) {
00814         global $wgPasswordAttemptThrottle, $wgMemc, $wgRequest;
00815         $username = trim( $username ); // sanity
00816 
00817         $throttleCount = 0;
00818         if ( is_array( $wgPasswordAttemptThrottle ) ) {
00819             $throttleKey = wfMemcKey( 'password-throttle', $wgRequest->getIP(), md5( $username ) );
00820             $count = $wgPasswordAttemptThrottle['count'];
00821             $period = $wgPasswordAttemptThrottle['seconds'];
00822 
00823             $throttleCount = $wgMemc->get( $throttleKey );
00824             if ( !$throttleCount ) {
00825                 $wgMemc->add( $throttleKey, 1, $period ); // start counter
00826             } elseif ( $throttleCount < $count ) {
00827                 $wgMemc->incr( $throttleKey );
00828             } elseif ( $throttleCount >= $count ) {
00829                 return true;
00830             }
00831         }
00832 
00833         return $throttleCount;
00834     }
00835 
00841     public static function clearLoginThrottle( $username ) {
00842         global $wgMemc, $wgRequest;
00843         $username = trim( $username ); // sanity
00844 
00845         $throttleKey = wfMemcKey( 'password-throttle', $wgRequest->getIP(), md5( $username ) );
00846         $wgMemc->delete( $throttleKey );
00847     }
00848 
00857     function attemptAutoCreate( $user ) {
00858         global $wgAuth;
00859 
00860         if ( $this->getUser()->isBlockedFromCreateAccount() ) {
00861             wfDebug( __METHOD__ . ": user is blocked from account creation\n" );
00862 
00863             return self::CREATE_BLOCKED;
00864         }
00865 
00866         if ( !$wgAuth->autoCreate() ) {
00867             return self::NOT_EXISTS;
00868         }
00869 
00870         if ( !$wgAuth->userExists( $user->getName() ) ) {
00871             wfDebug( __METHOD__ . ": user does not exist\n" );
00872 
00873             return self::NOT_EXISTS;
00874         }
00875 
00876         if ( !$wgAuth->authenticate( $user->getName(), $this->mPassword ) ) {
00877             wfDebug( __METHOD__ . ": \$wgAuth->authenticate() returned false, aborting\n" );
00878 
00879             return self::WRONG_PLUGIN_PASS;
00880         }
00881 
00882         $abortError = '';
00883         if ( !wfRunHooks( 'AbortAutoAccount', array( $user, &$abortError ) ) ) {
00884             // Hook point to add extra creation throttles and blocks
00885             wfDebug( "LoginForm::attemptAutoCreate: a hook blocked creation: $abortError\n" );
00886             $this->mAbortLoginErrorMsg = $abortError;
00887 
00888             return self::ABORTED;
00889         }
00890 
00891         wfDebug( __METHOD__ . ": creating account\n" );
00892         $status = $this->initUser( $user, true );
00893 
00894         if ( !$status->isOK() ) {
00895             $errors = $status->getErrorsByType( 'error' );
00896             $this->mAbortLoginErrorMsg = $errors[0]['message'];
00897 
00898             return self::ABORTED;
00899         }
00900 
00901         return self::SUCCESS;
00902     }
00903 
00904     function processLogin() {
00905         global $wgMemc, $wgLang, $wgSecureLogin, $wgPasswordAttemptThrottle,
00906             $wgInvalidPasswordReset;
00907 
00908         switch ( $this->authenticateUserData() ) {
00909             case self::SUCCESS:
00910                 # We've verified now, update the real record
00911                 $user = $this->getUser();
00912                 $user->invalidateCache();
00913 
00914                 if ( $user->requiresHTTPS() ) {
00915                     $this->mStickHTTPS = true;
00916                 }
00917 
00918                 if ( $wgSecureLogin && !$this->mStickHTTPS ) {
00919                     $user->setCookies( $this->mRequest, false, $this->mRemember );
00920                 } else {
00921                     $user->setCookies( $this->mRequest, null, $this->mRemember );
00922                 }
00923                 self::clearLoginToken();
00924 
00925                 // Reset the throttle
00926                 $request = $this->getRequest();
00927                 $key = wfMemcKey( 'password-throttle', $request->getIP(), md5( $this->mUsername ) );
00928                 $wgMemc->delete( $key );
00929 
00930                 if ( $this->hasSessionCookie() || $this->mSkipCookieCheck ) {
00931                     /* Replace the language object to provide user interface in
00932                      * correct language immediately on this first page load.
00933                      */
00934                     $code = $request->getVal( 'uselang', $user->getOption( 'language' ) );
00935                     $userLang = Language::factory( $code );
00936                     $wgLang = $userLang;
00937                     $this->getContext()->setLanguage( $userLang );
00938                     // Reset SessionID on Successful login (bug 40995)
00939                     $this->renewSessionId();
00940                     if ( $this->getUser()->getPasswordExpired() == 'soft' ) {
00941                         $this->resetLoginForm( $this->msg( 'resetpass-expired-soft' ) );
00942                     } elseif ( $wgInvalidPasswordReset
00943                         && !$user->isValidPassword( $this->mPassword )
00944                     ) {
00945                         $status = $user->checkPasswordValidity( $this->mPassword );
00946                         $this->resetLoginForm(
00947                             $status->getMessage( 'resetpass-validity-soft' )
00948                         );
00949                     } else {
00950                         $this->successfulLogin();
00951                     }
00952                 } else {
00953                     $this->cookieRedirectCheck( 'login' );
00954                 }
00955                 break;
00956 
00957             case self::NEED_TOKEN:
00958                 $error = $this->mAbortLoginErrorMsg ?: 'nocookiesforlogin';
00959                 $this->mainLoginForm( $this->msg( $error )->parse() );
00960                 break;
00961             case self::WRONG_TOKEN:
00962                 $error = $this->mAbortLoginErrorMsg ?: 'sessionfailure';
00963                 $this->mainLoginForm( $this->msg( $error )->text() );
00964                 break;
00965             case self::NO_NAME:
00966             case self::ILLEGAL:
00967                 $error = $this->mAbortLoginErrorMsg ?: 'noname';
00968                 $this->mainLoginForm( $this->msg( $error )->text() );
00969                 break;
00970             case self::WRONG_PLUGIN_PASS:
00971                 $error = $this->mAbortLoginErrorMsg ?: 'wrongpassword';
00972                 $this->mainLoginForm( $this->msg( $error )->text() );
00973                 break;
00974             case self::NOT_EXISTS:
00975                 if ( $this->getUser()->isAllowed( 'createaccount' ) ) {
00976                     $error = $this->mAbortLoginErrorMsg ?: 'nosuchuser';
00977                     $this->mainLoginForm( $this->msg( $error,
00978                         wfEscapeWikiText( $this->mUsername ) )->parse() );
00979                 } else {
00980                     $error = $this->mAbortLoginErrorMsg ?: 'nosuchusershort';
00981                     $this->mainLoginForm( $this->msg( $error,
00982                         wfEscapeWikiText( $this->mUsername ) )->text() );
00983                 }
00984                 break;
00985             case self::WRONG_PASS:
00986                 $error = $this->mAbortLoginErrorMsg ?: 'wrongpassword';
00987                 $this->mainLoginForm( $this->msg( $error )->text() );
00988                 break;
00989             case self::EMPTY_PASS:
00990                 $error = $this->mAbortLoginErrorMsg ?: 'wrongpasswordempty';
00991                 $this->mainLoginForm( $this->msg( $error )->text() );
00992                 break;
00993             case self::RESET_PASS:
00994                 $error = $this->mAbortLoginErrorMsg ?: 'resetpass_announce';
00995                 $this->resetLoginForm( $this->msg( $error ) );
00996                 break;
00997             case self::CREATE_BLOCKED:
00998                 $this->userBlockedMessage( $this->getUser()->isBlockedFromCreateAccount() );
00999                 break;
01000             case self::THROTTLED:
01001                 $error = $this->mAbortLoginErrorMsg ?: 'login-throttled';
01002                 $this->mainLoginForm( $this->msg( $error )
01003                     ->params( $this->getLanguage()->formatDuration( $wgPasswordAttemptThrottle['seconds'] ) )
01004                     ->text()
01005                 );
01006                 break;
01007             case self::USER_BLOCKED:
01008                 $error = $this->mAbortLoginErrorMsg ?: 'login-userblocked';
01009                 $this->mainLoginForm( $this->msg( $error, $this->mUsername )->escaped() );
01010                 break;
01011             case self::ABORTED:
01012                 $error = $this->mAbortLoginErrorMsg ?: 'login-abort-generic';
01013                 $this->mainLoginForm( $this->msg( $error,
01014                         wfEscapeWikiText( $this->mUsername ) )->text() );
01015                 break;
01016             case self::USER_MIGRATED:
01017                 $error = $this->mAbortLoginErrorMsg ?: 'login-migrated-generic';
01018                 $params = array();
01019                 if ( is_array( $error ) ) {
01020                     $error = array_shift( $this->mAbortLoginErrorMsg );
01021                     $params = $this->mAbortLoginErrorMsg;
01022                 }
01023                 $this->mainLoginForm( $this->msg( $error, $params )->text() );
01024                 break;
01025             default:
01026                 throw new MWException( 'Unhandled case value' );
01027         }
01028     }
01029 
01034     protected function resetLoginForm( Message $msg ) {
01035         // Allow hooks to explain this password reset in more detail
01036         wfRunHooks( 'LoginPasswordResetMessage', array( &$msg, $this->mUsername ) );
01037         $reset = new SpecialChangePassword();
01038         $derivative = new DerivativeContext( $this->getContext() );
01039         $derivative->setTitle( $reset->getPageTitle() );
01040         $reset->setContext( $derivative );
01041         if ( !$this->mTempPasswordUsed ) {
01042             $reset->setOldPasswordMessage( 'oldpassword' );
01043         }
01044         $reset->setChangeMessage( $msg );
01045         $reset->execute( null );
01046     }
01047 
01055     function mailPasswordInternal( $u, $throttle = true, $emailTitle = 'passwordremindertitle',
01056         $emailText = 'passwordremindertext'
01057     ) {
01058         global $wgNewPasswordExpiry;
01059 
01060         if ( $u->getEmail() == '' ) {
01061             return Status::newFatal( 'noemail', $u->getName() );
01062         }
01063         $ip = $this->getRequest()->getIP();
01064         if ( !$ip ) {
01065             return Status::newFatal( 'badipaddress' );
01066         }
01067 
01068         $currentUser = $this->getUser();
01069         wfRunHooks( 'User::mailPasswordInternal', array( &$currentUser, &$ip, &$u ) );
01070 
01071         $np = $u->randomPassword();
01072         $u->setNewpassword( $np, $throttle );
01073         $u->saveSettings();
01074         $userLanguage = $u->getOption( 'language' );
01075 
01076         $mainPage = Title::newMainPage();
01077         $mainPageUrl = $mainPage->getCanonicalURL();
01078 
01079         $m = $this->msg( $emailText, $ip, $u->getName(), $np, '<' . $mainPageUrl . '>',
01080             round( $wgNewPasswordExpiry / 86400 ) )->inLanguage( $userLanguage )->text();
01081         $result = $u->sendMail( $this->msg( $emailTitle )->inLanguage( $userLanguage )->text(), $m );
01082 
01083         return $result;
01084     }
01085 
01096     function successfulLogin() {
01097         # Run any hooks; display injected HTML if any, else redirect
01098         $currentUser = $this->getUser();
01099         $injected_html = '';
01100         wfRunHooks( 'UserLoginComplete', array( &$currentUser, &$injected_html ) );
01101 
01102         if ( $injected_html !== '' ) {
01103             $this->displaySuccessfulAction( 'success', $this->msg( 'loginsuccesstitle' ),
01104                 'loginsuccess', $injected_html );
01105         } else {
01106             $this->executeReturnTo( 'successredirect' );
01107         }
01108     }
01109 
01116     function successfulCreation() {
01117         # Run any hooks; display injected HTML
01118         $currentUser = $this->getUser();
01119         $injected_html = '';
01120         $welcome_creation_msg = 'welcomecreation-msg';
01121 
01122         wfRunHooks( 'UserLoginComplete', array( &$currentUser, &$injected_html ) );
01123 
01129         wfRunHooks( 'BeforeWelcomeCreation', array( &$welcome_creation_msg, &$injected_html ) );
01130 
01131         $this->displaySuccessfulAction(
01132             'signup',
01133             $this->msg( 'welcomeuser', $this->getUser()->getName() ),
01134             $welcome_creation_msg, $injected_html
01135         );
01136     }
01137 
01146     private function displaySuccessfulAction( $type, $title, $msgname, $injected_html ) {
01147         $out = $this->getOutput();
01148         $out->setPageTitle( $title );
01149         if ( $msgname ) {
01150             $out->addWikiMsg( $msgname, wfEscapeWikiText( $this->getUser()->getName() ) );
01151         }
01152 
01153         $out->addHTML( $injected_html );
01154 
01155         $this->executeReturnTo( $type );
01156     }
01157 
01166     function userBlockedMessage( Block $block ) {
01167         # Let's be nice about this, it's likely that this feature will be used
01168         # for blocking large numbers of innocent people, e.g. range blocks on
01169         # schools. Don't blame it on the user. There's a small chance that it
01170         # really is the user's fault, i.e. the username is blocked and they
01171         # haven't bothered to log out before trying to create an account to
01172         # evade it, but we'll leave that to their guilty conscience to figure
01173         # out.
01174         $errorParams = array(
01175             $block->getTarget(),
01176             $block->mReason ? $block->mReason : $this->msg( 'blockednoreason' )->text(),
01177             $block->getByName()
01178         );
01179 
01180         if ( $block->getType() === Block::TYPE_RANGE ) {
01181             $errorMessage = 'cantcreateaccount-range-text';
01182             $errorParams[] = $this->getRequest()->getIP();
01183         } else {
01184             $errorMessage = 'cantcreateaccount-text';
01185         }
01186 
01187         throw new ErrorPageError(
01188             'cantcreateaccounttitle',
01189             $errorMessage,
01190             $errorParams
01191         );
01192     }
01193 
01209     public function showReturnToPage(
01210         $type, $returnTo = '', $returnToQuery = '', $stickHTTPs = false
01211     ) {
01212         $this->mReturnTo = $returnTo;
01213         $this->mReturnToQuery = $returnToQuery;
01214         $this->mStickHTTPS = $stickHTTPs;
01215         $this->executeReturnTo( $type );
01216     }
01217 
01227     private function executeReturnTo( $type ) {
01228         global $wgRedirectOnLogin, $wgSecureLogin;
01229 
01230         if ( $type != 'error' && $wgRedirectOnLogin !== null ) {
01231             $returnTo = $wgRedirectOnLogin;
01232             $returnToQuery = array();
01233         } else {
01234             $returnTo = $this->mReturnTo;
01235             $returnToQuery = wfCgiToArray( $this->mReturnToQuery );
01236         }
01237 
01238         // Allow modification of redirect behavior
01239         wfRunHooks( 'PostLoginRedirect', array( &$returnTo, &$returnToQuery, &$type ) );
01240 
01241         $returnToTitle = Title::newFromText( $returnTo );
01242         if ( !$returnToTitle ) {
01243             $returnToTitle = Title::newMainPage();
01244         }
01245 
01246         if ( $wgSecureLogin && !$this->mStickHTTPS ) {
01247             $options = array( 'http' );
01248             $proto = PROTO_HTTP;
01249         } elseif ( $wgSecureLogin ) {
01250             $options = array( 'https' );
01251             $proto = PROTO_HTTPS;
01252         } else {
01253             $options = array();
01254             $proto = PROTO_RELATIVE;
01255         }
01256 
01257         if ( $type == 'successredirect' ) {
01258             $redirectUrl = $returnToTitle->getFullURL( $returnToQuery, false, $proto );
01259             $this->getOutput()->redirect( $redirectUrl );
01260         } else {
01261             $this->getOutput()->addReturnTo( $returnToTitle, $returnToQuery, null, $options );
01262         }
01263     }
01264 
01270     function mainLoginForm( $msg, $msgtype = 'error' ) {
01271         global $wgEnableEmail, $wgEnableUserEmail;
01272         global $wgHiddenPrefs, $wgLoginLanguageSelector;
01273         global $wgAuth, $wgEmailConfirmToEdit, $wgCookieExpiration;
01274         global $wgSecureLogin, $wgPasswordResetRoutes;
01275 
01276         $titleObj = $this->getPageTitle();
01277         $user = $this->getUser();
01278         $out = $this->getOutput();
01279 
01280         if ( $this->mType == 'signup' ) {
01281             // Block signup here if in readonly. Keeps user from
01282             // going through the process (filling out data, etc)
01283             // and being informed later.
01284             $permErrors = $titleObj->getUserPermissionsErrors( 'createaccount', $user, true );
01285             if ( count( $permErrors ) ) {
01286                 throw new PermissionsError( 'createaccount', $permErrors );
01287             } elseif ( $user->isBlockedFromCreateAccount() ) {
01288                 $this->userBlockedMessage( $user->isBlockedFromCreateAccount() );
01289 
01290                 return;
01291             } elseif ( wfReadOnly() ) {
01292                 throw new ReadOnlyError;
01293             }
01294         }
01295 
01296         // Pre-fill username (if not creating an account, bug 44775).
01297         if ( $this->mUsername == '' && $this->mType != 'signup' ) {
01298             if ( $user->isLoggedIn() ) {
01299                 $this->mUsername = $user->getName();
01300             } else {
01301                 $this->mUsername = $this->getRequest()->getCookie( 'UserName' );
01302             }
01303         }
01304 
01305         // Generic styles and scripts for both login and signup form
01306         $out->addModuleStyles( array(
01307             'mediawiki.ui',
01308             'mediawiki.ui.button',
01309             'mediawiki.ui.checkbox',
01310             'mediawiki.ui.input',
01311             'mediawiki.special.userlogin.common.styles'
01312         ) );
01313         $out->addModules( array(
01314             'mediawiki.special.userlogin.common.js'
01315         ) );
01316 
01317         if ( $this->mType == 'signup' ) {
01318             // XXX hack pending RL or JS parse() support for complex content messages
01319             // https://bugzilla.wikimedia.org/show_bug.cgi?id=25349
01320             $out->addJsConfigVars( 'wgCreateacctImgcaptchaHelp',
01321                 $this->msg( 'createacct-imgcaptcha-help' )->parse() );
01322 
01323             // Additional styles and scripts for signup form
01324             $out->addModules( array(
01325                 'mediawiki.special.userlogin.signup.js'
01326             ) );
01327             $out->addModuleStyles( array(
01328                 'mediawiki.special.userlogin.signup.styles'
01329             ) );
01330 
01331             $template = new UsercreateTemplate();
01332 
01333             // Must match number of benefits defined in messages
01334             $template->set( 'benefitCount', 3 );
01335 
01336             $q = 'action=submitlogin&type=signup';
01337             $linkq = 'type=login';
01338         } else {
01339             // Additional styles for login form
01340             $out->addModuleStyles( array(
01341                 'mediawiki.special.userlogin.login.styles'
01342             ) );
01343 
01344             $template = new UserloginTemplate();
01345 
01346             $q = 'action=submitlogin&type=login';
01347             $linkq = 'type=signup';
01348         }
01349 
01350         if ( $this->mReturnTo !== '' ) {
01351             $returnto = '&returnto=' . wfUrlencode( $this->mReturnTo );
01352             if ( $this->mReturnToQuery !== '' ) {
01353                 $returnto .= '&returntoquery=' .
01354                     wfUrlencode( $this->mReturnToQuery );
01355             }
01356             $q .= $returnto;
01357             $linkq .= $returnto;
01358         }
01359 
01360         # Don't show a "create account" link if the user can't.
01361         if ( $this->showCreateOrLoginLink( $user ) ) {
01362             # Pass any language selection on to the mode switch link
01363             if ( $wgLoginLanguageSelector && $this->mLanguage ) {
01364                 $linkq .= '&uselang=' . $this->mLanguage;
01365             }
01366             // Supply URL, login template creates the button.
01367             $template->set( 'createOrLoginHref', $titleObj->getLocalURL( $linkq ) );
01368         } else {
01369             $template->set( 'link', '' );
01370         }
01371 
01372         $resetLink = $this->mType == 'signup'
01373             ? null
01374             : is_array( $wgPasswordResetRoutes ) && in_array( true, array_values( $wgPasswordResetRoutes ) );
01375 
01376         $template->set( 'header', '' );
01377         $template->set( 'skin', $this->getSkin() );
01378         $template->set( 'name', $this->mUsername );
01379         $template->set( 'password', $this->mPassword );
01380         $template->set( 'retype', $this->mRetype );
01381         $template->set( 'createemailset', $this->mCreateaccountMail );
01382         $template->set( 'email', $this->mEmail );
01383         $template->set( 'realname', $this->mRealName );
01384         $template->set( 'domain', $this->mDomain );
01385         $template->set( 'reason', $this->mReason );
01386 
01387         $template->set( 'action', $titleObj->getLocalURL( $q ) );
01388         $template->set( 'message', $msg );
01389         $template->set( 'messagetype', $msgtype );
01390         $template->set( 'createemail', $wgEnableEmail && $user->isLoggedIn() );
01391         $template->set( 'userealname', !in_array( 'realname', $wgHiddenPrefs ) );
01392         $template->set( 'useemail', $wgEnableEmail );
01393         $template->set( 'emailrequired', $wgEmailConfirmToEdit );
01394         $template->set( 'emailothers', $wgEnableUserEmail );
01395         $template->set( 'canreset', $wgAuth->allowPasswordChange() );
01396         $template->set( 'resetlink', $resetLink );
01397         $template->set( 'canremember', ( $wgCookieExpiration > 0 ) );
01398         $template->set( 'usereason', $user->isLoggedIn() );
01399         $template->set( 'remember', $this->mRemember );
01400         $template->set( 'cansecurelogin', ( $wgSecureLogin === true ) );
01401         $template->set( 'stickhttps', (int)$this->mStickHTTPS );
01402         $template->set( 'loggedin', $user->isLoggedIn() );
01403         $template->set( 'loggedinuser', $user->getName() );
01404 
01405         if ( $this->mType == 'signup' ) {
01406             if ( !self::getCreateaccountToken() ) {
01407                 self::setCreateaccountToken();
01408             }
01409             $template->set( 'token', self::getCreateaccountToken() );
01410         } else {
01411             if ( !self::getLoginToken() ) {
01412                 self::setLoginToken();
01413             }
01414             $template->set( 'token', self::getLoginToken() );
01415         }
01416 
01417         # Prepare language selection links as needed
01418         if ( $wgLoginLanguageSelector ) {
01419             $template->set( 'languages', $this->makeLanguageSelector() );
01420             if ( $this->mLanguage ) {
01421                 $template->set( 'uselang', $this->mLanguage );
01422             }
01423         }
01424 
01425         $template->set( 'secureLoginUrl', $this->mSecureLoginUrl );
01426         // Use loginend-https for HTTPS requests if it's not blank, loginend otherwise
01427         // Ditto for signupend.  New forms use neither.
01428         $usingHTTPS = $this->mRequest->getProtocol() == 'https';
01429         $loginendHTTPS = $this->msg( 'loginend-https' );
01430         $signupendHTTPS = $this->msg( 'signupend-https' );
01431         if ( $usingHTTPS && !$loginendHTTPS->isBlank() ) {
01432             $template->set( 'loginend', $loginendHTTPS->parse() );
01433         } else {
01434             $template->set( 'loginend', $this->msg( 'loginend' )->parse() );
01435         }
01436         if ( $usingHTTPS && !$signupendHTTPS->isBlank() ) {
01437             $template->set( 'signupend', $signupendHTTPS->parse() );
01438         } else {
01439             $template->set( 'signupend', $this->msg( 'signupend' )->parse() );
01440         }
01441 
01442         // Give authentication and captcha plugins a chance to modify the form
01443         $wgAuth->modifyUITemplate( $template, $this->mType );
01444         if ( $this->mType == 'signup' ) {
01445             wfRunHooks( 'UserCreateForm', array( &$template ) );
01446         } else {
01447             wfRunHooks( 'UserLoginForm', array( &$template ) );
01448         }
01449 
01450         $out->disallowUserJs(); // just in case...
01451         $out->addTemplate( $template );
01452     }
01453 
01461     private function showCreateOrLoginLink( &$user ) {
01462         if ( $this->mType == 'signup' ) {
01463             return true;
01464         } elseif ( $user->isAllowed( 'createaccount' ) ) {
01465             return true;
01466         } else {
01467             return false;
01468         }
01469     }
01470 
01481     function hasSessionCookie() {
01482         global $wgDisableCookieCheck;
01483 
01484         return $wgDisableCookieCheck ? true : $this->getRequest()->checkSessionCookie();
01485     }
01486 
01491     public static function getLoginToken() {
01492         global $wgRequest;
01493 
01494         return $wgRequest->getSessionData( 'wsLoginToken' );
01495     }
01496 
01500     public static function setLoginToken() {
01501         global $wgRequest;
01502         // Generate a token directly instead of using $user->getEditToken()
01503         // because the latter reuses $_SESSION['wsEditToken']
01504         $wgRequest->setSessionData( 'wsLoginToken', MWCryptRand::generateHex( 32 ) );
01505     }
01506 
01510     public static function clearLoginToken() {
01511         global $wgRequest;
01512         $wgRequest->setSessionData( 'wsLoginToken', null );
01513     }
01514 
01519     public static function getCreateaccountToken() {
01520         global $wgRequest;
01521 
01522         return $wgRequest->getSessionData( 'wsCreateaccountToken' );
01523     }
01524 
01528     public static function setCreateaccountToken() {
01529         global $wgRequest;
01530         $wgRequest->setSessionData( 'wsCreateaccountToken', MWCryptRand::generateHex( 32 ) );
01531     }
01532 
01536     public static function clearCreateaccountToken() {
01537         global $wgRequest;
01538         $wgRequest->setSessionData( 'wsCreateaccountToken', null );
01539     }
01540 
01544     private function renewSessionId() {
01545         global $wgSecureLogin, $wgCookieSecure;
01546         if ( $wgSecureLogin && !$this->mStickHTTPS ) {
01547             $wgCookieSecure = false;
01548         }
01549 
01550         wfResetSessionID();
01551     }
01552 
01557     function cookieRedirectCheck( $type ) {
01558         $titleObj = SpecialPage::getTitleFor( 'Userlogin' );
01559         $query = array( 'wpCookieCheck' => $type );
01560         if ( $this->mReturnTo !== '' ) {
01561             $query['returnto'] = $this->mReturnTo;
01562             $query['returntoquery'] = $this->mReturnToQuery;
01563         }
01564         $check = $titleObj->getFullURL( $query );
01565 
01566         $this->getOutput()->redirect( $check );
01567     }
01568 
01573     function onCookieRedirectCheck( $type ) {
01574         if ( !$this->hasSessionCookie() ) {
01575             if ( $type == 'new' ) {
01576                 $this->mainLoginForm( $this->msg( 'nocookiesnew' )->parse() );
01577             } elseif ( $type == 'login' ) {
01578                 $this->mainLoginForm( $this->msg( 'nocookieslogin' )->parse() );
01579             } else {
01580                 # shouldn't happen
01581                 $this->mainLoginForm( $this->msg( 'error' )->text() );
01582             }
01583         } else {
01584             $this->successfulLogin();
01585         }
01586     }
01587 
01594     function makeLanguageSelector() {
01595         $msg = $this->msg( 'loginlanguagelinks' )->inContentLanguage();
01596         if ( !$msg->isBlank() ) {
01597             $langs = explode( "\n", $msg->text() );
01598             $links = array();
01599             foreach ( $langs as $lang ) {
01600                 $lang = trim( $lang, '* ' );
01601                 $parts = explode( '|', $lang );
01602                 if ( count( $parts ) >= 2 ) {
01603                     $links[] = $this->makeLanguageSelectorLink( $parts[0], trim( $parts[1] ) );
01604                 }
01605             }
01606 
01607             return count( $links ) > 0 ? $this->msg( 'loginlanguagelabel' )->rawParams(
01608                 $this->getLanguage()->pipeList( $links ) )->escaped() : '';
01609         } else {
01610             return '';
01611         }
01612     }
01613 
01622     function makeLanguageSelectorLink( $text, $lang ) {
01623         if ( $this->getLanguage()->getCode() == $lang ) {
01624             // no link for currently used language
01625             return htmlspecialchars( $text );
01626         }
01627         $query = array( 'uselang' => $lang );
01628         if ( $this->mType == 'signup' ) {
01629             $query['type'] = 'signup';
01630         }
01631         if ( $this->mReturnTo !== '' ) {
01632             $query['returnto'] = $this->mReturnTo;
01633             $query['returntoquery'] = $this->mReturnToQuery;
01634         }
01635 
01636         $attr = array();
01637         $targetLanguage = Language::factory( $lang );
01638         $attr['lang'] = $attr['hreflang'] = $targetLanguage->getHtmlCode();
01639 
01640         return Linker::linkKnown(
01641             $this->getPageTitle(),
01642             htmlspecialchars( $text ),
01643             $attr,
01644             $query
01645         );
01646     }
01647 
01648     protected function getGroupName() {
01649         return 'login';
01650     }
01651 }