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

CsrfMiddleware::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
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\ResponseInterface as Response;
15
use Psr\Http\Message\ServerRequestInterface as Request;
16
use Psr\Http\Server\MiddlewareInterface;
17
use Psr\Http\Server\RequestHandlerInterface;
18
use Spiral\Cookies\Cookie;
19
use Spiral\Csrf\Config\CsrfConfig;
20
21
/**
22
 * Provides generic CSRF protection using cookie as token storage. Set "csrfToken" attribute to
23
 * request.
24
 *
25
 * Do not use middleware without CookieManager at top!
26
 *
27
 * @see https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Double_Submit_Cookie
28
 */
29
final class CsrfMiddleware implements MiddlewareInterface
30
{
31
    public const ATTRIBUTE = 'csrfToken';
32
33
    /** @var CsrfConfig */
34
    protected $config;
35
36
    /**
37
     * @param CsrfConfig $config
38
     */
39
    public function __construct(CsrfConfig $config)
40
    {
41
        $this->config = $config;
42
    }
43
44
    /**
45
     * {@inheritdoc}
46
     */
47
    public function process(Request $request, RequestHandlerInterface $handler): Response
48
    {
49
        if (isset($request->getCookieParams()[$this->config->getCookie()])) {
50
            $token = $request->getCookieParams()[$this->config->getCookie()];
51
        } else {
52
            //Making new token
53
            $token = $this->random($this->config->getTokenLength());
54
55
            //Token cookie!
56
            $cookie = $this->tokenCookie($token);
57
        }
58
59
        //CSRF issues must be handled by Firewall middleware
60
        $response = $handler->handle($request->withAttribute(static::ATTRIBUTE, $token));
61
62
        if (!empty($cookie)) {
63
            return $response->withAddedHeader('Set-Cookie', $cookie);
64
        }
65
66
        return $response;
67
    }
68
69
    /**
70
     * Generate CSRF cookie.
71
     *
72
     * @param string $token
73
     * @return string
74
     */
75
    protected function tokenCookie(string $token): string
76
    {
77
        return Cookie::create(
78
            $this->config->getCookie(),
79
            $token,
80
            $this->config->getCookieLifetime(),
81
            null,
82
            null,
83
            $this->config->isCookieSecure(),
84
            true,
85
            $this->config->getSameSite()
86
        )->createHeader();
87
    }
88
89
    /**
90
     * Create a random string with desired length.
91
     *
92
     * @param int $length String length. 32 symbols by default.
93
     * @return string
94
     */
95
    private function random(int $length = 32): string
96
    {
97
        try {
98
            if (empty($string = random_bytes($length))) {
99
                throw new \RuntimeException('Unable to generate random string');
100
            }
101
        } catch (\Throwable $e) {
102
            throw new \RuntimeException('Unable to generate random string', $e->getCode(), $e);
103
        }
104
105
        return substr(base64_encode($string), 0, $length);
106
    }
107
}
108