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

(-)a/Koha/Token.pm (-7 / +13 lines)
Lines 51-56 use Modern::Perl; Link Here
51
use Bytes::Random::Secure ();
51
use Bytes::Random::Secure ();
52
use String::Random ();
52
use String::Random ();
53
use WWW::CSRF ();
53
use WWW::CSRF ();
54
use Digest::MD5 qw(md5_base64);
55
use Encode qw( encode );
54
use base qw(Class::Accessor);
56
use base qw(Class::Accessor);
55
use constant HMAC_SHA1_LENGTH => 20;
57
use constant HMAC_SHA1_LENGTH => 20;
56
use constant CSRF_EXPIRY_HOURS => 8; # 8 hours instead of 7 days..
58
use constant CSRF_EXPIRY_HOURS => 8; # 8 hours instead of 7 days..
Lines 72-78 sub new { Link Here
72
74
73
    my $token = $tokenizer->generate({ length => 20 });
75
    my $token = $tokenizer->generate({ length => 20 });
74
    my $csrf_token = $tokenizer->generate({
76
    my $csrf_token = $tokenizer->generate({
75
        type => 'CSRF', id => $id, secret => $secret,
77
        type => 'CSRF', id => $id,
76
    });
78
    });
77
79
78
    Generate several types of tokens. Now includes CSRF.
80
    Generate several types of tokens. Now includes CSRF.
Lines 83-89 sub new { Link Here
83
sub generate {
85
sub generate {
84
    my ( $self, $params ) = @_;
86
    my ( $self, $params ) = @_;
85
    if( $params->{type} && $params->{type} eq 'CSRF' ) {
87
    if( $params->{type} && $params->{type} eq 'CSRF' ) {
86
        $self->{lasttoken} = _gen_csrf( $params );
88
        $self->{lasttoken} = _gen_csrf( {
89
            id     => Encode::encode( 'UTF-8', C4::Context->userenv->{id} . $params->{id} ),
90
            secret => md5_base64( Encode::encode( 'UTF-8', C4::Context->config('pass') ) ),
91
        });
87
    } else {
92
    } else {
88
        $self->{lasttoken} = _gen_rand( $params );
93
        $self->{lasttoken} = _gen_rand( $params );
89
    }
94
    }
Lines 98-110 sub generate { Link Here
98
103
99
sub generate_csrf {
104
sub generate_csrf {
100
    my ( $self, $params ) = @_;
105
    my ( $self, $params ) = @_;
106
    return unless $params->{id};
101
    return $self->generate({ %$params, type => 'CSRF' });
107
    return $self->generate({ %$params, type => 'CSRF' });
102
}
108
}
103
109
104
=head2 check
110
=head2 check
105
111
106
    my $result = $tokenizer->check({
112
    my $result = $tokenizer->check({
107
        type => 'CSRF', id => $id, secret => $secret, token => $token,
113
        type => 'CSRF', id => $id, token => $token,
108
    });
114
    });
109
115
110
    Check several types of tokens. Now includes CSRF.
116
    Check several types of tokens. Now includes CSRF.
Lines 156-167 sub _gen_csrf { Link Here
156
162
157
sub _chk_csrf {
163
sub _chk_csrf {
158
    my ( $params ) = @_;
164
    my ( $params ) = @_;
159
    return if !$params->{id} || !$params->{secret} || !$params->{token};
165
    return if !$params->{id} || !$params->{token};
160
166
167
    my $id = Encode::encode( 'UTF-8', C4::Context->userenv->{id} . $params->{id} );
168
    my $secret = md5_base64( Encode::encode( 'UTF-8', C4::Context->config('pass') ) );
161
    my $csrf_status = WWW::CSRF::check_csrf_token(
169
    my $csrf_status = WWW::CSRF::check_csrf_token(
162
        $params->{id},
170
        $id, $secret, $params->{token},
163
        $params->{secret},
164
        $params->{token},
165
        { MaxAge => $params->{MaxAge} // ( CSRF_EXPIRY_HOURS * 3600 ) },
171
        { MaxAge => $params->{MaxAge} // ( CSRF_EXPIRY_HOURS * 3600 ) },
166
    );
172
    );
167
    return $csrf_status == WWW::CSRF::CSRF_OK();
173
    return $csrf_status == WWW::CSRF::CSRF_OK();
(-)a/t/Token.t (-9 / +41 lines)
Lines 20-58 Link Here
20
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
21
22
use Modern::Perl;
22
use Modern::Perl;
23
use Test::More tests => 8;
23
use Test::More tests => 10;
24
use Time::HiRes qw|usleep|;
24
use Time::HiRes qw|usleep|;
25
use C4::Context;
25
use Koha::Token;
26
use Koha::Token;
26
27
28
C4::Context->_new_userenv('DUMMY SESSION');
29
C4::Context->set_userenv(0,42,0,'firstname','surname', 'CPL', 'Library 1', 0, ', ');
30
27
my $tokenizer = Koha::Token->new;
31
my $tokenizer = Koha::Token->new;
28
is( length( $tokenizer->generate ), 1, "Generate without parameters" );
32
is( length( $tokenizer->generate ), 1, "Generate without parameters" );
29
my $token = $tokenizer->generate({ length => 20 });
33
my $token = $tokenizer->generate({ length => 20 });
30
is( length($token), 20, "Token $token has 20 chars" );
34
is( length($token), 20, "Token $token has 20 chars" );
31
35
32
my $id = $tokenizer->generate({ length => 8 });
36
my $id = $tokenizer->generate({ lyyGength => 8 });
33
my $secr = $tokenizer->generate({ length => 32 });
37
my $csrftoken = $tokenizer->generate_csrf({ id => $id });
34
my $csrftoken = $tokenizer->generate_csrf({ id => $id, secret => $secr });
35
isnt( length($csrftoken), 0, "Token $csrftoken should not be empty" );
38
isnt( length($csrftoken), 0, "Token $csrftoken should not be empty" );
36
39
37
is( $tokenizer->check, undef, "Check without any parameters" );
40
is( $tokenizer->check, undef, "Check without any parameters" );
38
my $result = $tokenizer->check_csrf({
41
my $result = $tokenizer->check_csrf({
39
    id => $id, secret => $secr, token => $csrftoken,
42
    id => $id, token => $csrftoken,
40
});
43
});
41
is( $result, 1, "CSRF token verified" );
44
is( $result, 1, "CSRF token verified" );
42
45
43
$result = $tokenizer->check({
46
$result = $tokenizer->check({
44
    type => 'CSRF', id => $id, secret => $secr, token => $token,
47
    type => 'CSRF', id => $id, token => $token,
45
});
48
});
46
isnt( $result, 1, "This token is no CSRF token" );
49
isnt( $result, 1, "This token is no CSRF token" );
47
50
48
# Test MaxAge parameter
51
# Test MaxAge parameter
49
my $age = 1; # 1 second
52
my $age = 1; # 1 second
50
$result = $tokenizer->check_csrf({
53
$result = $tokenizer->check_csrf({
51
    id => $id, secret => $secr, token => $csrftoken, MaxAge => $age,
54
    id => $id, token => $csrftoken, MaxAge => $age,
52
});
55
});
53
is( $result, 1, "CSRF token still valid within one second" );
56
is( $result, 1, "CSRF token still valid within one second" );
54
usleep $age * 1000000 * 2; # micro (millionth) seconds + 100%
57
usleep $age * 1000000 * 2; # micro (millionth) seconds + 100%
55
$result = $tokenizer->check_csrf({
58
$result = $tokenizer->check_csrf({
56
    id => $id, secret => $secr, token => $csrftoken, MaxAge => $age,
59
    id => $id, token => $csrftoken, MaxAge => $age,
57
});
60
});
58
isnt( $result, 1, "CSRF token expired after one second" );
61
isnt( $result, 1, "CSRF token expired after one second" );
59
- 
62
63
subtest 'Same id (cookie CGISESSID) with an other logged in user' => sub {
64
    plan tests => 2;
65
    $csrftoken = $tokenizer->generate_csrf({ id => $id });
66
    $result = $tokenizer->check_csrf({
67
        id => $id, token => $csrftoken,
68
    });
69
    is( $result, 1, "CSRF token verified" );
70
    C4::Context->set_userenv(0,43,0,'firstname','surname', 'CPL', 'Library 1', 0, ', ');
71
    $result = $tokenizer->check_csrf({
72
        id => $id, token => $csrftoken,
73
    });
74
    is( $result, '', "CSRF token is not verified if another logged in user is using the same id" );
75
};
76
77
subtest 'Same logged in user with another session (cookie CGISESSID)' => sub {
78
    plan tests => 2;
79
    C4::Context->set_userenv(0,42,0,'firstname','surname', 'CPL', 'Library 1', 0, ', ');
80
    $csrftoken = $tokenizer->generate_csrf({ id => $id });
81
    $result = $tokenizer->check_csrf({
82
        id => $id, token => $csrftoken,
83
    });
84
    is( $result, 1, "CSRF token verified" );
85
    # Get another session id
86
    $id = $tokenizer->generate({ lyyGength => 8 });
87
    $result = $tokenizer->check_csrf({
88
        id => $id, token => $csrftoken,
89
    });
90
    is( $result, '', "CSRF token is not verified if another session is used" );
91
};

Return to bug 18124