File Coverage

lib/Mojolicious/Plugin/DeCSRF.pm
Criterion Covered Total %
statement 47 47 100.0
branch 24 24 100.0
condition 9 11 81.8
subroutine 9 9 100.0
pod 1 1 100.0
total 90 92 97.8


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::DeCSRF;
2              
3 2     2   1854 use Mojo::Base 'Mojolicious::Plugin';
  2         5  
  2         16  
4              
5             our $VERSION = '0.94';
6              
7             sub register {
8 2     2 1 92 my ($self, $app, $conf) = @_;
9              
10 2         17 my $base = Mojolicious::Plugin::DeCSRF::Base->new;
11 2 100       25 $base->{token_length} = $conf->{token_length} if $conf->{token_length};
12 2 100       9 $base->{token_name} = $conf->{token_name} if $conf->{token_name};
13 2 100       11 $base->{on_mismatch} = $conf->{on_mismatch} if $conf->{on_mismatch};
14 2 100       7 push @{$base->urls}, $conf->{urls} if $conf->{urls};
  1         24  
15              
16 2     11   22 $app->helper(decsrf => sub { $base->c(shift) });
  11         132974  
17              
18             $app->hook(before_dispatch => sub {
19 13     13   593091 my $c = shift;
20 13 100       401 return $c if $base->c($c)->check;
21 5 100 66     2980 if ($base->on_mismatch && ref($base->on_mismatch) eq 'CODE') {
22 1         67 $base->on_mismatch->($c);
23             } else {
24 4         190 $c->render(
25             text => "Forbidden!",
26             status => 403,
27             );
28             }
29             }
30 2         256 );
31             }
32              
33             package Mojolicious::Plugin::DeCSRF::Base;
34              
35 2     2   1215 use Mojo::Base -base;
  2         5  
  2         13  
36              
37             my $_token_checked = 1;
38             has c => undef;
39             has token_length => 4;
40             has token_name => 'token';
41             has on_mismatch => undef;
42             has urls => sub { [] };
43              
44             sub check {
45 13     13   397 my $self = shift;
46 13         400 my $c = $self->c;
47 13         437 my $token = $c->session($self->token_name);
48 13         561 $_token_checked = 1;
49 13 100       51 if ($self->_match($c->req->url))
50             {
51 8 100 100     1062 return 0 unless (
52             $c->req->param($self->token_name)
53             && $c->req->param($self->token_name) eq $token
54             );
55             };
56 8         2400 return 1;
57             }
58              
59             sub url {
60 10     10   96 my $self = shift;
61 10   100     48 my $url = shift // '';
62 10         226 my $c = $self->c;
63 10 100       75 if ($_token_checked) {
64 8         278 $c->session($self->token_name => $self->_token);
65 8         1094 $_token_checked = 0;
66             }
67 10 100       45 if ($self->_match($url)) {
68 3         3714 return $c->url_for($url)->query([$self->token_name => $c->session($self->token_name)]);
69             }
70 7         35 return $c->url_for($url);
71             }
72              
73             sub _match {
74 23     23   1362 my ($self, $url) = @_;
75 23 100 66     45 if (@{$self->urls} && $url) {
  23         724  
76 17         655 foreach (@{$self->urls}) {
  17         796  
77 24 100       6587 return 1 if $self->c->url_for($url)->path =~ m;^$_$;;
78             }
79             }
80 12         3875 return 0;
81             }
82              
83             sub _token {
84 8     8   284 my @chars = ( "A" .. "Z", "a" .. "z", 0 .. 9, qw(@ $ - _) );
85 8         286 return join("", @chars[ map { rand @chars } ( 1 .. shift->token_length ) ]);
  40         242  
86             }
87              
88             1;
89              
90             __END__