userprefs.cgi 16.6 KB
Newer Older
1
#!/usr/bin/perl -wT
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
17
#                 Dan Mosedale <dmose@mozilla.org>
18
#                 Alan Raetz <al_raetz@yahoo.com>
19
#                 David Miller <justdave@syndicomm.com>
20
#                 Christopher Aillon <christopher@aillon.com>
21
#                 Gervase Markham <gerv@gerv.net>
22
#                 Vlad Dascalu <jocuri@softhome.net>
23
#                 Shane H. W. Travis <travis@sedsystems.ca>
24 25 26

use strict;

27 28
use lib qw(.);

29
use Bugzilla;
30
use Bugzilla::Constants;
31
use Bugzilla::Search;
32
use Bugzilla::Util;
33
use Bugzilla::User;
34

35
require "globals.pl";
36

37 38
my $template = Bugzilla->template;
my $vars = {};
39

40 41 42 43 44 45 46
###############################################################################
# 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 {
47
    my $dbh = Bugzilla->dbh;
48 49
    my $user = Bugzilla->user;

50
    ($vars->{'realname'}) = $dbh->selectrow_array(
51
        "SELECT realname FROM profiles WHERE userid = ?", undef, $user->id);
52

53 54
    if(Param('allowemailchange') 
       && Bugzilla->user->authorizer->can_change_email) {
55 56
        my @token = $dbh->selectrow_array(
            "SELECT tokentype, issuedate + " .
57
                    $dbh->sql_interval(3, 'DAY') . ", eventdata
58 59 60
               FROM tokens
              WHERE userid = ?
                AND tokentype LIKE 'email%'
61
           ORDER BY tokentype ASC " . $dbh->sql_limit(1), undef, $user->id);
62 63
        if (scalar(@token) > 0) {
            my ($tokentype, $change_date, $eventdata) = @token;
64 65 66 67 68 69 70 71
            $vars->{'login_change_date'} = $change_date;

            if($tokentype eq 'emailnew') {
                my ($oldemail,$newemail) = split(/:/,$eventdata);
                $vars->{'new_login_name'} = $newemail;
            }
        }
    }
72 73 74
}

sub SaveAccount {
75
    my $cgi = Bugzilla->cgi;
76
    my $dbh = Bugzilla->dbh;
77
    my $user = Bugzilla->user;
78

79 80 81 82
    my $pwd1 = $cgi->param('new_password1');
    my $pwd2 = $cgi->param('new_password2');

    if ($cgi->param('Bugzilla_password') ne "" || 
83
        $pwd1 ne "" || $pwd2 ne "") 
84
    {
85 86
        my ($oldcryptedpwd) = $dbh->selectrow_array(
                        q{SELECT cryptpassword FROM profiles WHERE userid = ?},
87
                        undef, $user->id);
88 89
        $oldcryptedpwd || ThrowCodeError("unable_to_retrieve_password");

90
        if (crypt(scalar($cgi->param('Bugzilla_password')), $oldcryptedpwd) ne 
91 92
                  $oldcryptedpwd) 
        {
93
            ThrowUserError("old_password_incorrect");
94
        }
95 96 97

        if ($pwd1 ne "" || $pwd2 ne "")
        {
98 99
            $cgi->param('new_password1')
              || ThrowUserError("new_password_missing");
100
            validate_password($pwd1, $pwd2);
101 102 103 104 105 106 107 108 109 110 111 112

            if ($cgi->param('Bugzilla_password') ne $pwd1) {
                my $cryptedpassword = bz_crypt($pwd1);
                trick_taint($cryptedpassword); # Only used in a placeholder
                $dbh->do(q{UPDATE profiles
                              SET cryptpassword = ?
                            WHERE userid = ?},
                         undef, ($cryptedpassword, $user->id));

                # Invalidate all logins except for the current one
                Bugzilla->logout(LOGOUT_KEEP_CURRENT);
            }
113
        }
114 115
    }

116 117 118
    if(Param("allowemailchange") && $cgi->param('new_login_name')) {
        my $old_login_name = $cgi->param('Bugzilla_login');
        my $new_login_name = trim($cgi->param('new_login_name'));
119 120

        if($old_login_name ne $new_login_name) {
121
            $cgi->param('Bugzilla_password') 
122
              || ThrowUserError("old_password_required");
123

124
            use Bugzilla::Token;
125
            # Block multiple email changes for the same user.
126
            if (Bugzilla::Token::HasEmailChangeToken($user->id)) {
127
                ThrowUserError("email_change_in_progress");
128 129 130
            }

            # Before changing an email address, confirm one does not exist.
131 132
            validate_email_syntax($new_login_name)
              || ThrowUserError('illegal_email_address', {addr => $new_login_name});
133
            trick_taint($new_login_name);
134
            is_available_username($new_login_name)
135
              || ThrowUserError("account_exists", {email => $new_login_name});
136

137 138
            Bugzilla::Token::IssueEmailChangeToken($user->id, $old_login_name,
                                                   $new_login_name);
139

140
            $vars->{'email_changes_saved'} = 1;
141 142
        }
    }
143

144 145 146
    my $realname = trim($cgi->param('realname'));
    trick_taint($realname); # Only used in a placeholder
    $dbh->do("UPDATE profiles SET realname = ? WHERE userid = ?",
147
             undef, ($realname, $user->id));
148 149 150
}


151
sub DoSettings {
152 153 154
    my $user = Bugzilla->user;

    my $settings = $user->settings;
155
    $vars->{'settings'} = $settings;
156

157
    my @setting_list = keys %$settings;
158
    $vars->{'setting_names'} = \@setting_list;
159 160 161 162 163 164 165 166 167 168

    $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'};
169 170 171 172
}

sub SaveSettings {
    my $cgi = Bugzilla->cgi;
173
    my $user = Bugzilla->user;
174

175 176
    my $settings = $user->settings;
    my @setting_list = keys %$settings;
177 178 179 180

    foreach my $name (@setting_list) {
        next if ! ($settings->{$name}->{'is_enabled'});
        my $value = $cgi->param($name);
181
        my $setting = new Bugzilla::User::Setting($name);
182 183 184

        if ($value eq "${name}-isdefault" ) {
            if (! $settings->{$name}->{'is_default'}) {
185
                $settings->{$name}->reset_to_default;
186 187 188
            }
        }
        else {
189 190
            $setting->validate_value($value);
            $settings->{$name}->set($value);
191 192
        }
    }
193
    $vars->{'settings'} = $user->settings(1);
194 195
}

196
sub DoEmail {
197
    my $dbh = Bugzilla->dbh;
198
    my $user = Bugzilla->user;
199 200 201 202
    
    ###########################################################################
    # User watching
    ###########################################################################
203
    if (Param("supportwatchers")) {
204
        my $watched_ref = $dbh->selectcol_arrayref(
205 206 207
            "SELECT profiles.login_name FROM watch INNER JOIN profiles" .
            " ON watch.watched = profiles.userid" .
            " WHERE watcher = ?",
208
            undef, $user->id);
209
        $vars->{'watchedusers'} = join(',', @$watched_ref);
210 211 212

        my $watcher_ids = $dbh->selectcol_arrayref(
            "SELECT watcher FROM watch WHERE watched = ?",
213
            undef, $user->id);
214 215 216 217 218 219 220 221 222

        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;
223
    }
224

225 226 227
    ###########################################################################
    # Role-based preferences
    ###########################################################################
228 229 230 231
    my $sth = $dbh->prepare("SELECT relationship, event " . 
                            "FROM email_setting " . 
                            "WHERE user_id = ?");
    $sth->execute($user->id);
232 233 234 235 236

    my %mail;
    while (my ($relationship, $event) = $sth->fetchrow_array()) {
        $mail{$relationship}{$event} = 1;
    }
237

238 239
    $vars->{'mail'} = \%mail;      
}
240

241 242 243
sub SaveEmail {
    my $dbh = Bugzilla->dbh;
    my $cgi = Bugzilla->cgi;
244
    my $user = Bugzilla->user;
245
    
246 247 248 249 250 251
    ###########################################################################
    # Role-based preferences
    ###########################################################################
    $dbh->bz_lock_tables("email_setting WRITE");

    # Delete all the user's current preferences
252
    $dbh->do("DELETE FROM email_setting WHERE user_id = ?", undef, $user->id);
253 254 255 256 257 258 259 260

    # Repopulate the table - first, with normal events in the 
    # relationship/event matrix.
    # Note: the database holds only "off" email preferences, as can be implied 
    # from the name of the table - profiles_nomail.
    foreach my $rel (RELATIONSHIPS) {
        # Positive events: a ticked box means "send me mail."
        foreach my $event (POS_EVENTS) {
261 262 263
            if (defined($cgi->param("email-$rel-$event"))
                && $cgi->param("email-$rel-$event") == 1)
            {
264 265
                $dbh->do("INSERT INTO email_setting " . 
                         "(user_id, relationship, event) " . 
266 267
                         "VALUES (?, ?, ?)",
                         undef, ($user->id, $rel, $event));
268
            }
269 270 271 272 273 274 275 276 277
        }
        
        # Negative events: a ticked box means "don't send me mail."
        foreach my $event (NEG_EVENTS) {
            if (!defined($cgi->param("neg-email-$rel-$event")) ||
                $cgi->param("neg-email-$rel-$event") != 1) 
            {
                $dbh->do("INSERT INTO email_setting " . 
                         "(user_id, relationship, event) " . 
278 279
                         "VALUES (?, ?, ?)",
                         undef, ($user->id, $rel, $event));
280
            }
281
        }
282
    }
283

284 285
    # Global positive events: a ticked box means "send me mail."
    foreach my $event (GLOBAL_EVENTS) {
286 287 288
        if (defined($cgi->param("email-" . REL_ANY . "-$event"))
            && $cgi->param("email-" . REL_ANY . "-$event") == 1)
        {
289 290
            $dbh->do("INSERT INTO email_setting " . 
                     "(user_id, relationship, event) " . 
291 292
                     "VALUES (?, ?, ?)",
                     undef, ($user->id, REL_ANY, $event));
293
        }
294
    }
295

296 297 298 299 300
    $dbh->bz_unlock_tables();

    ###########################################################################
    # User watching
    ###########################################################################
301
    if (Param("supportwatchers") && defined $cgi->param('watchedusers')) {
302 303 304 305
        # Just in case.  Note that this much locking is actually overkill:
        # we don't really care if anyone reads the watch table.  So 
        # some small amount of contention could be gotten rid of by
        # using user-defined locks rather than table locking.
306
        $dbh->bz_lock_tables('watch WRITE', 'profiles READ');
307 308

        # what the db looks like now
309 310
        my $old_watch_ids =
            $dbh->selectcol_arrayref("SELECT watched FROM watch"
311
                                   . " WHERE watcher = ?", undef, $user->id);
312 313 314
 
       # The new information given to us by the user.
        my @new_watch_names = split(/[,\s]+/, $cgi->param('watchedusers'));
315
        my %new_watch_ids;
316
        foreach my $username (@new_watch_names) {
317
            my $watched_userid = login_to_id(trim($username), THROW_ERROR);
318
            $new_watch_ids{$watched_userid} = 1;
319
        }
320
        my ($removed, $added) = diff_arrays($old_watch_ids, [keys %new_watch_ids]);
321 322 323 324 325

        # Remove people who were removed.
        my $delete_sth = $dbh->prepare('DELETE FROM watch WHERE watched = ?'
                                     . ' AND watcher = ?');
        foreach my $remove_me (@$removed) {
326
            $delete_sth->execute($remove_me, $user->id);
327 328 329 330 331 332
        }

        # Add people who were added.
        my $insert_sth = $dbh->prepare('INSERT INTO watch (watched, watcher)'
                                     . ' VALUES (?, ?)');
        foreach my $add_me (@$added) {
333
            $insert_sth->execute($add_me, $user->id);
334
        }
335

336
        $dbh->bz_unlock_tables();
337
    }
338 339 340
}


341
sub DoPermissions {
342
    my $dbh = Bugzilla->dbh;
343
    my $user = Bugzilla->user;
344 345
    my (@has_bits, @set_bits);
    
346 347
    my $groups = $dbh->selectall_arrayref(
               "SELECT DISTINCT name, description FROM groups WHERE id IN (" . 
348
               $user->groups_as_string . ") ORDER BY name");
349 350
    foreach my $group (@$groups) {
        my ($nam, $desc) = @$group;
351
        push(@has_bits, {"desc" => $desc, "name" => $nam});
352
    }
353 354 355 356
    $groups = $dbh->selectall_arrayref(
                "SELECT DISTINCT name, description FROM groups ORDER BY name");
    foreach my $group (@$groups) {
        my ($nam, $desc) = @$group;
357
        if ($user->can_bless($nam)) {
358
            push(@set_bits, {"desc" => $desc, "name" => $nam});
359 360
        }
    }
361 362 363
    
    $vars->{'has_bits'} = \@has_bits;
    $vars->{'set_bits'} = \@set_bits;    
364
}
365

366
# No SavePermissions() because this panel has no changeable fields.
367

368

369
sub DoSavedSearches {
370 371
    # 2004-12-13 - colin.ogilvie@gmail.com, bug 274397
    # Need to work around the possibly missing query_format=advanced
372 373 374
    my $user = Bugzilla->user;

    my @queries = @{$user->queries};
375 376
    my @newqueries;
    foreach my $q (@queries) {
377 378 379 380 381 382 383 384 385
        if ($q->{'query'} =~ /query_format=([^&]*)/) {
            my $format = $1;
            if (!IsValidQueryType($format)) {
                if ($format eq "") {
                    $q->{'query'} =~ s/query_format=/query_format=advanced/;
                }
                else {
                    $q->{'query'} .= '&query_format=advanced';
                }
386
            }
387 388
        } else {
            $q->{'query'} .= '&query_format=advanced';
389 390 391 392
        }
        push @newqueries, $q;
    }
    $vars->{'queries'} = \@newqueries;
393 394
}

395
sub SaveSavedSearches {
396 397
    my $cgi = Bugzilla->cgi;
    my $dbh = Bugzilla->dbh;
398 399 400
    my $user = Bugzilla->user;

    my @queries = @{$user->queries};
401 402 403 404 405 406
    my $sth = $dbh->prepare("UPDATE namedqueries SET linkinfooter = ?
                          WHERE userid = ?
                          AND name = ?");
    foreach my $q (@queries) {
        my $linkinfooter = 
            defined($cgi->param("linkinfooter_$q->{'name'}")) ? 1 : 0;
407
            $sth->execute($linkinfooter, $user->id, $q->{'name'});
408 409
    }

410
    $user->flush_queries_cache;
411 412
    
    my $showmybugslink = defined($cgi->param("showmybugslink")) ? 1 : 0;
413 414 415
    $dbh->do("UPDATE profiles SET mybugslink = ? WHERE userid = ?",
             undef, ($showmybugslink, $user->id));    
    $user->{'showmybugslink'} = $showmybugslink;
416
}
417 418


419 420 421
###############################################################################
# Live code (not subroutine definitions) starts here
###############################################################################
422

423 424 425
my $cgi = Bugzilla->cgi;

# This script needs direct access to the username and password CGI variables,
426 427
# so we save them before their removal in Bugzilla->login, and delete them 
# prior to login if we might possibly be in an sudo session.
428 429
my $bugzilla_login    = $cgi->param('Bugzilla_login');
my $bugzilla_password = $cgi->param('Bugzilla_password');
430
$cgi->delete('Bugzilla_login', 'Bugzilla_password') if ($cgi->cookie('sudo'));
431

432
Bugzilla->login(LOGIN_REQUIRED);
433 434
$cgi->param('Bugzilla_login', $bugzilla_login);
$cgi->param('Bugzilla_password', $bugzilla_password);
435 436 437

GetVersionTable();

438
$vars->{'changes_saved'} = $cgi->param('dosave');
439

440
my $current_tab_name = $cgi->param('tab') || "account";
441

442 443 444
# The SWITCH below makes sure that this is valid
trick_taint($current_tab_name);

445
$vars->{'current_tab_name'} = $current_tab_name;
446

447 448 449
# Do any saving, and then display the current tab.
SWITCH: for ($current_tab_name) {
    /^account$/ && do {
450
        SaveAccount() if $cgi->param('dosave');
451 452 453
        DoAccount();
        last SWITCH;
    };
454 455 456 457 458
    /^settings$/ && do {
        SaveSettings() if $cgi->param('dosave');
        DoSettings();
        last SWITCH;
    };
459
    /^email$/ && do {
460
        SaveEmail() if $cgi->param('dosave');
461 462 463 464 465 466 467
        DoEmail();
        last SWITCH;
    };
    /^permissions$/ && do {
        DoPermissions();
        last SWITCH;
    };
468
    /^saved-searches$/ && do {
469
        SaveSavedSearches() if $cgi->param('dosave');
470 471 472
        DoSavedSearches();
        last SWITCH;
    };
473 474
    ThrowUserError("unknown_tab",
                   { current_tab_name => $current_tab_name });
475 476
}

477
# Generate and return the UI (HTML page) from the appropriate template.
478
print $cgi->header();
479 480
$template->process("account/prefs/prefs.html.tmpl", $vars)
  || ThrowTemplateError($template->error());