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

(-)a/Koha/Token.pm (-11 / +32 lines)
Lines 31-42 Koha::Token - Tokenizer Link Here
31
31
32
    # safely generate a CSRF token (nonblocking)
32
    # safely generate a CSRF token (nonblocking)
33
    my $csrf_token = $tokenizer->generate({
33
    my $csrf_token = $tokenizer->generate({
34
        CSRF => 1, id => $id, secret => $secret,
34
        type => 'CSRF', id => $id, secret => $secret,
35
    });
35
    });
36
36
37
    # or check a CSRF token
37
    # or check a CSRF token
38
    my $result = $tokenizer->check({
38
    my $result = $tokenizer->check_csrf({
39
        CSRF => 1, id => $id, secret => $secret, token => $token,
39
        id => $id, secret => $secret, token => $token,
40
    });
40
    });
41
41
42
=head1 DESCRIPTION
42
=head1 DESCRIPTION
Lines 48-53 Koha::Token - Tokenizer Link Here
48
=cut
48
=cut
49
49
50
use Modern::Perl;
50
use Modern::Perl;
51
use Bytes::Random::Secure ();
52
use String::Random ();
53
use WWW::CSRF ();
51
use base qw(Class::Accessor);
54
use base qw(Class::Accessor);
52
use constant HMAC_SHA1_LENGTH => 20;
55
use constant HMAC_SHA1_LENGTH => 20;
53
56
Lines 68-74 sub new { Link Here
68
71
69
    my $token = $tokenizer->generate({ length => 20 });
72
    my $token = $tokenizer->generate({ length => 20 });
70
    my $csrf_token = $tokenizer->generate({
73
    my $csrf_token = $tokenizer->generate({
71
        CSRF => 1, id => $id, secret => $secret,
74
        type => 'CSRF', id => $id, secret => $secret,
72
    });
75
    });
73
76
74
    Generate several types of tokens. Now includes CSRF.
77
    Generate several types of tokens. Now includes CSRF.
Lines 78-84 sub new { Link Here
78
81
79
sub generate {
82
sub generate {
80
    my ( $self, $params ) = @_;
83
    my ( $self, $params ) = @_;
81
    if( $params->{CSRF} ) {
84
    if( $params->{type} && $params->{type} eq 'CSRF' ) {
82
        $self->{lasttoken} = _gen_csrf( $params );
85
        $self->{lasttoken} = _gen_csrf( $params );
83
    } else {
86
    } else {
84
        $self->{lasttoken} = _gen_rand( $params );
87
        $self->{lasttoken} = _gen_rand( $params );
Lines 86-95 sub generate { Link Here
86
    return $self->{lasttoken};
89
    return $self->{lasttoken};
87
}
90
}
88
91
92
=head2 generate_csrf
93
94
    Shortcut for: generate({ type => 'CSRF', ... })
95
96
=cut
97
98
sub generate_csrf {
99
    my ( $self, $params ) = @_;
100
    return $self->generate({ %$params, type => 'CSRF' });
101
}
102
89
=head2 check
103
=head2 check
90
104
91
    my $result = $tokenizer->check({
105
    my $result = $tokenizer->check({
92
        CSRF => 1, id => $id, secret => $secret, token => $token,
106
        type => 'CSRF', id => $id, secret => $secret, token => $token,
93
    });
107
    });
94
108
95
    Check several types of tokens. Now includes CSRF.
109
    Check several types of tokens. Now includes CSRF.
Lines 99-110 sub generate { Link Here
99
113
100
sub check {
114
sub check {
101
    my ( $self, $params ) = @_;
115
    my ( $self, $params ) = @_;
102
    if( $params->{CSRF} ) {
116
    if( $params->{type} && $params->{type} eq 'CSRF' ) {
103
        return _chk_csrf( $params );
117
        return _chk_csrf( $params );
104
    }
118
    }
105
    return;
119
    return;
106
}
120
}
107
121
122
=head2 check_csrf
123
124
    Shortcut for: check({ type => 'CSRF', ... })
125
126
=cut
127
128
sub check_csrf {
129
    my ( $self, $params ) = @_;
130
    return $self->check({ %$params, type => 'CSRF' });
131
}
132
108
# --- Internal routines ---
133
# --- Internal routines ---
109
134
110
sub _gen_csrf {
135
sub _gen_csrf {
Lines 116-123 sub _gen_csrf { Link Here
116
    my ( $params ) = @_;
141
    my ( $params ) = @_;
117
    return if !$params->{id} || !$params->{secret};
142
    return if !$params->{id} || !$params->{secret};
118
143
119
    require Bytes::Random::Secure;
120
    require WWW::CSRF;
121
144
122
    my $randomizer = Bytes::Random::Secure->new( NonBlocking => 1 );
145
    my $randomizer = Bytes::Random::Secure->new( NonBlocking => 1 );
123
        # this is most fundamental: do not use /dev/random since it is
146
        # this is most fundamental: do not use /dev/random since it is
Lines 134-140 sub _chk_csrf { Link Here
134
    my ( $params ) = @_;
157
    my ( $params ) = @_;
135
    return if !$params->{id} || !$params->{secret} || !$params->{token};
158
    return if !$params->{id} || !$params->{secret} || !$params->{token};
136
159
137
    require WWW::CSRF;
138
    my $csrf_status = WWW::CSRF::check_csrf_token(
160
    my $csrf_status = WWW::CSRF::check_csrf_token(
139
        $params->{id},
161
        $params->{id},
140
        $params->{secret},
162
        $params->{secret},
Lines 148-154 sub _gen_rand { Link Here
148
    my $length = $params->{length} || 1;
170
    my $length = $params->{length} || 1;
149
    $length = 1 unless $length > 0;
171
    $length = 1 unless $length > 0;
150
172
151
    require String::Random;
152
    return String::Random::random_string( '.' x $length );
173
    return String::Random::random_string( '.' x $length );
153
}
174
}
154
175
(-)a/opac/opac-memberentry.pl (-8 / +4 lines)
Lines 183-190 elsif ( $action eq 'update' ) { Link Here
183
183
184
    my $borrower = GetMember( borrowernumber => $borrowernumber );
184
    my $borrower = GetMember( borrowernumber => $borrowernumber );
185
    die "Wrong CSRF token"
185
    die "Wrong CSRF token"
186
        unless Koha::Token->new->check({
186
        unless Koha::Token->new->check_csrf({
187
            CSRF   => 1,
188
            id     => $borrower->{userid},
187
            id     => $borrower->{userid},
189
            secret => md5_base64( C4::Context->config('pass') ),
188
            secret => md5_base64( C4::Context->config('pass') ),
190
            token  => scalar $cgi->param('csrf_token'),
189
            token  => scalar $cgi->param('csrf_token'),
Lines 202-209 elsif ( $action eq 'update' ) { Link Here
202
            empty_mandatory_fields => \@empty_mandatory_fields,
201
            empty_mandatory_fields => \@empty_mandatory_fields,
203
            invalid_form_fields    => $invalidformfields,
202
            invalid_form_fields    => $invalidformfields,
204
            borrower               => \%borrower,
203
            borrower               => \%borrower,
205
            csrf_token             => Koha::Token->new->generate({
204
            csrf_token             => Koha::Token->new->generate_csrf({
206
                CSRF   => 1,
207
                id     => $borrower->{userid},
205
                id     => $borrower->{userid},
208
                secret => md5_base64( C4::Context->config('pass') ),
206
                secret => md5_base64( C4::Context->config('pass') ),
209
            }),
207
            }),
Lines 238-245 elsif ( $action eq 'update' ) { Link Here
238
                action => 'edit',
236
                action => 'edit',
239
                nochanges => 1,
237
                nochanges => 1,
240
                borrower => GetMember( borrowernumber => $borrowernumber ),
238
                borrower => GetMember( borrowernumber => $borrowernumber ),
241
                csrf_token => Koha::Token->new->generate({
239
                csrf_token => Koha::Token->new->generate_csrf({
242
                    CSRF   => 1,
243
                    id     => $borrower->{userid},
240
                    id     => $borrower->{userid},
244
                    secret => md5_base64( C4::Context->config('pass') ),
241
                    secret => md5_base64( C4::Context->config('pass') ),
245
                }),
242
                }),
Lines 262-269 elsif ( $action eq 'edit' ) { #Display logged in borrower's data Link Here
262
        borrower  => $borrower,
259
        borrower  => $borrower,
263
        guarantor => scalar Koha::Patrons->find($borrowernumber)->guarantor(),
260
        guarantor => scalar Koha::Patrons->find($borrowernumber)->guarantor(),
264
        hidden => GetHiddenFields( $mandatory, 'modification' ),
261
        hidden => GetHiddenFields( $mandatory, 'modification' ),
265
        csrf_token => Koha::Token->new->generate({
262
        csrf_token => Koha::Token->new->generate_csrf({
266
            CSRF   => 1,
267
            id     => $borrower->{userid},
263
            id     => $borrower->{userid},
268
            secret => md5_base64( C4::Context->config('pass') ),
264
            secret => md5_base64( C4::Context->config('pass') ),
269
        }),
265
        }),
(-)a/t/Token.t (-5 / +4 lines)
Lines 30-45 is( length($token), 20, "Token $token has 20 chars" ); Link Here
30
30
31
my $id = $tokenizer->generate({ length => 8 });
31
my $id = $tokenizer->generate({ length => 8 });
32
my $secr = $tokenizer->generate({ length => 32 });
32
my $secr = $tokenizer->generate({ length => 32 });
33
my $csrftoken = $tokenizer->generate({ CSRF => 1, id => $id, secret => $secr });
33
my $csrftoken = $tokenizer->generate_csrf({ id => $id, secret => $secr });
34
isnt( length($csrftoken), 0, "Token $csrftoken should not be empty" );
34
isnt( length($csrftoken), 0, "Token $csrftoken should not be empty" );
35
35
36
is( $tokenizer->check, undef, "Check without any parameters" );
36
is( $tokenizer->check, undef, "Check without any parameters" );
37
my $result = $tokenizer->check({
37
my $result = $tokenizer->check_csrf({
38
    CSRF => 1, id => $id, secret => $secr, token => $csrftoken,
38
    id => $id, secret => $secr, token => $csrftoken,
39
});
39
});
40
is( $result, 1, "CSRF token verified" );
40
is( $result, 1, "CSRF token verified" );
41
41
42
$result = $tokenizer->check({
42
$result = $tokenizer->check({
43
    CSRF => 1, id => $id, secret => $secr, token => $token,
43
    type => 'CSRF', id => $id, secret => $secr, token => $token,
44
});
44
});
45
isnt( $result, 1, "This token is no CSRF token" );
45
isnt( $result, 1, "This token is no CSRF token" );
46
- 

Return to bug 16929