summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Bugzilla.pm40
-rw-r--r--Bugzilla/Auth.pm2
-rw-r--r--Bugzilla/Config/Auth.pm17
-rw-r--r--Bugzilla/DB/Schema.pm1
-rw-r--r--Bugzilla/Install/DB.pm1
-rw-r--r--Bugzilla/User.pm22
-rwxr-xr-xenter_bug.cgi8
-rw-r--r--skins/standard/global.css18
-rw-r--r--template/en/default/account/prefs/mfa.html.tmpl26
-rw-r--r--template/en/default/admin/params/auth.html.tmpl8
-rw-r--r--template/en/default/global/header.html.tmpl19
-rwxr-xr-xuserprefs.cgi2
12 files changed, 145 insertions, 19 deletions
diff --git a/Bugzilla.pm b/Bugzilla.pm
index 0ffd63e04..2e105e0f5 100644
--- a/Bugzilla.pm
+++ b/Bugzilla.pm
@@ -383,21 +383,49 @@ sub login {
# At this point, we now know if a real person is logged in.
# Check if a password reset is required
- if ($authenticated_user->password_change_required) {
+ my $cgi = Bugzilla->cgi;
+ if ( $authenticated_user->password_change_required ) {
+
# We cannot show the password reset UI for API calls, so treat those as
# a disabled account.
- if (i_am_webservice()) {
- ThrowUserError("account_disabled", { disabled_reason => $authenticated_user->password_change_reason });
+ if ( i_am_webservice() ) {
+ ThrowUserError( "account_disabled", { disabled_reason => $authenticated_user->password_change_reason } );
}
# only allow the reset-password and token pages to handle requests
# (tokens handles the 'forgot password' process)
# otherwise redirect user to the reset-password page.
- if ($ENV{SCRIPT_NAME} !~ m#/(?:reset_password|token)\.cgi$#) {
- print Bugzilla->cgi->redirect('reset_password.cgi');
+ if ( $ENV{SCRIPT_NAME} !~ m#/(?:reset_password|token)\.cgi$# ) {
+ print $cgi->redirect('reset_password.cgi');
exit;
}
}
+ elsif ( !i_am_webservice() && $authenticated_user->in_mfa_group && !$authenticated_user->mfa ) {
+
+ # decide if the user needs a warning or to be blocked.
+ my $date = $authenticated_user->mfa_required_date('UTC');
+ my $grace_period = Bugzilla->params->{mfa_group_grace_period};
+ my $expired = defined $date && $date < DateTime->now;
+ my $on_mfa_page = $cgi->script_name eq '/userprefs.cgi' && $cgi->param('tab') eq 'mfa';
+
+ Bugzilla->request_cache->{mfa_warning} = 1;
+ Bugzilla->request_cache->{mfa_grace_period_expired} = $expired;
+ Bugzilla->request_cache->{on_mfa_page} = $on_mfa_page;
+
+ if ( $grace_period == 0 || $expired) {
+ if (!$on_mfa_page) {
+ print Bugzilla->cgi->redirect("userprefs.cgi?tab=mfa");
+ exit;
+ }
+ }
+ else {
+ my $dbh = Bugzilla->dbh_main;
+ my $date = $dbh->sql_date_math( 'NOW()', '+', '?', 'DAY' );
+ my ($mfa_required_date) = $dbh->selectrow_array( "SELECT $date", undef, $grace_period );
+ $authenticated_user->set_mfa_required_date($mfa_required_date);
+ $authenticated_user->update();
+ }
+ }
# We must now check to see if an sudo session is in progress.
# For a session to be in progress, the following must be true:
@@ -1222,4 +1250,4 @@ information.
=back
-=back
+=back \ No newline at end of file
diff --git a/Bugzilla/Auth.pm b/Bugzilla/Auth.pm
index 797ec1122..58ac248c5 100644
--- a/Bugzilla/Auth.pm
+++ b/Bugzilla/Auth.pm
@@ -111,6 +111,8 @@ sub login {
});
}
+
+
return $self->_handle_login_result($login_info, $type);
}
diff --git a/Bugzilla/Config/Auth.pm b/Bugzilla/Config/Auth.pm
index 58a3d3cd7..612fd1f3f 100644
--- a/Bugzilla/Config/Auth.pm
+++ b/Bugzilla/Config/Auth.pm
@@ -183,6 +183,21 @@ sub get_param_list {
type => 't',
default => '',
},
+
+ {
+ name => 'mfa_group',
+ type => 's',
+ choices => \&get_all_group_names,
+ default => '',
+ checker => \&check_group,
+ },
+
+ {
+ name => 'mfa_group_grace_period',
+ type => 't',
+ default => '7',
+ checker => \&check_numeric,
+ }
);
return @param_list;
}
@@ -234,4 +249,4 @@ sub _check_passwdqc_random_bits {
return "";
}
-1;
+1; \ No newline at end of file
diff --git a/Bugzilla/DB/Schema.pm b/Bugzilla/DB/Schema.pm
index 2c8778c27..7448d8878 100644
--- a/Bugzilla/DB/Schema.pm
+++ b/Bugzilla/DB/Schema.pm
@@ -936,6 +936,7 @@ use constant ABSTRACT_SCHEMA => {
password_change_required => { TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE' },
password_change_reason => { TYPE => 'varchar(64)' },
mfa => {TYPE => 'varchar(8)', DEFAULT => "''" },
+ mfa_required_date => {TYPE => 'DATETIME'},
],
INDEXES => [
profiles_login_name_idx => {FIELDS => ['login_name'],
diff --git a/Bugzilla/Install/DB.pm b/Bugzilla/Install/DB.pm
index 539a7cf78..3b1836c26 100644
--- a/Bugzilla/Install/DB.pm
+++ b/Bugzilla/Install/DB.pm
@@ -746,6 +746,7 @@ sub update_table_definitions {
$dbh->bz_add_column('profiles', 'mfa', { TYPE => 'varchar(8)', , DEFAULT => "''" });
+ $dbh->bz_add_column('profiles', 'mfa_required_date', { TYPE => 'DATETIME' });
_migrate_group_owners();
$dbh->bz_add_column('groups', 'idle_member_removal',
diff --git a/Bugzilla/User.pm b/Bugzilla/User.pm
index 2d8256080..68a3b8313 100644
--- a/Bugzilla/User.pm
+++ b/Bugzilla/User.pm
@@ -80,6 +80,7 @@ sub DB_COLUMNS {
'profiles.password_change_required',
'profiles.password_change_reason',
'profiles.mfa',
+ 'profiles.mfa_required_date'
),
}
@@ -112,6 +113,7 @@ sub UPDATE_COLUMNS {
password_change_required
password_change_reason
mfa
+ mfa_required_date
);
push(@cols, 'cryptpassword') if exists $self->{cryptpassword};
return @cols;
@@ -502,6 +504,11 @@ sub set_mfa {
delete $self->{mfa_provider};
}
+sub set_mfa_required_date {
+ my ($self, $value) = @_;
+ $self->set('mfa_required_date', $value);
+}
+
sub set_groups {
my $self = shift;
$self->_set_groups(GROUP_MEMBERSHIP, @_);
@@ -670,6 +677,12 @@ sub authorizer {
}
sub mfa { $_[0]->{mfa} }
+
+sub mfa_required_date {
+ my $self = shift;
+ return $self->{mfa_required_date} ? datetime_from($self->{mfa_required_date}, @_) : undef;
+}
+
sub mfa_provider {
my ($self) = @_;
my $mfa = $self->{mfa} || return undef;
@@ -679,6 +692,15 @@ sub mfa_provider {
return $self->{mfa_provider};
}
+
+sub in_mfa_group {
+ my $self = shift;
+ return $self->{in_mfa_group} if exists $self->{in_mfa_group};
+
+ my $mfa_group = Bugzilla->params->{mfa_group};
+ return $self->{in_mfa_group} = ($mfa_group && $self->in_group($mfa_group));
+}
+
sub name_or_login {
my $self = shift;
diff --git a/enter_bug.cgi b/enter_bug.cgi
index 0fae8158d..33cdf8535 100755
--- a/enter_bug.cgi
+++ b/enter_bug.cgi
@@ -395,14 +395,14 @@ $vars->{'bug_status'} = \@statuses;
# to the first confirmed bug status on the list, if available.
my $picked_status = formvalue('bug_status');
-if ($picked_status and grep($_->name eq $picked_status, @statuses)) {
+if ( $picked_status and grep( $_->name eq $picked_status, @statuses ) ) {
$default{'bug_status'} = formvalue('bug_status');
-} elsif (scalar @statuses == 1) {
+}
+elsif ( scalar @statuses == 1 ) {
$default{'bug_status'} = $statuses[0]->name;
}
else {
- $default{'bug_status'} = ($statuses[0]->name ne 'UNCONFIRMED')
- ? $statuses[0]->name : $statuses[1]->name;
+ $default{'bug_status'} = ( $statuses[0]->name ne 'UNCONFIRMED' ) ? $statuses[0]->name : $statuses[1]->name;
}
my @groups = $cgi->param('groups');
diff --git a/skins/standard/global.css b/skins/standard/global.css
index e6f63a927..f6579efee 100644
--- a/skins/standard/global.css
+++ b/skins/standard/global.css
@@ -884,9 +884,25 @@ hr {
border-top: 2px solid rgb(255, 255, 255);
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
margin: -15px -15px 0 -15px;
- color: transparent;
}
+#mfa-warning {
+ outline: none;
+ border-color: #FF5300;
+ border-width: 1px;
+ box-shadow: 2px 2px 15px #FF5300;
+ color: black;
+ padding: 2px 2px 2px 2px;
+}
+
+body.mfa-warning #mfa-select button {
+ outline: none;
+ border-color: #FF5300;
+ border-width: 1px;
+ box-shadow: 2px 2px 15px #FF5300;
+}
+
+
#header .subheader {
text-align: left;
padding-left: 10px;
diff --git a/template/en/default/account/prefs/mfa.html.tmpl b/template/en/default/account/prefs/mfa.html.tmpl
index fc748cdd1..99a4b0f2a 100644
--- a/template/en/default/account/prefs/mfa.html.tmpl
+++ b/template/en/default/account/prefs/mfa.html.tmpl
@@ -6,6 +6,8 @@
# defined by the Mozilla Public License, v. 2.0.
#%]
+[% SET MFA_HOWTO = "https://wiki.mozilla.org/BMO/UserGuide/Two-Factor_Authentication" %]
+
[% IF NOT Bugzilla.feature('mfa') %]
<input type="hidden" name="mfa_action" id="mfa-action" value="">
<p>
@@ -126,9 +128,25 @@
</div>
[% ELSE %]
- <p>
- Two-factor authentication is currently <b>disabled</b>.
- </p>
+ [% IF Bugzilla.request_cache.mfa_warning %]
+ <p class="mfa-warning-msg">
+ You <b>must</b> enable two-factor authentication
+ [% UNLESS Bugzilla.request_cache.mfa_grace_period_expired %]
+ before <i>[% Bugzilla.user.mfa_required_date FILTER time %]</i>.
+ After that date, you will be restricted to this page until 2FA is configured.
+ [% ELSE %]
+ before continuing to use [% terms.Bugzilla %].
+ [% END %]
+ </p>
+ <p>
+ <b>Need help setting ip 2FA?</b>
+ You may want to <a href="[% MFA_HOWTO FILTER html %]">read these comprensive instructions</a>.
+ </p>
+ [% ELSE %]
+ <p>
+ Two-factor authentication is currently <b>disabled</b>.
+ </p>
+ [% END %]
<input type="hidden" name="mfa_action" id="mfa-action" value="enable">
<input type="hidden" name="mfa" id="mfa">
@@ -257,4 +275,4 @@
<li>If in doubt, generate and print new recovery codes</li>
<li><b>Do not store these codes electronically</b></li>
</ul>
-[% END %]
+[% END %] \ No newline at end of file
diff --git a/template/en/default/admin/params/auth.html.tmpl b/template/en/default/admin/params/auth.html.tmpl
index 99c52f759..e19712351 100644
--- a/template/en/default/admin/params/auth.html.tmpl
+++ b/template/en/default/admin/params/auth.html.tmpl
@@ -244,5 +244,13 @@
"The 'secret key' for Duo 2FA. This value is provided by your " _
"Duo Security administrator.",
+ mfa_group =>
+ "Members of this group must enable MFA. If the grace period is set, " _
+ "users will receive a warning on every page until end of the grace period. " _
+ "Users without MFA after the grace period (or when it is set to 0) will only " _
+ "be able to access the mfa tab of the user preferences page."
+
+ mfa_group_grace_period =>
+ "Number of days to warn user to turn on 2FA."
},
%]
diff --git a/template/en/default/global/header.html.tmpl b/template/en/default/global/header.html.tmpl
index e808df9bd..1ea652c10 100644
--- a/template/en/default/global/header.html.tmpl
+++ b/template/en/default/global/header.html.tmpl
@@ -39,7 +39,7 @@
# no_body: if true the body element will not be generated
# allow_mobile: allow special CSS and viewport for detected mobile useragents
# use_login_page: display a link to the full login page, rather than an inline login.
- # no_index: Disable search engine from adding page into search index.
+ # no_index: Disable search engine from adding page into search index.
#%]
[% IF message %]
@@ -234,6 +234,9 @@
<body
class="[% urlbase.replace('^https?://','').replace('/$','').replace('[-~@:/.]+','-') FILTER css_class_quote %]
skin-[% user.settings.skin.value FILTER css_class_quote %]
+ [% IF Bugzilla.request_cache.mfa_warning %]
+ mfa-warning
+ [% END %]
[% FOREACH class = bodyclasses %]
[% ' ' %][% class FILTER css_class_quote %]
[% END %] yui-skin-sam">
@@ -252,6 +255,18 @@
</td>
<td>
[% Hook.process("message") %]
+ [% IF Bugzilla.request_cache.mfa_warning
+ AND user.mfa_required_date
+ AND NOT Bugzilla.request_cache.on_mfa_page %]
+ <span id="mfa-warning">
+ Please <a href="userprefs.cgi?tab=mfa">enabled two-factor authentication</a>
+ [% IF Param('mfa_group_grace_period') %]
+ before <i>[% user.mfa_required_date FILTER time %]</i>.
+ [% ELSE %]
+ now.
+ [% END %]
+ </span>
+ [% END %]
</td>
<td id="moz_login">
[% IF user.id %]
@@ -355,4 +370,4 @@
[% BLOCK format_js_link %]
<script [% script_nonce FILTER none %] type="text/javascript" src="[% asset_url FILTER mtime FILTER html %]"></script>
-[% END %]
+[% END %] \ No newline at end of file
diff --git a/userprefs.cgi b/userprefs.cgi
index 7d6a66c6d..00771ceac 100755
--- a/userprefs.cgi
+++ b/userprefs.cgi
@@ -696,7 +696,7 @@ sub SaveMFAupdate {
$user->set_mfa($mfa);
$user->mfa_provider->enrolled();
-
+ Bugzilla->request_cache->{mfa_warning} = 0;
my $settings = Bugzilla->user->settings;
$settings->{api_key_only}->set('on');
clear_settings_cache(Bugzilla->user->id);