Remaining pieces of Bug 23067 from yesterday... no idea why the first commit didn't pick these up.

parent 0e583bdb
......@@ -37,6 +37,62 @@ package Token;
# Functions
################################################################################
sub IssueEmailChangeToken {
my ($userid, $old_email, $new_email) = @_;
# Generate a unique token and insert it into the tokens table.
# We have to lock the tokens table before generating the token,
# since the database must be queried for token uniqueness.
&::SendSQL("LOCK TABLES tokens WRITE");
my $token = GenerateUniqueToken();
my $quotedtoken = &::SqlQuote($token);
my $quoted_emails = &::SqlQuote($old_email . ":" . $new_email);
&::SendSQL("INSERT INTO tokens ( userid , issuedate , token ,
tokentype , eventdata )
VALUES ( $userid , NOW() , $quotedtoken ,
'emailold' , $quoted_emails )");
my $newtoken = GenerateUniqueToken();
$quotedtoken = &::SqlQuote($newtoken);
&::SendSQL("INSERT INTO tokens ( userid , issuedate , token ,
tokentype , eventdata )
VALUES ( $userid , NOW() , $quotedtoken ,
'emailnew' , $quoted_emails )");
&::SendSQL("UNLOCK TABLES");
# Mail the user the token along with instructions for using it.
my $template = $::template;
my $vars = $::vars;
$vars->{'oldemailaddress'} = $old_email . &::Param('emailsuffix');
$vars->{'newemailaddress'} = $new_email . &::Param('emailsuffix');
$vars->{'token'} = &::url_quote($token);
$vars->{'emailaddress'} = $old_email . &::Param('emailsuffix');
my $message;
$template->process("token/emailchangeold.txt.tmpl", $vars, \$message)
|| &::DisplayError("Template process failed: " . $template->error())
&& exit;
open SENDMAIL, "|/usr/lib/sendmail -t -i";
print SENDMAIL $message;
close SENDMAIL;
$vars->{'token'} = &::url_quote($newtoken);
$vars->{'emailaddress'} = $new_email . &::Param('emailsuffix');
$message = "";
$template->process("token/emailchangenew.txt.tmpl", $vars, \$message)
|| &::DisplayError("Template process failed: " . $template->error())
&& exit;
open SENDMAIL, "|/usr/lib/sendmail -t -i";
print SENDMAIL $message;
close SENDMAIL;
}
sub IssuePasswordToken {
# Generates a random token, adds it to the tokens table, and sends it
# to the user with instructions for using it to change their password.
......@@ -65,6 +121,14 @@ sub IssuePasswordToken {
}
sub CleanTokenTable {
&::SendSQL("LOCK TABLES tokens WRITE");
&::SendSQL("DELETE FROM tokens
WHERE TO_DAYS(NOW()) - TO_DAYS(issuedate) >= 3");
&::SendSQL("UNLOCK TABLES");
}
sub GenerateUniqueToken {
# Generates a unique random token. Uses &GenerateRandomPassword
# for the tokens themselves and checks uniqueness by searching for
......@@ -143,25 +207,27 @@ sub Cancel {
# Format the user's real name and email address into a single string.
my $username = $realname ? $realname . " <" . $loginname . ">" : $loginname;
# Notify the user via email about the cancellation.
open SENDMAIL, "|/usr/lib/sendmail -t -i";
print SENDMAIL qq|From: bugzilla-daemon
To: $username
Subject: "$tokentype" token cancelled
my $template = $::template;
my $vars = $::vars;
A token was cancelled from $::ENV{'REMOTE_ADDR'}. This is either
an honest mistake or the result of a malicious hack attempt.
Take a look at the information below and forward this email
to $maintainer if you suspect foul play.
$vars->{'emailaddress'} = $username;
$vars->{'maintainer'} = $maintainer;
$vars->{'remoteaddress'} = $::ENV{'REMOTE_ADDR'};
$vars->{'token'} = &::url_quote($token);
$vars->{'tokentype'} = $tokentype;
$vars->{'issuedate'} = $issuedate;
$vars->{'eventdata'} = $eventdata;
$vars->{'cancelaction'} = $cancelaction;
Token: $token
Token Type: $tokentype
User: $username
Issue Date: $issuedate
Event Data: $eventdata
# Notify the user via email about the cancellation.
Cancelled Because: $cancelaction
|;
my $message;
$template->process("token/tokencancel.txt.tmpl", $vars, \$message)
|| &::DisplayError("Template process failed: " . $template->error())
&& exit;
open SENDMAIL, "|/usr/lib/sendmail -t -i";
print SENDMAIL $message;
close SENDMAIL;
# Delete the token from the database.
......@@ -171,14 +237,30 @@ Cancelled Because: $cancelaction
}
sub HasPasswordToken {
# Returns a password token if the user has one. Otherwise returns 0 (false).
# Returns a password token if the user has one.
my ($userid) = @_;
&::SendSQL("SELECT token FROM tokens WHERE userid = $userid LIMIT 1");
&::SendSQL("SELECT token FROM tokens
WHERE userid = $userid AND tokentype = 'password' LIMIT 1");
my ($token) = &::FetchSQLData();
return $token;
}
sub HasEmailChangeToken {
# Returns an email change token if the user has one.
my ($userid) = @_;
&::SendSQL("SELECT token FROM tokens
WHERE userid = $userid
AND tokentype = 'emailnew'
OR tokentype = 'emailold' LIMIT 1");
my ($token) = &::FetchSQLData();
return $token;
}
1;
......@@ -814,6 +814,12 @@ sub confirm_login {
# If this is a new user, generate a password, insert a record
# into the database, and email their password to them.
if ( defined $::FORM{"PleaseMailAPassword"} && !$userid ) {
# Ensure the new login is valid
if(!ValidateNewUser($enteredlogin)) {
DisplayError("Account Exists");
exit;
}
my $password = InsertNewUser($enteredlogin, "");
# There's a template for this - account_created.tmpl - but
# it's easier to wait to use it until templatisation has progressed
......
......@@ -37,6 +37,62 @@ package Token;
# Functions
################################################################################
sub IssueEmailChangeToken {
my ($userid, $old_email, $new_email) = @_;
# Generate a unique token and insert it into the tokens table.
# We have to lock the tokens table before generating the token,
# since the database must be queried for token uniqueness.
&::SendSQL("LOCK TABLES tokens WRITE");
my $token = GenerateUniqueToken();
my $quotedtoken = &::SqlQuote($token);
my $quoted_emails = &::SqlQuote($old_email . ":" . $new_email);
&::SendSQL("INSERT INTO tokens ( userid , issuedate , token ,
tokentype , eventdata )
VALUES ( $userid , NOW() , $quotedtoken ,
'emailold' , $quoted_emails )");
my $newtoken = GenerateUniqueToken();
$quotedtoken = &::SqlQuote($newtoken);
&::SendSQL("INSERT INTO tokens ( userid , issuedate , token ,
tokentype , eventdata )
VALUES ( $userid , NOW() , $quotedtoken ,
'emailnew' , $quoted_emails )");
&::SendSQL("UNLOCK TABLES");
# Mail the user the token along with instructions for using it.
my $template = $::template;
my $vars = $::vars;
$vars->{'oldemailaddress'} = $old_email . &::Param('emailsuffix');
$vars->{'newemailaddress'} = $new_email . &::Param('emailsuffix');
$vars->{'token'} = &::url_quote($token);
$vars->{'emailaddress'} = $old_email . &::Param('emailsuffix');
my $message;
$template->process("token/emailchangeold.txt.tmpl", $vars, \$message)
|| &::DisplayError("Template process failed: " . $template->error())
&& exit;
open SENDMAIL, "|/usr/lib/sendmail -t -i";
print SENDMAIL $message;
close SENDMAIL;
$vars->{'token'} = &::url_quote($newtoken);
$vars->{'emailaddress'} = $new_email . &::Param('emailsuffix');
$message = "";
$template->process("token/emailchangenew.txt.tmpl", $vars, \$message)
|| &::DisplayError("Template process failed: " . $template->error())
&& exit;
open SENDMAIL, "|/usr/lib/sendmail -t -i";
print SENDMAIL $message;
close SENDMAIL;
}
sub IssuePasswordToken {
# Generates a random token, adds it to the tokens table, and sends it
# to the user with instructions for using it to change their password.
......@@ -65,6 +121,14 @@ sub IssuePasswordToken {
}
sub CleanTokenTable {
&::SendSQL("LOCK TABLES tokens WRITE");
&::SendSQL("DELETE FROM tokens
WHERE TO_DAYS(NOW()) - TO_DAYS(issuedate) >= 3");
&::SendSQL("UNLOCK TABLES");
}
sub GenerateUniqueToken {
# Generates a unique random token. Uses &GenerateRandomPassword
# for the tokens themselves and checks uniqueness by searching for
......@@ -143,25 +207,27 @@ sub Cancel {
# Format the user's real name and email address into a single string.
my $username = $realname ? $realname . " <" . $loginname . ">" : $loginname;
# Notify the user via email about the cancellation.
open SENDMAIL, "|/usr/lib/sendmail -t -i";
print SENDMAIL qq|From: bugzilla-daemon
To: $username
Subject: "$tokentype" token cancelled
my $template = $::template;
my $vars = $::vars;
A token was cancelled from $::ENV{'REMOTE_ADDR'}. This is either
an honest mistake or the result of a malicious hack attempt.
Take a look at the information below and forward this email
to $maintainer if you suspect foul play.
$vars->{'emailaddress'} = $username;
$vars->{'maintainer'} = $maintainer;
$vars->{'remoteaddress'} = $::ENV{'REMOTE_ADDR'};
$vars->{'token'} = &::url_quote($token);
$vars->{'tokentype'} = $tokentype;
$vars->{'issuedate'} = $issuedate;
$vars->{'eventdata'} = $eventdata;
$vars->{'cancelaction'} = $cancelaction;
Token: $token
Token Type: $tokentype
User: $username
Issue Date: $issuedate
Event Data: $eventdata
# Notify the user via email about the cancellation.
Cancelled Because: $cancelaction
|;
my $message;
$template->process("token/tokencancel.txt.tmpl", $vars, \$message)
|| &::DisplayError("Template process failed: " . $template->error())
&& exit;
open SENDMAIL, "|/usr/lib/sendmail -t -i";
print SENDMAIL $message;
close SENDMAIL;
# Delete the token from the database.
......@@ -171,14 +237,30 @@ Cancelled Because: $cancelaction
}
sub HasPasswordToken {
# Returns a password token if the user has one. Otherwise returns 0 (false).
# Returns a password token if the user has one.
my ($userid) = @_;
&::SendSQL("SELECT token FROM tokens WHERE userid = $userid LIMIT 1");
&::SendSQL("SELECT token FROM tokens
WHERE userid = $userid AND tokentype = 'password' LIMIT 1");
my ($token) = &::FetchSQLData();
return $token;
}
sub HasEmailChangeToken {
# Returns an email change token if the user has one.
my ($userid) = @_;
&::SendSQL("SELECT token FROM tokens
WHERE userid = $userid
AND tokentype = 'emailnew'
OR tokentype = 'emailold' LIMIT 1");
my ($token) = &::FetchSQLData();
return $token;
}
1;
......@@ -65,7 +65,7 @@ if (defined($login)) {
CheckEmailSyntax($login);
$vars->{'login'} = $login;
if (DBname_to_id($login) != 0) {
if (!ValidateNewUser($login)) {
# Account already exists
$template->process("admin/account_exists.tmpl", $vars)
|| DisplayError("Template process failed: " . $template->error());
......
......@@ -625,6 +625,12 @@ DefParam("allowbugdeletion",
0);
DefParam("allowemailchange",
q{Users can change their own email address through the preferences. Note that the change is validated by emailing both addresses, so switching this option on will not let users use an invalid address.},
"b",
0);
DefParam("allowuserdeletion",
q{The pages to edit users can also let you delete a user. But there is no code that goes and cleans up any references to that user in other tables, so such deletions are kinda scary. So, you have to turn on this option before any such deletions will ever happen.},
"b",
......
......@@ -451,7 +451,7 @@ if ($action eq 'new') {
PutTrailer($localtrailer);
exit;
}
if (TestUser($user)) {
if (!ValidateNewUser($user)) {
print "The user '$user' does already exist. Please press\n";
print "<b>Back</b> and try again.\n";
PutTrailer($localtrailer);
......
......@@ -671,6 +671,8 @@ sub GetVersionTable {
$mtime = 0;
}
if (time() - $mtime > 3600) {
use Token;
Token::CleanTokenTable();
GenerateVersionTable();
}
require 'data/versioncache';
......@@ -686,6 +688,31 @@ sub GetVersionTable {
}
# Validates a given username as a new username
# returns 1 if valid, 0 if invalid
sub ValidateNewUser {
my ($username, $old_username) = @_;
if(DBname_to_id($username) != 0) {
return 0;
}
# Reject if the new login is part of an email change which is
# still in progress
SendSQL("SELECT eventdata FROM tokens WHERE tokentype = 'emailold'
AND eventdata like '%:$username'
OR eventdata like '$username:%'");
if (my ($eventdata) = FetchSQLData()) {
# Allow thru owner of token
if($old_username && ($eventdata eq "$old_username:$username")) {
return 1;
}
return 0;
}
return 1;
}
sub InsertNewUser {
my ($username, $realname) = (@_);
......@@ -963,10 +990,12 @@ sub DBNameToIdAndCheck {
return $result;
}
if ($forceok) {
InsertNewUser($name, "");
$result = DBname_to_id($name);
if ($result > 0) {
return $result;
if(ValidateNewUser($name)) {
InsertNewUser($name, "");
$result = DBname_to_id($name);
if ($result > 0) {
return $result;
}
}
print "Yikes; couldn't create user $name. Please report problem to " .
Param("maintainer") ."\n";
......
......@@ -69,10 +69,13 @@ if ($::FORM{'t'}) {
exit;
}
Token::CleanTokenTable();
# Make sure the token exists in the database.
SendSQL( "SELECT tokentype FROM tokens WHERE token = $::quotedtoken" );
(my $tokentype = FetchSQLData())
|| DisplayError("The token you submitted does not exist.")
|| DisplayError("The token you submitted does not exist, has expired, or has been cancelled.")
&& exit;
# Make sure the token is the correct type for the action being taken.
......@@ -81,6 +84,20 @@ if ($::FORM{'t'}) {
Token::Cancel($::token, "user tried to use token to change password");
exit;
}
if ( ($::action eq 'cxlem')
&& (($tokentype ne 'emailold') && ($tokentype ne 'emailnew')) ) {
DisplayError("That token cannot be used to cancel an email address change.");
Token::Cancel($::token,
"user tried to use token to cancel email address change");
exit;
}
if ( grep($::action eq $_ , qw(cfmem chgem))
&& ($tokentype ne 'emailnew') ) {
DisplayError("That token cannot be used to change your email address.");
Token::Cancel($::token,
"user tried to use token to confirm email address change");
exit;
}
}
# If the user is requesting a password change, make sure they submitted
......@@ -132,6 +149,12 @@ if ($::action eq 'reqpw') {
cancelChangePassword();
} elsif ($::action eq 'chgpw') {
changePassword();
} elsif ($::action eq 'cfmem') {
confirmChangeEmail();
} elsif ($::action eq 'cxlem') {
cancelChangeEmail();
} elsif ($::action eq 'chgem') {
changeEmail();
} else {
# If the action that the user wants to take (specified in the "a" form field)
# is none of the above listed actions, display an error telling the user
......@@ -210,6 +233,110 @@ sub changePassword {
&& exit;
}
sub confirmChangeEmail {
# Return HTTP response headers.
print "Content-Type: text/html\n\n";
$vars->{'title'} = "Confirm Change Email";
$vars->{'token'} = $::token;
$template->process("token/confirmemail.html.tmpl", $vars)
|| &::DisplayError("Template process failed: " . $template->error())
&& exit;
}
sub changeEmail {
# Get the user's ID from the tokens table.
SendSQL("SELECT userid, eventdata FROM tokens
WHERE token = $::quotedtoken");
my ($userid, $eventdata) = FetchSQLData();
my ($old_email, $new_email) = split(/:/,$eventdata);
my $quotednewemail = SqlQuote($new_email);
# Check the user entered the correct old email address
if($::FORM{'email'} ne $old_email) {
DisplayError("Email Address confirmation failed");
exit;
}
# The new email address should be available as this was
# confirmed initially so cancel token if it is not still available
if (! ValidateNewUser($new_email,$old_email)) {
DisplayError("Account $new_email already exists.");
Token::Cancel($::token,"Account $new_email already exists.");
exit;
}
# Update the user's login name in the profiles table and delete the token
# from the tokens table.
SendSQL("LOCK TABLES profiles WRITE , tokens WRITE");
SendSQL("UPDATE profiles
SET login_name = $quotednewemail
WHERE userid = $userid");
SendSQL("DELETE FROM tokens WHERE token = $::quotedtoken");
SendSQL("DELETE FROM tokens WHERE userid = $userid
AND tokentype = 'emailnew'");
SendSQL("UNLOCK TABLES");
# Return HTTP response headers.
print "Content-Type: text/html\n\n";
# Let the user know their email address has been changed.
$vars->{'title'} = "Bugzilla Login Changed";
$vars->{'message'} = "Your Bugzilla login has been changed.";
$template->process("global/message.html.tmpl", $vars)
|| &::DisplayError("Template process failed: " . $template->error())
&& exit;
}
sub cancelChangeEmail {
# Get the user's ID from the tokens table.
SendSQL("SELECT userid, tokentype, eventdata FROM tokens
WHERE token = $::quotedtoken");
my ($userid, $tokentype, $eventdata) = FetchSQLData();
my ($old_email, $new_email) = split(/:/,$eventdata);
if($tokentype eq "emailold") {
$vars->{'message'} = "The request to change the email address " .
"for your account to $new_email has been cancelled.";
SendSQL("SELECT login_name FROM profiles WHERE userid = $userid");
my $actualemail = FetchSQLData();
# check to see if it has been altered
if($actualemail ne $old_email) {
my $quotedoldemail = SqlQuote($old_email);
SendSQL("LOCK TABLES profiles WRITE");
SendSQL("UPDATE profiles
SET login_name = $quotedoldemail
WHERE userid = $userid");
SendSQL("UNLOCK TABLES");
$vars->{'message'} .=
" Your old account settings have been reinstated.";
}
}
else {
$vars->{'message'} = "The request to change the email address " .
"for the $old_email account to $new_email has been cancelled.";
}
Token::Cancel($::token, $vars->{'message'});
SendSQL("LOCK TABLES tokens WRITE");
SendSQL("DELETE FROM tokens
WHERE userid = $userid
AND tokentype = 'emailold' OR tokentype = 'emailnew'");
SendSQL("UNLOCK TABLES");
# Return HTTP response headers.
print "Content-Type: text/html\n\n";
$vars->{'title'} = "Cancel Request to Change Email Address";
$template->process("global/message.html.tmpl", $vars)
|| &::DisplayError("Template process failed: " . $template->error())
&& exit;
}
......@@ -65,16 +65,33 @@ chop $defaultflagstring;
sub DoAccount {
SendSQL("SELECT realname FROM profiles WHERE userid = $userid");
$vars->{'realname'} = FetchSQLData();
if(Param('allowemailchange')) {
SendSQL("SELECT tokentype, issuedate + INTERVAL 3 DAY, eventdata
FROM tokens
WHERE userid = $userid
AND tokentype LIKE 'email%'
ORDER BY tokentype ASC LIMIT 1");
if(MoreSQLData()) {
my ($tokentype, $change_date, $eventdata) = &::FetchSQLData();
$vars->{'login_change_date'} = $change_date;
if($tokentype eq 'emailnew') {
my ($oldemail,$newemail) = split(/:/,$eventdata);
$vars->{'new_login_name'} = $newemail;
}
}
}
}
sub SaveAccount {
my $pwd1 = $::FORM{'new_password1'};
my $pwd2 = $::FORM{'new_password2'};
if ($::FORM{'Bugzilla_password'} ne "" ||
$::FORM{'new_password1'} ne "" ||
$::FORM{'new_password2'} ne "")
$pwd1 ne "" || $pwd2 ne "")
{
my $old = SqlQuote($::FORM{'Bugzilla_password'});
my $pwd1 = SqlQuote($::FORM{'new_password1'});
my $pwd2 = SqlQuote($::FORM{'new_password2'});
SendSQL("SELECT cryptpassword FROM profiles WHERE userid = $userid");
my $oldcryptedpwd = FetchOneColumn();
if (!$oldcryptedpwd) {
......@@ -87,23 +104,63 @@ sub SaveAccount {
DisplayError("You did not enter your old password correctly.");
exit;
}
if ($pwd1 ne $pwd2) {
DisplayError("The two passwords you entered did not match.");
exit;
if ($pwd1 ne "" || $pwd2 ne "")
{
if ($pwd1 ne $pwd2) {
DisplayError("The two passwords you entered did not match.");
exit;
}
if ($::FORM{'new_password1'} eq '') {
DisplayError("You must enter a new password.");
exit;
}
my $passworderror = ValidatePassword($pwd1);
(DisplayError($passworderror) && exit) if $passworderror;
my $cryptedpassword = SqlQuote(Crypt($pwd1));
SendSQL("UPDATE profiles
SET cryptpassword = $cryptedpassword
WHERE userid = $userid");
# Invalidate all logins except for the current one
InvalidateLogins($userid, $::COOKIE{"Bugzilla_logincookie"});
}
if ($::FORM{'new_password1'} eq '') {
DisplayError("You must enter a new password.");
exit;
}
if(Param("allowemailchange") && $::FORM{'new_login_name'}) {
my $old_login_name = $::FORM{'Bugzilla_login'};
my $new_login_name = trim($::FORM{'new_login_name'});
if($old_login_name ne $new_login_name) {
if( $::FORM{'Bugzilla_password'} eq "") {
DisplayError("You must enter your old password to
change email address.");
exit;
}
use Token;
# Block multiple email changes for the same user.
if (Token::HasEmailChangeToken($userid)) {
DisplayError("Email change already in progress;
please check your email.");
exit;
}
# Before changing an email address, confirm one does not exist.
CheckEmailSyntax($new_login_name);
trick_taint($new_login_name);
if (!ValidateNewUser($new_login_name)) {
DisplayError("Account $new_login_name already exists");
exit;
}
Token::IssueEmailChangeToken($userid,$old_login_name,
$new_login_name);
$vars->{'changes_saved'} =
"An email has been sent to both old and new email
addresses to confirm the change of email address.";
}
my $passworderror = ValidatePassword($::FORM{'new_password1'});
(DisplayError($passworderror) && exit) if $passworderror;
my $cryptedpassword = SqlQuote(Crypt($::FORM{'new_password1'}));
SendSQL("UPDATE profiles
SET cryptpassword = $cryptedpassword
WHERE userid = $userid");
# Invalidate all logins except for the current one
InvalidateLogins($userid, $::COOKIE{"Bugzilla_logincookie"});
}
SendSQL("UPDATE profiles SET " .
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment