From b7b6e684f64e1e4319dd2bd551321e6ce2516197 Mon Sep 17 00:00:00 2001
From: Sam Lau <samalau@gmail.com>
Date: Fri, 7 Jun 2024 18:44:54 +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' and a new column to the categories table
'force_password_reset_when_set_by_staff.

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: Laura_Escamilla <laura.escamilla@bywatersolutions.com>
---
 C4/Auth.pm                                    |  2 ++
 Koha/Patron.pm                                | 15 +++++---
 Koha/Patron/Category.pm                       | 16 +++++++++
 admin/categories.pl                           |  4 +++
 .../prog/en/modules/admin/categories.tt       | 35 +++++++++++++++++++
 .../en/modules/admin/preferences/patrons.pref |  2 +-
 .../bootstrap/en/modules/opac-auth.tt         | 12 +++++--
 7 files changed, 77 insertions(+), 9 deletions(-)

diff --git a/C4/Auth.pm b/C4/Auth.pm
index 57c05778d1..41405c9d37 100644
--- a/C4/Auth.pm
+++ b/C4/Auth.pm
@@ -1457,6 +1457,8 @@ sub checkauth {
         too_many_login_attempts               => ( $patron and $patron->account_locked ),
         password_has_expired                  => ( $patron and $patron->password_expired ),
         is_anonymous_patron                   => ( $is_anonymous_patron ),
+        password_expiration_date              => ( $patron and $patron->password_expiration_date ),
+        date_enrolled                         => ( $patron and $patron->dateenrolled ),
         auth_error                            => $auth_error,
     );
 
diff --git a/Koha/Patron.pm b/Koha/Patron.pm
index 1691c9d25b..8dd771fe54 100644
--- a/Koha/Patron.pm
+++ b/Koha/Patron.pm
@@ -291,11 +291,16 @@ 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);
+                }
+                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(
diff --git a/Koha/Patron/Category.pm b/Koha/Patron/Category.pm
index 8a9a77a320..b0fb33809a 100644
--- a/Koha/Patron/Category.pm
+++ b/Koha/Patron/Category.pm
@@ -229,6 +229,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 c89fce0fba..b5bff62f4e 100755
--- a/admin/categories.pl
+++ b/admin/categories.pl
@@ -81,11 +81,13 @@ elsif ( $op eq 'cud-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");
 
@@ -113,6 +115,7 @@ elsif ( $op eq 'cud-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 );
@@ -147,6 +150,7 @@ elsif ( $op eq 'cud-add_validate' ) {
             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/koha-tmpl/intranet-tmpl/prog/en/modules/admin/categories.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/categories.tt
index 5f6b18fe8b..7d141e14f7 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/categories.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/categories.tt
@@ -341,6 +341,41 @@
                       [% END %]
                     </select>
                 </li>
+                <li>
+                    <label for="force_password_reset_when_set_by_staff">Force new patron password reset: </label>
+                    <select name="force_password_reset_when_set_by_staff" id="force_password_reset_when_set_by_staff">
+                        [% IF category.force_password_reset_when_set_by_staff.defined %]
+                            [% IF category.force_password_reset_when_set_by_staff %]
+                                [% IF Koha.Preference('ForcePasswordResetWhenSetByStaff') %]
+                                    <option value="-1">Follow system preference ForcePasswordResetWhenSetByStaff (Force)</option>
+                                [% ELSE %]
+                                    <option value="-1">Follow system preference ForcePasswordResetWhenSetByStaff (Don't force)</option>
+                                [% END %]
+                                    <option value="1" selected="selected">Force</option>
+                                    <option value="0">Don't force</option>
+                            [% ELSE %]
+                                [% IF Koha.Preference('ForcePasswordResetWhenSetByStaff') %]
+                                    <option value="-1">Follow system preference ForcePasswordResetWhenSetByStaff (Force)</option>
+                                [% ELSE %]
+                                    <option value="-1">Follow system preference ForcePasswordResetWhenSetByStaff (Don't force)</option>
+                                [% END %]
+                                    <option value="1">Force</option>
+                                    <option value="0" selected="selected">Don't force</option>
+                            [% END %]
+                        [% ELSE %]
+                            [% IF Koha.Preference('ForcePasswordResetWhenSetByStaff') %]
+                                <option value="-1" selected="selected">Follow system preference ForcePasswordResetWhenSetByStaff (Force)</option>
+                            [% ELSE %]
+                                <option value="-1" selected="selected">Follow system preference ForcePasswordResetWhenSetByStaff (Don't force)</option>
+                            [% END %]
+                                <option value="1">Force</option>
+                                <option value="0">Don't force</option>
+                        [% END %]
+                    </select>
+                    <div class="hint">
+                        Choose whether staff created patrons of this category be forced into resetting their password after their first OPAC login.
+                    </div>
+                </li>
                 <li><label for="block_expired">Block expired patron OPAC actions:</label>
                     <select name="BlockExpiredPatronOpacActions" id="block_expired" multiple="multiple">
                         <optgroup label="Follow system preference">
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref
index 6d8ccf1183..e49b216d38 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/patrons.pref
@@ -1,4 +1,4 @@
-fPatrons:
+Patrons:
     General:
      -
          - pref: CheckPrevCheckout
diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-auth.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-auth.tt
index d2fe338e54..e176ca9b3c 100644
--- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-auth.tt
+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-auth.tt
@@ -197,9 +197,15 @@
                         [% END %]
 
                         [% IF !(invalid_username_or_password || too_many_login_attempts) and password_has_expired %]
-                            <div class="alert alert-info">
-                                <p><strong>Error: </strong>Your password has expired!</p>
-                            </div>
+                            [% IF date_enrolled == password_expiration_date %]
+                                <div class="alert alert-info">
+                                    <p><strong>Error: </strong>It's your first login! You need to reset your password.</p>
+                                </div>
+                            [% ELSE %]
+                                <div class="alert alert-info">
+                                    <p><strong>Error: </strong>Your password has expired!</p>
+                                </div>
+                            [% END %]
                             [% IF Koha.Preference('EnableExpiredPasswordReset') %]
                                 <a href="/cgi-bin/koha/opac-reset-password.pl">Reset your password</a>.
                             [% ELSIF Koha.Preference('OpacPasswordChange') && Categories.can_any_reset_password %]
-- 
2.39.2