| #!/usr/bin/perl -T |
| # 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. |
| |
| use 5.10.1; |
| use strict; |
| use warnings; |
| |
| use lib qw(. lib); |
| |
| use Bugzilla; |
| use Bugzilla::BugMail; |
| use Bugzilla::Constants; |
| use Bugzilla::Mailer; |
| use Bugzilla::Search; |
| use Bugzilla::Util; |
| use Bugzilla::Error; |
| use Bugzilla::User; |
| use Bugzilla::User::APIKey; |
| use Bugzilla::User::Setting qw(clear_settings_cache); |
| use Bugzilla::Token; |
| |
| my $template = Bugzilla->template; |
| local our $vars = {}; |
| |
| ############################################################################### |
| # Each panel has two functions - panel Foo has a DoFoo, to get the data |
| # necessary for displaying the panel, and a SaveFoo, to save the panel's |
| # contents from the form data (if appropriate). |
| # SaveFoo may be called before DoFoo. |
| ############################################################################### |
| sub DoAccount { |
| my $dbh = Bugzilla->dbh; |
| my $user = Bugzilla->user; |
| |
| $vars->{'realname'} = $user->name; |
| |
| if (Bugzilla->params->{'allowemailchange'} |
| && $user->authorizer->can_change_email) |
| { |
| # First delete old tokens. |
| Bugzilla::Token::CleanTokenTable(); |
| |
| my @token = $dbh->selectrow_array( |
| "SELECT tokentype, " . |
| $dbh->sql_date_math('issuedate', '+', MAX_TOKEN_AGE, 'DAY') |
| . ", eventdata |
| FROM tokens |
| WHERE userid = ? |
| AND tokentype LIKE 'email%' |
| ORDER BY tokentype ASC " . $dbh->sql_limit(1), undef, $user->id); |
| if (scalar(@token) > 0) { |
| my ($tokentype, $change_date, $eventdata) = @token; |
| $vars->{'login_change_date'} = $change_date; |
| |
| if($tokentype eq 'emailnew') { |
| my ($oldemail,$newemail) = split(/:/,$eventdata); |
| $vars->{'new_login_name'} = $newemail; |
| } |
| } |
| } |
| } |
| |
| sub SaveAccount { |
| my $cgi = Bugzilla->cgi; |
| my $dbh = Bugzilla->dbh; |
| |
| $dbh->bz_start_transaction; |
| |
| my $user = Bugzilla->user; |
| |
| my $oldpassword = $cgi->param('old_password'); |
| my $pwd1 = $cgi->param('new_password1'); |
| my $pwd2 = $cgi->param('new_password2'); |
| my $new_login_name = trim($cgi->param('new_login_name')); |
| |
| if ($user->authorizer->can_change_password |
| && ($oldpassword ne "" || $pwd1 ne "" || $pwd2 ne "")) |
| { |
| my $oldcryptedpwd = $user->cryptpassword; |
| $oldcryptedpwd || ThrowCodeError("unable_to_retrieve_password"); |
| |
| if (bz_crypt($oldpassword, $oldcryptedpwd) ne $oldcryptedpwd) { |
| ThrowUserError("old_password_incorrect"); |
| } |
| |
| if ($pwd1 ne "" || $pwd2 ne "") { |
| $pwd1 || ThrowUserError("new_password_missing"); |
| validate_password($pwd1, $pwd2); |
| |
| if ($oldpassword ne $pwd1) { |
| $user->set_password($pwd1); |
| # Invalidate all logins except for the current one |
| Bugzilla->logout(LOGOUT_KEEP_CURRENT); |
| } |
| } |
| } |
| |
| if ($user->authorizer->can_change_email |
| && Bugzilla->params->{"allowemailchange"} |
| && $new_login_name) |
| { |
| if ($user->login ne $new_login_name) { |
| $oldpassword || ThrowUserError("old_password_required"); |
| |
| # Block multiple email changes for the same user. |
| if (Bugzilla::Token::HasEmailChangeToken($user->id)) { |
| ThrowUserError("email_change_in_progress"); |
| } |
| |
| # Before changing an email address, confirm one does not exist. |
| check_email_syntax($new_login_name); |
| is_available_username($new_login_name) |
| || ThrowUserError("account_exists", {email => $new_login_name}); |
| |
| $vars->{'email_token'} = Bugzilla::Token::IssueEmailChangeToken($new_login_name); |
| $vars->{'email_changes_saved'} = 1; |
| } |
| } |
| |
| $user->set_name($cgi->param('realname')); |
| $user->update({ keep_session => 1, keep_tokens => 1 }); |
| $dbh->bz_commit_transaction; |
| } |
| |
| |
| sub DoSettings { |
| my $user = Bugzilla->user; |
| |
| my $settings = $user->settings; |
| $vars->{'settings'} = $settings; |
| |
| my @setting_list = sort keys %$settings; |
| $vars->{'setting_names'} = \@setting_list; |
| |
| $vars->{'has_settings_enabled'} = 0; |
| # Is there at least one user setting enabled? |
| foreach my $setting_name (@setting_list) { |
| if ($settings->{"$setting_name"}->{'is_enabled'}) { |
| $vars->{'has_settings_enabled'} = 1; |
| last; |
| } |
| } |
| $vars->{'dont_show_button'} = !$vars->{'has_settings_enabled'}; |
| } |
| |
| sub SaveSettings { |
| my $cgi = Bugzilla->cgi; |
| my $user = Bugzilla->user; |
| |
| my $settings = $user->settings; |
| my @setting_list = keys %$settings; |
| |
| foreach my $name (@setting_list) { |
| next if ! ($settings->{$name}->{'is_enabled'}); |
| my $value = $cgi->param($name); |
| next unless defined $value; |
| my $setting = new Bugzilla::User::Setting($name); |
| |
| if ($value eq "${name}-isdefault" ) { |
| if (! $settings->{$name}->{'is_default'}) { |
| $settings->{$name}->reset_to_default; |
| } |
| } |
| else { |
| $setting->validate_value($value); |
| $settings->{$name}->set($value); |
| } |
| } |
| $vars->{'settings'} = $user->settings(1); |
| clear_settings_cache($user->id); |
| } |
| |
| sub DoEmail { |
| my $dbh = Bugzilla->dbh; |
| my $user = Bugzilla->user; |
| |
| ########################################################################### |
| # User watching |
| ########################################################################### |
| my $watched_ref = $dbh->selectcol_arrayref( |
| "SELECT profiles.login_name FROM watch INNER JOIN profiles" . |
| " ON watch.watched = profiles.userid" . |
| " WHERE watcher = ?" . |
| " ORDER BY profiles.login_name", |
| undef, $user->id); |
| $vars->{'watchedusers'} = $watched_ref; |
| |
| my $watcher_ids = $dbh->selectcol_arrayref( |
| "SELECT watcher FROM watch WHERE watched = ?", |
| undef, $user->id); |
| |
| my @watchers; |
| foreach my $watcher_id (@$watcher_ids) { |
| my $watcher = new Bugzilla::User($watcher_id); |
| push(@watchers, Bugzilla::User::identity($watcher)); |
| } |
| |
| @watchers = sort { lc($a) cmp lc($b) } @watchers; |
| $vars->{'watchers'} = \@watchers; |
| } |
| |
| sub SaveEmail { |
| my $dbh = Bugzilla->dbh; |
| my $cgi = Bugzilla->cgi; |
| my $user = Bugzilla->user; |
| |
| Bugzilla::User::match_field({ 'new_watchedusers' => {'type' => 'multi'} }); |
| |
| ########################################################################### |
| # Role-based preferences |
| ########################################################################### |
| $dbh->bz_start_transaction(); |
| |
| my $sth_insert = $dbh->prepare('INSERT INTO email_setting |
| (user_id, relationship, event) VALUES (?, ?, ?)'); |
| |
| my $sth_delete = $dbh->prepare('DELETE FROM email_setting |
| WHERE user_id = ? AND relationship = ? AND event = ?'); |
| # Load current email preferences into memory before updating them. |
| my $settings = $user->mail_settings; |
| |
| # Update the table - first, with normal events in the |
| # relationship/event matrix. |
| my %relationships = Bugzilla::BugMail::relationships(); |
| foreach my $rel (keys %relationships) { |
| next if ($rel == REL_QA && !Bugzilla->params->{'useqacontact'}); |
| # Positive events: a ticked box means "send me mail." |
| foreach my $event (POS_EVENTS) { |
| my $is_set = $cgi->param("email-$rel-$event"); |
| if ($is_set xor $settings->{$rel}{$event}) { |
| if ($is_set) { |
| $sth_insert->execute($user->id, $rel, $event); |
| } |
| else { |
| $sth_delete->execute($user->id, $rel, $event); |
| } |
| } |
| } |
| |
| # Negative events: a ticked box means "don't send me mail." |
| foreach my $event (NEG_EVENTS) { |
| my $is_set = $cgi->param("neg-email-$rel-$event"); |
| if (!$is_set xor $settings->{$rel}{$event}) { |
| if (!$is_set) { |
| $sth_insert->execute($user->id, $rel, $event); |
| } |
| else { |
| $sth_delete->execute($user->id, $rel, $event); |
| } |
| } |
| } |
| } |
| |
| # Global positive events: a ticked box means "send me mail." |
| foreach my $event (GLOBAL_EVENTS) { |
| my $is_set = $cgi->param("email-" . REL_ANY . "-$event"); |
| if ($is_set xor $settings->{+REL_ANY}{$event}) { |
| if ($is_set) { |
| $sth_insert->execute($user->id, REL_ANY, $event); |
| } |
| else { |
| $sth_delete->execute($user->id, REL_ANY, $event); |
| } |
| } |
| } |
| |
| $dbh->bz_commit_transaction(); |
| |
| # We have to clear the cache about email preferences. |
| delete $user->{'mail_settings'}; |
| |
| ########################################################################### |
| # User watching |
| ########################################################################### |
| if (defined $cgi->param('new_watchedusers') |
| || defined $cgi->param('remove_watched_users')) |
| { |
| $dbh->bz_start_transaction(); |
| |
| # Use this to protect error messages on duplicate submissions |
| my $old_watch_ids = |
| $dbh->selectcol_arrayref("SELECT watched FROM watch" |
| . " WHERE watcher = ?", undef, $user->id); |
| |
| # The new information given to us by the user. |
| my $new_watched_users = join(',', $cgi->param('new_watchedusers')) || ''; |
| my @new_watch_names = split(/[,\s]+/, $new_watched_users); |
| my %new_watch_ids; |
| |
| foreach my $username (@new_watch_names) { |
| my $watched_userid = login_to_id(trim($username), THROW_ERROR); |
| $new_watch_ids{$watched_userid} = 1; |
| } |
| |
| # Add people who were added. |
| my $insert_sth = $dbh->prepare('INSERT INTO watch (watched, watcher)' |
| . ' VALUES (?, ?)'); |
| foreach my $add_me (keys(%new_watch_ids)) { |
| next if grep($_ == $add_me, @$old_watch_ids); |
| $insert_sth->execute($add_me, $user->id); |
| } |
| |
| if (defined $cgi->param('remove_watched_users')) { |
| my @removed = $cgi->param('watched_by_you'); |
| # Remove people who were removed. |
| my $delete_sth = $dbh->prepare('DELETE FROM watch WHERE watched = ?' |
| . ' AND watcher = ?'); |
| |
| my %remove_watch_ids; |
| foreach my $username (@removed) { |
| my $watched_userid = login_to_id(trim($username), THROW_ERROR); |
| $remove_watch_ids{$watched_userid} = 1; |
| } |
| foreach my $remove_me (keys(%remove_watch_ids)) { |
| $delete_sth->execute($remove_me, $user->id); |
| } |
| } |
| |
| $dbh->bz_commit_transaction(); |
| } |
| |
| ########################################################################### |
| # Ignore Bugs |
| ########################################################################### |
| my %ignored_bugs = map { $_->{'id'} => 1 } @{$user->bugs_ignored}; |
| |
| # Validate the new bugs to ignore by checking that they exist and also |
| # if the user gave an alias |
| my @add_ignored = split(/[\s,]+/, $cgi->param('add_ignored_bugs')); |
| @add_ignored = map { Bugzilla::Bug->check($_)->id } @add_ignored; |
| map { $ignored_bugs{$_} = 1 } @add_ignored; |
| |
| # Remove any bug ids the user no longer wants to ignore |
| foreach my $key (grep(/^remove_ignored_bug_/, $cgi->param)) { |
| my ($bug_id) = $key =~ /(\d+)$/; |
| delete $ignored_bugs{$bug_id}; |
| } |
| |
| # Update the database with any changes made |
| my ($removed, $added) = diff_arrays([ map { $_->{'id'} } @{$user->bugs_ignored} ], |
| [ keys %ignored_bugs ]); |
| |
| if (scalar @$removed || scalar @$added) { |
| $dbh->bz_start_transaction(); |
| |
| if (scalar @$removed) { |
| $dbh->do('DELETE FROM email_bug_ignore WHERE user_id = ? AND ' . |
| $dbh->sql_in('bug_id', $removed), |
| undef, $user->id); |
| } |
| if (scalar @$added) { |
| my $sth = $dbh->prepare('INSERT INTO email_bug_ignore |
| (user_id, bug_id) VALUES (?, ?)'); |
| $sth->execute($user->id, $_) foreach @$added; |
| } |
| |
| # Reset the cache of ignored bugs if the list changed. |
| delete $user->{bugs_ignored}; |
| |
| $dbh->bz_commit_transaction(); |
| } |
| } |
| |
| |
| sub DoPermissions { |
| my $dbh = Bugzilla->dbh; |
| my $user = Bugzilla->user; |
| my (@has_bits, @set_bits); |
| |
| my $groups = $dbh->selectall_arrayref( |
| "SELECT DISTINCT name, description FROM groups WHERE id IN (" . |
| $user->groups_as_string . ") ORDER BY name"); |
| foreach my $group (@$groups) { |
| my ($nam, $desc) = @$group; |
| push(@has_bits, {"desc" => $desc, "name" => $nam}); |
| } |
| $groups = $dbh->selectall_arrayref('SELECT DISTINCT id, name, description |
| FROM groups |
| ORDER BY name'); |
| foreach my $group (@$groups) { |
| my ($group_id, $nam, $desc) = @$group; |
| if ($user->can_bless($group_id)) { |
| push(@set_bits, {"desc" => $desc, "name" => $nam}); |
| } |
| } |
| |
| # If the user has product specific privileges, inform them about that. |
| foreach my $privs (PER_PRODUCT_PRIVILEGES) { |
| next if $user->in_group($privs); |
| $vars->{"local_$privs"} = $user->get_products_by_permission($privs); |
| } |
| |
| $vars->{'has_bits'} = \@has_bits; |
| $vars->{'set_bits'} = \@set_bits; |
| } |
| |
| # No SavePermissions() because this panel has no changeable fields. |
| |
| |
| sub DoSavedSearches { |
| my $dbh = Bugzilla->dbh; |
| my $user = Bugzilla->user; |
| |
| if ($user->queryshare_groups_as_string) { |
| $vars->{'queryshare_groups'} = |
| Bugzilla::Group->new_from_list($user->queryshare_groups); |
| } |
| $vars->{'bless_group_ids'} = [map { $_->id } @{$user->bless_groups}]; |
| } |
| |
| sub SaveSavedSearches { |
| my $cgi = Bugzilla->cgi; |
| my $dbh = Bugzilla->dbh; |
| my $user = Bugzilla->user; |
| |
| # We'll need this in a loop, so do the call once. |
| my $user_id = $user->id; |
| |
| my $sth_insert_nl = $dbh->prepare('INSERT INTO namedqueries_link_in_footer |
| (namedquery_id, user_id) |
| VALUES (?, ?)'); |
| my $sth_delete_nl = $dbh->prepare('DELETE FROM namedqueries_link_in_footer |
| WHERE namedquery_id = ? |
| AND user_id = ?'); |
| my $sth_insert_ngm = $dbh->prepare('INSERT INTO namedquery_group_map |
| (namedquery_id, group_id) |
| VALUES (?, ?)'); |
| my $sth_update_ngm = $dbh->prepare('UPDATE namedquery_group_map |
| SET group_id = ? |
| WHERE namedquery_id = ?'); |
| my $sth_delete_ngm = $dbh->prepare('DELETE FROM namedquery_group_map |
| WHERE namedquery_id = ?'); |
| |
| # Update namedqueries_link_in_footer for this user. |
| foreach my $q (@{$user->queries}, @{$user->queries_available}) { |
| if (defined $cgi->param("link_in_footer_" . $q->id)) { |
| $sth_insert_nl->execute($q->id, $user_id) if !$q->link_in_footer; |
| } |
| else { |
| $sth_delete_nl->execute($q->id, $user_id) if $q->link_in_footer; |
| } |
| } |
| |
| # For user's own queries, update namedquery_group_map. |
| foreach my $q (@{$user->queries}) { |
| my $group_id; |
| |
| if ($user->in_group(Bugzilla->params->{'querysharegroup'})) { |
| $group_id = $cgi->param("share_" . $q->id) || ''; |
| } |
| |
| if ($group_id) { |
| # Don't allow the user to share queries with groups they're not |
| # allowed to. |
| next unless grep($_ eq $group_id, @{$user->queryshare_groups}); |
| |
| # $group_id is now definitely a valid ID of a group the |
| # user can share queries with, so we can trick_taint. |
| detaint_natural($group_id); |
| if ($q->shared_with_group) { |
| $sth_update_ngm->execute($group_id, $q->id); |
| } |
| else { |
| $sth_insert_ngm->execute($q->id, $group_id); |
| } |
| |
| # If we're sharing our query with a group we can bless, we |
| # have the ability to add link to our search to the footer of |
| # direct group members automatically. |
| if ($user->can_bless($group_id) && $cgi->param('force_' . $q->id)) { |
| my $group = new Bugzilla::Group($group_id); |
| my $members = $group->members_non_inherited; |
| foreach my $member (@$members) { |
| next if $member->id == $user->id; |
| $sth_insert_nl->execute($q->id, $member->id) |
| if !$q->link_in_footer($member); |
| } |
| } |
| } |
| else { |
| # They have unshared that query. |
| if ($q->shared_with_group) { |
| $sth_delete_ngm->execute($q->id); |
| } |
| |
| # Don't remove namedqueries_link_in_footer entries for users |
| # subscribing to the shared query. The idea is that they will |
| # probably want to be subscribers again should the sharing |
| # user choose to share the query again. |
| } |
| } |
| |
| $user->flush_queries_cache; |
| |
| # Update profiles.mybugslink. |
| my $showmybugslink = defined($cgi->param("showmybugslink")) ? 1 : 0; |
| $dbh->do("UPDATE profiles SET mybugslink = ? WHERE userid = ?", |
| undef, ($showmybugslink, $user->id)); |
| $user->{'showmybugslink'} = $showmybugslink; |
| Bugzilla->memcached->clear({ table => 'profiles', id => $user->id }); |
| } |
| |
| |
| sub DoApiKey { |
| my $user = Bugzilla->user; |
| |
| my $api_keys = Bugzilla::User::APIKey->match({ user_id => $user->id }); |
| $vars->{api_keys} = $api_keys; |
| $vars->{any_revoked} = grep { $_->revoked } @$api_keys; |
| } |
| |
| sub SaveApiKey { |
| my $cgi = Bugzilla->cgi; |
| my $dbh = Bugzilla->dbh; |
| my $user = Bugzilla->user; |
| |
| # Do it in a transaction. |
| $dbh->bz_start_transaction; |
| |
| # Update any existing keys |
| my $api_keys = Bugzilla::User::APIKey->match({ user_id => $user->id }); |
| foreach my $api_key (@$api_keys) { |
| my $description = $cgi->param('description_' . $api_key->id); |
| my $revoked = $cgi->param('revoked_' . $api_key->id); |
| |
| if ($description ne $api_key->description |
| || $revoked != $api_key->revoked) |
| { |
| $api_key->set_all({ |
| description => $description, |
| revoked => $revoked, |
| }); |
| $api_key->update(); |
| } |
| } |
| |
| # Create a new API key if requested. |
| if ($cgi->param('new_key')) { |
| $vars->{new_key} = Bugzilla::User::APIKey->create({ |
| user_id => $user->id, |
| description => scalar $cgi->param('new_description'), |
| }); |
| |
| # As a security precaution, we always sent out an e-mail when |
| # an API key is created |
| my $template = Bugzilla->template_inner($user->setting('lang')); |
| my $message; |
| $template->process('email/new-api-key.txt.tmpl', $vars, \$message) |
| || ThrowTemplateError($template->error()); |
| |
| MessageToMTA($message); |
| } |
| |
| $dbh->bz_commit_transaction; |
| } |
| |
| ############################################################################### |
| # Live code (not subroutine definitions) starts here |
| ############################################################################### |
| |
| my $cgi = Bugzilla->cgi; |
| |
| # Delete credentials before logging in in case we are in a sudo session. |
| $cgi->delete('Bugzilla_login', 'Bugzilla_password') if ($cgi->cookie('sudo')); |
| $cgi->delete('GoAheadAndLogIn'); |
| |
| # First try to get credentials from cookies. |
| my $user = Bugzilla->login(LOGIN_OPTIONAL); |
| |
| if (!$user->id) { |
| # Use credentials given in the form if login cookies are not available. |
| $cgi->param('Bugzilla_login', $cgi->param('old_login')); |
| $cgi->param('Bugzilla_password', $cgi->param('old_password')); |
| } |
| Bugzilla->login(LOGIN_REQUIRED); |
| |
| my $save_changes = $cgi->param('dosave'); |
| $vars->{'changes_saved'} = $save_changes; |
| |
| my $current_tab_name = $cgi->param('tab') || "settings"; |
| |
| # The SWITCH below makes sure that this is valid |
| trick_taint($current_tab_name); |
| |
| $vars->{'current_tab_name'} = $current_tab_name; |
| |
| my $token = $cgi->param('token'); |
| check_token_data($token, 'edit_user_prefs') if $save_changes; |
| |
| # Do any saving, and then display the current tab. |
| SWITCH: for ($current_tab_name) { |
| |
| # Extensions must set it to 1 to confirm the tab is valid. |
| my $handled = 0; |
| Bugzilla::Hook::process('user_preferences', |
| { 'vars' => $vars, |
| save_changes => $save_changes, |
| current_tab => $current_tab_name, |
| handled => \$handled }); |
| last SWITCH if $handled; |
| |
| /^account$/ && do { |
| SaveAccount() if $save_changes; |
| DoAccount(); |
| last SWITCH; |
| }; |
| /^settings$/ && do { |
| SaveSettings() if $save_changes; |
| DoSettings(); |
| last SWITCH; |
| }; |
| /^email$/ && do { |
| SaveEmail() if $save_changes; |
| DoEmail(); |
| last SWITCH; |
| }; |
| /^permissions$/ && do { |
| DoPermissions(); |
| last SWITCH; |
| }; |
| /^saved-searches$/ && do { |
| SaveSavedSearches() if $save_changes; |
| DoSavedSearches(); |
| last SWITCH; |
| }; |
| /^apikey$/ && do { |
| SaveApiKey() if $save_changes; |
| DoApiKey(); |
| last SWITCH; |
| }; |
| |
| ThrowUserError("unknown_tab", |
| { current_tab_name => $current_tab_name }); |
| } |
| |
| delete_token($token) if $save_changes; |
| if ($current_tab_name ne 'permissions') { |
| $vars->{'token'} = issue_session_token('edit_user_prefs'); |
| } |
| |
| # Generate and return the UI (HTML page) from the appropriate template. |
| print $cgi->header(); |
| $template->process("account/prefs/prefs.html.tmpl", $vars) |
| || ThrowTemplateError($template->error()); |