Passed
Push — master ( 0f8d0a...9774d5 )
by Alexander
02:58
created

Csrf::withName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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