package LIMS::Controller::Admin::User;
use strict;
use base 'LIMS::Controller::Admin';
use LIMS::Local::Utils;
use Moose;
with (
'LIMS::Controller::Roles::User', # generate_new_password
'LIMS::Controller::Roles::FormData', # validate_form_params
);
__PACKAGE__->meta->make_immutable(inline_constructor => 0);
__PACKAGE__->authz->authz_runmodes( ':all' => 'do_admin' );
use Data::Dumper;
#-------------------------------------------------------------------------------
sub default : StartRunmode {
my $self = shift; $self->_debug_path($self->get_current_runmode);
my $errs = shift; # $self->stash( errs => $errs );
my $model = $self->model('User');
my %data = (
users => $model->get_all_users({sort_by => 'username'}),
groups => $model->get_user_groups({sort_by => 'group_label'}),
locations => $model->get_user_locations({sort_by => 'location_name'}),
);
if ( my $id = $self->param('id') || $self->query->param('id') ) {
# get user details:
my $this_user_details = $self->_get_user_details($id);
# $self->debug([keys %$user_details]);
# load this users details into %data:
map {
$data{$_} = $this_user_details->{$_};
} keys %$this_user_details;
}
map $self->tt_params($_ => $data{$_}), keys %data;
# $self->debug([ keys %data ]);
# get js validation foo_onsubmit & foo_dfv_js vars into template:
$self->js_validation_profile('user_details');
return $self->tt_process($errs);
}
#-------------------------------------------------------------------------------
sub get_user : Runmode {
my $self = shift;
my $username = $self->query->param('username'); # warn $username;
my $user = $self->model('User')->get_user_by_username($username);
# add user.id as param for default():
$self->param( id => $user->id );
return $self->forward('default');
}
#-------------------------------------------------------------------------------
# used to create new user and to update existing user details:
sub update_user_details : Runmode {
my $self = shift; $self->_debug_path($self->get_current_runmode);
my $user_id = $self->param('id');
# put id (if submitted) into params() for validate('user_details') profile:
if ( $user_id ) { # warn $user_id;
$self->query->param( _record_id => $user_id );
}
my $dfv = $self->check_rm('default', $self->validate('user_details') )
|| return $self->dfv_error_page;
my $data = $dfv->valid
|| return $self->forward('default'); # eg if empty param; # $self->debug($data);
if ($user_id) {
# provide 'id' if supplied, so record updated, otherwise new one created:
$data->{id} = $user_id;
# load existing user (if it's an 'update user' action):
my $user = $self->model('User')->get_user_profile($user_id)
|| return $self->error("Cannot retreive user details for id=$user_id");
# check if password has been changed - if form pwd not same as retrieved
# pwd, must have been changed & will need encrypting:
if ($data->{password} ne $user->password) {
# encrypt password using SHA1 (or MD5):
my $sha1 = LIMS::Local::Utils::sha1_digest($data->{password});
$data->{password} = $sha1; # $self->debug('sha1:'.$sha1);
}
# set message:
$self->stash( user_update_action => 'edit_success' );
}
# it's a 'create new user' action, so encrypt password:
else {
my $sha1 = LIMS::Local::Utils::sha1_digest($data->{password});
$data->{password} = $sha1; # $self->debug('sha1:'.$sha1);
# set message:
$self->stash( user_update_action => 'create_success' );
}
my $rtn = $self->model('User')->update_user($data);
if ( $rtn ) {
$self->error($rtn);
}
else { # redirect after db edit:
# set MessageStack msg set above:
my $action = $self->stash->{user_update_action};
$self->flash( info => $self->messages('admin')->{user}->{$action} );
if (! $user_id) { # use new 'username' to get new record id:
my $username = $data->{username};
my $user = $self->model('User')->get_user_by_username($username);
$user_id = $user->id;
}
$self->redirect( $self->query->url . '/admin/user/default/'.$user_id );
}
}
#-------------------------------------------------------------------------------
sub edit_permissions : Runmode {
my $self = shift; $self->_debug_path($self->get_current_runmode);
my $id = $self->param('id')
|| return $self->error( 'no id passed to '.$self->get_current_runmode );
my $data = $self->_get_user_details($id);
map $self->tt_params($_ => $data->{$_}), keys %$data;
return $self->tt_process('admin/user/edit_permissions.tt');
}
#-------------------------------------------------------------------------------
sub reset_user_permissions : Runmode {
my $self = shift; $self->_debug_path($self->get_current_runmode);
my $id = $self->param('id')
|| return $self->error( 'no id passed to '.$self->get_current_runmode );
my $user = $self->model('User')->get_user_profile($id);
$self->tt_params( user => $user );
# need confirmation before resetting permissions:
return $self->tt_process('admin/user/reset_permissions.tt')
unless $self->query->param('confirm_reset');
my $rtn = $self->model('User')->delete_user_permissions($id);
return $rtn ?
$self->error($rtn) : # redirect after db edit:
$self->redirect( $self->query->url . '/admin/user/default/'.$id );
}
#-------------------------------------------------------------------------------
sub update_user_permissions : Runmode {
my $self = shift; $self->_debug_path($self->get_current_runmode);
my $user_id = $self->param('id')
|| return $self->error('no id passed to '.$self->get_current_runmode);
my @function_ids = $self->query->param('function_id'); # $self->debug(\@function_ids);
my %args = (
function_ids => \@function_ids,
user_id => $user_id,
);
my $rtn = $self->model('User')->update_user_permissions(\%args);
return $rtn ?
$self->error($rtn) :
$self->redirect( $self->query->url . '/admin/user/default/'.$user_id );
}
#-------------------------------------------------------------------------------
sub delete_user : Runmode {
my $self = shift; $self->_debug_path($self->get_current_runmode);
my $user_id = $self->param('id')
|| return $self->error('no id passed to '.$self->get_current_runmode);
my $user = $self->model('User')->get_user_profile($user_id);
$self->tt_params( user => $user );
# need confirmation before deleting user:
return $self->tt_process('admin/user/delete_user.tt')
unless $self->query->param('confirm_reset');
my $rtn = $self->model('User')->delete_user($user_id);
return $rtn ?
$self->error($rtn) : # redirect after db edit:
$self->redirect( $self->query->url . '/admin/user' );
}
#-------------------------------------------------------------------------------
sub new_user : Runmode {
my $self = shift; $self->_debug_path($self->get_current_runmode);
my $vars = $self->query->Vars(); # warn Dumper $vars;
# extract user details from textfield:
my $src_data = $self->_parse_message($vars->{src_data});
return $self->tt_process() unless $src_data; # ie 1st call to method
if ($vars->{preview}) {
return $self->_preview_new_user($src_data);
}
elsif ($vars->{post}) {
# generate new plain text password:
my $password = $self->generate_new_password();
# encrypt password using SHA1 (or MD5) for model:
my $sha1_pwd = LIMS::Local::Utils::sha1_digest($password);
# store as plain_txt_pwd for .tt and email:
$src_data->{plain_txt_pwd} = $password;
# store sha1-encrypted password for model/db:
$src_data->{password} = $sha1_pwd; # $self->debug($sha1_pwd);
# check we have user group id or return to form:
unless ( $vars->{group_id} ) {
my $message = $self->_get_msg_body($src_data);
$src_data->{message} = $message; # warn Dumper $src_data;
return $self->_preview_new_user($src_data); # return to 'preview' page
}
$src_data->{group_id} = $vars->{group_id};
{ # set last_login timestamp to 1 month before auto-expiry:
my $today = LIMS::Local::Utils::today();
$src_data->{last_login} = $today->clone->subtract( months => 5 );
# add epoch time as token to msg for 1st login link:
$src_data->{epoch} = $src_data->{last_login}->epoch;
}
{ # execute update_user method & get last_insert_id:
my $rtn = $self->model('User')->update_user($src_data);
return $self->error($rtn) if $rtn;
$src_data->{uid} = # add new user ID to msg for 1st login link:
$self->lims_db->dbh->last_insert_id(undef, undef, 'users', 'id');
}
{ # send new registration details to new user:
my $message = $self->_get_msg_body($src_data);
$src_data->{message} = $message; # warn Dumper $src_data;
my $rtn = $self->_email_registration_details($src_data);
return $self->error($rtn->string) if $rtn->type ne 'success';
}
{ # set success flash message:
my $msg = $self->messages('admin')->{user}->{create_success};
$self->flash( info => $msg );
# set flag for tt to avoid loading warning msgs:
$self->tt_params( new_user_create_success => 1 );
}
}
return $self->tt_process();
}
#-------------------------------------------------------------------------------
sub _get_msg_body {
my $self = shift;
my $data = shift;
my $message_body = do { # turn off POST_CHOMP to control formatting in .tt:
local $self->cfg('tt_config')->{TEMPLATE_OPTIONS}->{POST_CHOMP} = undef;
$self->tt_params( user_details => $data );
$self->tt_process('admin/user/new_user_msg.tt');
}; # warn Dumper $message_body;
return ${$message_body}; # return deref'ed string
}
#-------------------------------------------------------------------------------
sub _preview_new_user {
my $self = shift; $self->_debug_path();
my $data = shift;
# get user groups:
my $groups
= $self->model('User')->get_user_groups({sort_by => 'group_label'});
# check role/designation:
my $has_role = $self->model('User')->check_user_roles($data->{designation});
$self->tt_params(
user_details => $data,
user_groups => $groups,
has_role => $has_role,
);
{ # validate data:
my $profile = $self->validate('user_details');
# add dummy vals for validation profile:
@{$data}{qw(password group_id active)} = (1,1,1); # just needs to exist
my $dfv = $self->validate_form_params($profile, $data); # warn Dumper $dfv;
if ($dfv->has_invalid || $dfv->has_missing) {
$self->tt_params( errs => $dfv->msgs );
}
}
return $self->tt_process(); # will use caller's template
}
#-------------------------------------------------------------------------------
sub _email_registration_details {
my $self = shift; $self->_debug_path();
my $data = shift;
my $subject = sprintf 'HILIS registration details (%s, %s)',
uc $data->{last_name}, ucfirst lc $data->{first_name};
my $cfg = $self->cfg('settings');
my %mail_data = (
recipient => $data->{email},
subject => $subject,
message => $data->{message},
config => $cfg,
); # $self->debug(\%mail_data);
my $rtn = $self->model('Email')->send_message(\%mail_data); # Return::Value object
# confirmation to admin:
{
$mail_data{recipient} = $cfg->{admin_contact};
$self->model('Email')->send_message(\%mail_data);
}
return $rtn;
}
#-------------------------------------------------------------------------------
# returns hashref of selected_user (users details), permissions_type
# (custom/default), user_permissions (list)
sub _get_user_details {
my $self = shift; $self->_debug_path();
my $user_id = shift;
my %data;
my $this_user = $data{selected_user}
= $self->model('User')->get_user_profile($user_id); # hashref
# load custom user permissions (if any):
my $this_user_permissions
= $self->model('User')->get_user_permissions($user_id); # arrayref
# flag for template (if permissions set here - it's custom):
$data{permissions_type} = @$this_user_permissions ? 'custom' : 'default';
# if no user_permissions, load default settings for this users' group:
if (! @$this_user_permissions) {
$this_user_permissions
= $self->model('User')->get_user_group_functions($this_user->group_id);
}
# get user_permissions, with active => 1 if function.id also in user_group_functions table:
$data{user_permissions} = $self->get_user_functions($this_user_permissions); # in LIMS::Admin::Controller
return \%data;
}
#-------------------------------------------------------------------------------
sub _parse_message {
my ($self, $msg) = @_; $self->_debug_path(); # warn Dumper $msg;
$msg = LIMS::Local::Utils::trim($msg);
return 0 if ! $msg; # warn Dumper $msg; # in case submitted empty except spaces
my $details = {
first_name => $msg =~ /First Name: (.*)/,
last_name => $msg =~ /Last Name: (.*)/,
email => $msg =~ /E-mail: (.*)/,
location => $msg =~ /Source: (.*)/,
designation => $msg =~ /Position: (.*)/,
}; # warn Dumper $details;
map { # trim white-space:
$details->{$_} = LIMS::Local::Utils::trim($details->{$_})
} keys %$details; # warn Dumper $details;
# add user_location_id if user location exist:
unless ($details->{user_location_id}) { # unless already set in 'preview':
my $location = $details->{location} || return 0; # unlikely, but check
my $o = $self->model('User')->get_location_by_name($location);
if ($o) { $details->{user_location_id} = $o->id }
}
# enforce case on names:
$details->{last_name} = lc $details->{last_name};
$details->{first_name} = lc $details->{first_name};
$details->{email} = lc $details->{email};
my $max_tries = length($details->{first_name}) - 1;
my $username = $details->{last_name};
# loop through each first_name character, appending to last_name
# until we have a unique user_id:
CHAR: for my $i ( 0 .. $max_tries ) {
# if already used all first_name chars:
if ($i == $max_tries) {
$username = '**** FULL NAME ALREADY IN USE ****';
last CHAR;
}
my $next_character = (split '', $details->{first_name})[$i];
# if username exists, append successive letters from first_name & try again
if ( $self->model('User')->get_user_by_username($username) ) {
$username .= $next_character;
next CHAR;
}
# else username is unique so exit loop:
last CHAR;
}
$details->{username} = $username;
return $details;
}
=begin # needs a bit more work - eg tt can't access c.cfg or app_url, requires
cpan modules MIME::Base64 & Authen::SASL for smtp authentication
#-------------------------------------------------------------------------------
sub _email_registration_details {
my $self = shift; $self->_debug_path();
my $data = shift;
use Email::Template;
my $path_to_app_root = $self->cfg('path_to_app_root');
my $subject = sprintf 'HILIS registration details (%s, %s)',
uc $data->{last_name}, ucfirst lc $data->{first_name};
my @smtp = ('smtp', 'smtp.hmds.org.uk', AuthUser => 'ra.jones@hmds.org.uk',
AuthPass => 'tetramor' );
my %args = (
From => $self->cfg('settings')->{email_from} || 'hmds.lth@nhs.net',
To => $data->{email},
Subject => $subject,
tt_new => { INCLUDE_PATH => $path_to_app_root.'/templates' },
tt_vars => { user_details => $data },
convert => { rightmargin => 80, no_rowspacing => 1 },
mime_lite_send => \@smtp,
);
Email::Template->send( 'admin/user/new_user_msg.htm', \%args )
or warn "could not send the email";
}
=cut
1;