[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Implements Special:UserLogin 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License along 16 * with this program; if not, write to the Free Software Foundation, Inc., 17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 * http://www.gnu.org/copyleft/gpl.html 19 * 20 * @file 21 * @ingroup SpecialPage 22 */ 23 24 /** 25 * Implements Special:UserLogin 26 * 27 * @ingroup SpecialPage 28 */ 29 class LoginForm extends SpecialPage { 30 const SUCCESS = 0; 31 const NO_NAME = 1; 32 const ILLEGAL = 2; 33 const WRONG_PLUGIN_PASS = 3; 34 const NOT_EXISTS = 4; 35 const WRONG_PASS = 5; 36 const EMPTY_PASS = 6; 37 const RESET_PASS = 7; 38 const ABORTED = 8; 39 const CREATE_BLOCKED = 9; 40 const THROTTLED = 10; 41 const USER_BLOCKED = 11; 42 const NEED_TOKEN = 12; 43 const WRONG_TOKEN = 13; 44 const USER_MIGRATED = 14; 45 46 /** 47 * Valid error and warning messages 48 * 49 * Special:Userlogin can show an error or warning message on the form when 50 * coming from another page. This is done via the ?error= or ?warning= GET 51 * parameters. 52 * 53 * This array is the list of valid message keys. All other values will be 54 * ignored. 55 * 56 * @since 1.24 57 * @var string[] 58 */ 59 public static $validErrorMessages = array( 60 'exception-nologin-text', 61 'watchlistanontext', 62 'changeemail-no-info', 63 'resetpass-no-info', 64 'confirmemail_needlogin', 65 'prefsnologintext2', 66 ); 67 68 public $mAbortLoginErrorMsg = null; 69 70 protected $mUsername; 71 protected $mPassword; 72 protected $mRetype; 73 protected $mReturnTo; 74 protected $mCookieCheck; 75 protected $mPosted; 76 protected $mAction; 77 protected $mCreateaccount; 78 protected $mCreateaccountMail; 79 protected $mLoginattempt; 80 protected $mRemember; 81 protected $mEmail; 82 protected $mDomain; 83 protected $mLanguage; 84 protected $mSkipCookieCheck; 85 protected $mReturnToQuery; 86 protected $mToken; 87 protected $mStickHTTPS; 88 protected $mType; 89 protected $mReason; 90 protected $mRealName; 91 protected $mEntryError = ''; 92 protected $mEntryErrorType = 'error'; 93 94 private $mTempPasswordUsed; 95 private $mLoaded = false; 96 private $mSecureLoginUrl; 97 98 /** @var WebRequest */ 99 private $mOverrideRequest = null; 100 101 /** @var WebRequest Effective request; set at the beginning of load */ 102 private $mRequest = null; 103 104 /** 105 * @param WebRequest $request 106 */ 107 public function __construct( $request = null ) { 108 parent::__construct( 'Userlogin' ); 109 110 $this->mOverrideRequest = $request; 111 } 112 113 /** 114 * Loader 115 */ 116 function load() { 117 global $wgAuth, $wgHiddenPrefs, $wgEnableEmail; 118 119 if ( $this->mLoaded ) { 120 return; 121 } 122 $this->mLoaded = true; 123 124 if ( $this->mOverrideRequest === null ) { 125 $request = $this->getRequest(); 126 } else { 127 $request = $this->mOverrideRequest; 128 } 129 $this->mRequest = $request; 130 131 $this->mType = $request->getText( 'type' ); 132 $this->mUsername = $request->getText( 'wpName' ); 133 $this->mPassword = $request->getText( 'wpPassword' ); 134 $this->mRetype = $request->getText( 'wpRetype' ); 135 $this->mDomain = $request->getText( 'wpDomain' ); 136 $this->mReason = $request->getText( 'wpReason' ); 137 $this->mCookieCheck = $request->getVal( 'wpCookieCheck' ); 138 $this->mPosted = $request->wasPosted(); 139 $this->mCreateaccountMail = $request->getCheck( 'wpCreateaccountMail' ) 140 && $wgEnableEmail; 141 $this->mCreateaccount = $request->getCheck( 'wpCreateaccount' ) && !$this->mCreateaccountMail; 142 $this->mLoginattempt = $request->getCheck( 'wpLoginattempt' ); 143 $this->mAction = $request->getVal( 'action' ); 144 $this->mRemember = $request->getCheck( 'wpRemember' ); 145 $this->mFromHTTP = $request->getBool( 'fromhttp', false ); 146 $this->mStickHTTPS = ( !$this->mFromHTTP && $request->getProtocol() === 'https' ) 147 || $request->getBool( 'wpForceHttps', false ); 148 $this->mLanguage = $request->getText( 'uselang' ); 149 $this->mSkipCookieCheck = $request->getCheck( 'wpSkipCookieCheck' ); 150 $this->mToken = $this->mType == 'signup' 151 ? $request->getVal( 'wpCreateaccountToken' ) 152 : $request->getVal( 'wpLoginToken' ); 153 $this->mReturnTo = $request->getVal( 'returnto', '' ); 154 $this->mReturnToQuery = $request->getVal( 'returntoquery', '' ); 155 156 // Show an error or warning passed on from a previous page 157 $entryError = $this->msg( $request->getVal( 'error', '' ) ); 158 $entryWarning = $this->msg( $request->getVal( 'warning', '' ) ); 159 // bc: provide login link as a parameter for messages where the translation 160 // was not updated 161 $loginreqlink = Linker::linkKnown( 162 $this->getPageTitle(), 163 $this->msg( 'loginreqlink' )->escaped(), 164 array(), 165 array( 166 'returnto' => $this->mReturnTo, 167 'returntoquery' => $this->mReturnToQuery, 168 'uselang' => $this->mLanguage, 169 'fromhttp' => $this->mFromHTTP ? '1' : '0', 170 ) 171 ); 172 173 // Only show valid error or warning messages. 174 if ( $entryError->exists() 175 && in_array( $entryError->getKey(), self::$validErrorMessages ) 176 ) { 177 $this->mEntryErrorType = 'error'; 178 $this->mEntryError = $entryError->rawParams( $loginreqlink )->escaped(); 179 180 } elseif ( $entryWarning->exists() 181 && in_array( $entryWarning->getKey(), self::$validErrorMessages ) 182 ) { 183 $this->mEntryErrorType = 'warning'; 184 $this->mEntryError = $entryWarning->rawParams( $loginreqlink )->escaped(); 185 } 186 187 if ( $wgEnableEmail ) { 188 $this->mEmail = $request->getText( 'wpEmail' ); 189 } else { 190 $this->mEmail = ''; 191 } 192 if ( !in_array( 'realname', $wgHiddenPrefs ) ) { 193 $this->mRealName = $request->getText( 'wpRealName' ); 194 } else { 195 $this->mRealName = ''; 196 } 197 198 if ( !$wgAuth->validDomain( $this->mDomain ) ) { 199 $this->mDomain = $wgAuth->getDomain(); 200 } 201 $wgAuth->setDomain( $this->mDomain ); 202 203 # 1. When switching accounts, it sucks to get automatically logged out 204 # 2. Do not return to PasswordReset after a successful password change 205 # but goto Wiki start page (Main_Page) instead ( bug 33997 ) 206 $returnToTitle = Title::newFromText( $this->mReturnTo ); 207 if ( is_object( $returnToTitle ) 208 && ( $returnToTitle->isSpecial( 'Userlogout' ) 209 || $returnToTitle->isSpecial( 'PasswordReset' ) ) 210 ) { 211 $this->mReturnTo = ''; 212 $this->mReturnToQuery = ''; 213 } 214 } 215 216 function getDescription() { 217 if ( $this->mType === 'signup' ) { 218 return $this->msg( 'createaccount' )->text(); 219 } else { 220 return $this->msg( 'login' )->text(); 221 } 222 } 223 224 /** 225 * @param string|null $subPage 226 */ 227 public function execute( $subPage ) { 228 if ( session_id() == '' ) { 229 wfSetupSession(); 230 } 231 232 $this->load(); 233 234 // Check for [[Special:Userlogin/signup]]. This affects form display and 235 // page title. 236 if ( $subPage == 'signup' ) { 237 $this->mType = 'signup'; 238 } 239 $this->setHeaders(); 240 241 // In the case where the user is already logged in, and was redirected to the login form from a 242 // page that requires login, do not show the login page. The use case scenario for this is when 243 // a user opens a large number of tabs, is redirected to the login page on all of them, and then 244 // logs in on one, expecting all the others to work properly. 245 // 246 // However, do show the form if it was visited intentionally (no 'returnto' is present). People 247 // who often switch between several accounts have grown accustomed to this behavior. 248 if ( 249 $this->mType !== 'signup' && 250 !$this->mPosted && 251 $this->getUser()->isLoggedIn() && 252 ( $this->mReturnTo !== '' || $this->mReturnToQuery !== '' ) 253 ) { 254 $this->successfulLogin(); 255 } 256 257 // If logging in and not on HTTPS, either redirect to it or offer a link. 258 global $wgSecureLogin; 259 if ( $this->mRequest->getProtocol() !== 'https' ) { 260 $title = $this->getFullTitle(); 261 $query = array( 262 'returnto' => $this->mReturnTo !== '' ? $this->mReturnTo : null, 263 'returntoquery' => $this->mReturnToQuery !== '' ? 264 $this->mReturnToQuery : null, 265 'title' => null, 266 ( $this->mEntryErrorType === 'error' ? 'error' : 'warning' ) => $this->mEntryError, 267 ) + $this->mRequest->getQueryValues(); 268 $url = $title->getFullURL( $query, false, PROTO_HTTPS ); 269 if ( $wgSecureLogin 270 && wfCanIPUseHTTPS( $this->getRequest()->getIP() ) 271 && !$this->mFromHTTP ) // Avoid infinite redirect 272 { 273 $url = wfAppendQuery( $url, 'fromhttp=1' ); 274 $this->getOutput()->redirect( $url ); 275 // Since we only do this redir to change proto, always vary 276 $this->getOutput()->addVaryHeader( 'X-Forwarded-Proto' ); 277 278 return; 279 } else { 280 // A wiki without HTTPS login support should set $wgServer to 281 // http://somehost, in which case the secure URL generated 282 // above won't actually start with https:// 283 if ( substr( $url, 0, 8 ) === 'https://' ) { 284 $this->mSecureLoginUrl = $url; 285 } 286 } 287 } 288 289 if ( !is_null( $this->mCookieCheck ) ) { 290 $this->onCookieRedirectCheck( $this->mCookieCheck ); 291 292 return; 293 } elseif ( $this->mPosted ) { 294 if ( $this->mCreateaccount ) { 295 $this->addNewAccount(); 296 297 return; 298 } elseif ( $this->mCreateaccountMail ) { 299 $this->addNewAccountMailPassword(); 300 301 return; 302 } elseif ( ( 'submitlogin' == $this->mAction ) || $this->mLoginattempt ) { 303 $this->processLogin(); 304 305 return; 306 } 307 } 308 $this->mainLoginForm( $this->mEntryError, $this->mEntryErrorType ); 309 } 310 311 /** 312 * @private 313 */ 314 function addNewAccountMailPassword() { 315 if ( $this->mEmail == '' ) { 316 $this->mainLoginForm( $this->msg( 'noemailcreate' )->escaped() ); 317 318 return; 319 } 320 321 $status = $this->addNewAccountInternal(); 322 if ( !$status->isGood() ) { 323 $error = $status->getMessage(); 324 $this->mainLoginForm( $error->toString() ); 325 326 return; 327 } 328 329 $u = $status->getValue(); 330 331 // Wipe the initial password and mail a temporary one 332 $u->setPassword( null ); 333 $u->saveSettings(); 334 $result = $this->mailPasswordInternal( $u, false, 'createaccount-title', 'createaccount-text' ); 335 336 wfRunHooks( 'AddNewAccount', array( $u, true ) ); 337 $u->addNewUserLogEntry( 'byemail', $this->mReason ); 338 339 $out = $this->getOutput(); 340 $out->setPageTitle( $this->msg( 'accmailtitle' ) ); 341 342 if ( !$result->isGood() ) { 343 $this->mainLoginForm( $this->msg( 'mailerror', $result->getWikiText() )->text() ); 344 } else { 345 $out->addWikiMsg( 'accmailtext', $u->getName(), $u->getEmail() ); 346 $this->executeReturnTo( 'success' ); 347 } 348 } 349 350 /** 351 * @private 352 * @return bool 353 */ 354 function addNewAccount() { 355 global $wgContLang, $wgUser, $wgEmailAuthentication, $wgLoginLanguageSelector; 356 357 # Create the account and abort if there's a problem doing so 358 $status = $this->addNewAccountInternal(); 359 if ( !$status->isGood() ) { 360 $error = $status->getMessage(); 361 $this->mainLoginForm( $error->toString() ); 362 363 return false; 364 } 365 366 $u = $status->getValue(); 367 368 # Only save preferences if the user is not creating an account for someone else. 369 if ( $this->getUser()->isAnon() ) { 370 # If we showed up language selection links, and one was in use, be 371 # smart (and sensible) and save that language as the user's preference 372 if ( $wgLoginLanguageSelector && $this->mLanguage ) { 373 $u->setOption( 'language', $this->mLanguage ); 374 } else { 375 376 # Otherwise the user's language preference defaults to $wgContLang, 377 # but it may be better to set it to their preferred $wgContLang variant, 378 # based on browser preferences or URL parameters. 379 $u->setOption( 'language', $wgContLang->getPreferredVariant() ); 380 } 381 if ( $wgContLang->hasVariants() ) { 382 $u->setOption( 'variant', $wgContLang->getPreferredVariant() ); 383 } 384 } 385 386 $out = $this->getOutput(); 387 388 # Send out an email authentication message if needed 389 if ( $wgEmailAuthentication && Sanitizer::validateEmail( $u->getEmail() ) ) { 390 $status = $u->sendConfirmationMail(); 391 if ( $status->isGood() ) { 392 $out->addWikiMsg( 'confirmemail_oncreate' ); 393 } else { 394 $out->addWikiText( $status->getWikiText( 'confirmemail_sendfailed' ) ); 395 } 396 } 397 398 # Save settings (including confirmation token) 399 $u->saveSettings(); 400 401 # If not logged in, assume the new account as the current one and set 402 # session cookies then show a "welcome" message or a "need cookies" 403 # message as needed 404 if ( $this->getUser()->isAnon() ) { 405 $u->setCookies(); 406 $wgUser = $u; 407 // This should set it for OutputPage and the Skin 408 // which is needed or the personal links will be 409 // wrong. 410 $this->getContext()->setUser( $u ); 411 wfRunHooks( 'AddNewAccount', array( $u, false ) ); 412 $u->addNewUserLogEntry( 'create' ); 413 if ( $this->hasSessionCookie() ) { 414 $this->successfulCreation(); 415 } else { 416 $this->cookieRedirectCheck( 'new' ); 417 } 418 } else { 419 # Confirm that the account was created 420 $out->setPageTitle( $this->msg( 'accountcreated' ) ); 421 $out->addWikiMsg( 'accountcreatedtext', $u->getName() ); 422 $out->addReturnTo( $this->getPageTitle() ); 423 wfRunHooks( 'AddNewAccount', array( $u, false ) ); 424 $u->addNewUserLogEntry( 'create2', $this->mReason ); 425 } 426 427 return true; 428 } 429 430 /** 431 * Make a new user account using the loaded data. 432 * @private 433 * @throws PermissionsError|ReadOnlyError 434 * @return Status 435 */ 436 public function addNewAccountInternal() { 437 global $wgAuth, $wgMemc, $wgAccountCreationThrottle, 438 $wgMinimalPasswordLength, $wgEmailConfirmToEdit; 439 440 // If the user passes an invalid domain, something is fishy 441 if ( !$wgAuth->validDomain( $this->mDomain ) ) { 442 return Status::newFatal( 'wrongpassword' ); 443 } 444 445 // If we are not allowing users to login locally, we should be checking 446 // to see if the user is actually able to authenticate to the authenti- 447 // cation server before they create an account (otherwise, they can 448 // create a local account and login as any domain user). We only need 449 // to check this for domains that aren't local. 450 if ( 'local' != $this->mDomain && $this->mDomain != '' ) { 451 if ( 452 !$wgAuth->canCreateAccounts() && 453 ( 454 !$wgAuth->userExists( $this->mUsername ) || 455 !$wgAuth->authenticate( $this->mUsername, $this->mPassword ) 456 ) 457 ) { 458 return Status::newFatal( 'wrongpassword' ); 459 } 460 } 461 462 if ( wfReadOnly() ) { 463 throw new ReadOnlyError; 464 } 465 466 # Request forgery checks. 467 if ( !self::getCreateaccountToken() ) { 468 self::setCreateaccountToken(); 469 470 return Status::newFatal( 'nocookiesfornew' ); 471 } 472 473 # The user didn't pass a createaccount token 474 if ( !$this->mToken ) { 475 return Status::newFatal( 'sessionfailure' ); 476 } 477 478 # Validate the createaccount token 479 if ( $this->mToken !== self::getCreateaccountToken() ) { 480 return Status::newFatal( 'sessionfailure' ); 481 } 482 483 # Check permissions 484 $currentUser = $this->getUser(); 485 $creationBlock = $currentUser->isBlockedFromCreateAccount(); 486 if ( !$currentUser->isAllowed( 'createaccount' ) ) { 487 throw new PermissionsError( 'createaccount' ); 488 } elseif ( $creationBlock instanceof Block ) { 489 // Throws an ErrorPageError. 490 $this->userBlockedMessage( $creationBlock ); 491 492 // This should never be reached. 493 return false; 494 } 495 496 # Include checks that will include GlobalBlocking (Bug 38333) 497 $permErrors = $this->getPageTitle()->getUserPermissionsErrors( 498 'createaccount', 499 $currentUser, 500 true 501 ); 502 503 if ( count( $permErrors ) ) { 504 throw new PermissionsError( 'createaccount', $permErrors ); 505 } 506 507 $ip = $this->getRequest()->getIP(); 508 if ( $currentUser->isDnsBlacklisted( $ip, true /* check $wgProxyWhitelist */ ) ) { 509 return Status::newFatal( 'sorbs_create_account_reason' ); 510 } 511 512 // Normalize the name so that silly things don't cause "invalid username" 513 // errors. User::newFromName does some rather strict checking, rejecting 514 // e.g. leading/trailing/multiple spaces. But first we need to reject 515 // usernames that would be treated as titles with a fragment part. 516 if ( strpos( $this->mUsername, '#' ) !== false ) { 517 return Status::newFatal( 'noname' ); 518 } 519 $title = Title::makeTitleSafe( NS_USER, $this->mUsername ); 520 if ( !is_object( $title ) ) { 521 return Status::newFatal( 'noname' ); 522 } 523 524 # Now create a dummy user ($u) and check if it is valid 525 $u = User::newFromName( $title->getText(), 'creatable' ); 526 if ( !is_object( $u ) ) { 527 return Status::newFatal( 'noname' ); 528 } elseif ( 0 != $u->idForName() ) { 529 return Status::newFatal( 'userexists' ); 530 } 531 532 if ( $this->mCreateaccountMail ) { 533 # do not force a password for account creation by email 534 # set invalid password, it will be replaced later by a random generated password 535 $this->mPassword = null; 536 } else { 537 if ( $this->mPassword !== $this->mRetype ) { 538 return Status::newFatal( 'badretype' ); 539 } 540 541 # check for minimal password length 542 $valid = $u->getPasswordValidity( $this->mPassword ); 543 if ( $valid !== true ) { 544 if ( !is_array( $valid ) ) { 545 $valid = array( $valid, $wgMinimalPasswordLength ); 546 } 547 548 return call_user_func_array( 'Status::newFatal', $valid ); 549 } 550 } 551 552 # if you need a confirmed email address to edit, then obviously you 553 # need an email address. 554 if ( $wgEmailConfirmToEdit && strval( $this->mEmail ) === '' ) { 555 return Status::newFatal( 'noemailtitle' ); 556 } 557 558 if ( strval( $this->mEmail ) !== '' && !Sanitizer::validateEmail( $this->mEmail ) ) { 559 return Status::newFatal( 'invalidemailaddress' ); 560 } 561 562 # Set some additional data so the AbortNewAccount hook can be used for 563 # more than just username validation 564 $u->setEmail( $this->mEmail ); 565 $u->setRealName( $this->mRealName ); 566 567 $abortError = ''; 568 $abortStatus = null; 569 if ( !wfRunHooks( 'AbortNewAccount', array( $u, &$abortError, &$abortStatus ) ) ) { 570 // Hook point to add extra creation throttles and blocks 571 wfDebug( "LoginForm::addNewAccountInternal: a hook blocked creation\n" ); 572 if ( $abortStatus === null ) { 573 // Report back the old string as a raw message status. 574 // This will report the error back as 'createaccount-hook-aborted' 575 // with the given string as the message. 576 // To return a different error code, return a Status object. 577 $abortError = new Message( 'createaccount-hook-aborted', array( $abortError ) ); 578 $abortError->text(); 579 580 return Status::newFatal( $abortError ); 581 } else { 582 // For MediaWiki 1.23+ and updated hooks, return the Status object 583 // returned from the hook. 584 return $abortStatus; 585 } 586 } 587 588 // Hook point to check for exempt from account creation throttle 589 if ( !wfRunHooks( 'ExemptFromAccountCreationThrottle', array( $ip ) ) ) { 590 wfDebug( "LoginForm::exemptFromAccountCreationThrottle: a hook " . 591 "allowed account creation w/o throttle\n" ); 592 } else { 593 if ( ( $wgAccountCreationThrottle && $currentUser->isPingLimitable() ) ) { 594 $key = wfMemcKey( 'acctcreate', 'ip', $ip ); 595 $value = $wgMemc->get( $key ); 596 if ( !$value ) { 597 $wgMemc->set( $key, 0, 86400 ); 598 } 599 if ( $value >= $wgAccountCreationThrottle ) { 600 return Status::newFatal( 'acct_creation_throttle_hit', $wgAccountCreationThrottle ); 601 } 602 $wgMemc->incr( $key ); 603 } 604 } 605 606 if ( !$wgAuth->addUser( $u, $this->mPassword, $this->mEmail, $this->mRealName ) ) { 607 return Status::newFatal( 'externaldberror' ); 608 } 609 610 self::clearCreateaccountToken(); 611 612 return $this->initUser( $u, false ); 613 } 614 615 /** 616 * Actually add a user to the database. 617 * Give it a User object that has been initialised with a name. 618 * 619 * @param User $u 620 * @param bool $autocreate True if this is an autocreation via auth plugin 621 * @return Status Status object, with the User object in the value member on success 622 * @private 623 */ 624 function initUser( $u, $autocreate ) { 625 global $wgAuth; 626 627 $status = $u->addToDatabase(); 628 if ( !$status->isOK() ) { 629 return $status; 630 } 631 632 if ( $wgAuth->allowPasswordChange() ) { 633 $u->setPassword( $this->mPassword ); 634 } 635 636 $u->setEmail( $this->mEmail ); 637 $u->setRealName( $this->mRealName ); 638 $u->setToken(); 639 640 $wgAuth->initUser( $u, $autocreate ); 641 642 $u->saveSettings(); 643 644 // Update user count 645 DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 0, 0, 0, 1 ) ); 646 647 // Watch user's userpage and talk page 648 $u->addWatch( $u->getUserPage(), WatchedItem::IGNORE_USER_RIGHTS ); 649 650 return Status::newGood( $u ); 651 } 652 653 /** 654 * Internally authenticate the login request. 655 * 656 * This may create a local account as a side effect if the 657 * authentication plugin allows transparent local account 658 * creation. 659 * @return int 660 */ 661 public function authenticateUserData() { 662 global $wgUser, $wgAuth; 663 664 $this->load(); 665 666 if ( $this->mUsername == '' ) { 667 return self::NO_NAME; 668 } 669 670 // We require a login token to prevent login CSRF 671 // Handle part of this before incrementing the throttle so 672 // token-less login attempts don't count towards the throttle 673 // but wrong-token attempts do. 674 675 // If the user doesn't have a login token yet, set one. 676 if ( !self::getLoginToken() ) { 677 self::setLoginToken(); 678 679 return self::NEED_TOKEN; 680 } 681 // If the user didn't pass a login token, tell them we need one 682 if ( !$this->mToken ) { 683 return self::NEED_TOKEN; 684 } 685 686 $throttleCount = self::incLoginThrottle( $this->mUsername ); 687 if ( $throttleCount === true ) { 688 return self::THROTTLED; 689 } 690 691 // Validate the login token 692 if ( $this->mToken !== self::getLoginToken() ) { 693 return self::WRONG_TOKEN; 694 } 695 696 // Load the current user now, and check to see if we're logging in as 697 // the same name. This is necessary because loading the current user 698 // (say by calling getName()) calls the UserLoadFromSession hook, which 699 // potentially creates the user in the database. Until we load $wgUser, 700 // checking for user existence using User::newFromName($name)->getId() below 701 // will effectively be using stale data. 702 if ( $this->getUser()->getName() === $this->mUsername ) { 703 wfDebug( __METHOD__ . ": already logged in as {$this->mUsername}\n" ); 704 705 return self::SUCCESS; 706 } 707 708 $u = User::newFromName( $this->mUsername ); 709 710 // Give extensions a way to indicate the username has been updated, 711 // rather than telling the user the account doesn't exist. 712 if ( !wfRunHooks( 'LoginUserMigrated', array( $u, &$msg ) ) ) { 713 $this->mAbortLoginErrorMsg = $msg; 714 return self::USER_MIGRATED; 715 } 716 717 if ( !( $u instanceof User ) || !User::isUsableName( $u->getName() ) ) { 718 return self::ILLEGAL; 719 } 720 721 $isAutoCreated = false; 722 if ( $u->getID() == 0 ) { 723 $status = $this->attemptAutoCreate( $u ); 724 if ( $status !== self::SUCCESS ) { 725 return $status; 726 } else { 727 $isAutoCreated = true; 728 } 729 } else { 730 $u->load(); 731 } 732 733 // Give general extensions, such as a captcha, a chance to abort logins 734 $abort = self::ABORTED; 735 $msg = null; 736 if ( !wfRunHooks( 'AbortLogin', array( $u, $this->mPassword, &$abort, &$msg ) ) ) { 737 $this->mAbortLoginErrorMsg = $msg; 738 739 return $abort; 740 } 741 742 global $wgBlockDisablesLogin; 743 if ( !$u->checkPassword( $this->mPassword ) ) { 744 if ( $u->checkTemporaryPassword( $this->mPassword ) ) { 745 // The e-mailed temporary password should not be used for actu- 746 // al logins; that's a very sloppy habit, and insecure if an 747 // attacker has a few seconds to click "search" on someone's o- 748 // pen mail reader. 749 // 750 // Allow it to be used only to reset the password a single time 751 // to a new value, which won't be in the user's e-mail ar- 752 // chives. 753 // 754 // For backwards compatibility, we'll still recognize it at the 755 // login form to minimize surprises for people who have been 756 // logging in with a temporary password for some time. 757 // 758 // As a side-effect, we can authenticate the user's e-mail ad- 759 // dress if it's not already done, since the temporary password 760 // was sent via e-mail. 761 if ( !$u->isEmailConfirmed() ) { 762 $u->confirmEmail(); 763 $u->saveSettings(); 764 } 765 766 // At this point we just return an appropriate code/ indicating 767 // that the UI should show a password reset form; bot inter- 768 // faces etc will probably just fail cleanly here. 769 $this->mAbortLoginErrorMsg = 'resetpass-temp-emailed'; 770 $this->mTempPasswordUsed = true; 771 $retval = self::RESET_PASS; 772 } else { 773 $retval = ( $this->mPassword == '' ) ? self::EMPTY_PASS : self::WRONG_PASS; 774 } 775 } elseif ( $wgBlockDisablesLogin && $u->isBlocked() ) { 776 // If we've enabled it, make it so that a blocked user cannot login 777 $retval = self::USER_BLOCKED; 778 } elseif ( $u->getPasswordExpired() == 'hard' ) { 779 // Force reset now, without logging in 780 $retval = self::RESET_PASS; 781 $this->mAbortLoginErrorMsg = 'resetpass-expired'; 782 } else { 783 $wgAuth->updateUser( $u ); 784 $wgUser = $u; 785 // This should set it for OutputPage and the Skin 786 // which is needed or the personal links will be 787 // wrong. 788 $this->getContext()->setUser( $u ); 789 790 // Please reset throttle for successful logins, thanks! 791 if ( $throttleCount ) { 792 self::clearLoginThrottle( $this->mUsername ); 793 } 794 795 if ( $isAutoCreated ) { 796 // Must be run after $wgUser is set, for correct new user log 797 wfRunHooks( 'AuthPluginAutoCreate', array( $u ) ); 798 } 799 800 $retval = self::SUCCESS; 801 } 802 wfRunHooks( 'LoginAuthenticateAudit', array( $u, $this->mPassword, $retval ) ); 803 804 return $retval; 805 } 806 807 /** 808 * Increment the login attempt throttle hit count for the (username,current IP) 809 * tuple unless the throttle was already reached. 810 * @param string $username The user name 811 * @return bool|int The integer hit count or True if it is already at the limit 812 */ 813 public static function incLoginThrottle( $username ) { 814 global $wgPasswordAttemptThrottle, $wgMemc, $wgRequest; 815 $username = trim( $username ); // sanity 816 817 $throttleCount = 0; 818 if ( is_array( $wgPasswordAttemptThrottle ) ) { 819 $throttleKey = wfMemcKey( 'password-throttle', $wgRequest->getIP(), md5( $username ) ); 820 $count = $wgPasswordAttemptThrottle['count']; 821 $period = $wgPasswordAttemptThrottle['seconds']; 822 823 $throttleCount = $wgMemc->get( $throttleKey ); 824 if ( !$throttleCount ) { 825 $wgMemc->add( $throttleKey, 1, $period ); // start counter 826 } elseif ( $throttleCount < $count ) { 827 $wgMemc->incr( $throttleKey ); 828 } elseif ( $throttleCount >= $count ) { 829 return true; 830 } 831 } 832 833 return $throttleCount; 834 } 835 836 /** 837 * Clear the login attempt throttle hit count for the (username,current IP) tuple. 838 * @param string $username The user name 839 * @return void 840 */ 841 public static function clearLoginThrottle( $username ) { 842 global $wgMemc, $wgRequest; 843 $username = trim( $username ); // sanity 844 845 $throttleKey = wfMemcKey( 'password-throttle', $wgRequest->getIP(), md5( $username ) ); 846 $wgMemc->delete( $throttleKey ); 847 } 848 849 /** 850 * Attempt to automatically create a user on login. Only succeeds if there 851 * is an external authentication method which allows it. 852 * 853 * @param User $user 854 * 855 * @return int Status code 856 */ 857 function attemptAutoCreate( $user ) { 858 global $wgAuth; 859 860 if ( $this->getUser()->isBlockedFromCreateAccount() ) { 861 wfDebug( __METHOD__ . ": user is blocked from account creation\n" ); 862 863 return self::CREATE_BLOCKED; 864 } 865 866 if ( !$wgAuth->autoCreate() ) { 867 return self::NOT_EXISTS; 868 } 869 870 if ( !$wgAuth->userExists( $user->getName() ) ) { 871 wfDebug( __METHOD__ . ": user does not exist\n" ); 872 873 return self::NOT_EXISTS; 874 } 875 876 if ( !$wgAuth->authenticate( $user->getName(), $this->mPassword ) ) { 877 wfDebug( __METHOD__ . ": \$wgAuth->authenticate() returned false, aborting\n" ); 878 879 return self::WRONG_PLUGIN_PASS; 880 } 881 882 $abortError = ''; 883 if ( !wfRunHooks( 'AbortAutoAccount', array( $user, &$abortError ) ) ) { 884 // Hook point to add extra creation throttles and blocks 885 wfDebug( "LoginForm::attemptAutoCreate: a hook blocked creation: $abortError\n" ); 886 $this->mAbortLoginErrorMsg = $abortError; 887 888 return self::ABORTED; 889 } 890 891 wfDebug( __METHOD__ . ": creating account\n" ); 892 $status = $this->initUser( $user, true ); 893 894 if ( !$status->isOK() ) { 895 $errors = $status->getErrorsByType( 'error' ); 896 $this->mAbortLoginErrorMsg = $errors[0]['message']; 897 898 return self::ABORTED; 899 } 900 901 return self::SUCCESS; 902 } 903 904 function processLogin() { 905 global $wgMemc, $wgLang, $wgSecureLogin, $wgPasswordAttemptThrottle, 906 $wgInvalidPasswordReset; 907 908 switch ( $this->authenticateUserData() ) { 909 case self::SUCCESS: 910 # We've verified now, update the real record 911 $user = $this->getUser(); 912 $user->invalidateCache(); 913 914 if ( $user->requiresHTTPS() ) { 915 $this->mStickHTTPS = true; 916 } 917 918 if ( $wgSecureLogin && !$this->mStickHTTPS ) { 919 $user->setCookies( $this->mRequest, false, $this->mRemember ); 920 } else { 921 $user->setCookies( $this->mRequest, null, $this->mRemember ); 922 } 923 self::clearLoginToken(); 924 925 // Reset the throttle 926 $request = $this->getRequest(); 927 $key = wfMemcKey( 'password-throttle', $request->getIP(), md5( $this->mUsername ) ); 928 $wgMemc->delete( $key ); 929 930 if ( $this->hasSessionCookie() || $this->mSkipCookieCheck ) { 931 /* Replace the language object to provide user interface in 932 * correct language immediately on this first page load. 933 */ 934 $code = $request->getVal( 'uselang', $user->getOption( 'language' ) ); 935 $userLang = Language::factory( $code ); 936 $wgLang = $userLang; 937 $this->getContext()->setLanguage( $userLang ); 938 // Reset SessionID on Successful login (bug 40995) 939 $this->renewSessionId(); 940 if ( $this->getUser()->getPasswordExpired() == 'soft' ) { 941 $this->resetLoginForm( $this->msg( 'resetpass-expired-soft' ) ); 942 } elseif ( $wgInvalidPasswordReset 943 && !$user->isValidPassword( $this->mPassword ) 944 ) { 945 $status = $user->checkPasswordValidity( $this->mPassword ); 946 $this->resetLoginForm( 947 $status->getMessage( 'resetpass-validity-soft' ) 948 ); 949 } else { 950 $this->successfulLogin(); 951 } 952 } else { 953 $this->cookieRedirectCheck( 'login' ); 954 } 955 break; 956 957 case self::NEED_TOKEN: 958 $error = $this->mAbortLoginErrorMsg ?: 'nocookiesforlogin'; 959 $this->mainLoginForm( $this->msg( $error )->parse() ); 960 break; 961 case self::WRONG_TOKEN: 962 $error = $this->mAbortLoginErrorMsg ?: 'sessionfailure'; 963 $this->mainLoginForm( $this->msg( $error )->text() ); 964 break; 965 case self::NO_NAME: 966 case self::ILLEGAL: 967 $error = $this->mAbortLoginErrorMsg ?: 'noname'; 968 $this->mainLoginForm( $this->msg( $error )->text() ); 969 break; 970 case self::WRONG_PLUGIN_PASS: 971 $error = $this->mAbortLoginErrorMsg ?: 'wrongpassword'; 972 $this->mainLoginForm( $this->msg( $error )->text() ); 973 break; 974 case self::NOT_EXISTS: 975 if ( $this->getUser()->isAllowed( 'createaccount' ) ) { 976 $error = $this->mAbortLoginErrorMsg ?: 'nosuchuser'; 977 $this->mainLoginForm( $this->msg( $error, 978 wfEscapeWikiText( $this->mUsername ) )->parse() ); 979 } else { 980 $error = $this->mAbortLoginErrorMsg ?: 'nosuchusershort'; 981 $this->mainLoginForm( $this->msg( $error, 982 wfEscapeWikiText( $this->mUsername ) )->text() ); 983 } 984 break; 985 case self::WRONG_PASS: 986 $error = $this->mAbortLoginErrorMsg ?: 'wrongpassword'; 987 $this->mainLoginForm( $this->msg( $error )->text() ); 988 break; 989 case self::EMPTY_PASS: 990 $error = $this->mAbortLoginErrorMsg ?: 'wrongpasswordempty'; 991 $this->mainLoginForm( $this->msg( $error )->text() ); 992 break; 993 case self::RESET_PASS: 994 $error = $this->mAbortLoginErrorMsg ?: 'resetpass_announce'; 995 $this->resetLoginForm( $this->msg( $error ) ); 996 break; 997 case self::CREATE_BLOCKED: 998 $this->userBlockedMessage( $this->getUser()->isBlockedFromCreateAccount() ); 999 break; 1000 case self::THROTTLED: 1001 $error = $this->mAbortLoginErrorMsg ?: 'login-throttled'; 1002 $this->mainLoginForm( $this->msg( $error ) 1003 ->params( $this->getLanguage()->formatDuration( $wgPasswordAttemptThrottle['seconds'] ) ) 1004 ->text() 1005 ); 1006 break; 1007 case self::USER_BLOCKED: 1008 $error = $this->mAbortLoginErrorMsg ?: 'login-userblocked'; 1009 $this->mainLoginForm( $this->msg( $error, $this->mUsername )->escaped() ); 1010 break; 1011 case self::ABORTED: 1012 $error = $this->mAbortLoginErrorMsg ?: 'login-abort-generic'; 1013 $this->mainLoginForm( $this->msg( $error, 1014 wfEscapeWikiText( $this->mUsername ) )->text() ); 1015 break; 1016 case self::USER_MIGRATED: 1017 $error = $this->mAbortLoginErrorMsg ?: 'login-migrated-generic'; 1018 $params = array(); 1019 if ( is_array( $error ) ) { 1020 $error = array_shift( $this->mAbortLoginErrorMsg ); 1021 $params = $this->mAbortLoginErrorMsg; 1022 } 1023 $this->mainLoginForm( $this->msg( $error, $params )->text() ); 1024 break; 1025 default: 1026 throw new MWException( 'Unhandled case value' ); 1027 } 1028 } 1029 1030 /** 1031 * Show the Special:ChangePassword form, with custom message 1032 * @param Message $msg 1033 */ 1034 protected function resetLoginForm( Message $msg ) { 1035 // Allow hooks to explain this password reset in more detail 1036 wfRunHooks( 'LoginPasswordResetMessage', array( &$msg, $this->mUsername ) ); 1037 $reset = new SpecialChangePassword(); 1038 $derivative = new DerivativeContext( $this->getContext() ); 1039 $derivative->setTitle( $reset->getPageTitle() ); 1040 $reset->setContext( $derivative ); 1041 if ( !$this->mTempPasswordUsed ) { 1042 $reset->setOldPasswordMessage( 'oldpassword' ); 1043 } 1044 $reset->setChangeMessage( $msg ); 1045 $reset->execute( null ); 1046 } 1047 1048 /** 1049 * @param User $u 1050 * @param bool $throttle 1051 * @param string $emailTitle Message name of email title 1052 * @param string $emailText Message name of email text 1053 * @return Status 1054 */ 1055 function mailPasswordInternal( $u, $throttle = true, $emailTitle = 'passwordremindertitle', 1056 $emailText = 'passwordremindertext' 1057 ) { 1058 global $wgNewPasswordExpiry; 1059 1060 if ( $u->getEmail() == '' ) { 1061 return Status::newFatal( 'noemail', $u->getName() ); 1062 } 1063 $ip = $this->getRequest()->getIP(); 1064 if ( !$ip ) { 1065 return Status::newFatal( 'badipaddress' ); 1066 } 1067 1068 $currentUser = $this->getUser(); 1069 wfRunHooks( 'User::mailPasswordInternal', array( &$currentUser, &$ip, &$u ) ); 1070 1071 $np = $u->randomPassword(); 1072 $u->setNewpassword( $np, $throttle ); 1073 $u->saveSettings(); 1074 $userLanguage = $u->getOption( 'language' ); 1075 1076 $mainPage = Title::newMainPage(); 1077 $mainPageUrl = $mainPage->getCanonicalURL(); 1078 1079 $m = $this->msg( $emailText, $ip, $u->getName(), $np, '<' . $mainPageUrl . '>', 1080 round( $wgNewPasswordExpiry / 86400 ) )->inLanguage( $userLanguage )->text(); 1081 $result = $u->sendMail( $this->msg( $emailTitle )->inLanguage( $userLanguage )->text(), $m ); 1082 1083 return $result; 1084 } 1085 1086 /** 1087 * Run any hooks registered for logins, then HTTP redirect to 1088 * $this->mReturnTo (or Main Page if that's undefined). Formerly we had a 1089 * nice message here, but that's really not as useful as just being sent to 1090 * wherever you logged in from. It should be clear that the action was 1091 * successful, given the lack of error messages plus the appearance of your 1092 * name in the upper right. 1093 * 1094 * @private 1095 */ 1096 function successfulLogin() { 1097 # Run any hooks; display injected HTML if any, else redirect 1098 $currentUser = $this->getUser(); 1099 $injected_html = ''; 1100 wfRunHooks( 'UserLoginComplete', array( &$currentUser, &$injected_html ) ); 1101 1102 if ( $injected_html !== '' ) { 1103 $this->displaySuccessfulAction( 'success', $this->msg( 'loginsuccesstitle' ), 1104 'loginsuccess', $injected_html ); 1105 } else { 1106 $this->executeReturnTo( 'successredirect' ); 1107 } 1108 } 1109 1110 /** 1111 * Run any hooks registered for logins, then display a message welcoming 1112 * the user. 1113 * 1114 * @private 1115 */ 1116 function successfulCreation() { 1117 # Run any hooks; display injected HTML 1118 $currentUser = $this->getUser(); 1119 $injected_html = ''; 1120 $welcome_creation_msg = 'welcomecreation-msg'; 1121 1122 wfRunHooks( 'UserLoginComplete', array( &$currentUser, &$injected_html ) ); 1123 1124 /** 1125 * Let any extensions change what message is shown. 1126 * @see https://www.mediawiki.org/wiki/Manual:Hooks/BeforeWelcomeCreation 1127 * @since 1.18 1128 */ 1129 wfRunHooks( 'BeforeWelcomeCreation', array( &$welcome_creation_msg, &$injected_html ) ); 1130 1131 $this->displaySuccessfulAction( 1132 'signup', 1133 $this->msg( 'welcomeuser', $this->getUser()->getName() ), 1134 $welcome_creation_msg, $injected_html 1135 ); 1136 } 1137 1138 /** 1139 * Display a "successful action" page. 1140 * 1141 * @param string $type Condition of return to; see `executeReturnTo` 1142 * @param string|Message $title Page's title 1143 * @param string $msgname 1144 * @param string $injected_html 1145 */ 1146 private function displaySuccessfulAction( $type, $title, $msgname, $injected_html ) { 1147 $out = $this->getOutput(); 1148 $out->setPageTitle( $title ); 1149 if ( $msgname ) { 1150 $out->addWikiMsg( $msgname, wfEscapeWikiText( $this->getUser()->getName() ) ); 1151 } 1152 1153 $out->addHTML( $injected_html ); 1154 1155 $this->executeReturnTo( $type ); 1156 } 1157 1158 /** 1159 * Output a message that informs the user that they cannot create an account because 1160 * there is a block on them or their IP which prevents account creation. Note that 1161 * User::isBlockedFromCreateAccount(), which gets this block, ignores the 'hardblock' 1162 * setting on blocks (bug 13611). 1163 * @param Block $block The block causing this error 1164 * @throws ErrorPageError 1165 */ 1166 function userBlockedMessage( Block $block ) { 1167 # Let's be nice about this, it's likely that this feature will be used 1168 # for blocking large numbers of innocent people, e.g. range blocks on 1169 # schools. Don't blame it on the user. There's a small chance that it 1170 # really is the user's fault, i.e. the username is blocked and they 1171 # haven't bothered to log out before trying to create an account to 1172 # evade it, but we'll leave that to their guilty conscience to figure 1173 # out. 1174 $errorParams = array( 1175 $block->getTarget(), 1176 $block->mReason ? $block->mReason : $this->msg( 'blockednoreason' )->text(), 1177 $block->getByName() 1178 ); 1179 1180 if ( $block->getType() === Block::TYPE_RANGE ) { 1181 $errorMessage = 'cantcreateaccount-range-text'; 1182 $errorParams[] = $this->getRequest()->getIP(); 1183 } else { 1184 $errorMessage = 'cantcreateaccount-text'; 1185 } 1186 1187 throw new ErrorPageError( 1188 'cantcreateaccounttitle', 1189 $errorMessage, 1190 $errorParams 1191 ); 1192 } 1193 1194 /** 1195 * Add a "return to" link or redirect to it. 1196 * Extensions can use this to reuse the "return to" logic after 1197 * inject steps (such as redirection) into the login process. 1198 * 1199 * @param string $type One of the following: 1200 * - error: display a return to link ignoring $wgRedirectOnLogin 1201 * - signup: display a return to link using $wgRedirectOnLogin if needed 1202 * - success: display a return to link using $wgRedirectOnLogin if needed 1203 * - successredirect: send an HTTP redirect using $wgRedirectOnLogin if needed 1204 * @param string $returnTo 1205 * @param array|string $returnToQuery 1206 * @param bool $stickHTTPs Keep redirect link on HTTPs 1207 * @since 1.22 1208 */ 1209 public function showReturnToPage( 1210 $type, $returnTo = '', $returnToQuery = '', $stickHTTPs = false 1211 ) { 1212 $this->mReturnTo = $returnTo; 1213 $this->mReturnToQuery = $returnToQuery; 1214 $this->mStickHTTPS = $stickHTTPs; 1215 $this->executeReturnTo( $type ); 1216 } 1217 1218 /** 1219 * Add a "return to" link or redirect to it. 1220 * 1221 * @param string $type One of the following: 1222 * - error: display a return to link ignoring $wgRedirectOnLogin 1223 * - signup: display a return to link using $wgRedirectOnLogin if needed 1224 * - success: display a return to link using $wgRedirectOnLogin if needed 1225 * - successredirect: send an HTTP redirect using $wgRedirectOnLogin if needed 1226 */ 1227 private function executeReturnTo( $type ) { 1228 global $wgRedirectOnLogin, $wgSecureLogin; 1229 1230 if ( $type != 'error' && $wgRedirectOnLogin !== null ) { 1231 $returnTo = $wgRedirectOnLogin; 1232 $returnToQuery = array(); 1233 } else { 1234 $returnTo = $this->mReturnTo; 1235 $returnToQuery = wfCgiToArray( $this->mReturnToQuery ); 1236 } 1237 1238 // Allow modification of redirect behavior 1239 wfRunHooks( 'PostLoginRedirect', array( &$returnTo, &$returnToQuery, &$type ) ); 1240 1241 $returnToTitle = Title::newFromText( $returnTo ); 1242 if ( !$returnToTitle ) { 1243 $returnToTitle = Title::newMainPage(); 1244 } 1245 1246 if ( $wgSecureLogin && !$this->mStickHTTPS ) { 1247 $options = array( 'http' ); 1248 $proto = PROTO_HTTP; 1249 } elseif ( $wgSecureLogin ) { 1250 $options = array( 'https' ); 1251 $proto = PROTO_HTTPS; 1252 } else { 1253 $options = array(); 1254 $proto = PROTO_RELATIVE; 1255 } 1256 1257 if ( $type == 'successredirect' ) { 1258 $redirectUrl = $returnToTitle->getFullURL( $returnToQuery, false, $proto ); 1259 $this->getOutput()->redirect( $redirectUrl ); 1260 } else { 1261 $this->getOutput()->addReturnTo( $returnToTitle, $returnToQuery, null, $options ); 1262 } 1263 } 1264 1265 /** 1266 * @param string $msg 1267 * @param string $msgtype 1268 * @private 1269 */ 1270 function mainLoginForm( $msg, $msgtype = 'error' ) { 1271 global $wgEnableEmail, $wgEnableUserEmail; 1272 global $wgHiddenPrefs, $wgLoginLanguageSelector; 1273 global $wgAuth, $wgEmailConfirmToEdit, $wgCookieExpiration; 1274 global $wgSecureLogin, $wgPasswordResetRoutes; 1275 1276 $titleObj = $this->getPageTitle(); 1277 $user = $this->getUser(); 1278 $out = $this->getOutput(); 1279 1280 if ( $this->mType == 'signup' ) { 1281 // Block signup here if in readonly. Keeps user from 1282 // going through the process (filling out data, etc) 1283 // and being informed later. 1284 $permErrors = $titleObj->getUserPermissionsErrors( 'createaccount', $user, true ); 1285 if ( count( $permErrors ) ) { 1286 throw new PermissionsError( 'createaccount', $permErrors ); 1287 } elseif ( $user->isBlockedFromCreateAccount() ) { 1288 $this->userBlockedMessage( $user->isBlockedFromCreateAccount() ); 1289 1290 return; 1291 } elseif ( wfReadOnly() ) { 1292 throw new ReadOnlyError; 1293 } 1294 } 1295 1296 // Pre-fill username (if not creating an account, bug 44775). 1297 if ( $this->mUsername == '' && $this->mType != 'signup' ) { 1298 if ( $user->isLoggedIn() ) { 1299 $this->mUsername = $user->getName(); 1300 } else { 1301 $this->mUsername = $this->getRequest()->getCookie( 'UserName' ); 1302 } 1303 } 1304 1305 // Generic styles and scripts for both login and signup form 1306 $out->addModuleStyles( array( 1307 'mediawiki.ui', 1308 'mediawiki.ui.button', 1309 'mediawiki.ui.checkbox', 1310 'mediawiki.ui.input', 1311 'mediawiki.special.userlogin.common.styles' 1312 ) ); 1313 $out->addModules( array( 1314 'mediawiki.special.userlogin.common.js' 1315 ) ); 1316 1317 if ( $this->mType == 'signup' ) { 1318 // XXX hack pending RL or JS parse() support for complex content messages 1319 // https://bugzilla.wikimedia.org/show_bug.cgi?id=25349 1320 $out->addJsConfigVars( 'wgCreateacctImgcaptchaHelp', 1321 $this->msg( 'createacct-imgcaptcha-help' )->parse() ); 1322 1323 // Additional styles and scripts for signup form 1324 $out->addModules( array( 1325 'mediawiki.special.userlogin.signup.js' 1326 ) ); 1327 $out->addModuleStyles( array( 1328 'mediawiki.special.userlogin.signup.styles' 1329 ) ); 1330 1331 $template = new UsercreateTemplate(); 1332 1333 // Must match number of benefits defined in messages 1334 $template->set( 'benefitCount', 3 ); 1335 1336 $q = 'action=submitlogin&type=signup'; 1337 $linkq = 'type=login'; 1338 } else { 1339 // Additional styles for login form 1340 $out->addModuleStyles( array( 1341 'mediawiki.special.userlogin.login.styles' 1342 ) ); 1343 1344 $template = new UserloginTemplate(); 1345 1346 $q = 'action=submitlogin&type=login'; 1347 $linkq = 'type=signup'; 1348 } 1349 1350 if ( $this->mReturnTo !== '' ) { 1351 $returnto = '&returnto=' . wfUrlencode( $this->mReturnTo ); 1352 if ( $this->mReturnToQuery !== '' ) { 1353 $returnto .= '&returntoquery=' . 1354 wfUrlencode( $this->mReturnToQuery ); 1355 } 1356 $q .= $returnto; 1357 $linkq .= $returnto; 1358 } 1359 1360 # Don't show a "create account" link if the user can't. 1361 if ( $this->showCreateOrLoginLink( $user ) ) { 1362 # Pass any language selection on to the mode switch link 1363 if ( $wgLoginLanguageSelector && $this->mLanguage ) { 1364 $linkq .= '&uselang=' . $this->mLanguage; 1365 } 1366 // Supply URL, login template creates the button. 1367 $template->set( 'createOrLoginHref', $titleObj->getLocalURL( $linkq ) ); 1368 } else { 1369 $template->set( 'link', '' ); 1370 } 1371 1372 $resetLink = $this->mType == 'signup' 1373 ? null 1374 : is_array( $wgPasswordResetRoutes ) && in_array( true, array_values( $wgPasswordResetRoutes ) ); 1375 1376 $template->set( 'header', '' ); 1377 $template->set( 'skin', $this->getSkin() ); 1378 $template->set( 'name', $this->mUsername ); 1379 $template->set( 'password', $this->mPassword ); 1380 $template->set( 'retype', $this->mRetype ); 1381 $template->set( 'createemailset', $this->mCreateaccountMail ); 1382 $template->set( 'email', $this->mEmail ); 1383 $template->set( 'realname', $this->mRealName ); 1384 $template->set( 'domain', $this->mDomain ); 1385 $template->set( 'reason', $this->mReason ); 1386 1387 $template->set( 'action', $titleObj->getLocalURL( $q ) ); 1388 $template->set( 'message', $msg ); 1389 $template->set( 'messagetype', $msgtype ); 1390 $template->set( 'createemail', $wgEnableEmail && $user->isLoggedIn() ); 1391 $template->set( 'userealname', !in_array( 'realname', $wgHiddenPrefs ) ); 1392 $template->set( 'useemail', $wgEnableEmail ); 1393 $template->set( 'emailrequired', $wgEmailConfirmToEdit ); 1394 $template->set( 'emailothers', $wgEnableUserEmail ); 1395 $template->set( 'canreset', $wgAuth->allowPasswordChange() ); 1396 $template->set( 'resetlink', $resetLink ); 1397 $template->set( 'canremember', ( $wgCookieExpiration > 0 ) ); 1398 $template->set( 'usereason', $user->isLoggedIn() ); 1399 $template->set( 'remember', $this->mRemember ); 1400 $template->set( 'cansecurelogin', ( $wgSecureLogin === true ) ); 1401 $template->set( 'stickhttps', (int)$this->mStickHTTPS ); 1402 $template->set( 'loggedin', $user->isLoggedIn() ); 1403 $template->set( 'loggedinuser', $user->getName() ); 1404 1405 if ( $this->mType == 'signup' ) { 1406 if ( !self::getCreateaccountToken() ) { 1407 self::setCreateaccountToken(); 1408 } 1409 $template->set( 'token', self::getCreateaccountToken() ); 1410 } else { 1411 if ( !self::getLoginToken() ) { 1412 self::setLoginToken(); 1413 } 1414 $template->set( 'token', self::getLoginToken() ); 1415 } 1416 1417 # Prepare language selection links as needed 1418 if ( $wgLoginLanguageSelector ) { 1419 $template->set( 'languages', $this->makeLanguageSelector() ); 1420 if ( $this->mLanguage ) { 1421 $template->set( 'uselang', $this->mLanguage ); 1422 } 1423 } 1424 1425 $template->set( 'secureLoginUrl', $this->mSecureLoginUrl ); 1426 // Use loginend-https for HTTPS requests if it's not blank, loginend otherwise 1427 // Ditto for signupend. New forms use neither. 1428 $usingHTTPS = $this->mRequest->getProtocol() == 'https'; 1429 $loginendHTTPS = $this->msg( 'loginend-https' ); 1430 $signupendHTTPS = $this->msg( 'signupend-https' ); 1431 if ( $usingHTTPS && !$loginendHTTPS->isBlank() ) { 1432 $template->set( 'loginend', $loginendHTTPS->parse() ); 1433 } else { 1434 $template->set( 'loginend', $this->msg( 'loginend' )->parse() ); 1435 } 1436 if ( $usingHTTPS && !$signupendHTTPS->isBlank() ) { 1437 $template->set( 'signupend', $signupendHTTPS->parse() ); 1438 } else { 1439 $template->set( 'signupend', $this->msg( 'signupend' )->parse() ); 1440 } 1441 1442 // Give authentication and captcha plugins a chance to modify the form 1443 $wgAuth->modifyUITemplate( $template, $this->mType ); 1444 if ( $this->mType == 'signup' ) { 1445 wfRunHooks( 'UserCreateForm', array( &$template ) ); 1446 } else { 1447 wfRunHooks( 'UserLoginForm', array( &$template ) ); 1448 } 1449 1450 $out->disallowUserJs(); // just in case... 1451 $out->addTemplate( $template ); 1452 } 1453 1454 /** 1455 * Whether the login/create account form should display a link to the 1456 * other form (in addition to whatever the skin provides). 1457 * 1458 * @param User $user 1459 * @return bool 1460 */ 1461 private function showCreateOrLoginLink( &$user ) { 1462 if ( $this->mType == 'signup' ) { 1463 return true; 1464 } elseif ( $user->isAllowed( 'createaccount' ) ) { 1465 return true; 1466 } else { 1467 return false; 1468 } 1469 } 1470 1471 /** 1472 * Check if a session cookie is present. 1473 * 1474 * This will not pick up a cookie set during _this_ request, but is meant 1475 * to ensure that the client is returning the cookie which was set on a 1476 * previous pass through the system. 1477 * 1478 * @private 1479 * @return bool 1480 */ 1481 function hasSessionCookie() { 1482 global $wgDisableCookieCheck; 1483 1484 return $wgDisableCookieCheck ? true : $this->getRequest()->checkSessionCookie(); 1485 } 1486 1487 /** 1488 * Get the login token from the current session 1489 * @return mixed 1490 */ 1491 public static function getLoginToken() { 1492 global $wgRequest; 1493 1494 return $wgRequest->getSessionData( 'wsLoginToken' ); 1495 } 1496 1497 /** 1498 * Randomly generate a new login token and attach it to the current session 1499 */ 1500 public static function setLoginToken() { 1501 global $wgRequest; 1502 // Generate a token directly instead of using $user->getEditToken() 1503 // because the latter reuses $_SESSION['wsEditToken'] 1504 $wgRequest->setSessionData( 'wsLoginToken', MWCryptRand::generateHex( 32 ) ); 1505 } 1506 1507 /** 1508 * Remove any login token attached to the current session 1509 */ 1510 public static function clearLoginToken() { 1511 global $wgRequest; 1512 $wgRequest->setSessionData( 'wsLoginToken', null ); 1513 } 1514 1515 /** 1516 * Get the createaccount token from the current session 1517 * @return mixed 1518 */ 1519 public static function getCreateaccountToken() { 1520 global $wgRequest; 1521 1522 return $wgRequest->getSessionData( 'wsCreateaccountToken' ); 1523 } 1524 1525 /** 1526 * Randomly generate a new createaccount token and attach it to the current session 1527 */ 1528 public static function setCreateaccountToken() { 1529 global $wgRequest; 1530 $wgRequest->setSessionData( 'wsCreateaccountToken', MWCryptRand::generateHex( 32 ) ); 1531 } 1532 1533 /** 1534 * Remove any createaccount token attached to the current session 1535 */ 1536 public static function clearCreateaccountToken() { 1537 global $wgRequest; 1538 $wgRequest->setSessionData( 'wsCreateaccountToken', null ); 1539 } 1540 1541 /** 1542 * Renew the user's session id, using strong entropy 1543 */ 1544 private function renewSessionId() { 1545 global $wgSecureLogin, $wgCookieSecure; 1546 if ( $wgSecureLogin && !$this->mStickHTTPS ) { 1547 $wgCookieSecure = false; 1548 } 1549 1550 wfResetSessionID(); 1551 } 1552 1553 /** 1554 * @param string $type 1555 * @private 1556 */ 1557 function cookieRedirectCheck( $type ) { 1558 $titleObj = SpecialPage::getTitleFor( 'Userlogin' ); 1559 $query = array( 'wpCookieCheck' => $type ); 1560 if ( $this->mReturnTo !== '' ) { 1561 $query['returnto'] = $this->mReturnTo; 1562 $query['returntoquery'] = $this->mReturnToQuery; 1563 } 1564 $check = $titleObj->getFullURL( $query ); 1565 1566 $this->getOutput()->redirect( $check ); 1567 } 1568 1569 /** 1570 * @param string $type 1571 * @private 1572 */ 1573 function onCookieRedirectCheck( $type ) { 1574 if ( !$this->hasSessionCookie() ) { 1575 if ( $type == 'new' ) { 1576 $this->mainLoginForm( $this->msg( 'nocookiesnew' )->parse() ); 1577 } elseif ( $type == 'login' ) { 1578 $this->mainLoginForm( $this->msg( 'nocookieslogin' )->parse() ); 1579 } else { 1580 # shouldn't happen 1581 $this->mainLoginForm( $this->msg( 'error' )->text() ); 1582 } 1583 } else { 1584 $this->successfulLogin(); 1585 } 1586 } 1587 1588 /** 1589 * Produce a bar of links which allow the user to select another language 1590 * during login/registration but retain "returnto" 1591 * 1592 * @return string 1593 */ 1594 function makeLanguageSelector() { 1595 $msg = $this->msg( 'loginlanguagelinks' )->inContentLanguage(); 1596 if ( !$msg->isBlank() ) { 1597 $langs = explode( "\n", $msg->text() ); 1598 $links = array(); 1599 foreach ( $langs as $lang ) { 1600 $lang = trim( $lang, '* ' ); 1601 $parts = explode( '|', $lang ); 1602 if ( count( $parts ) >= 2 ) { 1603 $links[] = $this->makeLanguageSelectorLink( $parts[0], trim( $parts[1] ) ); 1604 } 1605 } 1606 1607 return count( $links ) > 0 ? $this->msg( 'loginlanguagelabel' )->rawParams( 1608 $this->getLanguage()->pipeList( $links ) )->escaped() : ''; 1609 } else { 1610 return ''; 1611 } 1612 } 1613 1614 /** 1615 * Create a language selector link for a particular language 1616 * Links back to this page preserving type and returnto 1617 * 1618 * @param string $text Link text 1619 * @param string $lang Language code 1620 * @return string 1621 */ 1622 function makeLanguageSelectorLink( $text, $lang ) { 1623 if ( $this->getLanguage()->getCode() == $lang ) { 1624 // no link for currently used language 1625 return htmlspecialchars( $text ); 1626 } 1627 $query = array( 'uselang' => $lang ); 1628 if ( $this->mType == 'signup' ) { 1629 $query['type'] = 'signup'; 1630 } 1631 if ( $this->mReturnTo !== '' ) { 1632 $query['returnto'] = $this->mReturnTo; 1633 $query['returntoquery'] = $this->mReturnToQuery; 1634 } 1635 1636 $attr = array(); 1637 $targetLanguage = Language::factory( $lang ); 1638 $attr['lang'] = $attr['hreflang'] = $targetLanguage->getHtmlCode(); 1639 1640 return Linker::linkKnown( 1641 $this->getPageTitle(), 1642 htmlspecialchars( $text ), 1643 $attr, 1644 $query 1645 ); 1646 } 1647 1648 protected function getGroupName() { 1649 return 'login'; 1650 } 1651 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |