Completed
Push — master ( 04ca9b...962d73 )
by Artem
02:56
created

CSRF::getIdentity()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
namespace Zakirullin\Middlewares;
5
6
use Psr\Http\Server\MiddlewareInterface;
7
use Psr\Http\Server\RequestHandlerInterface;
8
use Psr\Http\Message\ServerRequestInterface;
9
use Psr\Http\Message\ResponseInterface;
10
use Middlewares\Utils\Factory;
11
12
class CSRF implements MiddlewareInterface
13
{
14
    /**
15
     * @var string $identity
16
     */
17
    protected $identity;
18
19
    /**
20
     * @var string
21
     */
22
    protected $secret;
23
24
    /**
25
     * @var string
26
     */
27
    protected $attribute;
28
29
    /**
30
     * @var int
31
     */
32
    protected $ttl;
33
34
    /**
35
     * @var string
36
     */
37
    protected $algorithm;
38
39
    protected const READ_METHODS = ['HEAD', 'GET', 'OPTIONS'];
40
    protected const STATUS_ON_ERROR = 403;
41
    protected const CERTIFICATE_SEPARATOR = ':';
42
    protected const ATTRIBUTE = 'csrf';
43
    protected const TTL = 60 * 20;
44
    protected const ALGORITHM = 'ripemd160';
45
46
    /**
47
     * @param string $identity
48
     * @param string $secret
49
     * @param string $attribute
50
     * @param int $ttl
51
     * @param string $algorithm
52
     */
53
    public function __construct(
54
        string $identity,
55
        string $secret,
56
        string $attribute = self::ATTRIBUTE,
57
        int $ttl = self::TTL,
58
        string $algorithm = self::ALGORITHM
59
    ) {
60
        $this->identity = $identity;
61
        $this->secret = $secret;
62
        $this->attribute = $attribute;
63
        $this->ttl = $ttl;
64
        $this->algorithm = $algorithm;
65
    }
66
67
    /**
68
     * @param ServerRequestInterface $request
69
     * @param RequestHandlerInterface $handler
70
     * @return ResponseInterface
71
     */
72
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
73
    {
74
        $isWriteMethod = !in_array($request->getMethod(), static::READ_METHODS);
75
        if ($isWriteMethod) {
76
            if (!$this->verify($request)) {
77
                $response = Factory::createResponse(static::STATUS_ON_ERROR);
78
                $response->getBody()->write('Invalid or missing CSRF token!');
79
80
                return $response;
81
            }
82
        }
83
84
        $request = $this->add($request);
85
86
        return $handler->handle($request);
87
    }
88
89
    /**
90
     * @param ServerRequestInterface $request
91
     * @return ServerRequestInterface $request
92
     */
93
    protected function add(ServerRequestInterface $request): ServerRequestInterface
94
    {
95
        $expireAt = time() + $this->ttl;
96
        $certificate = $this->createCertificate($this->identity, $expireAt);
97
        $signature = $this->signCertificate($certificate);
98
        $signatureWithExpiration = implode(static::CERTIFICATE_SEPARATOR, [$expireAt, $signature]);
99
100
        return $request->withAttribute($this->attribute, $signatureWithExpiration);
101
    }
102
103
    /**
104
     * @param ServerRequestInterface $request
105
     * @return bool
106
     */
107
    protected function verify(ServerRequestInterface $request): bool
108
    {
109
        $token = trim($request->getParsedBody()[$this->attribute] ?? '');
110
        $parts = explode(static::CERTIFICATE_SEPARATOR, $token);
111
        if (count($parts) > 1) {
112
            list($expireAt, $signature) = explode(static::CERTIFICATE_SEPARATOR, $token);
113
            $certificate = $this->createCertificate($this->identity, (int)$expireAt);
114
            $actualSignature = $this->signCertificate($certificate);
115
            $isSignatureValid = hash_equals($actualSignature, $signature);
116
            $isNotExpired = $expireAt > time();
117
            if ($isSignatureValid && $isNotExpired) {
118
                return true;
119
            }
120
        }
121
122
        return false;
123
    }
124
125
    /**
126
     * @param string $identity
127
     * @param int $expireAt
128
     * @return string
129
     */
130
    protected function createCertificate(string $identity, int $expireAt): string
131
    {
132
        return implode(static::CERTIFICATE_SEPARATOR, [$identity, $expireAt]);
133
    }
134
135
    /**
136
     * @param string $certificate
137
     * @return string
138
     */
139
    protected function signCertificate(string $certificate)
140
    {
141
        return hash_hmac($this->algorithm, $certificate, $this->secret);
142
    }
143
}