package HTML::FormFu::Element::RequestToken;
use strict;
our $VERSION = '2.04'; # VERSION
our $AUTHORITY = 'cpan:NIGELM'; # AUTHORITY
use Moose;
use MooseX::Attribute::Chained;
extends 'HTML::FormFu::Element::Text';
use HTML::FormFu::Util qw( process_attrs );
use Carp qw( croak );
has expiration_time => ( is => 'rw', traits => ['Chained'], default => 3600 );
has session_key => ( is => 'rw', traits => ['Chained'], default => '__token' );
has context => ( is => 'rw', traits => ['Chained'], default => 'context' );
has limit => ( is => 'rw', traits => ['Chained'], default => 20 );
has message => ( is => 'rw', traits => ['Chained'], default => 'Form submission failed. Please try again.' );
after BUILD => sub {
my $self = shift;
$self->name('_token');
$self->constraints( [qw(RequestToken Required)] );
$self->field_type('hidden');
};
sub process_value {
my ( $self, $value ) = @_;
return $self->verify_token($value)
? $value
: $self->value( $self->get_token )->value;
}
sub verify_token {
my ( $self, $token ) = @_;
return unless ($token);
my $form = $self->form;
croak "verify_token() can only be called if form has been submitted"
if !$form->submitted;
my $field_name = $self->name;
my $c = $self->form->stash->{ $self->context };
for ( @{ $c->session->{ $self->session_key } || [] } ) {
return 1 if ( $_->[0] eq $token );
}
return;
}
sub expire_token {
my ($self) = @_;
my $c = $self->form->stash->{ $self->context };
my @token;
for ( @{ $c->session->{ $self->session_key } || [] } ) {
push( @token, $_ ) if ( $_->[1] > time );
}
@token = splice( @token, -$self->limit, $self->limit ) if ( @token > $self->limit );
$c->session->{ $self->session_key } = \@token;
}
sub get_token {
my ($self) = @_;
my $token;
my $c = $self->form->stash->{ $self->context };
my @chars = ( 'a' .. 'z', 0 .. 9 );
$token .= $chars[ int( rand() * 36 ) ] for ( 0 .. 15 );
$c->session->{ $self->session_key } ||= [];
push @{ $c->session->{ $self->session_key } }, [ $token, time + $self->expiration_time ];
$self->expire_token;
return $token;
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
HTML::FormFu::Element::RequestToken
=head1 VERSION
version 2.04
=head1 SYNOPSIS
my $e = $form->element( { type => 'Token' } );
my $p = $form->element( { plugin => 'Token' } );
=head1 DESCRIPTION
This field can prevent CSRF attacks. It contains a random token. After
submission the token is checked with the token which is stored in the session
of the current user. See
L for a convenient
way how to use it.
=head1 NAME
HTML::FormFu::Element::RequestToken - Hidden text field which contains a unique
token
=head1 ATTRIBUTES
=head2 context
Value of the stash key for the Catalyst context object (C<< $c >>). Defaults to
C.
=head2 expiration_time
Time to life for a token in seconds. Defaults to C<3600>.
=head2 session_key
Session key which is used to store the tokens. Defaults to C<__token>.
=head2 limit
Limit the number of tokens which are kept in the session. Defaults to 20.
=head2 constraints
Defaults to L and
L.
=head2 message
Set the error message.
=head1 METHODS
=head2 expire_token
This method looks in the session for expired tokens and removes them.
=head2 get_token
Generates a new token and stores it in the stash.
=head2 verify_token
Checks whether a given token is already in the session. Returns C<1> if it
exists, C<0> otherwise.
=head1 SEE ALSO
L, L,
L
L
=head1 AUTHOR
Moritz Onken, C
=head1 LICENSE
This library is free software, you can redistribute it and/or modify it under
the same terms as Perl itself.
=head1 AUTHORS
=over 4
=item *
Carl Franks
=item *
Nigel Metheringham
=item *
Dean Hamstead
=back
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 2007-2018 by Carl Franks / Nigel Metheringham / Dean Hamstead.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut