Browse Source

Bug 29924: Add password expiration feature

This patch adds the ability to define password_expiry_days for a patron
category.

When defined a patron's password will expire after X days and they will
be required to reset their password. If OPAC resets are enabled for the
catgeory they may do so on their own, otherwise they will need to
contact the library

To test:
 1 - Apply patch, updatedatabase
 2 - Set 'Password expiration' for a patron category
     Home-> Administration-> Patron categories-> Edit
 3 - Create a new patron in this category with a userid/password set,
     and an email
 4 - Confirm their password_expiration_date field is set
     SELECT password_expiration_date FROM borrowers WHERE borrowernumber=51;
 5 - Create a new patron, do not set a password
 6 - Confirm their password_expiration_date field is NULL
 7 - Update the patron with an expiration to be expired
     UPDATE borrowers SET password_expiration_date='2022-01-01' WHERE borrowernumber=51;
 8 - Give the borrower catalogue permission
 9 - Attempt to log in to Straff interface
10 - Confirm you are signed out and notified that password must be
     reset
11 - Attempt to sign in to OPAC
12 - Confirm you are signed out and notified password must be reset
13 - Enable password reset for the patron's category and perform a
     password reset
     Note: you will have to find the link in the message_queue unless
     you have emails setup on your test environment
     SELECT * FROM message_queue WHERE borrowernumber=51;
14 - Confirm that you can now sign in and password_expiration_date field
     is set 10 days in the future
15 - Expire the patron's password again
16 - Change the patron's password via the staff interface
17 - Confirm they can sign in and the expiration is updated

Signed-off-by: Owen Leonard <oleonard@myacpl.org>

Signed-off-by: Bob Bennhoff <bbennhoff@clicweb.org>

Signed-off-by: Andrew Fuerste-Henry <andrew@bywatersolutions.com>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Fridolin Somers <fridolin.somers@biblibre.com>
22.05.x
Nick Clemens 2 years ago
committed by Fridolin Somers
parent
commit
a732138d9d
  1. 12
      C4/Auth.pm
  2. 20
      Koha/Patron.pm
  3. 20
      Koha/Patron/Category.pm
  4. 7
      Koha/REST/V1/Auth.pm
  5. 3
      admin/categories.pl
  6. 11
      koha-tmpl/intranet-tmpl/prog/en/modules/admin/categories.tt
  7. 7
      koha-tmpl/intranet-tmpl/prog/en/modules/auth.tt
  8. 13
      koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-auth.tt

12
C4/Auth.pm

@ -966,6 +966,8 @@ sub checkauth {
$info{oldip} = $more_info->{old_ip};
$info{newip} = $more_info->{new_ip};
$info{different_ip} = 1;
} elsif ( $return eq 'password_expired' ) {
$info{password_has_expired} = 1;
}
}
}
@ -1404,7 +1406,8 @@ sub checkauth {
PatronSelfRegistration => C4::Context->preference("PatronSelfRegistration"),
PatronSelfRegistrationDefaultCategory => C4::Context->preference("PatronSelfRegistrationDefaultCategory"),
opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'},
too_many_login_attempts => ( $patron and $patron->account_locked )
too_many_login_attempts => ( $patron and $patron->account_locked ),
password_has_expired => ( $patron and $patron->password_expired )
);
$template->param( SCO_login => 1 ) if ( $query->param('sco_user_login') );
@ -1792,6 +1795,9 @@ sub check_cookie_auth {
} elsif ( $userid ) {
$session->param( 'lasttime', time() );
my $patron = Koha::Patrons->find({ userid => $userid });
$patron = Koha::Patron->find({ cardnumber => $userid }) unless $patron;
return ("password_expired", undef ) if $patron->password_expired;
my $flags = defined($flagsrequired) ? haspermission( $userid, $flagsrequired ) : 1;
if ($flags) {
C4::Context->_new_userenv($sessionID);
@ -1903,8 +1909,8 @@ sub checkpw {
# 0 if auth is nok
# -1 if user bind failed (LDAP only)
if ( $patron and $patron->account_locked ) {
# Nothing to check, account is locked
if ( $patron and ( $patron->account_locked || $patron->password_expired ) ) {
# Nothing to check, account is locked or password expired
} elsif ($ldap && defined($password)) {
my ( $retval, $retcard, $retuserid ) = checkpw_ldap(@_); # EXTERNAL AUTH
if ( $retval == 1 ) {

20
Koha/Patron.pm

@ -268,6 +268,9 @@ 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 );
# Create a disabled account if no password provided
$self->password( $self->password
? Koha::AuthUtils::hash_password( $self->password )
@ -788,6 +791,21 @@ sub is_expired {
return 0;
}
=head3 password_expired
my $password_expired = $patron->password_expired;
Returns 1 if the patron's password is expired or 0;
=cut
sub password_expired {
my ($self) = @_;
return 0 unless $self->password_expiration_date;
return 1 if dt_from_string( $self->password_expiration_date ) < dt_from_string->truncate( to => 'day' );
return 0;
}
=head3 is_going_to_expire
my $is_going_to_expire = $patron->is_going_to_expire;
@ -887,6 +905,8 @@ sub set_password {
my $digest = Koha::AuthUtils::hash_password($password);
$self->password_expiration_date( $self->category->get_password_expiry_date );
# We do not want to call $self->store and retrieve password from DB
$self->password($digest);
$self->login_attempts(0);

20
Koha/Patron/Category.pm

@ -113,6 +113,26 @@ sub get_expiry_date {
}
}
=head3 get_password_expiry_date
Returns date based on password expiry days set for the category. If the value is not set
we return undef, password does not expire
my $expiry_date = $category->get_password_expiry_date();
=cut
sub get_password_expiry_date {
my ($self, $date ) = @_;
if ( $self->password_expiry_days ) {
$date ||= dt_from_string;
$date = dt_from_string( $date ) unless ref $date;
return $date->add( days => $self->password_expiry_days )->ymd;
} else {
return;
}
}
=head3 effective_reset_password
Returns if patrons in this category can reset their password. If set in $self->reset_password

7
Koha/REST/V1/Auth.pm

@ -491,7 +491,12 @@ sub _basic_auth {
Koha::Exceptions::Authorization::Unauthorized->throw( error => 'Invalid password' );
}
return Koha::Patrons->find({ userid => $user_id });
my $patron = Koha::Patrons->find({ userid => $user_id });
if ( $patron->password_expired ) {
Koha::Exceptions::Authorization::Unauthorized->throw( error => 'Password has expired' );
}
return $patron;
}
=head3 _set_userenv

3
admin/categories.pl

@ -63,6 +63,7 @@ elsif ( $op eq 'add_validate' ) {
my $description = $input->param('description');
my $enrolmentperiod = $input->param('enrolmentperiod');
my $enrolmentperioddate = $input->param('enrolmentperioddate') || undef;
my $password_expiry_days = $input->param('password_expiry_days') || undef;
my $upperagelimit = $input->param('upperagelimit');
my $dateofbirthrequired = $input->param('dateofbirthrequired');
my $enrolmentfee = $input->param('enrolmentfee');
@ -103,6 +104,7 @@ elsif ( $op eq 'add_validate' ) {
$category->description($description);
$category->enrolmentperiod($enrolmentperiod);
$category->enrolmentperioddate($enrolmentperioddate);
$category->password_expiry_days($password_expiry_days);
$category->upperagelimit($upperagelimit);
$category->dateofbirthrequired($dateofbirthrequired);
$category->enrolmentfee($enrolmentfee);
@ -134,6 +136,7 @@ elsif ( $op eq 'add_validate' ) {
description => $description,
enrolmentperiod => $enrolmentperiod,
enrolmentperioddate => $enrolmentperioddate,
password_expiry_days => $password_expiry_days,
upperagelimit => $upperagelimit,
dateofbirthrequired => $dateofbirthrequired,
enrolmentfee => $enrolmentfee,

11
koha-tmpl/intranet-tmpl/prog/en/modules/admin/categories.tt

@ -162,6 +162,10 @@
</ol>
</fieldset>
</li>
<li>
<label for="password_expiry_days">Password expiration: </label>
<input type="text" name="password_expiry_days" id="password_expiry_days" value="[% category.password_expiry_days | html %]" size="3" maxlength="5" /> days
</li>
<li>
<label for="dateofbirthrequired">Age required: </label>
<input type="text" name="dateofbirthrequired" id="dateofbirthrequired" value="[% category.dateofbirthrequired | html %]" size="3" maxlength="3" /> years
@ -451,6 +455,7 @@
[% END %]
</td>
</tr>
<tr><th scope="row">Password expiration: </th><td>[% category.password_expiry_days | html %] days</td></tr>
<tr><th scope="row">Age required: </th><td>[% category.dateofbirthrequired | html %] years</td></tr>
<tr><th scope="row">Upperage limit: </th><td>[% category.upperagelimit | html %] years</td></tr>
<tr><th scope="row">Enrollment fee: </th><td>[% category.enrolmentfee | $Price %]</td></tr>
@ -519,6 +524,7 @@
<th scope="col">Category name</th>
<th scope="col">Type</th>
<th scope="col">Enrollment period</th>
<th scope="col">Password expiration</th>
<th scope="col">Age required</th>
<th scope="col">Upper age limit</th>
<th scope="col">Enrollment fee</th>
@ -561,6 +567,11 @@
until [% category.enrolmentperioddate | $KohaDates %]
[% END %]
</td>
[% IF (category.password_expiry_days) %]
<td>[% category.password_expiry_days | html %] days</td>
[% ELSE %]
<td>-</td>
[% END %]
[% IF (category.dateofbirthrequired) %]
<td>[% category.dateofbirthrequired | html %] years</td>
[% ELSE %]

7
koha-tmpl/intranet-tmpl/prog/en/modules/auth.tt

@ -50,6 +50,13 @@
[% IF Categories.can_any_reset_password && Koha.Preference('OpacBaseURL') %]
<a href="[% Koha.Preference('OpacBaseURL') | url %]/cgi-bin/koha/opac-password-recovery.pl">You must reset your password</a>.
[% END %]
[% ELSIF password_has_expired %]
<div id="login_error"><strong>Error: </strong>Your password has expired!</div>
[% IF Categories.can_any_reset_password && Koha.Preference('OpacBaseURL') %]
<a href="[% Koha.Preference('OpacBaseURL') | url %]/cgi-bin/koha/opac-password-recovery.pl">You must reset your password</a>.
[% ELSE %]
<p>You must contact the library to have your password reset</p>
[% END %]
[% ELSIF invalid_username_or_password %]
<div id="login_error"><strong>Error: </strong>Invalid username or password</div>
[% END %]

13
koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-auth.tt

@ -167,7 +167,16 @@
[% END # /IF GoogleOpenIDConnect %]
[% END # /UNLESS OPACShibOnly %]
[% IF !Koha.Preference('OPACShibOnly') or SCO_login or SCI_login %]
[% IF password_has_expired %]
<p class="error"><strong>Error: </strong>Your password has expired!</p>
[% IF Koha.Preference('OpacPasswordChange') && Categories.can_any_reset_password %]
<div id="resetpassword">
<a href="/cgi-bin/koha/opac-password-recovery.pl">Reset your password?</a>
</div>
[% ELSE %]
<p>You must contact the library to reset your password</p>
[% END %]
[% ELSIF !Koha.Preference('OPACShibOnly') or SCO_login or SCI_login %]
[% IF SCO_login %]
<form action="/cgi-bin/koha/sco/sco-main.pl" name="auth" id="auth" method="post" autocomplete="off">
[% ELSIF SCI_login %]
@ -218,7 +227,7 @@
</div>
[% END %]
</form>
[% END # / IF !OPACShibOnly or SCO_login or SCI_login %]
[% END # / IF password_has_expired / ELSIF !OPACShibOnly or SCO_login or SCI_login %]
[% END # / IF loginprompt %]
[% ELSE %]

Loading…
Cancel
Save