Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Bugzilla/MFA.pm
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use 5.10.1;
use strict;
use warnings;

use Bugzilla::Error;
use Bugzilla::RNG qw( irand );
use Bugzilla::Token
qw( issue_short_lived_session_token set_token_extra_data get_token_extra_data delete_token );
Expand Down Expand Up @@ -58,6 +59,9 @@ sub check { }
# if false then the mfa provider requires an intermediate verification page
sub can_verify_inline {0}

# if true the provider can use BMO recovery codes
sub can_generate_recovery_codes {0}

# verification

sub verify_prompt {
Expand Down Expand Up @@ -127,6 +131,9 @@ sub verify_check {
sub generate_recovery_codes {
my ($self) = @_;

ThrowUserError('mfa_recovery_not_supported')
unless $self->can_generate_recovery_codes;

my @codes;
foreach my $i (1 .. 10) {

Expand Down
4 changes: 4 additions & 0 deletions Bugzilla/MFA/TOTP.pm
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ sub can_verify_inline {
return 1;
}

sub can_generate_recovery_codes {
return 1;
}

sub _auth {
my ($self) = @_;
return Auth::GoogleAuth->new({
Expand Down
8 changes: 4 additions & 4 deletions docs/en/rst/using/two-factor-authentication.rst
Original file line number Diff line number Diff line change
Expand Up @@ -188,10 +188,10 @@ Do not store recovery codes with your password or on the device that provides
your second factor. If you are unsure whether your codes remain private,
generate and print a new set.

BMO recovery codes cannot replace a Duo verification, even though the 2FA
preferences page offers Duo users the recovery-code generator. Duo users should
configure more than one authentication method in Duo and contact `Mozilla
Service Desk`_ if none of those methods are available.
BMO does not offer its recovery-code generator for Duo accounts because BMO
recovery codes cannot replace a Duo verification. Duo users should configure
more than one authentication method in Duo and contact `Mozilla Service Desk`_
if none of those methods are available.

.. _two-factor-troubleshooting:

Expand Down
49 changes: 47 additions & 2 deletions qa/t/2_test_login_duo.t
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,63 @@ $sel->title_is('User Preferences');
$sel->click_ok('mfa-select-duo');
$sel->type_ok('mfa-duo-user', $config->{admin_user_login});
$sel->type_ok('mfa-password', $config->{admin_user_passwd});
$sel->click_ok('update');
$sel->driver->find_element('//form[@name="userprefsform"]')->submit;
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->click_ok('//a[contains(text(),"Redirect Back")]',
'Click Duo Security verification');
$sel->title_is('User Preferences');
$sel->is_text_present_ok(
'The changes to your two-factor authentication have been saved',
'Duo successfully enabled');

ok(
!$sel->is_element_present('mfa-recovery'),
'Recovery code generation is not offered for Duo'
);

# A forged recovery request must fail before opening the Duo prompt.
$sel->driver->execute_script(
q{document.getElementById('mfa-auth-container').style.display = 'block';}
);
$sel->type_ok('mfa-password', $config->{admin_user_passwd});
$sel->driver->execute_script(q{
const source = document.forms.userprefsform;
const form = document.createElement('form');
form.method = 'post';
form.action = source.action;

[
['tab', 'mfa'],
['token', source.elements.token.value],
['dosave', '1'],
['mfa_action', 'recovery'],
['mfa', 'TOTP'],
['password', source.elements.password.value],
].forEach(([name, value]) => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = name;
input.value = value;
form.appendChild(input);
});

document.body.appendChild(form);
form.submit();
});
sleep(2);
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->title_is('Recovery Codes Unavailable');
$sel->is_text_present_ok(
'Recovery codes are not available for the two-factor authentication method configured on your account',
'Forged Duo recovery request rejected'
);

# Disable Duo for the admin user
$sel->open_ok('/userprefs.cgi?tab=mfa');
$sel->click_ok('mfa-disable');
$sel->type_ok('mfa-password', $config->{admin_user_passwd});
$sel->click_ok('update');
$sel->driver->find_element('//form[@name="userprefsform"]')->submit;
$sel->wait_for_page_to_load(WAIT_TIME);
$sel->click_ok('//a[contains(text(),"Redirect Back")]',
'Click Duo Security verification');
$sel->title_is('User Preferences');
Expand Down
33 changes: 19 additions & 14 deletions template/en/default/account/prefs/mfa.html.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
</iframe>

[% ELSIF user.mfa %]
[% recovery_codes_available = user.mfa_provider.can_generate_recovery_codes %]
[% IF user.mfa != 'Duo' && user.in_duo_required_group && !user.in_duo_excluded_group %]
<p class="mfa-warning-msg">
You are in a group requiring Duo Security for two-factor authentication. You must
Expand Down Expand Up @@ -87,10 +88,12 @@
[% INCLUDE "mfa/protected.html.tmpl" %]
</div>
[% END %]
<div>
<button type="button" id="mfa-recovery">Generate Printable Recovery Codes</button>
[% INCLUDE "mfa/protected.html.tmpl" %]
</div>
[% IF recovery_codes_available %]
<div>
<button type="button" id="mfa-recovery">Generate Printable Recovery Codes</button>
[% INCLUDE "mfa/protected.html.tmpl" %]
</div>
[% END %]
</div>

<p class="mfa-api-blurb">
Expand All @@ -117,16 +120,18 @@
than the current one after setting up the new method.
</p>

<div id="mfa-recovery-container" style="display:none">
<p>
Your current password and verification code is required to generate
recovery codes.
</p>
<ul>
<li><b>Generating recovery codes obsoletes previously generated ones</b></li>
</ul>
[% INCLUDE recovery_blurb %]
</div>
[% IF recovery_codes_available %]
<div id="mfa-recovery-container" style="display:none">
<p>
Your current password and verification code is required to generate
recovery codes.
</p>
<ul>
<li><b>Generating recovery codes obsoletes previously generated ones</b></li>
</ul>
[% INCLUDE recovery_blurb %]
</div>
[% END %]

<div id="mfa-disable-container" style="display:none">
<p>
Expand Down
5 changes: 5 additions & 0 deletions template/en/default/global/user-error.html.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -1263,6 +1263,11 @@
[% title = "Change Denied" %]
You do not have permission to disable MFA for other users.

[% ELSIF error == "mfa_recovery_not_supported" %]
[% title = "Recovery Codes Unavailable" %]
Recovery codes are not available for the two-factor authentication method
configured on your account.

[% ELSIF error == "mfa_already_enrolled" %]
[% title = "Already Enrolled in MFA" %]
You are already enrolled in two-factor authentication and cannot enroll
Expand Down
6 changes: 5 additions & 1 deletion userprefs.cgi
Original file line number Diff line number Diff line change
Expand Up @@ -736,9 +736,13 @@ sub SaveMFA {
ThrowUserError('password_incorrect');
}

my $mfa = $cgi->param('mfa') // $user->mfa;
my $mfa = $action eq 'recovery' ? $user->mfa : ($cgi->param('mfa') // $user->mfa);
my $provider = Bugzilla::MFA->new_from($user, $mfa) // return;

if ($action eq 'recovery' && !$provider->can_generate_recovery_codes) {
ThrowUserError('mfa_recovery_not_supported');
}

my $reason;
if ($action eq 'enable') {
$provider->enroll(Bugzilla->input_params);
Expand Down