summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Bugzilla.pm7
-rw-r--r--Bugzilla/Token.pm22
-rwxr-xr-xreset_password.cgi58
-rw-r--r--t/hash-sig.t24
-rw-r--r--template/en/default/account/reset-password.html.tmpl16
-rw-r--r--template/en/default/global/user-error.html.tmpl3
6 files changed, 119 insertions, 11 deletions
diff --git a/Bugzilla.pm b/Bugzilla.pm
index 1f01979ea..9bce73010 100644
--- a/Bugzilla.pm
+++ b/Bugzilla.pm
@@ -396,7 +396,12 @@ sub login {
# (tokens handles the 'forgot password' process)
# otherwise redirect user to the reset-password page.
if ( $ENV{SCRIPT_NAME} !~ m#/(?:reset_password|token)\.cgi$# ) {
- print $cgi->redirect('reset_password.cgi');
+ my $self_url = trim($cgi->self_url);
+ my $sig_type = 'prev_url:' . $authenticated_user->id;
+ my $self_url_sig = issue_hash_sig($sig_type, $self_url);
+ my $redir_url = URI->new( correct_urlbase() . "reset_password.cgi" );
+ $redir_url->query_form(prev_url => $self_url, prev_url_sig => $self_url_sig);
+ print $cgi->redirect($redir_url);
exit;
}
}
diff --git a/Bugzilla/Token.pm b/Bugzilla/Token.pm
index c6288f491..4b12f836b 100644
--- a/Bugzilla/Token.pm
+++ b/Bugzilla/Token.pm
@@ -32,6 +32,7 @@ use base qw(Exporter);
issue_auth_delegation_token check_auth_delegation_token
check_token_data delete_token
issue_hash_token check_hash_token
+ issue_hash_sig check_hash_sig
set_token_extra_data get_token_extra_data);
# 128 bits password:
@@ -221,6 +222,27 @@ sub issue_short_lived_session_token {
return _create_token($user->id ? $user->id : undef, 'session.short', $data);
}
+sub issue_hash_sig {
+ my ($type, $data, $salt) = @_;
+ $data //= "";
+ $salt //= generate_random_password(16);
+
+ my $hmac = hmac_sha256_base64(
+ $salt,
+ $type,
+ $data,
+ Bugzilla->localconfig->{site_wide_secret}
+ );
+ return sprintf("%s|%s|%x", $salt, $hmac, length($data));
+}
+
+sub check_hash_sig {
+ my ($type, $sig, $data) = @_;
+ return 0 unless defined $sig && defined $data;
+ my ($salt, undef, $len) = split(/\|/, $sig, 3);
+ return length($data) == hex($len) && $sig eq issue_hash_sig($type, $data, $salt);
+}
+
sub issue_hash_token {
my ($data, $time) = @_;
$data ||= [];
diff --git a/reset_password.cgi b/reset_password.cgi
index 86ace9e12..a79fea063 100755
--- a/reset_password.cgi
+++ b/reset_password.cgi
@@ -17,14 +17,34 @@ use Bugzilla;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Token;
-use Bugzilla::Util qw( bz_crypt );
+use Bugzilla::Util qw( bz_crypt trim );
+use Data::Dumper;
-my $cgi = Bugzilla->cgi;
-my $user = Bugzilla->login(LOGIN_REQUIRED);
-my $template = Bugzilla->template;
-my $dbh = Bugzilla->dbh;
+my $cgi = Bugzilla->cgi;
+my $user = Bugzilla->login(LOGIN_REQUIRED);
+my $template = Bugzilla->template;
+my $dbh = Bugzilla->dbh;
+my $prev_url = $cgi->param('prev_url');
+my $prev_url_sig = $cgi->param('prev_url_sig');
+my $sig_type = 'prev_url:' . $user->id;
+my $prev_url_ok = check_hash_sig($sig_type, $prev_url_sig, $prev_url );
-ThrowUserError('reset_password_denied') unless $user->password_change_required;
+unless ($prev_url_ok) {
+ open my $fh, '>', '/tmp/dump.pl' or die $!;
+ print $fh Dumper([$prev_url, $prev_url_sig]);
+ close $fh or die $!;
+}
+
+unless ($user->password_change_required) {
+ ThrowUserError(
+ 'reset_password_denied',
+ {
+ prev_url_ok => $prev_url_ok,
+ prev_url => $prev_url,
+ }
+ );
+
+}
if ($cgi->param('do_save')) {
my $token = $cgi->param('token');
@@ -64,14 +84,32 @@ if ($cgi->param('do_save')) {
# done
print $cgi->header();
- $template->process('index.html.tmpl', { message => 'password_changed' })
- || ThrowTemplateError($template->error());
+ $template->process(
+ 'account/reset-password.html.tmpl',
+ {
+ message => 'password_changed',
+ prev_url => $prev_url,
+ prev_url_ok => $prev_url_ok,
+ password_changed => 1
+ }
+ ) || ThrowTemplateError( $template->error() );
+
}
else {
my $token = issue_session_token('reset_password');
print $cgi->header();
- $template->process('account/reset-password.html.tmpl', { token => $token })
- || ThrowTemplateError($template->error());
+ $template->process(
+ 'account/reset-password.html.tmpl',
+ {
+ token => $token,
+ prev_url => $prev_url,
+ prev_url_ok => $prev_url_ok,
+ prev_url_sig => $prev_url_sig,
+ sig_type => $sig_type,
+ }
+ ) || ThrowTemplateError( $template->error() );
+
+
}
diff --git a/t/hash-sig.t b/t/hash-sig.t
new file mode 100644
index 000000000..30d3098d4
--- /dev/null
+++ b/t/hash-sig.t
@@ -0,0 +1,24 @@
+# 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 strict;
+use warnings;
+use 5.10.1;
+use lib qw( . lib local/lib/perl5 );
+use Bugzilla::Util qw(generate_random_password);
+use Bugzilla::Token qw(issue_hash_sig check_hash_sig);
+use Test::More;
+
+my $localconfig = { site_wide_secret => generate_random_password(256) };
+{
+ package Bugzilla;
+ sub localconfig { $localconfig }
+}
+
+my $sig = issue_hash_sig("hero", "batman");
+ok(check_hash_sig("hero", $sig, "batman"), "sig for batman checks out");
+
+done_testing(); \ No newline at end of file
diff --git a/template/en/default/account/reset-password.html.tmpl b/template/en/default/account/reset-password.html.tmpl
index ca60c5772..a2bec34fd 100644
--- a/template/en/default/account/reset-password.html.tmpl
+++ b/template/en/default/account/reset-password.html.tmpl
@@ -71,6 +71,20 @@ $(function() {
<h1>Password Reset</h1>
+[% BLOCK link %]
+ <a href="[% prev_url FILTER html %]">[% prev_url FILTER html %]</a>
+[% END %]
+
+[% IF password_changed && prev_url_ok %]
+ <p>Continue to [% PROCESS link %]</p>
+ [% RETURN %]
+[% ELSIF prev_url_ok %]
+ <p>
+ If you've already reset your password, you may continue to [% PROCESS link %]
+ </p>
+[% END %]
+
+
<p>
[% user.password_change_reason || "You are required to update your password." FILTER html %]
</p>
@@ -82,6 +96,8 @@ $(function() {
<form method="POST" action="reset_password.cgi">
<input type="hidden" name="token" value="[% token FILTER html %]">
<input type="hidden" name="do_save" value="1">
+<input type="hidden" name="prev_url" value="[% prev_url FILTER html %]">
+<input type="hidden" name="prev_url_sig" value="[% prev_url_sig FILTER html %]">
<div class="flex">
<div id="password-reset" class="flex-left">
diff --git a/template/en/default/global/user-error.html.tmpl b/template/en/default/global/user-error.html.tmpl
index 3e4d7c4a0..9eefbcb73 100644
--- a/template/en/default/global/user-error.html.tmpl
+++ b/template/en/default/global/user-error.html.tmpl
@@ -1402,6 +1402,9 @@
[% ELSIF error == "reset_password_denied" %]
[% title = "Reset Password Denied" %]
You cannot reset your password without administrative permission.
+ [% IF prev_url_ok %]
+ Continue to <a href="[% prev_url FILTER html %]">[% prev_url FILTER html %]</a>.
+ [% END %]
[% ELSIF error == "no_axes_defined" %]
[% title = "No Axes Defined" %]