MediaWiki
master
|
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 global $wgSecureLogin; 00153 if ( 00154 $this->mType !== 'signup' && 00155 $wgSecureLogin && 00156 WebRequest::detectProtocol() !== 'https' 00157 ) { 00158 $title = $this->getFullTitle(); 00159 $query = array( 00160 'returnto' => $this->mReturnTo, 00161 'returntoquery' => $this->mReturnToQuery, 00162 'wpStickHTTPS' => $this->mStickHTTPS 00163 ); 00164 $url = $title->getFullURL( $query, false, PROTO_HTTPS ); 00165 $this->getOutput()->redirect( $url ); 00166 return; 00167 } 00168 00169 if ( $par == 'signup' ) { # Check for [[Special:Userlogin/signup]] 00170 $this->mType = 'signup'; 00171 } 00172 00173 if ( !is_null( $this->mCookieCheck ) ) { 00174 $this->onCookieRedirectCheck( $this->mCookieCheck ); 00175 return; 00176 } elseif( $this->mPosted ) { 00177 if( $this->mCreateaccount ) { 00178 $this->addNewAccount(); 00179 return; 00180 } elseif ( $this->mCreateaccountMail ) { 00181 $this->addNewAccountMailPassword(); 00182 return; 00183 } elseif ( ( 'submitlogin' == $this->mAction ) || $this->mLoginattempt ) { 00184 $this->processLogin(); 00185 return; 00186 } 00187 } 00188 $this->mainLoginForm( '' ); 00189 } 00190 00194 function addNewAccountMailPassword() { 00195 if ( $this->mEmail == '' ) { 00196 $this->mainLoginForm( $this->msg( 'noemailcreate' )->escaped() ); 00197 return; 00198 } 00199 00200 $u = $this->addNewaccountInternal(); 00201 00202 if ( $u == null ) { 00203 return; 00204 } 00205 00206 // Wipe the initial password and mail a temporary one 00207 $u->setPassword( null ); 00208 $u->saveSettings(); 00209 $result = $this->mailPasswordInternal( $u, false, 'createaccount-title', 'createaccount-text' ); 00210 00211 wfRunHooks( 'AddNewAccount', array( $u, true ) ); 00212 $u->addNewUserLogEntry( true, $this->mReason ); 00213 00214 $out = $this->getOutput(); 00215 $out->setPageTitle( $this->msg( 'accmailtitle' ) ); 00216 00217 if( !$result->isGood() ) { 00218 $this->mainLoginForm( $this->msg( 'mailerror', $result->getWikiText() )->text() ); 00219 } else { 00220 $out->addWikiMsg( 'accmailtext', $u->getName(), $u->getEmail() ); 00221 $this->executeReturnTo( 'success' ); 00222 } 00223 } 00224 00229 function addNewAccount() { 00230 global $wgUser, $wgEmailAuthentication, $wgLoginLanguageSelector; 00231 00232 # Create the account and abort if there's a problem doing so 00233 $u = $this->addNewAccountInternal(); 00234 if( $u == null ) { 00235 return false; 00236 } 00237 00238 # If we showed up language selection links, and one was in use, be 00239 # smart (and sensible) and save that language as the user's preference 00240 if( $wgLoginLanguageSelector && $this->mLanguage ) { 00241 $u->setOption( 'language', $this->mLanguage ); 00242 } 00243 00244 $out = $this->getOutput(); 00245 00246 # Send out an email authentication message if needed 00247 if( $wgEmailAuthentication && Sanitizer::validateEmail( $u->getEmail() ) ) { 00248 $status = $u->sendConfirmationMail(); 00249 if( $status->isGood() ) { 00250 $out->addWikiMsg( 'confirmemail_oncreate' ); 00251 } else { 00252 $out->addWikiText( $status->getWikiText( 'confirmemail_sendfailed' ) ); 00253 } 00254 } 00255 00256 # Save settings (including confirmation token) 00257 $u->saveSettings(); 00258 00259 # If not logged in, assume the new account as the current one and set 00260 # session cookies then show a "welcome" message or a "need cookies" 00261 # message as needed 00262 if( $this->getUser()->isAnon() ) { 00263 $u->setCookies(); 00264 $wgUser = $u; 00265 // This should set it for OutputPage and the Skin 00266 // which is needed or the personal links will be 00267 // wrong. 00268 $this->getContext()->setUser( $u ); 00269 wfRunHooks( 'AddNewAccount', array( $u, false ) ); 00270 $u->addNewUserLogEntry(); 00271 if( $this->hasSessionCookie() ) { 00272 $this->successfulCreation(); 00273 } else { 00274 $this->cookieRedirectCheck( 'new' ); 00275 } 00276 } else { 00277 # Confirm that the account was created 00278 $out->setPageTitle( $this->msg( 'accountcreated' ) ); 00279 $out->addWikiMsg( 'accountcreatedtext', $u->getName() ); 00280 $out->addReturnTo( $this->getTitle() ); 00281 wfRunHooks( 'AddNewAccount', array( $u, false ) ); 00282 $u->addNewUserLogEntry( false, $this->mReason ); 00283 } 00284 return true; 00285 } 00286 00292 function addNewAccountInternal() { 00293 global $wgAuth, $wgMemc, $wgAccountCreationThrottle, 00294 $wgMinimalPasswordLength, $wgEmailConfirmToEdit; 00295 00296 // If the user passes an invalid domain, something is fishy 00297 if( !$wgAuth->validDomain( $this->mDomain ) ) { 00298 $this->mainLoginForm( $this->msg( 'wrongpassword' )->text() ); 00299 return false; 00300 } 00301 00302 // If we are not allowing users to login locally, we should be checking 00303 // to see if the user is actually able to authenticate to the authenti- 00304 // cation server before they create an account (otherwise, they can 00305 // create a local account and login as any domain user). We only need 00306 // to check this for domains that aren't local. 00307 if( 'local' != $this->mDomain && $this->mDomain != '' ) { 00308 if( !$wgAuth->canCreateAccounts() && ( !$wgAuth->userExists( $this->mUsername ) 00309 || !$wgAuth->authenticate( $this->mUsername, $this->mPassword ) ) ) { 00310 $this->mainLoginForm( $this->msg( 'wrongpassword' )->text() ); 00311 return false; 00312 } 00313 } 00314 00315 if ( wfReadOnly() ) { 00316 throw new ReadOnlyError; 00317 } 00318 00319 # Request forgery checks. 00320 if ( !self::getCreateaccountToken() ) { 00321 self::setCreateaccountToken(); 00322 $this->mainLoginForm( $this->msg( 'nocookiesfornew' )->parse() ); 00323 return false; 00324 } 00325 00326 # The user didn't pass a createaccount token 00327 if ( !$this->mToken ) { 00328 $this->mainLoginForm( $this->msg( 'sessionfailure' )->text() ); 00329 return false; 00330 } 00331 00332 # Validate the createaccount token 00333 if ( $this->mToken !== self::getCreateaccountToken() ) { 00334 $this->mainLoginForm( $this->msg( 'sessionfailure' )->text() ); 00335 return false; 00336 } 00337 00338 # Check permissions 00339 $currentUser = $this->getUser(); 00340 if ( !$currentUser->isAllowed( 'createaccount' ) ) { 00341 throw new PermissionsError( 'createaccount' ); 00342 } elseif ( $currentUser->isBlockedFromCreateAccount() ) { 00343 $this->userBlockedMessage( $currentUser->isBlockedFromCreateAccount() ); 00344 return false; 00345 } 00346 00347 # Include checks that will include GlobalBlocking (Bug 38333) 00348 $permErrors = $this->getTitle()->getUserPermissionsErrors( 'createaccount', $currentUser, true ); 00349 if ( count( $permErrors ) ) { 00350 throw new PermissionsError( 'createaccount', $permErrors ); 00351 } 00352 00353 $ip = $this->getRequest()->getIP(); 00354 if ( $currentUser->isDnsBlacklisted( $ip, true /* check $wgProxyWhitelist */ ) ) { 00355 $this->mainLoginForm( $this->msg( 'sorbs_create_account_reason' )->text() . ' ' . $this->msg( 'parentheses', $ip )->escaped() ); 00356 return false; 00357 } 00358 00359 # Now create a dummy user ($u) and check if it is valid 00360 $name = trim( $this->mUsername ); 00361 $u = User::newFromName( $name, 'creatable' ); 00362 if ( !is_object( $u ) ) { 00363 $this->mainLoginForm( $this->msg( 'noname' )->text() ); 00364 return false; 00365 } 00366 00367 if ( 0 != $u->idForName() ) { 00368 $this->mainLoginForm( $this->msg( 'userexists' )->text() ); 00369 return false; 00370 } 00371 00372 if ( 0 != strcmp( $this->mPassword, $this->mRetype ) ) { 00373 $this->mainLoginForm( $this->msg( 'badretype' )->text() ); 00374 return false; 00375 } 00376 00377 # check for minimal password length 00378 $valid = $u->getPasswordValidity( $this->mPassword ); 00379 if ( $valid !== true ) { 00380 if ( !$this->mCreateaccountMail ) { 00381 if ( is_array( $valid ) ) { 00382 $message = array_shift( $valid ); 00383 $params = $valid; 00384 } else { 00385 $message = $valid; 00386 $params = array( $wgMinimalPasswordLength ); 00387 } 00388 $this->mainLoginForm( $this->msg( $message, $params )->text() ); 00389 return false; 00390 } else { 00391 # do not force a password for account creation by email 00392 # set invalid password, it will be replaced later by a random generated password 00393 $this->mPassword = null; 00394 } 00395 } 00396 00397 # if you need a confirmed email address to edit, then obviously you 00398 # need an email address. 00399 if ( $wgEmailConfirmToEdit && empty( $this->mEmail ) ) { 00400 $this->mainLoginForm( $this->msg( 'noemailtitle' )->text() ); 00401 return false; 00402 } 00403 00404 if( !empty( $this->mEmail ) && !Sanitizer::validateEmail( $this->mEmail ) ) { 00405 $this->mainLoginForm( $this->msg( 'invalidemailaddress' )->text() ); 00406 return false; 00407 } 00408 00409 # Set some additional data so the AbortNewAccount hook can be used for 00410 # more than just username validation 00411 $u->setEmail( $this->mEmail ); 00412 $u->setRealName( $this->mRealName ); 00413 00414 $abortError = ''; 00415 if( !wfRunHooks( 'AbortNewAccount', array( $u, &$abortError ) ) ) { 00416 // Hook point to add extra creation throttles and blocks 00417 wfDebug( "LoginForm::addNewAccountInternal: a hook blocked creation\n" ); 00418 $this->mainLoginForm( $abortError ); 00419 return false; 00420 } 00421 00422 // Hook point to check for exempt from account creation throttle 00423 if ( !wfRunHooks( 'ExemptFromAccountCreationThrottle', array( $ip ) ) ) { 00424 wfDebug( "LoginForm::exemptFromAccountCreationThrottle: a hook allowed account creation w/o throttle\n" ); 00425 } else { 00426 if ( ( $wgAccountCreationThrottle && $currentUser->isPingLimitable() ) ) { 00427 $key = wfMemcKey( 'acctcreate', 'ip', $ip ); 00428 $value = $wgMemc->get( $key ); 00429 if ( !$value ) { 00430 $wgMemc->set( $key, 0, 86400 ); 00431 } 00432 if ( $value >= $wgAccountCreationThrottle ) { 00433 $this->throttleHit( $wgAccountCreationThrottle ); 00434 return false; 00435 } 00436 $wgMemc->incr( $key ); 00437 } 00438 } 00439 00440 if( !$wgAuth->addUser( $u, $this->mPassword, $this->mEmail, $this->mRealName ) ) { 00441 $this->mainLoginForm( $this->msg( 'externaldberror' )->text() ); 00442 return false; 00443 } 00444 00445 self::clearCreateaccountToken(); 00446 00447 $status = $this->initUser( $u, false ); 00448 if ( !$status->isOK() ) { 00449 $this->mainLoginForm( $status->getHTML() ); 00450 return false; 00451 } 00452 return $status->value; 00453 } 00454 00464 function initUser( $u, $autocreate ) { 00465 global $wgAuth; 00466 00467 $status = $u->addToDatabase(); 00468 if ( !$status->isOK() ) { 00469 return $status; 00470 } 00471 00472 if ( $wgAuth->allowPasswordChange() ) { 00473 $u->setPassword( $this->mPassword ); 00474 } 00475 00476 $u->setEmail( $this->mEmail ); 00477 $u->setRealName( $this->mRealName ); 00478 $u->setToken(); 00479 00480 $wgAuth->initUser( $u, $autocreate ); 00481 00482 if ( $this->mExtUser ) { 00483 $this->mExtUser->linkToLocal( $u->getId() ); 00484 $email = $this->mExtUser->getPref( 'emailaddress' ); 00485 if ( $email && !$this->mEmail ) { 00486 $u->setEmail( $email ); 00487 } 00488 } 00489 00490 $u->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 ); 00491 $u->saveSettings(); 00492 00493 # Update user count 00494 DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 0, 0, 0, 1 ) ); 00495 00496 return Status::newGood( $u ); 00497 } 00498 00507 public function authenticateUserData() { 00508 global $wgUser, $wgAuth; 00509 00510 $this->load(); 00511 00512 if ( $this->mUsername == '' ) { 00513 return self::NO_NAME; 00514 } 00515 00516 // We require a login token to prevent login CSRF 00517 // Handle part of this before incrementing the throttle so 00518 // token-less login attempts don't count towards the throttle 00519 // but wrong-token attempts do. 00520 00521 // If the user doesn't have a login token yet, set one. 00522 if ( !self::getLoginToken() ) { 00523 self::setLoginToken(); 00524 return self::NEED_TOKEN; 00525 } 00526 // If the user didn't pass a login token, tell them we need one 00527 if ( !$this->mToken ) { 00528 return self::NEED_TOKEN; 00529 } 00530 00531 $throttleCount = self::incLoginThrottle( $this->mUsername ); 00532 if ( $throttleCount === true ) { 00533 return self::THROTTLED; 00534 } 00535 00536 // Validate the login token 00537 if ( $this->mToken !== self::getLoginToken() ) { 00538 return self::WRONG_TOKEN; 00539 } 00540 00541 // Load the current user now, and check to see if we're logging in as 00542 // the same name. This is necessary because loading the current user 00543 // (say by calling getName()) calls the UserLoadFromSession hook, which 00544 // potentially creates the user in the database. Until we load $wgUser, 00545 // checking for user existence using User::newFromName($name)->getId() below 00546 // will effectively be using stale data. 00547 if ( $this->getUser()->getName() === $this->mUsername ) { 00548 wfDebug( __METHOD__ . ": already logged in as {$this->mUsername}\n" ); 00549 return self::SUCCESS; 00550 } 00551 00552 $this->mExtUser = ExternalUser::newFromName( $this->mUsername ); 00553 00554 # TODO: Allow some magic here for invalid external names, e.g., let the 00555 # user choose a different wiki name. 00556 $u = User::newFromName( $this->mUsername ); 00557 if( !( $u instanceof User ) || !User::isUsableName( $u->getName() ) ) { 00558 return self::ILLEGAL; 00559 } 00560 00561 $isAutoCreated = false; 00562 if ( 0 == $u->getID() ) { 00563 $status = $this->attemptAutoCreate( $u ); 00564 if ( $status !== self::SUCCESS ) { 00565 return $status; 00566 } else { 00567 $isAutoCreated = true; 00568 } 00569 } else { 00570 global $wgExternalAuthType, $wgAutocreatePolicy; 00571 if ( $wgExternalAuthType && $wgAutocreatePolicy != 'never' 00572 && is_object( $this->mExtUser ) 00573 && $this->mExtUser->authenticate( $this->mPassword ) ) { 00574 # The external user and local user have the same name and 00575 # password, so we assume they're the same. 00576 $this->mExtUser->linkToLocal( $u->getID() ); 00577 } 00578 00579 $u->load(); 00580 } 00581 00582 // Give general extensions, such as a captcha, a chance to abort logins 00583 $abort = self::ABORTED; 00584 if( !wfRunHooks( 'AbortLogin', array( $u, $this->mPassword, &$abort, &$this->mAbortLoginErrorMsg ) ) ) { 00585 return $abort; 00586 } 00587 00588 global $wgBlockDisablesLogin; 00589 if ( !$u->checkPassword( $this->mPassword ) ) { 00590 if( $u->checkTemporaryPassword( $this->mPassword ) ) { 00591 // The e-mailed temporary password should not be used for actu- 00592 // al logins; that's a very sloppy habit, and insecure if an 00593 // attacker has a few seconds to click "search" on someone's o- 00594 // pen mail reader. 00595 // 00596 // Allow it to be used only to reset the password a single time 00597 // to a new value, which won't be in the user's e-mail ar- 00598 // chives. 00599 // 00600 // For backwards compatibility, we'll still recognize it at the 00601 // login form to minimize surprises for people who have been 00602 // logging in with a temporary password for some time. 00603 // 00604 // As a side-effect, we can authenticate the user's e-mail ad- 00605 // dress if it's not already done, since the temporary password 00606 // was sent via e-mail. 00607 if( !$u->isEmailConfirmed() ) { 00608 $u->confirmEmail(); 00609 $u->saveSettings(); 00610 } 00611 00612 // At this point we just return an appropriate code/ indicating 00613 // that the UI should show a password reset form; bot inter- 00614 // faces etc will probably just fail cleanly here. 00615 $retval = self::RESET_PASS; 00616 } else { 00617 $retval = ( $this->mPassword == '' ) ? self::EMPTY_PASS : self::WRONG_PASS; 00618 } 00619 } elseif ( $wgBlockDisablesLogin && $u->isBlocked() ) { 00620 // If we've enabled it, make it so that a blocked user cannot login 00621 $retval = self::USER_BLOCKED; 00622 } else { 00623 $wgAuth->updateUser( $u ); 00624 $wgUser = $u; 00625 // This should set it for OutputPage and the Skin 00626 // which is needed or the personal links will be 00627 // wrong. 00628 $this->getContext()->setUser( $u ); 00629 00630 // Please reset throttle for successful logins, thanks! 00631 if ( $throttleCount ) { 00632 self::clearLoginThrottle( $this->mUsername ); 00633 } 00634 00635 if ( $isAutoCreated ) { 00636 // Must be run after $wgUser is set, for correct new user log 00637 wfRunHooks( 'AuthPluginAutoCreate', array( $u ) ); 00638 } 00639 00640 $retval = self::SUCCESS; 00641 } 00642 wfRunHooks( 'LoginAuthenticateAudit', array( $u, $this->mPassword, $retval ) ); 00643 return $retval; 00644 } 00645 00652 public static function incLoginThrottle( $username ) { 00653 global $wgPasswordAttemptThrottle, $wgMemc, $wgRequest; 00654 $username = trim( $username ); // sanity 00655 00656 $throttleCount = 0; 00657 if ( is_array( $wgPasswordAttemptThrottle ) ) { 00658 $throttleKey = wfMemcKey( 'password-throttle', $wgRequest->getIP(), md5( $username ) ); 00659 $count = $wgPasswordAttemptThrottle['count']; 00660 $period = $wgPasswordAttemptThrottle['seconds']; 00661 00662 $throttleCount = $wgMemc->get( $throttleKey ); 00663 if ( !$throttleCount ) { 00664 $wgMemc->add( $throttleKey, 1, $period ); // start counter 00665 } elseif ( $throttleCount < $count ) { 00666 $wgMemc->incr( $throttleKey ); 00667 } elseif ( $throttleCount >= $count ) { 00668 return true; 00669 } 00670 } 00671 00672 return $throttleCount; 00673 } 00674 00680 public static function clearLoginThrottle( $username ) { 00681 global $wgMemc, $wgRequest; 00682 $username = trim( $username ); // sanity 00683 00684 $throttleKey = wfMemcKey( 'password-throttle', $wgRequest->getIP(), md5( $username ) ); 00685 $wgMemc->delete( $throttleKey ); 00686 } 00687 00696 function attemptAutoCreate( $user ) { 00697 global $wgAuth, $wgAutocreatePolicy; 00698 00699 if ( $this->getUser()->isBlockedFromCreateAccount() ) { 00700 wfDebug( __METHOD__ . ": user is blocked from account creation\n" ); 00701 return self::CREATE_BLOCKED; 00702 } 00703 00709 if ( $this->mExtUser ) { 00710 # mExtUser is neither null nor false, so use the new ExternalAuth 00711 # system. 00712 if ( $wgAutocreatePolicy == 'never' ) { 00713 return self::NOT_EXISTS; 00714 } 00715 if ( !$this->mExtUser->authenticate( $this->mPassword ) ) { 00716 return self::WRONG_PLUGIN_PASS; 00717 } 00718 } else { 00719 # Old AuthPlugin. 00720 if ( !$wgAuth->autoCreate() ) { 00721 return self::NOT_EXISTS; 00722 } 00723 if ( !$wgAuth->userExists( $user->getName() ) ) { 00724 wfDebug( __METHOD__ . ": user does not exist\n" ); 00725 return self::NOT_EXISTS; 00726 } 00727 if ( !$wgAuth->authenticate( $user->getName(), $this->mPassword ) ) { 00728 wfDebug( __METHOD__ . ": \$wgAuth->authenticate() returned false, aborting\n" ); 00729 return self::WRONG_PLUGIN_PASS; 00730 } 00731 } 00732 00733 $abortError = ''; 00734 if( !wfRunHooks( 'AbortAutoAccount', array( $user, &$abortError ) ) ) { 00735 // Hook point to add extra creation throttles and blocks 00736 wfDebug( "LoginForm::attemptAutoCreate: a hook blocked creation: $abortError\n" ); 00737 $this->mAbortLoginErrorMsg = $abortError; 00738 return self::ABORTED; 00739 } 00740 00741 wfDebug( __METHOD__ . ": creating account\n" ); 00742 $status = $this->initUser( $user, true ); 00743 00744 if ( !$status->isOK() ) { 00745 $errors = $status->getErrorsByType( 'error' ); 00746 $this->mAbortLoginErrorMsg = $errors[0]['message']; 00747 return self::ABORTED; 00748 } 00749 00750 return self::SUCCESS; 00751 } 00752 00753 function processLogin() { 00754 global $wgMemc, $wgLang; 00755 00756 switch ( $this->authenticateUserData() ) { 00757 case self::SUCCESS: 00758 global $wgSecureLogin; 00759 # We've verified now, update the real record 00760 $user = $this->getUser(); 00761 if( (bool)$this->mRemember != (bool)$user->getOption( 'rememberpassword' ) ) { 00762 $user->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 ); 00763 $user->saveSettings(); 00764 } else { 00765 $user->invalidateCache(); 00766 } 00767 00768 if( $wgSecureLogin && !$this->mStickHTTPS ) { 00769 $user->setCookies( null, false ); 00770 } else { 00771 $user->setCookies(); 00772 } 00773 self::clearLoginToken(); 00774 00775 // Reset the throttle 00776 $request = $this->getRequest(); 00777 $key = wfMemcKey( 'password-throttle', $request->getIP(), md5( $this->mUsername ) ); 00778 $wgMemc->delete( $key ); 00779 00780 if( $this->hasSessionCookie() || $this->mSkipCookieCheck ) { 00781 /* Replace the language object to provide user interface in 00782 * correct language immediately on this first page load. 00783 */ 00784 $code = $request->getVal( 'uselang', $user->getOption( 'language' ) ); 00785 $userLang = Language::factory( $code ); 00786 $wgLang = $userLang; 00787 $this->getContext()->setLanguage( $userLang ); 00788 $this->successfulLogin(); 00789 } else { 00790 $this->cookieRedirectCheck( 'login' ); 00791 } 00792 break; 00793 00794 case self::NEED_TOKEN: 00795 $this->mainLoginForm( $this->msg( 'nocookiesforlogin' )->parse() ); 00796 break; 00797 case self::WRONG_TOKEN: 00798 $this->mainLoginForm( $this->msg( 'sessionfailure' )->text() ); 00799 break; 00800 case self::NO_NAME: 00801 case self::ILLEGAL: 00802 $this->mainLoginForm( $this->msg( 'noname' )->text() ); 00803 break; 00804 case self::WRONG_PLUGIN_PASS: 00805 $this->mainLoginForm( $this->msg( 'wrongpassword' )->text() ); 00806 break; 00807 case self::NOT_EXISTS: 00808 if( $this->getUser()->isAllowed( 'createaccount' ) ) { 00809 $this->mainLoginForm( $this->msg( 'nosuchuser', 00810 wfEscapeWikiText( $this->mUsername ) )->parse() ); 00811 } else { 00812 $this->mainLoginForm( $this->msg( 'nosuchusershort', 00813 wfEscapeWikiText( $this->mUsername ) )->text() ); 00814 } 00815 break; 00816 case self::WRONG_PASS: 00817 $this->mainLoginForm( $this->msg( 'wrongpassword' )->text() ); 00818 break; 00819 case self::EMPTY_PASS: 00820 $this->mainLoginForm( $this->msg( 'wrongpasswordempty' )->text() ); 00821 break; 00822 case self::RESET_PASS: 00823 $this->resetLoginForm( $this->msg( 'resetpass_announce' )->text() ); 00824 break; 00825 case self::CREATE_BLOCKED: 00826 $this->userBlockedMessage( $this->getUser()->mBlock ); 00827 break; 00828 case self::THROTTLED: 00829 $this->mainLoginForm( $this->msg( 'login-throttled' )->text() ); 00830 break; 00831 case self::USER_BLOCKED: 00832 $this->mainLoginForm( $this->msg( 'login-userblocked', 00833 $this->mUsername )->escaped() ); 00834 break; 00835 case self::ABORTED: 00836 $this->mainLoginForm( $this->msg( $this->mAbortLoginErrorMsg )->text() ); 00837 break; 00838 default: 00839 throw new MWException( 'Unhandled case value' ); 00840 } 00841 } 00842 00843 function resetLoginForm( $error ) { 00844 $this->getOutput()->addHTML( Xml::element('p', array( 'class' => 'error' ), $error ) ); 00845 $reset = new SpecialChangePassword(); 00846 $reset->setContext( $this->getContext() ); 00847 $reset->execute( null ); 00848 } 00849 00857 function mailPasswordInternal( $u, $throttle = true, $emailTitle = 'passwordremindertitle', $emailText = 'passwordremindertext' ) { 00858 global $wgCanonicalServer, $wgScript, $wgNewPasswordExpiry; 00859 00860 if ( $u->getEmail() == '' ) { 00861 return Status::newFatal( 'noemail', $u->getName() ); 00862 } 00863 $ip = $this->getRequest()->getIP(); 00864 if( !$ip ) { 00865 return Status::newFatal( 'badipaddress' ); 00866 } 00867 00868 $currentUser = $this->getUser(); 00869 wfRunHooks( 'User::mailPasswordInternal', array( &$currentUser, &$ip, &$u ) ); 00870 00871 $np = $u->randomPassword(); 00872 $u->setNewpassword( $np, $throttle ); 00873 $u->saveSettings(); 00874 $userLanguage = $u->getOption( 'language' ); 00875 $m = $this->msg( $emailText, $ip, $u->getName(), $np, '<' . $wgCanonicalServer . $wgScript . '>', 00876 round( $wgNewPasswordExpiry / 86400 ) )->inLanguage( $userLanguage )->text(); 00877 $result = $u->sendMail( $this->msg( $emailTitle )->inLanguage( $userLanguage )->text(), $m ); 00878 00879 return $result; 00880 } 00881 00882 00893 function successfulLogin() { 00894 # Run any hooks; display injected HTML if any, else redirect 00895 $currentUser = $this->getUser(); 00896 $injected_html = ''; 00897 wfRunHooks( 'UserLoginComplete', array( &$currentUser, &$injected_html ) ); 00898 00899 if( $injected_html !== '' ) { 00900 $this->displaySuccessfulLogin( 'loginsuccess', $injected_html ); 00901 } else { 00902 $this->executeReturnTo( 'successredirect' ); 00903 } 00904 } 00905 00912 function successfulCreation() { 00913 # Run any hooks; display injected HTML 00914 $currentUser = $this->getUser(); 00915 $injected_html = ''; 00916 $welcome_creation_msg = 'welcomecreation'; 00917 00918 wfRunHooks( 'UserLoginComplete', array( &$currentUser, &$injected_html ) ); 00919 00925 wfRunHooks( 'BeforeWelcomeCreation', array( &$welcome_creation_msg, &$injected_html ) ); 00926 00927 $this->displaySuccessfulLogin( $welcome_creation_msg, $injected_html ); 00928 } 00929 00935 private function displaySuccessfulLogin( $msgname, $injected_html ) { 00936 $out = $this->getOutput(); 00937 $out->setPageTitle( $this->msg( 'loginsuccesstitle' ) ); 00938 if( $msgname ){ 00939 $out->addWikiMsg( $msgname, wfEscapeWikiText( $this->getUser()->getName() ) ); 00940 } 00941 00942 $out->addHTML( $injected_html ); 00943 00944 $this->executeReturnTo( 'success' ); 00945 } 00946 00954 function userBlockedMessage( Block $block ) { 00955 # Let's be nice about this, it's likely that this feature will be used 00956 # for blocking large numbers of innocent people, e.g. range blocks on 00957 # schools. Don't blame it on the user. There's a small chance that it 00958 # really is the user's fault, i.e. the username is blocked and they 00959 # haven't bothered to log out before trying to create an account to 00960 # evade it, but we'll leave that to their guilty conscience to figure 00961 # out. 00962 00963 $out = $this->getOutput(); 00964 $out->setPageTitle( $this->msg( 'cantcreateaccounttitle' ) ); 00965 00966 $block_reason = $block->mReason; 00967 if ( strval( $block_reason ) === '' ) { 00968 $block_reason = $this->msg( 'blockednoreason' )->text(); 00969 } 00970 00971 $out->addWikiMsg( 00972 'cantcreateaccount-text', 00973 $block->getTarget(), 00974 $block_reason, 00975 $block->getByName() 00976 ); 00977 00978 $this->executeReturnTo( 'error' ); 00979 } 00980 00989 private function executeReturnTo( $type ) { 00990 global $wgRedirectOnLogin, $wgSecureLogin; 00991 00992 if ( $type != 'error' && $wgRedirectOnLogin !== null ) { 00993 $returnTo = $wgRedirectOnLogin; 00994 $returnToQuery = array(); 00995 } else { 00996 $returnTo = $this->mReturnTo; 00997 $returnToQuery = wfCgiToArray( $this->mReturnToQuery ); 00998 } 00999 01000 $returnToTitle = Title::newFromText( $returnTo ); 01001 if ( !$returnToTitle ) { 01002 $returnToTitle = Title::newMainPage(); 01003 } 01004 01005 if ( $wgSecureLogin && !$this->mStickHTTPS ) { 01006 $options = array( 'http' ); 01007 $proto = PROTO_HTTP; 01008 } elseif( $wgSecureLogin ) { 01009 $options = array( 'https' ); 01010 $proto = PROTO_HTTPS; 01011 } else { 01012 $options = array(); 01013 $proto = PROTO_RELATIVE; 01014 } 01015 01016 if ( $type == 'successredirect' ) { 01017 $redirectUrl = $returnToTitle->getFullURL( $returnToQuery, false, $proto ); 01018 $this->getOutput()->redirect( $redirectUrl ); 01019 } else { 01020 $this->getOutput()->addReturnTo( $returnToTitle, $returnToQuery, null, $options ); 01021 } 01022 } 01023 01027 function mainLoginForm( $msg, $msgtype = 'error' ) { 01028 global $wgEnableEmail, $wgEnableUserEmail; 01029 global $wgHiddenPrefs, $wgLoginLanguageSelector; 01030 global $wgAuth, $wgEmailConfirmToEdit, $wgCookieExpiration; 01031 global $wgSecureLogin, $wgPasswordResetRoutes; 01032 01033 $titleObj = $this->getTitle(); 01034 $user = $this->getUser(); 01035 01036 if ( $this->mType == 'signup' ) { 01037 // Block signup here if in readonly. Keeps user from 01038 // going through the process (filling out data, etc) 01039 // and being informed later. 01040 $permErrors = $titleObj->getUserPermissionsErrors( 'createaccount', $user, true ); 01041 if ( count( $permErrors ) ) { 01042 throw new PermissionsError( 'createaccount', $permErrors ); 01043 } elseif ( $user->isBlockedFromCreateAccount() ) { 01044 $this->userBlockedMessage( $user->isBlockedFromCreateAccount() ); 01045 return; 01046 } elseif ( wfReadOnly() ) { 01047 throw new ReadOnlyError; 01048 } 01049 } 01050 01051 if ( $this->mUsername == '' ) { 01052 if ( $user->isLoggedIn() ) { 01053 $this->mUsername = $user->getName(); 01054 } else { 01055 $this->mUsername = $this->getRequest()->getCookie( 'UserName' ); 01056 } 01057 } 01058 01059 if ( $this->mType == 'signup' ) { 01060 $template = new UsercreateTemplate(); 01061 $q = 'action=submitlogin&type=signup'; 01062 $linkq = 'type=login'; 01063 $linkmsg = 'gotaccount'; 01064 } else { 01065 $template = new UserloginTemplate(); 01066 $q = 'action=submitlogin&type=login'; 01067 $linkq = 'type=signup'; 01068 $linkmsg = 'nologin'; 01069 } 01070 01071 if ( $this->mReturnTo !== '' ) { 01072 $returnto = '&returnto=' . wfUrlencode( $this->mReturnTo ); 01073 if ( $this->mReturnToQuery !== '' ) { 01074 $returnto .= '&returntoquery=' . 01075 wfUrlencode( $this->mReturnToQuery ); 01076 } 01077 $q .= $returnto; 01078 $linkq .= $returnto; 01079 } 01080 01081 # Don't show a "create account" link if the user can't 01082 if( $this->showCreateOrLoginLink( $user ) ) { 01083 # Pass any language selection on to the mode switch link 01084 if( $wgLoginLanguageSelector && $this->mLanguage ) { 01085 $linkq .= '&uselang=' . $this->mLanguage; 01086 } 01087 $link = Html::element( 'a', array( 'href' => $titleObj->getLocalURL( $linkq ) ), 01088 $this->msg( $linkmsg . 'link' )->text() ); # Calling either 'gotaccountlink' or 'nologinlink' 01089 01090 $template->set( 'link', $this->msg( $linkmsg )->rawParams( $link )->parse() ); 01091 } else { 01092 $template->set( 'link', '' ); 01093 } 01094 01095 $resetLink = $this->mType == 'signup' 01096 ? null 01097 : is_array( $wgPasswordResetRoutes ) && in_array( true, array_values( $wgPasswordResetRoutes ) ); 01098 01099 $template->set( 'header', '' ); 01100 $template->set( 'name', $this->mUsername ); 01101 $template->set( 'password', $this->mPassword ); 01102 $template->set( 'retype', $this->mRetype ); 01103 $template->set( 'email', $this->mEmail ); 01104 $template->set( 'realname', $this->mRealName ); 01105 $template->set( 'domain', $this->mDomain ); 01106 $template->set( 'reason', $this->mReason ); 01107 01108 $template->set( 'action', $titleObj->getLocalURL( $q ) ); 01109 $template->set( 'message', $msg ); 01110 $template->set( 'messagetype', $msgtype ); 01111 $template->set( 'createemail', $wgEnableEmail && $user->isLoggedIn() ); 01112 $template->set( 'userealname', !in_array( 'realname', $wgHiddenPrefs ) ); 01113 $template->set( 'useemail', $wgEnableEmail ); 01114 $template->set( 'emailrequired', $wgEmailConfirmToEdit ); 01115 $template->set( 'emailothers', $wgEnableUserEmail ); 01116 $template->set( 'canreset', $wgAuth->allowPasswordChange() ); 01117 $template->set( 'resetlink', $resetLink ); 01118 $template->set( 'canremember', ( $wgCookieExpiration > 0 ) ); 01119 $template->set( 'usereason', $user->isLoggedIn() ); 01120 $template->set( 'remember', $user->getOption( 'rememberpassword' ) || $this->mRemember ); 01121 $template->set( 'cansecurelogin', ( $wgSecureLogin === true ) ); 01122 $template->set( 'stickHTTPS', $this->mStickHTTPS ); 01123 01124 if ( $this->mType == 'signup' ) { 01125 if ( !self::getCreateaccountToken() ) { 01126 self::setCreateaccountToken(); 01127 } 01128 $template->set( 'token', self::getCreateaccountToken() ); 01129 } else { 01130 if ( !self::getLoginToken() ) { 01131 self::setLoginToken(); 01132 } 01133 $template->set( 'token', self::getLoginToken() ); 01134 } 01135 01136 # Prepare language selection links as needed 01137 if( $wgLoginLanguageSelector ) { 01138 $template->set( 'languages', $this->makeLanguageSelector() ); 01139 if( $this->mLanguage ) { 01140 $template->set( 'uselang', $this->mLanguage ); 01141 } 01142 } 01143 01144 // Use loginend-https for HTTPS requests if it's not blank, loginend otherwise 01145 // Ditto for signupend 01146 $usingHTTPS = WebRequest::detectProtocol() == 'https'; 01147 $loginendHTTPS = $this->msg( 'loginend-https' ); 01148 $signupendHTTPS = $this->msg( 'signupend-https' ); 01149 if ( $usingHTTPS && !$loginendHTTPS->isBlank() ) { 01150 $template->set( 'loginend', $loginendHTTPS->parse() ); 01151 } else { 01152 $template->set( 'loginend', $this->msg( 'loginend' )->parse() ); 01153 } 01154 if ( $usingHTTPS && !$signupendHTTPS->isBlank() ) { 01155 $template->set( 'signupend', $signupendHTTPS->parse() ); 01156 } else { 01157 $template->set( 'signupend', $this->msg( 'signupend' )->parse() ); 01158 } 01159 01160 // Give authentication and captcha plugins a chance to modify the form 01161 $wgAuth->modifyUITemplate( $template, $this->mType ); 01162 if ( $this->mType == 'signup' ) { 01163 wfRunHooks( 'UserCreateForm', array( &$template ) ); 01164 } else { 01165 wfRunHooks( 'UserLoginForm', array( &$template ) ); 01166 } 01167 01168 $out = $this->getOutput(); 01169 $out->disallowUserJs(); // just in case... 01170 $out->addTemplate( $template ); 01171 } 01172 01180 function showCreateOrLoginLink( &$user ) { 01181 if( $this->mType == 'signup' ) { 01182 return true; 01183 } elseif( $user->isAllowed( 'createaccount' ) ) { 01184 return true; 01185 } else { 01186 return false; 01187 } 01188 } 01189 01200 function hasSessionCookie() { 01201 global $wgDisableCookieCheck; 01202 return $wgDisableCookieCheck ? true : $this->getRequest()->checkSessionCookie(); 01203 } 01204 01209 public static function getLoginToken() { 01210 global $wgRequest; 01211 return $wgRequest->getSessionData( 'wsLoginToken' ); 01212 } 01213 01217 public static function setLoginToken() { 01218 global $wgRequest; 01219 // Generate a token directly instead of using $user->editToken() 01220 // because the latter reuses $_SESSION['wsEditToken'] 01221 $wgRequest->setSessionData( 'wsLoginToken', MWCryptRand::generateHex( 32 ) ); 01222 } 01223 01227 public static function clearLoginToken() { 01228 global $wgRequest; 01229 $wgRequest->setSessionData( 'wsLoginToken', null ); 01230 } 01231 01236 public static function getCreateaccountToken() { 01237 global $wgRequest; 01238 return $wgRequest->getSessionData( 'wsCreateaccountToken' ); 01239 } 01240 01244 public static function setCreateaccountToken() { 01245 global $wgRequest; 01246 $wgRequest->setSessionData( 'wsCreateaccountToken', MWCryptRand::generateHex( 32 ) ); 01247 } 01248 01252 public static function clearCreateaccountToken() { 01253 global $wgRequest; 01254 $wgRequest->setSessionData( 'wsCreateaccountToken', null ); 01255 } 01256 01260 function cookieRedirectCheck( $type ) { 01261 $titleObj = SpecialPage::getTitleFor( 'Userlogin' ); 01262 $query = array( 'wpCookieCheck' => $type ); 01263 if ( $this->mReturnTo !== '' ) { 01264 $query['returnto'] = $this->mReturnTo; 01265 $query['returntoquery'] = $this->mReturnToQuery; 01266 } 01267 $check = $titleObj->getFullURL( $query ); 01268 01269 $this->getOutput()->redirect( $check ); 01270 } 01271 01275 function onCookieRedirectCheck( $type ) { 01276 if ( !$this->hasSessionCookie() ) { 01277 if ( $type == 'new' ) { 01278 $this->mainLoginForm( $this->msg( 'nocookiesnew' )->parse() ); 01279 } elseif ( $type == 'login' ) { 01280 $this->mainLoginForm( $this->msg( 'nocookieslogin' )->parse() ); 01281 } else { 01282 # shouldn't happen 01283 $this->mainLoginForm( $this->msg( 'error' )->text() ); 01284 } 01285 } else { 01286 $this->successfulLogin(); 01287 } 01288 } 01289 01293 function throttleHit( $limit ) { 01294 $this->mainLoginForm( $this->msg( 'acct_creation_throttle_hit' )->numParams( $limit )->parse() ); 01295 } 01296 01303 function makeLanguageSelector() { 01304 $msg = $this->msg( 'loginlanguagelinks' )->inContentLanguage(); 01305 if( !$msg->isBlank() ) { 01306 $langs = explode( "\n", $msg->text() ); 01307 $links = array(); 01308 foreach( $langs as $lang ) { 01309 $lang = trim( $lang, '* ' ); 01310 $parts = explode( '|', $lang ); 01311 if ( count( $parts ) >= 2 ) { 01312 $links[] = $this->makeLanguageSelectorLink( $parts[0], trim( $parts[1] ) ); 01313 } 01314 } 01315 return count( $links ) > 0 ? $this->msg( 'loginlanguagelabel' )->rawParams( 01316 $this->getLanguage()->pipeList( $links ) )->escaped() : ''; 01317 } else { 01318 return ''; 01319 } 01320 } 01321 01330 function makeLanguageSelectorLink( $text, $lang ) { 01331 if( $this->getLanguage()->getCode() == $lang ) { 01332 // no link for currently used language 01333 return htmlspecialchars( $text ); 01334 } 01335 $query = array( 'uselang' => $lang ); 01336 if( $this->mType == 'signup' ) { 01337 $query['type'] = 'signup'; 01338 } 01339 if( $this->mReturnTo !== '' ) { 01340 $query['returnto'] = $this->mReturnTo; 01341 $query['returntoquery'] = $this->mReturnToQuery; 01342 } 01343 01344 $attr = array(); 01345 $targetLanguage = Language::factory( $lang ); 01346 $attr['lang'] = $attr['hreflang'] = $targetLanguage->getHtmlCode(); 01347 01348 return Linker::linkKnown( 01349 $this->getTitle(), 01350 htmlspecialchars( $text ), 01351 $attr, 01352 $query 01353 ); 01354 } 01355 }