Bug 36148: Add explanatory notes
[koha.git] / Koha / Middleware / CSRF.pm
1 package Koha::Middleware::CSRF;
2
3 # This file is part of Koha.
4 #
5 # Koha is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # Koha is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18 use Modern::Perl;
19
20 use parent qw(Plack::Middleware);
21 use Plack::Response;
22
23 sub call {
24     my ( $self, $env ) = @_;
25     my $req = Plack::Request->new($env);
26
27     my %stateless_methods = (
28         GET     => 1,
29         HEAD    => 1,
30         OPTIONS => 1,
31         TRACE   => 1,
32     );
33
34     my %stateful_methods = (
35         POST   => 1,
36         PUT    => 1,
37         DELETE => 1,
38         PATCH  => 1,
39     );
40
41     my $original_op    = $req->param('op');
42     my $request_method = $req->method // q{};
43     my ($error);
44     if ( $stateless_methods{$request_method} && defined $original_op && $original_op =~ m{^cud-} ) {
45         $error = sprintf "Programming error - op '%s' must not start with 'cud-' for %s", $original_op,
46             $request_method;
47     } elsif ( $stateful_methods{$request_method} ) {
48
49         # Get the CSRF token from the param list or the header
50         my $csrf_token = $req->param('csrf_token') || $req->header('HTTP_CSRF_TOKEN');
51
52         if ( defined $req->param('op') && $original_op !~ m{^cud-} ) {
53             $error = sprintf "Programming error - op '%s' must start with 'cud-' for %s", $original_op,
54                 $request_method;
55         } elsif ( !$csrf_token ) {
56             $error = sprintf "Programming error - No CSRF token passed for %s", $request_method;
57         } else {
58             unless (
59                 Koha::Token->new->check_csrf(
60                     {
61                         session_id => scalar $req->cookies->{CGISESSID},
62                         token      => $csrf_token,
63                     }
64                 )
65                 )
66             {
67                 $error = "wrong_csrf_token";
68             }
69         }
70     }
71
72     #NOTE: It is essential to check for this environmental variable.
73     #NOTE: If we don't check for it, then we'll also throw an error for the "subrequest" that ErrorDocument uses to
74     #fetch the error page document. Then we'll wind up with a very ugly error page and not our pretty one.
75     if ( $error && !$env->{'plack.middleware.Koha.CSRF'} ) {
76
77         #NOTE: Other Middleware will take care of logging to correct place, as Koha::Logger doesn't know where to go here
78         warn $error;
79         $env->{'plack.middleware.Koha.CSRF'} = "BAD_CSRF";
80         my $res = Plack::Response->new( 403, [ 'Content-Type' => 'text/plain' ], ["Bad CSRF"] );
81         return $res->finalize;
82     }
83
84     return $self->app->($env);
85 }
86
87 1;