Completed
Branch 09branch (946dde)
by Anton
05:16
created

CsrfMiddleware   A

Complexity

Total Complexity 6

Size/Duplication

Total Lines 81
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 0
Metric Value
dl 0
loc 81
rs 10
c 0
b 0
f 0
wmc 6
lcom 1
cbo 6

4 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
B __invoke() 0 26 3
A generateToken() 0 4 1
A tokenCookie() 0 12 1
1
<?php
2
/**
3
 * Spiral Framework.
4
 *
5
 * @license   MIT
6
 * @author    Anton Titov (Wolfy-J)
7
 */
8
9
namespace Spiral\Http\Middlewares;
10
11
use Psr\Http\Message\ResponseInterface as Response;
12
use Psr\Http\Message\ServerRequestInterface as Request;
13
use Psr\Http\Message\UriInterface;
14
use Spiral\Http\Configs\HttpConfig;
15
use Spiral\Http\Cookies\Cookie;
16
use Spiral\Http\Cookies\CookieQueue;
17
use Spiral\Http\MiddlewareInterface;
18
use Spiral\Support\Strings;
19
20
/**
21
 * Provides generic CSRF protection using cookie as token storage. Set "csrfToken" attribute to
22
 * request.
23
 *
24
 * Do not use middleware without CookieManager at top!
25
 *
26
 * @see https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Double_Submit_Cookie
27
 */
28
class CsrfMiddleware implements MiddlewareInterface
29
{
30
    /**
31
     * Request attribute value.
32
     */
33
    const ATTRIBUTE = 'csrfToken';
34
35
    /**
36
     * @var HttpConfig
37
     */
38
    protected $httpConfig = null;
39
40
    /**
41
     * @param HttpConfig $httpConfig
42
     */
43
    public function __construct(HttpConfig $httpConfig)
44
    {
45
        $this->httpConfig = $httpConfig;
46
    }
47
48
    /**
49
     * {@inheritdoc}
50
     */
51
    public function __invoke(Request $request, Response $response, callable $next)
52
    {
53
        if (isset($request->getCookieParams()[$this->httpConfig->csrfCookie()])) {
54
            $token = $request->getCookieParams()[$this->httpConfig->csrfCookie()];
55
        } else {
56
            //Making new token
57
            $token = $this->generateToken();
58
59
            //Token cookie!
60
            $cookie = $this->tokenCookie($request->getUri(), $token);
61
62
            if (!empty($queue = $request->getAttribute(CookieQueue::ATTRIBUTE))) {
63
                /** @var CookieQueue $queue */
64
                $queue->schedule($cookie);
65
            } else {
66
                //Fallback, this is less secure but faster way
67
                $response = $response->withAddedHeader('Set-Cookie', (string)$cookie);
68
            }
69
        }
70
71
        //CSRF issues must be handled by Firewall middleware
72
        return $next(
73
            $request->withAttribute(static::ATTRIBUTE, $token),
74
            $response
75
        );
76
    }
77
78
    /**
79
     * Generate CSRF token.
80
     *
81
     * @return string
82
     */
83
    public function generateToken(): string
84
    {
85
        return Strings::random($this->httpConfig->csrfLength());
86
    }
87
88
    /**
89
     * Generate CSRF cookie.
90
     *
91
     * @param UriInterface $uri Incoming uri.
92
     * @param string       $token
93
     *
94
     * @return Cookie
95
     */
96
    protected function tokenCookie(UriInterface $uri, string $token): Cookie
97
    {
98
        return Cookie::create(
99
            $this->httpConfig->csrfCookie(),
100
            $token,
101
            $this->httpConfig->csrfLifetime(),
102
            $this->httpConfig->basePath(),
103
            $this->httpConfig->cookiesDomain($uri),
104
            $this->httpConfig->csrfSecure(),
105
            true
106
        );
107
    }
108
}