4 * Encapsulates the backend activities of logging a user into the wiki.
11 const WRONG_PLUGIN_PASS
= 3;
17 const CREATE_BLOCKED
= 9;
20 const MAIL_READ_ONLY
= 11;
21 const MAIL_PASSCHANGE_FORBIDDEN
= 12;
22 const MAIL_BLOCKED
= 13;
23 const MAIL_PING_THROTTLED
= 14;
24 const MAIL_PASS_THROTTLED
= 15;
25 const MAIL_EMPTY_EMAIL
= 16;
26 const MAIL_BAD_IP
= 17;
27 const MAIL_ERROR
= 18;
29 var $mName, $mPassword, $mPosted;
30 var $mLoginattempt, $mRemember, $mEmail, $mDomain, $mLanguage;
32 private $mExtUser = null;
39 * @param WebRequest $request A WebRequest object passed by reference.
40 * uses $wgRequest if not given.
42 public function __construct( &$request=null ) {
43 global $wgRequest, $wgAuth, $wgHiddenPrefs, $wgEnableEmail, $wgRedirectOnLogin;
44 if( !$request ) $request = &$wgRequest;
46 $this->mName
= $request->getText( 'wpName' );
47 $this->mPassword
= $request->getText( 'wpPassword' );
48 $this->mDomain
= $request->getText( 'wpDomain' );
49 $this->mPosted
= $request->wasPosted();
50 $this->mRemember
= $request->getCheck( 'wpRemember' );
52 if( $wgEnableEmail ) {
53 $this->mEmail
= $request->getText( 'wpEmail' );
57 if( !in_array( 'realname', $wgHiddenPrefs ) ) {
58 $this->mRealName
= $request->getText( 'wpRealName' );
60 $this->mRealName
= '';
63 if( !$wgAuth->validDomain( $this->mDomain
) ) {
64 $this->mDomain
= 'invaliddomain';
66 $wgAuth->setDomain( $this->mDomain
);
68 # Attempt to generate the User
69 $this->mUser
= User
::newFromName( $this->mName
);
73 * Actually add a user to the database.
74 * Give it a User object that has been initialised with a name.
76 * @param $u User object.
77 * @param $autocreate boolean -- true if this is an autocreation via auth plugin
78 * @return User object.
80 public function initUser( $autocreate ) {
83 $this->mUser
->addToDatabase();
85 if ( $wgAuth->allowPasswordChange() ) {
86 $this->mUser
->setPassword( $this->mPassword
);
89 $this->mUser
->setEmail( $this->mEmail
);
90 $this->mUser
->setRealName( $this->mRealName
);
91 $this->mUser
->setToken();
93 $wgAuth->initUser( $this->mUser
, $autocreate );
95 if( $this->mExtUser
) {
96 $this->mExtUser
->link( $this->mUser
->getId() );
97 $email = $this->mExtUser
->getPref( 'emailaddress' );
98 if( $email && !$this->mEmail
) {
99 $this->mUser
->setEmail( $email );
103 $this->mUser
->setOption( 'rememberpassword', $this->mRemember ?
1 : 0 );
104 $this->mUser
->saveSettings();
107 $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 );
108 $ssUpdate->doUpdate();
113 public function login(){
115 $code = $this->authenticateUserData();
116 if( !$code == self
::SUCCESS
){
119 if( (bool)$this->mRemember
!= (bool)$wgUser->getOption( 'rememberpassword' ) ) {
120 $wgUser->setOption( 'rememberpassword', $this->mRemember ?
1 : 0 );
121 $wgUser->saveSettings();
123 $wgUser->invalidateCache();
125 $wgUser->setCookies();
128 $key = wfMemcKey( 'password-throttle', wfGetIP(), md5( $this->mName
) );
130 $wgMemc->delete( $key );
133 wfRunHooks('UserLoginComplete', array(&$wgUser, &$injected_html));
135 return self
::SUCCESS
;
139 * Internally authenticate the login request.
141 * This may create a local account as a side effect if the
142 * authentication plugin allows transparent local account
145 public function authenticateUserData() {
146 global $wgUser, $wgAuth;
147 if ( '' == $this->mName
) {
148 return self
::NO_NAME
;
151 global $wgPasswordAttemptThrottle;
154 if ( is_array( $wgPasswordAttemptThrottle ) ) {
155 $throttleKey = wfMemcKey( 'password-throttle', wfGetIP(), md5( $this->mName
) );
156 $count = $wgPasswordAttemptThrottle['count'];
157 $period = $wgPasswordAttemptThrottle['seconds'];
160 $throttleCount = $wgMemc->get( $throttleKey );
161 if ( !$throttleCount ) {
162 $wgMemc->add( $throttleKey, 1, $period ); // start counter
163 } else if ( $throttleCount < $count ) {
164 $wgMemc->incr($throttleKey);
165 } else if ( $throttleCount >= $count ) {
166 return self
::THROTTLED
;
170 # Load $wgUser now, and check to see if we're logging in as the same
171 # name. This is necessary because loading $wgUser (say by calling
172 # getName()) calls the UserLoadFromSession hook, which potentially
173 # creates the user in the database. Until we load $wgUser, checking
174 # for user existence using User::newFromName($name)->getId() below
175 # will effectively be using stale data.
176 if ( $wgUser->getName() === $this->mName
) {
177 wfDebug( __METHOD__
.": already logged in as {$this->mName}\n" );
178 return self
::SUCCESS
;
181 $this->mExtUser
= ExternalUser
::newFromName( $this->mName
);
183 # TODO: Allow some magic here for invalid external names, e.g., let the
184 # user choose a different wiki name.
185 if( is_null( $this->mUser
) ||
!User
::isUsableName( $this->mUser
->getName() ) ) {
186 return self
::ILLEGAL
;
189 $isAutoCreated = false;
190 if ( 0 == $this->mUser
->getID() ) {
191 $status = $this->attemptAutoCreate( $this->mUser
);
192 if ( $status !== self
::SUCCESS
) {
195 $isAutoCreated = true;
198 $this->mUser
->load();
201 # Give general extensions, such as a captcha, a chance to abort logins
202 $abort = self
::ABORTED
;
203 if( !wfRunHooks( 'AbortLogin', array( $this->mUser
, $this->mPassword
, &$abort ) ) ) {
207 if( !$this->mUser
->checkPassword( $this->mPassword
) ) {
208 if( $this->mUser
->checkTemporaryPassword( $this->mPassword
) ) {
209 # The e-mailed temporary password should not be used for actual
210 # logins; that's a very sloppy habit, and insecure if an
211 # attacker has a few seconds to click "search" on someone's
214 # Allow it to be used only to reset the password a single time
215 # to a new value, which won't be in the user's e-mail archives
217 # For backwards compatibility, we'll still recognize it at the
218 # login form to minimize surprises for people who have been
219 # logging in with a temporary password for some time.
221 # As a side-effect, we can authenticate the user's e-mail ad-
222 # dress if it's not already done, since the temporary password
223 # was sent via e-mail.
224 if( !$this->mUser
->isEmailConfirmed() ) {
225 $this->mUser
->confirmEmail();
226 $this->mUser
->saveSettings();
229 # At this point we just return an appropriate code/ indicating
230 # that the UI should show a password reset form; bot interfaces
231 # etc will probably just fail cleanly here.
232 $retval = self
::RESET_PASS
;
234 $retval = '' == $this->mPassword ? self
::EMPTY_PASS
: self
::WRONG_PASS
;
237 $wgAuth->updateUser( $this->mUser
);
238 $wgUser = $this->mUser
;
240 # Reset throttle after a successful login
241 if( $throttleCount ) {
242 $wgMemc->delete( $throttleKey );
245 if( $isAutoCreated ) {
246 # Must be run after $wgUser is set, for correct new user log
247 wfRunHooks( 'AuthPluginAutoCreate', array( $wgUser ) );
250 $retval = self
::SUCCESS
;
252 wfRunHooks( 'LoginAuthenticateAudit', array( $this->mUser
, $this->mPassword
, $retval ) );
257 * Attempt to automatically create a user on login. Only succeeds if there
258 * is an external authentication method which allows it.
259 * @return integer Status code
261 public function attemptAutoCreate( $user ) {
262 global $wgAuth, $wgUser, $wgAutocreatePolicy;
264 if( $wgUser->isBlockedFromCreateAccount() ) {
265 wfDebug( __METHOD__
.": user is blocked from account creation\n" );
266 return self
::CREATE_BLOCKED
;
269 # If the external authentication plugin allows it, automatically cre-
270 # ate a new account for users that are externally defined but have not
272 if( $this->mExtUser
) {
273 # mExtUser is neither null nor false, so use the new ExternalAuth
275 if( $wgAutocreatePolicy == 'never' ) {
276 return self
::NOT_EXISTS
;
278 if( !$this->mExtUser
->authenticate( $this->mPassword
) ) {
279 return self
::WRONG_PLUGIN_PASS
;
283 if( !$wgAuth->autoCreate() ) {
284 return self
::NOT_EXISTS
;
286 if( !$wgAuth->userExists( $user->getName() ) ) {
287 wfDebug( __METHOD__
.": user does not exist\n" );
288 return self
::NOT_EXISTS
;
290 if( !$wgAuth->authenticate( $user->getName(), $this->mPassword
) ) {
291 wfDebug( __METHOD__
.": \$wgAuth->authenticate() returned false, aborting\n" );
292 return self
::WRONG_PLUGIN_PASS
;
296 wfDebug( __METHOD__
.": creating account\n" );
297 $this->initUser( true );
298 return self
::SUCCESS
;
302 * Email the user a new password, if appropriate to do so.
303 * @param $text String message key
304 * @param $title String message key
305 * @return Status code
307 public function mailPassword( $text='passwordremindertext', $title='passwordremindertitle' ) {
308 global $wgUser, $wgOut, $wgAuth, $wgServer, $wgScript, $wgNewPasswordExpiry;
311 return self
::MAIL_READ_ONLY
;
313 if( !$wgAuth->allowPasswordChange() )
314 return self
::MAIL_PASSCHANGE_FORBIDDEN
;
316 # Check against blocked IPs
317 # FIXME: -- should we not?
318 if( $wgUser->isBlocked() )
319 return self
::MAIL_BLOCKED
;
323 if ( ! wfRunHooks( 'UserLoginMailPassword', array( $this->mName
, &$error ) ) )
326 # Check against the rate limiter
327 if( $wgUser->pingLimiter( 'mailpassword' ) )
328 return self
::MAIL_PING_THROTTLED
;
330 # Check for a valid name
331 if ( '' == $this->mName
)
332 return self
::NO_NAME
;
333 $this->mUser
= User
::newFromName( $this->mName
);
334 if( is_null( $this->mUser
) )
335 return self
::NO_NAME
;
337 # And that the resulting user actually exists
338 if ( 0 == $this->mUser
->getId() )
339 return self
::NOT_EXISTS
;
341 # Check against password throttle
342 if ( $this->mUser
->isPasswordReminderThrottled() )
343 return self
::MAIL_PASS_THROTTLED
;
345 # User doesn't have email address set
346 if ( '' == $this->mUser
->getEmail() )
347 return self
::MAIL_EMPTY_EMAIL
;
349 # Don't send to people who are acting fishily by hiding their IP
352 return self
::MAIL_BAD_IP
;
354 # Let hooks do things with the data
355 wfRunHooks( 'User::mailPasswordInternal', array(&$wgUser, &$ip, &$this->mUser
) );
357 $newpass = $this->mUser
->randomPassword();
358 $this->mUser
->setNewpassword( $newpass, true );
359 $this->mUser
->saveSettings();
361 $message = wfMsgExt( $text, array( 'parsemag' ), $ip, $this->mUser
->getName(), $newpass,
362 $wgServer . $wgScript, round( $wgNewPasswordExpiry / 86400 ) );
363 $this->mMailResult
= $this->mUser
->sendMail( wfMsg( $title ), $message );
365 if( WikiError
::isError( $this->mMailResult
) ) {
366 var_dump( $message );
367 return self
::SUCCESS
;
368 #return self::MAIL_ERROR;
370 return self
::SUCCESS
;
376 * For backwards compatibility, mainly with the state constants, which
377 * could be referred to in old extensions with the old class name.
380 class LoginForm
extends Login
{}