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