Lines 1-6
Link Here
|
1 |
package Koha::Token; |
1 |
package Koha::Token; |
2 |
|
2 |
|
3 |
# Created as wrapper for CSRF tokens, but designed for more general use |
3 |
# Created as wrapper for CSRF and JWT tokens, but designed for more general use |
4 |
|
4 |
|
5 |
# Copyright 2016 Rijksmuseum |
5 |
# Copyright 2016 Rijksmuseum |
6 |
# |
6 |
# |
Lines 52-57
use Modern::Perl;
Link Here
|
52 |
use Bytes::Random::Secure; |
52 |
use Bytes::Random::Secure; |
53 |
use String::Random; |
53 |
use String::Random; |
54 |
use WWW::CSRF; |
54 |
use WWW::CSRF; |
|
|
55 |
use Mojo::JWT; |
55 |
use Digest::MD5 qw( md5_base64 ); |
56 |
use Digest::MD5 qw( md5_base64 ); |
56 |
use Encode; |
57 |
use Encode; |
57 |
use Koha::Exceptions::Token; |
58 |
use Koha::Exceptions::Token; |
Lines 78-83
sub new {
Link Here
|
78 |
my $csrf_token = $tokenizer->generate({ |
79 |
my $csrf_token = $tokenizer->generate({ |
79 |
type => 'CSRF', id => $id, secret => $secret, |
80 |
type => 'CSRF', id => $id, secret => $secret, |
80 |
}); |
81 |
}); |
|
|
82 |
my $jwt = $tokenizer->generate({ |
83 |
type => 'JWT, id => $id, secret => $secret, |
84 |
}); |
81 |
|
85 |
|
82 |
Generate several types of tokens. Now includes CSRF. |
86 |
Generate several types of tokens. Now includes CSRF. |
83 |
For non-CSRF tokens an optional pattern parameter overrides length. |
87 |
For non-CSRF tokens an optional pattern parameter overrides length. |
Lines 101-106
sub generate {
Link Here
|
101 |
my ( $self, $params ) = @_; |
105 |
my ( $self, $params ) = @_; |
102 |
if( $params->{type} && $params->{type} eq 'CSRF' ) { |
106 |
if( $params->{type} && $params->{type} eq 'CSRF' ) { |
103 |
$self->{lasttoken} = _gen_csrf( $params ); |
107 |
$self->{lasttoken} = _gen_csrf( $params ); |
|
|
108 |
} elsif( $params->{type} && $params->{type} eq 'JWT' ) { |
109 |
$self->{lasttoken} = _gen_jwt( $params ); |
104 |
} else { |
110 |
} else { |
105 |
$self->{lasttoken} = _gen_rand( $params ); |
111 |
$self->{lasttoken} = _gen_rand( $params ); |
106 |
} |
112 |
} |
Lines 122-127
sub generate_csrf {
Link Here
|
122 |
return $self->generate({ %$params, type => 'CSRF' }); |
128 |
return $self->generate({ %$params, type => 'CSRF' }); |
123 |
} |
129 |
} |
124 |
|
130 |
|
|
|
131 |
=head2 generate_jwt |
132 |
|
133 |
Like: generate({ type => 'JWT', ... }) |
134 |
Note that JWT is designed to encode a structure but here we are actually only allowing a value |
135 |
that will be store in the key 'id'. |
136 |
|
137 |
=cut |
138 |
|
139 |
sub generate_jwt { |
140 |
my ( $self, $params ) = @_; |
141 |
return if !$params->{id}; |
142 |
$params = _add_default_jwt_params( $params ); |
143 |
return $self->generate({ %$params, type => 'JWT' }); |
144 |
} |
145 |
|
125 |
=head2 check |
146 |
=head2 check |
126 |
|
147 |
|
127 |
my $result = $tokenizer->check({ |
148 |
my $result = $tokenizer->check({ |
Lines 138-143
sub check {
Link Here
|
138 |
if( $params->{type} && $params->{type} eq 'CSRF' ) { |
159 |
if( $params->{type} && $params->{type} eq 'CSRF' ) { |
139 |
return _chk_csrf( $params ); |
160 |
return _chk_csrf( $params ); |
140 |
} |
161 |
} |
|
|
162 |
elsif( $params->{type} && $params->{type} eq 'JWT' ) { |
163 |
return _chk_jwt( $params ); |
164 |
} |
141 |
return; |
165 |
return; |
142 |
} |
166 |
} |
143 |
|
167 |
|
Lines 156-161
sub check_csrf {
Link Here
|
156 |
return $self->check({ %$params, type => 'CSRF' }); |
180 |
return $self->check({ %$params, type => 'CSRF' }); |
157 |
} |
181 |
} |
158 |
|
182 |
|
|
|
183 |
=head2 check_jwt |
184 |
|
185 |
Like: check({ type => 'JWT', id => $id, token => $token }) |
186 |
|
187 |
Will return true if the token contains the passed id |
188 |
|
189 |
=cut |
190 |
|
191 |
sub check_jwt { |
192 |
my ( $self, $params ) = @_; |
193 |
$params = _add_default_jwt_params( $params ); |
194 |
return $self->check({ %$params, type => 'JWT' }); |
195 |
} |
196 |
|
197 |
=head2 decode_jwt |
198 |
|
199 |
$tokenizer->decode_jwt({ type => 'JWT', token => $token }) |
200 |
|
201 |
Will return the value of the id stored in the token. |
202 |
|
203 |
=cut |
204 |
sub decode_jwt { |
205 |
my ( $self, $params ) = @_; |
206 |
$params = _add_default_jwt_params( $params ); |
207 |
return _decode_jwt( $params ); |
208 |
} |
209 |
|
159 |
# --- Internal routines --- |
210 |
# --- Internal routines --- |
160 |
|
211 |
|
161 |
sub _add_default_csrf_params { |
212 |
sub _add_default_csrf_params { |
Lines 220-225
sub _gen_rand {
Link Here
|
220 |
return $token; |
271 |
return $token; |
221 |
} |
272 |
} |
222 |
|
273 |
|
|
|
274 |
sub _add_default_jwt_params { |
275 |
my ( $params ) = @_; |
276 |
my $pw = C4::Context->config('pass'); |
277 |
$params->{secret} //= md5_base64( Encode::encode( 'UTF-8', $pw ) ), |
278 |
return $params; |
279 |
} |
280 |
|
281 |
sub _gen_jwt { |
282 |
my ( $params ) = @_; |
283 |
return if !$params->{id} || !$params->{secret}; |
284 |
|
285 |
return Mojo::JWT->new( |
286 |
claims => { id => $params->{id} }, |
287 |
secret => $params->{secret} |
288 |
)->encode; |
289 |
} |
290 |
|
291 |
sub _chk_jwt { |
292 |
my ( $params ) = @_; |
293 |
return if !$params->{id} || !$params->{secret} || !$params->{token}; |
294 |
|
295 |
my $claims = Mojo::JWT->new(secret => $params->{secret})->decode($params->{token}); |
296 |
|
297 |
return 1 if exists $claims->{id} && $claims->{id} == $params->{id}; |
298 |
} |
299 |
|
300 |
sub _decode_jwt { |
301 |
my ( $params ) = @_; |
302 |
return if !$params->{token} || !$params->{secret}; |
303 |
|
304 |
my $claims = Mojo::JWT->new(secret => $params->{secret})->decode($params->{token}); |
305 |
|
306 |
return $claims->{id}; |
307 |
} |
308 |
|
223 |
=head1 AUTHOR |
309 |
=head1 AUTHOR |
224 |
|
310 |
|
225 |
Marcel de Rooy, Rijksmuseum Amsterdam, The Netherlands |
311 |
Marcel de Rooy, Rijksmuseum Amsterdam, The Netherlands |