Permalink
Browse files

added tests

  • Loading branch information...
1 parent 9be4d5f commit 71c440ffacddf6965e55a2871166fffe2dfe1277 Viktor Turskyi committed Oct 23, 2011
Showing with 124 additions and 40 deletions.
  1. +13 −0 Changes
  2. +6 −0 MANIFEST
  3. +8 −5 Makefile.PL
  4. +19 −21 lib/Mojolicious/Plugin/CSRFProtect.pm
  5. +78 −14 t/basic.t
View
13 Changes
@@ -0,0 +1,13 @@
+Revision history for Mojolicious-Plugin-CSRFProtect
+
+0.01
+ First version, released on an unsuspecting world.
+
+0.02
+ Better debug messages
+
+0.03
+ "is_valid_csrftoken" now works with token from route placeholder
+
+0.04
+ Updated documentation and added tests
View
@@ -0,0 +1,6 @@
+Changes
+MANIFEST
+Makefile.PL
+README
+lib/Mojolicious/Plugin/CSRFProtect.pm
+t/basic.t
View
@@ -6,9 +6,12 @@ use warnings;
use ExtUtils::MakeMaker;
WriteMakefile(
- NAME => 'Mojolicious::Plugin::CSRFProtect',
- VERSION_FROM => 'lib/Mojolicious/Plugin/CSRFProtect.pm',
- AUTHOR => 'A Good Programmer <nospam@cpan.org>',
- PREREQ_PM => {'Mojolicious' => '1.90'},
- test => {TESTS => 't/*.t'}
+ NAME => 'Mojolicious::Plugin::CSRFProtect',
+ VERSION_FROM => 'lib/Mojolicious/Plugin/CSRFProtect.pm',
+ ABSTRACT_FROM => 'lib/Mojolicious/Plugin/CSRFProtect.pm',
+ AUTHOR => 'Viktor Turskyi <koorchik@cpan.org>',
+ PREREQ_PM => { 'Mojolicious' => '1.90' },
+ dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', },
+ clean => { FILES => 'Mojolicious-Plugin-CSRFProtect-*' },
+ test => { TESTS => 't/*.t' },
);
@@ -7,7 +7,7 @@ use Mojo::Base 'Mojolicious::Plugin';
use Mojo::Util qw/md5_sum/;
use Mojo::ByteStream qw/b/;
-our $VERSION = '0.03';
+our $VERSION = '0.04';
sub register {
my ( $self, $app ) = @_;
@@ -51,21 +51,19 @@ sub register {
$app->hook(
after_static_dispatch => sub {
my ($c) = @_;
+
my $request_token = $c->req->param('csrftoken');
my $is_ajax = ( $c->req->headers->header('X-Requested-With') || '' ) eq 'XMLHttpRequest';
+
if ( ( $is_ajax || $c->req->method ne 'GET' ) && !$self->_is_valid_csrftoken($c) ) {
- # Path
- my $req = $c->req;
- my $path = $c->stash->{path};
-
- # Log
+ my $path = $c->tx->req->url->to_abs->to_string;
$c->app->log->debug("CSRFProtect: Wrong CSRF protection token for [$path]!");
-
+
$c->render(
status => 403,
text => "Forbidden!",
);
-
+
return;
}
@@ -76,9 +74,9 @@ sub register {
sub _is_valid_csrftoken {
my ( $self, $c ) = @_;
+
my $valid_token = $c->session('csrftoken');
my $form_token = $c->req->headers->header('X-CSRF-Token') || $c->param('csrftoken');
-
unless ( $valid_token && $form_token && $form_token eq $valid_token ) {
return 0;
}
@@ -88,9 +86,11 @@ sub _is_valid_csrftoken {
sub _csrftoken {
my ( $self, $c ) = @_;
+
return $c->session('csrftoken') if $c->session('csrftoken');
my $token = md5_sum( md5_sum( time() . {} . rand() . $$ ) );
+
$c->session( 'csrftoken' => $token );
return $token;
}
@@ -128,14 +128,14 @@ Mojolicious::Plugin::CSRFProtect - Mojolicious Plugin
L<Mojolicious::Plugin::CSRFProtect> is a L<Mojolicious> plugin fully protects you from CSRF attacks.
-It does next thing:
+It does next things:
-1. Adds hidden input (csrftoken) with CSRF protection token to every form
-(works only if you use C<form_for> helper from Mojolicious::Plugin::TagHelpers)
+1. Adds a hidden input (with name 'csrftoken') with CSRF protection token to every form
+(works only if you use C<form_for> helper from Mojolicious::Plugin::TagHelpers.)
-2. Adds header "X-CSRF-Token" with CSRF token to every AJAX request (works with JQuery only)
+2. Adds the header "X-CSRF-Token" with CSRF token to every AJAX request (works with JQuery only)
-3. Rejects all non GET request without correct CSRF protection token.
+3. Rejects all non GET requests without the correct CSRF protection token.
If you want protect your GET requests then you can do it manually
@@ -168,21 +168,19 @@ In controller: $self->is_valid_csrftoken()
=head2 C<is_valid_csrftoken>
- With this helper you can check $csrftoken manually.
+ With this helper you can check $csrftoken manually. It will take $csrftoken from $c->param('csrftoken');
- $self->is_valid_csrftoken($csrftoken) will return 1 or 0
+ $self->is_valid_csrftoken() will return 1 or 0
=head1 SEE ALSO
=over 4
-=item L<Mojolicious::Plugin::CSRFDefender>
-
-=item L<Mojolicious>
+=item L<Mojolicious::Plugin::CSRFDefender>
-=item L<Mojolicious::Guides>
+This plugin followes the same aproach but it works in different manner.
-=item L<http://mojolicio.us>
+It will parse your response body searching for '<form>' tag and then will insert CSRF token there.
=back
View
@@ -1,23 +1,87 @@
#!/usr/bin/env perl
use Mojo::Base -strict;
-use lib '../lib';
use Mojolicious::Lite;
+use Test::Mojo;
+use Test::More;
+use lib 'lib';
plugin 'CSRFProtect';
-any '/' => sub {
- my $self = shift;
- $self->render('index');
+my $t = Test::Mojo->new;
+
+my $csrftoken;
+
+get '/get_without_token' => sub {
+ my $self = shift;
+ $csrftoken = $self->csrftoken;
+ $self->render_text('get_without_token');
+};
+
+get '/protected_document';
+
+get '/get_with_token/:csrftoken' => sub {
+ my $self = shift;
+
+ if ( $self->is_valid_csrftoken() ) {
+ $self->render_text( 'valid csrftokentoken', status => 200 );
+ } else {
+ $self->render_text( 'Forbidden!', status => 403 );
+ }
+
+};
+
+post '/post_with_token' => sub {
+ my $self = shift;
+ $self->render_text('valid csrftokentoken');
};
-app->start;
+# GET /get_without_token. First request will generate new token
+$t->get_ok('/get_without_token')->status_is(200)->content_is('get_without_token');
+$t->get_ok('/get_without_token')->status_is(200)->content_is('get_without_token');
+
+# GET /get_with_token
+$t->get_ok("/get_with_token/$csrftoken")->status_is(200)->content_is('valid csrftokentoken');
+$t->get_ok("/get_with_token/wrongtoken")->status_is(403)->content_is('Forbidden!');
+
+# POST /post_with_token
+$t->post_form_ok( "/post_with_token", { csrftoken => $csrftoken } )->status_is(200)
+ ->content_is('valid csrftokentoken');
+$t->post_form_ok( "/post_with_token", { csrftoken => 'wrongtoken' } )->status_is(403)
+ ->content_is('Forbidden!');
+
+# Emulate AJAX All
+# AJAX request should be checked (including GET)
+$t->ua->on(
+ start => sub {
+ my ( $ua, $tx ) = @_;
+ $tx->req->headers->header( 'X-Requested-With', 'XMLHttpRequest' );
+ } );
+
+$t->get_ok('/get_without_token')->status_is(403)->content_is('Forbidden!');
+$t->post_form_ok( "/post_with_token", { csrftoken => $csrftoken } )->status_is(200)
+ ->content_is('valid csrftokentoken');
+$t->post_form_ok( "/post_with_token", { csrftoken => 'wrongtoken' } )->status_is(403)
+ ->content_is('Forbidden!');
+
+# Add header with csrftoken
+$t->ua->on(
+ start => sub {
+ my ( $ua, $tx ) = @_;
+ $tx->req->headers->header( 'X-CSRF-Token', $csrftoken );
+ } );
+
+# All request should pass
+$t->get_ok('/get_without_token')->status_is(200)->content_is('get_without_token');
+$t->get_ok("/get_with_token/notoken")->status_is(200)->content_is('valid csrftokentoken');
+$t->post_ok("/post_with_token")->status_is(200)->content_is('valid csrftokentoken');
+
+# Check helpers
+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~;
+$t->get_ok('/protected_document')->status_is(200)->content_is("$javascript");
+
+done_testing;
+
+__DATA__;
-__DATA__
-@@ index.html.ep
- <%= javascript '/js/jquery.js' %>
- <%= form_for '/' => (method => 'post') => begin %>
- <%= text_field 'first_name' %>
- <%= submit_button %>
- <% end %>
-
- <%= jquery_ajax_csrf_protection %>
+@@ protected_document.html.ep
+<%= jquery_ajax_csrf_protection %>

0 comments on commit 71c440f

Please sign in to comment.