View | Details | Raw Unified | Return to bug 40824
Collapse All | Expand All

(-)a/Koha/PatronPasswordHistories.pm (+175 lines)
Line 0 Link Here
1
package Koha::PatronPasswordHistories;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <https://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Koha::Database;
21
use Koha::PatronPasswordHistory;
22
use C4::Context;
23
use Koha::AuthUtils;
24
25
use base qw(Koha::Objects);
26
27
=head1 NAME
28
29
Koha::PatronPasswordHistories - Koha PatronPasswordHistory Object set class
30
31
=head1 API
32
33
=head2 Class Methods
34
35
=head3 cleanup_old_password_history
36
37
    Koha::PatronPasswordHistories->cleanup_old_password_history($borrowernumber, $count);
38
39
Clean up old password history for a patron, keeping only the specified number of most recent entries.
40
If count is 0 or negative, all password history for the patron will be deleted.
41
42
=cut
43
44
sub cleanup_old_password_history {
45
    my ($class, $borrowernumber, $count) = @_;
46
47
    return unless $borrowernumber;
48
49
    # If count is 0 or negative, delete all entries
50
    if ($count <= 0) {
51
        my $deleted = $class->search({
52
            borrowernumber => $borrowernumber
53
        })->delete;
54
        return $deleted;
55
    }
56
57
    # Get IDs of entries to keep (the most recent ones)
58
    my @keep_ids = $class->search(
59
        { borrowernumber => $borrowernumber },
60
        {
61
            order_by => { -desc => 'created_on' },
62
            rows => $count,
63
            columns => ['id']
64
        }
65
    )->get_column('id');
66
67
    # Delete all entries for this borrower except the ones we're keeping
68
    my $deleted = 0;
69
    if (@keep_ids) {
70
        $deleted = $class->search({
71
            borrowernumber => $borrowernumber,
72
            id => { -not_in => \@keep_ids }
73
        })->delete;
74
    }
75
76
    return $deleted;
77
}
78
79
=head3 _type
80
81
Return the DBIC resultset type for this class
82
83
=cut
84
85
sub _type {
86
    # The table name in DB is borrower_password_history
87
    return 'BorrowerPasswordHistory';
88
}
89
90
=head3 object_class
91
92
Return the object class for items in this collection
93
94
=cut
95
96
sub object_class {
97
    return 'Koha::PatronPasswordHistory';
98
}
99
100
=head3 has_used_password
101
102
    my $bool = Koha::PatronPasswordHistories->has_used_password({
103
        borrowernumber => $borrowernumber,
104
        password => $plain_text_password,
105
        current_password => $hashed_current_password # Optional
106
    });
107
108
Check if the given password exists in the password history for this patron.
109
If current_password is provided, it will also check against that.
110
Returns true if the password has been used before, false otherwise.
111
112
=cut
113
114
sub has_used_password {
115
    my ($class, $params) = @_;
116
117
    my $borrowernumber = $params->{borrowernumber};
118
    my $password = $params->{password};
119
    my $current_password = $params->{current_password};
120
121
    unless ($borrowernumber && $password) {
122
        return 0;
123
    }
124
125
    my $history_count = C4::Context->preference('PasswordHistoryCount') || 0;
126
127
    # If history count is 0, don't check history at all
128
    if ($history_count == 0) {
129
        return 0;
130
    }
131
132
    # If history count is 1, only check current password
133
    if ($history_count == 1) {
134
        my $matches_current = $current_password && C4::Auth::checkpw_hash($password, $current_password);
135
        return $matches_current ? 1 : 0;
136
    }
137
138
    # First check if password matches current one, if provided
139
    if ($current_password && C4::Auth::checkpw_hash($password, $current_password)) {
140
        return 1; # Current password counts as a used password
141
    }
142
143
    # Adjust history count to account for current password
144
    my $history_entries_to_check = $current_password ? $history_count - 1 : $history_count;
145
146
    return 0 if $history_entries_to_check <= 0; # No need to check history if only checking current
147
148
    # Get the most recent password history entries
149
    my $password_history = $class->search(
150
        { borrowernumber => $borrowernumber },
151
        {
152
            order_by => { -desc => 'created_on' },
153
            rows => $history_entries_to_check
154
        }
155
    );
156
157
    # Check each password history entry
158
    while (my $history_entry = $password_history->next) {
159
        # Use checkpw_hash to properly compare the plaintext password with stored hash
160
        my $matches = C4::Auth::checkpw_hash($password, $history_entry->password);
161
        if ($matches) {
162
            return 1; # Password found in history
163
        }
164
    }
165
166
    return 0; # Password not found in history
167
}
168
169
=head1 AUTHOR
170
171
Koha Development Team <https://koha-community.org/>
172
173
=cut
174
175
1;
(-)a/Koha/PatronPasswordHistory.pm (-1 / +105 lines)
Line 0 Link Here
0
- 
1
package Koha::PatronPasswordHistory;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <https://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Koha::Database;
21
use Koha::AuthUtils;
22
use C4::Context;
23
use C4::Auth;
24
use Koha::Patrons;
25
26
use base qw(Koha::Object);
27
28
=head1 NAME
29
30
Koha::PatronPasswordHistory - Koha PatronPasswordHistory Object class
31
32
=head1 API
33
34
=head2 Class methods
35
36
=head3 _type
37
38
Return the DBIC resultset type for this object
39
40
=cut
41
42
sub _type {
43
    # The table name in DB is borrower_password_history
44
    return 'BorrowerPasswordHistory';
45
}
46
47
=head3 koha_objects_class
48
49
Define relationship with the Koha::PatronPasswordHistories class
50
51
=cut
52
53
sub koha_objects_class {
54
    return 'Koha::PatronPasswordHistories';
55
}
56
57
=head3 store
58
59
Overridden store method to ensure the password is hashed before storing.
60
Also handles cleanup of old password history entries.
61
62
=cut
63
64
sub store {
65
    my ($self) = @_;
66
67
    my $borrowernumber = $self->borrowernumber;
68
    my $password = $self->password;
69
70
    unless ($password) {
71
        return;
72
    }
73
74
    # NOTE: Passwords should always be pre-hashed (bcrypt) before being passed to this method.
75
    # In production, passwords come from the database via Koha::Patron->set_password(),
76
    # which always passes the already-hashed old password from storage.
77
78
    # Store in database
79
    $self = $self->SUPER::store();
80
81
    # Get the history count preference
82
    my $history_count = C4::Context->preference('PasswordHistoryCount') || 0;
83
84
    # Clean up old history entries
85
    # Adjust history count to account for current password
86
    my $history_entries_to_keep = $history_count <= 1
87
        ? $history_count  # If 0 or 1, keep that many
88
        : $history_count - 1;  # Otherwise keep historical entries minus current
89
90
    # Clean up old entries
91
    Koha::PatronPasswordHistories->cleanup_old_password_history(
92
        $borrowernumber,
93
        $history_entries_to_keep
94
    );
95
96
    return $self;
97
}
98
99
=head1 AUTHOR
100
101
Koha Development Team <https://koha-community.org/>
102
103
=cut
104
105
1;

Return to bug 40824