* Add Special:Confirmemail unlisted page for requesting confirmation emails and as the destination
* There is now a confirmation token separate from the login password, which is cleaner and hopefully a lot less confusing.
* Confirmation token expires after 7 days
* Added support functions for nullable timestamp columns: wfTimestampOrNull and Database::timestampOrNull
* userMailer now returns WikiError objects
* Added convenience functions to User for email management, consolidated some checks
There are changes to the user table, so run update.php
return wfTimestamp(TS_MW,$ts);
}
+ /**
+ * Local database timestamp format or null
+ */
+ function timestampOrNull( $ts = null ) {
+ if( is_null( $ts ) ) {
+ return null;
+ } else {
+ return $this->timestamp( $ts );
+ }
+ }
+
/**
* @todo document
*/
}
}
+/**
+ * Return a formatted timestamp, or null if input is null.
+ * For dealing with nullable timestamp columns in the database.
+ * @param int $outputtype
+ * @param string $ts
+ * @return string
+ */
+function wfTimestampOrNull( $outputtype = TS_UNIX, $ts = null ) {
+ if( is_null( $ts ) ) {
+ return null;
+ } else {
+ return wfTimestamp( $outputtype, $ts );
+ }
+}
+
/**
* Check where as the operating system is Windows
*
--- /dev/null
+<?php
+/**
+ * Entry point to confirm a user's e-mail address.
+ * When a new address is entered, a random unique code is generated and
+ * mailed to the user. A clickable link to this page is provided.
+ *
+ * @package MediaWiki
+ * @subpackage SpecialPage
+ */
+
+function wfSpecialConfirmemail( $code ) {
+ $form = new ConfirmationForm();
+ $form->show( $code );
+}
+
+class ConfirmationForm {
+ function show( $code ) {
+ if( empty( $code ) ) {
+ $this->showEmpty( $this->checkAndSend() );
+ } else {
+ $this->showCode( $code );
+ }
+ }
+
+ function showCode( $code ) {
+ $user = User::newFromConfirmationCode( $code );
+ if( is_null( $user ) ) {
+ $this->showInvalidCode();
+ } else {
+ $this->confirmAndShow( $user );
+ }
+ }
+
+
+ function confirmAndShow( $user ) {
+ if( $user->confirmEmail() ) {
+ $this->showSuccess();
+ } else {
+ $this->showError();
+ }
+ }
+
+ function checkAndSend() {
+ global $wgUser, $wgRequest;
+ if( $wgRequest->wasPosted() &&
+ $wgUser->isLoggedIn() &&
+ $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) {
+ $result = $wgUser->sendConfirmationMail();
+ if( WikiError::isError( $result ) ) {
+ return 'confirmemail_sendfailed';
+ } else {
+ return 'confirmemail_sent';
+ }
+ } else {
+ # boo
+ return '';
+ }
+ }
+
+ function showEmpty( $err ) {
+ require_once( 'templates/Confirmemail.php' );
+ global $wgOut, $wgUser;
+
+ $tpl = new ConfirmemailTemplate();
+ $tpl->set( 'error', $err );
+ $tpl->set( 'edittoken', $wgUser->editToken() );
+
+ $title = Title::makeTitle( NS_SPECIAL, 'Confirmemail' );
+ $tpl->set( 'action', $title->getLocalUrl() );
+
+
+ $wgOut->addTemplate( $tpl );
+ }
+
+ function showInvalidCode() {
+ global $wgOut;
+ $wgOut->addWikiText( wfMsg( 'confirmemail_invalid' ) );
+ }
+
+ function showError() {
+ global $wgOut;
+ $wgOut->addWikiText( wfMsg( 'confirmemail_error' ) );
+ }
+
+ function showSuccess() {
+ global $wgOut, $wgRequest, $wgUser;
+
+ if( $wgUser->isLoggedIn() ) {
+ $wgOut->addWikiText( wfMsg( 'confirmemail_loggedin' ) );
+ } else {
+ $wgOut->addWikiText( wfMsg( 'confirmemail_success' ) );
+ require_once( 'SpecialUserlogin.php' );
+ $form = new LoginForm( $wgRequest );
+ $form->execute();
+ }
+ }
+}
+
+?>
return;
}
- if ( $wgUser->isAnon() ||
- ( !$wgUser->isValidEmailAddr( $wgUser->getEmail() ) ) ) {
+ if( !$wgUser->canSendEmail() ) {
+ wfDebug( "User can't send.\n" );
$wgOut->errorpage( "mailnologin", "mailnologintext" );
return;
}
$target = $par;
}
if ( "" == $target ) {
+ wfDebug( "Target is empty.\n" );
$wgOut->errorpage( "notargettitle", "notargettext" );
return;
}
+
$nt = Title::newFromURL( $target );
if ( is_null( $nt ) ) {
+ wfDebug( "Target is invalid title.\n" );
$wgOut->errorpage( "notargettitle", "notargettext" );
return;
}
+
$nu = User::newFromName( $nt->getText() );
-
- if ( 0 == $nu->getID() ) {
+ if( is_null( $nu ) || !$nu->canReceiveEmail() ) {
+ wfDebug( "Target is invalid user or can't receive.\n" );
$wgOut->errorpage( "noemailtitle", "noemailtext" );
return;
}
$address = $nu->getEmail();
-
- if ( ( !$nu->isValidEmailAddr( $address ) ) ||
- ( 1 == $nu->getOption( "disablemail" ) ) ||
- ( 0 == $nu->getEmailauthenticationtimestamp() ) ) {
- $wgOut->errorpage( "noemailtitle", "noemailtext" );
- return;
- }
-
$f = new EmailUserForm( $nu->getName() . " <{$address}>", $target );
if ( "success" == $action ) {
$mailResult = userMailer( $this->mAddress, $from, $subject, $this->text );
- if (!$mailResult) {
+ if( WikiError::isError( $mailResult ) ) {
+ $wgOut->addHTML( wfMsg( "usermailererror" ) . $mailResult);
+ } else {
$titleObj = Title::makeTitle( NS_SPECIAL, "Emailuser" );
$encTarget = wfUrlencode( $this->target );
$wgOut->redirect( $titleObj->getFullURL( "target={$encTarget}&action=success" ) );
wfRunHooks('EmailUserComplete', array($this->mAddress, $from, $subject, $this->text));
- } else {
- $wgOut->addHTML( wfMsg( "usermailererror" ) . $mailResult);
}
}
}
$wgSpecialPages['Search'] = new UnlistedSpecialPage( 'Search' );
}
+global $wgEmailAuthentication;
+if( $wgEmailAuthentication ) {
+ $wgSpecialPages['Confirmemail'] = new UnlistedSpecialPage( 'Confirmemail' );
+}
+
$wgSpecialPages = array_merge($wgSpecialPages, array (
'Wantedpages' => new SpecialPage( 'Wantedpages' ),
'Shortpages' => new SpecialPage( 'Shortpages' ),
$wgUser->setCookies();
$wgUser->saveSettings();
+ $error = wfMsg( 'savedprefs' );
if( $wgEnableEmail ) {
- $newadr = strtolower( $this->mUserEmail );
- $oldadr = strtolower($wgUser->getEmail());
- if (($newadr <> '') && ($newadr <> $oldadr)) { # the user has supplied a new email address on the login page
- # prepare for authentication and mail a temporary password to newadr
- require_once( 'SpecialUserlogin.php' );
- if ( !$wgUser->isValidEmailAddr( $newadr ) ) {
- $this->mainPrefsForm( wfMsg( 'invalidemailaddress' ) );
- return;
- }
- $wgUser->mEmail = $newadr; # new behaviour: set this new emailaddr from login-page into user database record
- $wgUser->mEmailAuthenticationtimestamp = 0; # but flag as "dirty" = unauthenticated
- $wgUser->saveSettings();
- if ($wgEmailAuthentication) {
- # mail a temporary password to the dirty address
- # on "save options", this user will be logged-out automatically
- $error = LoginForm::mailPasswordInternal( $wgUser, true, $dummy );
- if ($error === '') {
- return LoginForm::mainLoginForm( wfMsg( 'passwordsentforemailauthentication', $wgUser->getName() ) );
- } else {
- return LoginForm::mainLoginForm( wfMsg( 'mailerror', $error ) );
+ $newadr = $this->mUserEmail;
+ $oldadr = $wgUser->getEmail();
+ if( ($newadr != '') && ($newadr != $oldadr) ) {
+ # the user has supplied a new email address on the login page
+ if( $wgUser->isValidEmailAddr( $newadr ) ) {
+ $wgUser->mEmail = $newadr; # new behaviour: set this new emailaddr from login-page into user database record
+ $wgUser->mEmailAuthenticated = null; # but flag as "dirty" = unauthenticated
+ $wgUser->saveSettings();
+ if ($wgEmailAuthentication) {
+ # Mail a temporary password to the dirty address.
+ # User can come back through the confirmation URL to re-enable email.
+ $result = $wgUser->sendConfirmationMail();
+ if( WikiError::isError( $result ) ) {
+ $error = wfMsg( 'mailerror', $result->getMessage() );
+ } else {
+ $error = wfMsg( 'passwordsentforemailauthentication', $wgUser->getName() );
+ }
}
- # if user returns, that new email address gets authenticated in checkpassword()
+ } else {
+ $error = wfMsg( 'invalidemailaddress' );
}
} else {
- $wgUser->setEmail( strtolower($this->mUserEmail) );
+ $wgUser->setEmail( $this->mUserEmail );
$wgUser->setCookies();
$wgUser->saveSettings();
}
$wgOut->setParserOptions( ParserOptions::newFromUser( $wgUser ) );
$po = ParserOptions::newFromUser( $wgUser );
- $this->mainPrefsForm( wfMsg( 'savedprefs' ) );
+ $this->mainPrefsForm( $error );
}
/**
$this->mOldpass = $this->mNewpass = $this->mRetypePass = '';
$this->mUserEmail = $wgUser->getEmail();
- $this->mUserEmailAuthenticationtimestamp = $wgUser->getEmailAuthenticationtimestamp();
+ $this->mUserEmailAuthenticated = $wgUser->getEmailAuthenticationTimestamp();
$this->mRealName = ($wgAllowRealName) ? $wgUser->getRealName() : '';
$this->mUserLanguage = $wgUser->getOption( 'language' );
if( empty( $this->mUserLanguage ) ) {
else { $emfc = ''; }
if ($wgEmailAuthentication && ($this->mUserEmail != '') ) {
- if ($wgUser->getEmailAuthenticationtimestamp() != 0) {
- $emailauthenticated = wfMsg('emailauthenticated',$wgLang->timeanddate($wgUser->getEmailAuthenticationtimestamp(), true ) ).'<br />';
+ if( $wgUser->getEmailAuthenticationTimestamp() ) {
+ $emailauthenticated = wfMsg('emailauthenticated',$wgLang->timeanddate($wgUser->getEmailAuthenticationTimestamp(), true ) ).'<br />';
$disabled = '';
} else {
- $emailauthenticated = wfMsg('emailnotauthenticated').'<br />';
+ $skin = $wgUser->getSkin();
+ $emailauthenticated = wfMsg('emailnotauthenticated').'<br />' .
+ $skin->makeKnownLinkObj( Title::makeTitle( NS_SPECIAL, 'Confirmemail' ),
+ wfMsg( 'emailconfirmlink' ) );
$disabled = ' '.wfMsg('disableduntilauthent');
}
} else {
*/
function addNewAccountMailPassword() {
global $wgOut;
- global $wgEmailAuthentication;
if ('' == $this->mEmail) {
$this->mainLoginForm( wfMsg( 'noemail', htmlspecialchars( $this->mName ) ) );
return;
}
- $newadr = strtolower($this->mEmail);
-
- # prepare for authentication and mail a temporary password to newadr
- if ( !$u->isValidEmailAddr( $newadr ) ) {
- return $this->mainLoginForm( wfMsg( 'invalidemailaddress', $error ) );
- }
- $u->mEmail = $newadr; # new behaviour: set this new emailaddr from login-page into user database record
- $u->mEmailAuthenticationtimestamp = 0; # but flag as "dirty" = unauthenticated
-
- if ($wgEmailAuthentication) {
- $error = $this->mailPasswordInternal( $u, true, $dummy ); # mail a temporary password to the dirty address
- }
-
+ $u->saveSettings();
+ $result = $this->mailPasswordInternal($u);
+
$wgOut->setPageTitle( wfMsg( 'accmailtitle' ) );
$wgOut->setRobotpolicy( 'noindex,nofollow' );
$wgOut->setArticleRelated( false );
- if ($wgEmailAuthentication) {
- if ($error === '') {
- return $this->mainLoginForm( wfMsg( 'passwordsentforemailauthentication', $u->getName() ) );
+ if( WikiError::isError( $result ) ) {
+ $this->mainLoginForm( wfMsg( 'mailerror', $result->getMessage() ) );
} else {
- return $this->mainLoginForm( wfMsg( 'mailerror', $error ) );
- }
- # if user returns, that new email address gets authenticated in checkpassword()
+ $wgOut->addWikiText( wfMsg( 'accmailtext', $u->getName(), $u->getEmail() ) );
+ $wgOut->returnToMain( false );
}
-# if ( $error === '' ) {
-# $wgOut->addWikiText( wfMsg( 'accmailtext', $u->getName(), $u->getEmail() ) );
-# $wgOut->returnToMain( false );
-# } else {
-# $this->mainLoginForm( wfMsg( 'mailerror', $error ) );
-# }
$u = 0;
}
*/
function addNewAccount() {
global $wgUser, $wgOut;
- global $wgEmailAuthentication;
$u = $this->addNewAccountInternal();
return;
}
- $newadr = strtolower($this->mEmail);
- if ($newadr != '') { # prepare for authentication and mail a temporary password to newadr
- if ( !$u->isValidEmailAddr( $newadr ) ) {
- return $this->mainLoginForm( wfMsg( 'invalidemailaddress', $error ) );
- }
- $u->mEmail = $newadr; # new behaviour: set this new emailaddr from login-page into user database record
- $u->mEmailAuthenticationtimestamp = 0; # but flag as "dirty" = unauthenticated
-
- if ($wgEmailAuthentication) {
- # mail a temporary password to the dirty address
-
- $error = $this->mailPasswordInternal( $u, true, $dummy );
- if ($error === '') {
- return $this->mainLoginForm( wfMsg( 'passwordsentforemailauthentication', $u->getName() ) );
- } else {
- return $this->mainLoginForm( wfMsg( 'mailerror', $error ) );
- }
- # if user returns, that new email address gets authenticated in checkpassword()
- }
- }
-
$wgUser = $u;
$wgUser->setCookies();
$wgUser->saveSettings();
+ if( $wgUser->isValidEmailAddr( $wgUser->getEmail() ) ) {
+ $wgUser->sendConfirmationMail();
+ }
if( $this->hasSessionCookie() ) {
return $this->successfulLogin( wfMsg( 'welcomecreation', $wgUser->getName() ) );
* @access private
*/
function processLogin() {
- global $wgUser, $wgLang;
- global $wgEmailAuthentication;
+ global $wgUser;
if ( '' == $this->mName ) {
$this->mainLoginForm( wfMsg( 'noname' ) );
$u->loadFromDatabase();
}
- # store temporarily the status before the password check is performed
- $mailmsg = '';
- $oldadr = strtolower($u->getEmail());
- $newadr = strtolower($this->mEmail);
- $alreadyauthenticated = (( $u->mEmailAuthenticationtimestamp != 0 ) || ($oldadr == '')) ;
-
- # checkPassword sets EmailAuthenticationtimestamp, if the newPassword is used
-
if (!$u->checkPassword( $this->mPassword )) {
$this->mainLoginForm( wfMsg( 'wrongpassword' ) );
return;
}
$u->setOption( 'rememberpassword', $r );
- /* check if user with correct password has entered a new email address */
- if (($newadr <> '') && ($newadr <> $oldadr)) { # the user supplied a new email address on the login page
-
- # prepare for authentication and mail a temporary password to newadr
- if ( !$u->isValidEmailAddr( $newadr ) ) {
- return $this->mainLoginForm( wfMsg( 'invalidemailaddress', $error ) );
- }
- $u->mEmail = $newadr; # new behaviour: store this new emailaddr from login-page now into user database record ...
- $u->mEmailAuthenticationtimestamp = 0; # ... but flag the address as "dirty" (unauthenticated)
- $alreadyauthenticated = false;
-
- if ($wgEmailAuthentication) {
-
- # mail a temporary one-time password to the dirty address and return here to complete the user login
- # if the user returns now or later using this temp. password, then the new email address $newadr
- # - which is already stored in his user record - gets authenticated in checkpassword()
-
- $error = $this->mailPasswordInternal( $u, false, $newpassword_temp);
- $u->mNewpassword = $newpassword_temp;
-
- # The temporary password is mailed. The user is logged-in as he entered his correct password
- # This appears to be more intuitive than alternative 2.
-
- if ($error === '') {
- $mailmsg = '<br />' . wfMsg( 'passwordsentforemailauthentication', $u->getName() );
- } else {
- $mailmsg = '<br />' . wfMsg( 'mailerror', $error ) ;
- }
- }
- }
-
$wgUser = $u;
$wgUser->setCookies();
- # save all settings (incl. new email address and/or temporary password, if applicable)
$wgUser->saveSettings();
- if ( !$wgEmailAuthentication || $alreadyauthenticated ) {
- $authenticated = '';
- $mailmsg = '';
- } elseif ($u->mEmailAuthenticationtimestamp != 0) {
- $authenticated = ' ' . wfMsg( 'emailauthenticated', $wgLang->timeanddate( $u->mEmailAuthenticationtimestamp, true ) );
- } else {
- $authenticated = ' ' . wfMsg( 'emailnotauthenticated' );
- }
-
if( $this->hasSessionCookie() ) {
- return $this->successfulLogin( wfMsg( 'loginsuccess', $wgUser->getName() ) . $authenticated . $mailmsg );
+ return $this->successfulLogin( wfMsg( 'loginsuccess', $wgUser->getName() ) );
} else {
return $this->cookieRedirectCheck( 'login' );
}
$u->loadFromDatabase();
- $error = $this->mailPasswordInternal( $u, true, $dummy );
- if ($error === '') {
- $this->mainLoginForm( wfMsg( 'passwordsent', $u->getName() ) );
+ $result = $this->mailPasswordInternal( $u );
+ if( WikiError::isError( $result ) ) {
+ $this->mainLoginForm( wfMsg( 'mailerror', $result->getMessage() ) );
} else {
- $this->mainLoginForm( wfMsg( 'mailerror', $error ) );
+ $this->mainLoginForm( wfMsg( 'passwordsent', $u->getName() ) );
}
- return;
}
/**
+ * @return mixed true on success, WikiError on failure
* @access private
*/
- function mailPasswordInternal( $u, $savesettings = true, &$newpassword_out ) {
+ function mailPasswordInternal( $u ) {
global $wgPasswordSender, $wgDBname, $wgIP;
global $wgCookiePath, $wgCookieDomain;
$np = $u->randomPassword();
$u->setNewpassword( $np );
- # we want to store this new password together with other values in the calling function
- $newpassword_out = $u->mNewpassword;
-
- # WHY IS THIS HERE ? SHOULDN'T IT BE User::setcookie ???
setcookie( "{$wgDBname}Token", '', time() - 3600, $wgCookiePath, $wgCookieDomain );
- if ($savesettings) {
$u->saveSettings();
- }
$ip = $wgIP;
if ( '' == $ip ) { $ip = '(Unknown)'; }
$m = wfMsg( 'passwordremindermailbody', $ip, $u->getName(), wfUrlencode($u->getName()), $np );
-
- require_once('UserMailer.php');
- $error = userMailer( $u->getEmail(), $wgPasswordSender, wfMsg( 'passwordremindermailsubject' ), $m );
+ $result = $u->sendMail( wfMsg( 'passwordremindermailsubject' ), $m );
- return htmlspecialchars( $error );
+ return $result;
}
function mainLoginForm( $err ) {
global $wgUser, $wgOut, $wgLang;
global $wgDBname, $wgAllowRealName, $wgEnableEmail;
- global $wgEmailAuthentication;
if ( '' == $this->mName ) {
if ( $wgUser->isLoggedIn() ) {
$template->set( 'createemail', $wgEnableEmail && $wgUser->isLoggedIn() );
$template->set( 'userealname', $wgAllowRealName );
$template->set( 'useemail', $wgEnableEmail );
- $template->set( 'useemailauthent', $wgEmailAuthentication );
$template->set( 'remember', $wgUser->getOption( 'rememberpassword' ) or $this->mRemember );
$wgOut->setPageTitle( wfMsg( 'userlogin' ) );
* @access private
*/
var $mId, $mName, $mPassword, $mEmail, $mNewtalk;
- var $mEmailAuthenticationtimestamp;
+ var $mEmailAuthenticated;
var $mRights, $mOptions;
var $mDataLoaded, $mNewpassword;
var $mSkin;
/**
* Static factory method
- * @static
* @param string $name Username, validated by Title:newFromText()
+ * @return User
+ * @static
*/
function newFromName( $name ) {
$u = new User();
return $u;
}
}
+
+ /**
+ * Factory method to fetch whichever use has a given email confirmation code.
+ * This code is generated when an account is created or its e-mail address
+ * has changed.
+ *
+ * If the code is invalid or has expired, returns NULL.
+ *
+ * @param string $code
+ * @return User
+ * @static
+ */
+ function newFromConfirmationCode( $code ) {
+ $dbr =& wfGetDB( DB_SLAVE );
+ $name = $dbr->selectField( 'user', 'user_name', array(
+ 'user_email_token' => md5( $code ),
+ 'user_email_token_expires > ' . $dbr->addQuotes( $dbr->timestamp() ),
+ ) );
+ if( is_string( $name ) ) {
+ return User::newFromName( $name );
+ } else {
+ return null;
+ }
+ }
/**
* Get username given an id.
function isValidEmailAddr ( $addr ) {
# There used to be a regular expression here, it got removed because it
# rejected valid addresses.
- return true;
+ return ( trim( $addr ) != '' ) &&
+ (false !== strpos( $addr, '@' ) );
}
/**
$this->mNewtalk = -1;
$this->mName = $wgIP;
$this->mRealName = $this->mEmail = '';
- $this->mEmailAuthenticationtimestamp = 0;
+ $this->mEmailAuthenticated = null;
$this->mPassword = $this->mNewpassword = '';
$this->mRights = array();
$this->mGroups = array();
$dbr =& wfGetDB( DB_SLAVE );
$s = $dbr->selectRow( 'user', array( 'user_name','user_password','user_newpassword','user_email',
- 'user_emailauthenticationtimestamp',
+ 'user_email_authenticated',
'user_real_name','user_options','user_touched', 'user_token' ),
array( 'user_id' => $this->mId ), $fname );
if ( $s !== false ) {
$this->mName = $s->user_name;
$this->mEmail = $s->user_email;
- $this->mEmailAuthenticationtimestamp = wfTimestamp(TS_MW,$s->user_emailauthenticationtimestamp);
+ $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $s->user_email_authenticated );
$this->mRealName = $s->user_real_name;
$this->mPassword = $s->user_password;
$this->mNewpassword = $s->user_newpassword;
return $this->mEmail;
}
- function getEmailAuthenticationtimestamp() {
+ function getEmailAuthenticationTimestamp() {
$this->loadFromDatabase();
- return $this->mEmailAuthenticationtimestamp;
+ return $this->mEmailAuthenticated;
}
function setEmail( $str ) {
'user_newpassword' => $this->mNewpassword,
'user_real_name' => $this->mRealName,
'user_email' => $this->mEmail,
- 'user_emailauthenticationtimestamp' => $dbw->timestamp($this->mEmailAuthenticationtimestamp),
+ 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
'user_options' => $this->encodeOptions(),
'user_touched' => $dbw->timestamp($this->mTouched),
'user_token' => $this->mToken
'user_password' => $this->mPassword,
'user_newpassword' => $this->mNewpassword,
'user_email' => $this->mEmail,
- 'user_emailauthenticationtimestamp' => $dbw->timestamp($this->mEmailAuthenticationtimestamp),
+ 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
'user_real_name' => $this->mRealName,
'user_options' => $this->encodeOptions(),
'user_token' => $this->mToken
if ( 0 == strcmp( $ep, $this->mPassword ) ) {
return true;
} elseif ( ($this->mNewpassword != '') && (0 == strcmp( $ep, $this->mNewpassword )) ) {
- $this->mEmailAuthenticationtimestamp = wfTimestampNow();
- $this->mNewpassword = ''; # use the temporary one-time password only once: clear it now !
+ # If e-mail confirmation hasn't been done already,
+ # we may as well confirm it here -- the user can only
+ # get this password via e-mail.
+ $this->mEmailAuthenticated = wfTimestampNow();
+
+ # use the temporary one-time password only once: clear it now !
+ $this->mNewpassword = '';
$this->saveSettings();
return true;
} elseif ( function_exists( 'iconv' ) ) {
*/
function editToken( $salt = '' ) {
if( !isset( $_SESSION['wsEditToken'] ) ) {
- $token = dechex( mt_rand() ) . dechex( mt_rand() );
+ $token = $this->generateToken();
$_SESSION['wsEditToken'] = $token;
} else {
$token = $_SESSION['wsEditToken'];
return md5( $token . $salt );
}
+ /**
+ * Generate a hex-y looking random token for various uses.
+ * Could be made more cryptographically sure if someone cares.
+ * @return string
+ */
+ function generateToken( $salt = '' ) {
+ $token = dechex( mt_rand() ) . dechex( mt_rand() );
+ return md5( $token . $salt );
+ }
+
/**
* Check given value against the token value stored in the session.
* A match should confirm that the form was submitted from the
function matchEditToken( $val, $salt = '' ) {
return ( $val == $this->editToken( $salt ) );
}
+
+ /**
+ * Generate a new e-mail confirmation token and send a confirmation
+ * mail to the user's given address.
+ *
+ * @return mixed True on success, a WikiError object on failure.
+ */
+ function sendConfirmationMail() {
+ global $wgIP, $wgContLang;
+ $url = $this->confirmationTokenUrl( $expiration );
+ return $this->sendMail( wfMsg( 'confirmemail_subject' ),
+ wfMsg( 'confirmemail_body',
+ $wgIP,
+ $this->getName(),
+ $url,
+ $wgContLang->timeanddate( $expiration, false ) ) );
+ }
+
+ /**
+ * Send an e-mail to this user's account. Does not check for
+ * confirmed status or validity.
+ *
+ * @param string $subject
+ * @param string $body
+ * @param strong $from Optional from address; default $wgPasswordSender will be used otherwise.
+ * @return mixed True on success, a WikiError object on failure.
+ */
+ function sendMail( $subject, $body, $from = null ) {
+ if( is_null( $from ) ) {
+ global $wgPasswordSender;
+ $from = $wgPasswordSender;
+ }
+
+ require_once( 'UserMailer.php' );
+ $error = userMailer( $this->getEmail(), $from, $subject, $body );
+
+ if( $error == '' ) {
+ return true;
+ } else {
+ return new WikiError( $error );
+ }
+ }
+
+ /**
+ * Generate, store, and return a new e-mail confirmation code.
+ * A hash (unsalted since it's used as a key) is stored.
+ * @param &$expiration mixed output: accepts the expiration time
+ * @return string
+ * @access private
+ */
+ function confirmationToken( &$expiration ) {
+ $fname = 'User::confirmationToken';
+
+ $now = time();
+ $expires = $now + 7 * 24 * 60 * 60;
+ $expiration = wfTimestamp( TS_MW, $expires );
+
+ $token = $this->generateToken( $this->mId . $this->mEmail . $expires );
+ $hash = md5( $token );
+
+ $dbw =& wfGetDB( DB_MASTER );
+ $dbw->update( 'user',
+ array( 'user_email_token' => $hash,
+ 'user_email_token_expires' => $dbw->timestamp( $expires ) ),
+ array( 'user_id' => $this->mId ),
+ $fname );
+
+ return $token;
+ }
+
+ /**
+ * Generate and store a new e-mail confirmation token, and return
+ * the URL the user can use to confirm.
+ * @param &$expiration mixed output: accepts the expiration time
+ * @return string
+ * @access private
+ */
+ function confirmationTokenUrl( &$expiration ) {
+ $token = $this->confirmationToken( $expiration );
+ $title = Title::makeTitle( NS_SPECIAL, 'Confirmemail/' . $token );
+ return $title->getFullUrl();
+ }
+
+ /**
+ * Mark the e-mail address confirmed and save.
+ */
+ function confirmEmail() {
+ $this->loadFromDatabase();
+ $this->mEmailAuthenticated = wfTimestampNow();
+ $this->saveSettings();
+ return true;
+ }
+
+ /**
+ * Is this user allowed to send e-mails within limits of current
+ * site configuration?
+ * @return bool
+ */
+ function canSendEmail() {
+ return $this->isEmailConfirmed();
+ }
+
+ /**
+ * Is this user allowed to receive e-mails within limits of current
+ * site configuration?
+ * @return bool
+ */
+ function canReceiveEmail() {
+ return $this->canSendEmail() && !$this->getOption( 'disablemail' );
+ }
+
+ /**
+ * Is this user's e-mail address valid-looking and confirmed within
+ * limits of the current site configuration?
+ *
+ * If $wgEmailAuthentication is on, this may require the user to have
+ * confirmed their address by returning a code or using a password
+ * sent to the address from the wiki.
+ *
+ * @return bool
+ */
+ function isEmailConfirmed() {
+ global $wgEmailAuthentication;
+ $this->loadFromDatabase();
+ if( $this->isAnon() )
+ return false;
+ if( !$this->isValidEmailAddr( $this->mEmail ) )
+ return false;
+ if( $wgEmailAuthentication && !$this->getEmailAuthenticationTimestamp() )
+ return false;
+ return true;
+ }
}
?>
* @package MediaWiki
*/
+require_once( 'WikiError.php' );
+
/**
* Provide mail capabilities
* @param string $string ????
if ( ( $enotifwatchlistpage && $watchingUser->getOption('enotifwatchlistpages') ) ||
( $enotifusertalkpage && $watchingUser->getOption('enotifusertalkpages') )
&& (!$currentMinorEdit || ($wgEmailNotificationForMinorEdits && $watchingUser->getOption('enotifminoredits') ) )
- && ($watchingUser->getEmail() != '')
- && (!$wgEmailAuthentication || ($watchingUser->getEmailAuthenticationtimestamp() != 0 ) ) ) {
+ && ($watchingUser->isEmailConfirmed() ) ) {
# ... adjust remaining text and page edit time placeholders
# which needs to be personalized for each user
$sent = $this->composeAndSendPersonalisedMail( $watchingUser, $mail, $article );
--- /dev/null
+<?php
+/**
+ * @package MediaWiki
+ * @subpackage Templates
+ */
+if( !defined( 'MEDIAWIKI' ) ) die();
+
+/** */
+require_once( 'includes/SkinTemplate.php' );
+
+/**
+ * HTML template for Special:Confirmemail form
+ * @package MediaWiki
+ * @subpackage Templates
+ */
+class ConfirmemailTemplate extends QuickTemplate {
+ function execute() {
+ if( $this->data['error'] ) {
+?>
+ <div class='error'><?php $this->msgWiki( $this->data['error']) ?></div>
+<?php } else { ?>
+ <div>
+ <?php $this->msgWiki( 'confirmemail_text' ) ?>
+ </div>
+<?php } ?>
+<form name="confirmemail" id="confirmemail" method="post" action="<?php $this->text('action') ?>">
+ <input type="hidden" name="action" value="submit" />
+ <input type="hidden" name="wpEditToken" value="<?php $this->text('edittoken') ?>" />
+ <table border='0'>
+ <tr>
+ <td></td>
+ <td><input type="submit" name="wpConfirm" value="<?php $this->msg('confirmemail_send') ?>" /></td>
+ </tr>
+ </table>
+</form>
+<?php
+ }
+}
+
+?>
\ No newline at end of file
<p>
<?php $this->msgHtml( 'emailforlost' ) ?><br />
<input tabindex='10' type='submit' name="wpMailmypassword"
- value="<?php if ( $this->data['useemailauthent'] ) {
- $this->msg('mailmypasswordauthent') ?>" />
- <?php } else {
- $this->msg('mailmypassword') ?>" />
- <?php } ?>
+ value="<?php $this->msg('mailmypassword') ?>" />
</p>
</td>
</tr>
'mailerror' => "Error sending mail: $1",
'acct_creation_throttle_hit' => 'Sorry, you have already created $1 accounts. You can\'t make any more.',
'emailauthenticated' => 'Your email address was authenticated on $1.',
-'emailnotauthenticated' => 'Your email address is <strong>not yet authenticated</strong> and the advanced email features are disabled until authentication <strong>(d.u.a.)</strong>.<br />
-To authenticate, please login in with the temporary password which has been mailed to you, or request a new one on the login page.',
+'emailnotauthenticated' => 'Your email address is <strong>not yet authenticated</strong> and the advanced email features are disabled until authentication <strong>(d.u.a.)</strong>.',
+'emailconfirmlink' => 'Confirm your e-mail address',
'invalidemailaddress' => 'The email address cannot be accepted as it appears to have an invalid format. Please enter a well-formatted address or empty that field.',
'disableduntilauthent' => '<strong>(d.u.a.)</strong>',
'disablednoemail' => '<strong>(disabled; no email address)</strong>',
'watchlistall1' => 'all',
'watchlistall2' => 'all',
'contributionsall' => 'all',
+
+# E-mail address confirmation
+'confirmemail' => 'Confirm E-mail address',
+'confirmemail_text' => "This wiki requires that you validate your e-mail address
+before using e-mail features. Activate the button below to send a confirmation
+mail to your address. The mail will include a link containing a code; load the
+link in your browser to confirm that your e-mail address is valid.",
+'confirmemail_send' => 'Mail a confirmation code',
+'confirmemail_sent' => 'Confirmation e-mail sent.',
+'confirmemail_sendfailed' => 'Could not send confirmation mail. Check address for invalid characters.',
+'confirmemail_invalid' => 'Invalid confirmation code. The code may have expired.',
+'confirmemail_success' => 'Your e-mail address has been confirmed. You may now log in and enjoy the wiki.',
+'confirmemail_loggedin' => 'Your e-mail address has now been confirmed.',
+'confirmemail_error' => 'Something went wrong saving your confirmation.',
+
+'confirmemail_subject' => '{{SITENAME}} e-mail address confirmation',
+'confirmemail_body' => "Someone, probably you from IP address $1, has registered an
+account \"$2\" with this e-mail address on {{SITENAME}}.
+
+To confirm that this account really does belong to you and activate
+e-mail features on {{SITENAME}}, open this link in your browser:
+
+$3
+
+If this is *not* you, don't follow the link. This confirmation code
+will expire at $4.
+",
+
);
/* a fake language converter */
--- Patch for email authentication T.Gries/M.Arndt 27.11.2004
--- A new column is added to the table 'user'.
-ALTER TABLE /*$wgDBprefix*/user ADD (user_emailauthenticationtimestamp varchar(14) binary NOT NULL default '0');
+-- Added early in 1.5 alpha development, removed 2005-04-25
+
+ALTER TABLE /*$wgDBprefix*/user DROP COLUMN user_emailauthenticationtimestamp;
--- /dev/null
+--
+-- E-mail confirmation token and expiration timestamp,
+-- for verification of e-mail addresses.
+--
+-- 2005-04-25
+--
+
+ALTER TABLE /*$wgDBprefix*/user
+ ADD COLUMN user_email_authenticated CHAR(14) BINARY,
+ ADD COLUMN user_email_token CHAR(32) BINARY,
+ ADD COLUMN user_email_token_expires CHAR(14) BINARY,
+ ADD INDEX (user_email_token);
array( 'recentchanges', 'rc_patrolled', 'patch-rc-patrol.sql' ),
array( 'user', 'user_real_name', 'patch-user-realname.sql' ),
array( 'user', 'user_token', 'patch-user_token.sql' ),
+ array( 'user', 'user_email_token', 'patch-user_email_token.sql' ),
array( 'user_rights', 'ur_user', 'patch-rename-user_groups-and_rights.sql' ),
array( 'group', 'group_rights', 'patch-userlevels-rights.sql' ),
array( 'logging', 'log_params', 'patch-log_params.sql' ),
function do_user_update() {
global $wgDatabase;
if( $wgDatabase->fieldExists( 'user', 'user_emailauthenticationtimestamp' ) ) {
- echo "EAUTHENT: The user table is already set up for email authentication.\n";
- } else {
- echo "EAUTHENT: Adding user_emailauthenticationtimestamp field for email authentication management.";
- /* ALTER TABLE user ADD (user_emailauthenticationtimestamp varchar(14) binary NOT NULL default '0'); */
+ echo "User table contains old email authentication field. Dropping... ";
dbsource( "maintenance/archives/patch-email-authentication.sql", $wgDatabase );
echo "ok\n";
+ } else {
+ echo "...user table does not contain old email authentication field.\n";
}
}