token.cgi 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  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. # Script Initialization
  25. ############################################################################
  26. # Make it harder for us to do dangerous things in Perl.
  27. use strict;
  28. use lib qw(. lib);
  29. use Bugzilla;
  30. use Bugzilla::Constants;
  31. use Bugzilla::Util;
  32. use Bugzilla::Error;
  33. use Bugzilla::Token;
  34. use Bugzilla::User;
  35. use Date::Parse;
  36. my $dbh = Bugzilla->dbh;
  37. local our $cgi = Bugzilla->cgi;
  38. local our $template = Bugzilla->template;
  39. local our $vars = {};
  40. Bugzilla->login(LOGIN_OPTIONAL);
  41. ################################################################################
  42. # Data Validation / Security Authorization
  43. ################################################################################
  44. # Throw an error if the form does not contain an "action" field specifying
  45. # what the user wants to do.
  46. $cgi->param('a') || ThrowCodeError("unknown_action");
  47. # Assign the action to a global variable.
  48. $::action = $cgi->param('a');
  49. # If a token was submitted, make sure it is a valid token that exists in the
  50. # database and is the correct type for the action being taken.
  51. if ($cgi->param('t')) {
  52. # Assign the token and its SQL quoted equivalent to global variables.
  53. $::token = $cgi->param('t');
  54. # Make sure the token contains only valid characters in the right amount.
  55. # validate_password will throw an error if token is invalid
  56. validate_password($::token);
  57. Bugzilla::Token::CleanTokenTable();
  58. # Make sure the token exists in the database.
  59. my ($tokentype) = $dbh->selectrow_array('SELECT tokentype FROM tokens
  60. WHERE token = ?', undef, $::token);
  61. $tokentype || ThrowUserError("token_does_not_exist");
  62. # Make sure the token is the correct type for the action being taken.
  63. if ( grep($::action eq $_ , qw(cfmpw cxlpw chgpw)) && $tokentype ne 'password' ) {
  64. Bugzilla::Token::Cancel($::token, "wrong_token_for_changing_passwd");
  65. ThrowUserError("wrong_token_for_changing_passwd");
  66. }
  67. if ( ($::action eq 'cxlem')
  68. && (($tokentype ne 'emailold') && ($tokentype ne 'emailnew')) ) {
  69. Bugzilla::Token::Cancel($::token, "wrong_token_for_cancelling_email_change");
  70. ThrowUserError("wrong_token_for_cancelling_email_change");
  71. }
  72. if ( grep($::action eq $_ , qw(cfmem chgem))
  73. && ($tokentype ne 'emailnew') ) {
  74. Bugzilla::Token::Cancel($::token, "wrong_token_for_confirming_email_change");
  75. ThrowUserError("wrong_token_for_confirming_email_change");
  76. }
  77. if (($::action =~ /^(request|confirm|cancel)_new_account$/)
  78. && ($tokentype ne 'account'))
  79. {
  80. Bugzilla::Token::Cancel($::token, 'wrong_token_for_creating_account');
  81. ThrowUserError('wrong_token_for_creating_account');
  82. }
  83. }
  84. # If the user is requesting a password change, make sure they submitted
  85. # their login name and it exists in the database, and that the DB module is in
  86. # the list of allowed verification methods.
  87. my $user_account;
  88. if ( $::action eq 'reqpw' ) {
  89. my $login_name = $cgi->param('loginname')
  90. || ThrowUserError("login_needed_for_password_change");
  91. # check verification methods
  92. unless (Bugzilla->user->authorizer->can_change_password) {
  93. ThrowUserError("password_change_requests_not_allowed");
  94. }
  95. validate_email_syntax($login_name)
  96. || ThrowUserError('illegal_email_address', {addr => $login_name});
  97. $user_account = Bugzilla::User->check($login_name);
  98. }
  99. # If the user is changing their password, make sure they submitted a new
  100. # password and that the new password is valid.
  101. my $password;
  102. if ( $::action eq 'chgpw' ) {
  103. $password = $cgi->param('password');
  104. defined $password
  105. && defined $cgi->param('matchpassword')
  106. || ThrowUserError("require_new_password");
  107. validate_password($password, $cgi->param('matchpassword'));
  108. }
  109. ################################################################################
  110. # Main Body Execution
  111. ################################################################################
  112. # All calls to this script should contain an "action" variable whose value
  113. # determines what the user wants to do. The code below checks the value of
  114. # that variable and runs the appropriate code.
  115. if ($::action eq 'reqpw') {
  116. requestChangePassword($user_account);
  117. } elsif ($::action eq 'cfmpw') {
  118. confirmChangePassword();
  119. } elsif ($::action eq 'cxlpw') {
  120. cancelChangePassword();
  121. } elsif ($::action eq 'chgpw') {
  122. changePassword($password);
  123. } elsif ($::action eq 'cfmem') {
  124. confirmChangeEmail();
  125. } elsif ($::action eq 'cxlem') {
  126. cancelChangeEmail();
  127. } elsif ($::action eq 'chgem') {
  128. changeEmail();
  129. } elsif ($::action eq 'request_new_account') {
  130. request_create_account();
  131. } elsif ($::action eq 'confirm_new_account') {
  132. confirm_create_account();
  133. } elsif ($::action eq 'cancel_new_account') {
  134. cancel_create_account();
  135. } else {
  136. # If the action that the user wants to take (specified in the "a" form field)
  137. # is none of the above listed actions, display an error telling the user
  138. # that we do not understand what they would like to do.
  139. ThrowCodeError("unknown_action", { action => $::action });
  140. }
  141. exit;
  142. ################################################################################
  143. # Functions
  144. ################################################################################
  145. sub requestChangePassword {
  146. my ($user) = @_;
  147. Bugzilla::Token::IssuePasswordToken($user);
  148. $vars->{'message'} = "password_change_request";
  149. print $cgi->header();
  150. $template->process("global/message.html.tmpl", $vars)
  151. || ThrowTemplateError($template->error());
  152. }
  153. sub confirmChangePassword {
  154. $vars->{'token'} = $::token;
  155. print $cgi->header();
  156. $template->process("account/password/set-forgotten-password.html.tmpl", $vars)
  157. || ThrowTemplateError($template->error());
  158. }
  159. sub cancelChangePassword {
  160. $vars->{'message'} = "password_change_canceled";
  161. Bugzilla::Token::Cancel($::token, $vars->{'message'});
  162. print $cgi->header();
  163. $template->process("global/message.html.tmpl", $vars)
  164. || ThrowTemplateError($template->error());
  165. }
  166. sub changePassword {
  167. my ($password) = @_;
  168. my $dbh = Bugzilla->dbh;
  169. # Create a crypted version of the new password
  170. my $cryptedpassword = bz_crypt($password);
  171. # Get the user's ID from the tokens table.
  172. my ($userid) = $dbh->selectrow_array('SELECT userid FROM tokens
  173. WHERE token = ?', undef, $::token);
  174. # Update the user's password in the profiles table and delete the token
  175. # from the tokens table.
  176. $dbh->bz_start_transaction();
  177. $dbh->do(q{UPDATE profiles
  178. SET cryptpassword = ?
  179. WHERE userid = ?},
  180. undef, ($cryptedpassword, $userid) );
  181. $dbh->do('DELETE FROM tokens WHERE token = ?', undef, $::token);
  182. $dbh->bz_commit_transaction();
  183. Bugzilla->logout_user_by_id($userid);
  184. $vars->{'message'} = "password_changed";
  185. print $cgi->header();
  186. $template->process("global/message.html.tmpl", $vars)
  187. || ThrowTemplateError($template->error());
  188. }
  189. sub confirmChangeEmail {
  190. # Return HTTP response headers.
  191. print $cgi->header();
  192. $vars->{'token'} = $::token;
  193. $template->process("account/email/confirm.html.tmpl", $vars)
  194. || ThrowTemplateError($template->error());
  195. }
  196. sub changeEmail {
  197. my $dbh = Bugzilla->dbh;
  198. # Get the user's ID from the tokens table.
  199. my ($userid, $eventdata) = $dbh->selectrow_array(
  200. q{SELECT userid, eventdata FROM tokens
  201. WHERE token = ?}, undef, $::token);
  202. my ($old_email, $new_email) = split(/:/,$eventdata);
  203. # Check the user entered the correct old email address
  204. if(lc($cgi->param('email')) ne lc($old_email)) {
  205. ThrowUserError("email_confirmation_failed");
  206. }
  207. # The new email address should be available as this was
  208. # confirmed initially so cancel token if it is not still available
  209. if (! is_available_username($new_email,$old_email)) {
  210. $vars->{'email'} = $new_email; # Needed for Bugzilla::Token::Cancel's mail
  211. Bugzilla::Token::Cancel($::token, "account_exists", $vars);
  212. ThrowUserError("account_exists", { email => $new_email } );
  213. }
  214. # Update the user's login name in the profiles table and delete the token
  215. # from the tokens table.
  216. $dbh->bz_start_transaction();
  217. $dbh->do(q{UPDATE profiles
  218. SET login_name = ?
  219. WHERE userid = ?},
  220. undef, ($new_email, $userid));
  221. $dbh->do('DELETE FROM tokens WHERE token = ?', undef, $::token);
  222. $dbh->do(q{DELETE FROM tokens WHERE userid = ?
  223. AND tokentype = 'emailnew'}, undef, $userid);
  224. $dbh->bz_commit_transaction();
  225. # The email address has been changed, so we need to rederive the groups
  226. my $user = new Bugzilla::User($userid);
  227. $user->derive_regexp_groups;
  228. # Return HTTP response headers.
  229. print $cgi->header();
  230. # Let the user know their email address has been changed.
  231. $vars->{'message'} = "login_changed";
  232. $template->process("global/message.html.tmpl", $vars)
  233. || ThrowTemplateError($template->error());
  234. }
  235. sub cancelChangeEmail {
  236. my $dbh = Bugzilla->dbh;
  237. # Get the user's ID from the tokens table.
  238. my ($userid, $tokentype, $eventdata) = $dbh->selectrow_array(
  239. q{SELECT userid, tokentype, eventdata FROM tokens
  240. WHERE token = ?}, undef, $::token);
  241. my ($old_email, $new_email) = split(/:/,$eventdata);
  242. if($tokentype eq "emailold") {
  243. $vars->{'message'} = "emailold_change_canceled";
  244. my $actualemail = $dbh->selectrow_array(
  245. q{SELECT login_name FROM profiles
  246. WHERE userid = ?}, undef, $userid);
  247. # check to see if it has been altered
  248. if($actualemail ne $old_email) {
  249. $dbh->do(q{UPDATE profiles
  250. SET login_name = ?
  251. WHERE userid = ?},
  252. undef, ($old_email, $userid));
  253. # email has changed, so rederive groups
  254. # Note that this is done _after_ the tables are unlocked
  255. # This is sort of a race condition (given the lack of transactions)
  256. # but the user had access to it just now, so it's not a security
  257. # issue
  258. my $user = new Bugzilla::User($userid);
  259. $user->derive_regexp_groups;
  260. $vars->{'message'} = "email_change_canceled_reinstated";
  261. }
  262. }
  263. else {
  264. $vars->{'message'} = 'email_change_canceled'
  265. }
  266. $vars->{'old_email'} = $old_email;
  267. $vars->{'new_email'} = $new_email;
  268. Bugzilla::Token::Cancel($::token, $vars->{'message'}, $vars);
  269. $dbh->do(q{DELETE FROM tokens WHERE userid = ?
  270. AND tokentype = 'emailold' OR tokentype = 'emailnew'},
  271. undef, $userid);
  272. # Return HTTP response headers.
  273. print $cgi->header();
  274. $template->process("global/message.html.tmpl", $vars)
  275. || ThrowTemplateError($template->error());
  276. }
  277. sub request_create_account {
  278. my (undef, $date, $login_name) = Bugzilla::Token::GetTokenData($::token);
  279. $vars->{'token'} = $::token;
  280. $vars->{'email'} = $login_name . Bugzilla->params->{'emailsuffix'};
  281. $vars->{'date'} = str2time($date);
  282. # When 'ssl' equals 'always' or 'authenticated sessions',
  283. # we want this form to always be over SSL.
  284. if ($cgi->protocol ne 'https' && Bugzilla->params->{'sslbase'} ne ''
  285. && Bugzilla->params->{'ssl'} ne 'never')
  286. {
  287. $cgi->require_https(Bugzilla->params->{'sslbase'});
  288. }
  289. print $cgi->header();
  290. $template->process('account/email/confirm-new.html.tmpl', $vars)
  291. || ThrowTemplateError($template->error());
  292. }
  293. sub confirm_create_account {
  294. my (undef, undef, $login_name) = Bugzilla::Token::GetTokenData($::token);
  295. my $password = $cgi->param('passwd1') || '';
  296. validate_password($password, $cgi->param('passwd2') || '');
  297. my $otheruser = Bugzilla::User->create({
  298. login_name => $login_name,
  299. realname => $cgi->param('realname'),
  300. cryptpassword => $password});
  301. # Now delete this token.
  302. delete_token($::token);
  303. # Let the user know that his user account has been successfully created.
  304. $vars->{'message'} = 'account_created';
  305. $vars->{'otheruser'} = $otheruser;
  306. $vars->{'login_info'} = 1;
  307. print $cgi->header();
  308. $template->process('global/message.html.tmpl', $vars)
  309. || ThrowTemplateError($template->error());
  310. }
  311. sub cancel_create_account {
  312. my (undef, undef, $login_name) = Bugzilla::Token::GetTokenData($::token);
  313. $vars->{'message'} = 'account_creation_canceled';
  314. $vars->{'account'} = $login_name;
  315. Bugzilla::Token::Cancel($::token, $vars->{'message'});
  316. print $cgi->header();
  317. $template->process('global/message.html.tmpl', $vars)
  318. || ThrowTemplateError($template->error());
  319. }