diff --git a/.github/checksetup_answers.txt b/.github/checksetup_answers.txt index 272c436c04..ae9d6810c0 100644 --- a/.github/checksetup_answers.txt +++ b/.github/checksetup_answers.txt @@ -1,3 +1,4 @@ +$answer{'ADMIN_LOGIN_NAME'} = 'admin_mozilla_bugs'; $answer{'ADMIN_EMAIL'} = 'admin@mozilla.bugs'; $answer{'ADMIN_OK'} = 'Y'; $answer{'ADMIN_PASSWORD'} = 'Te6Oovohch'; diff --git a/Bugzilla/Auth.pm b/Bugzilla/Auth.pm index e76f59843a..d2bbac3cdc 100644 --- a/Bugzilla/Auth.pm +++ b/Bugzilla/Auth.pm @@ -182,6 +182,10 @@ sub extern_id_used { || $self->{_verifier}->extern_id_used; } +sub can_change_login { + return $_[0]->user_can_create_account; +} + sub can_change_email { return $_[0]->user_can_create_account; } @@ -474,6 +478,14 @@ Returns: C if users are allowed to create new Bugzilla accounts, Description: Whether or not current login system uses extern_id. +=item C + +Description: Whether or not the current login system allows users to + change their own login. +Params: None +Returns: C if users can change their own login, + C otherwise. + =item C Description: Whether or not the current login system allows users to diff --git a/Bugzilla/Auth/Verify.pm b/Bugzilla/Auth/Verify.pm index 1436b37d2b..c305dd8738 100644 --- a/Bugzilla/Auth/Verify.pm +++ b/Bugzilla/Auth/Verify.pm @@ -36,7 +36,8 @@ sub create_or_update_user { my $dbh = Bugzilla->dbh; my $extern_id = $params->{extern_id}; - my $username = $params->{bz_username} || $params->{username}; + my $login = $params->{bz_username} || $params->{username}; + my $email = $params->{email}; my $password = $params->{password} || '*'; my $real_name = $params->{realname} || ''; my $user_id = $params->{user_id}; @@ -44,7 +45,7 @@ sub create_or_update_user { # A passed-in user_id always overrides anything else, for determining # what account we should return. if (!$user_id) { - my $username_user_id = login_to_id($username || ''); + my $login_user_id = login_to_id($login || ''); my $extern_user_id; if ($extern_id) { $extern_user_id = $dbh->selectrow_array( @@ -55,27 +56,27 @@ sub create_or_update_user { # If we have both a valid extern_id and a valid username, and they are # not the same id, then we have a conflict. - if ( $username_user_id + if ( $login_user_id && $extern_user_id - && $username_user_id ne $extern_user_id) + && $login_user_id ne $extern_user_id) { my $extern_name = Bugzilla::User->new($extern_user_id)->login; return { failure => AUTH_ERROR, error => "extern_id_conflict", details => - {extern_id => $extern_id, extern_user => $extern_name, username => $username} + {extern_id => $extern_id, extern_user => $extern_name, username => $login} }; } # If we have a valid username, but no valid id, # then we have to create the user. This happens when we're # passed only a username, and that username doesn't exist already. - if ($username && !$username_user_id && !$extern_user_id) { - validate_email_syntax($username) || return { + if ($login && !$login_user_id && !$extern_user_id) { + validate_email_syntax($email) || return { failure => AUTH_ERROR, error => 'auth_invalid_email', - details => {addr => $username} + details => {addr => $login} }; # external authentication @@ -83,23 +84,24 @@ sub create_or_update_user { # XXX Theoretically this could fail with an error, but the fix for # that is too involved to be done right now. - my $user - = Bugzilla::User->create({ - login_name => $username, cryptpassword => $password, realname => $real_name - }); - $username_user_id = $user->id; + my $user = Bugzilla::User->create({ + login_name => $login, + email => $email, + cryptpassword => $password, + realname => $real_name}); + $login_user_id = $user->id; } # If we have a valid username id and an extern_id, but no valid # extern_user_id, then we have to set the user's extern_id. - if ($extern_id && $username_user_id && !$extern_user_id) { + if ($extern_id && $login_user_id && !$extern_user_id) { $dbh->do('UPDATE profiles SET extern_id = ? WHERE userid = ?', - undef, $extern_id, $username_user_id); - Bugzilla->memcached->clear({table => 'profiles', id => $username_user_id}); + undef, $extern_id, $login_user_id); + Bugzilla->memcached->clear({table => 'profiles', id => $login_user_id}); } # Finally, at this point, one of these will give us a valid user id. - $user_id = $extern_user_id || $username_user_id; + $user_id = $extern_user_id || $login_user_id; } # If we still don't have a valid user_id, then we weren't passed @@ -117,13 +119,13 @@ sub create_or_update_user { # Now that we have a valid User, we need to see if any data has to be # updated. my $user_updated = 0; - if ($username && lc($user->login) ne lc($username)) { - validate_email_syntax($username) || return { + if ($email && lc($user->email) ne lc($email)) { + validate_email_syntax($email) || return { failure => AUTH_ERROR, error => 'auth_invalid_email', - details => {addr => $username} + details => {addr => $email} }; - $user->set_login($username); + $user->set_email($email); $user_updated = 1; } if ($real_name && $user->name ne $real_name) { diff --git a/Bugzilla/Config/Common.pm b/Bugzilla/Config/Common.pm index 14d72115a5..f1388a240f 100644 --- a/Bugzilla/Config/Common.pm +++ b/Bugzilla/Config/Common.pm @@ -79,7 +79,7 @@ sub check_regexp { sub check_email { my ($value) = @_; my ($address) = Email::Address::XS->parse($value); - if (!$address->is_valid) { + if (defined $address && !$address->is_valid) { return "must be a valid email address."; } return ""; diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm index 46512dec9a..a69e792e28 100644 --- a/Bugzilla/Constants.pm +++ b/Bugzilla/Constants.pm @@ -166,6 +166,7 @@ use Memoize; MAX_SUDO_TOKEN_AGE MAX_LOGIN_ATTEMPTS LOGIN_LOCKOUT_INTERVAL + ACCOUNT_CHANGE_INTERVAL MAX_STS_AGE SAFE_PROTOCOLS @@ -473,6 +474,10 @@ use constant MAX_LOGIN_ATTEMPTS => 5; # account is locked. use constant LOGIN_LOCKOUT_INTERVAL => 30; +# The time in minutes a user must wait before they can request another email to +# create a new account or change their password. +use constant ACCOUNT_CHANGE_INTERVAL => 10; + # The maximum number of seconds the Strict-Transport-Security header # will remain valid. BMO uses one year. use constant MAX_STS_AGE => 31536000; diff --git a/Bugzilla/DB/Schema.pm b/Bugzilla/DB/Schema.pm index a11b9639ac..56c1639df3 100644 --- a/Bugzilla/DB/Schema.pm +++ b/Bugzilla/DB/Schema.pm @@ -1016,6 +1016,24 @@ use constant ABSTRACT_SCHEMA => { ], }, + profiles_emails => { + FIELDS => [ + id => {TYPE => 'MEDIUMSERIAL', NOTNULL => 1, PRIMARYKEY => 1}, + user_id => { + TYPE => 'INT3', + NOTNULL => 1, + REFERENCES => {TABLE => 'profiles', COLUMN => 'userid', DELETE => 'CASCADE'} + }, + email => {TYPE => 'varchar(255)', NOTNULL => 1}, + is_primary_email => {TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE'}, + display_order => {TYPE => 'INT1', NOTNULL => 1, DEFAULT => 1}, + ], + INDEXES => [ + profiles_emails_userid_idx => ['user_id'], + profiles_emails_email_idx => {FIELDS => ['email'], TYPE => 'UNIQUE'}, + ], + }, + profile_mfa => { FIELDS => [ id => {TYPE => 'INTSERIAL', NOTNULL => 1, PRIMARYKEY => 1}, diff --git a/Bugzilla/Install.pm b/Bugzilla/Install.pm index c982c072ed..b76258c621 100644 --- a/Bugzilla/Install.pm +++ b/Bugzilla/Install.pm @@ -26,10 +26,12 @@ use Bugzilla::Error; use Bugzilla::Group; use Bugzilla::Product; use Bugzilla::User; +use Bugzilla::User::Email; use Bugzilla::User::Setting; use Bugzilla::Util qw(get_text); use Bugzilla::Version; + use constant STATUS_WORKFLOW => ( [undef, 'UNCONFIRMED'], [undef, 'CONFIRMED'], @@ -420,16 +422,17 @@ sub create_admin { return if $admin_count; my %answer = %{Bugzilla->installation_answers}; - my $login = $answer{'ADMIN_EMAIL'}; + my $login = $answer{'ADMIN_LOGIN_NAME'}; + my $email = $answer{'ADMIN_EMAIL'}; my $password = $answer{'ADMIN_PASSWORD'}; my $full_name = $answer{'ADMIN_REALNAME'}; - if (!$login || !$password || !$full_name) { + if (!$login || !$email || !$password || !$full_name) { print "\n" . get_text('install_admin_setup') . "\n\n"; } while (!$login) { - print get_text('install_admin_get_email') . ' '; + print get_text('install_admin_get_login_name') . ' '; $login = ; chomp $login; eval { Bugzilla::User->check_login_name_for_creation($login); }; @@ -439,6 +442,17 @@ sub create_admin { } } + while (!$email) { + print get_text('install_admin_get_email') . ' '; + $email = ; + chomp $email; + eval { Bugzilla::User::Email->check_email_for_creation($email); }; + if ($@) { + print $@ . "\n"; + undef $email; + } + } + while (!defined $full_name) { print get_text('install_admin_get_name') . ' '; $full_name = ; @@ -451,8 +465,9 @@ sub create_admin { my $admin = Bugzilla::User->create({ - login_name => $login, realname => $full_name, cryptpassword => $password + login_name => $login, email => $email, realname => $full_name, cryptpassword => $password }); + make_admin($admin); } diff --git a/Bugzilla/Install/DB.pm b/Bugzilla/Install/DB.pm index 0265b33507..7254407221 100644 --- a/Bugzilla/Install/DB.pm +++ b/Bugzilla/Install/DB.pm @@ -817,7 +817,8 @@ sub update_table_definitions { $dbh->bz_alter_column('user_request_log', 'attach_id', {TYPE => 'INT5', NOTNULL => 0}); _populate_attachment_storage_class(); - + # Bug 1963773 - topunixguy@gmail.com + _copy_valid_emails_to_profiles_emails(); ################################################################ # New --TABLE-- changes should go *** A B O V E *** this point # ################################################################ @@ -4393,6 +4394,64 @@ sub _populate_attachment_storage_class { } } +sub _copy_valid_emails_to_profiles_emails { + my $dbh = Bugzilla->dbh; + + my ($total) = $dbh->selectrow_array("SELECT COUNT(*) FROM profiles"); + unless ($total) { + print "Skipping profiles_emails population: no profiles to process.\n"; + return; + } + + # Check if 'email' column exists in 'profiles' + my $columns = $dbh->selectcol_arrayref( + "SHOW COLUMNS FROM profiles LIKE 'email'" + ); + my $has_email_column = scalar(@$columns) > 0; + + # Build SELECT statement dynamically + my $select_sql = 'SELECT userid, login_name'; + $select_sql .= ', email' if $has_email_column; + $select_sql .= ' FROM profiles'; + + my $select_sth = $dbh->prepare($select_sql); + my $check_sth = $dbh->prepare('SELECT 1 FROM profiles_emails WHERE user_id = ?'); + my $insert_sth = $dbh->prepare(' + INSERT INTO profiles_emails (user_id, email, is_primary_email, display_order) + VALUES (?, ?, 1, 1) + '); + + $select_sth->execute(); + + while (my $row = $select_sth->fetchrow_hashref) { + my $user_id = $row->{userid}; + + # Skip if the user already has an entry + $check_sth->execute($user_id); + next if $check_sth->fetchrow_array; + + my $email = $has_email_column ? $row->{email} : undef; + my $login = $row->{login_name}; + + my $valid_email; + if (defined $email && $email ne '' && validate_email_syntax($email)) { + $valid_email = $email; + } elsif (defined $login && $login ne '' && validate_email_syntax($login)) { + $valid_email = $login; + } + + next unless defined $valid_email; + + eval { + $insert_sth->execute($user_id, $valid_email); + }; + warn "Failed to insert email for user $user_id: $@" if $@; + } + + $select_sth->finish; + $check_sth->finish; + $insert_sth->finish; +} 1; diff --git a/Bugzilla/Token.pm b/Bugzilla/Token.pm index 96de234de1..1632362a3a 100644 --- a/Bugzilla/Token.pm +++ b/Bugzilla/Token.pm @@ -96,44 +96,44 @@ sub check_auth_delegation_token { # Creates and sends a token to create a new user account. # It assumes that the login has the correct format and is not already in use. sub issue_new_user_account_token { - my $login_name = shift; - my $dbh = Bugzilla->dbh; - my $template = Bugzilla->template; - my $vars = {}; - - # Is there already a pending request for this login name? If yes, do not throw - # an error because the user may have lost their email with the token inside. - # But to prevent using this way to mailbomb an email address, make sure - # the last request is at least 10 minutes old before sending a new email. - - my $pending_requests = $dbh->selectrow_array( - 'SELECT COUNT(*) + my ($login, $email) = @_; + my $dbh = Bugzilla->dbh; + my $template = Bugzilla->template; + my $vars = {}; + + # Is there already a pending request for this email? If yes, do not throw + # an error because the user may have lost their email with the token inside. + # But to prevent using this way to mailbomb an email address, make sure + # the last request is old enough before sending a new email (default: 10 minutes). + + my $regexp = "^$email:"; + my $pending_requests = $dbh->selectrow_array( + 'SELECT COUNT(*) FROM tokens WHERE tokentype = ? - AND ' . $dbh->sql_istrcmp('eventdata', '?') . ' + AND ' . $dbh->sql_regexp('eventdata', $dbh->quote($regexp)) . ' AND issuedate > ' - . $dbh->sql_date_math('NOW()', '-', 10, 'MINUTE'), undef, - ('account', $login_name) - ); + . $dbh->sql_date_math('NOW()', '-', ACCOUNT_CHANGE_INTERVAL, 'MINUTE'), + undef, 'account'); - ThrowUserError('too_soon_for_new_token', {'type' => 'account'}) - if $pending_requests; + ThrowUserError('too_soon_for_new_token', {'type' => 'account'}) if $pending_requests; - my ($token, $token_ts) = _create_token(undef, 'account', $login_name); + my ($token, $token_ts) = _create_token(undef, 'account', "$email:$login"); - $vars->{'email'} = $login_name . Bugzilla->params->{'emailsuffix'}; - $vars->{'expiration_ts'} = ctime($token_ts + MAX_TOKEN_AGE * 86400); - $vars->{'token'} = $token; + $vars->{'login'} = $login; + $vars->{'email'} = $email; + $vars->{'expiration_ts'} = ctime($token_ts + MAX_TOKEN_AGE * 86400); + $vars->{'token'} = $token; - my $message; - $template->process('account/email/request-new.txt.tmpl', $vars, \$message) - || ThrowTemplateError($template->error()); + my $message; + $template->process('account/email/request-new.txt.tmpl', $vars, \$message) + || ThrowTemplateError($template->error()); - # In 99% of cases, the user getting the confirmation email is the same one - # who made the request, and so it is reasonable to send the email in the same - # language used to view the "Create a New Account" page (we cannot use their - # user prefs as the user has no account yet!). - MessageToMTA($message); + # In 99% of cases, the user getting the confirmation email is the same one + # who made the request, and so it is reasonable to send the email in the same + # language used to view the "Create a New Account" page (we cannot use their + # user prefs as the user has no account yet!). + MessageToMTA($message); } sub IssueEmailChangeToken { @@ -388,7 +388,18 @@ sub Cancel { # is no entry in the 'profiles' table. my $user = new Bugzilla::User($userid); - $vars->{'emailaddress'} = $userid ? $user->email : $eventdata; + + if ($userid) { + $vars->{'emailaddress'} = $user->email; + $vars->{'login'} = $user->login; + } + else { + # Be careful! Some logins may contain ":" in them. + my ($email, $login) = split(':', $eventdata, 2); + $vars->{'emailaddress'} = $email; + $vars->{'login'} = $login; + } + $vars->{'remoteaddress'} = remote_ip(); $vars->{'token'} = $token; $vars->{'tokentype'} = $tokentype; diff --git a/Bugzilla/User.pm b/Bugzilla/User.pm index 7ae4f68978..d817dacaf2 100644 --- a/Bugzilla/User.pm +++ b/Bugzilla/User.pm @@ -16,6 +16,7 @@ use Bugzilla::Util; use Bugzilla::Constants; use Bugzilla::Search::Recent; use Bugzilla::User::Setting; +use Bugzilla::User::Email; use Bugzilla::Product; use Bugzilla::Classification; use Bugzilla::Field; @@ -23,6 +24,7 @@ use Bugzilla::Group; use Bugzilla::Hook; use Bugzilla::BugUserLastVisit; + use DateTime::TimeZone; use List::Util qw(max); use List::MoreUtils qw(any); @@ -154,6 +156,15 @@ sub new { $param = {condition => 'extern_id = ?', values => [$param->{extern_id}]}; $_[0] = $param; } + elsif (exists $param->{name}) { + my $email = $param->{name}; + + my $user_id = Bugzilla::User::Email->get_user_by_email($email); + if ($user_id) { + $param->{id} = $user_id; + delete $param->{name}; + } + } } $user = $class->SUPER::new(@_); @@ -326,18 +337,24 @@ sub check_login_name_for_creation { my ($invocant, $name) = @_; $name = trim($name); $name || ThrowUserError('user_login_required'); - validate_email_syntax($name) - || ThrowUserError('illegal_email_address', {addr => $name}); + + # No whitespace + $name !~ /\s/ || ThrowUserError('login_illegal_character'); + + # We set the max length to 127 to ensure logins aren't truncated when + # inserted into the tokens.eventdata field. + length($name) <= 127 or ThrowUserError('login_too_long'); # Check the name if it's a new user, or if we're changing the name. if (!ref($invocant) || $invocant->login ne $name) { is_available_username($name) - || ThrowUserError('account_exists', {email => $name}); + || ThrowUserError('account_exists', {login => $name}); } return $name; } + sub _check_password { my ($self, $pass) = @_; @@ -434,6 +451,19 @@ sub _generate_nickname { return $nick; } +sub set_email { + my ($self, $email) = @_; + # Create a user email account + my $email_data = { + user_id => $self->id, + email => $email, + is_primary_email => 1 + }; + + my $user_email = Bugzilla::User::Email->create($email_data); +} + + sub set_name { my ($self, $name) = @_; $self->set('realname', $name); @@ -612,7 +642,7 @@ sub update_last_seen_date { sub name { $_[0]->{realname}; } sub login { $_[0]->{login_name}; } sub extern_id { $_[0]->{extern_id}; } -sub email { $_[0]->login . Bugzilla->params->{'emailsuffix'}; } +sub email { Bugzilla::User::Email->get_primary_email_of_user($_[0]->{userid}) || $_[0]->login;} sub disabledtext { $_[0]->{'disabledtext'}; } sub is_enabled { $_[0]->{'is_enabled'} ? 1 : 0; } sub showmybugslink { $_[0]->{showmybugslink}; } @@ -2580,8 +2610,11 @@ sub create { $dbh->bz_start_transaction(); $params->{nickname} = _generate_nickname($params->{realname}, $params->{login_name}, 0); + my $email = exists $params->{email} ? delete $params->{email} : $params->{login_name}; my $user = $class->SUPER::create($params); + $user->set_email($email); + # Turn on all email for the new user require Bugzilla::BugMail; my %relationships = Bugzilla::BugMail::relationships(); @@ -2769,21 +2802,22 @@ sub check_account_creation_enabled { } sub check_and_send_account_creation_confirmation { - my ($self, $login) = @_; + my ($self, $login, $email) = @_; $login = $self->check_login_name_for_creation($login); + $email = Bugzilla::User::Email->check_email_for_creation($email); my $creation_regexp = Bugzilla->params->{'createemailregexp'}; - if ($login !~ /$creation_regexp/i) { + if ($email !~ /$creation_regexp/i) { ThrowUserError('account_creation_restricted'); } # BMO - add a hook to allow extra validation prior to account creation. - Bugzilla::Hook::process("user_verify_login", {login => $login}); + Bugzilla::Hook::process("user_verify_login", {login => $login, email => $email}); # Create and send a token for this new account. require Bugzilla::Token; - Bugzilla::Token::issue_new_user_account_token($login); + Bugzilla::Token::issue_new_user_account_token($login, $email); } # This is used in a few performance-critical areas where we don't want to diff --git a/Bugzilla/User/Email.pm b/Bugzilla/User/Email.pm new file mode 100644 index 0000000000..2f1c336e11 --- /dev/null +++ b/Bugzilla/User/Email.pm @@ -0,0 +1,166 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::User::Email; + +use 5.10.1; +use strict; +use warnings; + +use base qw(Bugzilla::Object); + +use Bugzilla::Constants; +use Bugzilla::Util; +use Bugzilla::Error; +use Scalar::Util qw(looks_like_number); + +############# +# Constants # +############# + +use constant DB_TABLE => 'profiles_emails'; +use constant NAME_FIELD => 'email'; + +use constant DB_COLUMNS => qw( + id + user_id + email + is_primary_email + display_order +); + +use constant VALIDATORS => { + user_id => \&_check_user_id, + email => \&check_email_for_creation, + is_primary_email => \&Bugzilla::Object::check_boolean, + display_order => \&_check_display_order, +}; + +use constant UPDATE_COLUMNS => qw(email is_primary_email display_order); + +# There's no gain to caching these objects +use constant USE_MEMCACHED => 0; + +############################### +#### Accessors ###### +############################### + +sub email { return $_[0]->{email}; } +sub user_id { return $_[0]->{'user_id'}; } +sub is_primary_email { return $_[0]->{'is_primary_email'}; } + +############ +# Mutators # +############ + +sub set_email { $_[0]->set('email', $_[1]); } +sub set_primary_email { $_[0]->set('is_primary_email', $_[1]); } +sub set_display_order { $_[0]->set('display_order', $_[1]); } + +############################### +#### Constructors ##### +############################### + +sub new { + my ($class, $params) = @_; + my $user_email = $class->SUPER::new($params); + return $user_email; +} + +sub create { + my ($class, $params) = @_; + my $user_email = $class->SUPER::create($params); + + # Return the newly created user email account. + return $user_email; +} + +sub update { + my ($class, $params) = @_; + my $updated_email = $class->SUPER::update(); + + # Return the updated user email account. + return $updated_email; +} + +sub get_emails_by_user { + my ($class, $user_id) = @_; + my $dbh = Bugzilla->dbh; + my $emails_ref = $dbh->selectall_arrayref("SELECT email, is_primary_email, display_order FROM profiles_emails WHERE user_id = ?", + undef, $user_id); + return $emails_ref || []; +} + +sub get_user_by_email { + my ($class, $email) = @_; + my $dbh = Bugzilla->dbh; + my ($user_id) = $dbh->selectrow_array("SELECT user_id FROM profiles_emails WHERE email = ? LIMIT 1", undef, $email); + # We use the defined-or operator because a user_id might be 0 + return $user_id // 0; +} + +sub get_primary_email_of_user { + my ($class, $user_id) = @_; + my $emails_ref = $class->get_emails_by_user($user_id); + + for my $email_info (@$emails_ref) { + my ($email, $is_primary) = @$email_info; + if ($is_primary == 1) { + return $email; + } + } + + return undef; # No primary email found +} + +sub remove_from_db { + my $self = shift; + return $self->is_primary_email ? 0 : $self->SUPER::remove_from_db(); +} + + + +############################### +### Validators ### +############################### + +sub _check_user_id { + my ($invocant, $id) = @_; + require Bugzilla::User; + return Bugzilla::User->check({id => $id})->id; +} + +sub check_email_for_creation { + my ($invocant, $email) = @_; + + validate_email_syntax($email) + || ThrowUserError('illegal_email_address', {addr => $email}); + + if ($invocant->get_user_by_email($email)) { + ThrowUserError("email_exists", {'email' => $email}) + } + + return $email; +} + +sub _check_display_order { + my ($invocant, $value) = @_; + + unless (defined $value && looks_like_number($value)) { + return 0; + } + if ($value == int($value) && $value >= 1) { + return $value; + } else { + return 0; + } +} + + +1; + +__END__ diff --git a/conf/checksetup_answers.txt b/conf/checksetup_answers.txt index 2c8e7e6b7c..e2bbc716d7 100644 --- a/conf/checksetup_answers.txt +++ b/conf/checksetup_answers.txt @@ -1,3 +1,4 @@ +$answer{'ADMIN_LOGIN_NAME'} = 'admin_bmo_test'; $answer{'ADMIN_EMAIL'} = 'admin@bmo.test'; $answer{'ADMIN_OK'} = 'Y'; $answer{'ADMIN_PASSWORD'} = 'password01!'; diff --git a/createaccount.cgi b/createaccount.cgi index f0fd407305..2aa656e565 100644 --- a/createaccount.cgi +++ b/createaccount.cgi @@ -36,9 +36,11 @@ if (defined($login)) { # the create account form. my $token = $cgi->param('token'); check_hash_token($token, ['create_account']); - - $user->check_and_send_account_creation_confirmation($login); + + my $email = $cgi->param('email'); + $user->check_and_send_account_creation_confirmation($login, $email); $vars->{'login'} = $login; + $vars->{'email'} = $email; $template->process("account/created.html.tmpl", $vars) || ThrowTemplateError($template->error()); diff --git a/docs/en/rst/administering/users.rst b/docs/en/rst/administering/users.rst index 064354bba5..4eface54c8 100644 --- a/docs/en/rst/administering/users.rst +++ b/docs/en/rst/administering/users.rst @@ -27,8 +27,7 @@ will appear in the Administration page. The first screen is a search form to search for existing user accounts. You can run searches based either on the user ID, real -name or login name (i.e. the email address, or just the first part -of the email address if the :param:`emailsuffix` parameter is set). +name, login name or email address. The search can be conducted in different ways using the listbox to the right of the text entry box. You can match by case-insensitive substring (the default), @@ -56,11 +55,13 @@ Once you have found your user, you can change the following fields: - *Login Name*: - This is generally the user's full email address. However, if you - have are using the :param:`emailsuffix` parameter, this may - just be the user's login name. Unless you turn off the + This is the user's login name, which we encourage to be different + from the user's email address and real name. + +- *Email Address*: + This is the user's full email address. Unless you turn off the :param:`allowemailchange` parameter, users can change their - login names themselves (to any valid email address). + email address themselves (to any valid email address). - *Real Name*: The user's real name. Note that Bugzilla does not require this to create an account. diff --git a/editusers.cgi b/editusers.cgi index 1a5b24979c..b2e89f3b17 100755 --- a/editusers.cgi +++ b/editusers.cgi @@ -77,15 +77,28 @@ elsif ($action eq 'list') { my $matchstr = trim($cgi->param('matchstr')); my $matchtype = $cgi->param('matchtype'); my $grouprestrict = $cgi->param('grouprestrict') || '0'; - my $query - = 'SELECT DISTINCT userid, login_name, realname, is_enabled, ' - . $dbh->sql_date_format('last_seen_date', '%Y-%m-%d') - . ' AS last_seen_date ' - . 'FROM profiles'; + my @bindValues; my $nextCondition; my $visibleGroups; + my $select_fields = 'profiles.userid, profiles.login_name, profiles.realname, profiles.is_enabled, ' + . $dbh->sql_date_format('profiles.last_seen_date', '%Y-%m-%d') . ' AS last_seen_date'; + + # Add email as a column from profiles_emails table + $select_fields .= ', profiles_emails.email AS email'; + + my $query = 'SELECT DISTINCT ' . $select_fields . ' FROM profiles'; + + # Join the two tables by userid + $query .= ' INNER JOIN profiles_emails ON profiles.userid = profiles_emails.user_id'; + + my $expr; + if ($matchvalue eq 'email') { + $expr = 'profiles_emails.email'; + $nextCondition = 'WHERE'; + } + # If a group ID is given, make sure it is a valid one. my $group; if ($grouprestrict) { @@ -128,20 +141,21 @@ elsif ($action eq 'list') { # Handle selection by login name, real name, or userid. if (defined($matchtype)) { $query .= " $nextCondition "; - my $expr = ""; - if ($matchvalue eq 'userid') { - if ($matchstr) { - my $stored_matchstr = $matchstr; - detaint_natural($matchstr) - || ThrowUserError('illegal_user_id', {userid => $stored_matchstr}); + if (!$expr) { + if ($matchvalue eq 'userid') { + if ($matchstr) { + my $stored_matchstr = $matchstr; + detaint_natural($matchstr) + || ThrowUserError('illegal_user_id', {userid => $stored_matchstr}); + } + $expr = "profiles.userid"; + } + elsif ($matchvalue eq 'realname') { + $expr = "profiles.realname"; + } + else { + $expr = "profiles.login_name"; } - $expr = "profiles.userid"; - } - elsif ($matchvalue eq 'realname') { - $expr = "profiles.realname"; - } - else { - $expr = "profiles.login_name"; } if ($matchtype =~ /^(regexp|notregexp|exact)$/) { @@ -176,9 +190,9 @@ elsif ($action eq 'list') { } $query .= ' ORDER BY profiles.login_name'; + $vars->{'users'} = $dbh->selectall_arrayref($query, {'Slice' => {}}, @bindValues); - } if ($matchtype && $matchtype eq 'exact' && scalar(@{$vars->{'users'}}) == 1) { diff --git a/extensions/BMO/Extension.pm b/extensions/BMO/Extension.pm index ff587d4785..2da60905dd 100644 --- a/extensions/BMO/Extension.pm +++ b/extensions/BMO/Extension.pm @@ -967,7 +967,7 @@ sub object_end_of_create { my $user = $args->{object}; # Log real IP addresses for auditing - Bugzilla->audit(sprintf('<%s> created user %s', remote_ip(), $user->login)); + Bugzilla->audit(sprintf('<%s> created user %s', remote_ip() || 'unknown', $user->login)); # Add default searches to new user's footer my $dbh = Bugzilla->dbh; diff --git a/qa/config/checksetup_answers.txt b/qa/config/checksetup_answers.txt index 79d9cb90c3..c8a9f73db6 100644 --- a/qa/config/checksetup_answers.txt +++ b/qa/config/checksetup_answers.txt @@ -1,3 +1,4 @@ +$answer{'ADMIN_LOGIN_NAME'} = 'admin_mozilla_bugs'; $answer{'ADMIN_EMAIL'} = 'admin@mozilla.bugs'; $answer{'ADMIN_OK'} = 'Y'; $answer{'ADMIN_PASSWORD'} = 'password'; diff --git a/scripts/entrypoint.pl b/scripts/entrypoint.pl index dc5facf6a2..a61611c148 100755 --- a/scripts/entrypoint.pl +++ b/scripts/entrypoint.pl @@ -123,14 +123,14 @@ sub cmd_dev_httpd { run( 'perl', 'scripts/generate_bmo_data.pl', '--param' => 'use_mailer_queue=0', - 'admin@bmo.test' + 'admin_bmo_test' ); } require Bugzilla; my $answers = Bugzilla->installation_answers($ENV{BZ_ANSWERS_FILE}); my $BZ_URLBASE = $::ENV{'BMO_urlbase'}; - my $LOGIN_USER = "Admin user: $answers->{'ADMIN_EMAIL'}"; + my $LOGIN_USER = "Admin user: $answers->{'ADMIN_LOGIN_NAME'}"; my $LOGIN_PASS = "Admin password: $answers->{'ADMIN_PASSWORD'}"; print <