Passed
Push — master ( 83c445...092c55 )
by Kirill
05:01
created

CsrfFirewall::fetchToken()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 6
c 1
b 0
f 0
dl 0
loc 12
rs 9.6111
cc 5
nc 3
nop 1
1
<?php
2
3
/**
4
 * Spiral Framework.
5
 *
6
 * @license   MIT
7
 * @author    Anton Titov (Wolfy-J)
8
 */
9
10
declare(strict_types=1);
11
12
namespace Spiral\Csrf\Middleware;
13
14
use Psr\Http\Message\ResponseFactoryInterface;
15
use Psr\Http\Message\ResponseInterface as Response;
16
use Psr\Http\Message\ServerRequestInterface as Request;
17
use Psr\Http\Server\MiddlewareInterface;
18
use Psr\Http\Server\RequestHandlerInterface;
19
20
/**
21
 * Provides generic CSRF protection using cookie as token storage. Set "csrfToken" attribute to
22
 * request.
23
 */
24
final class CsrfFirewall implements MiddlewareInterface
25
{
26
    /**
27
     * Header to check for token instead of POST/GET data.
28
     */
29
    public const HEADER = 'X-CSRF-Token';
30
31
    /**
32
     * Parameter name used to represent client token in POST data.
33
     */
34
    public const PARAMETER = 'csrf-token';
35
36
    /**
37
     * Methods to be allowed to be passed with proper token.
38
     */
39
    public const ALLOW_METHODS = ['GET', 'HEAD', 'OPTIONS'];
40
41
    /** @var ResponseFactoryInterface */
42
    private $responseFactory;
43
44
    /** @var array */
45
    private $allowMethods;
46
47
    /**
48
     * @param ResponseFactoryInterface $responseFactory
49
     * @param array                    $allowMethods
50
     */
51
    public function __construct(ResponseFactoryInterface $responseFactory, array $allowMethods = self::ALLOW_METHODS)
52
    {
53
        $this->responseFactory = $responseFactory;
54
        $this->allowMethods = $allowMethods;
55
    }
56
57
    /**
58
     * {@inheritdoc}
59
     */
60
    public function process(Request $request, RequestHandlerInterface $handler): Response
61
    {
62
        $token = $request->getAttribute(CsrfMiddleware::ATTRIBUTE);
63
64
        if (empty($token)) {
65
            throw new \LogicException('Unable to apply CSRF firewall, attribute is missing');
66
        }
67
68
        if ($this->isRequired($request) && !hash_equals($token, $this->fetchToken($request))) {
69
            return $this->responseFactory->createResponse(412, 'Bad CSRF Token');
70
        }
71
72
        return $handler->handle($request);
73
    }
74
75
    /**
76
     * Check if middleware should validate csrf token.
77
     *
78
     * @param Request $request
79
     * @return bool
80
     */
81
    protected function isRequired(Request $request): bool
82
    {
83
        return !in_array($request->getMethod(), $this->allowMethods, true);
84
    }
85
86
    /**
87
     * Fetch token from request.
88
     *
89
     * @param Request $request
90
     * @return string
91
     */
92
    protected function fetchToken(Request $request): string
93
    {
94
        if ($request->hasHeader(self::HEADER)) {
95
            return (string)$request->getHeaderLine(self::HEADER);
96
        }
97
98
        $data = $request->getParsedBody();
99
        if (is_array($data) && isset($data[self::PARAMETER]) && is_string($data[self::PARAMETER])) {
100
            return $data[self::PARAMETER];
101
        }
102
103
        return '';
104
    }
105
}
106