555 lines
17 KiB
PHP
555 lines
17 KiB
PHP
<?php
|
|
/**
|
|
* SpecialPage for logging users into the wiki
|
|
* @ingroup SpecialPage
|
|
*/
|
|
|
|
class SpecialUserLogin extends SpecialPage {
|
|
|
|
var $mUsername, $mPassword, $mReturnTo, $mCookieCheck, $mPosted;
|
|
var $mCreateaccount, $mCreateaccountMail, $mMailmypassword;
|
|
var $mRemember, $mDomain, $mLanguage;
|
|
var $mSkipCookieCheck, $mReturnToQuery;
|
|
|
|
public $mDomains = array();
|
|
|
|
public $mFormHeader = ''; # Can be filled by hooks etc
|
|
public $mFormFields = array(
|
|
'Name' => array(
|
|
'type' => 'text',
|
|
'label-message' => 'yourname',
|
|
'id' => 'wpName1',
|
|
'tabindex' => '1',
|
|
'size' => '20',
|
|
'required' => '1',
|
|
),
|
|
'Password' => array(
|
|
'type' => 'password',
|
|
'label-message' => 'yourpassword',
|
|
'size' => '20',
|
|
'id' => 'wpPassword1',
|
|
),
|
|
'Domain' => array(
|
|
'type' => 'select',
|
|
'id' => 'wpDomain',
|
|
'label-message' => 'yourdomainname',
|
|
'options' => null,
|
|
'default' => null,
|
|
),
|
|
'Remember' => array(
|
|
'type' => 'check',
|
|
'label-message' => 'remembermypassword',
|
|
'id' => 'wpRemember',
|
|
)
|
|
);
|
|
|
|
protected $mLogin; # Login object
|
|
|
|
public function __construct(){
|
|
parent::__construct( 'Userlogin' );
|
|
}
|
|
|
|
function execute( $par ) {
|
|
global $wgRequest;
|
|
|
|
# Redirect out for account creation, for B/C
|
|
$type = ( $par == 'signup' ) ? $par : $wgRequest->getText( 'type' );
|
|
if( $type == 'signup' ){
|
|
$sp = new SpecialCreateAccount();
|
|
$sp->execute( $par );
|
|
return;
|
|
}
|
|
|
|
# Because we're transitioning from logged-out, who might not
|
|
# have a session, to logged-in, who always do, we need to make
|
|
# sure that we *always* have a session...
|
|
if( session_id() == '' ) {
|
|
wfSetupSession();
|
|
}
|
|
|
|
$this->loadQuery();
|
|
$this->mLogin = new Login();
|
|
|
|
if ( $wgRequest->getCheck( 'wpCookieCheck' ) ) {
|
|
$this->onCookieRedirectCheck();
|
|
return;
|
|
} else if( $wgRequest->wasPosted() ) {
|
|
if ( $this->mMailmypassword ) {
|
|
return $this->showMailPage();
|
|
} else {
|
|
return $this->processLogin();
|
|
}
|
|
} else {
|
|
$this->mainLoginForm( '' );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load member variables from the HTTP request data
|
|
* @param $par String the fragment passed to execute()
|
|
*/
|
|
protected function loadQuery(){
|
|
global $wgRequest, $wgAuth, $wgHiddenPrefs, $wgEnableEmail, $wgRedirectOnLogin;
|
|
|
|
$this->mUsername = $wgRequest->getText( 'wpName' );
|
|
$this->mPassword = $wgRequest->getText( 'wpPassword' );
|
|
$this->mDomain = $wgRequest->getText( 'wpDomain' );
|
|
$this->mLanguage = $wgRequest->getText( 'uselang' );
|
|
|
|
$this->mReturnTo = $wgRequest->getVal( 'returnto' );
|
|
$this->mReturnToQuery = $wgRequest->getVal( 'returntoquery' );
|
|
|
|
$this->mMailmypassword = $wgRequest->getCheck( 'wpMailmypassword' )
|
|
&& $wgEnableEmail;
|
|
$this->mRemember = $wgRequest->getCheck( 'wpRemember' );
|
|
$this->mSkipCookieCheck = $wgRequest->getCheck( 'wpSkipCookieCheck' );
|
|
|
|
if( !$wgAuth->validDomain( $this->mDomain ) ) {
|
|
$this->mDomain = 'invaliddomain';
|
|
}
|
|
$wgAuth->setDomain( $this->mDomain );
|
|
|
|
if ( $wgRedirectOnLogin ) {
|
|
$this->mReturnTo = $wgRedirectOnLogin;
|
|
$this->mReturnToQuery = '';
|
|
}
|
|
# When switching accounts, it sucks to get automatically logged out
|
|
$returnToTitle = Title::newFromText( $this->mReturnTo );
|
|
if( is_object( $returnToTitle ) && $returnToTitle->isSpecial( 'Userlogout' ) ) {
|
|
$this->mReturnTo = '';
|
|
$this->mReturnToQuery = '';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show the main login form
|
|
* @param $msg String a message key for a warning/error message
|
|
* that may have been generated on a previous iteration
|
|
*/
|
|
protected function mainLoginForm( $msg, $msgtype = 'error' ) {
|
|
global $wgUser, $wgOut, $wgEnableEmail;
|
|
global $wgCookiePrefix, $wgLoginLanguageSelector;
|
|
global $wgAuth, $wgCookieExpiration;
|
|
|
|
# Preload the name field with something if we can
|
|
if ( '' == $this->mUsername ) {
|
|
if ( $wgUser->isLoggedIn() ) {
|
|
$this->mUsername = $wgUser->getName();
|
|
} elseif( isset( $_COOKIE[$wgCookiePrefix.'UserName'] ) ) {
|
|
$this->mUsername = $_COOKIE[$wgCookiePrefix.'UserName'];
|
|
}
|
|
}
|
|
if( $this->mUsername ){
|
|
$this->mFormFields['Name']['default'] = $this->mUsername;
|
|
$this->mFormFields['Password']['autofocus'] = '1';
|
|
} else {
|
|
$this->mFormFields['Name']['autofocus'] = '1';
|
|
}
|
|
|
|
# Parse the error message if we got one
|
|
if( $msg ){
|
|
if( $msgtype == 'error' ){
|
|
$msg = wfMsgExt( 'loginerror', 'parseinline' ) . ' ' . $msg;
|
|
}
|
|
$msg = Html::rawElement(
|
|
'div',
|
|
array( 'class' => $msgtype . 'box' ),
|
|
$msg
|
|
);
|
|
} else {
|
|
$msg = '';
|
|
}
|
|
|
|
# Make sure the returnTo strings don't get lost if the
|
|
# user changes language, etc
|
|
$linkq = array();
|
|
if ( !empty( $this->mReturnTo ) ) {
|
|
$linkq['returnto'] = wfUrlencode( $this->mReturnTo );
|
|
if ( !empty( $this->mReturnToQuery ) )
|
|
$linkq['returntoquery'] = wfUrlencode( $this->mReturnToQuery );
|
|
}
|
|
|
|
# Pass any language selection on to the mode switch link
|
|
if( $wgLoginLanguageSelector && $this->mLanguage )
|
|
$linkq['uselang'] = $this->mLanguage;
|
|
|
|
$skin = $wgUser->getSkin();
|
|
$link = $skin->link(
|
|
SpecialPage::getTitleFor( 'CreateAccount' ),
|
|
wfMsgHtml( 'nologinlink' ),
|
|
array(),
|
|
$linkq );
|
|
|
|
# Don't show a "create account" link if the user can't
|
|
$link = $wgUser->isAllowed( 'createaccount' ) && !$wgUser->isLoggedIn()
|
|
? wfMsgWikiHtml( 'nologin', $link )
|
|
: '';
|
|
|
|
# Prepare language selection links as needed
|
|
$langSelector = $wgLoginLanguageSelector
|
|
? Html::rawElement(
|
|
'div',
|
|
array( 'id' => 'languagelinks' ),
|
|
self::makeLanguageSelector( $this->getTitle(), $this->mReturnTo ) )
|
|
: '';
|
|
|
|
# Add a 'mail reset' button if available
|
|
$buttons = '';
|
|
if( $wgEnableEmail && $wgAuth->allowPasswordChange() ){
|
|
$buttons = Html::element(
|
|
'input',
|
|
array(
|
|
'type' => 'submit',
|
|
'name' => 'wpMailmypassword',
|
|
'value' => wfMsg( 'mailmypassword' ),
|
|
'id' => 'wpMailmypassword',
|
|
)
|
|
);
|
|
}
|
|
|
|
# Give authentication and captcha plugins a chance to
|
|
# modify the form, by hook or by using $wgAuth
|
|
$wgAuth->modifyUITemplate( $this, 'login' );
|
|
wfRunHooks( 'UserLoginForm', array( &$this ) );
|
|
|
|
# The most likely use of the hook is to enable domains;
|
|
# check that now, and add fields if necessary
|
|
if( $this->mDomains ){
|
|
$this->mFormFields['Domain']['options'] = $this->mDomains;
|
|
$this->mFormFields['Domain']['default'] = $this->mDomain;
|
|
} else {
|
|
unset( $this->mFormFields['Domain'] );
|
|
}
|
|
|
|
# Or to tweak the 'remember my password' checkbox
|
|
if( !($wgCookieExpiration > 0) ){
|
|
# Remove it altogether
|
|
unset( $this->mFormFields['Remember'] );
|
|
} elseif( $wgUser->getOption( 'rememberpassword' ) || $this->mRemember ){
|
|
# Or check it by default
|
|
# FIXME: this doesn't always work?
|
|
$this->mFormFields['Remember']['checked'] = '1';
|
|
}
|
|
|
|
$form = new HTMLForm( $this->mFormFields, '' );
|
|
$form->setTitle( $this->getTitle() );
|
|
$form->setSubmitText( wfMsg( 'login' ) );
|
|
$form->setSubmitId( 'wpLoginAttempt' );
|
|
$form->suppressReset();
|
|
$form->loadData();
|
|
|
|
$formContents = ''
|
|
. Html::rawElement( 'p', array( 'id' => 'userloginlink' ),
|
|
$link )
|
|
. Html::rawElement( 'div', array( 'id' => 'userloginprompt' ),
|
|
wfMsgExt( 'loginprompt', array( 'parseinline' ) ) )
|
|
. $this->mFormHeader
|
|
. $langSelector
|
|
. $form->getBody()
|
|
. $form->getButtons()
|
|
. $buttons
|
|
. Xml::hidden( 'returnto', $this->mReturnTo )
|
|
. Xml::hidden( 'returntoquery', $this->mReturnToQuery )
|
|
;
|
|
|
|
$wgOut->setPageTitle( wfMsg( 'login' ) );
|
|
$wgOut->setRobotPolicy( 'noindex,nofollow' );
|
|
$wgOut->setArticleRelated( false );
|
|
$wgOut->disallowUserJs(); # Stop malicious userscripts sniffing passwords
|
|
|
|
$wgOut->addHTML(
|
|
Html::rawElement(
|
|
'div',
|
|
array( 'id' => 'loginstart' ),
|
|
wfMsgExt( 'loginstart', array( 'parseinline' ) )
|
|
) .
|
|
$msg .
|
|
Html::rawElement(
|
|
'div',
|
|
array( 'id' => 'userloginForm' ),
|
|
$form->wrapForm( $formContents )
|
|
) .
|
|
Html::rawElement(
|
|
'div',
|
|
array( 'id' => 'loginend' ),
|
|
wfMsgExt( 'loginend', array( 'parseinline' ) )
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Check if a session cookie is present.
|
|
*
|
|
* This will not pick up a cookie set during _this_ request, but is meant
|
|
* to ensure that the client is returning the cookie which was set on a
|
|
* previous pass through the system.
|
|
*
|
|
* @private
|
|
*/
|
|
protected function hasSessionCookie() {
|
|
global $wgDisableCookieCheck, $wgRequest;
|
|
return $wgDisableCookieCheck || $wgRequest->checkSessionCookie();
|
|
}
|
|
|
|
/**
|
|
* Do a redirect back to the same page, so we can check any
|
|
* new session cookies.
|
|
*/
|
|
protected function cookieRedirectCheck() {
|
|
global $wgOut;
|
|
|
|
$query = array( 'wpCookieCheck' => '1');
|
|
if ( $this->mReturnTo ) $query['returnto'] = $this->mReturnTo;
|
|
$check = $this->getTitle()->getFullURL( $query );
|
|
|
|
return $wgOut->redirect( $check );
|
|
}
|
|
|
|
/**
|
|
* Check the cookies and show errors if they're not enabled.
|
|
* @param $type String action being performed
|
|
*/
|
|
protected function onCookieRedirectCheck() {
|
|
if ( $this->hasSessionCookie() ) {
|
|
return self::successfulLogin(
|
|
'loginsuccess',
|
|
$this->mReturnTo,
|
|
$this->mReturnToQuery
|
|
);
|
|
} else {
|
|
return $this->mainLoginForm( wfMsgExt( 'nocookieslogin', array( 'parseinline' ) ) );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Produce a bar of links which allow the user to select another language
|
|
* during login/registration but retain "returnto"
|
|
* @param $title Title to use in the link
|
|
* @param $returnTo query string to append
|
|
* @return String HTML for bar
|
|
*/
|
|
public static function makeLanguageSelector( $title, $returnTo=false ) {
|
|
global $wgLang;
|
|
|
|
$msg = wfMsgForContent( 'loginlanguagelinks' );
|
|
if( $msg != '' && !wfEmptyMsg( 'loginlanguagelinks', $msg ) ) {
|
|
$langs = explode( "\n", $msg );
|
|
$links = array();
|
|
foreach( $langs as $lang ) {
|
|
$lang = trim( $lang, '* ' );
|
|
$parts = explode( '|', $lang );
|
|
if (count($parts) >= 2) {
|
|
$links[] = SpecialUserLogin::makeLanguageSelectorLink(
|
|
$parts[0], $parts[1], $title, $returnTo );
|
|
}
|
|
}
|
|
return count( $links ) > 0 ? wfMsgHtml( 'loginlanguagelabel', $wgLang->pipeList( $links ) ) : '';
|
|
} else {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a language selector link for a particular language
|
|
* Links back to this page preserving type and returnto
|
|
* @param $text Link text
|
|
* @param $lang Language code
|
|
* @param $title Title to link to
|
|
* @param $returnTo String returnto query
|
|
*/
|
|
public static function makeLanguageSelectorLink( $text, $lang, $title, $returnTo=false ) {
|
|
global $wgUser;
|
|
$attr = array( 'uselang' => $lang );
|
|
if( $returnTo )
|
|
$attr['returnto'] = $returnTo;
|
|
$skin = $wgUser->getSkin();
|
|
return $skin->linkKnown(
|
|
$title,
|
|
htmlspecialchars( $text ),
|
|
array(),
|
|
$attr
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Display a "login successful" page.
|
|
* @param $message String message key of main message to display
|
|
* @param $html String HTML to optionally add
|
|
* @param $returnto Title to returnto
|
|
* @param $returntoQuery String query string for returnto link
|
|
*/
|
|
public static function displaySuccessfulLogin( $message, $html='', $returnTo=false, $returnToQuery=false ) {
|
|
global $wgOut, $wgUser;
|
|
|
|
$wgOut->setPageTitle( wfMsg( 'loginsuccesstitle' ) );
|
|
$wgOut->setRobotPolicy( 'noindex,nofollow' );
|
|
$wgOut->setArticleRelated( false );
|
|
$wgOut->addWikiMsg( $message, $wgUser->getName() );
|
|
$wgOut->addHTML( $html );
|
|
|
|
if ( $returnTo ) {
|
|
$wgOut->returnToMain( null, $returnTo, $returnToQuery );
|
|
} else {
|
|
$wgOut->returnToMain( null );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Display any messages generated by hooks, or HTTP redirect to
|
|
* $this->mReturnTo (or Main Page if that's undefined). Formerly we had a
|
|
* nice message here, but that's not as useful as just being sent to
|
|
* wherever you logged in from. It should be clear that the action was
|
|
* successful, given the lack of error messages plus the appearance of your
|
|
* name in the upper right.
|
|
*
|
|
* Remember that this function can be accessed from a variety of
|
|
* places, such as Special:ResetPass, or Special:CreateAccount.
|
|
* @param $message String message key of a message to display if
|
|
* we don't redirect
|
|
* @param $returnTo String title of page to redirect to
|
|
* @param $returnToQuery String query string to add to the redirect.
|
|
* @param $html String empty string to go straight
|
|
* to the redirect, or valid HTML to add underneath the text.
|
|
*/
|
|
public static function successfulLogin( $message, $returnTo='', $returnToQuery='', $html='' ) {
|
|
global $wgUser, $wgOut;
|
|
|
|
if( $html === '' ) {
|
|
$titleObj = Title::newFromText( $returnTo );
|
|
if ( !$titleObj instanceof Title ) {
|
|
$titleObj = Title::newMainPage();
|
|
}
|
|
$wgOut->redirect( $titleObj->getFullURL( $returnToQuery ) );
|
|
} else {
|
|
SpecialUserLogin::displaySuccessfulLogin( $message, $html, $returnTo, $returnToQuery );
|
|
}
|
|
}
|
|
|
|
|
|
protected function processLogin(){
|
|
global $wgUser, $wgAuth;
|
|
$result = $this->mLogin->attemptLogin();
|
|
switch ( $result ) {
|
|
case Login::SUCCESS:
|
|
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 self::successfulLogin(
|
|
'loginsuccess',
|
|
$this->mReturnTo,
|
|
$this->mReturnToQuery,
|
|
$this->mLogin->mLoginResult );
|
|
} else {
|
|
# Do a redirect check to ensure that the cookies are
|
|
# being retained by the user's browser.
|
|
return $this->cookieRedirectCheck();
|
|
}
|
|
break;
|
|
|
|
case Login::NO_NAME:
|
|
case Login::ILLEGAL:
|
|
case Login::WRONG_PLUGIN_PASS:
|
|
case Login::WRONG_PASS:
|
|
case Login::EMPTY_PASS:
|
|
case Login::THROTTLED:
|
|
$this->mainLoginForm( wfMsgExt( $this->mLogin->mLoginResult, 'parseinline' ) );
|
|
break;
|
|
|
|
case Login::NOT_EXISTS:
|
|
if( $wgUser->isAllowed( 'createaccount' ) ){
|
|
$this->mainLoginForm( wfMsgExt( 'nosuchuser', 'parseinline', htmlspecialchars( $this->mName ) ) );
|
|
} else {
|
|
$this->mainLoginForm( wfMsgExt( 'nosuchusershort', 'parseinline', htmlspecialchars( $this->mName ) ) );
|
|
}
|
|
break;
|
|
|
|
case Login::RESET_PASS:
|
|
# 'Shell out' to Special:ResetPass to get the user to
|
|
# set a new permanent password from a temporary one.
|
|
$reset = new SpecialResetpass();
|
|
$reset->mHeaderMsg = 'resetpass_announce';
|
|
$reset->mHeaderMsgType = 'success';
|
|
$reset->execute( null );
|
|
break;
|
|
|
|
case Login::CREATE_BLOCKED:
|
|
$this->userBlockedMessage();
|
|
break;
|
|
|
|
case Login::ABORTED:
|
|
$msg = $this->mLogin->mLoginResult ? $this->mLogin->mLoginResult : $this->mLogin->mCreateResult;
|
|
$this->mainLoginForm( wfMsgExt( $msg, 'parseinline' ) );
|
|
break;
|
|
|
|
default:
|
|
throw new MWException( "Unhandled case value: $result" );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attempt to send the user a password-reset mail, and display
|
|
* the results (good, bad or ugly).
|
|
*/
|
|
protected function showMailPage(){
|
|
global $wgOut;
|
|
$result = $this->mLogin->mailPassword();
|
|
|
|
switch( $result ){
|
|
case Login::READ_ONLY :
|
|
$wgOut->readOnlyPage();
|
|
return;
|
|
case Login::MAIL_PASSCHANGE_FORBIDDEN:
|
|
$this->mainLoginForm( wfMsgExt( 'resetpass_forbidden', 'parseinline' ) );
|
|
return;
|
|
case Login::MAIL_BLOCKED:
|
|
$this->mainLoginForm( wfMsgExt( 'blocked-mailpassword', 'parseinline' ) );
|
|
return;
|
|
case Login::MAIL_PING_THROTTLED:
|
|
$wgOut->rateLimited();
|
|
return;
|
|
case Login::MAIL_PASS_THROTTLED:
|
|
global $wgPasswordReminderResendTime;
|
|
# Round the time in hours to 3 d.p., in case someone
|
|
# is specifying minutes or seconds.
|
|
$this->mainLoginForm( wfMsgExt(
|
|
'throttled-mailpassword',
|
|
array( 'parsemag' ),
|
|
round( $wgPasswordReminderResendTime, 3 )
|
|
) );
|
|
return;
|
|
case Login::NO_NAME:
|
|
$this->mainLoginForm( wfMsgExt( 'noname', 'parseinline' ) );
|
|
return;
|
|
case Login::NOT_EXISTS:
|
|
$this->mainLoginForm( wfMsgWikiHtml( 'nosuchuser', htmlspecialchars( $this->mLogin->mUser->getName() ) ) );
|
|
return;
|
|
case Login::MAIL_EMPTY_EMAIL:
|
|
$this->mainLoginForm( wfMsgExt( 'noemail', 'parseinline', $this->mLogin->mUser->getName() ) );
|
|
return;
|
|
case Login::MAIL_BAD_IP:
|
|
$this->mainLoginForm( wfMsgExt( 'badipaddress', 'parseinline' ) );
|
|
return;
|
|
case Login::MAIL_ERROR:
|
|
$this->mainLoginForm( wfMsgExt( 'mailerror', 'parseinline', $this->mLogin->mMailResult->getMessage() ) );
|
|
return;
|
|
case Login::SUCCESS:
|
|
$this->mainLoginForm( wfMsgExt( 'passwordsent', 'parseinline', $this->mLogin->mUser->getName() ), 'success' );
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Since the UserLoginForm hook was changed to pass a SpecialPage
|
|
* instead of a QuickTemplate derivative, old extensions might
|
|
* easily try calling this method expecing it to exist. Tempting
|
|
* though it is to let them have the fatal error, let's at least
|
|
* fail gracefully...
|
|
* @deprecated
|
|
*/
|
|
public function set(){
|
|
wfDeprecated( __METHOD__ );
|
|
}
|
|
}
|