File Coverage

lib/Mojolicious/Plugin/CSRFProtect.pm
Criterion Covered Total %
statement 59 65 90.7
branch 9 12 75.0
condition 9 18 50.0
subroutine 14 15 93.3
pod 1 1 100.0
total 92 111 82.8


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::CSRFProtect;
2 2     2   1930 use strict;
  2         6  
  2         84  
3 2     2   13 use warnings;
  2         4  
  2         72  
4 2     2   23 use Carp qw/croak/;
  2         4  
  2         161  
5              
6 2     2   10 use Mojo::Base 'Mojolicious::Plugin';
  2         4  
  2         15  
7 2     2   384 use Mojo::Util qw/md5_sum/;
  2         4  
  2         107  
8 2     2   12 use Mojo::ByteStream qw/b/;
  2         2  
  2         2231  
9              
10             our $VERSION = '0.16';
11              
12             sub register {
13 2     2 1 93 my ( $self, $app, $conf ) = @_;
14              
15             # On error callback
16 2         4 my $on_error;
17 2 100 66     18 if ( $conf->{on_error} && ref($conf->{on_error}) eq 'CODE' ) {
18 1         3 $on_error = $conf->{on_error};
19             } else {
20 1     2   4 $on_error = sub { shift->render( status => 403, text => "Forbidden!" ) };
  2         11  
21             }
22              
23             # Replace "form_for" helper
24 2         54 my $original_form_for = delete $app->renderer->helpers->{form_for};
25 2 50       89 croak qq{Cannot find helper "form_for". Please, load plugin "TagHelpers" before}
26             unless $original_form_for;
27              
28             $app->helper(
29             form_for => sub {
30 0     0   0 my $c = shift;
31 0 0 0     0 if ( defined $_[-1] && ref( $_[-1] ) eq 'CODE' ) {
32 0         0 my $cb = $_[-1];
33             $_[-1] = sub {
34 0         0 $app->hidden_field( 'csrftoken' => $self->_csrftoken($c) ) . $cb->();
35 0         0 };
36             }
37 0         0 return $app->$original_form_for(@_);
38 2         21 } );
39              
40             # Add "csrftoken" helper
41 2     7   180 $app->helper( csrftoken => sub { $self->_csrftoken( $_[0] ) } );
  7         14093  
42              
43             # Add "is_valid_csrftoken" helper
44 2     3   156 $app->helper( is_valid_csrftoken => sub { $self->_is_valid_csrftoken( $_[0] ) } );
  3         7539  
45              
46             # Add "jquery_ajax_csrf_protection" helper
47             $app->helper(
48             jquery_ajax_csrf_protection => sub {
49 1     1   7317 my $js = '';
50 1         39 $js .= q!!;
55              
56 1         8 b($js);
57 2         159 } );
58              
59             # input check
60             $app->hook(
61             before_routes => sub {
62 18     18   459879 my ($c) = @_;
63              
64 18         92 my $request_token = $c->req->param('csrftoken');
65             #my $is_ajax = ( $c->req->headers->header('X-Requested-With') || '' ) eq 'XMLHttpRequest';
66              
67 18 100 66     8317 if ( $c->req->method !~ m/^(?:GET|HEAD|OPTIONS)$/ && !$self->_is_valid_csrftoken($c) ) {
68 3         74 my $path = $c->tx->req->url->to_abs->to_string;
69 3         2695 $c->app->log->debug("CSRFProtect: Wrong CSRF protection token for [$path]!");
70              
71 3         156 $on_error->($c);
72 3         4971 return;
73             }
74              
75 15         946 return 1;
76 2         166 } );
77              
78             }
79              
80             sub _is_valid_csrftoken {
81 10     10   605 my ( $self, $c ) = @_;
82              
83 10         54 my $valid_token = $c->session('csrftoken');
84 10   66     192 my $form_token = $c->req->headers->header('X-CSRF-Token') || $c->param('csrftoken');
85 10 100 33     2340 unless ( $valid_token && $form_token && $form_token eq $valid_token ) {
      66        
86 4         18 return 0;
87             }
88              
89 6         47 return 1;
90             }
91              
92             sub _csrftoken {
93 8     8   19 my ( $self, $c ) = @_;
94              
95 8 100       36 return $c->session('csrftoken') if $c->session('csrftoken');
96              
97 2         104 my $token = md5_sum( md5_sum( time() . {} . rand() . $$ ) );
98              
99 2         31 $c->session( 'csrftoken' => $token );
100 2         40 return $token;
101             }
102              
103             1;
104              
105             __END__