Passed
Push — master ( 52cb59...719d42 )
by Vincent
04:52
created

CsrfElement::patch()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
cc 1
nc 1
nop 1
crap 2
1
<?php
2
3
namespace Bdf\Form\Csrf;
4
5
use BadMethodCallException;
6
use Bdf\Form\Child\Http\HttpFieldPath;
7
use Bdf\Form\ElementInterface;
8
use Bdf\Form\Error\FormError;
9
use Bdf\Form\Leaf\LeafRootElement;
10
use Bdf\Form\Leaf\View\SimpleElementView;
11
use Bdf\Form\RootElementInterface;
12
use Bdf\Form\Util\ContainerTrait;
13
use Bdf\Form\View\ElementViewInterface;
14
use Symfony\Component\Security\Csrf\CsrfToken;
15
use Symfony\Component\Security\Csrf\CsrfTokenManager;
16
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
17
18
/**
19
 * Element for add a CSRF token on the form
20
 * The token value is auto-generated using CsrfTokenManagerInterface
21
 *
22
 * This element is always required, and validated, and cannot be `import()'d`
23
 *
24
 * The dependency "symfony/security-csrf" is required to use this element
25
 *
26
 * @see CsrfTokenManager
27
 *
28
 * @implements ElementInterface<CsrfToken>
29
 */
30
final class CsrfElement implements ElementInterface
31
{
32
    use ContainerTrait;
33
34
    /**
35
     * @var string
36
     */
37
    private $tokenId;
38
39
    /**
40
     * @var CsrfValueValidator
41
     */
42
    private $validator;
43
44
    /**
45
     * @var CsrfTokenManagerInterface
46
     */
47
    private $tokenManager;
48
49
    /**
50
     * @var CsrfToken|null
51
     */
52
    private $value = null;
53
54
    /**
55
     * @var FormError
56
     */
57
    private $error;
58
59
    /**
60
     * CsrfElement constructor.
61
     *
62
     * @param string|null $tokenId
63
     * @param CsrfValueValidator|null $validator
64
     * @param CsrfTokenManagerInterface|null $tokenManager
65
     */
66 21
    public function __construct(?string $tokenId = null, ?CsrfValueValidator $validator = null, ?CsrfTokenManagerInterface $tokenManager = null)
67
    {
68 21
        $this->tokenId = $tokenId ?: self::class;
69 21
        $this->validator = $validator ?: new CsrfValueValidator();
70 21
        $this->tokenManager = $tokenManager ?: new CsrfTokenManager();
71
72 21
        $this->error = FormError::null();
73 21
    }
74
75
    /**
76
     * {@inheritdoc}
77
     */
78 12
    public function submit($data): ElementInterface
79
    {
80 12
        $this->value = new CsrfToken($this->tokenId, $data);
81 12
        $this->error = $this->validator->validate($this->value, $this);
82
83 12
        return $this;
84
    }
85
86
    /**
87
     * {@inheritdoc}
88
     */
89
    public function patch($data): ElementInterface
90
    {
91
        // CSRF element must be submitted
92
        return $this->submit($data);
93
    }
94
95
    /**
96
     * {@inheritdoc}
97
     */
98 1
    public function import($entity): ElementInterface
99
    {
100 1
        throw new BadMethodCallException('Cannot set a Csrf token value');
101
    }
102
103
    /**
104
     * {@inheritdoc}
105
     *
106
     * @return CsrfToken
107
     */
108 13
    public function value(): CsrfToken
109
    {
110 13
        if ($this->value) {
111 8
            return $this->value;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->value returns the type Symfony\Component\Security\Csrf\CsrfToken which is incompatible with the return type mandated by Bdf\Form\ElementInterface::value() of Bdf\Form\T|null.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
112
        }
113
114 11
        return $this->value = $this->tokenManager->getToken($this->tokenId);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->value = $t...etToken($this->tokenId) returns the type Symfony\Component\Security\Csrf\CsrfToken which is incompatible with the return type mandated by Bdf\Form\ElementInterface::value() of Bdf\Form\T|null.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
115
    }
116
117
    /**
118
     * {@inheritdoc}
119
     */
120 5
    public function httpValue(): string
121
    {
122 5
        return $this->value()->getValue();
123
    }
124
125
    /**
126
     * {@inheritdoc}
127
     */
128 9
    public function valid(): bool
129
    {
130 9
        return $this->value && $this->error->empty();
131
    }
132
133
    /**
134
     * {@inheritdoc}
135
     */
136 9
    public function error(?HttpFieldPath $field = null): FormError
137
    {
138 9
        return $field ? $this->error->withField($field) : $this->error;
139
    }
140
141
    /**
142
     * {@inheritdoc}
143
     */
144 14
    public function root(): RootElementInterface
145
    {
146 14
        return $this->container() ? $this->container()->parent()->root() : new LeafRootElement($this);
147
    }
148
149
    /**
150
     * {@inheritdoc}
151
     */
152 1
    public function view(?HttpFieldPath $field = null): ElementViewInterface
153
    {
154 1
        return new SimpleElementView(
155 1
            self::class,
156 1
            (string) $field,
157 1
            $this->tokenManager->getToken($this->tokenId), // Always get the real token value
158 1
            $this->error->global(),
159 1
            true, // Token is always required
160 1
            []
161
        );
162
    }
163
164
    /**
165
     * @return CsrfTokenManagerInterface
166
     * @internal Used by the validator
167
     */
168 13
    public function getTokenManager(): CsrfTokenManagerInterface
169
    {
170 13
        return $this->tokenManager;
171
    }
172
173
    /**
174
     * Invalidate the current CSRF token
175
     * After this call, the CSRF cannot be valid anymore
176
     */
177 2
    public function invalidateToken(): void
178
    {
179 2
        $this->tokenManager->removeToken($this->tokenId);
180 2
    }
181
}
182