Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

added tests

  • Loading branch information...
commit 71c440ffacddf6965e55a2871166fffe2dfe1277 1 parent 9be4d5f
Viktor Turskyi authored
13 Changes
... ... @@ -0,0 +1,13 @@
  1 +Revision history for Mojolicious-Plugin-CSRFProtect
  2 +
  3 +0.01
  4 + First version, released on an unsuspecting world.
  5 +
  6 +0.02
  7 + Better debug messages
  8 +
  9 +0.03
  10 + "is_valid_csrftoken" now works with token from route placeholder
  11 +
  12 +0.04
  13 + Updated documentation and added tests
6 MANIFEST
... ... @@ -0,0 +1,6 @@
  1 +Changes
  2 +MANIFEST
  3 +Makefile.PL
  4 +README
  5 +lib/Mojolicious/Plugin/CSRFProtect.pm
  6 +t/basic.t
13 Makefile.PL
@@ -6,9 +6,12 @@ use warnings;
6 6 use ExtUtils::MakeMaker;
7 7
8 8 WriteMakefile(
9   - NAME => 'Mojolicious::Plugin::CSRFProtect',
10   - VERSION_FROM => 'lib/Mojolicious/Plugin/CSRFProtect.pm',
11   - AUTHOR => 'A Good Programmer <nospam@cpan.org>',
12   - PREREQ_PM => {'Mojolicious' => '1.90'},
13   - test => {TESTS => 't/*.t'}
  9 + NAME => 'Mojolicious::Plugin::CSRFProtect',
  10 + VERSION_FROM => 'lib/Mojolicious/Plugin/CSRFProtect.pm',
  11 + ABSTRACT_FROM => 'lib/Mojolicious/Plugin/CSRFProtect.pm',
  12 + AUTHOR => 'Viktor Turskyi <koorchik@cpan.org>',
  13 + PREREQ_PM => { 'Mojolicious' => '1.90' },
  14 + dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', },
  15 + clean => { FILES => 'Mojolicious-Plugin-CSRFProtect-*' },
  16 + test => { TESTS => 't/*.t' },
14 17 );
40 lib/Mojolicious/Plugin/CSRFProtect.pm
@@ -7,7 +7,7 @@ use Mojo::Base 'Mojolicious::Plugin';
7 7 use Mojo::Util qw/md5_sum/;
8 8 use Mojo::ByteStream qw/b/;
9 9
10   -our $VERSION = '0.03';
  10 +our $VERSION = '0.04';
11 11
12 12 sub register {
13 13 my ( $self, $app ) = @_;
@@ -51,21 +51,19 @@ sub register {
51 51 $app->hook(
52 52 after_static_dispatch => sub {
53 53 my ($c) = @_;
  54 +
54 55 my $request_token = $c->req->param('csrftoken');
55 56 my $is_ajax = ( $c->req->headers->header('X-Requested-With') || '' ) eq 'XMLHttpRequest';
  57 +
56 58 if ( ( $is_ajax || $c->req->method ne 'GET' ) && !$self->_is_valid_csrftoken($c) ) {
57   - # Path
58   - my $req = $c->req;
59   - my $path = $c->stash->{path};
60   -
61   - # Log
  59 + my $path = $c->tx->req->url->to_abs->to_string;
62 60 $c->app->log->debug("CSRFProtect: Wrong CSRF protection token for [$path]!");
63   -
  61 +
64 62 $c->render(
65 63 status => 403,
66 64 text => "Forbidden!",
67 65 );
68   -
  66 +
69 67 return;
70 68 }
71 69
@@ -76,9 +74,9 @@ sub register {
76 74
77 75 sub _is_valid_csrftoken {
78 76 my ( $self, $c ) = @_;
  77 +
79 78 my $valid_token = $c->session('csrftoken');
80 79 my $form_token = $c->req->headers->header('X-CSRF-Token') || $c->param('csrftoken');
81   -
82 80 unless ( $valid_token && $form_token && $form_token eq $valid_token ) {
83 81 return 0;
84 82 }
@@ -88,9 +86,11 @@ sub _is_valid_csrftoken {
88 86
89 87 sub _csrftoken {
90 88 my ( $self, $c ) = @_;
  89 +
91 90 return $c->session('csrftoken') if $c->session('csrftoken');
92 91
93 92 my $token = md5_sum( md5_sum( time() . {} . rand() . $$ ) );
  93 +
94 94 $c->session( 'csrftoken' => $token );
95 95 return $token;
96 96 }
@@ -128,14 +128,14 @@ Mojolicious::Plugin::CSRFProtect - Mojolicious Plugin
128 128
129 129 L<Mojolicious::Plugin::CSRFProtect> is a L<Mojolicious> plugin fully protects you from CSRF attacks.
130 130
131   -It does next thing:
  131 +It does next things:
132 132
133   -1. Adds hidden input (csrftoken) with CSRF protection token to every form
134   -(works only if you use C<form_for> helper from Mojolicious::Plugin::TagHelpers)
  133 +1. Adds a hidden input (with name 'csrftoken') with CSRF protection token to every form
  134 +(works only if you use C<form_for> helper from Mojolicious::Plugin::TagHelpers.)
135 135
136   -2. Adds header "X-CSRF-Token" with CSRF token to every AJAX request (works with JQuery only)
  136 +2. Adds the header "X-CSRF-Token" with CSRF token to every AJAX request (works with JQuery only)
137 137
138   -3. Rejects all non GET request without correct CSRF protection token.
  138 +3. Rejects all non GET requests without the correct CSRF protection token.
139 139
140 140
141 141 If you want protect your GET requests then you can do it manually
@@ -168,21 +168,19 @@ In controller: $self->is_valid_csrftoken()
168 168
169 169 =head2 C<is_valid_csrftoken>
170 170
171   - With this helper you can check $csrftoken manually.
  171 + With this helper you can check $csrftoken manually. It will take $csrftoken from $c->param('csrftoken');
172 172
173   - $self->is_valid_csrftoken($csrftoken) will return 1 or 0
  173 + $self->is_valid_csrftoken() will return 1 or 0
174 174
175 175 =head1 SEE ALSO
176 176
177 177 =over 4
178 178
179   -=item L<Mojolicious::Plugin::CSRFDefender>
180   -
181   -=item L<Mojolicious>
  179 +=item L<Mojolicious::Plugin::CSRFDefender>
182 180
183   -=item L<Mojolicious::Guides>
  181 +This plugin followes the same aproach but it works in different manner.
184 182
185   -=item L<http://mojolicio.us>
  183 +It will parse your response body searching for '<form>' tag and then will insert CSRF token there.
186 184
187 185 =back
188 186
92 t/basic.t
... ... @@ -1,23 +1,87 @@
1 1 #!/usr/bin/env perl
2 2 use Mojo::Base -strict;
3   -use lib '../lib';
4 3 use Mojolicious::Lite;
  4 +use Test::Mojo;
  5 +use Test::More;
  6 +use lib 'lib';
5 7 plugin 'CSRFProtect';
6 8
7   -any '/' => sub {
8   - my $self = shift;
9   - $self->render('index');
  9 +my $t = Test::Mojo->new;
  10 +
  11 +my $csrftoken;
  12 +
  13 +get '/get_without_token' => sub {
  14 + my $self = shift;
  15 + $csrftoken = $self->csrftoken;
  16 + $self->render_text('get_without_token');
  17 +};
  18 +
  19 +get '/protected_document';
  20 +
  21 +get '/get_with_token/:csrftoken' => sub {
  22 + my $self = shift;
  23 +
  24 + if ( $self->is_valid_csrftoken() ) {
  25 + $self->render_text( 'valid csrftokentoken', status => 200 );
  26 + } else {
  27 + $self->render_text( 'Forbidden!', status => 403 );
  28 + }
  29 +
  30 +};
  31 +
  32 +post '/post_with_token' => sub {
  33 + my $self = shift;
  34 + $self->render_text('valid csrftokentoken');
10 35 };
11 36
12 37
13   -app->start;
  38 +# GET /get_without_token. First request will generate new token
  39 +$t->get_ok('/get_without_token')->status_is(200)->content_is('get_without_token');
  40 +$t->get_ok('/get_without_token')->status_is(200)->content_is('get_without_token');
  41 +
  42 +# GET /get_with_token
  43 +$t->get_ok("/get_with_token/$csrftoken")->status_is(200)->content_is('valid csrftokentoken');
  44 +$t->get_ok("/get_with_token/wrongtoken")->status_is(403)->content_is('Forbidden!');
  45 +
  46 +# POST /post_with_token
  47 +$t->post_form_ok( "/post_with_token", { csrftoken => $csrftoken } )->status_is(200)
  48 + ->content_is('valid csrftokentoken');
  49 +$t->post_form_ok( "/post_with_token", { csrftoken => 'wrongtoken' } )->status_is(403)
  50 + ->content_is('Forbidden!');
  51 +
  52 +# Emulate AJAX All
  53 +# AJAX request should be checked (including GET)
  54 +$t->ua->on(
  55 + start => sub {
  56 + my ( $ua, $tx ) = @_;
  57 + $tx->req->headers->header( 'X-Requested-With', 'XMLHttpRequest' );
  58 + } );
  59 +
  60 +$t->get_ok('/get_without_token')->status_is(403)->content_is('Forbidden!');
  61 +$t->post_form_ok( "/post_with_token", { csrftoken => $csrftoken } )->status_is(200)
  62 + ->content_is('valid csrftokentoken');
  63 +$t->post_form_ok( "/post_with_token", { csrftoken => 'wrongtoken' } )->status_is(403)
  64 + ->content_is('Forbidden!');
  65 +
  66 +# Add header with csrftoken
  67 +$t->ua->on(
  68 + start => sub {
  69 + my ( $ua, $tx ) = @_;
  70 + $tx->req->headers->header( 'X-CSRF-Token', $csrftoken );
  71 + } );
  72 +
  73 +# All request should pass
  74 +$t->get_ok('/get_without_token')->status_is(200)->content_is('get_without_token');
  75 +$t->get_ok("/get_with_token/notoken")->status_is(200)->content_is('valid csrftokentoken');
  76 +$t->post_ok("/post_with_token")->status_is(200)->content_is('valid csrftokentoken');
  77 +
  78 +# Check helpers
  79 +my $javascript = qq~<meta name="csrftoken" content="$csrftoken"/><script type="text/javascript"> \$(document).ajaxSend(function(e, xhr, options) { var token = \$("meta[name='csrftoken']").attr("content"); xhr.setRequestHeader("X-CSRF-Token", token); });</script>\n~;
  80 +$t->get_ok('/protected_document')->status_is(200)->content_is("$javascript");
  81 +
  82 +done_testing;
  83 +
  84 +__DATA__;
14 85
15   -__DATA__
16   -@@ index.html.ep
17   - <%= javascript '/js/jquery.js' %>
18   - <%= form_for '/' => (method => 'post') => begin %>
19   - <%= text_field 'first_name' %>
20   - <%= submit_button %>
21   - <% end %>
22   -
23   - <%= jquery_ajax_csrf_protection %>
  86 +@@ protected_document.html.ep
  87 +<%= jquery_ajax_csrf_protection %>

0 comments on commit 71c440f

Please sign in to comment.
Something went wrong with that request. Please try again.