From: Llewellyn Marshall Date: Thu, 9 Dec 2021 22:00:44 +0000 (-0500) Subject: API call to get the age of a patron's password, this is displayed in the X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=069275a6c82b291c21ff17d00bc6157cfc2c0abd;p=working%2FEvergreen.git API call to get the age of a patron's password, this is displayed in the user editor and myopac home page. Added a configurable variable in config to set when the password reminder should show up. Patrons can not enter a duplicate password to bypass message. Passive Hook for password update based on edit date. Example event definition for password update notice. --- diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index 674ace4fc6..85cec9a422 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -2409,6 +2409,24 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + + + + + + + + + + + + + + diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm index 4298f2837d..03cac4acd7 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm @@ -4987,6 +4987,57 @@ sub get_barcodes { return $db_result; } } + +__PACKAGE__->register_method( + method => "get_password_last_edit_age", + api_name => "open-ils.actor.get_password_age", + signature => { + desc => "Finds the number of days since a user's password was last updated.", + params => [ + {desc => 'Authentication token', type => 'string'}, + {desc => 'Patron ID', type => 'number'}, + {desc => 'Reference Time', type => 'string'}, + ], + return => {desc => 'Number of days since password update'} + } +); + +sub get_password_last_edit_age { + my( $self, $client, $auth, $patron_id, $ref_time ) = @_; + my $e = new_editor(authtoken => $auth); + return $e->event unless $e->checkauth; + my $patron = $e->retrieve_actor_user($patron_id); + + #return unless the requestor is either the patron in question, or can view users at that patron's home ou + unless($patron && ($patron_id == $e->requestor->id || $e->allowed('VIEW_USER', $patron->home_ou))) { + return $e->event; + } + + #get the password dates from the virtual table + my $aupsds = $e->json_query({ + select => {aupsd => ['create_date','edit_date']}, + from => 'aupsd', + where => { + usr => $patron_id + } + }); + + if(defined $aupsds){ + my $pwd = $aupsds->[0]; + if($pwd){ + #convert the dates with the DateTime module + my $edit_datetime = DateTime::Format::ISO8601->parse_datetime(clean_ISO8601($pwd->{'edit_date'})); + #get the time we are subtracting from, use ref_time if it's defined or the current datetime otherwise + my $now = defined($ref_time) ? DateTime::Format::ISO8601->parse_datetime(clean_ISO8601($ref_time)) : DateTime->now(); + + my $duration = $now->subtract_datetime_absolute($edit_datetime)->delta_seconds / (24*60*60); + return int($duration); + } + } + #no password entry, return -1 days + return -1; +} + __PACKAGE__->register_method( method => 'address_alert_test', api_name => 'open-ils.actor.address_alert.test', diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm index 5763cb8e03..be29646860 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm @@ -381,6 +381,9 @@ sub load_common { $ctx->{authtoken} = $e->authtoken; $ctx->{authtime} = $e->authtime; $ctx->{user} = $e->requestor; + $ctx->{password_age} = int($U->simplereq( + 'open-ils.actor', + 'open-ils.actor.get_password_age', $e->authtoken, $ctx->{user}->id)); my $card = $self->editor->retrieve_actor_card($ctx->{user}->card); $ctx->{active_card} = (ref $card) ? $card->barcode : undef; $ctx->{place_unfillable} = 1 if $e->requestor->wsid && $e->allowed('PLACE_UNFILLABLE_HOLD', $e->requestor->ws_ou); diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm index f77c1e0a8c..d26383f6d3 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm @@ -2760,6 +2760,11 @@ sub load_myopac_update_password { return Apache2::Const::OK; } + if($current_pw eq $new_pw) { + $ctx->{password_duplicate} = 1; + return Apache2::Const::OK; + } + my $pw_regex = $ctx->{get_org_setting}->($e->requestor->home_ou, 'global.password_regex'); if(!$pw_regex) { diff --git a/Open-ILS/src/perlmods/live_t/33-password-age.t b/Open-ILS/src/perlmods/live_t/33-password-age.t new file mode 100644 index 0000000000..f2d2007eeb --- /dev/null +++ b/Open-ILS/src/perlmods/live_t/33-password-age.t @@ -0,0 +1,87 @@ +#!perl +use constant FUTURE_DAYS => 150; +use strict; use warnings; +use Test::More tests => 4; +use OpenILS::Utils::TestUtils; +use OpenILS::Const qw(:const); +use OpenILS::Utils::CStoreEditor qw/:funcs/; +use OpenILS::Utils::Fieldmapper; +use DateTime; +use DateTime::Format::ISO8601; + +diag("test password age"); + +my $U = 'OpenILS::Application::AppUtils'; + +my $script = OpenILS::Utils::TestUtils->new(); +$script->bootstrap; + +$script->authenticate({ + username => 'admin', + password => 'demo123', + type => 'staff' +}); + +my $authtoken = $script->authtoken; +ok($authtoken, 'was able to authenticate'); + +my $new_user = Fieldmapper::actor::user->new(); +my $new_card = Fieldmapper::actor::card->new(); + +$new_card->barcode("lew_$$"); +$new_card->id(-1); # virtual ID +$new_card->usr(undef); +$new_card->isnew(1); + +$new_user->cards([ $new_card ]); +$new_user->card($new_card); +$new_user->usrname("lew_$$"); +$new_user->passwd('lew_$$'); +$new_user->family_name('Marshall'); +$new_user->first_given_name('Llewellyn'); +$new_user->profile(2); +$new_user->home_ou(4); +$new_user->ident_type(1); +$new_user->isnew(1); + +my $resp = $U->simplereq( + 'open-ils.actor', + 'open-ils.actor.patron.update', + $authtoken, + $new_user +); + +isa_ok($resp, 'Fieldmapper::actor::user', 'new patron'); + +my $new_id = $resp->id(); + +$resp = $U->simplereq( + 'open-ils.actor', + 'open-ils.actor.get_password_age', + $authtoken, + $new_id +); + +cmp_ok($resp, '==', 0, 'Password age on new user is 0 days'); + +my $dt = DateTime->now(); + +$dt->add( days => FUTURE_DAYS ); + +$resp = $U->simplereq( + 'open-ils.actor', + 'open-ils.actor.get_password_age', + $authtoken, + $new_id, + $dt->iso8601() +); + +cmp_ok($resp, '==', FUTURE_DAYS, FUTURE_DAYS." days from now, Password age on new user is ".FUTURE_DAYS." days"); + +# clean up +$U->simplereq( + 'open-ils.actor', + 'open-ils.actor.user.delete', + $authtoken, + $new_id +); \ No newline at end of file diff --git a/Open-ILS/src/sql/Pg/950.data.seed-values.sql b/Open-ILS/src/sql/Pg/950.data.seed-values.sql index ad3c3e5dfc..c70474ad8e 100644 --- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql +++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql @@ -21305,6 +21305,67 @@ INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable) aout.name = 'Consortium' AND (perm.code = 'ADMIN_GEOLOCATION_SERVICES' OR perm.code = 'VIEW_GEOLOCATION_SERVICES'); +-- Password age reset + +INSERT INTO config.org_unit_setting_type + (name, grp, label, description, datatype) + VALUES ( + 'auth.password_expire_age', + 'sec', + oils_i18n_gettext( + 'auth.password_expire_age', + 'Password Reset Age', + 'coust', + 'label' + ), + oils_i18n_gettext( + 'auth.password_expire_age', + 'The number of days after a password has been changed before ' || + 'users will be alerted that they should update it.', + 'coust', + 'description' + ), + 'integer' + ); + +INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES ( + 'aupsd.passwd_changed', + 'aupsd', + oils_i18n_gettext( + 'au.passwd_changed', + 'An account password was updated', + 'ath', + 'description' + ), + true +); + +-- Sample Password Update Notice -- + +INSERT INTO action_trigger.event_definition (active, owner, name, delay_field, delay, max_delay, repeat_delay, hook, validator, reactor, template) + VALUES ('f', 1, 'Password Update Notice', 'edit_date','90 days', '91 days','90 days' 'aupsd.passwd_changed', 'NOOP_True', 'SendEmail', +$$ +[%- USE date -%] +[%- user = target.usr -%] +To: [%- params.recipient_email || user.email %] +From: [%- params.sender_email || default_sender || helpers.get_org_setting(user.home_ou, 'org.bounced_emails') %] +Date: [%- date.format(date.now, '%a, %d %b %Y %T -0000', gmt => 1) %] +Subject: Password Update Required +Auto-Submitted: auto-generated + +Dear [% user.family_name %], [% user.first_given_name %] +Regularly updating your password is an essential part of maintaining the security of your account. At the time of writing, your password is 90 days old. Please log in to the system or contact a system administrator to update your password. + +$$); + +INSERT INTO action_trigger.environment ( + event_def, + path +) VALUES ( + currval('action_trigger.event_definition_id_seq'), + 'usr' +); + ------------------- Disabled example A/T defintions ------------------------------ -- Create a "dummy" slot when applicable, and trigger the "offer curbside" events diff --git a/Open-ILS/src/sql/Pg/upgrade/xxxx.data.password_age_reset.sql b/Open-ILS/src/sql/Pg/upgrade/xxxx.data.password_age_reset.sql new file mode 100644 index 0000000000..036ad0a145 --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/xxxx.data.password_age_reset.sql @@ -0,0 +1,66 @@ +BEGIN; + +--SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version); + +-- password age display setting + +INSERT INTO config.org_unit_setting_type + (name, grp, label, description, datatype) + VALUES ( + 'auth.password_expire_age', + 'sec', + oils_i18n_gettext( + 'auth.password_expire_age', + 'Password Reset Age', + 'coust', + 'label' + ), + oils_i18n_gettext( + 'auth.password_expire_age', + 'The number of days after a password has been changed before ' || + 'users will be alerted that they should update it.', + 'coust', + 'description' + ), + 'integer' + ); + +INSERT INTO action_trigger.hook (key,core_type,description,passive) VALUES ( + 'aupsd.passwd_changed', + 'aupsd', + oils_i18n_gettext( + 'au.passwd_changed', + 'An account password was updated', + 'ath', + 'description' + ), + true +); + +-- Sample Password Update Notice -- + +INSERT INTO action_trigger.event_definition (active, owner, name, delay_field, delay, max_delay, repeat_delay, hook, validator, reactor, template) + VALUES ('f', 1, 'Password Update Notice', 'edit_date','90 days', '91 days','90 days' 'aupsd.passwd_changed', 'NOOP_True', 'SendEmail', +$$ +[%- USE date -%] +[%- user = target.usr -%] +To: [%- params.recipient_email || user.email %] +From: [%- params.sender_email || default_sender || helpers.get_org_setting(user.home_ou, 'org.bounced_emails') %] +Date: [%- date.format(date.now, '%a, %d %b %Y %T -0000', gmt => 1) %] +Subject: Password Update Required +Auto-Submitted: auto-generated + +Dear [% user.family_name %], [% user.first_given_name %] +Regularly updating your password is an essential part of maintaining the security of your account. At the time of writing, your password is 90 days old. Please log in to the system or contact a system administrator to update your password. + +$$); + +INSERT INTO action_trigger.environment ( + event_def, + path +) VALUES ( + currval('action_trigger.event_definition_id_seq'), + 'usr' +); +--ROLLBACK; +COMMIT; \ No newline at end of file diff --git a/Open-ILS/src/templates-bootstrap/opac/myopac/main.tt2 b/Open-ILS/src/templates-bootstrap/opac/myopac/main.tt2 index 29b8198538..2a11559b9d 100755 --- a/Open-ILS/src/templates-bootstrap/opac/myopac/main.tt2 +++ b/Open-ILS/src/templates-bootstrap/opac/myopac/main.tt2 @@ -14,6 +14,24 @@

[% l('My Account Summary') %]

+
+ [% IF ctx.password_age && ctx.password_age_reminder && ctx.disable_password_change != 'true' %] + [% need_password_change = ctx.password_age == -1 || ctx.password_age >= ctx.password_age_reminder %] +
+ [% IF ctx.password_age == -1 %] + [% l('You have never changed your password. Please consider updating your password.') %] + [% ELSIF ctx.password_age >= (ctx.password_age_reminder - 7) %] + [% l('Your password is [_1] days old.',ctx.password_age) %][%- IF !need_password_change %] [% l('You will be asked to change your password soon.') %][%- ELSE %] [% l('It is recommended to update your password every [_1] days. Please consider updating your password.',ctx.password_age_reminder) %][% END %] + [% END %] + [% IF need_password_change %] +
+ [% l("Change Password") %] +
+ [% END %] +
+
+ [% END %]
[% l("Account Expiration Date - ") %] @@ -29,7 +47,7 @@ [% END %]
-
+
+[% ELSIF ctx.password_duplicate %] +
+ [% |l %]New password can not be the same as current password.[% END %] +
+ [% ELSIF ctx.password_incorrect %]
[% |l %]Your current password was not correct.[% END %] diff --git a/Open-ILS/src/templates-bootstrap/opac/parts/config.tt2 b/Open-ILS/src/templates-bootstrap/opac/parts/config.tt2 index 7409a4b4ec..a93b76bcf9 100755 --- a/Open-ILS/src/templates-bootstrap/opac/parts/config.tt2 +++ b/Open-ILS/src/templates-bootstrap/opac/parts/config.tt2 @@ -280,4 +280,11 @@ ctx.max_cart_size = 500; ############################################################################## ctx.show_reservations_tab = 'false'; +############################################################################## +# Password Reminder Settings +############################################################################## + +# days since last password change to start reminding a patron to change their password +# commenting out this line will disable the reminder +ctx.password_age_reminder = ctx.get_org_setting(ctx.physical_loc || 1, 'auth.password_expire_age'); %] diff --git a/Open-ILS/src/templates/opac/myopac/update_password.tt2 b/Open-ILS/src/templates/opac/myopac/update_password.tt2 index d86d8e1cee..93aed23a14 100644 --- a/Open-ILS/src/templates/opac/myopac/update_password.tt2 +++ b/Open-ILS/src/templates/opac/myopac/update_password.tt2 @@ -22,6 +22,11 @@ [% |l %]Passwords do not match.[% END %]
+[% ELSIF ctx.password_duplicate %] +
+ [% |l %]New password can not be the same as current password.[% END %] +
+ [% ELSIF ctx.password_incorrect %]
[% |l %]Your current password was not correct.[% END %] diff --git a/Open-ILS/src/templates/opac/parts/config.tt2 b/Open-ILS/src/templates/opac/parts/config.tt2 index eda51ad814..da7c15dde6 100644 --- a/Open-ILS/src/templates/opac/parts/config.tt2 +++ b/Open-ILS/src/templates/opac/parts/config.tt2 @@ -285,4 +285,12 @@ contents_truncate_length = 50; # Edit parts/record/contents.tt2 to designate character length on a field-by- # field basis for notes. + +############################################################################## +# Password Reminder Settings +############################################################################## + +# days since last password change to start reminding a patron to change their password +# commenting out this line will disable the reminder +ctx.password_age_reminder = ctx.get_org_setting(ctx.physical_loc || 1, 'auth.password_expire_age'); %] diff --git a/Open-ILS/src/templates/staff/circ/patron/t_edit.tt2 b/Open-ILS/src/templates/staff/circ/patron/t_edit.tt2 index 820e346ac3..25de4ef250 100644 --- a/Open-ILS/src/templates/staff/circ/patron/t_edit.tt2 +++ b/Open-ILS/src/templates/staff/circ/patron/t_edit.tt2 @@ -222,7 +222,14 @@ within the "form" by name for validation. [% l('Generate Password') %]
- +
+
+
+
+
[% l('Password last changed [_1] day(s) ago', '{{password_age}}') %]
+
[% l('User has never changed their password') %]
+
+
+

diff --git a/Open-ILS/web/js/ui/default/staff/app.js b/Open-ILS/web/js/ui/default/staff/app.js index 58afd0664d..8dd0c10ec4 100644 --- a/Open-ILS/web/js/ui/default/staff/app.js +++ b/Open-ILS/web/js/ui/default/staff/app.js @@ -157,10 +157,26 @@ function($routeProvider , $locationProvider) { function($scope, $window,egCore) { $scope.focus_search = true; - + $scope.can_self_update = false; + egCore.strings.setPageTitle( egCore.strings['PAGE_TITLE_SPLASH']); - + egCore.org.settingsFromServer(['auth.password_expire_age','global.password_regex']).then(function(settings) { + $scope.password_reset_age = parseInt(settings['auth.password_expire_age']); + }); + egCore.net.request( + 'open-ils.actor', + 'open-ils.actor.get_password_age', + egCore.auth.token(), + egCore.auth.user().id() + ).then(function(age) { + $scope.password_age = parseInt(age); + }); + egCore.perm.hasPermHere('EDIT_SELF_IN_CLIENT') + .then(function(bool){ + $scope.can_self_update = bool; + $scope.self_update_link = './circ/patron/' + egCore.auth.user().id() + '/edit' + }); $scope.catalog_search = function($event) { $scope.focus_search = true; if (!$scope.cat_query) return; diff --git a/Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js b/Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js index ac1894ce45..de023c3f98 100644 --- a/Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js +++ b/Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js @@ -30,6 +30,7 @@ angular.module('egCoreMod') // These are fetched with every instance of the page. var page_data = [ service.get_user_settings(), + service.get_user_password_age(), service.get_clone_user(), service.get_stage_user() ]; @@ -638,6 +639,18 @@ angular.module('egCoreMod') }); } + service.get_user_password_age = function() { + return egCore.net.request( + 'open-ils.actor', + 'open-ils.actor.get_password_age', + egCore.auth.token(), + service.patron_id + ).then(function(age) { + console.log("retrieved from AUSPD: "+age) + service.password_age = age; + }); + } + service.invalidate_field = function(patron, field) { console.log('Invalidating patron field ' + field); @@ -1394,6 +1407,7 @@ function($scope , $routeParams , $q , $uibModal , $window , egCore , $scope.stat_cat_entry_maps = prs.stat_cat_entry_maps; $scope.stage_user = prs.stage_user; $scope.stage_user_requestor = prs.stage_user_requestor; + $scope.password_age = prs.password_age; $scope.user_settings = prs.user_settings; prs.user_settings = {};