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

(-)a/Koha/Patron.pm (-1 / +1 lines)
Lines 1162-1168 sub set_password { Link Here
1162
    if ($self->password && $self->in_storage) {
1162
    if ($self->password && $self->in_storage) {
1163
        # Get the old password before it's replaced
1163
        # Get the old password before it's replaced
1164
        my $old_password = $self->get_from_storage->password;
1164
        my $old_password = $self->get_from_storage->password;
1165
        my $history_count = C4::Context->preference('PasswordHistoryCount') || 0;
1165
        my $history_count = $self->category->effective_password_history_count || 0;
1166
1166
1167
        # Always store the old password, regardless of current preference
1167
        # Always store the old password, regardless of current preference
1168
        # This ensures we maintain history if the preference is increased later
1168
        # This ensures we maintain history if the preference is increased later
(-)a/Koha/PatronPasswordHistories.pm (-29 / +48 lines)
Lines 21-26 use Koha::Database; Link Here
21
use Koha::PatronPasswordHistory;
21
use Koha::PatronPasswordHistory;
22
use C4::Context;
22
use C4::Context;
23
use Koha::AuthUtils;
23
use Koha::AuthUtils;
24
use Koha::Patrons;
24
25
25
use base qw(Koha::Objects);
26
use base qw(Koha::Objects);
26
27
Lines 42-56 If count is 0 or negative, all password history for the patron will be deleted. Link Here
42
=cut
43
=cut
43
44
44
sub cleanup_old_password_history {
45
sub cleanup_old_password_history {
45
    my ($class, $borrowernumber, $count) = @_;
46
    my ( $class, $borrowernumber, $count ) = @_;
46
47
47
    return unless $borrowernumber;
48
    return unless $borrowernumber;
48
49
49
    # If count is 0 or negative, delete all entries
50
    # If count is 0 or negative, delete all entries
50
    if ($count <= 0) {
51
    if ( $count <= 0 ) {
51
        my $deleted = $class->search({
52
        my $deleted = $class->search(
52
            borrowernumber => $borrowernumber
53
            {
53
        })->delete;
54
                borrowernumber => $borrowernumber
55
            }
56
        )->delete;
54
        return $deleted;
57
        return $deleted;
55
    }
58
    }
56
59
Lines 59-76 sub cleanup_old_password_history { Link Here
59
        { borrowernumber => $borrowernumber },
62
        { borrowernumber => $borrowernumber },
60
        {
63
        {
61
            order_by => { -desc => 'created_on' },
64
            order_by => { -desc => 'created_on' },
62
            rows => $count,
65
            rows     => $count,
63
            columns => ['id']
66
            columns  => ['id']
64
        }
67
        }
65
    )->get_column('id');
68
    )->get_column('id');
66
69
67
    # Delete all entries for this borrower except the ones we're keeping
70
    # Delete all entries for this borrower except the ones we're keeping
68
    my $deleted = 0;
71
    my $deleted = 0;
69
    if (@keep_ids) {
72
    if (@keep_ids) {
70
        $deleted = $class->search({
73
        $deleted = $class->search(
71
            borrowernumber => $borrowernumber,
74
            {
72
            id => { -not_in => \@keep_ids }
75
                borrowernumber => $borrowernumber,
73
        })->delete;
76
                id             => { -not_in => \@keep_ids }
77
            }
78
        )->delete;
74
    }
79
    }
75
80
76
    return $deleted;
81
    return $deleted;
Lines 83-88 Return the DBIC resultset type for this class Link Here
83
=cut
88
=cut
84
89
85
sub _type {
90
sub _type {
91
86
    # The table name in DB is borrower_password_history
92
    # The table name in DB is borrower_password_history
87
    return 'BorrowerPasswordHistory';
93
    return 'BorrowerPasswordHistory';
88
}
94
}
Lines 112-169 Returns true if the password has been used before, false otherwise. Link Here
112
=cut
118
=cut
113
119
114
sub has_used_password {
120
sub has_used_password {
115
    my ($class, $params) = @_;
121
    my ( $class, $params ) = @_;
116
122
117
    my $borrowernumber = $params->{borrowernumber};
123
    my $borrowernumber   = $params->{borrowernumber};
118
    my $password = $params->{password};
124
    my $password         = $params->{password};
119
    my $current_password = $params->{current_password};
125
    my $current_password = $params->{current_password};
120
126
121
    unless ($borrowernumber && $password) {
127
    unless ( $borrowernumber && $password ) {
122
        return 0;
128
        return 0;
123
    }
129
    }
124
130
125
    my $history_count = C4::Context->preference('PasswordHistoryCount') || 0;
131
    # Get the patron and their category
132
    my $patron = Koha::Patrons->find($borrowernumber);
133
    return 0 unless $patron;
134
135
    my $history_count =
136
      $patron->category->effective_password_history_count || 0;
126
137
127
    # If history count is 0, don't check history at all
138
    # If history count is 0, don't check history at all
128
    if ($history_count == 0) {
139
    if ( $history_count == 0 ) {
129
        return 0;
140
        return 0;
130
    }
141
    }
131
142
132
    # If history count is 1, only check current password
143
    # If history count is 1, only check current password
133
    if ($history_count == 1) {
144
    if ( $history_count == 1 ) {
134
        my $matches_current = $current_password && C4::Auth::checkpw_hash($password, $current_password);
145
        my $matches_current = $current_password
146
          && C4::Auth::checkpw_hash( $password, $current_password );
135
        return $matches_current ? 1 : 0;
147
        return $matches_current ? 1 : 0;
136
    }
148
    }
137
149
138
    # First check if password matches current one, if provided
150
    # First check if password matches current one, if provided
139
    if ($current_password && C4::Auth::checkpw_hash($password, $current_password)) {
151
    if ( $current_password
140
        return 1; # Current password counts as a used password
152
        && C4::Auth::checkpw_hash( $password, $current_password ) )
153
    {
154
        return 1;    # Current password counts as a used password
141
    }
155
    }
142
156
143
    # Adjust history count to account for current password
157
    # Adjust history count to account for current password
144
    my $history_entries_to_check = $current_password ? $history_count - 1 : $history_count;
158
    my $history_entries_to_check =
159
      $current_password ? $history_count - 1 : $history_count;
145
160
146
    return 0 if $history_entries_to_check <= 0; # No need to check history if only checking current
161
    return 0
162
      if $history_entries_to_check <=
163
      0;    # No need to check history if only checking current
147
164
148
    # Get the most recent password history entries
165
    # Get the most recent password history entries
149
    my $password_history = $class->search(
166
    my $password_history = $class->search(
150
        { borrowernumber => $borrowernumber },
167
        { borrowernumber => $borrowernumber },
151
        {
168
        {
152
            order_by => { -desc => 'created_on' },
169
            order_by => { -desc => 'created_on' },
153
            rows => $history_entries_to_check
170
            rows     => $history_entries_to_check
154
        }
171
        }
155
    );
172
    );
156
173
157
    # Check each password history entry
174
    # Check each password history entry
158
    while (my $history_entry = $password_history->next) {
175
    while ( my $history_entry = $password_history->next ) {
159
        # Use checkpw_hash to properly compare the plaintext password with stored hash
176
160
        my $matches = C4::Auth::checkpw_hash($password, $history_entry->password);
177
  # Use checkpw_hash to properly compare the plaintext password with stored hash
178
        my $matches =
179
          C4::Auth::checkpw_hash( $password, $history_entry->password );
161
        if ($matches) {
180
        if ($matches) {
162
            return 1; # Password found in history
181
            return 1;    # Password found in history
163
        }
182
        }
164
    }
183
    }
165
184
166
    return 0; # Password not found in history
185
    return 0;            # Password not found in history
167
}
186
}
168
187
169
=head1 AUTHOR
188
=head1 AUTHOR
(-)a/Koha/PatronPasswordHistory.pm (-8 / +11 lines)
Lines 40-45 Return the DBIC resultset type for this object Link Here
40
=cut
40
=cut
41
41
42
sub _type {
42
sub _type {
43
43
    # The table name in DB is borrower_password_history
44
    # The table name in DB is borrower_password_history
44
    return 'BorrowerPasswordHistory';
45
    return 'BorrowerPasswordHistory';
45
}
46
}
Lines 65-71 sub store { Link Here
65
    my ($self) = @_;
66
    my ($self) = @_;
66
67
67
    my $borrowernumber = $self->borrowernumber;
68
    my $borrowernumber = $self->borrowernumber;
68
    my $password = $self->password;
69
    my $password       = $self->password;
69
70
70
    unless ($password) {
71
    unless ($password) {
71
        return;
72
        return;
Lines 78-97 sub store { Link Here
78
    # Store in database
79
    # Store in database
79
    $self = $self->SUPER::store();
80
    $self = $self->SUPER::store();
80
81
82
    # Get the patron and their category
83
    my $patron = Koha::Patrons->find($borrowernumber);
84
    return $self unless $patron;
85
81
    # Get the history count preference
86
    # Get the history count preference
82
    my $history_count = C4::Context->preference('PasswordHistoryCount') || 0;
87
    my $history_count =
88
      $patron->category->effective_password_history_count || 0;
83
89
84
    # Clean up old history entries
90
    # Clean up old history entries
85
    # Adjust history count to account for current password
91
    # Adjust history count to account for current password
86
    my $history_entries_to_keep = $history_count <= 1
92
    my $history_entries_to_keep = $history_count <= 1
87
        ? $history_count  # If 0 or 1, keep that many
93
      ? $history_count         # If 0 or 1, keep that many
88
        : $history_count - 1;  # Otherwise keep historical entries minus current
94
      : $history_count - 1;    # Otherwise keep historical entries minus current
89
95
90
    # Clean up old entries
96
    # Clean up old entries
91
    Koha::PatronPasswordHistories->cleanup_old_password_history(
97
    Koha::PatronPasswordHistories->cleanup_old_password_history(
92
        $borrowernumber,
98
        $borrowernumber, $history_entries_to_keep );
93
        $history_entries_to_keep
94
    );
95
99
96
    return $self;
100
    return $self;
97
}
101
}
98
- 

Return to bug 40824