X-Git-Url: https://scripts.mit.edu/gitweb/autoinstallsdev/mediawiki.git/blobdiff_plain/d7967d5e4460e08b6b258307afbca0596b18a3dd:/includes/SpecialUserlogin.php..refs/tags/mediawiki-1.14.0:/includes/specials/SpecialUserlogin.php diff --git a/includes/SpecialUserlogin.php b/includes/specials/SpecialUserlogin.php similarity index 58% rename from includes/SpecialUserlogin.php rename to includes/specials/SpecialUserlogin.php index f358c1fd..6a4da7a4 100644 --- a/includes/SpecialUserlogin.php +++ b/includes/specials/SpecialUserlogin.php @@ -1,25 +1,25 @@ execute(); } /** * implements Special:Login - * @addtogroup SpecialPage + * @ingroup SpecialPage */ class LoginForm { @@ -32,20 +32,22 @@ class LoginForm { const EMPTY_PASS = 6; const RESET_PASS = 7; const ABORTED = 8; + const CREATE_BLOCKED = 9; + const THROTTLED = 10; var $mName, $mPassword, $mRetype, $mReturnTo, $mCookieCheck, $mPosted; var $mAction, $mCreateaccount, $mCreateaccountMail, $mMailmypassword; - var $mLoginattempt, $mRemember, $mEmail, $mDomain, $mLanguage; + var $mLoginattempt, $mRemember, $mEmail, $mDomain, $mLanguage, $mSkipCookieCheck; /** * Constructor * @param WebRequest $request A WebRequest object passed by reference */ - function LoginForm( &$request ) { + function LoginForm( &$request, $par = '' ) { global $wgLang, $wgAllowRealName, $wgEnableEmail; global $wgAuth; - $this->mType = $request->getText( 'type' ); + $this->mType = ( $par == 'signup' ) ? $par : $request->getText( 'type' ); # Check for [[Special:Userlogin/signup]] $this->mName = $request->getText( 'wpName' ); $this->mPassword = $request->getText( 'wpPassword' ); $this->mRetype = $request->getText( 'wpRetype' ); @@ -62,6 +64,7 @@ class LoginForm { $this->mAction = $request->getVal( 'action' ); $this->mRemember = $request->getCheck( 'wpRemember' ); $this->mLanguage = $request->getText( 'uselang' ); + $this->mSkipCookieCheck = $request->getCheck( 'wpSkipCookieCheck' ); if( $wgEnableEmail ) { $this->mEmail = $request->getText( 'wpEmail' ); @@ -123,18 +126,19 @@ class LoginForm { // Wipe the initial password and mail a temporary one $u->setPassword( null ); $u->saveSettings(); - $result = $this->mailPasswordInternal( $u, false ); + $result = $this->mailPasswordInternal( $u, false, 'createaccount-title', 'createaccount-text' ); - wfRunHooks( 'AddNewAccount', array( $u ) ); + wfRunHooks( 'AddNewAccount', array( $u, true ) ); + $u->addNewUserLogEntry(); $wgOut->setPageTitle( wfMsg( 'accmailtitle' ) ); - $wgOut->setRobotpolicy( 'noindex,nofollow' ); + $wgOut->setRobotPolicy( 'noindex,nofollow' ); $wgOut->setArticleRelated( false ); if( WikiError::isError( $result ) ) { $this->mainLoginForm( wfMsg( 'mailerror', $result->getMessage() ) ); } else { - $wgOut->addWikiText( wfMsg( 'accmailtext', $u->getName(), $u->getEmail() ) ); + $wgOut->addWikiMsg( 'accmailtext', $u->getName(), $u->getEmail() ); $wgOut->returnToMain( false ); } $u = 0; @@ -158,26 +162,30 @@ class LoginForm { if( $wgLoginLanguageSelector && $this->mLanguage ) $u->setOption( 'language', $this->mLanguage ); - # Save user settings and send out an email authentication message if needed - $u->saveSettings(); + # Send out an email authentication message if needed if( $wgEmailAuthentication && User::isValidEmailAddr( $u->getEmail() ) ) { global $wgOut; $error = $u->sendConfirmationMail(); if( WikiError::isError( $error ) ) { - $wgOut->addWikiText( wfMsg( 'confirmemail_sendfailed', $error->getMessage() ) ); + $wgOut->addWikiMsg( 'confirmemail_sendfailed', $error->getMessage() ); } else { - $wgOut->addWikiText( wfMsg( 'confirmemail_oncreate' ) ); + $wgOut->addWikiMsg( 'confirmemail_oncreate' ); } } - # If not logged in, assume the new account as the current one and set session cookies - # then show a "welcome" message or a "need cookies" message as needed + # Save settings (including confirmation token) + $u->saveSettings(); + + # If not logged in, assume the new account as the current one and set + # session cookies then show a "welcome" message or a "need cookies" + # message as needed if( $wgUser->isAnon() ) { $wgUser = $u; $wgUser->setCookies(); wfRunHooks( 'AddNewAccount', array( $wgUser ) ); + $wgUser->addNewUserLogEntry(); if( $this->hasSessionCookie() ) { - return $this->successfulLogin( wfMsg( 'welcomecreation', $wgUser->getName() ), false ); + return $this->successfulCreation(); } else { return $this->cookieRedirectCheck( 'new' ); } @@ -188,9 +196,10 @@ class LoginForm { $wgOut->setPageTitle( wfMsgHtml( 'accountcreated' ) ); $wgOut->setArticleRelated( false ); $wgOut->setRobotPolicy( 'noindex,nofollow' ); - $wgOut->addHtml( wfMsgWikiHtml( 'accountcreatedtext', $u->getName() ) ); - $wgOut->returnToMain( $self->getPrefixedText() ); + $wgOut->addHTML( wfMsgWikiHtml( 'accountcreatedtext', $u->getName() ) ); + $wgOut->returnToMain( false, $self ); wfRunHooks( 'AddNewAccount', array( $u ) ); + $u->addNewUserLogEntry(); return true; } } @@ -203,6 +212,7 @@ class LoginForm { global $wgEnableSorbs, $wgProxyWhitelist; global $wgMemc, $wgAccountCreationThrottle; global $wgAuth, $wgMinimalPasswordLength; + global $wgEmailConfirmToEdit; // If the user passes an invalid domain, something is fishy if( !$wgAuth->validDomain( $this->mDomain ) ) { @@ -210,12 +220,11 @@ class LoginForm { return false; } - // If we are not allowing users to login locally, we should - // be checking to see if the user is actually able to - // authenticate to the authentication server before they - // create an account (otherwise, they can create a local account - // and login as any domain user). We only need to check this for - // domains that aren't local. + // If we are not allowing users to login locally, we should be checking + // to see if the user is actually able to authenticate to the authenti- + // cation server before they create an account (otherwise, they can + // create a local account and login as any domain user). We only need + // to check this for domains that aren't local. if( 'local' != $this->mDomain && '' != $this->mDomain ) { if( !$wgAuth->canCreateAccounts() && ( !$wgAuth->userExists( $this->mName ) || !$wgAuth->authenticate( $this->mName, $this->mPassword ) ) ) { $this->mainLoginForm( wfMsg( 'wrongpassword' ) ); @@ -228,10 +237,13 @@ class LoginForm { return false; } - #Â Check anonymous user ($wgUser) limitations : - if (!$wgUser->isAllowedToCreateAccount()) { + # Check permissions + if ( !$wgUser->isAllowed( 'createaccount' ) ) { $this->userNotPrivilegedMessage(); return false; + } elseif ( $wgUser->isBlockedFromCreateAccount() ) { + $this->userBlockedMessage(); + return false; } $ip = wfGetIP(); @@ -260,13 +272,32 @@ class LoginForm { return false; } + # check for minimal password length if ( !$u->isValidPassword( $this->mPassword ) ) { - $this->mainLoginForm( wfMsg( 'passwordtooshort', $wgMinimalPasswordLength ) ); + if ( !$this->mCreateaccountMail ) { + $this->mainLoginForm( wfMsgExt( 'passwordtooshort', array( 'parsemag' ), $wgMinimalPasswordLength ) ); + return false; + } else { + # do not force a password for account creation by email + # set invalid password, it will be replaced later by a random generated password + $this->mPassword = null; + } + } + + # if you need a confirmed email address to edit, then obviously you + # need an email address. + if ( $wgEmailConfirmToEdit && empty( $this->mEmail ) ) { + $this->mainLoginForm( wfMsg( 'noemailtitle' ) ); return false; } - - # Set some additional data so the AbortNewAccount hook can be - # used for more than just username validation + + if( !empty( $this->mEmail ) && !User::isValidEmailAddr( $this->mEmail ) ) { + $this->mainLoginForm( wfMsg( 'invalidemailaddress' ) ); + return false; + } + + # Set some additional data so the AbortNewAccount hook can be used for + # more than just username validation $u->setEmail( $this->mEmail ); $u->setRealName( $this->mRealName ); @@ -280,14 +311,15 @@ class LoginForm { if ( $wgAccountCreationThrottle && $wgUser->isPingLimitable() ) { $key = wfMemcKey( 'acctcreate', 'ip', $ip ); - $value = $wgMemc->incr( $key ); + $value = $wgMemc->get( $key ); if ( !$value ) { - $wgMemc->set( $key, 1, 86400 ); + $wgMemc->set( $key, 0, 86400 ); } - if ( $value > $wgAccountCreationThrottle ) { + if ( $value >= $wgAccountCreationThrottle ) { $this->throttleHit( $wgAccountCreationThrottle ); return false; } + $wgMemc->incr( $key ); } if( !$wgAuth->addUser( $u, $this->mPassword, $this->mEmail, $this->mRealName ) ) { @@ -346,25 +378,48 @@ class LoginForm { if ( '' == $this->mName ) { return self::NO_NAME; } + + global $wgPasswordAttemptThrottle; + + $throttleCount=0; + if ( is_array($wgPasswordAttemptThrottle) ) { + $throttleKey = wfMemcKey( 'password-throttle', wfGetIP(), md5( $this->mName ) ); + $count = $wgPasswordAttemptThrottle['count']; + $period = $wgPasswordAttemptThrottle['seconds']; + + global $wgMemc; + $throttleCount = $wgMemc->get($throttleKey); + if ( !$throttleCount ) { + $wgMemc->add( $throttleKey, 1, $period ); // start counter + } else if ( $throttleCount < $count ) { + $wgMemc->incr($throttleKey); + } else if ( $throttleCount >= $count ) { + return self::THROTTLED; + } + } + + // Load $wgUser now, and check to see if we're logging in as the same + // name. This is necessary because loading $wgUser (say by calling + // getName()) calls the UserLoadFromSession hook, which potentially + // creates the user in the database. Until we load $wgUser, checking + // for user existence using User::newFromName($name)->getId() below + // will effectively be using stale data. + if ( $wgUser->getName() === $this->mName ) { + wfDebug( __METHOD__.": already logged in as {$this->mName}\n" ); + return self::SUCCESS; + } $u = User::newFromName( $this->mName ); if( is_null( $u ) || !User::isUsableName( $u->getName() ) ) { return self::ILLEGAL; } + + $isAutoCreated = false; if ( 0 == $u->getID() ) { - global $wgAuth; - /** - * If the external authentication plugin allows it, - * automatically create a new account for users that - * are externally defined but have not yet logged in. - */ - if ( $wgAuth->autoCreate() && $wgAuth->userExists( $u->getName() ) ) { - if ( $wgAuth->authenticate( $u->getName(), $this->mPassword ) ) { - $u = $this->initUser( $u, true ); - } else { - return self::WRONG_PLUGIN_PASS; - } + $status = $this->attemptAutoCreate( $u ); + if ( $status !== self::SUCCESS ) { + return $status; } else { - return self::NOT_EXISTS; + $isAutoCreated = true; } } else { $u->load(); @@ -375,36 +430,33 @@ class LoginForm { if( !wfRunHooks( 'AbortLogin', array( $u, $this->mPassword, &$abort ) ) ) { return $abort; } - + if (!$u->checkPassword( $this->mPassword )) { if( $u->checkTemporaryPassword( $this->mPassword ) ) { - // The e-mailed temporary password should not be used - // for actual logins; that's a very sloppy habit, - // and insecure if an attacker has a few seconds to - // click "search" on someone's open mail reader. + // The e-mailed temporary password should not be used for actu- + // al logins; that's a very sloppy habit, and insecure if an + // attacker has a few seconds to click "search" on someone's o- + // pen mail reader. // - // Allow it to be used only to reset the password - // a single time to a new value, which won't be in - // the user's e-mail archives. + // Allow it to be used only to reset the password a single time + // to a new value, which won't be in the user's e-mail ar- + // chives. // - // For backwards compatibility, we'll still recognize - // it at the login form to minimize surprises for - // people who have been logging in with a temporary - // password for some time. - // - // As a side-effect, we can authenticate the user's - // e-mail address if it's not already done, since - // the temporary password was sent via e-mail. + // For backwards compatibility, we'll still recognize it at the + // login form to minimize surprises for people who have been + // logging in with a temporary password for some time. // + // As a side-effect, we can authenticate the user's e-mail ad- + // dress if it's not already done, since the temporary password + // was sent via e-mail. if( !$u->isEmailConfirmed() ) { $u->confirmEmail(); + $u->saveSettings(); } - // At this point we just return an appropriate code - // indicating that the UI should show a password - // reset form; bot interfaces etc will probably just - // fail cleanly here. - // + // At this point we just return an appropriate code/ indicating + // that the UI should show a password reset form; bot inter- + // faces etc will probably just fail cleanly here. $retval = self::RESET_PASS; } else { $retval = '' == $this->mPassword ? self::EMPTY_PASS : self::WRONG_PASS; @@ -413,12 +465,55 @@ class LoginForm { $wgAuth->updateUser( $u ); $wgUser = $u; + // Please reset throttle for successful logins, thanks! + if($throttleCount) { + $wgMemc->delete($throttleKey); + } + + if ( $isAutoCreated ) { + // Must be run after $wgUser is set, for correct new user log + wfRunHooks( 'AuthPluginAutoCreate', array( $wgUser ) ); + } + $retval = self::SUCCESS; } wfRunHooks( 'LoginAuthenticateAudit', array( $u, $this->mPassword, $retval ) ); return $retval; } + /** + * Attempt to automatically create a user on login. Only succeeds if there + * is an external authentication method which allows it. + * @return integer Status code + */ + function attemptAutoCreate( $user ) { + global $wgAuth, $wgUser; + /** + * If the external authentication plugin allows it, automatically cre- + * ate a new account for users that are externally defined but have not + * yet logged in. + */ + if ( !$wgAuth->autoCreate() ) { + return self::NOT_EXISTS; + } + if ( !$wgAuth->userExists( $user->getName() ) ) { + wfDebug( __METHOD__.": user does not exist\n" ); + return self::NOT_EXISTS; + } + if ( !$wgAuth->authenticate( $user->getName(), $this->mPassword ) ) { + wfDebug( __METHOD__.": \$wgAuth->authenticate() returned false, aborting\n" ); + return self::WRONG_PLUGIN_PASS; + } + if ( $wgUser->isBlockedFromCreateAccount() ) { + wfDebug( __METHOD__.": user is blocked from account creation\n" ); + return self::CREATE_BLOCKED; + } + + wfDebug( __METHOD__.": creating account\n" ); + $user = $this->initUser( $user, true ); + return self::SUCCESS; + } + function processLogin() { global $wgUser, $wgAuth; @@ -434,8 +529,19 @@ class LoginForm { } $wgUser->setCookies(); - if( $this->hasSessionCookie() ) { - return $this->successfulLogin( wfMsg( 'loginsuccess', $wgUser->getName() ) ); + // Reset the throttle + $key = wfMemcKey( 'password-throttle', wfGetIP(), md5( $this->mName ) ); + global $wgMemc; + $wgMemc->delete( $key ); + + if( $this->hasSessionCookie() || $this->mSkipCookieCheck ) { + /* Replace the language object to provide user interface in + * correct language immediately on this first page load. + */ + global $wgLang, $wgRequest; + $code = $wgRequest->getVal( 'uselang', $wgUser->getOption( 'language' ) ); + $wgLang = Language::factory( $code ); + return $this->successfulLogin(); } else { return $this->cookieRedirectCheck( 'login' ); } @@ -449,7 +555,11 @@ class LoginForm { $this->mainLoginForm( wfMsg( 'wrongpassword' ) ); break; case self::NOT_EXISTS: - $this->mainLoginForm( wfMsg( 'nosuchuser', htmlspecialchars( $this->mName ) ) ); + if( $wgUser->isAllowed( 'createaccount' ) ){ + $this->mainLoginForm( wfMsgWikiHtml( 'nosuchuser', htmlspecialchars( $this->mName ) ) ); + } else { + $this->mainLoginForm( wfMsg( 'nosuchusershort', htmlspecialchars( $this->mName ) ) ); + } break; case self::WRONG_PASS: $this->mainLoginForm( wfMsg( 'wrongpassword' ) ); @@ -460,16 +570,22 @@ class LoginForm { case self::RESET_PASS: $this->resetLoginForm( wfMsg( 'resetpass_announce' ) ); break; + case self::CREATE_BLOCKED: + $this->userBlockedMessage(); + break; + case self::THROTTLED: + $this->mainLoginForm( wfMsg( 'login-throttled' ) ); + break; default: - wfDebugDieBacktrace( "Unhandled case value" ); + throw new MWException( "Unhandled case value" ); } } function resetLoginForm( $error ) { global $wgOut; - $wgOut->addWikiText( "