[TASK] Fix CGL issues
[Packages/TYPO3.CMS.git] / typo3 / sysext / form / Classes / Domain / Configuration / FormDefinitionValidationService.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Form\Domain\Configuration;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use TYPO3\CMS\Core\SingletonInterface;
19 use TYPO3\CMS\Core\Utility\GeneralUtility;
20 use TYPO3\CMS\Extbase\Object\ObjectManager;
21 use TYPO3\CMS\Form\Domain\Configuration\ArrayProcessing\ArrayProcessing;
22 use TYPO3\CMS\Form\Domain\Configuration\ArrayProcessing\ArrayProcessor;
23 use TYPO3\CMS\Form\Domain\Configuration\Exception\PropertyException;
24 use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators\CreatableFormElementPropertiesValidator;
25 use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators\CreatablePropertyCollectionElementPropertiesValidator;
26 use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators\FormElementHmacDataValidator;
27 use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators\PropertyCollectionElementHmacDataValidator;
28 use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators\ValidationDto;
29
30 /**
31 * @internal
32 */
33 class FormDefinitionValidationService implements SingletonInterface
34 {
35
36 /**
37 * @var ConfigurationService
38 */
39 protected $configurationService;
40
41 /**
42 * Validate the form definition properties using the form setup.
43 * Pseudo workflow:
44 * Is the form element type creatable by the form editor?
45 * YES
46 * foreach(form element properties) (without finishers|validators)
47 * is the form element property defined in the form setup (can be manipulated)?
48 * YES
49 * valid!
50 * NO
51 * is the form element property defined in "predefinedDefaults" in the form setup (cannot be manipulated but should be written)?
52 * YES
53 * is the form element property value equals to the value defined in "predefinedDefaults" in the form setup?
54 * YES
55 * valid!
56 * NO
57 * invalid! throw exception
58 * NO
59 * is there a hmac hash available for the form element property value (cannot be manipulated but should be written)?
60 * YES
61 * is the form element property value equals the historical value (and is the historical value valid)?
62 * YES
63 * valid!
64 * NO
65 * invalid! throw exception
66 * NO
67 * invalid! throw exception
68 * foreach(form elements finishers|validators)
69 * is the form elements finisher|validator creatable by the form editor?
70 * YES
71 * foreach(form elements finisher|validator properties)
72 * is the form elements finisher|validator property defined in the form setup (can be manipulated)?
73 * YES
74 * valid!
75 * NO
76 * is the form elements finisher|validator property defined in "predefinedDefaults" in the form setup (cannot be manipulated but should be written)?
77 * YES
78 * is the form elements finisher|validator property value equals to the value defined in "predefinedDefaults" in the form setup?
79 * YES
80 * valid!
81 * NO
82 * invalid! throw exception
83 * NO
84 * is there a hmac hash available for the form elements finisher|validator property value (can not be manipulated but should be written)?
85 * YES
86 * is the form elements finisher|validator property value equals the historical value (and is the historical value valid)?
87 * YES
88 * valid!
89 * NO
90 * invalid! throw exception
91 * NO
92 * invalid! throw exception
93 * NO
94 * foreach(form elements finisher|validator properties)
95 * is there a hmac hash available for the form elements finisher|validator property value (can not be manipulated but should be written)?
96 * YES
97 * is the form elements finisher|validator property value equals the historical value (and is the historical value valid)?
98 * YES
99 * valid!
100 * NO
101 * invalid! throw exception
102 * NO
103 * invalid! throw exception
104 * NO
105 * foreach(form element properties) (without finishers|validators)
106 * is there a hmac hash available for the form element property value (cannot be manipulated but should be written)?
107 * YES
108 * is the form element property value equals the historical value (and is the historical value valid)?
109 * YES
110 * valid!
111 * NO
112 * invalid! throw exception
113 * NO
114 * invalid! throw exception
115 * foreach(form elements finisher|validator properties)
116 * is there a hmac hash available for the form elements finisher|validator property value (can not be manipulated but should be written)?
117 * YES
118 * is the form elements finisher|validator property value equals the historical value (and is the historical value valid)?
119 * YES
120 * valid!
121 * NO
122 * invalid! throw exception
123 * NO
124 * invalid! throw exception
125 *
126 * @param array $currentFormElement
127 * @param string $prototypeName
128 * @param string $sessionToken
129 * @throws PropertyException
130 */
131 public function validateFormDefinitionProperties(
132 array $currentFormElement,
133 string $prototypeName,
134 string $sessionToken
135 ): void {
136 $renderables = $currentFormElement['renderables'] ?? [];
137 $propertyCollectionElements = $currentFormElement['finishers'] ?? $currentFormElement['validators'] ?? [];
138 $propertyCollectionName = $currentFormElement['type'] === 'Form' ? 'finishers' : 'validators';
139 unset($currentFormElement['renderables'], $currentFormElement['finishers'], $currentFormElement['validators']);
140
141 $validationDto = GeneralUtility::makeInstance(
142 ValidationDto::class,
143 $prototypeName,
144 $currentFormElement['type'],
145 $currentFormElement['identifier'],
146 null,
147 $propertyCollectionName
148 );
149
150 if ($this->getConfigurationService()->isFormElementTypeCreatableByFormEditor($validationDto)) {
151 $this->validateAllPropertyValuesFromCreatableFormElement(
152 $currentFormElement,
153 $sessionToken,
154 $validationDto
155 );
156
157 foreach ($propertyCollectionElements as $propertyCollectionElement) {
158 $validationDto = $validationDto->withPropertyCollectionElementIdentifier(
159 $propertyCollectionElement['identifier']
160 );
161
162 if ($this->getConfigurationService()->isPropertyCollectionElementIdentifierCreatableByFormEditor($validationDto)) {
163 $this->validateAllPropertyValuesFromCreatablePropertyCollectionElement(
164 $propertyCollectionElement,
165 $sessionToken,
166 $validationDto
167 );
168 } else {
169 $this->validateAllPropertyCollectionElementValuesByHmac(
170 $propertyCollectionElement,
171 $sessionToken,
172 $validationDto
173 );
174 }
175 }
176 } else {
177 $this->validateAllFormElementPropertyValuesByHmac($currentFormElement, $sessionToken, $validationDto);
178
179 foreach ($propertyCollectionElements as $propertyCollectionElement) {
180 $this->validateAllPropertyCollectionElementValuesByHmac(
181 $propertyCollectionElement,
182 $sessionToken,
183 $validationDto
184 );
185 }
186 }
187
188 foreach ($renderables as $renderable) {
189 $this->validateFormDefinitionProperties($renderable, $prototypeName, $sessionToken);
190 }
191 }
192
193 /**
194 * Returns TRUE if a property value is equals to the historical value
195 * and FALSE if not.
196 * "Historical values" means values which are available within the form definition
197 * while the form editor is loaded and the values which are available after a
198 * successful validation of the form definition on a save operation.
199 * The value must be equal to the historical value if the property key for the value
200 * is not defined within the form setup.
201 * This means that the property can not be changed by the form editor but we want to keep the value
202 * in its original state.
203 * If this is not the case (return value is FALSE), an exception must be thrown.
204 *
205 * @param array $hmacContent
206 * @param mixed $propertyValue
207 * @param array $hmacData
208 * @param string $sessionToken
209 * @return bool
210 * @throws PropertyException
211 */
212 public function isPropertyValueEqualToHistoricalValue(
213 array $hmacContent,
214 $propertyValue,
215 array $hmacData,
216 string $sessionToken
217 ): bool {
218 $this->checkHmacDataIntegrity($hmacData, $hmacContent, $sessionToken);
219 $hmacContent[] = $propertyValue;
220
221 $expectedHash = GeneralUtility::hmac(serialize($hmacContent), $sessionToken);
222 return hash_equals($expectedHash, $hmacData['hmac']);
223 }
224
225 /**
226 * Compares the historical value and the hmac hash to ensure the integrity
227 * of the data.
228 * An exception will be thrown if the value is modified.
229 *
230 * @param array $hmacData
231 * @param array $hmacContent
232 * @param string $sessionToken
233 * @throws PropertyException
234 */
235 protected function checkHmacDataIntegrity(array $hmacData, array $hmacContent, string $sessionToken)
236 {
237 $hmac = $hmacData['hmac'] ?? null;
238 if (empty($hmac)) {
239 throw new PropertyException('Hmac must not be empty. #1528538222', 1528538222);
240 }
241
242 $hmacContent[] = $hmacData['value'] ?? '';
243 $expectedHash = GeneralUtility::hmac(serialize($hmacContent), $sessionToken);
244
245 if (!hash_equals($expectedHash, $hmac)) {
246 throw new PropertyException('Unauthorized modification of historical data. #1528538252', 1528538252);
247 }
248 }
249
250 /**
251 * Walk through all form element properties and checks
252 * if the values matches to their hmac hashes.
253 *
254 * @param array $currentElement
255 * @param string $sessionToken
256 * @param ValidationDto $validationDto
257 */
258 protected function validateAllFormElementPropertyValuesByHmac(
259 array $currentElement,
260 $sessionToken,
261 ValidationDto $validationDto
262 ): void {
263 GeneralUtility::makeInstance(ArrayProcessor::class, $currentElement)->forEach(
264 GeneralUtility::makeInstance(
265 ArrayProcessing::class,
266 'validateProperties',
267 '^(?!(_orig_.*|.*\._orig_.*)$).*',
268 GeneralUtility::makeInstance(
269 FormElementHmacDataValidator::class,
270 $currentElement,
271 $sessionToken,
272 $validationDto
273 )
274 )
275 );
276 }
277
278 /**
279 * Walk through all property collection properties and checks
280 * if the values matches to their hmac hashes.
281 *
282 * @param array $currentElement
283 * @param string $sessionToken
284 * @param ValidationDto $validationDto
285 */
286 protected function validateAllPropertyCollectionElementValuesByHmac(
287 array $currentElement,
288 $sessionToken,
289 ValidationDto $validationDto
290 ): void {
291 GeneralUtility::makeInstance(ArrayProcessor::class, $currentElement)->forEach(
292 GeneralUtility::makeInstance(
293 ArrayProcessing::class,
294 'validateProperties',
295 '^(?!(_orig_.*|.*\._orig_.*)$).*',
296 GeneralUtility::makeInstance(
297 PropertyCollectionElementHmacDataValidator::class,
298 $currentElement,
299 $sessionToken,
300 $validationDto
301 )
302 )
303 );
304 }
305
306 /**
307 * Walk through all form element properties and checks
308 * if the property is defined within the form editor setup
309 * or if the property is definied within the "predefinedDefaults" in the form editor setup
310 * and the property value matches the predefined value
311 * or if there is a valid hmac hash for the value.
312 *
313 * @param array $currentElement
314 * @param string $sessionToken
315 * @param ValidationDto $validationDto
316 */
317 protected function validateAllPropertyValuesFromCreatableFormElement(
318 array $currentElement,
319 $sessionToken,
320 ValidationDto $validationDto
321 ): void {
322 GeneralUtility::makeInstance(ArrayProcessor::class, $currentElement)->forEach(
323 GeneralUtility::makeInstance(
324 ArrayProcessing::class,
325 'validateProperties',
326 '^(?!(_orig_.*|.*\._orig_.*|type|identifier)$).*',
327 GeneralUtility::makeInstance(
328 CreatableFormElementPropertiesValidator::class,
329 $currentElement,
330 $sessionToken,
331 $validationDto
332 )
333 )
334 );
335 }
336
337 /**
338 * Walk through all property collection properties and checks
339 * if the property is defined within the form editor setup
340 * or if the property is definied within the "predefinedDefaults" in the form editor setup
341 * and the property value matches the predefined value
342 * or if there is a valid hmac hash for the value.
343 *
344 * @param array $currentElement
345 * @param string $sessionToken
346 * @param ValidationDto $validationDto
347 */
348 protected function validateAllPropertyValuesFromCreatablePropertyCollectionElement(
349 array $currentElement,
350 $sessionToken,
351 ValidationDto $validationDto
352 ): void {
353 GeneralUtility::makeInstance(ArrayProcessor::class, $currentElement)->forEach(
354 GeneralUtility::makeInstance(
355 ArrayProcessing::class,
356 'validateProperties',
357 '^(?!(_orig_.*|.*\._orig_.*|identifier)$).*',
358 GeneralUtility::makeInstance(
359 CreatablePropertyCollectionElementPropertiesValidator::class,
360 $currentElement,
361 $sessionToken,
362 $validationDto
363 )
364 )
365 );
366 }
367
368 /**
369 * @return ConfigurationService
370 */
371 protected function getConfigurationService(): ConfigurationService
372 {
373 if (!($this->configurationService instanceof ConfigurationService)) {
374 $this->configurationService = $this->getObjectManager()->get(ConfigurationService::class);
375 }
376 return $this->configurationService;
377 }
378
379 /**
380 * @return ObjectManager
381 */
382 protected function getObjectManager(): ObjectManager
383 {
384 return GeneralUtility::makeInstance(ObjectManager::class);
385 }
386 }