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 = ( locations => $model->get_user_locations({sort_by => 'location_name'}), groups => $model->get_user_groups({sort_by => 'group_label'}), ); $self->tt_params($_ => $data{$_}) for keys %data; # $self->debug(%data); if ( my $id = $self->param('id') || $self->query->param('id') ) { # get user details: my $user_details = $self->_get_user_details($id); # $self->debug([keys %$user_details]); # load this users details into %data: $self->tt_params($_ => $user_details->{$_}) for keys %{$user_details}; } # 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; return $self->tt_process() unless %$vars; # ie 1st call to method # extract user details from textfield: my $src_data = $self->_parse_message($vars->{src_data}); # warn Dumper $src_data; my $data = $self->_validate_new_user($src_data); # warn Dumper $data; if ($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: $data->{plain_txt_pwd} = $password; # store sha1-encrypted password for model/db: $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($data); $data->{message} = $message; # warn Dumper $src_data; return $self->_preview_new_user($data); # return to 'preview' page } $data->{group_id} = $vars->{group_id}; # set user active (param set to 1 in _validate_new_user() ): $data->{active} = 'yes'; { # set last_login timestamp to 1 month before auto-expiry: my $today = LIMS::Local::Utils::today(); $data->{last_login} = $today->clone->subtract( months => 5 ); # add epoch time as token to msg for 1st login link: $data->{epoch} = $data->{last_login}->epoch; } { # execute update_user method & get last_insert_id: my $rtn = $self->model('User')->update_user($data); return $self->error($rtn) if $rtn; $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($data); $data->{message} = $message; # warn Dumper $data; my $rtn = $self->_email_registration_details($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 list_all : Runmode { my $self = shift; $self->_debug_path($self->get_current_runmode); my $total = $self->model('Base')->get_objects_count('User'); # warn $total; my %args = (); $args{sort_by} = $self->query->param('sort_by'); if ( $total > $self->cfg('settings')->{entries_per_page} ) { # invoke pager for template and add limit & offset params to \%args: $self->pager({ query => \%args, total => $total }); } my $users = $self->model('User')->get_all_users(\%args); return $self->tt_process({ users => $users }); } #------------------------------------------------------------------------------- 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 _validate_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_groups => $groups, has_role => $has_role, ); # validate data: my $profile = $self->validate('user_details'); # add dummy vals if not exists (eg for preview) for validation profile: $data->{$_} ||= 1 for qw(password group_id active); # overwritten during 'post' my $dfv = $self->validate_form_params($profile, $data); # warn Dumper $dfv; if ($dfv->has_invalid || $dfv->has_missing) { $self->tt_params( errs => $dfv->msgs ); } my $valid = $dfv->valid; # warn Dumper $valid; $self->tt_params( user_details => $valid ); return $valid; } #------------------------------------------------------------------------------- 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 admins: for my $recipient ( qw/email_from admin_contact/ ) { $mail_data{recipient} = $cfg->{$recipient}; $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 { # can't delete if user ever logged in (FK constraint) $data{login_count} = $self->model('User')->get_login_count($user_id); } 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 ($first_name) = $msg =~ /First Name: (.*)/; # warn $first_name; my ($last_name) = $msg =~ /Last Name: (.*)/; # warn $last_name; my ($email) = $msg =~ /E-mail: (.*)/; # warn $email; my ($location) = $msg =~ /Source: (.*)/; # warn $location; my ($designation) = $msg =~ /Position: (.*)/; # warn $designation; my $details = { first_name => $first_name, last_name => $last_name, email => $email, location => $location, designation => $designation, }; # warn Dumper $details; # trim white-space: $details->{$_} = LIMS::Local::Utils::trim($details->{$_}) for keys %$details; # add user_location_id if user location exist: unless ($details->{user_location_id}) { # unless already set in 'preview': if ( my $location = $details->{location} ) { my $o = $self->model('User')->get_location_by_name($location); if ($o) { $details->{user_location_id} = $o->id } } } # don't proceed unless all params present: return $details if grep ! defined $details->{$_}, keys %$details; my $max_tries = length($details->{first_name}) - 1; my $username = $details->{last_name}; # warn Dumper $username; # allowing apostrophies in surname now, but need to remove for username: $username =~ s/\'//; # warn Dumper $username; my @registered_users = (); # container for existing usernames # 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 ( my $o = $self->model('User')->get_user_by_username($username) ) { $username .= $next_character; push @registered_users, $o; next CHAR; } # else username is unique so exit loop: last CHAR; } $self->tt_params( registered_users => \@registered_users ); $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;