# 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:
=back
-=back
+=back
\ No newline at end of file
});
}
+
+
return $self->_handle_login_result($login_info, $type);
}
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;
}
return "";
}
-1;
+1;
\ No newline at end of file
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'],
$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',
'profiles.password_change_required',
'profiles.password_change_reason',
'profiles.mfa',
+ 'profiles.mfa_required_date'
),
}
password_change_required
password_change_reason
mfa
+ mfa_required_date
);
push(@cols, 'cryptpassword') if exists $self->{cryptpassword};
return @cols;
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, @_);
}
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;
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;
# 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');
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;
# 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>
</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">
<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
"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."
},
%]
# 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 %]
<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">
</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 %]
[% 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
$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);