From: Llewellyn Marshall Date: Thu, 9 Dec 2021 22:00:44 +0000 (-0500) Subject: password reset age 3.9 X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=be57151be6c6a74a1c441582d8c708b92a28318d;p=working%2FEvergreen.git password reset age 3.9 --- diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index 92abe1547d..bf8a0f9cf8 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -2599,6 +2599,24 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + + + + + + + + + + + + + + diff --git a/Open-ILS/src/eg2/src/app/staff/splash.component.html b/Open-ILS/src/eg2/src/app/staff/splash.component.html index d9e6b76025..9f1336892d 100644 --- a/Open-ILS/src/eg2/src/app/staff/splash.component.html +++ b/Open-ILS/src/eg2/src/app/staff/splash.component.html @@ -29,6 +29,19 @@ +
+

Your password is {{passwordAge}} days old. It is recommended that passwords be updated every {{passwordExpireAge}} days.

+

+ Please contact an administrator to have your password changed or change your password through the OPAC. +

+ +
+
diff --git a/Open-ILS/src/eg2/src/app/staff/splash.component.ts b/Open-ILS/src/eg2/src/app/staff/splash.component.ts index b310cd3e7a..69626d713a 100644 --- a/Open-ILS/src/eg2/src/app/staff/splash.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/splash.component.ts @@ -3,6 +3,8 @@ import {OrgService} from '@eg/core/org.service'; import {AuthService} from '@eg/core/auth.service'; import {PcrudService} from '@eg/core/pcrud.service'; import {ToastService} from '@eg/share/toast/toast.service'; +import {PermService} from '@eg/core/perm.service'; +import {NetService} from '@eg/core/net.service'; import {StringComponent} from '@eg/share/string/string.component'; import {Router} from '@angular/router'; @@ -14,6 +16,10 @@ export class StaffSplashComponent implements OnInit { @ViewChild('noPermissionString', { static: true }) noPermissionString: StringComponent; catSearchQuery: string; + passwordExpireAge: number; + passwordAge: number; + canSelfUpdate: boolean; + selfUpdateLink: string; portalEntries: any[][] = []; portalHeaders: any[] = []; @@ -23,7 +29,9 @@ export class StaffSplashComponent implements OnInit { private auth: AuthService, private org: OrgService, private router: Router, - private toast: ToastService + private toast: ToastService, + private net: NetService, + private perm: PermService ) {} ngOnInit() { @@ -92,6 +100,36 @@ export class StaffSplashComponent implements OnInit { } ); + //get the maximum password age + this.org.settings(['auth.password_expire_age']) + .then(settings => { + this.passwordExpireAge = Number(settings['auth.password_expire_age']); + console.log('password expire age: ', settings); + }); + //get the age of the current user's password + this.net.request( + 'open-ils.actor', + 'open-ils.actor.get_password_age', + this.auth.token(), + this.auth.user().id()).subscribe( + (res) => { + console.log('password age: ', res); + this.passwordAge = Number(res); + }, + (err) => { + console.error('splash', err); + } + ); + //check if user can change their own password + this.perm.hasWorkPermAt( + ['EDIT_SELF_IN_CLIENT'], + true).then(perms => { + if(perms['EDIT_SELF_IN_CLIENT']){ + this.canSelfUpdate = true; + //create a link to the user's profile + this.selfUpdateLink = '/eg/staff/circ/patron/' + this.auth.user().id() + '/edit'; + } + }); if (this.router.url === '/staff/no_permission') { this.noPermissionString.current() .then(str => { diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm index c0d9851f02..d9ee1a1ccc 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm @@ -5014,6 +5014,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 0f8557b142..811829e890 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm @@ -383,6 +383,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 dcc2f1403e..3bc5b42ac3 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader/Account.pm @@ -2823,6 +2823,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 a0f937cacf..f4571ed55e 100644 --- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql +++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql @@ -21427,6 +21427,67 @@ VALUES ( 'integer' ); +-- 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 ebafd0829f..cf30d2d6cd 100644 --- a/Open-ILS/src/templates/staff/circ/patron/t_edit.tt2 +++ b/Open-ILS/src/templates/staff/circ/patron/t_edit.tt2 @@ -225,7 +225,14 @@ within the "form" by name for validation. [% l('Send Password Reset Link') %]
- +
+
+
+
+
[% 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/circ/patron/regctl.js b/Open-ILS/web/js/ui/default/staff/circ/patron/regctl.js index 31bc4fe2af..3da947cb19 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 @@ -31,6 +31,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() ]; @@ -662,6 +663,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); @@ -1423,6 +1436,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 = {};