File Coverage

blib/lib/HTML/FormFu/Element/RequestToken.pm
Criterion Covered Total %
statement 45 45 100.0
branch 14 16 87.5
condition 2 2 100.0
subroutine 9 9 100.0
pod 3 4 75.0
total 73 76 96.0


line stmt bran cond sub pod time code
1             package HTML::FormFu::Element::RequestToken;
2              
3 2     2   13287 use strict;
  2         5  
  2         80  
4              
5             our $VERSION = '2.02'; # VERSION
6              
7 2     2   11 use Moose;
  2         4  
  2         13  
8 2     2   12681 use MooseX::Attribute::FormFuChained;
  2         4  
  2         61  
9              
10             extends 'HTML::FormFu::Element::Text';
11              
12 2     2   10 use HTML::FormFu::Util qw( process_attrs );
  2         5  
  2         101  
13 2     2   12 use Carp qw( croak );
  2         8  
  2         1061  
14              
15             has expiration_time => ( is => 'rw', traits => ['FormFuChained'], default => 3600 );
16             has session_key => ( is => 'rw', traits => ['FormFuChained'], default => '__token' );
17             has context => ( is => 'rw', traits => ['FormFuChained'], default => 'context' );
18             has limit => ( is => 'rw', traits => ['FormFuChained'], default => 20 );
19             has message => ( is => 'rw', traits => ['FormFuChained'], default => 'Form submission failed. Please try again.' );
20              
21             after BUILD => sub {
22             my $self = shift;
23             $self->name('_token');
24             $self->constraints([qw(RequestToken Required)]);
25             $self->field_type('hidden');
26             };
27              
28             sub process_value {
29 26     26 0 151026 my ($self, $value) = @_;
30              
31 26 100       100 return $self->verify_token($value) ? $value
32             : $self->value($self->get_token)->value;
33             }
34              
35             sub verify_token {
36 34     34 1 144 my ($self, $token) = @_;
37              
38 34 100       182 return unless($token);
39              
40 10         29 my $form = $self->form;
41              
42 10 50       276 croak "verify_token() can only be called if form has been submitted"
43             if !$form->submitted;
44              
45 10         89 my $field_name = $self->name;
46              
47 10         95 my $c = $self->form->stash->{ $self->context };
48              
49 10 50       24 for ( @{ $c->session->{ $self->session_key } || [] } ) {
  10         45  
50 48 100       133 return 1 if ( $_->[0] eq $token );
51             }
52              
53 3         14 return;
54             }
55              
56             sub expire_token {
57 25     25 1 65 my ($self) = @_;
58              
59 25         88 my $c = $self->form->stash->{ $self->context };
60              
61 25         63 my @token;
62 25 100       54 for ( @{ $c->session->{ $self->session_key } || [] } ) {
  25         96  
63 253 100       664 push( @token, $_ ) if ( $_->[1] > time );
64             }
65              
66 25 100       799 @token = splice(@token, -$self->limit, $self->limit) if(@token > $self->limit);
67              
68 25         105 $c->session->{ $self->session_key } = \@token;
69             }
70              
71             sub get_token {
72 25     25 1 70 my ($self) = @_;
73              
74 25         52 my $token;
75 25         84 my $c = $self->form->stash->{ $self->context };
76 25         207 my @chars = ( 'a' .. 'z', 0 .. 9 );
77              
78 25         384 $token .= $chars[ int( rand() * 36 ) ] for ( 0 .. 15 );
79              
80 25   100     162 $c->session->{ $self->session_key } ||= [];
81              
82 25         59 push @{ $c->session->{ $self->session_key } },
  25         82  
83             [ $token, time + $self->expiration_time ];
84              
85 25         117 $self->expire_token;
86              
87 25         188 return $token;
88             }
89              
90             1;
91              
92             __END__
93              
94             =head1 NAME
95              
96             HTML::FormFu::Element::RequestToken - Hidden text field which contains a unique
97             token
98              
99             =head1 VERSION
100              
101             version 2.02
102              
103             =head1 SYNOPSIS
104              
105             my $e = $form->element( { type => 'Token' } );
106              
107             my $p = $form->element( { plugin => 'Token' } );
108              
109             =head1 DESCRIPTION
110              
111             This field can prevent CSRF attacks. It contains a random token. After
112             submission the token is checked with the token which is stored in the session
113             of the current user.
114             See L<Catalyst::Controller::HTML::FormFu/"request_token_enable"> for a
115             convenient way how to use it.
116              
117             =head1 ATTRIBUTES
118              
119             =head2 context
120              
121             Value of the stash key for the Catalyst context object (C<< $c >>).
122             Defaults to C<context>.
123              
124             =head2 expiration_time
125              
126             Time to life for a token in seconds. Defaults to C<3600>.
127              
128             =head2 session_key
129              
130             Session key which is used to store the tokens. Defaults to C<__token>.
131              
132             =head2 limit
133              
134             Limit the number of tokens which are kept in the session. Defaults to 20.
135              
136             =head2 constraints
137              
138             Defaults to L<HTML::FormFu::Constraint::RequestToken> and L<HTML::FormFu::Constraint::Required>.
139              
140             =head2 message
141              
142             Set the error message.
143              
144             =head1 METHODS
145              
146             =head2 expire_token
147              
148             This method looks in the session for expired tokens and removes them.
149              
150             =head2 get_token
151              
152             Generates a new token and stores it in the stash.
153              
154             =head2 verify_token
155              
156             Checks whether a given token is already in the session. Returns C<1> if it exists, C<0> otherwise.
157              
158             =head1 SEE ALSO
159              
160             L<Catalyst::Controller::HTML::FormFu>,
161             L<HTML::FormFu::Plugin::RequestToken>,
162             L<HTML::FormFu::Constraint::RequestToken>
163              
164             L<HTML::FormFu>
165              
166             =head1 AUTHOR
167              
168             Moritz Onken, C<onken@houseofdesign.de>
169              
170             =head1 LICENSE
171              
172             This library is free software, you can redistribute it and/or modify it under
173             the same terms as Perl itself.