From dcefa008cb1ac6d042cf6ddfee1b0f9400866a33 Mon Sep 17 00:00:00 2001 From: Sam Lau Date: Fri, 14 Jul 2023 19:43:33 +0000 Subject: [PATCH] Bug 33462: Add ability to force password change for new patrons entered by staff This patch attempts to force a password change for new staff created patrons. This is done by setting the password_expiration_date to an expired date when adding a new patron. This patch adds a new system preference 'ForcePasswordResetWhenSetByStaff', a new column to the categories table 'force_password_reset_when_set_by_staff', and new columns to the borrowers, borrower_modification, and deletedborrowers table 'needs_password_reset' To test: 1) Apply patch, restart_all, updatedatabase, and also be sure to update schema. 2) Visit Administration->Sytem Preferences and search for 'EnableExpiredPasswordReset'. Make sure this is set to enable. Now search for 'ForcePasswordResetWhenSetByStaff'. This should be defaulted to 'Don't force'. 3) Keep that tab open and visit Administration->Patron categories. Click on edit on the Board category. Noitce that there is a now a 'Force new patron password reset' section. Notice that the by default, this is set to follow the ForcePasswordResetWhenSetByStaff system preference (currently set to don't force). Click on the dropdown and change it to 'Force'. Save changes 4) Click on the Patrons tab to visit members-home.pl and then click 'New Patron'. Select on Patron. Fill in the required information and also enter a password. 5) Submit this form and notice that the patron's password expiration date is set to never. This should be the case because the default for 'Force new patron password reset' follows the sys. pref. which is still set to 'Don't force' (You could have some expiry date in this step, but it should at least be set to a date that is not expired. this depends on whether or not you have a defalut password expiration date set in patron categories ) 6) Log into the OPAC with this patron and notice it works as expected and log in was successful. 7) Go back to the patron home page and click to add a new patron. This time select 'Board'. Once again fill out the required info, enter a password, and then save the form. 8) Notice that for this patron, the password expiration date is set for today's date. This is because we changed the setting for the 'Board' patron category to force. 9) Log into the OPAC with this patron. You should be redirected to a page with an error that says: "It's your first login! You need to reset your password." Click on the reset password link below this message. 10) You should be sent to a page where you can reset your password. Fill in the form and click 'Update password'. Attempt to sign into the OPAC with this new password. Everything works as expected. 11) Go back to the staff interface and view this patron's detail page. Notice the password expiration date is now set to what the default is in the patron category. 12) Edit this patrons information and set their password expiration date to yesterday. Go back to the OPAC and try to sign in with this patron again. Note that this time, you are also redirected but the message says "Error: Your password has expired!" 13) Go back to the staff interface and visit the sys. pref tab we left open. Set it to the 'Force' option and save changes. 14) Visit the patron home page and click add patron, now select the patron category again. Fill in required info and enter password. Submit form and note that the patron's password expiration date is set to today. Try to login to the OPAC with this patron, you should be redirected to the page with the error that says "Error: It's your first login! You need to reset your password." 15) Sign-off :) Signed-off-by: Christopher Brannon --- C4/Auth.pm | 2 + Koha/Database/Columns.pm | 1 + Koha/Patron.pm | 13 ++++-- Koha/Patron/Category.pm | 16 +++++++ admin/categories.pl | 46 ++++++++++--------- .../data/mysql/atomicupdate/bug_33462.pl | 36 +++++++++++++++ installer/data/mysql/kohastructure.sql | 4 ++ installer/data/mysql/mandatory/sysprefs.sql | 1 + .../prog/en/modules/admin/categories.tt | 35 ++++++++++++++ .../en/modules/admin/preferences/patrons.pref | 6 +++ .../bootstrap/en/modules/opac-auth.tt | 12 +++-- opac/opac-reset-password.pl | 1 + 12 files changed, 146 insertions(+), 27 deletions(-) create mode 100755 installer/data/mysql/atomicupdate/bug_33462.pl diff --git a/C4/Auth.pm b/C4/Auth.pm index 9b298eed87..c21247471e 100644 --- a/C4/Auth.pm +++ b/C4/Auth.pm @@ -1320,6 +1320,7 @@ sub checkauth { track_login_daily( $userid ); + # In case, that this request was a login attempt, we want to prevent that users can repost the opac login # request. We therefore redirect the user to the requested page again without the login parameters. # See Post/Redirect/Get (PRG) design pattern: https://en.wikipedia.org/wiki/Post/Redirect/Get @@ -1389,6 +1390,7 @@ sub checkauth { opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'}, too_many_login_attempts => ( $patron and $patron->account_locked ), password_has_expired => ( $patron and $patron->password_expired ), + needs_password_reset => ( $patron and $patron->needs_password_reset), auth_error => $auth_error, ); diff --git a/Koha/Database/Columns.pm b/Koha/Database/Columns.pm index 6b46a1bc39..a5586adf23 100644 --- a/Koha/Database/Columns.pm +++ b/Koha/Database/Columns.pm @@ -168,6 +168,7 @@ sub columns { "lost" => __("Lost card flag"), "middle_name" => __("Middle name"), "mobile" => __("Other phone"), + "needs_password_reset" => __("Needs password reset"), "opacnote" => __("OPAC note"), "othernames" => __("Other name"), "overdrive_auth_token" => __("Overdrive auth token"), diff --git a/Koha/Patron.pm b/Koha/Patron.pm index 6937ea904a..d0d49420a6 100644 --- a/Koha/Patron.pm +++ b/Koha/Patron.pm @@ -272,9 +272,15 @@ sub store { # Make a copy of the plain text password for later use $self->plain_text_password( $self->password ); - $self->password_expiration_date( $self->password - ? $self->category->get_password_expiry_date || undef - : undef ); + if($self->category->effective_force_password_reset_when_set_by_staff and ($self->categorycode ne C4::Context->preference("PatronSelfRegistrationDefaultCategory"))){ + $self->password_expiration_date(dt_from_string); + $self->needs_password_reset(1); + } + else{ + $self->password_expiration_date( $self->password + ? $self->category->get_password_expiry_date || undef + : undef ); + } # Create a disabled account if no password provided $self->password( $self->password ? Koha::AuthUtils::hash_password( $self->password ) @@ -2159,6 +2165,7 @@ sub to_api_mapping { altcontactzipcode => 'altcontact_postal_code', password_expiration_date => undef, primary_contact_method => undef, + needs_password_reset => undef, secret => undef, auth_method => undef, }; diff --git a/Koha/Patron/Category.pm b/Koha/Patron/Category.pm index 9eb6d26edc..54bd0c0dfc 100644 --- a/Koha/Patron/Category.pm +++ b/Koha/Patron/Category.pm @@ -187,6 +187,22 @@ sub effective_require_strong_password { return $self->require_strong_password // C4::Context->preference('RequireStrongPassword'); } +=head3 effective_force_password_reset_when_set_by_staff + + $category->effective_force_password_reset_when_set_by_staff() + +Returns if new staff created patrons in this category are forced to reset their password. If set in $self->force_password_reset_when_set_by_staff +or, if undef, falls back to the ForcePasswordResetWhenSetByStaff system preference. + +=cut + +sub effective_force_password_reset_when_set_by_staff { + my ($self) = @_; + + return $self->force_password_reset_when_set_by_staff // C4::Context->preference('ForcePasswordResetWhenSetByStaff'); +} + + =head3 override_hidden_items if ( $patron->category->override_hidden_items ) { diff --git a/admin/categories.pl b/admin/categories.pl index 53c4182b47..9b4e0f1b1a 100755 --- a/admin/categories.pl +++ b/admin/categories.pl @@ -80,11 +80,13 @@ elsif ( $op eq 'add_validate' ) { my $require_strong_password = $input->param('require_strong_password'); my @branches = grep { $_ ne q{} } $input->multi_param('branches'); my $can_be_guarantee = $input->param('can_be_guarantee'); + my $force_password_reset_when_set_by_staff = $input->param('force_password_reset_when_set_by_staff'); $reset_password = undef if $reset_password eq -1; $change_password = undef if $change_password eq -1; $min_password_length = undef unless length($min_password_length); $require_strong_password = undef if $require_strong_password eq -1; + $force_password_reset_when_set_by_staff = undef if $force_password_reset_when_set_by_staff eq -1; my $is_a_modif = $input->param("is_a_modif"); @@ -111,6 +113,7 @@ elsif ( $op eq 'add_validate' ) { $category->exclude_from_local_holds_priority($exclude_from_local_holds_priority); $category->min_password_length($min_password_length); $category->require_strong_password($require_strong_password); + $category->force_password_reset_when_set_by_staff($force_password_reset_when_set_by_staff); eval { $category->store; $category->replace_library_limits( \@branches ); @@ -123,27 +126,28 @@ elsif ( $op eq 'add_validate' ) { } else { my $category = Koha::Patron::Category->new({ - categorycode => $categorycode, - description => $description, - enrolmentperiod => $enrolmentperiod, - enrolmentperioddate => $enrolmentperioddate, - password_expiry_days => $password_expiry_days, - upperagelimit => $upperagelimit, - dateofbirthrequired => $dateofbirthrequired, - enrolmentfee => $enrolmentfee, - reservefee => $reservefee, - hidelostitems => $hidelostitems, - overduenoticerequired => $overduenoticerequired, - category_type => $category_type, - can_be_guarantee => $can_be_guarantee, - BlockExpiredPatronOpacActions => $BlockExpiredPatronOpacActions, - checkprevcheckout => $checkPrevCheckout, - default_privacy => $default_privacy, - reset_password => $reset_password, - change_password => $change_password, - exclude_from_local_holds_priority => $exclude_from_local_holds_priority, - min_password_length => $min_password_length, - require_strong_password => $require_strong_password, + categorycode => $categorycode, + description => $description, + enrolmentperiod => $enrolmentperiod, + enrolmentperioddate => $enrolmentperioddate, + password_expiry_days => $password_expiry_days, + upperagelimit => $upperagelimit, + dateofbirthrequired => $dateofbirthrequired, + enrolmentfee => $enrolmentfee, + reservefee => $reservefee, + hidelostitems => $hidelostitems, + overduenoticerequired => $overduenoticerequired, + category_type => $category_type, + can_be_guarantee => $can_be_guarantee, + BlockExpiredPatronOpacActions => $BlockExpiredPatronOpacActions, + checkprevcheckout => $checkPrevCheckout, + default_privacy => $default_privacy, + reset_password => $reset_password, + change_password => $change_password, + exclude_from_local_holds_priority => $exclude_from_local_holds_priority, + min_password_length => $min_password_length, + require_strong_password => $require_strong_password, + force_password_reset_when_set_by_staff => $force_password_reset_when_set_by_staff, }); eval { $category->store; diff --git a/installer/data/mysql/atomicupdate/bug_33462.pl b/installer/data/mysql/atomicupdate/bug_33462.pl new file mode 100755 index 0000000000..e059d12e18 --- /dev/null +++ b/installer/data/mysql/atomicupdate/bug_33462.pl @@ -0,0 +1,36 @@ +use Modern::Perl; + +return { + bug_number => "33462", + description => "Force password reset for new patrons entered by staff", + up => sub { + my ($args) = @_; + my ($dbh, $out) = @$args{qw(dbh out)}; + $dbh->do(q{ + INSERT IGNORE INTO systempreferences ( `variable`, `value`, `options`, `explanation`, `type` ) +VALUES ('ForcePasswordResetWhenSetByStaff', '0', NULL,'Force a staff created patron account to reset its password after its first OPAC login.', 'YesNo') + }); + say $out "Added new system preference 'ForcePasswordResetWhenSetByStaff'"; + + if( !column_exists( 'categories', 'force_password_reset_when_set_by_staff' ) ) { + $dbh->do("ALTER TABLE categories ADD COLUMN `force_password_reset_when_set_by_staff` TINYINT(1) NULL DEFAULT NULL AFTER `require_strong_password` -- if patrons of this category are required to reset password after being created by a staff member"); + } + say $out "Added column to categories 'force_password_reset_when_set_by_staff'"; + + if( !column_exists( 'borrowers', 'needs_password_reset' ) ) { + $dbh->do("ALTER TABLE borrowers ADD COLUMN `needs_password_reset` TINYINT(1) NULL DEFAULT NULL AFTER `primary_contact_method` -- tracks if a patron needs to reset their password after account creation"); + } + say $out "Added column to borrowers 'needs_password_reset'"; + + if( !column_exists( 'borrower_modifications', 'needs_password_reset' ) ) { + $dbh->do("ALTER TABLE borrower_modifications ADD COLUMN `needs_password_reset` TINYINT(1) NULL DEFAULT NULL AFTER `primary_contact_method` -- tracks if a patron needs to reset their password after account creation"); + } + say $out "Added column to borrower_modifications 'needs_password_reset'"; + + if( !column_exists( 'deletedborrowers', 'needs_password_reset' ) ) { + $dbh->do("ALTER TABLE deletedborrowers ADD COLUMN `needs_password_reset` TINYINT(1) NULL DEFAULT NULL AFTER `primary_contact_method` -- tracks if a patron needs to reset their password after account creation"); + } + say $out "Added column to deletedborrowers 'needs_password_reset'"; + + }, +}; diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql index c1f800fe7f..2715797335 100644 --- a/installer/data/mysql/kohastructure.sql +++ b/installer/data/mysql/kohastructure.sql @@ -1410,6 +1410,7 @@ CREATE TABLE `borrower_modifications` ( `extended_attributes` mediumtext DEFAULT NULL, `gdpr_proc_consent` datetime DEFAULT NULL COMMENT 'data processing consent', `primary_contact_method` varchar(45) DEFAULT NULL COMMENT 'useful for reporting purposes', + `needs_password_reset` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'tracks if a patron needs to reset their password after account creation', PRIMARY KEY (`verification_token`(191),`borrowernumber`), KEY `verification_token` (`verification_token`(191)), KEY `borrowernumber` (`borrowernumber`) @@ -1542,6 +1543,7 @@ CREATE TABLE `borrowers` ( `anonymized` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'flag for data anonymization', `autorenew_checkouts` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'flag for allowing auto-renewal', `primary_contact_method` varchar(45) DEFAULT NULL COMMENT 'useful for reporting purposes', + `needs_password_reset` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'tracks if a patron needs to reset their password after account creation', PRIMARY KEY (`borrowernumber`), UNIQUE KEY `cardnumber` (`cardnumber`), UNIQUE KEY `userid` (`userid`), @@ -1749,6 +1751,7 @@ CREATE TABLE `categories` ( `min_password_length` smallint(6) DEFAULT NULL COMMENT 'set minimum password length for patrons in this category', `require_strong_password` tinyint(1) DEFAULT NULL COMMENT 'set required password strength for patrons in this category', `exclude_from_local_holds_priority` tinyint(1) DEFAULT NULL COMMENT 'Exclude patrons of this category from local holds priority', + `force_password_reset_when_set_by_staff` tinyint(1) DEFAULT NULL COMMENT 'if patrons of this category are required to reset password after being created by a staff member', PRIMARY KEY (`categorycode`), UNIQUE KEY `categorycode` (`categorycode`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; @@ -2669,6 +2672,7 @@ CREATE TABLE `deletedborrowers` ( `anonymized` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'flag for data anonymization', `autorenew_checkouts` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'flag for allowing auto-renewal', `primary_contact_method` varchar(45) DEFAULT NULL COMMENT 'useful for reporting purposes', + `needs_password_reset` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'tracks if a patron needs to reset their password after account creation', KEY `borrowernumber` (`borrowernumber`), KEY `cardnumber` (`cardnumber`), KEY `sms_provider_id` (`sms_provider_id`) diff --git a/installer/data/mysql/mandatory/sysprefs.sql b/installer/data/mysql/mandatory/sysprefs.sql index 30197591a8..36266e5950 100644 --- a/installer/data/mysql/mandatory/sysprefs.sql +++ b/installer/data/mysql/mandatory/sysprefs.sql @@ -255,6 +255,7 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` ('FinesIncludeGracePeriod','1',NULL,'If enabled, fines calculations will include the grace period.','YesNo'), ('FinesLog','1',NULL,'If ON, log fines','YesNo'), ('finesMode','off','off|production','Choose the fines mode, \'off\' (no charges), \'production\' (accrue overdue fines). Requires accruefines cronjob.','Choice'), +('ForcePasswordResetWhenSetByStaff','0', NULL, 'Forces a staff created patron account to reset its password after its first OPAC login.','YesNo'), ('FRBRizeEditions','0','','If ON, Koha will query one or more ISBN web services for associated ISBNs and display an Editions tab on the details pages','YesNo'), ('GenerateAuthorityField667', 'Machine generated authority record', NULL, 'When BiblioAddsAuthorities and AutoCreateAuthorities are enabled, use this as a default value for the 667$a field of MARC21 records', 'free'), ('GenerateAuthorityField670', 'Work cat.', NULL, 'When BiblioAddsAuthorities and AutoCreateAuthorities are enabled, use this as a default value for the 670$a field of MARC21 records', 'free'), diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/categories.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/categories.tt index a1d49cadc7..a38c888269 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/categories.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/categories.tt @@ -337,6 +337,41 @@ [% END %] +
  • + + +
    + Choose whether staff created patrons of this category be forced into resetting their password after their first OPAC login. +
    +