File Coverage

blib/lib/Nephia/Plugin/CSRFDefender.pm
Criterion Covered Total %
statement 62 63 98.4
branch 12 14 85.7
condition 4 6 66.6
subroutine 15 15 100.0
pod 3 5 60.0
total 96 103 93.2


line stmt bran cond sub pod time code
1             package Nephia::Plugin::CSRFDefender;
2 3     3   869920 use 5.008005;
  3         13  
  3         154  
3 3     3   19 use strict;
  3         6  
  3         112  
4 3     3   34 use warnings;
  3         5  
  3         115  
5 3     3   1133 use parent 'Nephia::Plugin';
  3         405  
  3         19  
6              
7             our $VERSION = "0.81";
8              
9             our $ERROR_HTML = <<'...';
10            
11            
12            
13             403 Forbidden
14            
15            
16            

403 Forbidden

17            

18             Session validation failed.
19            

20            
21            
22             ...
23              
24             sub new {
25 2     2 0 44 my ($class, %opts) = @_;
26 2         22 my $self = $class->SUPER::new(%opts);
27 2         142 my $app = $self->app;
28 2         22 $app->action_chain->prepend(CSRFDefender_Before => $self->can('_before_action'));
29 2         249 $app->action_chain->append(CSRFDefender_After => $self->can('_process_content'));
30 2         211 return $self;
31             }
32              
33             sub exports {
34 17     17 1 265345 qw/ get_csrf_defender_token validate_csrf /;
35             }
36              
37             sub get_csrf_defender_token {
38 26     26 1 181 my ($self, $context) = @_;
39             return sub {
40 2     2   1058 _get_csrf_defender_token($self->app);
41 26         133 };
42             }
43              
44             sub validate_csrf {
45 26     26 1 162 my ($self, $context) = @_;
46             return sub {
47 1     1   519 _validate_csrf($self->app);
48 26         132 };
49             }
50              
51             sub _get_csrf_defender_token {
52 4     4   18 my ($app) = @_;
53            
54 4         15 my $session = $app->dsl('session')->();
55              
56 4 50       114 if ( my $token = $session->get('csrf_token') ) {
57 0         0 $token;
58             } else {
59 4         57 $token = generate_token();
60 4         19 $session->set('csrf_token' => $token);
61 4         199 $token;
62             }
63             }
64              
65             sub _validate_csrf {
66 6     6   17 my ($app) = @_;
67            
68 6         28 my $session = $app->dsl('session')->();
69 6         297 my $req = $app->dsl('req')->();
70            
71 6 100       80 if ( $req->{env}->{REQUEST_METHOD} eq 'POST' ) {
72 3         59 my $r_token = $req->param('csrf_token');
73 3         2138 my $session_token = $session->get('csrf_token');
74 3 100 66     63 if ( !$r_token || !$session_token || ( $r_token ne $session_token ) ) {
      66        
75 2         10 return 0;
76             }
77             }
78 4         21 return 1;
79             }
80              
81             sub generate_token {
82 4     4 0 122 my @chars = ('A'..'Z', 'a'..'z', 0..9);
83 4         8 my $ret;
84 4         12 for (1..32) {
85 128         199 $ret .= $chars[int rand @chars];
86             }
87 4         26 return $ret;
88             }
89              
90             sub _before_action {
91 11     11   2960 my ($app, $context) = @_;
92 11         33 my $obj = $app->loaded_plugins->fetch('Nephia::Plugin::CSRFDefender');
93              
94 11 100       220 unless ($obj->{no_validate_hook}) {
95 5 100       20 if (!_validate_csrf($app)) {
96 1         15 return ($context, Nephia::Response->new(
97             403,
98             [
99             'Content-Type' => 'text/html',
100             'Content-Length' => length($ERROR_HTML)
101             ],
102             [ $ERROR_HTML ],
103             ));
104             }
105             }
106              
107 10         31 return $context;
108             }
109              
110             sub _process_content {
111 10     10   4924 my ($app, $context) = @_;
112              
113 10         31 my $res = $context->get('res');
114 10         58 my $body = $res->{body}->[0];
115 10         39 my $obj = $app->loaded_plugins->fetch('Nephia::Plugin::CSRFDefender');
116              
117 10 50       183 my $form_regexp = $obj->{post_only} ? qr{}is : qr{}is;
118            
119 10 100       65 if (defined $body) {
120 7         90 $body =~ s!($form_regexp)!qq{$1\n}!ge;
  2         13  
121             }
122              
123 10         28 $res->{body}->[0] = $body;
124 10         37 $context->set('res' => $res);
125              
126 10         77 return $context;
127             }
128              
129             1;
130              
131             __END__