2010-04-29 James Robinson <jamesr@chromium.org>
[WebKit-https.git] / BugsSite / token.cgi
1 #!/usr/bin/env perl -wT
2 # -*- Mode: perl; indent-tabs-mode: nil -*-
3 #
4 # The contents of this file are subject to the Mozilla Public
5 # License Version 1.1 (the "License"); you may not use this file
6 # except in compliance with the License. You may obtain a copy of
7 # the License at http://www.mozilla.org/MPL/
8 #
9 # Software distributed under the License is distributed on an "AS
10 # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
11 # implied. See the License for the specific language governing
12 # rights and limitations under the License.
13 #
14 # The Original Code is the Bugzilla Bug Tracking System.
15 #
16 # The Initial Developer of the Original Code is Netscape Communications
17 # Corporation. Portions created by Netscape are
18 # Copyright (C) 1998 Netscape Communications Corporation. All
19 # Rights Reserved.
20 #
21 # Contributor(s): Myk Melez <myk@mozilla.org>
22 #                 Frédéric Buclin <LpSolit@gmail.com>
23
24 ############################################################################
25 # Script Initialization
26 ############################################################################
27
28 # Make it harder for us to do dangerous things in Perl.
29 use strict;
30
31 use lib qw(. lib);
32
33 use Bugzilla;
34 use Bugzilla::Constants;
35 use Bugzilla::Util;
36 use Bugzilla::Error;
37 use Bugzilla::Token;
38 use Bugzilla::User;
39
40 use Date::Parse;
41
42 my $dbh = Bugzilla->dbh;
43 local our $cgi = Bugzilla->cgi;
44 local our $template = Bugzilla->template;
45 local our $vars = {};
46
47 Bugzilla->login(LOGIN_OPTIONAL);
48
49 ################################################################################
50 # Data Validation / Security Authorization
51 ################################################################################
52
53 # Throw an error if the form does not contain an "action" field specifying
54 # what the user wants to do.
55 $cgi->param('a') || ThrowCodeError("unknown_action");
56
57 # Assign the action to a global variable.
58 $::action = $cgi->param('a');
59
60 # If a token was submitted, make sure it is a valid token that exists in the
61 # database and is the correct type for the action being taken.
62 if ($cgi->param('t')) {
63   # Assign the token and its SQL quoted equivalent to global variables.
64   $::token = $cgi->param('t');
65   
66   # Make sure the token contains only valid characters in the right amount.
67   # validate_password will throw an error if token is invalid
68   validate_password($::token);
69
70   Bugzilla::Token::CleanTokenTable();
71
72   # Make sure the token exists in the database.
73   my ($tokentype) = $dbh->selectrow_array('SELECT tokentype FROM tokens
74                                            WHERE token = ?', undef, $::token);
75   $tokentype || ThrowUserError("token_does_not_exist");
76
77   # Make sure the token is the correct type for the action being taken.
78   if ( grep($::action eq $_ , qw(cfmpw cxlpw chgpw)) && $tokentype ne 'password' ) {
79     Bugzilla::Token::Cancel($::token, "wrong_token_for_changing_passwd");
80     ThrowUserError("wrong_token_for_changing_passwd");
81   }
82   if ( ($::action eq 'cxlem') 
83       && (($tokentype ne 'emailold') && ($tokentype ne 'emailnew')) ) {
84     Bugzilla::Token::Cancel($::token, "wrong_token_for_cancelling_email_change");
85     ThrowUserError("wrong_token_for_cancelling_email_change");
86   }
87   if ( grep($::action eq $_ , qw(cfmem chgem)) 
88       && ($tokentype ne 'emailnew') ) {
89     Bugzilla::Token::Cancel($::token, "wrong_token_for_confirming_email_change");
90     ThrowUserError("wrong_token_for_confirming_email_change");
91   }
92   if (($::action =~ /^(request|confirm|cancel)_new_account$/)
93       && ($tokentype ne 'account'))
94   {
95       Bugzilla::Token::Cancel($::token, 'wrong_token_for_creating_account');
96       ThrowUserError('wrong_token_for_creating_account');
97   }
98 }
99
100
101 # If the user is requesting a password change, make sure they submitted
102 # their login name and it exists in the database, and that the DB module is in
103 # the list of allowed verification methods.
104 my $user_account;
105 if ( $::action eq 'reqpw' ) {
106     my $login_name = $cgi->param('loginname')
107                        || ThrowUserError("login_needed_for_password_change");
108
109     # check verification methods
110     unless (Bugzilla->user->authorizer->can_change_password) {
111         ThrowUserError("password_change_requests_not_allowed");
112     }
113
114     validate_email_syntax($login_name)
115         || ThrowUserError('illegal_email_address', {addr => $login_name});
116
117     $user_account = Bugzilla::User->check($login_name);
118 }
119
120 # If the user is changing their password, make sure they submitted a new
121 # password and that the new password is valid.
122 my $password;
123 if ( $::action eq 'chgpw' ) {
124     $password = $cgi->param('password');
125     defined $password
126       && defined $cgi->param('matchpassword')
127       || ThrowUserError("require_new_password");
128
129     validate_password($password, $cgi->param('matchpassword'));
130 }
131
132 ################################################################################
133 # Main Body Execution
134 ################################################################################
135
136 # All calls to this script should contain an "action" variable whose value
137 # determines what the user wants to do.  The code below checks the value of
138 # that variable and runs the appropriate code.
139
140 if ($::action eq 'reqpw') { 
141     requestChangePassword($user_account);
142 } elsif ($::action eq 'cfmpw') { 
143     confirmChangePassword(); 
144 } elsif ($::action eq 'cxlpw') { 
145     cancelChangePassword(); 
146 } elsif ($::action eq 'chgpw') { 
147     changePassword($password);
148 } elsif ($::action eq 'cfmem') {
149     confirmChangeEmail();
150 } elsif ($::action eq 'cxlem') {
151     cancelChangeEmail();
152 } elsif ($::action eq 'chgem') {
153     changeEmail();
154 } elsif ($::action eq 'request_new_account') {
155     request_create_account();
156 } elsif ($::action eq 'confirm_new_account') {
157     confirm_create_account();
158 } elsif ($::action eq 'cancel_new_account') {
159     cancel_create_account();
160 } else { 
161     # If the action that the user wants to take (specified in the "a" form field)
162     # is none of the above listed actions, display an error telling the user 
163     # that we do not understand what they would like to do.
164     ThrowCodeError("unknown_action", { action => $::action });
165 }
166
167 exit;
168
169 ################################################################################
170 # Functions
171 ################################################################################
172
173 sub requestChangePassword {
174     my ($user) = @_;
175     Bugzilla::Token::IssuePasswordToken($user);
176
177     $vars->{'message'} = "password_change_request";
178
179     print $cgi->header();
180     $template->process("global/message.html.tmpl", $vars)
181       || ThrowTemplateError($template->error());
182 }
183
184 sub confirmChangePassword {
185     $vars->{'token'} = $::token;
186     
187     print $cgi->header();
188     $template->process("account/password/set-forgotten-password.html.tmpl", $vars)
189       || ThrowTemplateError($template->error());
190 }
191
192 sub cancelChangePassword {    
193     $vars->{'message'} = "password_change_canceled";
194     Bugzilla::Token::Cancel($::token, $vars->{'message'});
195
196     print $cgi->header();
197     $template->process("global/message.html.tmpl", $vars)
198       || ThrowTemplateError($template->error());
199 }
200
201 sub changePassword {
202     my ($password) = @_;
203     my $dbh = Bugzilla->dbh;
204
205     # Create a crypted version of the new password
206     my $cryptedpassword = bz_crypt($password);
207
208     # Get the user's ID from the tokens table.
209     my ($userid) = $dbh->selectrow_array('SELECT userid FROM tokens
210                                           WHERE token = ?', undef, $::token);
211     
212     # Update the user's password in the profiles table and delete the token
213     # from the tokens table.
214     $dbh->bz_start_transaction();
215     $dbh->do(q{UPDATE   profiles
216                SET      cryptpassword = ?
217                WHERE    userid = ?},
218              undef, ($cryptedpassword, $userid) );
219     $dbh->do('DELETE FROM tokens WHERE token = ?', undef, $::token);
220     $dbh->bz_commit_transaction();
221
222     Bugzilla->logout_user_by_id($userid);
223
224     $vars->{'message'} = "password_changed";
225
226     print $cgi->header();
227     $template->process("global/message.html.tmpl", $vars)
228       || ThrowTemplateError($template->error());
229 }
230
231 sub confirmChangeEmail {
232     # Return HTTP response headers.
233     print $cgi->header();
234
235     $vars->{'token'} = $::token;
236
237     $template->process("account/email/confirm.html.tmpl", $vars)
238       || ThrowTemplateError($template->error());
239 }
240
241 sub changeEmail {
242     my $dbh = Bugzilla->dbh;
243
244     # Get the user's ID from the tokens table.
245     my ($userid, $eventdata) = $dbh->selectrow_array(
246                                  q{SELECT userid, eventdata FROM tokens
247                                    WHERE token = ?}, undef, $::token);
248     my ($old_email, $new_email) = split(/:/,$eventdata);
249
250     # Check the user entered the correct old email address
251     if(lc($cgi->param('email')) ne lc($old_email)) {
252         ThrowUserError("email_confirmation_failed");
253     }
254     # The new email address should be available as this was 
255     # confirmed initially so cancel token if it is not still available
256     if (! is_available_username($new_email,$old_email)) {
257         $vars->{'email'} = $new_email; # Needed for Bugzilla::Token::Cancel's mail
258         Bugzilla::Token::Cancel($::token, "account_exists", $vars);
259         ThrowUserError("account_exists", { email => $new_email } );
260     } 
261
262     # Update the user's login name in the profiles table and delete the token
263     # from the tokens table.
264     $dbh->bz_start_transaction();
265     $dbh->do(q{UPDATE   profiles
266                SET      login_name = ?
267                WHERE    userid = ?},
268              undef, ($new_email, $userid));
269     $dbh->do('DELETE FROM tokens WHERE token = ?', undef, $::token);
270     $dbh->do(q{DELETE FROM tokens WHERE userid = ?
271                AND tokentype = 'emailnew'}, undef, $userid);
272     $dbh->bz_commit_transaction();
273
274     # The email address has been changed, so we need to rederive the groups
275     my $user = new Bugzilla::User($userid);
276     $user->derive_regexp_groups;
277
278     # Return HTTP response headers.
279     print $cgi->header();
280
281     # Let the user know their email address has been changed.
282
283     $vars->{'message'} = "login_changed";
284
285     $template->process("global/message.html.tmpl", $vars)
286       || ThrowTemplateError($template->error());
287 }
288
289 sub cancelChangeEmail {
290     my $dbh = Bugzilla->dbh;
291
292     # Get the user's ID from the tokens table.
293     my ($userid, $tokentype, $eventdata) = $dbh->selectrow_array(
294                               q{SELECT userid, tokentype, eventdata FROM tokens
295                                 WHERE token = ?}, undef, $::token);
296     my ($old_email, $new_email) = split(/:/,$eventdata);
297
298     if($tokentype eq "emailold") {
299         $vars->{'message'} = "emailold_change_canceled";
300
301         my $actualemail = $dbh->selectrow_array(
302                             q{SELECT login_name FROM profiles
303                               WHERE userid = ?}, undef, $userid);
304         
305         # check to see if it has been altered
306         if($actualemail ne $old_email) {
307             $dbh->do(q{UPDATE   profiles
308                        SET      login_name = ?
309                        WHERE    userid = ?},
310                      undef, ($old_email, $userid));
311
312             # email has changed, so rederive groups
313             # Note that this is done _after_ the tables are unlocked
314             # This is sort of a race condition (given the lack of transactions)
315             # but the user had access to it just now, so it's not a security
316             # issue
317
318             my $user = new Bugzilla::User($userid);
319             $user->derive_regexp_groups;
320
321             $vars->{'message'} = "email_change_canceled_reinstated";
322         } 
323     } 
324     else {
325         $vars->{'message'} = 'email_change_canceled'
326      }
327
328     $vars->{'old_email'} = $old_email;
329     $vars->{'new_email'} = $new_email;
330     Bugzilla::Token::Cancel($::token, $vars->{'message'}, $vars);
331
332     $dbh->do(q{DELETE FROM tokens WHERE userid = ?
333                AND tokentype = 'emailold' OR tokentype = 'emailnew'},
334              undef, $userid);
335
336     # Return HTTP response headers.
337     print $cgi->header();
338
339     $template->process("global/message.html.tmpl", $vars)
340       || ThrowTemplateError($template->error());
341 }
342
343 sub request_create_account {
344     my (undef, $date, $login_name) = Bugzilla::Token::GetTokenData($::token);
345     $vars->{'token'} = $::token;
346     $vars->{'email'} = $login_name . Bugzilla->params->{'emailsuffix'};
347     $vars->{'date'} = str2time($date);
348
349     # When 'ssl' equals 'always' or 'authenticated sessions', 
350     # we want this form to always be over SSL.
351     if ($cgi->protocol ne 'https' && Bugzilla->params->{'sslbase'} ne ''
352         && Bugzilla->params->{'ssl'} ne 'never')
353     {
354         $cgi->require_https(Bugzilla->params->{'sslbase'});
355     }
356     print $cgi->header();
357
358     $template->process('account/email/confirm-new.html.tmpl', $vars)
359       || ThrowTemplateError($template->error());
360 }
361
362 sub confirm_create_account {
363     my (undef, undef, $login_name) = Bugzilla::Token::GetTokenData($::token);
364
365     my $password = $cgi->param('passwd1') || '';
366     validate_password($password, $cgi->param('passwd2') || '');
367
368     my $otheruser = Bugzilla::User->create({
369         login_name => $login_name, 
370         realname   => $cgi->param('realname'), 
371         cryptpassword => $password});
372
373     # Now delete this token.
374     delete_token($::token);
375
376     # Let the user know that his user account has been successfully created.
377     $vars->{'message'} = 'account_created';
378     $vars->{'otheruser'} = $otheruser;
379     $vars->{'login_info'} = 1;
380
381     print $cgi->header();
382
383     $template->process('global/message.html.tmpl', $vars)
384       || ThrowTemplateError($template->error());
385 }
386
387 sub cancel_create_account {
388     my (undef, undef, $login_name) = Bugzilla::Token::GetTokenData($::token);
389
390     $vars->{'message'} = 'account_creation_canceled';
391     $vars->{'account'} = $login_name;
392     Bugzilla::Token::Cancel($::token, $vars->{'message'});
393
394     print $cgi->header();
395     $template->process('global/message.html.tmpl', $vars)
396       || ThrowTemplateError($template->error());
397 }