validateFormDefinitionProperties()   B
last analyzed

Complexity

Conditions 7
Paths 12

Size

Total Lines 60
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 39
dl 0
loc 60
rs 8.3626
c 0
b 0
f 0
cc 7
nc 12
nop 3

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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