Passed
Push — 5.x ( 65a76f...3a1e20 )
by Enjoys
59s queued 13s
created

Csrf   A

Complexity

Total Complexity 8

Size/Duplication

Total Lines 77
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 1 Features 0
Metric Value
wmc 8
eloc 25
c 2
b 1
f 0
dl 0
loc 77
ccs 25
cts 25
cp 1
rs 10

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 19 2
A getCsrfToken() 0 3 1
A getCsrfSecret() 0 9 2
A prepare() 0 11 2
A generateSecret() 0 7 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Enjoys\Forms\Elements;
6
7
use Enjoys\Forms\Exception\CsrfAttackDetected;
8
use Enjoys\Forms\Exception\ExceptionRule;
9
use Enjoys\Forms\Form;
10
use Enjoys\Forms\Rules;
11
use Enjoys\Session\Session;
12
13
/**
14
 * Включает защиту от CSRF.
15
 * Сross Site Request Forgery — «Подделка межсайтовых запросов», также известен как XSRF
16
 */
17
class Csrf extends Hidden
18
{
19
    /**
20
     * @throws ExceptionRule
21
     * @throws \Exception
22
     */
23 81
    public function __construct(private Session $session)
24
    {
25 81
        $csrfSecret = $this->getCsrfSecret();
26 80
        $token = $this->getCsrfToken($csrfSecret);
27
28
29 80
        parent::__construct(Form::_TOKEN_CSRF_, $token);
30
31 80
        $this->addRule(
32
            Rules::CALLBACK,
33
            'CSRF Attack detected',
34
            [
35 80
                function (string $key) {
36 2
                    if (password_verify($key, $this->getRequest()->getPostData(Form::_TOKEN_CSRF_, ''))) {
0 ignored issues
show
Bug introduced by
Enjoys\Forms\Form::_TOKEN_CSRF_ of type string is incompatible with the type Enjoys\T expected by parameter $key of Enjoys\ServerRequestWrapper::getPostData(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

36
                    if (password_verify($key, $this->getRequest()->getPostData(/** @scrutinizer ignore-type */ Form::_TOKEN_CSRF_, ''))) {
Loading history...
Bug introduced by
It seems like $this->getRequest()->get...Form::_TOKEN_CSRF_, '') can also be of type null; however, parameter $hash of password_verify() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

36
                    if (password_verify($key, /** @scrutinizer ignore-type */ $this->getRequest()->getPostData(Form::_TOKEN_CSRF_, ''))) {
Loading history...
37 1
                        return true;
38
                    }
39 1
                    throw new CsrfAttackDetected('CSRF Token is invalid');
40
                },
41
                $csrfSecret
42
            ]
43
        );
44
    }
45
46
    /**
47
     * @return true|void
48
     */
49 77
    public function prepare()
50
    {
51 77
        if (!in_array($this->getForm()->getMethod(), ['POST', 'PUT', 'DELETE', 'PATCH'])) {
52
            //удаляем элемент, если был заранее создан
53
            //$this->getForm()->removeElement($this->getForm()->getElement(\Enjoys\Forms\Form::_TOKEN_CSRF_));
54 12
            $this->getForm()->removeElement($this);
55
56
            //возвращаем true, чтобы не добавлять элемент.
57 12
            return true;
58
        }
59 69
        $this->unsetForm();
60
    }
61
62
    /**
63
     * @throws \Exception
64
     */
65 81
    private function getCsrfSecret(): string
66
    {
67 81
        $secret = (string) $this->session->get('csrf_secret');
68
69 80
        if (empty($secret)) {
70 57
            $secret = $this->generateSecret();
71
        }
72
73 80
        return $secret;
74
    }
75
76
77
78 80
    public function getCsrfToken(string $secret): string
79
    {
80 80
        return password_hash($secret, PASSWORD_DEFAULT);
0 ignored issues
show
Bug Best Practice introduced by
The expression return password_hash($se...ments\PASSWORD_DEFAULT) could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
81
    }
82
83
    /**
84
     * @return string
85
     * @throws \Exception
86
     */
87 57
    private function generateSecret(): string
88
    {
89 57
        $secret = base64_encode(random_bytes(32));
90 57
        $this->session->set([
91
            'csrf_secret' => $secret
92
        ]);
93 57
        return $secret;
94
    }
95
}
96