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); } #------------------------------------------------------------------------------- # 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 { # warn 'here'; 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} ); $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 = 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; # $self->debug($sha1); my $message_body = do { # turn off POST_CHOMP to retain formatting in .tt: local $self->cfg('tt_config')->{TEMPLATE_OPTIONS}->{POST_CHOMP} = undef; $self->tt_params( user_details => $src_data ); $self->tt_process('admin/user/new_user_msg.tt'); }; $src_data->{message} = ${$message_body}; # warn Dumper $data; # check we have user group id or return to form: my $user_group_id = $vars->{group_id} || return $self->_preview_new_user($src_data); # return to 'preview' page $src_data->{group_id} = $user_group_id; { # execute update_user method: my $rtn = $self->model('User')->update_user($src_data); return $self->error($rtn)if $rtn; } { # send new registration details to new user: my $rtn = $self->_email_registration_details($src_data); return $self->error($rtn) if $rtn; } { # set success flash message: my $msg = $self->messages('admin')->{user}->{create_success}; $self->flash( info => $msg ); } } return $self->tt_process(); } #------------------------------------------------------------------------------- sub _preview_new_user { my $self = shift; 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; my $data = shift; my $subject = sprintf 'HILIS registration details (%s, %s)', uc $data->{last_name}, ucfirst lc $data->{first_name}; my %mail_data = ( recipient => $data->{email}, subject => $subject, message => $data->{message}, config => $self->cfg('settings'), ); # $self->debug(\%mail_data); my $rtn = $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('_get_user_details'); 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) = @_; # 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->_username_exists($username) ) { $username .= $next_character; next CHAR; } # else username is unique so exit loop: last CHAR; } $details->{username} = $username; return $details; } #------------------------------------------------------------------------------- sub _username_exists { my ($self, $username) = @_; # warn Dumper $username; my %args_to_get = ( col => 'username', value => $username ); # get user details from users table using email address from query: my $user = $self->model('User')->get_user_details(\%args_to_get); # $self->debug($user); return $user; } 1;