MediaWiki  REL1_19
SpecialUserlogin.php
Go to the documentation of this file.
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, $wgRedirectOnLogin;
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->mReturnTo = $request->getVal( 'returnto' );
00097                 $this->mReturnToQuery = $request->getVal( 'returntoquery' );
00098                 $this->mCookieCheck = $request->getVal( 'wpCookieCheck' );
00099                 $this->mPosted = $request->wasPosted();
00100                 $this->mCreateaccount = $request->getCheck( 'wpCreateaccount' );
00101                 $this->mCreateaccountMail = $request->getCheck( 'wpCreateaccountMail' )
00102                                                                         && $wgEnableEmail;
00103                 $this->mLoginattempt = $request->getCheck( 'wpLoginattempt' );
00104                 $this->mAction = $request->getVal( 'action' );
00105                 $this->mRemember = $request->getCheck( 'wpRemember' );
00106                 $this->mStickHTTPS = $request->getCheck( 'wpStickHTTPS' );
00107                 $this->mLanguage = $request->getText( 'uselang' );
00108                 $this->mSkipCookieCheck = $request->getCheck( 'wpSkipCookieCheck' );
00109                 $this->mToken = ( $this->mType == 'signup' ) ? $request->getVal( 'wpCreateaccountToken' ) : $request->getVal( 'wpLoginToken' );
00110 
00111                 if ( $wgRedirectOnLogin ) {
00112                         $this->mReturnTo = $wgRedirectOnLogin;
00113                         $this->mReturnToQuery = '';
00114                 }
00115 
00116                 if( $wgEnableEmail ) {
00117                         $this->mEmail = $request->getText( 'wpEmail' );
00118                 } else {
00119                         $this->mEmail = '';
00120                 }
00121                 if( !in_array( 'realname', $wgHiddenPrefs ) ) {
00122                         $this->mRealName = $request->getText( 'wpRealName' );
00123                 } else {
00124                         $this->mRealName = '';
00125                 }
00126 
00127                 if( !$wgAuth->validDomain( $this->mDomain ) ) {
00128                         if ( isset( $_SESSION['wsDomain'] ) ) {
00129                                 $this->mDomain = $_SESSION['wsDomain'];
00130                         } else {
00131                                 $this->mDomain = 'invaliddomain';
00132                         }
00133                 }
00134                 $wgAuth->setDomain( $this->mDomain );
00135 
00136                 # When switching accounts, it sucks to get automatically logged out
00137                 $returnToTitle = Title::newFromText( $this->mReturnTo );
00138                 if( is_object( $returnToTitle ) && $returnToTitle->isSpecial( 'Userlogout' ) ) {
00139                         $this->mReturnTo = '';
00140                         $this->mReturnToQuery = '';
00141                 }
00142         }
00143 
00144         function getDescription() {
00145                 return $this->msg( $this->getUser()->isAllowed( 'createaccount' ) ?
00146                         'userlogin' : 'userloginnocreate' )->text();
00147         }
00148 
00149         public function execute( $par ) {
00150                 if ( session_id() == '' ) {
00151                         wfSetupSession();
00152                 }
00153 
00154                 $this->load();
00155                 $this->setHeaders();
00156 
00157                 if ( $par == 'signup' ) { # Check for [[Special:Userlogin/signup]]
00158                         $this->mType = 'signup';
00159                 }
00160 
00161                 if ( !is_null( $this->mCookieCheck ) ) {
00162                         $this->onCookieRedirectCheck( $this->mCookieCheck );
00163                         return;
00164                 } elseif( $this->mPosted ) {
00165                         if( $this->mCreateaccount ) {
00166                                 return $this->addNewAccount();
00167                         } elseif ( $this->mCreateaccountMail ) {
00168                                 return $this->addNewAccountMailPassword();
00169                         } elseif ( ( 'submitlogin' == $this->mAction ) || $this->mLoginattempt ) {
00170                                 return $this->processLogin();
00171                         }
00172                 }
00173                 $this->mainLoginForm( '' );
00174         }
00175 
00179         function addNewAccountMailPassword() {
00180                 if ( $this->mEmail == '' ) {
00181                         $this->mainLoginForm( $this->msg( 'noemailcreate' )->escaped() );
00182                         return;
00183                 }
00184 
00185                 $u = $this->addNewaccountInternal();
00186 
00187                 if ( $u == null ) {
00188                         return;
00189                 }
00190 
00191                 // Wipe the initial password and mail a temporary one
00192                 $u->setPassword( null );
00193                 $u->saveSettings();
00194                 $result = $this->mailPasswordInternal( $u, false, 'createaccount-title', 'createaccount-text' );
00195 
00196                 wfRunHooks( 'AddNewAccount', array( $u, true ) );
00197                 $u->addNewUserLogEntry( true, $this->mReason );
00198 
00199                 $out = $this->getOutput();
00200                 $out->setPageTitle( $this->msg( 'accmailtitle' ) );
00201 
00202                 if( !$result->isGood() ) {
00203                         $this->mainLoginForm( $this->msg( 'mailerror', $result->getWikiText() )->text() );
00204                 } else {
00205                         $out->addWikiMsg( 'accmailtext', $u->getName(), $u->getEmail() );
00206                         $out->returnToMain( false );
00207                 }
00208         }
00209 
00213         function addNewAccount() {
00214                 global $wgUser, $wgEmailAuthentication, $wgLoginLanguageSelector;
00215 
00216                 # Create the account and abort if there's a problem doing so
00217                 $u = $this->addNewAccountInternal();
00218                 if( $u == null ) {
00219                         return;
00220                 }
00221 
00222                 # If we showed up language selection links, and one was in use, be
00223                 # smart (and sensible) and save that language as the user's preference
00224                 if( $wgLoginLanguageSelector && $this->mLanguage ) {
00225                         $u->setOption( 'language', $this->mLanguage );
00226                 }
00227 
00228                 $out = $this->getOutput();
00229 
00230                 # Send out an email authentication message if needed
00231                 if( $wgEmailAuthentication && Sanitizer::validateEmail( $u->getEmail() ) ) {
00232                         $status = $u->sendConfirmationMail();
00233                         if( $status->isGood() ) {
00234                                 $out->addWikiMsg( 'confirmemail_oncreate' );
00235                         } else {
00236                                 $out->addWikiText( $status->getWikiText( 'confirmemail_sendfailed' ) );
00237                         }
00238                 }
00239 
00240                 # Save settings (including confirmation token)
00241                 $u->saveSettings();
00242 
00243                 # If not logged in, assume the new account as the current one and set
00244                 # session cookies then show a "welcome" message or a "need cookies"
00245                 # message as needed
00246                 if( $this->getUser()->isAnon() ) {
00247                         $u->setCookies();
00248                         $wgUser = $u;
00249                         // This should set it for OutputPage and the Skin
00250                         // which is needed or the personal links will be
00251                         // wrong.
00252                         $this->getContext()->setUser( $u );
00253                         wfRunHooks( 'AddNewAccount', array( $u, false ) );
00254                         $u->addNewUserLogEntry();
00255                         if( $this->hasSessionCookie() ) {
00256                                 return $this->successfulCreation();
00257                         } else {
00258                                 return $this->cookieRedirectCheck( 'new' );
00259                         }
00260                 } else {
00261                         # Confirm that the account was created
00262                         $out->setPageTitle( $this->msg( 'accountcreated' ) );
00263                         $out->addWikiMsg( 'accountcreatedtext', $u->getName() );
00264                         $out->returnToMain( false, $this->getTitle() );
00265                         wfRunHooks( 'AddNewAccount', array( $u, false ) );
00266                         $u->addNewUserLogEntry( false, $this->mReason );
00267                         return true;
00268                 }
00269         }
00270 
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() . ' (' . htmlspecialchars( $ip ) . ')' );
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 
00480         public function authenticateUserData() {
00481                 global $wgUser, $wgAuth;
00482 
00483                 $this->load();
00484 
00485                 if ( $this->mUsername == '' ) {
00486                         return self::NO_NAME;
00487                 }
00488 
00489                 // We require a login token to prevent login CSRF
00490                 // Handle part of this before incrementing the throttle so
00491                 // token-less login attempts don't count towards the throttle
00492                 // but wrong-token attempts do.
00493 
00494                 // If the user doesn't have a login token yet, set one.
00495                 if ( !self::getLoginToken() ) {
00496                         self::setLoginToken();
00497                         return self::NEED_TOKEN;
00498                 }
00499                 // If the user didn't pass a login token, tell them we need one
00500                 if ( !$this->mToken ) {
00501                         return self::NEED_TOKEN;
00502                 }
00503 
00504                 $throttleCount = self::incLoginThrottle( $this->mUsername );
00505                 if ( $throttleCount === true ) {
00506                         return self::THROTTLED;
00507                 }
00508 
00509                 // Validate the login token
00510                 if ( $this->mToken !== self::getLoginToken() ) {
00511                         return self::WRONG_TOKEN;
00512                 }
00513 
00514                 // Load the current user now, and check to see if we're logging in as
00515                 // the same name. This is necessary because loading the current user
00516                 // (say by calling getName()) calls the UserLoadFromSession hook, which
00517                 // potentially creates the user in the database. Until we load $wgUser,
00518                 // checking for user existence using User::newFromName($name)->getId() below
00519                 // will effectively be using stale data.
00520                 if ( $this->getUser()->getName() === $this->mUsername ) {
00521                         wfDebug( __METHOD__ . ": already logged in as {$this->mUsername}\n" );
00522                         return self::SUCCESS;
00523                 }
00524 
00525                 $this->mExtUser = ExternalUser::newFromName( $this->mUsername );
00526 
00527                 # TODO: Allow some magic here for invalid external names, e.g., let the
00528                 # user choose a different wiki name.
00529                 $u = User::newFromName( $this->mUsername );
00530                 if( !( $u instanceof User ) || !User::isUsableName( $u->getName() ) ) {
00531                         return self::ILLEGAL;
00532                 }
00533 
00534                 $isAutoCreated = false;
00535                 if ( 0 == $u->getID() ) {
00536                         $status = $this->attemptAutoCreate( $u );
00537                         if ( $status !== self::SUCCESS ) {
00538                                 return $status;
00539                         } else {
00540                                 $isAutoCreated = true;
00541                         }
00542                 } else {
00543                         global $wgExternalAuthType, $wgAutocreatePolicy;
00544                         if ( $wgExternalAuthType && $wgAutocreatePolicy != 'never'
00545                         && is_object( $this->mExtUser )
00546                         && $this->mExtUser->authenticate( $this->mPassword ) ) {
00547                                 # The external user and local user have the same name and
00548                                 # password, so we assume they're the same.
00549                                 $this->mExtUser->linkToLocal( $u->getID() );
00550                         }
00551 
00552                         $u->load();
00553                 }
00554 
00555                 // Give general extensions, such as a captcha, a chance to abort logins
00556                 $abort = self::ABORTED;
00557                 if( !wfRunHooks( 'AbortLogin', array( $u, $this->mPassword, &$abort, &$this->mAbortLoginErrorMsg ) ) ) {
00558                         return $abort;
00559                 }
00560 
00561                 global $wgBlockDisablesLogin;
00562                 if ( !$u->checkPassword( $this->mPassword ) ) {
00563                         if( $u->checkTemporaryPassword( $this->mPassword ) ) {
00564                                 // The e-mailed temporary password should not be used for actu-
00565                                 // al logins; that's a very sloppy habit, and insecure if an
00566                                 // attacker has a few seconds to click "search" on someone's o-
00567                                 // pen mail reader.
00568                                 //
00569                                 // Allow it to be used only to reset the password a single time
00570                                 // to a new value, which won't be in the user's e-mail ar-
00571                                 // chives.
00572                                 //
00573                                 // For backwards compatibility, we'll still recognize it at the
00574                                 // login form to minimize surprises for people who have been
00575                                 // logging in with a temporary password for some time.
00576                                 //
00577                                 // As a side-effect, we can authenticate the user's e-mail ad-
00578                                 // dress if it's not already done, since the temporary password
00579                                 // was sent via e-mail.
00580                                 if( !$u->isEmailConfirmed() ) {
00581                                         $u->confirmEmail();
00582                                         $u->saveSettings();
00583                                 }
00584 
00585                                 // At this point we just return an appropriate code/ indicating
00586                                 // that the UI should show a password reset form; bot inter-
00587                                 // faces etc will probably just fail cleanly here.
00588                                 $retval = self::RESET_PASS;
00589                         } else {
00590                                 $retval = ( $this->mPassword  == '' ) ? self::EMPTY_PASS : self::WRONG_PASS;
00591                         }
00592                 } elseif ( $wgBlockDisablesLogin && $u->isBlocked() ) {
00593                         // If we've enabled it, make it so that a blocked user cannot login
00594                         $retval = self::USER_BLOCKED;
00595                 } else {
00596                         $wgAuth->updateUser( $u );
00597                         $wgUser = $u;
00598                         // This should set it for OutputPage and the Skin
00599                         // which is needed or the personal links will be
00600                         // wrong.
00601                         $this->getContext()->setUser( $u );
00602 
00603                         // Please reset throttle for successful logins, thanks!
00604                         if ( $throttleCount ) {
00605                                 self::clearLoginThrottle( $this->mUsername );
00606                         }
00607 
00608                         if ( $isAutoCreated ) {
00609                                 // Must be run after $wgUser is set, for correct new user log
00610                                 wfRunHooks( 'AuthPluginAutoCreate', array( $u ) );
00611                         }
00612 
00613                         $retval = self::SUCCESS;
00614                 }
00615                 wfRunHooks( 'LoginAuthenticateAudit', array( $u, $this->mPassword, $retval ) );
00616                 return $retval;
00617         }
00618 
00625         public static function incLoginThrottle( $username ) {
00626                 global $wgPasswordAttemptThrottle, $wgMemc, $wgRequest;
00627                 $username = trim( $username ); // sanity
00628 
00629                 $throttleCount = 0;
00630                 if ( is_array( $wgPasswordAttemptThrottle ) ) {
00631                         $throttleKey = wfMemcKey( 'password-throttle', $wgRequest->getIP(), md5( $username ) );
00632                         $count = $wgPasswordAttemptThrottle['count'];
00633                         $period = $wgPasswordAttemptThrottle['seconds'];
00634 
00635                         $throttleCount = $wgMemc->get( $throttleKey );
00636                         if ( !$throttleCount ) {
00637                                 $wgMemc->add( $throttleKey, 1, $period ); // start counter
00638                         } elseif ( $throttleCount < $count ) {
00639                                 $wgMemc->incr( $throttleKey );
00640                         } elseif ( $throttleCount >= $count ) {
00641                                 return true;
00642                         }
00643                 }
00644 
00645                 return $throttleCount;
00646         }
00647 
00653         public static function clearLoginThrottle( $username ) {
00654                 global $wgMemc, $wgRequest;
00655                 $username = trim( $username ); // sanity
00656 
00657                 $throttleKey = wfMemcKey( 'password-throttle', $wgRequest->getIP(), md5( $username ) );
00658                 $wgMemc->delete( $throttleKey );
00659         }
00660 
00669         function attemptAutoCreate( $user ) {
00670                 global $wgAuth, $wgAutocreatePolicy;
00671 
00672                 if ( $this->getUser()->isBlockedFromCreateAccount() ) {
00673                         wfDebug( __METHOD__ . ": user is blocked from account creation\n" );
00674                         return self::CREATE_BLOCKED;
00675                 }
00676 
00682                 if ( $this->mExtUser ) {
00683                         # mExtUser is neither null nor false, so use the new ExternalAuth
00684                         # system.
00685                         if ( $wgAutocreatePolicy == 'never' ) {
00686                                 return self::NOT_EXISTS;
00687                         }
00688                         if ( !$this->mExtUser->authenticate( $this->mPassword ) ) {
00689                                 return self::WRONG_PLUGIN_PASS;
00690                         }
00691                 } else {
00692                         # Old AuthPlugin.
00693                         if ( !$wgAuth->autoCreate() ) {
00694                                 return self::NOT_EXISTS;
00695                         }
00696                         if ( !$wgAuth->userExists( $user->getName() ) ) {
00697                                 wfDebug( __METHOD__ . ": user does not exist\n" );
00698                                 return self::NOT_EXISTS;
00699                         }
00700                         if ( !$wgAuth->authenticate( $user->getName(), $this->mPassword ) ) {
00701                                 wfDebug( __METHOD__ . ": \$wgAuth->authenticate() returned false, aborting\n" );
00702                                 return self::WRONG_PLUGIN_PASS;
00703                         }
00704                 }
00705 
00706                 $abortError = '';
00707                 if( !wfRunHooks( 'AbortAutoAccount', array( $user, &$abortError ) ) ) {
00708                         // Hook point to add extra creation throttles and blocks
00709                         wfDebug( "LoginForm::attemptAutoCreate: a hook blocked creation: $abortError\n" );
00710                         $this->mAbortLoginErrorMsg = $abortError;
00711                         return self::ABORTED;
00712                 }
00713 
00714                 wfDebug( __METHOD__ . ": creating account\n" );
00715                 $this->initUser( $user, true );
00716                 return self::SUCCESS;
00717         }
00718 
00719         function processLogin() {
00720                 global $wgMemc, $wgLang;
00721 
00722                 switch ( $this->authenticateUserData() ) {
00723                         case self::SUCCESS:
00724                                 # We've verified now, update the real record
00725                                 $user = $this->getUser();
00726                                 if( (bool)$this->mRemember != (bool)$user->getOption( 'rememberpassword' ) ) {
00727                                         $user->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 );
00728                                         $user->saveSettings();
00729                                 } else {
00730                                         $user->invalidateCache();
00731                                 }
00732                                 $user->setCookies();
00733                                 self::clearLoginToken();
00734 
00735                                 // Reset the throttle
00736                                 $request = $this->getRequest();
00737                                 $key = wfMemcKey( 'password-throttle', $request->getIP(), md5( $this->mUsername ) );
00738                                 $wgMemc->delete( $key );
00739 
00740                                 if( $this->hasSessionCookie() || $this->mSkipCookieCheck ) {
00741                                         /* Replace the language object to provide user interface in
00742                                          * correct language immediately on this first page load.
00743                                          */
00744                                         $code = $request->getVal( 'uselang', $user->getOption( 'language' ) );
00745                                         $userLang = Language::factory( $code );
00746                                         $wgLang = $userLang;
00747                                         $this->getContext()->setLanguage( $userLang );
00748                                         // Reset SessionID on Successful login (bug 40995)
00749                                         $this->renewSessionId();
00750                                         return $this->successfulLogin();
00751                                 } else {
00752                                         return $this->cookieRedirectCheck( 'login' );
00753                                 }
00754                                 break;
00755 
00756                         case self::NEED_TOKEN:
00757                                 $this->mainLoginForm( $this->msg( 'nocookiesforlogin' )->parse() );
00758                                 break;
00759                         case self::WRONG_TOKEN:
00760                                 $this->mainLoginForm( $this->msg( 'sessionfailure' )->text() );
00761                                 break;
00762                         case self::NO_NAME:
00763                         case self::ILLEGAL:
00764                                 $this->mainLoginForm( $this->msg( 'noname' )->text() );
00765                                 break;
00766                         case self::WRONG_PLUGIN_PASS:
00767                                 $this->mainLoginForm( $this->msg( 'wrongpassword' )->text() );
00768                                 break;
00769                         case self::NOT_EXISTS:
00770                                 if( $this->getUser()->isAllowed( 'createaccount' ) ) {
00771                                         $this->mainLoginForm( $this->msg( 'nosuchuser',
00772                                            wfEscapeWikiText( $this->mUsername ) )->parse() );
00773                                 } else {
00774                                         $this->mainLoginForm( $this->msg( 'nosuchusershort',
00775                                                 wfEscapeWikiText( $this->mUsername ) )->text() );
00776                                 }
00777                                 break;
00778                         case self::WRONG_PASS:
00779                                 $this->mainLoginForm( $this->msg( 'wrongpassword' )->text() );
00780                                 break;
00781                         case self::EMPTY_PASS:
00782                                 $this->mainLoginForm( $this->msg( 'wrongpasswordempty' )->text() );
00783                                 break;
00784                         case self::RESET_PASS:
00785                                 $this->resetLoginForm( $this->msg( 'resetpass_announce' )->text() );
00786                                 break;
00787                         case self::CREATE_BLOCKED:
00788                                 $this->userBlockedMessage( $this->getUser()->mBlock );
00789                                 break;
00790                         case self::THROTTLED:
00791                                 $this->mainLoginForm( $this->msg( 'login-throttled' )->text() );
00792                                 break;
00793                         case self::USER_BLOCKED:
00794                                 $this->mainLoginForm( $this->msg( 'login-userblocked',
00795                                         $this->mUsername )->escaped() );
00796                                 break;
00797                         case self::ABORTED:
00798                                 $this->mainLoginForm( $this->msg( $this->mAbortLoginErrorMsg )->text() );
00799                                 break;
00800                         default:
00801                                 throw new MWException( 'Unhandled case value' );
00802                 }
00803         }
00804 
00805         function resetLoginForm( $error ) {
00806                 $this->getOutput()->addHTML( Xml::element('p', array( 'class' => 'error' ), $error ) );
00807                 $reset = new SpecialChangePassword();
00808                 $reset->setContext( $this->getContext() );
00809                 $reset->execute( null );
00810         }
00811 
00819         function mailPasswordInternal( $u, $throttle = true, $emailTitle = 'passwordremindertitle', $emailText = 'passwordremindertext' ) {
00820                 global $wgServer, $wgScript, $wgNewPasswordExpiry;
00821 
00822                 if ( $u->getEmail() == '' ) {
00823                         return Status::newFatal( 'noemail', $u->getName() );
00824                 }
00825                 $ip = $this->getRequest()->getIP();
00826                 if( !$ip ) {
00827                         return Status::newFatal( 'badipaddress' );
00828                 }
00829 
00830                 $currentUser = $this->getUser();
00831                 wfRunHooks( 'User::mailPasswordInternal', array( &$currentUser, &$ip, &$u ) );
00832 
00833                 $np = $u->randomPassword();
00834                 $u->setNewpassword( $np, $throttle );
00835                 $u->saveSettings();
00836                 $userLanguage = $u->getOption( 'language' );
00837                 $m = $this->msg( $emailText, $ip, $u->getName(), $np, $wgServer . $wgScript,
00838                         round( $wgNewPasswordExpiry / 86400 ) )->inLanguage( $userLanguage )->text();
00839                 $result = $u->sendMail( $this->msg( $emailTitle )->inLanguage( $userLanguage )->text(), $m );
00840 
00841                 return $result;
00842         }
00843 
00844 
00855         function successfulLogin() {
00856                 # Run any hooks; display injected HTML if any, else redirect
00857                 $currentUser = $this->getUser();
00858                 $injected_html = '';
00859                 wfRunHooks( 'UserLoginComplete', array( &$currentUser, &$injected_html ) );
00860 
00861                 if( $injected_html !== '' ) {
00862                         $this->displaySuccessfulLogin( 'loginsuccess', $injected_html );
00863                 } else {
00864                         $titleObj = Title::newFromText( $this->mReturnTo );
00865                         if ( !$titleObj instanceof Title ) {
00866                                 $titleObj = Title::newMainPage();
00867                         }
00868                         $redirectUrl = $titleObj->getFullURL( $this->mReturnToQuery );
00869                         global $wgSecureLogin;
00870                         if( $wgSecureLogin && !$this->mStickHTTPS ) {
00871                                 $redirectUrl = preg_replace( '/^https:/', 'http:', $redirectUrl );
00872                         }
00873                         $this->getOutput()->redirect( $redirectUrl );
00874                 }
00875         }
00876 
00883         function successfulCreation() {
00884                 # Run any hooks; display injected HTML
00885                 $currentUser = $this->getUser();
00886                 $injected_html = '';
00887                 $welcome_creation_msg = 'welcomecreation';
00888 
00889                 wfRunHooks( 'UserLoginComplete', array( &$currentUser, &$injected_html ) );
00890 
00896                 wfRunHooks( 'BeforeWelcomeCreation', array( &$welcome_creation_msg, &$injected_html ) );
00897 
00898                 $this->displaySuccessfulLogin( $welcome_creation_msg, $injected_html );
00899         }
00900 
00904         private function displaySuccessfulLogin( $msgname, $injected_html ) {
00905                 $out = $this->getOutput();
00906                 $out->setPageTitle( $this->msg( 'loginsuccesstitle' ) );
00907                 if( $msgname ){
00908                         $out->addWikiMsg( $msgname, wfEscapeWikiText( $this->getUser()->getName() ) );
00909                 }
00910 
00911                 $out->addHTML( $injected_html );
00912 
00913                 if ( !empty( $this->mReturnTo ) ) {
00914                         $out->returnToMain( null, $this->mReturnTo, $this->mReturnToQuery );
00915                 } else {
00916                         $out->returnToMain( null );
00917                 }
00918         }
00919 
00927         function userBlockedMessage( Block $block ) {
00928                 # Let's be nice about this, it's likely that this feature will be used
00929                 # for blocking large numbers of innocent people, e.g. range blocks on
00930                 # schools. Don't blame it on the user. There's a small chance that it
00931                 # really is the user's fault, i.e. the username is blocked and they
00932                 # haven't bothered to log out before trying to create an account to
00933                 # evade it, but we'll leave that to their guilty conscience to figure
00934                 # out.
00935 
00936                 $out = $this->getOutput();
00937                 $out->setPageTitle( $this->msg( 'cantcreateaccounttitle' ) );
00938 
00939                 $block_reason = $block->mReason;
00940                 if ( strval( $block_reason ) === '' ) {
00941                         $block_reason = $this->msg( 'blockednoreason' )->text();
00942                 }
00943 
00944                 $out->addWikiMsg(
00945                         'cantcreateaccount-text',
00946                         $block->getTarget(),
00947                         $block_reason,
00948                         $block->getByName()
00949                 );
00950 
00951                 $out->returnToMain( false );
00952         }
00953 
00957         function mainLoginForm( $msg, $msgtype = 'error' ) {
00958                 global $wgEnableEmail, $wgEnableUserEmail;
00959                 global $wgHiddenPrefs, $wgLoginLanguageSelector;
00960                 global $wgAuth, $wgEmailConfirmToEdit, $wgCookieExpiration;
00961                 global $wgSecureLogin, $wgPasswordResetRoutes;
00962 
00963                 $titleObj = $this->getTitle();
00964                 $user = $this->getUser();
00965 
00966                 if ( $this->mType == 'signup' ) {
00967                         // Block signup here if in readonly. Keeps user from
00968                         // going through the process (filling out data, etc)
00969                         // and being informed later.
00970                         $permErrors = $titleObj->getUserPermissionsErrors( 'createaccount', $user, true );
00971                         if ( count( $permErrors ) ) {
00972                                 throw new PermissionsError( 'createaccount', $permErrors );
00973                         } elseif ( $user->isBlockedFromCreateAccount() ) {
00974                                 $this->userBlockedMessage( $user->isBlockedFromCreateAccount() );
00975                                 return;
00976                         } elseif ( wfReadOnly() ) {
00977                                 throw new ReadOnlyError;
00978                         }
00979                 }
00980 
00981                 if ( $this->mUsername == '' ) {
00982                         if ( $user->isLoggedIn() ) {
00983                                 $this->mUsername = $user->getName();
00984                         } else {
00985                                 $this->mUsername = $this->getRequest()->getCookie( 'UserName' );
00986                         }
00987                 }
00988 
00989                 if ( $this->mType == 'signup' ) {
00990                         $template = new UsercreateTemplate();
00991                         $q = 'action=submitlogin&type=signup';
00992                         $linkq = 'type=login';
00993                         $linkmsg = 'gotaccount';
00994                 } else {
00995                         $template = new UserloginTemplate();
00996                         $q = 'action=submitlogin&type=login';
00997                         $linkq = 'type=signup';
00998                         $linkmsg = 'nologin';
00999                 }
01000 
01001                 if ( !empty( $this->mReturnTo ) ) {
01002                         $returnto = '&returnto=' . wfUrlencode( $this->mReturnTo );
01003                         if ( !empty( $this->mReturnToQuery ) ) {
01004                                 $returnto .= '&returntoquery=' .
01005                                         wfUrlencode( $this->mReturnToQuery );
01006                         }
01007                         $q .= $returnto;
01008                         $linkq .= $returnto;
01009                 }
01010 
01011                 # Don't show a "create account" link if the user can't
01012                 if( $this->showCreateOrLoginLink( $user ) ) {
01013                         # Pass any language selection on to the mode switch link
01014                         if( $wgLoginLanguageSelector && $this->mLanguage ) {
01015                                 $linkq .= '&uselang=' . $this->mLanguage;
01016                         }
01017                         $link = Html::element( 'a', array( 'href' => $titleObj->getLocalURL( $linkq ) ),
01018                                 $this->msg( $linkmsg . 'link' )->text() ); # Calling either 'gotaccountlink' or 'nologinlink'
01019 
01020                         $template->set( 'link', $this->msg( $linkmsg )->rawParams( $link )->parse() );
01021                 } else {
01022                         $template->set( 'link', '' );
01023                 }
01024 
01025                 $resetLink = $this->mType == 'signup'
01026                         ? null
01027                         : is_array( $wgPasswordResetRoutes ) && in_array( true, array_values( $wgPasswordResetRoutes ) );
01028 
01029                 $template->set( 'header', '' );
01030                 $template->set( 'name', $this->mUsername );
01031                 $template->set( 'password', $this->mPassword );
01032                 $template->set( 'retype', $this->mRetype );
01033                 $template->set( 'email', $this->mEmail );
01034                 $template->set( 'realname', $this->mRealName );
01035                 $template->set( 'domain', $this->mDomain );
01036                 $template->set( 'reason', $this->mReason );
01037 
01038                 $template->set( 'action', $titleObj->getLocalURL( $q ) );
01039                 $template->set( 'message', $msg );
01040                 $template->set( 'messagetype', $msgtype );
01041                 $template->set( 'createemail', $wgEnableEmail && $user->isLoggedIn() );
01042                 $template->set( 'userealname', !in_array( 'realname', $wgHiddenPrefs ) );
01043                 $template->set( 'useemail', $wgEnableEmail );
01044                 $template->set( 'emailrequired', $wgEmailConfirmToEdit );
01045                 $template->set( 'emailothers', $wgEnableUserEmail );
01046                 $template->set( 'canreset', $wgAuth->allowPasswordChange() );
01047                 $template->set( 'resetlink', $resetLink );
01048                 $template->set( 'canremember', ( $wgCookieExpiration > 0 ) );
01049                 $template->set( 'usereason', $user->isLoggedIn() );
01050                 $template->set( 'remember', $user->getOption( 'rememberpassword' ) || $this->mRemember );
01051                 $template->set( 'cansecurelogin', ( $wgSecureLogin === true ) );
01052                 $template->set( 'stickHTTPS', $this->mStickHTTPS );
01053 
01054                 if ( $this->mType == 'signup' ) {
01055                         if ( !self::getCreateaccountToken() ) {
01056                                 self::setCreateaccountToken();
01057                         }
01058                         $template->set( 'token', self::getCreateaccountToken() );
01059                 } else {
01060                         if ( !self::getLoginToken() ) {
01061                                 self::setLoginToken();
01062                         }
01063                         $template->set( 'token', self::getLoginToken() );
01064                 }
01065 
01066                 # Prepare language selection links as needed
01067                 if( $wgLoginLanguageSelector ) {
01068                         $template->set( 'languages', $this->makeLanguageSelector() );
01069                         if( $this->mLanguage ) {
01070                                 $template->set( 'uselang', $this->mLanguage );
01071                         }
01072                 }
01073 
01074                 // Use loginend-https for HTTPS requests if it's not blank, loginend otherwise
01075                 // Ditto for signupend
01076                 $usingHTTPS = WebRequest::detectProtocol() == 'https';
01077                 $loginendHTTPS = $this->msg( 'loginend-https' );
01078                 $signupendHTTPS = $this->msg( 'signupend-https' );
01079                 if ( $usingHTTPS && !$loginendHTTPS->isBlank() ) {
01080                         $template->set( 'loginend', $loginendHTTPS->parse() );
01081                 } else {
01082                         $template->set( 'loginend', $this->msg( 'loginend' )->parse() );
01083                 }
01084                 if ( $usingHTTPS && !$signupendHTTPS->isBlank() ) {
01085                         $template->set( 'signupend', $signupendHTTPS->parse() );
01086                 } else {
01087                         $template->set( 'signupend', $this->msg( 'signupend' )->parse() );
01088                 }
01089 
01090                 // Give authentication and captcha plugins a chance to modify the form
01091                 $wgAuth->modifyUITemplate( $template, $this->mType );
01092                 if ( $this->mType == 'signup' ) {
01093                         wfRunHooks( 'UserCreateForm', array( &$template ) );
01094                 } else {
01095                         wfRunHooks( 'UserLoginForm', array( &$template ) );
01096                 }
01097 
01098                 $out = $this->getOutput();
01099                 $out->disallowUserJs(); // just in case...
01100                 $out->addTemplate( $template );
01101         }
01102 
01110         function showCreateOrLoginLink( &$user ) {
01111                 if( $this->mType == 'signup' ) {
01112                         return true;
01113                 } elseif( $user->isAllowed( 'createaccount' ) ) {
01114                         return true;
01115                 } else {
01116                         return false;
01117                 }
01118         }
01119 
01129         function hasSessionCookie() {
01130                 global $wgDisableCookieCheck;
01131                 return $wgDisableCookieCheck ? true : $this->getRequest()->checkSessionCookie();
01132         }
01133 
01137         public static function getLoginToken() {
01138                 global $wgRequest;
01139                 return $wgRequest->getSessionData( 'wsLoginToken' );
01140         }
01141 
01145         public static function setLoginToken() {
01146                 global $wgRequest;
01147                 // Generate a token directly instead of using $user->editToken()
01148                 // because the latter reuses $_SESSION['wsEditToken']
01149                 $wgRequest->setSessionData( 'wsLoginToken', MWCryptRand::generateHex( 32 ) );
01150         }
01151 
01155         public static function clearLoginToken() {
01156                 global $wgRequest;
01157                 $wgRequest->setSessionData( 'wsLoginToken', null );
01158         }
01159 
01163         public static function getCreateaccountToken() {
01164                 global $wgRequest;
01165                 return $wgRequest->getSessionData( 'wsCreateaccountToken' );
01166         }
01167 
01171         public static function setCreateaccountToken() {
01172                 global $wgRequest;
01173                 $wgRequest->setSessionData( 'wsCreateaccountToken', MWCryptRand::generateHex( 32 ) );
01174         }
01175 
01179         public static function clearCreateaccountToken() {
01180                 global $wgRequest;
01181                 $wgRequest->setSessionData( 'wsCreateaccountToken', null );
01182         }
01183 
01187         private function renewSessionId() {
01188                 if ( wfCheckEntropy() ) {
01189                         session_regenerate_id( false );
01190                 } else {
01191                         //If we don't trust PHP's entropy, we have to replace the session manually
01192                         $tmp = $_SESSION;
01193                         session_unset();
01194                         session_write_close();
01195                         session_id( MWCryptRand::generateHex( 32 ) );
01196                         session_start();
01197                         $_SESSION = $tmp;
01198                 }
01199         }
01200 
01204         function cookieRedirectCheck( $type ) {
01205                 $titleObj = SpecialPage::getTitleFor( 'Userlogin' );
01206                 $query = array( 'wpCookieCheck' => $type );
01207                 if ( $this->mReturnTo ) {
01208                         $query['returnto'] = $this->mReturnTo;
01209                 }
01210                 $check = $titleObj->getFullURL( $query );
01211 
01212                 return $this->getOutput()->redirect( $check );
01213         }
01214 
01218         function onCookieRedirectCheck( $type ) {
01219                 if ( !$this->hasSessionCookie() ) {
01220                         if ( $type == 'new' ) {
01221                                 return $this->mainLoginForm( $this->msg( 'nocookiesnew' )->parse() );
01222                         } elseif ( $type == 'login' ) {
01223                                 return $this->mainLoginForm( $this->msg( 'nocookieslogin' )->parse() );
01224                         } else {
01225                                 # shouldn't happen
01226                                 return $this->mainLoginForm( $this->msg( 'error' )->text() );
01227                         }
01228                 } else {
01229                         return $this->successfulLogin();
01230                 }
01231         }
01232 
01236         function throttleHit( $limit ) {
01237                 $this->mainLoginForm( $this->msg( 'acct_creation_throttle_hit' )->numParams( $limit )->parse() );
01238         }
01239 
01246         function makeLanguageSelector() {
01247                 $msg = $this->msg( 'loginlanguagelinks' )->inContentLanguage();
01248                 if( !$msg->isBlank() ) {
01249                         $langs = explode( "\n", $msg->text() );
01250                         $links = array();
01251                         foreach( $langs as $lang ) {
01252                                 $lang = trim( $lang, '* ' );
01253                                 $parts = explode( '|', $lang );
01254                                 if ( count( $parts ) >= 2 ) {
01255                                         $links[] = $this->makeLanguageSelectorLink( $parts[0], trim( $parts[1] ) );
01256                                 }
01257                         }
01258                         return count( $links ) > 0 ? $this->msg( 'loginlanguagelabel' )->rawParams(
01259                                 $this->getLanguage()->pipeList( $links ) )->escaped() : '';
01260                 } else {
01261                         return '';
01262                 }
01263         }
01264 
01272         function makeLanguageSelectorLink( $text, $lang ) {
01273                 $attr = array( 'uselang' => $lang );
01274                 if( $this->mType == 'signup' ) {
01275                         $attr['type'] = 'signup';
01276                 }
01277                 if( $this->mReturnTo ) {
01278                         $attr['returnto'] = $this->mReturnTo;
01279                 }
01280                 return Linker::linkKnown(
01281                         $this->getTitle(),
01282                         htmlspecialchars( $text ),
01283                         array(),
01284                         $attr
01285                 );
01286         }
01287 }