Completed
Push — master ( e4c888...b002ee )
by Alexander
10:41 queued 05:38
created

Csrf::setName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 0
cts 3
cp 0
crap 2
rs 10
1
<?php
2
3
namespace Yiisoft\Yii\Web\Middleware;
4
5
use Psr\Http\Message\ResponseFactoryInterface;
6
use Psr\Http\Message\ResponseInterface;
7
use Psr\Http\Message\ServerRequestInterface;
8
use Psr\Http\Server\MiddlewareInterface;
9
use Psr\Http\Server\RequestHandlerInterface;
10
use Yiisoft\Router\Method;
11
use Yiisoft\Security\Random;
12
use Yiisoft\Security\TokenMasker;
13
use Yiisoft\Yii\Web\Session\SessionInterface;
14
15
final class Csrf implements MiddlewareInterface
16
{
17
    private const NAME = '_csrf';
18
    public const HEADER_NAME = 'X-CSRF-Token';
19
    public const REQUEST_NAME = 'csrf_token';
20
21
    private $name = self::NAME;
22
    private $requestName = self::REQUEST_NAME;
23
    private $responseFactory;
24
    private $session;
25
26
    public function __construct(ResponseFactoryInterface $responseFactory, SessionInterface $session)
27
    {
28
        $this->responseFactory = $responseFactory;
29
        $this->session = $session;
30
    }
31
32
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
33
    {
34
        $token = $this->getToken();
35
36
        if (!$this->validateCsrfToken($request, $token)) {
37
            $this->session->remove($this->name);
38
39
            $response = $this->responseFactory->createResponse(400);
40
            $response->getBody()->write('Unable to verify your data submission.');
41
            return $response;
42
        }
43
44
        $request = $request->withAttribute($this->requestName, TokenMasker::mask($token));
45
46
        return $handler->handle($request);
47
    }
48
49
    public function setName(string $name): void
50
    {
51
        $this->name = $name;
52
    }
53
54
    public function setRequestName(string $name): void
55
    {
56
        $this->requestName = $name;
57
    }
58
59
    private function getToken(): ?string
60
    {
61
        $token = $this->session->get($this->name);
62
        if (empty($token)) {
63
            $token = Random::string();
64
            $this->session->set($this->name, $token);
65
        }
66
67
        return $token;
68
    }
69
70
    private function validateCsrfToken(ServerRequestInterface $request, ?string $trueToken): bool
71
    {
72
        $method = $request->getMethod();
73
74
        if (\in_array($method, [Method::GET, Method::HEAD, Method::OPTIONS], true)) {
75
            return true;
76
        }
77
78
        $unmaskedToken = $this->getTokenFromRequest($request);
79
80
        return !empty($unmaskedToken) && hash_equals($unmaskedToken, $trueToken);
81
    }
82
83
    private function getTokenFromRequest(ServerRequestInterface $request): ?string
84
    {
85
        $parsedBody = $request->getParsedBody();
86
87
        $token = $parsedBody[$this->name] ?? null;
88
        if (empty($token)) {
89
            $headers = $request->getHeader(self::HEADER_NAME);
90
            $token = \reset($headers);
91
        }
92
93
        return TokenMasker::unmask($token);
94
    }
95
}
96