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

(-)a/Koha/Allowlist.pm (+93 lines)
Line 0 Link Here
1
package Koha::Allowlist;
2
3
use Modern::Perl;
4
use Array::Utils qw( array_minus );
5
6
=head1 NAME
7
8
Koha::Allowlist - Allowlist implementation base class
9
10
=head1 API
11
12
=head2 Class Methods
13
14
    my $allow_list Koha::Allowlist->new({ interface => 'staff' });
15
16
=head3 new
17
18
Constructor.
19
20
Interface must be 'staff' or 'opac' - default to 'opac'
21
22
=cut
23
24
sub new {
25
    my ($class, $args) = @_;
26
    $args = {} unless defined $args;
27
    my $self = bless ($args, $class);
28
    $self->{interface} =
29
      ( $args->{interface} && $args->{interface} eq 'staff' )
30
      ? 'staff'
31
      : 'opac';
32
    return $self;
33
}
34
35
=head3 apply
36
37
    my $ui_fields = Koha::Allowlist->new({ interface => 'staff' })->apply({ input => $hashref, additional_deny_list => [qw( list of fields )] });
38
39
40
Apply an allowlist to input data.
41
42
=cut
43
44
sub apply {
45
    my ( $self, $params ) = @_;
46
47
    my $input = $params->{input};
48
    my $additional_deny_list = $params->{additional_deny_list} || [];
49
50
    my $blocked = {};
51
    my $ui_fields = { map { $_ => 1 } $self->get_ui_fields() };
52
    return unless $ui_fields and %$ui_fields;
53
54
    delete $ui_fields->{$_} for @$additional_deny_list;
55
56
    return unless $input  and %$input;
57
58
    my @keys = keys %$input;
59
    foreach my $key (@keys){
60
        unless ( exists $ui_fields->{ $key } ) {
61
            #NOTE: We capture the deleted data so that it can be used for logging purposes
62
            $blocked->{$key} = delete $input->{ $key };
63
        }
64
    }
65
66
    if ( %$blocked ) {
67
        while ( my ($k, $v)=each %$blocked){
68
            # FIXME We should raise an exception from here
69
            my @c = caller;
70
            warn sprintf "Forbidden - Tried to modify '%s' with '%s' from %s", $k, $v, "@c";
71
        }
72
    }
73
74
    return $blocked;
75
}
76
77
=head3 get_ui_fields
78
79
    my $ui_fields = $self->get_ui_fields();
80
    my $ui_fields = Koha::Patron::Allowlist::Public->new->get_ui_fields
81
82
=cut
83
84
sub get_ui_fields {
85
    my ($self)           = @_;
86
    my @all_fields       = $self->_get_all_fields();
87
    my @global_deny_list = $self->_global_deny_list();
88
    my @deny_list        = ( $self->{interface} eq 'staff' ) ? $self->_deny_list_staff() : $self->_deny_list_opac();
89
    my @global_allow_list = array_minus @all_fields, @global_deny_list;
90
    return array_minus @global_allow_list, @deny_list;
91
}
92
93
1;
(-)a/Koha/Patron/Allowlist.pm (+129 lines)
Line 0 Link Here
1
package Koha::Patron::Allowlist;
2
3
use parent 'Koha::Allowlist';
4
5
# This must not be replaced by Koha::Patrons->columns
6
# We want developper to not forget to adjust it manually and add new attribute to the deny list if needed
7
sub _get_all_fields {
8
    return qw(
9
      borrowernumber
10
      cardnumber
11
      surname
12
      firstname
13
      title
14
      othernames
15
      initials
16
      streetnumber
17
      streettype
18
      address
19
      address2
20
      city
21
      state
22
      zipcode
23
      country
24
      email
25
      phone
26
      mobile
27
      fax
28
      emailpro
29
      phonepro
30
      B_streetnumber
31
      B_streettype
32
      B_address
33
      B_address2
34
      B_city
35
      B_state
36
      B_zipcode
37
      B_country
38
      B_email
39
      B_phone
40
      dateofbirth
41
      branchcode
42
      categorycode
43
      dateenrolled
44
      dateexpiry
45
      date_renewed
46
      gonenoaddress
47
      lost
48
      debarred
49
      debarredcomment
50
      contactname
51
      contactfirstname
52
      contacttitle
53
      borrowernotes
54
      relationship
55
      sex
56
      password
57
      flags
58
      userid
59
      opacnote
60
      contactnote
61
      sort1
62
      sort2
63
      altcontactfirstname
64
      altcontactsurname
65
      altcontactaddress1
66
      altcontactaddress2
67
      altcontactaddress3
68
      altcontactstate
69
      altcontactzipcode
70
      altcontactcountry
71
      altcontactphone
72
      smsalertnumber
73
      sms_provider_id
74
      privacy
75
      privacy_guarantor_fines
76
      privacy_guarantor_checkouts
77
      checkprevcheckout
78
      updated_on
79
      lastseen
80
      lang
81
      login_attempts
82
      overdrive_auth_token
83
      anonymized
84
      autorenew_checkouts
85
      primary_contact_method
86
    );
87
}
88
89
sub _global_deny_list {
90
    return qw(
91
      borrowernumber
92
      date_renewed
93
      debarred
94
      debarredcomment
95
      flags
96
      privacy
97
      privacy_guarantor_fines
98
      privacy_guarantor_checkouts
99
      checkprevcheckout
100
      updated_on
101
      lastseen
102
      lang
103
      login_attempts
104
      overdrive_auth_token
105
      anonymized
106
    );
107
}
108
109
sub _deny_list_opac {
110
    return qw(
111
      dateenrolled
112
      dateexpiry
113
      gonenoaddress
114
      lost
115
      borrowernotes
116
      relationship
117
      opacnote
118
      sort1
119
      sort2
120
      sms_provider_id
121
      autorenew_checkouts
122
    );
123
}
124
125
sub _deny_list_staff {
126
    return qw();
127
}
128
129
1;
(-)a/members/memberentry.pl (-1 / +7 lines)
Lines 46-51 use Koha::Patron::HouseboundRoles; Link Here
46
use Koha::Token;
46
use Koha::Token;
47
use Email::Valid;
47
use Email::Valid;
48
use Koha::SMS::Providers;
48
use Koha::SMS::Providers;
49
use Koha::Patron::Allowlist::Staff;
50
51
my $ui_allowlist = Koha::Patron::Allowlist->new({ interface => 'staff' });
52
49
53
50
my $input = CGI->new;
54
my $input = CGI->new;
51
my %data;
55
my %data;
Lines 323-329 if ($op eq 'save' || $op eq 'insert'){ Link Here
323
    # If the cardnumber is blank, treat it as null.
327
    # If the cardnumber is blank, treat it as null.
324
    $newdata{'cardnumber'} = undef if $newdata{'cardnumber'} =~ /^\s*$/;
328
    $newdata{'cardnumber'} = undef if $newdata{'cardnumber'} =~ /^\s*$/;
325
329
326
    if (my $error_code = checkcardnumber($newdata{cardnumber},$newdata{borrowernumber})){
330
    if (my $error_code = checkcardnumber($newdata{cardnumber}, $borrowernumber)){
327
        push @errors, $error_code == 1
331
        push @errors, $error_code == 1
328
            ? 'ERROR_cardnumber_already_exists'
332
            ? 'ERROR_cardnumber_already_exists'
329
            : $error_code == 2
333
            : $error_code == 2
Lines 430-435 if ( defined $sms ) { Link Here
430
    $newdata{smsalertnumber} = $sms;
434
    $newdata{smsalertnumber} = $sms;
431
}
435
}
432
436
437
$ui_allowlist->apply({ input => \%newdata });
438
433
###  Error checks should happen before this line.
439
###  Error checks should happen before this line.
434
$nok = $nok || scalar(@errors);
440
$nok = $nok || scalar(@errors);
435
if ((!$nok) and $nodouble and ($op eq 'insert' or $op eq 'save')){
441
if ((!$nok) and $nodouble and ($op eq 'insert' or $op eq 'save')){
(-)a/opac/opac-memberentry.pl (-1 / +13 lines)
Lines 45-50 use Koha::Patron::Modifications; Link Here
45
use Koha::Patron::Categories;
45
use Koha::Patron::Categories;
46
use Koha::Token;
46
use Koha::Token;
47
use Koha::AuthorisedValues;
47
use Koha::AuthorisedValues;
48
49
use Koha::Patron::Allowlist;
50
51
my $ui_allowlist = Koha::Patron::Allowlist->new();
52
48
my $cgi = CGI->new;
53
my $cgi = CGI->new;
49
my $dbh = C4::Context->dbh;
54
my $dbh = C4::Context->dbh;
50
55
Lines 127-133 if ( $action eq 'create' ) { Link Here
127
132
128
    my @empty_mandatory_fields = (CheckMandatoryFields( \%borrower, $action ), CheckMandatoryAttributes( \%borrower, $attributes ) );
133
    my @empty_mandatory_fields = (CheckMandatoryFields( \%borrower, $action ), CheckMandatoryAttributes( \%borrower, $attributes ) );
129
    my $invalidformfields = CheckForInvalidFields(\%borrower);
134
    my $invalidformfields = CheckForInvalidFields(\%borrower);
135
    my $consent_dt = delete $borrower{gdpr_proc_consent};
130
    delete $borrower{'password2'};
136
    delete $borrower{'password2'};
137
138
    my @unwanted_fields = split( /\|/, C4::Context->preference('PatronSelfRegistrationBorrowerUnwantedField') || q|| );
139
    $ui_allowlist->apply({ input => \%borrower, additional_deny_list => \@unwanted_fields });
140
131
    my $cardnumber_error_code;
141
    my $cardnumber_error_code;
132
    if ( !grep { $_ eq 'cardnumber' } @empty_mandatory_fields ) {
142
    if ( !grep { $_ eq 'cardnumber' } @empty_mandatory_fields ) {
133
        # No point in checking the cardnumber if it's missing and mandatory, it'll just generate a
143
        # No point in checking the cardnumber if it's missing and mandatory, it'll just generate a
Lines 218-224 if ( $action eq 'create' ) { Link Here
218
            );
228
            );
219
229
220
            $borrower{password}         ||= Koha::AuthUtils::generate_password(Koha::Patron::Categories->find($borrower{categorycode}));
230
            $borrower{password}         ||= Koha::AuthUtils::generate_password(Koha::Patron::Categories->find($borrower{categorycode}));
221
            my $consent_dt = delete $borrower{gdpr_proc_consent};
222
            my $patron = Koha::Patron->new( \%borrower )->store;
231
            my $patron = Koha::Patron->new( \%borrower )->store;
223
            Koha::Patron::Consent->new({ borrowernumber => $patron->borrowernumber, type => 'GDPR_PROCESSING', given_on => $consent_dt })->store if $consent_dt;
232
            Koha::Patron::Consent->new({ borrowernumber => $patron->borrowernumber, type => 'GDPR_PROCESSING', given_on => $consent_dt })->store if $consent_dt;
224
            if ( $patron ) {
233
            if ( $patron ) {
Lines 266-271 elsif ( $action eq 'update' ) { Link Here
266
    # Send back the data to the template
275
    # Send back the data to the template
267
    %borrower = ( %$borrower, %borrower );
276
    %borrower = ( %$borrower, %borrower );
268
277
278
    my @unwanted_fields = split( /\|/, C4::Context->preference('PatronSelfModificationBorrowerUnwantedField') || q|| );
279
    $ui_allowlist->apply({ input => \%borrower, additional_deny_list => \@unwanted_fields });
280
269
    if (@empty_mandatory_fields || @$invalidformfields) {
281
    if (@empty_mandatory_fields || @$invalidformfields) {
270
        $template->param(
282
        $template->param(
271
            empty_mandatory_fields => \@empty_mandatory_fields,
283
            empty_mandatory_fields => \@empty_mandatory_fields,
(-)a/t/db_dependent/Koha/Patron/Allowlist.t (-1 / +101 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/perl
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 <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Test::More tests => 4;
21
22
use Test::MockModule;
23
use Test::Exception;
24
use Test::Warn;
25
26
use Koha::Patrons;
27
use Koha::Patron::Allowlist;
28
29
use t::lib::Mocks;
30
31
subtest '_get_all_fields' => sub {
32
    my @columns = Koha::Patrons->columns;
33
    is_deeply(
34
        \@columns,
35
        [ Koha::Patron::Allowlist->_get_all_fields ],
36
        'All DB columns must be listed in _get_all_fields'
37
    );
38
};
39
40
subtest 'Staff Allowlist' => sub {
41
    plan tests => 4;
42
    my $input = { firstname => 'test firstname', surname => 'test surname', flags => '1' };
43
    my $allowlist = Koha::Patron::Allowlist->new({ interface => 'staff' });
44
    warning_like {
45
        $allowlist->apply({ input => $input, });
46
    } qr{Forbidden - Tried to modify 'flags' with '1' from};
47
    is( $input->{firstname}, 'test firstname', 'firstname preserved' );
48
    is( $input->{surname}, 'test surname', 'surname preserved' );
49
    is( $input->{flags}, undef, 'flags filtered' );
50
};
51
52
subtest 'Public Allowlist' => sub {
53
    plan tests => 4;
54
    my $input = { firstname => 'test firstname', surname => 'test surname', flags => '1' };
55
    my $allowlist = Koha::Patron::Allowlist->new();
56
    warning_like {
57
        $allowlist->apply({ input => $input, });
58
    } qr{Forbidden - Tried to modify 'flags' with '1' from};
59
60
    is( $input->{firstname}, 'test firstname', 'firstname preserved' );
61
    is( $input->{surname},  'test surname', 'surname preserved' );
62
    is( $input->{flags}, undef, 'flags filtered' );
63
};
64
65
subtest 'additional_deny_list' => sub {
66
    plan tests => 10;
67
68
    my $input = { firstname => 'test firstname', surname => 'test surname' };
69
    my $allowlist = Koha::Patron::Allowlist->new();
70
71
    $allowlist->apply({
72
        input => $input,
73
        additional_deny_list => [],
74
    });
75
76
    is( $input->{firstname}, 'test firstname', 'firstname preserved' );
77
    is( $input->{surname},   'test surname',   'surname filtered' );
78
    is( $input->{flags},     undef,            'flags filtered' );
79
80
    $allowlist->apply({
81
        input => $input,
82
        additional_deny_list => ['not_here'],
83
    });
84
85
    is( $input->{firstname}, 'test firstname', 'firstname preserved' );
86
    is( $input->{surname},   'test surname',   'surname filtered' );
87
    is( $input->{flags},     undef,            'flags filtered' );
88
89
    warning_like {
90
        $allowlist->apply({
91
            input => $input,
92
            additional_deny_list => ['surname'],
93
        });
94
    }
95
    qr{Forbidden - Tried to modify 'surname' with 'test surname' from};
96
97
    is( $input->{firstname}, 'test firstname', 'firstname preserved' );
98
    is( $input->{surname},   undef,            'surname filtered' );
99
    is( $input->{flags},     undef,            'flags filtered' );
100
101
};

Return to bug 28935