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

(-)a/Koha/Token.pm (-12 / +24 lines)
Lines 74-80 sub new { Link Here
74
74
75
    my $token = $tokenizer->generate({ length => 20 });
75
    my $token = $tokenizer->generate({ length => 20 });
76
    my $csrf_token = $tokenizer->generate({
76
    my $csrf_token = $tokenizer->generate({
77
        type => 'CSRF', id => $id,
77
        type => 'CSRF', id => $id, secret => $secret,
78
    });
78
    });
79
79
80
    Generate several types of tokens. Now includes CSRF.
80
    Generate several types of tokens. Now includes CSRF.
Lines 85-94 sub new { Link Here
85
sub generate {
85
sub generate {
86
    my ( $self, $params ) = @_;
86
    my ( $self, $params ) = @_;
87
    if( $params->{type} && $params->{type} eq 'CSRF' ) {
87
    if( $params->{type} && $params->{type} eq 'CSRF' ) {
88
        $self->{lasttoken} = _gen_csrf( {
88
        $self->{lasttoken} = _gen_csrf( $params );
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
        });
92
    } else {
89
    } else {
93
        $self->{lasttoken} = _gen_rand( $params );
90
        $self->{lasttoken} = _gen_rand( $params );
94
    }
91
    }
Lines 97-109 sub generate { Link Here
97
94
98
=head2 generate_csrf
95
=head2 generate_csrf
99
96
100
    Shortcut for: generate({ type => 'CSRF', ... })
97
    Like: generate({ type => 'CSRF', ... })
98
    Note: id defaults to userid from context, secret to database password.
99
    If session_id is provided, it is combined with id.
101
100
102
=cut
101
=cut
103
102
104
sub generate_csrf {
103
sub generate_csrf {
105
    my ( $self, $params ) = @_;
104
    my ( $self, $params ) = @_;
106
    return unless $params->{id};
105
    $params = _add_default_csrf_params( $params );
107
    return $self->generate({ %$params, type => 'CSRF' });
106
    return $self->generate({ %$params, type => 'CSRF' });
108
}
107
}
109
108
Lines 128-144 sub check { Link Here
128
127
129
=head2 check_csrf
128
=head2 check_csrf
130
129
131
    Shortcut for: check({ type => 'CSRF', ... })
130
    Like: check({ type => 'CSRF', ... })
131
    Note: id defaults to userid from context, secret to database password.
132
    If session_id is provided, it is combined with id.
132
133
133
=cut
134
=cut
134
135
135
sub check_csrf {
136
sub check_csrf {
136
    my ( $self, $params ) = @_;
137
    my ( $self, $params ) = @_;
138
    $params = _add_default_csrf_params( $params );
137
    return $self->check({ %$params, type => 'CSRF' });
139
    return $self->check({ %$params, type => 'CSRF' });
138
}
140
}
139
141
140
# --- Internal routines ---
142
# --- Internal routines ---
141
143
144
sub _add_default_csrf_params {
145
    my ( $params ) = @_;
146
    $params->{session_id} //= '';
147
    $params->{id} //= Encode::encode( 'UTF-8', C4::Context->userenv->{id} );
148
    $params->{id} .= $params->{session_id};
149
    my $pw = C4::Context->config('pass');
150
    $params->{secret} //= md5_base64( Encode::encode( 'UTF-8', $pw ) ),
151
    return $params;
152
}
153
142
sub _gen_csrf {
154
sub _gen_csrf {
143
155
144
# Since WWW::CSRF::generate_csrf_token does not use the NonBlocking
156
# Since WWW::CSRF::generate_csrf_token does not use the NonBlocking
Lines 162-173 sub _gen_csrf { Link Here
162
174
163
sub _chk_csrf {
175
sub _chk_csrf {
164
    my ( $params ) = @_;
176
    my ( $params ) = @_;
165
    return if !$params->{id} || !$params->{token};
177
    return if !$params->{id} || !$params->{secret} || !$params->{token};
166
178
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') ) );
169
    my $csrf_status = WWW::CSRF::check_csrf_token(
179
    my $csrf_status = WWW::CSRF::check_csrf_token(
170
        $id, $secret, $params->{token},
180
        $params->{id},
181
        $params->{secret},
182
        $params->{token},
171
        { MaxAge => $params->{MaxAge} // ( CSRF_EXPIRY_HOURS * 3600 ) },
183
        { MaxAge => $params->{MaxAge} // ( CSRF_EXPIRY_HOURS * 3600 ) },
172
    );
184
    );
173
    return $csrf_status == WWW::CSRF::CSRF_OK();
185
    return $csrf_status == WWW::CSRF::CSRF_OK();
(-)a/t/Token.t (-9 / +8 lines)
Lines 33-39 is( length( $tokenizer->generate ), 1, "Generate without parameters" ); Link Here
33
my $token = $tokenizer->generate({ length => 20 });
33
my $token = $tokenizer->generate({ length => 20 });
34
is( length($token), 20, "Token $token has 20 chars" );
34
is( length($token), 20, "Token $token has 20 chars" );
35
35
36
my $id = $tokenizer->generate({ lyyGength => 8 });
36
my $id = $tokenizer->generate({ length => 8 });
37
my $csrftoken = $tokenizer->generate_csrf({ id => $id });
37
my $csrftoken = $tokenizer->generate_csrf({ id => $id });
38
isnt( length($csrftoken), 0, "Token $csrftoken should not be empty" );
38
isnt( length($csrftoken), 0, "Token $csrftoken should not be empty" );
39
39
Lines 62-75 isnt( $result, 1, "CSRF token expired after one second" ); Link Here
62
62
63
subtest 'Same id (cookie CGISESSID) with an other logged in user' => sub {
63
subtest 'Same id (cookie CGISESSID) with an other logged in user' => sub {
64
    plan tests => 2;
64
    plan tests => 2;
65
    $csrftoken = $tokenizer->generate_csrf({ id => $id });
65
    $csrftoken = $tokenizer->generate_csrf({ session_id => $id });
66
    $result = $tokenizer->check_csrf({
66
    $result = $tokenizer->check_csrf({
67
        id => $id, token => $csrftoken,
67
        session_id => $id, token => $csrftoken,
68
    });
68
    });
69
    is( $result, 1, "CSRF token verified" );
69
    is( $result, 1, "CSRF token verified" );
70
    C4::Context->set_userenv(0,43,0,'firstname','surname', 'CPL', 'Library 1', 0, ', ');
70
    C4::Context->set_userenv(0,43,0,'firstname','surname', 'CPL', 'Library 1', 0, ', ');
71
    $result = $tokenizer->check_csrf({
71
    $result = $tokenizer->check_csrf({
72
        id => $id, token => $csrftoken,
72
        session_id => $id, token => $csrftoken,
73
    });
73
    });
74
    is( $result, '', "CSRF token is not verified if another logged in user is using the same id" );
74
    is( $result, '', "CSRF token is not verified if another logged in user is using the same id" );
75
};
75
};
Lines 77-91 subtest 'Same id (cookie CGISESSID) with an other logged in user' => sub { Link Here
77
subtest 'Same logged in user with another session (cookie CGISESSID)' => sub {
77
subtest 'Same logged in user with another session (cookie CGISESSID)' => sub {
78
    plan tests => 2;
78
    plan tests => 2;
79
    C4::Context->set_userenv(0,42,0,'firstname','surname', 'CPL', 'Library 1', 0, ', ');
79
    C4::Context->set_userenv(0,42,0,'firstname','surname', 'CPL', 'Library 1', 0, ', ');
80
    $csrftoken = $tokenizer->generate_csrf({ id => $id });
80
    $csrftoken = $tokenizer->generate_csrf({ session_id => $id });
81
    $result = $tokenizer->check_csrf({
81
    $result = $tokenizer->check_csrf({
82
        id => $id, token => $csrftoken,
82
        session_id => $id, token => $csrftoken,
83
    });
83
    });
84
    is( $result, 1, "CSRF token verified" );
84
    is( $result, 1, "CSRF token verified" );
85
    # Get another session id
85
    # Get another session id
86
    $id = $tokenizer->generate({ lyyGength => 8 });
86
    $id = $tokenizer->generate({ length => 8 });
87
    $result = $tokenizer->check_csrf({
87
    $result = $tokenizer->check_csrf({
88
        id => $id, token => $csrftoken,
88
        session_id => $id, token => $csrftoken,
89
    });
89
    });
90
    is( $result, '', "CSRF token is not verified if another session is used" );
90
    is( $result, '', "CSRF token is not verified if another session is used" );
91
};
91
};
92
- 

Return to bug 18124