Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow coderefs in token_per_request #16

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
31 changes: 26 additions & 5 deletions lib/Plack/Middleware/XSRFBlock.pm
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,12 @@ sub prepare_app {
$self->cookie_options( $self->cookie_options || {} );

# default to one token per session, not one per request
$self->token_per_request( $self->token_per_request || 0 );
my $token_per_request = $self->token_per_request ? 1 : 0;
$self->token_per_request(
ref $self->token_per_request eq 'CODE'
? $self->token_per_request
: sub { $token_per_request }
);

# default to a cookie life of three hours
$self->cookie_expiry_seconds( $self->cookie_expiry_seconds || (3 * 60 * 60) );
Expand Down Expand Up @@ -123,9 +128,9 @@ sub call {
return Plack::Util::response_cb($self->app->($env), sub {
my $res = shift;

# if we asked for token_per_request then we *always* create a new token
# Determine whether to create a new token, based on the request
$cookie_value = $self->_token_generator->()
if $self->token_per_request;
if $self->token_per_request->( $self, $request, $env );

# make it easier to work with the headers
my $headers = Plack::Util::headers($res->[1]);
Expand Down Expand Up @@ -388,11 +393,27 @@ and C<Secure> on the cookie to force the cookie to only be sent on SSL requests.

=item token_per_request (default: 0)

If this is true a new token is assigned for each request made.
If this is true a new token is assigned for each request made (but see below).

This may make your application more secure, or less susceptible to
This may make your application more secure, but more susceptible to
double-submit issues.

If this is a coderef, the coderef will be evaluated with the following arguments:

=over

=item * The middleware object itself,

=item * The request,

=item * The environment

=back

If the result of the evaluation is a true value, a new token will be assigned.
This allows fine-grained control, for example to avoid assigning new tokens when
incidental requests are made (e.g. on-page ajax requests).

=item meta_tag (default: undef)

If this is set, use the value as the name of the meta tag to add to the head
Expand Down
55 changes: 53 additions & 2 deletions t/03.token_per_request.t
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,65 @@ for my $appname (
};
}

# test buffered and non-buffered apps for token_per_request_sub behaviour
for my $appname (
'psgix.input.non-buffered.token_per_request_sub',
'psgix.input.buffered.token_per_request_sub',
) {
subtest $appname => sub {
my $ua = LWP::UserAgent->new;
$ua->cookie_jar( HTTP::Cookies->new );

test_psgi ua => $ua, app => $app{$appname}, client => sub {
my $cb = shift;
my ($res, $h_cookie, $jar, $token);

my %token = %{ _two_requests($cb, $ua) };

is(
$token{1},
$token{2},
'cookie tokens are the same for two requests for /form/html using token_per_request_sub'
);
};
};
}

for my $appname (
'psgix.input.non-buffered.token_per_request_sub',
'psgix.input.buffered.token_per_request_sub',
) {
subtest "$appname-post" => sub {
my $ua = LWP::UserAgent->new;
$ua->cookie_jar( HTTP::Cookies->new );

test_psgi ua => $ua, app => $app{$appname}, client => sub {
my $cb = shift;
my ($res, $h_cookie, $jar, $token);

my %token = %{ _two_requests($cb, $ua, '/form/xhtml') };

isnt(
$token{1},
$token{2},
'cookie tokens are different for two requests for /form/xhtml using token_per_request_sub'
);
};
};
}


sub _two_requests {
my ($cb, $ua) = @_;
my ($cb, $ua, $url) = @_;

$url ||= "/form/html";

my $jar = $ua->cookie_jar;

my %token;
# making two requests should result in different tokens
for (1..2) {
my $res = $cb->(GET "/form/html");
my $res = $cb->(GET $url);
is (
$res->code,
HTTP_OK,
Expand All @@ -92,4 +142,5 @@ sub _two_requests {
}



done_testing;
38 changes: 38 additions & 0 deletions t/lib/Test/XSRFBlock/App.pm
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,44 @@ sub setup_test_apps {
$mapped;
};

# create a new token only if the request is to a path which contains the string xhtml
$app{'psgix.input.non-buffered.token_per_request_sub'} = builder {
if ($ENV{PLACK_DEBUG}) {
use Log::Dispatch;
my $logger = Log::Dispatch->new(
outputs => [
[
'Screen',
min_level => 'debug',
stderr => 1,
newline => 1
]
],
);
enable "LogDispatch", logger => $logger;
}
enable 'XSRFBlock',
token_per_request => sub { $_[1]->path =~ /xhtml/i };
$mapped;
};

# psgix.input.buffered
# create a new token only if the request is to a path which contains the string xhtml
$app{'psgix.input.buffered.token_per_request_sub'} = builder {
enable sub {
my $app = shift;
sub {
my $env = shift;
my $req = Plack::Request->new($env);
my $content = $req->content; # <<< force psgix.input.buffered true.
$app->($env);
};
};
enable 'XSRFBlock',
token_per_request => sub { $_[1]->path =~ /xhtml/i };
$mapped;
};

$app{'psgix.input.non-buffered.token_per_session'} = builder {
if ($ENV{PLACK_DEBUG}) {
use Log::Dispatch;
Expand Down