Ark

 view release on metacpan or  search on metacpan

lib/Ark/Plugin/CSRFDefender.pm  view on Meta::CPAN

    isa => 'Str',
    lazy => 1,
    default => sub {
        shift->class_config->{error_action} || '';
    }
);

has csrf_defender_filter_form => (
    is      => 'ro',
    isa     => 'Bool',
    lazy    => 1,
    default => sub {
        shift->class_config->{filter_form} || undef;
    },
);

my $uuid = Data::UUID->new;
has csrf_token => (
    is     => 'ro',
    isa    => 'Str',
    lazy   => 1,
    default => sub {
        my $c = shift;

        if (my $token = $c->session->get($c->csrf_defender_session_name)) {
            return $token;
        }
        else {
            my $token = $uuid->create_str;
            $c->session->set($c->csrf_defender_session_name => $token);

            return $token;
        }
    },
    predicate => '_has_csrf_token',
);

sub validate_csrf_token {
    my $c = shift;
    my $req = $c->request;
    if ($c->_is_csrf_validation_needed) {
        my $param_token   = $req->param($c->csrf_defender_param_name);
        my $session_token = $c->csrf_token;
        if (!$param_token || !$session_token || ($param_token ne $session_token)) {
            return (); # bad
        }
    }
    return 1; # good
}

sub forward_csrf_error {
    my $c = shift;

    if ($c->csrf_defender_error_action) {
        $c->res->code($c->csrf_defender_error_code);
        $c->forward($c->csrf_defender_error_action);
    }
    else {
        $c->res->code($c->csrf_defender_error_code);
        $c->res->body($c->csrf_defender_error_output);
        $c->res->header('Content-Type', 'text/html; charset=UTF-8');
    }
}

sub _is_csrf_validation_needed {
    my $c = shift;
    my $method = $c->req->method;
    return () if !$method;

    return
        $method eq 'POST'   ? 1 :
        $method eq 'PUT'    ? 1 :
        $method eq 'DELETE' ? 1 : ();
}

sub html_filter_for_csrf {
    my ($c, $html) = @_;

    my $reg = qr/<form\s*.*?\s*method=['"]?post['"]?\s*.*?>/i;
    $html =~ s!($reg)!$1\n<input type="hidden" name="@{[$c->csrf_defender_param_name]}" value="@{[$c->csrf_token]}" />!isg;

    $html;
}

after finalize_body => sub {
    my $c = shift;

    return if $c->res->binary;
    my $html = $c->res->body or return;
    return unless $c->csrf_defender_filter_form;

    $html = $c->html_filter_for_csrf($html);
    $c->res->body($html);
};

around dispatch => sub {
    my $orig = shift;
    my ($c) = @_;

    # surely asign csrf_token
    $c->csrf_token;
    if (!$c->csrf_defender_validate_only && !$c->validate_csrf_token) {
        $c->forward_csrf_error;
    }
    else {
        $orig->(@_);
    }
};

1;
__END__

=encoding utf-8

=head1 NAME

Ark::Plugin::CSRFDefender - CSRF Defender for Ark

=head1 SYNOPSIS

    use Ark::Plugin::CSRFDefender;



( run in 2.157 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )