Skip to content

Commit

Permalink
Bug 1196134 - add ability for admins to force a user to change their …
Browse files Browse the repository at this point in the history
…password on next login
  • Loading branch information
globau committed Aug 25, 2015
1 parent 90a6182 commit e6d45b6
Show file tree
Hide file tree
Showing 12 changed files with 401 additions and 45 deletions.
20 changes: 19 additions & 1 deletion Bugzilla.pm
Expand Up @@ -367,8 +367,26 @@ sub login {
}

my $authenticated_user = $authorizer->login($type);

# 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) {
# 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 });
}

# 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');
exit;
}
}

# 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:
# 1: There must be a logged in user
Expand Down
4 changes: 2 additions & 2 deletions Bugzilla/DB/Schema.pm
Expand Up @@ -947,8 +947,8 @@ use constant ABSTRACT_SCHEMA => {
mybugslink => {TYPE => 'BOOLEAN', NOTNULL => 1,
DEFAULT => 'TRUE'},
extern_id => {TYPE => 'varchar(64)'},
is_enabled => {TYPE => 'BOOLEAN', NOTNULL => 1,
DEFAULT => 'TRUE'},
is_enabled => {TYPE => 'BOOLEAN', NOTNULL => 1,
DEFAULT => 'TRUE'},
last_seen_date => {TYPE => 'DATETIME'},
password_change_required => { TYPE => 'BOOLEAN', NOTNULL => 1, DEFAULT => 'FALSE' },
password_change_reason => { TYPE => 'varchar(64)' },
Expand Down
48 changes: 36 additions & 12 deletions Bugzilla/User.pm
Expand Up @@ -106,6 +106,8 @@ sub DB_COLUMNS {
'profiles.extern_id',
'profiles.is_enabled',
$dbh->sql_date_format('last_seen_date', '%Y-%m-%d') . ' AS last_seen_date',
'profiles.password_change_required',
'profiles.password_change_reason',
),
}

Expand All @@ -114,13 +116,15 @@ use constant ID_FIELD => 'userid';
use constant LIST_ORDER => NAME_FIELD;

use constant VALIDATORS => {
cryptpassword => \&_check_password,
disable_mail => \&_check_disable_mail,
disabledtext => \&_check_disabledtext,
login_name => \&check_login_name_for_creation,
realname => \&_check_realname,
extern_id => \&_check_extern_id,
is_enabled => \&_check_is_enabled,
cryptpassword => \&_check_password,
disable_mail => \&_check_disable_mail,
disabledtext => \&_check_disabledtext,
login_name => \&check_login_name_for_creation,
realname => \&_check_realname,
extern_id => \&_check_extern_id,
is_enabled => \&_check_is_enabled,
password_change_required => \&Bugzilla::Object::check_boolean,
password_change_reason => \&_check_password_change_reason,
};

sub UPDATE_COLUMNS {
Expand All @@ -132,13 +136,16 @@ sub UPDATE_COLUMNS {
realname
extern_id
is_enabled
password_change_required
password_change_reason
);
push(@cols, 'cryptpassword') if exists $self->{cryptpassword};
return @cols;
};

use constant VALIDATOR_DEPENDENCIES => {
is_enabled => ['disabledtext'],
is_enabled => [ 'disabledtext' ],
password_change_reason => [ 'password_change_required' ],
};

use constant EXTRA_REQUIRED_FIELDS => qw(is_enabled);
Expand Down Expand Up @@ -343,13 +350,22 @@ sub _check_is_enabled {
return $disabledtext ? 0 : 1;
}

sub _check_password_change_reason {
my ($self, $value) = @_;
return $self->password_change_required
? trim($_[1]) || ''
: '';
}

################################################################################
# Mutators
################################################################################

sub set_disable_mail { $_[0]->set('disable_mail', $_[1]); }
sub set_email_enabled { $_[0]->set('disable_mail', !$_[1]); }
sub set_extern_id { $_[0]->set('extern_id', $_[1]); }
sub set_disable_mail { $_[0]->set('disable_mail', $_[1]); }
sub set_email_enabled { $_[0]->set('disable_mail', !$_[1]); }
sub set_extern_id { $_[0]->set('extern_id', $_[1]); }
sub set_password_change_required { $_[0]->set('password_change_required', $_[1]); }
sub set_password_change_reason { $_[0]->set('password_change_reason', $_[1]); }

sub set_login {
my ($self, $login) = @_;
Expand All @@ -364,7 +380,12 @@ sub set_name {
delete $self->{identity};
}

sub set_password { $_[0]->set('cryptpassword', $_[1]); }
sub set_password {
my ($self, $password) = @_;
$self->set('cryptpassword', $password);
$self->set('password_change_required', 0);
$self->set('password_change_reason', '');
}

sub set_disabledtext {
my ($self, $text) = @_;
Expand Down Expand Up @@ -514,6 +535,9 @@ sub showmybugslink { $_[0]->{showmybugslink}; }
sub email_disabled { $_[0]->{disable_mail} || !$_[0]->{is_enabled}; }
sub email_enabled { !$_[0]->email_disabled; }
sub last_seen_date { $_[0]->{last_seen_date}; }
sub password_change_required { $_[0]->{password_change_required}; }
sub password_change_reason { $_[0]->{password_change_reason}; }

sub cryptpassword {
my $self = shift;
# We don't store it because we never want it in the object (we
Expand Down
6 changes: 6 additions & 0 deletions editusers.cgi
Expand Up @@ -264,6 +264,12 @@ if ($action eq 'search') {
$otherUser->set_disable_mail($cgi->param('disable_mail'));
$otherUser->set_extern_id($cgi->param('extern_id'))
if defined($cgi->param('extern_id'));
$otherUser->set_password_change_required($cgi->param('password_change_required'));
$otherUser->set_password_change_reason(
$otherUser->password_change_required
? $cgi->param('password_change_reason')
: ''
);
$changes = $otherUser->update();
}

Expand Down
72 changes: 72 additions & 0 deletions reset_password.cgi
@@ -0,0 +1,72 @@
#!/usr/bin/perl -wT

# 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 lib qw(. lib);

use Bugzilla;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Token;
use Bugzilla::User qw( validate_password );
use Bugzilla::Util qw( bz_crypt );

my $cgi = Bugzilla->cgi;
my $user = Bugzilla->login(LOGIN_REQUIRED);
my $template = Bugzilla->template;
my $dbh = Bugzilla->dbh;

if ($cgi->param('do_save')) {
my $token = $cgi->param('token');
check_token_data($token, 'reset_password');

my $old_password = $cgi->param('old_password') // '';
my $password_1 = $cgi->param('new_password1') // '';
my $password_2 = $cgi->param('new_password2') // '';

# make sure passwords never show up in the UI
foreach my $field (qw( old_password new_password1 new_password2 )) {
$cgi->delete($field);
}

# validation
my $old_crypt_password = $user->cryptpassword;
if (bz_crypt($old_password, $old_crypt_password) ne $old_crypt_password) {
ThrowUserError('old_password_incorrect');
}
if ($password_1 eq '' || $password_2 eq '') {
ThrowUserError('new_password_missing');
}
if ($old_password eq $password_1) {
ThrowUserError('new_password_same');
}
validate_password($password_1, $password_2);

# update
$dbh->bz_start_transaction;
$user->set_password($password_1);
$user->update({ keep_session => 1, keep_tokens => 1 });
Bugzilla->logout(LOGOUT_KEEP_CURRENT);
delete_token($token);
$dbh->bz_commit_transaction;

# done
print $cgi->header();
$template->process('index.html.tmpl', { message => 'password_changed' })
|| 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());
}
4 changes: 4 additions & 0 deletions skins/standard/admin.css
Expand Up @@ -179,6 +179,10 @@ th.title {
cursor: default;
}

input[disabled] {
background-color: transparent !important;
}

/* User Preferences Page */

#prefmain {
Expand Down

0 comments on commit e6d45b6

Please sign in to comment.