MediaWiki
REL1_20
|
00001 <?php 00029 class LoginForm extends SpecialPage { 00030 00031 const SUCCESS = 0; 00032 const NO_NAME = 1; 00033 const ILLEGAL = 2; 00034 const WRONG_PLUGIN_PASS = 3; 00035 const NOT_EXISTS = 4; 00036 const WRONG_PASS = 5; 00037 const EMPTY_PASS = 6; 00038 const RESET_PASS = 7; 00039 const ABORTED = 8; 00040 const CREATE_BLOCKED = 9; 00041 const THROTTLED = 10; 00042 const USER_BLOCKED = 11; 00043 const NEED_TOKEN = 12; 00044 const WRONG_TOKEN = 13; 00045 00046 var $mUsername, $mPassword, $mRetype, $mReturnTo, $mCookieCheck, $mPosted; 00047 var $mAction, $mCreateaccount, $mCreateaccountMail; 00048 var $mLoginattempt, $mRemember, $mEmail, $mDomain, $mLanguage; 00049 var $mSkipCookieCheck, $mReturnToQuery, $mToken, $mStickHTTPS; 00050 var $mType, $mReason, $mRealName; 00051 var $mAbortLoginErrorMsg = 'login-abort-generic'; 00052 private $mLoaded = false; 00053 00057 private $mExtUser = null; 00058 00062 private $mOverrideRequest = null; 00063 00067 public function __construct( $request = null ) { 00068 parent::__construct( 'Userlogin' ); 00069 00070 $this->mOverrideRequest = $request; 00071 } 00072 00076 function load() { 00077 global $wgAuth, $wgHiddenPrefs, $wgEnableEmail; 00078 00079 if ( $this->mLoaded ) { 00080 return; 00081 } 00082 $this->mLoaded = true; 00083 00084 if ( $this->mOverrideRequest === null ) { 00085 $request = $this->getRequest(); 00086 } else { 00087 $request = $this->mOverrideRequest; 00088 } 00089 00090 $this->mType = $request->getText( 'type' ); 00091 $this->mUsername = $request->getText( 'wpName' ); 00092 $this->mPassword = $request->getText( 'wpPassword' ); 00093 $this->mRetype = $request->getText( 'wpRetype' ); 00094 $this->mDomain = $request->getText( 'wpDomain' ); 00095 $this->mReason = $request->getText( 'wpReason' ); 00096 $this->mCookieCheck = $request->getVal( 'wpCookieCheck' ); 00097 $this->mPosted = $request->wasPosted(); 00098 $this->mCreateaccount = $request->getCheck( 'wpCreateaccount' ); 00099 $this->mCreateaccountMail = $request->getCheck( 'wpCreateaccountMail' ) 00100 && $wgEnableEmail; 00101 $this->mLoginattempt = $request->getCheck( 'wpLoginattempt' ); 00102 $this->mAction = $request->getVal( 'action' ); 00103 $this->mRemember = $request->getCheck( 'wpRemember' ); 00104 $this->mStickHTTPS = $request->getCheck( 'wpStickHTTPS' ); 00105 $this->mLanguage = $request->getText( 'uselang' ); 00106 $this->mSkipCookieCheck = $request->getCheck( 'wpSkipCookieCheck' ); 00107 $this->mToken = ( $this->mType == 'signup' ) ? $request->getVal( 'wpCreateaccountToken' ) : $request->getVal( 'wpLoginToken' ); 00108 $this->mReturnTo = $request->getVal( 'returnto', '' ); 00109 $this->mReturnToQuery = $request->getVal( 'returntoquery', '' ); 00110 00111 if( $wgEnableEmail ) { 00112 $this->mEmail = $request->getText( 'wpEmail' ); 00113 } else { 00114 $this->mEmail = ''; 00115 } 00116 if( !in_array( 'realname', $wgHiddenPrefs ) ) { 00117 $this->mRealName = $request->getText( 'wpRealName' ); 00118 } else { 00119 $this->mRealName = ''; 00120 } 00121 00122 if( !$wgAuth->validDomain( $this->mDomain ) ) { 00123 $this->mDomain = $wgAuth->getDomain(); 00124 } 00125 $wgAuth->setDomain( $this->mDomain ); 00126 00127 # 1. When switching accounts, it sucks to get automatically logged out 00128 # 2. Do not return to PasswordReset after a successful password change 00129 # but goto Wiki start page (Main_Page) instead ( bug 33997 ) 00130 $returnToTitle = Title::newFromText( $this->mReturnTo ); 00131 if( is_object( $returnToTitle ) && ( 00132 $returnToTitle->isSpecial( 'Userlogout' ) 00133 || $returnToTitle->isSpecial( 'PasswordReset' ) ) ) { 00134 $this->mReturnTo = ''; 00135 $this->mReturnToQuery = ''; 00136 } 00137 } 00138 00139 function getDescription() { 00140 return $this->msg( $this->getUser()->isAllowed( 'createaccount' ) ? 00141 'userlogin' : 'userloginnocreate' )->text(); 00142 } 00143 00144 public function execute( $par ) { 00145 if ( session_id() == '' ) { 00146 wfSetupSession(); 00147 } 00148 00149 $this->load(); 00150 $this->setHeaders(); 00151 00152 if ( $par == 'signup' ) { # Check for [[Special:Userlogin/signup]] 00153 $this->mType = 'signup'; 00154 } 00155 00156 if ( !is_null( $this->mCookieCheck ) ) { 00157 $this->onCookieRedirectCheck( $this->mCookieCheck ); 00158 return; 00159 } elseif( $this->mPosted ) { 00160 if( $this->mCreateaccount ) { 00161 $this->addNewAccount(); 00162 return; 00163 } elseif ( $this->mCreateaccountMail ) { 00164 $this->addNewAccountMailPassword(); 00165 return; 00166 } elseif ( ( 'submitlogin' == $this->mAction ) || $this->mLoginattempt ) { 00167 $this->processLogin(); 00168 return; 00169 } 00170 } 00171 $this->mainLoginForm( '' ); 00172 } 00173 00177 function addNewAccountMailPassword() { 00178 if ( $this->mEmail == '' ) { 00179 $this->mainLoginForm( $this->msg( 'noemailcreate' )->escaped() ); 00180 return; 00181 } 00182 00183 $u = $this->addNewaccountInternal(); 00184 00185 if ( $u == null ) { 00186 return; 00187 } 00188 00189 // Wipe the initial password and mail a temporary one 00190 $u->setPassword( null ); 00191 $u->saveSettings(); 00192 $result = $this->mailPasswordInternal( $u, false, 'createaccount-title', 'createaccount-text' ); 00193 00194 wfRunHooks( 'AddNewAccount', array( $u, true ) ); 00195 $u->addNewUserLogEntry( true, $this->mReason ); 00196 00197 $out = $this->getOutput(); 00198 $out->setPageTitle( $this->msg( 'accmailtitle' ) ); 00199 00200 if( !$result->isGood() ) { 00201 $this->mainLoginForm( $this->msg( 'mailerror', $result->getWikiText() )->text() ); 00202 } else { 00203 $out->addWikiMsg( 'accmailtext', $u->getName(), $u->getEmail() ); 00204 $this->executeReturnTo( 'success' ); 00205 } 00206 } 00207 00212 function addNewAccount() { 00213 global $wgUser, $wgEmailAuthentication, $wgLoginLanguageSelector; 00214 00215 # Create the account and abort if there's a problem doing so 00216 $u = $this->addNewAccountInternal(); 00217 if( $u == null ) { 00218 return false; 00219 } 00220 00221 # If we showed up language selection links, and one was in use, be 00222 # smart (and sensible) and save that language as the user's preference 00223 if( $wgLoginLanguageSelector && $this->mLanguage ) { 00224 $u->setOption( 'language', $this->mLanguage ); 00225 } 00226 00227 $out = $this->getOutput(); 00228 00229 # Send out an email authentication message if needed 00230 if( $wgEmailAuthentication && Sanitizer::validateEmail( $u->getEmail() ) ) { 00231 $status = $u->sendConfirmationMail(); 00232 if( $status->isGood() ) { 00233 $out->addWikiMsg( 'confirmemail_oncreate' ); 00234 } else { 00235 $out->addWikiText( $status->getWikiText( 'confirmemail_sendfailed' ) ); 00236 } 00237 } 00238 00239 # Save settings (including confirmation token) 00240 $u->saveSettings(); 00241 00242 # If not logged in, assume the new account as the current one and set 00243 # session cookies then show a "welcome" message or a "need cookies" 00244 # message as needed 00245 if( $this->getUser()->isAnon() ) { 00246 $u->setCookies(); 00247 $wgUser = $u; 00248 // This should set it for OutputPage and the Skin 00249 // which is needed or the personal links will be 00250 // wrong. 00251 $this->getContext()->setUser( $u ); 00252 wfRunHooks( 'AddNewAccount', array( $u, false ) ); 00253 $u->addNewUserLogEntry(); 00254 if( $this->hasSessionCookie() ) { 00255 $this->successfulCreation(); 00256 } else { 00257 $this->cookieRedirectCheck( 'new' ); 00258 } 00259 } else { 00260 # Confirm that the account was created 00261 $out->setPageTitle( $this->msg( 'accountcreated' ) ); 00262 $out->addWikiMsg( 'accountcreatedtext', $u->getName() ); 00263 $out->addReturnTo( $this->getTitle() ); 00264 wfRunHooks( 'AddNewAccount', array( $u, false ) ); 00265 $u->addNewUserLogEntry( false, $this->mReason ); 00266 } 00267 return true; 00268 } 00269 00274 function addNewAccountInternal() { 00275 global $wgAuth, $wgMemc, $wgAccountCreationThrottle, 00276 $wgMinimalPasswordLength, $wgEmailConfirmToEdit; 00277 00278 // If the user passes an invalid domain, something is fishy 00279 if( !$wgAuth->validDomain( $this->mDomain ) ) { 00280 $this->mainLoginForm( $this->msg( 'wrongpassword' )->text() ); 00281 return false; 00282 } 00283 00284 // If we are not allowing users to login locally, we should be checking 00285 // to see if the user is actually able to authenticate to the authenti- 00286 // cation server before they create an account (otherwise, they can 00287 // create a local account and login as any domain user). We only need 00288 // to check this for domains that aren't local. 00289 if( 'local' != $this->mDomain && $this->mDomain != '' ) { 00290 if( !$wgAuth->canCreateAccounts() && ( !$wgAuth->userExists( $this->mUsername ) 00291 || !$wgAuth->authenticate( $this->mUsername, $this->mPassword ) ) ) { 00292 $this->mainLoginForm( $this->msg( 'wrongpassword' )->text() ); 00293 return false; 00294 } 00295 } 00296 00297 if ( wfReadOnly() ) { 00298 throw new ReadOnlyError; 00299 } 00300 00301 # Request forgery checks. 00302 if ( !self::getCreateaccountToken() ) { 00303 self::setCreateaccountToken(); 00304 $this->mainLoginForm( $this->msg( 'nocookiesfornew' )->parse() ); 00305 return false; 00306 } 00307 00308 # The user didn't pass a createaccount token 00309 if ( !$this->mToken ) { 00310 $this->mainLoginForm( $this->msg( 'sessionfailure' )->text() ); 00311 return false; 00312 } 00313 00314 # Validate the createaccount token 00315 if ( $this->mToken !== self::getCreateaccountToken() ) { 00316 $this->mainLoginForm( $this->msg( 'sessionfailure' )->text() ); 00317 return false; 00318 } 00319 00320 # Check permissions 00321 $currentUser = $this->getUser(); 00322 if ( !$currentUser->isAllowed( 'createaccount' ) ) { 00323 throw new PermissionsError( 'createaccount' ); 00324 } elseif ( $currentUser->isBlockedFromCreateAccount() ) { 00325 $this->userBlockedMessage( $currentUser->isBlockedFromCreateAccount() ); 00326 return false; 00327 } 00328 00329 # Include checks that will include GlobalBlocking (Bug 38333) 00330 $permErrors = $this->getTitle()->getUserPermissionsErrors( 'createaccount', $currentUser, true ); 00331 if ( count( $permErrors ) ) { 00332 throw new PermissionsError( 'createaccount', $permErrors ); 00333 } 00334 00335 $ip = $this->getRequest()->getIP(); 00336 if ( $currentUser->isDnsBlacklisted( $ip, true /* check $wgProxyWhitelist */ ) ) { 00337 $this->mainLoginForm( $this->msg( 'sorbs_create_account_reason' )->text() . ' ' . $this->msg( 'parentheses', $ip )->escaped() ); 00338 return false; 00339 } 00340 00341 # Now create a dummy user ($u) and check if it is valid 00342 $name = trim( $this->mUsername ); 00343 $u = User::newFromName( $name, 'creatable' ); 00344 if ( !is_object( $u ) ) { 00345 $this->mainLoginForm( $this->msg( 'noname' )->text() ); 00346 return false; 00347 } 00348 00349 if ( 0 != $u->idForName() ) { 00350 $this->mainLoginForm( $this->msg( 'userexists' )->text() ); 00351 return false; 00352 } 00353 00354 if ( 0 != strcmp( $this->mPassword, $this->mRetype ) ) { 00355 $this->mainLoginForm( $this->msg( 'badretype' )->text() ); 00356 return false; 00357 } 00358 00359 # check for minimal password length 00360 $valid = $u->getPasswordValidity( $this->mPassword ); 00361 if ( $valid !== true ) { 00362 if ( !$this->mCreateaccountMail ) { 00363 if ( is_array( $valid ) ) { 00364 $message = array_shift( $valid ); 00365 $params = $valid; 00366 } else { 00367 $message = $valid; 00368 $params = array( $wgMinimalPasswordLength ); 00369 } 00370 $this->mainLoginForm( $this->msg( $message, $params )->text() ); 00371 return false; 00372 } else { 00373 # do not force a password for account creation by email 00374 # set invalid password, it will be replaced later by a random generated password 00375 $this->mPassword = null; 00376 } 00377 } 00378 00379 # if you need a confirmed email address to edit, then obviously you 00380 # need an email address. 00381 if ( $wgEmailConfirmToEdit && empty( $this->mEmail ) ) { 00382 $this->mainLoginForm( $this->msg( 'noemailtitle' )->text() ); 00383 return false; 00384 } 00385 00386 if( !empty( $this->mEmail ) && !Sanitizer::validateEmail( $this->mEmail ) ) { 00387 $this->mainLoginForm( $this->msg( 'invalidemailaddress' )->text() ); 00388 return false; 00389 } 00390 00391 # Set some additional data so the AbortNewAccount hook can be used for 00392 # more than just username validation 00393 $u->setEmail( $this->mEmail ); 00394 $u->setRealName( $this->mRealName ); 00395 00396 $abortError = ''; 00397 if( !wfRunHooks( 'AbortNewAccount', array( $u, &$abortError ) ) ) { 00398 // Hook point to add extra creation throttles and blocks 00399 wfDebug( "LoginForm::addNewAccountInternal: a hook blocked creation\n" ); 00400 $this->mainLoginForm( $abortError ); 00401 return false; 00402 } 00403 00404 // Hook point to check for exempt from account creation throttle 00405 if ( !wfRunHooks( 'ExemptFromAccountCreationThrottle', array( $ip ) ) ) { 00406 wfDebug( "LoginForm::exemptFromAccountCreationThrottle: a hook allowed account creation w/o throttle\n" ); 00407 } else { 00408 if ( ( $wgAccountCreationThrottle && $currentUser->isPingLimitable() ) ) { 00409 $key = wfMemcKey( 'acctcreate', 'ip', $ip ); 00410 $value = $wgMemc->get( $key ); 00411 if ( !$value ) { 00412 $wgMemc->set( $key, 0, 86400 ); 00413 } 00414 if ( $value >= $wgAccountCreationThrottle ) { 00415 $this->throttleHit( $wgAccountCreationThrottle ); 00416 return false; 00417 } 00418 $wgMemc->incr( $key ); 00419 } 00420 } 00421 00422 if( !$wgAuth->addUser( $u, $this->mPassword, $this->mEmail, $this->mRealName ) ) { 00423 $this->mainLoginForm( $this->msg( 'externaldberror' )->text() ); 00424 return false; 00425 } 00426 00427 self::clearCreateaccountToken(); 00428 return $this->initUser( $u, false ); 00429 } 00430 00440 function initUser( $u, $autocreate ) { 00441 global $wgAuth; 00442 00443 $u->addToDatabase(); 00444 00445 if ( $wgAuth->allowPasswordChange() ) { 00446 $u->setPassword( $this->mPassword ); 00447 } 00448 00449 $u->setEmail( $this->mEmail ); 00450 $u->setRealName( $this->mRealName ); 00451 $u->setToken(); 00452 00453 $wgAuth->initUser( $u, $autocreate ); 00454 00455 if ( $this->mExtUser ) { 00456 $this->mExtUser->linkToLocal( $u->getId() ); 00457 $email = $this->mExtUser->getPref( 'emailaddress' ); 00458 if ( $email && !$this->mEmail ) { 00459 $u->setEmail( $email ); 00460 } 00461 } 00462 00463 $u->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 ); 00464 $u->saveSettings(); 00465 00466 # Update user count 00467 $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 ); 00468 $ssUpdate->doUpdate(); 00469 00470 return $u; 00471 } 00472 00481 public function authenticateUserData() { 00482 global $wgUser, $wgAuth; 00483 00484 $this->load(); 00485 00486 if ( $this->mUsername == '' ) { 00487 return self::NO_NAME; 00488 } 00489 00490 // We require a login token to prevent login CSRF 00491 // Handle part of this before incrementing the throttle so 00492 // token-less login attempts don't count towards the throttle 00493 // but wrong-token attempts do. 00494 00495 // If the user doesn't have a login token yet, set one. 00496 if ( !self::getLoginToken() ) { 00497 self::setLoginToken(); 00498 return self::NEED_TOKEN; 00499 } 00500 // If the user didn't pass a login token, tell them we need one 00501 if ( !$this->mToken ) { 00502 return self::NEED_TOKEN; 00503 } 00504 00505 $throttleCount = self::incLoginThrottle( $this->mUsername ); 00506 if ( $throttleCount === true ) { 00507 return self::THROTTLED; 00508 } 00509 00510 // Validate the login token 00511 if ( $this->mToken !== self::getLoginToken() ) { 00512 return self::WRONG_TOKEN; 00513 } 00514 00515 // Load the current user now, and check to see if we're logging in as 00516 // the same name. This is necessary because loading the current user 00517 // (say by calling getName()) calls the UserLoadFromSession hook, which 00518 // potentially creates the user in the database. Until we load $wgUser, 00519 // checking for user existence using User::newFromName($name)->getId() below 00520 // will effectively be using stale data. 00521 if ( $this->getUser()->getName() === $this->mUsername ) { 00522 wfDebug( __METHOD__ . ": already logged in as {$this->mUsername}\n" ); 00523 return self::SUCCESS; 00524 } 00525 00526 $this->mExtUser = ExternalUser::newFromName( $this->mUsername ); 00527 00528 # TODO: Allow some magic here for invalid external names, e.g., let the 00529 # user choose a different wiki name. 00530 $u = User::newFromName( $this->mUsername ); 00531 if( !( $u instanceof User ) || !User::isUsableName( $u->getName() ) ) { 00532 return self::ILLEGAL; 00533 } 00534 00535 $isAutoCreated = false; 00536 if ( 0 == $u->getID() ) { 00537 $status = $this->attemptAutoCreate( $u ); 00538 if ( $status !== self::SUCCESS ) { 00539 return $status; 00540 } else { 00541 $isAutoCreated = true; 00542 } 00543 } else { 00544 global $wgExternalAuthType, $wgAutocreatePolicy; 00545 if ( $wgExternalAuthType && $wgAutocreatePolicy != 'never' 00546 && is_object( $this->mExtUser ) 00547 && $this->mExtUser->authenticate( $this->mPassword ) ) { 00548 # The external user and local user have the same name and 00549 # password, so we assume they're the same. 00550 $this->mExtUser->linkToLocal( $u->getID() ); 00551 } 00552 00553 $u->load(); 00554 } 00555 00556 // Give general extensions, such as a captcha, a chance to abort logins 00557 $abort = self::ABORTED; 00558 if( !wfRunHooks( 'AbortLogin', array( $u, $this->mPassword, &$abort, &$this->mAbortLoginErrorMsg ) ) ) { 00559 return $abort; 00560 } 00561 00562 global $wgBlockDisablesLogin; 00563 if ( !$u->checkPassword( $this->mPassword ) ) { 00564 if( $u->checkTemporaryPassword( $this->mPassword ) ) { 00565 // The e-mailed temporary password should not be used for actu- 00566 // al logins; that's a very sloppy habit, and insecure if an 00567 // attacker has a few seconds to click "search" on someone's o- 00568 // pen mail reader. 00569 // 00570 // Allow it to be used only to reset the password a single time 00571 // to a new value, which won't be in the user's e-mail ar- 00572 // chives. 00573 // 00574 // For backwards compatibility, we'll still recognize it at the 00575 // login form to minimize surprises for people who have been 00576 // logging in with a temporary password for some time. 00577 // 00578 // As a side-effect, we can authenticate the user's e-mail ad- 00579 // dress if it's not already done, since the temporary password 00580 // was sent via e-mail. 00581 if( !$u->isEmailConfirmed() ) { 00582 $u->confirmEmail(); 00583 $u->saveSettings(); 00584 } 00585 00586 // At this point we just return an appropriate code/ indicating 00587 // that the UI should show a password reset form; bot inter- 00588 // faces etc will probably just fail cleanly here. 00589 $retval = self::RESET_PASS; 00590 } else { 00591 $retval = ( $this->mPassword == '' ) ? self::EMPTY_PASS : self::WRONG_PASS; 00592 } 00593 } elseif ( $wgBlockDisablesLogin && $u->isBlocked() ) { 00594 // If we've enabled it, make it so that a blocked user cannot login 00595 $retval = self::USER_BLOCKED; 00596 } else { 00597 $wgAuth->updateUser( $u ); 00598 $wgUser = $u; 00599 // This should set it for OutputPage and the Skin 00600 // which is needed or the personal links will be 00601 // wrong. 00602 $this->getContext()->setUser( $u ); 00603 00604 // Please reset throttle for successful logins, thanks! 00605 if ( $throttleCount ) { 00606 self::clearLoginThrottle( $this->mUsername ); 00607 } 00608 00609 if ( $isAutoCreated ) { 00610 // Must be run after $wgUser is set, for correct new user log 00611 wfRunHooks( 'AuthPluginAutoCreate', array( $u ) ); 00612 } 00613 00614 $retval = self::SUCCESS; 00615 } 00616 wfRunHooks( 'LoginAuthenticateAudit', array( $u, $this->mPassword, $retval ) ); 00617 return $retval; 00618 } 00619 00626 public static function incLoginThrottle( $username ) { 00627 global $wgPasswordAttemptThrottle, $wgMemc, $wgRequest; 00628 $username = trim( $username ); // sanity 00629 00630 $throttleCount = 0; 00631 if ( is_array( $wgPasswordAttemptThrottle ) ) { 00632 $throttleKey = wfMemcKey( 'password-throttle', $wgRequest->getIP(), md5( $username ) ); 00633 $count = $wgPasswordAttemptThrottle['count']; 00634 $period = $wgPasswordAttemptThrottle['seconds']; 00635 00636 $throttleCount = $wgMemc->get( $throttleKey ); 00637 if ( !$throttleCount ) { 00638 $wgMemc->add( $throttleKey, 1, $period ); // start counter 00639 } elseif ( $throttleCount < $count ) { 00640 $wgMemc->incr( $throttleKey ); 00641 } elseif ( $throttleCount >= $count ) { 00642 return true; 00643 } 00644 } 00645 00646 return $throttleCount; 00647 } 00648 00654 public static function clearLoginThrottle( $username ) { 00655 global $wgMemc, $wgRequest; 00656 $username = trim( $username ); // sanity 00657 00658 $throttleKey = wfMemcKey( 'password-throttle', $wgRequest->getIP(), md5( $username ) ); 00659 $wgMemc->delete( $throttleKey ); 00660 } 00661 00670 function attemptAutoCreate( $user ) { 00671 global $wgAuth, $wgAutocreatePolicy; 00672 00673 if ( $this->getUser()->isBlockedFromCreateAccount() ) { 00674 wfDebug( __METHOD__ . ": user is blocked from account creation\n" ); 00675 return self::CREATE_BLOCKED; 00676 } 00677 00683 if ( $this->mExtUser ) { 00684 # mExtUser is neither null nor false, so use the new ExternalAuth 00685 # system. 00686 if ( $wgAutocreatePolicy == 'never' ) { 00687 return self::NOT_EXISTS; 00688 } 00689 if ( !$this->mExtUser->authenticate( $this->mPassword ) ) { 00690 return self::WRONG_PLUGIN_PASS; 00691 } 00692 } else { 00693 # Old AuthPlugin. 00694 if ( !$wgAuth->autoCreate() ) { 00695 return self::NOT_EXISTS; 00696 } 00697 if ( !$wgAuth->userExists( $user->getName() ) ) { 00698 wfDebug( __METHOD__ . ": user does not exist\n" ); 00699 return self::NOT_EXISTS; 00700 } 00701 if ( !$wgAuth->authenticate( $user->getName(), $this->mPassword ) ) { 00702 wfDebug( __METHOD__ . ": \$wgAuth->authenticate() returned false, aborting\n" ); 00703 return self::WRONG_PLUGIN_PASS; 00704 } 00705 } 00706 00707 $abortError = ''; 00708 if( !wfRunHooks( 'AbortAutoAccount', array( $user, &$abortError ) ) ) { 00709 // Hook point to add extra creation throttles and blocks 00710 wfDebug( "LoginForm::attemptAutoCreate: a hook blocked creation: $abortError\n" ); 00711 $this->mAbortLoginErrorMsg = $abortError; 00712 return self::ABORTED; 00713 } 00714 00715 wfDebug( __METHOD__ . ": creating account\n" ); 00716 $this->initUser( $user, true ); 00717 return self::SUCCESS; 00718 } 00719 00720 function processLogin() { 00721 global $wgMemc, $wgLang; 00722 00723 switch ( $this->authenticateUserData() ) { 00724 case self::SUCCESS: 00725 # We've verified now, update the real record 00726 $user = $this->getUser(); 00727 if( (bool)$this->mRemember != (bool)$user->getOption( 'rememberpassword' ) ) { 00728 $user->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 ); 00729 $user->saveSettings(); 00730 } else { 00731 $user->invalidateCache(); 00732 } 00733 $user->setCookies(); 00734 self::clearLoginToken(); 00735 00736 // Reset the throttle 00737 $request = $this->getRequest(); 00738 $key = wfMemcKey( 'password-throttle', $request->getIP(), md5( $this->mUsername ) ); 00739 $wgMemc->delete( $key ); 00740 00741 if( $this->hasSessionCookie() || $this->mSkipCookieCheck ) { 00742 /* Replace the language object to provide user interface in 00743 * correct language immediately on this first page load. 00744 */ 00745 $code = $request->getVal( 'uselang', $user->getOption( 'language' ) ); 00746 $userLang = Language::factory( $code ); 00747 $wgLang = $userLang; 00748 $this->getContext()->setLanguage( $userLang ); 00749 // Reset SessionID on Successful login (bug 40995) 00750 $this->renewSessionId(); 00751 $this->successfulLogin(); 00752 } else { 00753 $this->cookieRedirectCheck( 'login' ); 00754 } 00755 break; 00756 00757 case self::NEED_TOKEN: 00758 $this->mainLoginForm( $this->msg( 'nocookiesforlogin' )->parse() ); 00759 break; 00760 case self::WRONG_TOKEN: 00761 $this->mainLoginForm( $this->msg( 'sessionfailure' )->text() ); 00762 break; 00763 case self::NO_NAME: 00764 case self::ILLEGAL: 00765 $this->mainLoginForm( $this->msg( 'noname' )->text() ); 00766 break; 00767 case self::WRONG_PLUGIN_PASS: 00768 $this->mainLoginForm( $this->msg( 'wrongpassword' )->text() ); 00769 break; 00770 case self::NOT_EXISTS: 00771 if( $this->getUser()->isAllowed( 'createaccount' ) ) { 00772 $this->mainLoginForm( $this->msg( 'nosuchuser', 00773 wfEscapeWikiText( $this->mUsername ) )->parse() ); 00774 } else { 00775 $this->mainLoginForm( $this->msg( 'nosuchusershort', 00776 wfEscapeWikiText( $this->mUsername ) )->text() ); 00777 } 00778 break; 00779 case self::WRONG_PASS: 00780 $this->mainLoginForm( $this->msg( 'wrongpassword' )->text() ); 00781 break; 00782 case self::EMPTY_PASS: 00783 $this->mainLoginForm( $this->msg( 'wrongpasswordempty' )->text() ); 00784 break; 00785 case self::RESET_PASS: 00786 $this->resetLoginForm( $this->msg( 'resetpass_announce' )->text() ); 00787 break; 00788 case self::CREATE_BLOCKED: 00789 $this->userBlockedMessage( $this->getUser()->mBlock ); 00790 break; 00791 case self::THROTTLED: 00792 $this->mainLoginForm( $this->msg( 'login-throttled' )->text() ); 00793 break; 00794 case self::USER_BLOCKED: 00795 $this->mainLoginForm( $this->msg( 'login-userblocked', 00796 $this->mUsername )->escaped() ); 00797 break; 00798 case self::ABORTED: 00799 $this->mainLoginForm( $this->msg( $this->mAbortLoginErrorMsg )->text() ); 00800 break; 00801 default: 00802 throw new MWException( 'Unhandled case value' ); 00803 } 00804 } 00805 00806 function resetLoginForm( $error ) { 00807 $this->getOutput()->addHTML( Xml::element('p', array( 'class' => 'error' ), $error ) ); 00808 $reset = new SpecialChangePassword(); 00809 $reset->setContext( $this->getContext() ); 00810 $reset->execute( null ); 00811 } 00812 00820 function mailPasswordInternal( $u, $throttle = true, $emailTitle = 'passwordremindertitle', $emailText = 'passwordremindertext' ) { 00821 global $wgServer, $wgScript, $wgNewPasswordExpiry; 00822 00823 if ( $u->getEmail() == '' ) { 00824 return Status::newFatal( 'noemail', $u->getName() ); 00825 } 00826 $ip = $this->getRequest()->getIP(); 00827 if( !$ip ) { 00828 return Status::newFatal( 'badipaddress' ); 00829 } 00830 00831 $currentUser = $this->getUser(); 00832 wfRunHooks( 'User::mailPasswordInternal', array( &$currentUser, &$ip, &$u ) ); 00833 00834 $np = $u->randomPassword(); 00835 $u->setNewpassword( $np, $throttle ); 00836 $u->saveSettings(); 00837 $userLanguage = $u->getOption( 'language' ); 00838 $m = $this->msg( $emailText, $ip, $u->getName(), $np, $wgServer . $wgScript, 00839 round( $wgNewPasswordExpiry / 86400 ) )->inLanguage( $userLanguage )->text(); 00840 $result = $u->sendMail( $this->msg( $emailTitle )->inLanguage( $userLanguage )->text(), $m ); 00841 00842 return $result; 00843 } 00844 00845 00856 function successfulLogin() { 00857 # Run any hooks; display injected HTML if any, else redirect 00858 $currentUser = $this->getUser(); 00859 $injected_html = ''; 00860 wfRunHooks( 'UserLoginComplete', array( &$currentUser, &$injected_html ) ); 00861 00862 if( $injected_html !== '' ) { 00863 $this->displaySuccessfulLogin( 'loginsuccess', $injected_html ); 00864 } else { 00865 $this->executeReturnTo( 'successredirect' ); 00866 } 00867 } 00868 00875 function successfulCreation() { 00876 # Run any hooks; display injected HTML 00877 $currentUser = $this->getUser(); 00878 $injected_html = ''; 00879 $welcome_creation_msg = 'welcomecreation'; 00880 00881 wfRunHooks( 'UserLoginComplete', array( &$currentUser, &$injected_html ) ); 00882 00888 wfRunHooks( 'BeforeWelcomeCreation', array( &$welcome_creation_msg, &$injected_html ) ); 00889 00890 $this->displaySuccessfulLogin( $welcome_creation_msg, $injected_html ); 00891 } 00892 00898 private function displaySuccessfulLogin( $msgname, $injected_html ) { 00899 $out = $this->getOutput(); 00900 $out->setPageTitle( $this->msg( 'loginsuccesstitle' ) ); 00901 if( $msgname ){ 00902 $out->addWikiMsg( $msgname, wfEscapeWikiText( $this->getUser()->getName() ) ); 00903 } 00904 00905 $out->addHTML( $injected_html ); 00906 00907 $this->executeReturnTo( 'success' ); 00908 } 00909 00917 function userBlockedMessage( Block $block ) { 00918 # Let's be nice about this, it's likely that this feature will be used 00919 # for blocking large numbers of innocent people, e.g. range blocks on 00920 # schools. Don't blame it on the user. There's a small chance that it 00921 # really is the user's fault, i.e. the username is blocked and they 00922 # haven't bothered to log out before trying to create an account to 00923 # evade it, but we'll leave that to their guilty conscience to figure 00924 # out. 00925 00926 $out = $this->getOutput(); 00927 $out->setPageTitle( $this->msg( 'cantcreateaccounttitle' ) ); 00928 00929 $block_reason = $block->mReason; 00930 if ( strval( $block_reason ) === '' ) { 00931 $block_reason = $this->msg( 'blockednoreason' )->text(); 00932 } 00933 00934 $out->addWikiMsg( 00935 'cantcreateaccount-text', 00936 $block->getTarget(), 00937 $block_reason, 00938 $block->getByName() 00939 ); 00940 00941 $this->executeReturnTo( 'error' ); 00942 } 00943 00952 private function executeReturnTo( $type ) { 00953 global $wgRedirectOnLogin, $wgSecureLogin; 00954 00955 if ( $type != 'error' && $wgRedirectOnLogin !== null ) { 00956 $returnTo = $wgRedirectOnLogin; 00957 $returnToQuery = array(); 00958 } else { 00959 $returnTo = $this->mReturnTo; 00960 $returnToQuery = wfCgiToArray( $this->mReturnToQuery ); 00961 } 00962 00963 $returnToTitle = Title::newFromText( $returnTo ); 00964 if ( !$returnToTitle ) { 00965 $returnToTitle = Title::newMainPage(); 00966 } 00967 00968 if ( $type == 'successredirect' ) { 00969 $redirectUrl = $returnToTitle->getFullURL( $returnToQuery ); 00970 if( $wgSecureLogin && !$this->mStickHTTPS ) { 00971 $redirectUrl = preg_replace( '/^https:/', 'http:', $redirectUrl ); 00972 } 00973 $this->getOutput()->redirect( $redirectUrl ); 00974 } else { 00975 $this->getOutput()->addReturnTo( $returnToTitle, $returnToQuery ); 00976 } 00977 } 00978 00982 function mainLoginForm( $msg, $msgtype = 'error' ) { 00983 global $wgEnableEmail, $wgEnableUserEmail; 00984 global $wgHiddenPrefs, $wgLoginLanguageSelector; 00985 global $wgAuth, $wgEmailConfirmToEdit, $wgCookieExpiration; 00986 global $wgSecureLogin, $wgPasswordResetRoutes; 00987 00988 $titleObj = $this->getTitle(); 00989 $user = $this->getUser(); 00990 00991 if ( $this->mType == 'signup' ) { 00992 // Block signup here if in readonly. Keeps user from 00993 // going through the process (filling out data, etc) 00994 // and being informed later. 00995 $permErrors = $titleObj->getUserPermissionsErrors( 'createaccount', $user, true ); 00996 if ( count( $permErrors ) ) { 00997 throw new PermissionsError( 'createaccount', $permErrors ); 00998 } elseif ( $user->isBlockedFromCreateAccount() ) { 00999 $this->userBlockedMessage( $user->isBlockedFromCreateAccount() ); 01000 return; 01001 } elseif ( wfReadOnly() ) { 01002 throw new ReadOnlyError; 01003 } 01004 } 01005 01006 if ( $this->mUsername == '' ) { 01007 if ( $user->isLoggedIn() ) { 01008 $this->mUsername = $user->getName(); 01009 } else { 01010 $this->mUsername = $this->getRequest()->getCookie( 'UserName' ); 01011 } 01012 } 01013 01014 if ( $this->mType == 'signup' ) { 01015 $template = new UsercreateTemplate(); 01016 $q = 'action=submitlogin&type=signup'; 01017 $linkq = 'type=login'; 01018 $linkmsg = 'gotaccount'; 01019 } else { 01020 $template = new UserloginTemplate(); 01021 $q = 'action=submitlogin&type=login'; 01022 $linkq = 'type=signup'; 01023 $linkmsg = 'nologin'; 01024 } 01025 01026 if ( $this->mReturnTo !== '' ) { 01027 $returnto = '&returnto=' . wfUrlencode( $this->mReturnTo ); 01028 if ( $this->mReturnToQuery !== '' ) { 01029 $returnto .= '&returntoquery=' . 01030 wfUrlencode( $this->mReturnToQuery ); 01031 } 01032 $q .= $returnto; 01033 $linkq .= $returnto; 01034 } 01035 01036 # Don't show a "create account" link if the user can't 01037 if( $this->showCreateOrLoginLink( $user ) ) { 01038 # Pass any language selection on to the mode switch link 01039 if( $wgLoginLanguageSelector && $this->mLanguage ) { 01040 $linkq .= '&uselang=' . $this->mLanguage; 01041 } 01042 $link = Html::element( 'a', array( 'href' => $titleObj->getLocalURL( $linkq ) ), 01043 $this->msg( $linkmsg . 'link' )->text() ); # Calling either 'gotaccountlink' or 'nologinlink' 01044 01045 $template->set( 'link', $this->msg( $linkmsg )->rawParams( $link )->parse() ); 01046 } else { 01047 $template->set( 'link', '' ); 01048 } 01049 01050 $resetLink = $this->mType == 'signup' 01051 ? null 01052 : is_array( $wgPasswordResetRoutes ) && in_array( true, array_values( $wgPasswordResetRoutes ) ); 01053 01054 $template->set( 'header', '' ); 01055 $template->set( 'name', $this->mUsername ); 01056 $template->set( 'password', $this->mPassword ); 01057 $template->set( 'retype', $this->mRetype ); 01058 $template->set( 'email', $this->mEmail ); 01059 $template->set( 'realname', $this->mRealName ); 01060 $template->set( 'domain', $this->mDomain ); 01061 $template->set( 'reason', $this->mReason ); 01062 01063 $template->set( 'action', $titleObj->getLocalURL( $q ) ); 01064 $template->set( 'message', $msg ); 01065 $template->set( 'messagetype', $msgtype ); 01066 $template->set( 'createemail', $wgEnableEmail && $user->isLoggedIn() ); 01067 $template->set( 'userealname', !in_array( 'realname', $wgHiddenPrefs ) ); 01068 $template->set( 'useemail', $wgEnableEmail ); 01069 $template->set( 'emailrequired', $wgEmailConfirmToEdit ); 01070 $template->set( 'emailothers', $wgEnableUserEmail ); 01071 $template->set( 'canreset', $wgAuth->allowPasswordChange() ); 01072 $template->set( 'resetlink', $resetLink ); 01073 $template->set( 'canremember', ( $wgCookieExpiration > 0 ) ); 01074 $template->set( 'usereason', $user->isLoggedIn() ); 01075 $template->set( 'remember', $user->getOption( 'rememberpassword' ) || $this->mRemember ); 01076 $template->set( 'cansecurelogin', ( $wgSecureLogin === true ) ); 01077 $template->set( 'stickHTTPS', $this->mStickHTTPS ); 01078 01079 if ( $this->mType == 'signup' ) { 01080 if ( !self::getCreateaccountToken() ) { 01081 self::setCreateaccountToken(); 01082 } 01083 $template->set( 'token', self::getCreateaccountToken() ); 01084 } else { 01085 if ( !self::getLoginToken() ) { 01086 self::setLoginToken(); 01087 } 01088 $template->set( 'token', self::getLoginToken() ); 01089 } 01090 01091 # Prepare language selection links as needed 01092 if( $wgLoginLanguageSelector ) { 01093 $template->set( 'languages', $this->makeLanguageSelector() ); 01094 if( $this->mLanguage ) { 01095 $template->set( 'uselang', $this->mLanguage ); 01096 } 01097 } 01098 01099 // Use loginend-https for HTTPS requests if it's not blank, loginend otherwise 01100 // Ditto for signupend 01101 $usingHTTPS = WebRequest::detectProtocol() == 'https'; 01102 $loginendHTTPS = $this->msg( 'loginend-https' ); 01103 $signupendHTTPS = $this->msg( 'signupend-https' ); 01104 if ( $usingHTTPS && !$loginendHTTPS->isBlank() ) { 01105 $template->set( 'loginend', $loginendHTTPS->parse() ); 01106 } else { 01107 $template->set( 'loginend', $this->msg( 'loginend' )->parse() ); 01108 } 01109 if ( $usingHTTPS && !$signupendHTTPS->isBlank() ) { 01110 $template->set( 'signupend', $signupendHTTPS->parse() ); 01111 } else { 01112 $template->set( 'signupend', $this->msg( 'signupend' )->parse() ); 01113 } 01114 01115 // Give authentication and captcha plugins a chance to modify the form 01116 $wgAuth->modifyUITemplate( $template, $this->mType ); 01117 if ( $this->mType == 'signup' ) { 01118 wfRunHooks( 'UserCreateForm', array( &$template ) ); 01119 } else { 01120 wfRunHooks( 'UserLoginForm', array( &$template ) ); 01121 } 01122 01123 $out = $this->getOutput(); 01124 $out->disallowUserJs(); // just in case... 01125 $out->addTemplate( $template ); 01126 } 01127 01135 function showCreateOrLoginLink( &$user ) { 01136 if( $this->mType == 'signup' ) { 01137 return true; 01138 } elseif( $user->isAllowed( 'createaccount' ) ) { 01139 return true; 01140 } else { 01141 return false; 01142 } 01143 } 01144 01155 function hasSessionCookie() { 01156 global $wgDisableCookieCheck; 01157 return $wgDisableCookieCheck ? true : $this->getRequest()->checkSessionCookie(); 01158 } 01159 01164 public static function getLoginToken() { 01165 global $wgRequest; 01166 return $wgRequest->getSessionData( 'wsLoginToken' ); 01167 } 01168 01172 public static function setLoginToken() { 01173 global $wgRequest; 01174 // Generate a token directly instead of using $user->editToken() 01175 // because the latter reuses $_SESSION['wsEditToken'] 01176 $wgRequest->setSessionData( 'wsLoginToken', MWCryptRand::generateHex( 32 ) ); 01177 } 01178 01182 public static function clearLoginToken() { 01183 global $wgRequest; 01184 $wgRequest->setSessionData( 'wsLoginToken', null ); 01185 } 01186 01191 public static function getCreateaccountToken() { 01192 global $wgRequest; 01193 return $wgRequest->getSessionData( 'wsCreateaccountToken' ); 01194 } 01195 01199 public static function setCreateaccountToken() { 01200 global $wgRequest; 01201 $wgRequest->setSessionData( 'wsCreateaccountToken', MWCryptRand::generateHex( 32 ) ); 01202 } 01203 01207 public static function clearCreateaccountToken() { 01208 global $wgRequest; 01209 $wgRequest->setSessionData( 'wsCreateaccountToken', null ); 01210 } 01211 01215 private function renewSessionId() { 01216 if ( wfCheckEntropy() ) { 01217 session_regenerate_id( false ); 01218 } else { 01219 //If we don't trust PHP's entropy, we have to replace the session manually 01220 $tmp = $_SESSION; 01221 session_unset(); 01222 session_write_close(); 01223 session_id( MWCryptRand::generateHex( 32 ) ); 01224 session_start(); 01225 $_SESSION = $tmp; 01226 } 01227 } 01228 01232 function cookieRedirectCheck( $type ) { 01233 $titleObj = SpecialPage::getTitleFor( 'Userlogin' ); 01234 $query = array( 'wpCookieCheck' => $type ); 01235 if ( $this->mReturnTo !== '' ) { 01236 $query['returnto'] = $this->mReturnTo; 01237 $query['returntoquery'] = $this->mReturnToQuery; 01238 } 01239 $check = $titleObj->getFullURL( $query ); 01240 01241 $this->getOutput()->redirect( $check ); 01242 } 01243 01247 function onCookieRedirectCheck( $type ) { 01248 if ( !$this->hasSessionCookie() ) { 01249 if ( $type == 'new' ) { 01250 $this->mainLoginForm( $this->msg( 'nocookiesnew' )->parse() ); 01251 } elseif ( $type == 'login' ) { 01252 $this->mainLoginForm( $this->msg( 'nocookieslogin' )->parse() ); 01253 } else { 01254 # shouldn't happen 01255 $this->mainLoginForm( $this->msg( 'error' )->text() ); 01256 } 01257 } else { 01258 $this->successfulLogin(); 01259 } 01260 } 01261 01265 function throttleHit( $limit ) { 01266 $this->mainLoginForm( $this->msg( 'acct_creation_throttle_hit' )->numParams( $limit )->parse() ); 01267 } 01268 01275 function makeLanguageSelector() { 01276 $msg = $this->msg( 'loginlanguagelinks' )->inContentLanguage(); 01277 if( !$msg->isBlank() ) { 01278 $langs = explode( "\n", $msg->text() ); 01279 $links = array(); 01280 foreach( $langs as $lang ) { 01281 $lang = trim( $lang, '* ' ); 01282 $parts = explode( '|', $lang ); 01283 if ( count( $parts ) >= 2 ) { 01284 $links[] = $this->makeLanguageSelectorLink( $parts[0], trim( $parts[1] ) ); 01285 } 01286 } 01287 return count( $links ) > 0 ? $this->msg( 'loginlanguagelabel' )->rawParams( 01288 $this->getLanguage()->pipeList( $links ) )->escaped() : ''; 01289 } else { 01290 return ''; 01291 } 01292 } 01293 01302 function makeLanguageSelectorLink( $text, $lang ) { 01303 if( $this->getLanguage()->getCode() == $lang ) { 01304 // no link for currently used language 01305 return htmlspecialchars( $text ); 01306 } 01307 $query = array( 'uselang' => $lang ); 01308 if( $this->mType == 'signup' ) { 01309 $query['type'] = 'signup'; 01310 } 01311 if( $this->mReturnTo !== '' ) { 01312 $query['returnto'] = $this->mReturnTo; 01313 $query['returntoquery'] = $this->mReturnToQuery; 01314 } 01315 01316 $attr = array(); 01317 $targetLanguage = Language::factory( $lang ); 01318 $attr['lang'] = $attr['hreflang'] = $targetLanguage->getHtmlCode(); 01319 01320 return Linker::linkKnown( 01321 $this->getTitle(), 01322 htmlspecialchars( $text ), 01323 $attr, 01324 $query 01325 ); 01326 } 01327 }