|
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 |