TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,352 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
|
||||
use TYPO3\CMS\Core\Crypto\HashService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\ArrayProcessing\ArrayProcessing;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\ArrayProcessing\ArrayProcessor;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\Exception\PropertyException;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators\CreatableFormElementPropertiesValidator;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators\CreatablePropertyCollectionElementPropertiesValidator;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators\FormElementHmacDataValidator;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators\PropertyCollectionElementHmacDataValidator;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators\ValidationDto;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
#[Autoconfigure(public: true)]
|
||||
readonly class FormDefinitionValidationService
|
||||
{
|
||||
public function __construct(
|
||||
protected HashService $hashService,
|
||||
protected ConfigurationService $configurationService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Validate the form definition properties using the form setup.
|
||||
* Pseudo workflow:
|
||||
* Is the form element type creatable by the form editor?
|
||||
* YES
|
||||
* foreach(form element properties) (without finishers|validators)
|
||||
* is the form element property defined in the form setup (can be manipulated)?
|
||||
* YES
|
||||
* is the form element property configured to only allow a limited set of values (e.g. Inspector-MultiSelectEditor, Inspector-SingleSelectEditor ...)
|
||||
* YES
|
||||
* is the form element property value within the set of allowed values?
|
||||
* YES
|
||||
* valid!
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* valid!
|
||||
* NO
|
||||
* is the form element property defined in "predefinedDefaults" in the form setup (cannot be manipulated but should be written)?
|
||||
* YES
|
||||
* is the form element property value equals to the value defined in "predefinedDefaults" in the form setup?
|
||||
* YES
|
||||
* valid!
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* is there a hmac hash available for the form element property value (cannot be manipulated but should be written)?
|
||||
* YES
|
||||
* is the form element property value equals the historical value (and is the historical value valid)?
|
||||
* YES
|
||||
* valid!
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* foreach(form elements finishers|validators)
|
||||
* is the form elements finisher|validator creatable by the form editor?
|
||||
* YES
|
||||
* foreach(form elements finisher|validator properties)
|
||||
* is the form elements finisher|validator property defined in the form setup (can be manipulated)?
|
||||
* YES
|
||||
* is the form elements finisher|validator property configured to only allow a limited set of values (e.g. Inspector-MultiSelectEditor, Inspector-SingleSelectEditor ...)
|
||||
* YES
|
||||
* is the form elements finisher|validator property value within the set of allowed values?
|
||||
* YES
|
||||
* valid!
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* valid!
|
||||
* NO
|
||||
* is the form elements finisher|validator property defined in "predefinedDefaults" in the form setup (cannot be manipulated but should be written)?
|
||||
* YES
|
||||
* is the form elements finisher|validator property value equals to the value defined in "predefinedDefaults" in the form setup?
|
||||
* YES
|
||||
* valid!
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* is there a hmac hash available for the form elements finisher|validator property value (can not be manipulated but should be written)?
|
||||
* YES
|
||||
* is the form elements finisher|validator property value equals the historical value (and is the historical value valid)?
|
||||
* YES
|
||||
* valid!
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* foreach(form elements finisher|validator properties)
|
||||
* is there a hmac hash available for the form elements finisher|validator property value (can not be manipulated but should be written)?
|
||||
* YES
|
||||
* is the form elements finisher|validator property value equals the historical value (and is the historical value valid)?
|
||||
* YES
|
||||
* valid!
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* foreach(form element properties) (without finishers|validators)
|
||||
* is there a hmac hash available for the form element property value (cannot be manipulated but should be written)?
|
||||
* YES
|
||||
* is the form element property value equals the historical value (and is the historical value valid)?
|
||||
* YES
|
||||
* valid!
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* foreach(form elements finisher|validator properties)
|
||||
* is there a hmac hash available for the form elements finisher|validator property value (can not be manipulated but should be written)?
|
||||
* YES
|
||||
* is the form elements finisher|validator property value equals the historical value (and is the historical value valid)?
|
||||
* YES
|
||||
* valid!
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
*
|
||||
* @throws PropertyException
|
||||
*/
|
||||
public function validateFormDefinitionProperties(
|
||||
array $currentFormElement,
|
||||
string $prototypeName,
|
||||
string $sessionToken
|
||||
): void {
|
||||
$renderables = $currentFormElement['renderables'] ?? [];
|
||||
$propertyCollectionElements = $currentFormElement['finishers'] ?? $currentFormElement['validators'] ?? [];
|
||||
$propertyCollectionName = $currentFormElement['type'] === 'Form' ? 'finishers' : 'validators';
|
||||
unset($currentFormElement['renderables'], $currentFormElement['finishers'], $currentFormElement['validators']);
|
||||
|
||||
$validationDto = GeneralUtility::makeInstance(
|
||||
ValidationDto::class,
|
||||
$prototypeName,
|
||||
$currentFormElement['type'],
|
||||
$currentFormElement['identifier'],
|
||||
null,
|
||||
$propertyCollectionName
|
||||
);
|
||||
|
||||
if ($this->configurationService->isFormElementTypeCreatableByFormEditor($validationDto)) {
|
||||
$this->validateAllPropertyValuesFromCreatableFormElement(
|
||||
$currentFormElement,
|
||||
$sessionToken,
|
||||
$validationDto
|
||||
);
|
||||
foreach ($propertyCollectionElements as $propertyCollectionElement) {
|
||||
$validationDto = $validationDto->withPropertyCollectionElementIdentifier(
|
||||
$propertyCollectionElement['identifier']
|
||||
);
|
||||
if ($this->configurationService->isPropertyCollectionElementIdentifierCreatableByFormEditor($validationDto)) {
|
||||
$this->validateAllPropertyValuesFromCreatablePropertyCollectionElement(
|
||||
$propertyCollectionElement,
|
||||
$sessionToken,
|
||||
$validationDto
|
||||
);
|
||||
} else {
|
||||
$this->validateAllPropertyCollectionElementValuesByHmac(
|
||||
$propertyCollectionElement,
|
||||
$sessionToken,
|
||||
$validationDto
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->validateAllFormElementPropertyValuesByHmac($currentFormElement, $sessionToken, $validationDto);
|
||||
foreach ($propertyCollectionElements as $propertyCollectionElement) {
|
||||
$this->validateAllPropertyCollectionElementValuesByHmac(
|
||||
$propertyCollectionElement,
|
||||
$sessionToken,
|
||||
$validationDto
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($renderables as $renderable) {
|
||||
$this->validateFormDefinitionProperties($renderable, $prototypeName, $sessionToken);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns TRUE if a property value is equals to the historical value
|
||||
* and FALSE if not.
|
||||
* "Historical values" means values which are available within the form definition
|
||||
* while the form editor is loaded and the values which are available after a
|
||||
* successful validation of the form definition on a save operation.
|
||||
* The value must be equal to the historical value if the property key for the value
|
||||
* is not defined within the form setup.
|
||||
* This means that the property can not be changed by the form editor but we want to keep the value
|
||||
* in its original state.
|
||||
* If this is not the case (return value is FALSE), an exception must be thrown.
|
||||
*
|
||||
* @throws PropertyException
|
||||
*/
|
||||
public function isPropertyValueEqualToHistoricalValue(
|
||||
array $hmacContent,
|
||||
mixed $propertyValue,
|
||||
array $hmacData,
|
||||
string $sessionToken
|
||||
): bool {
|
||||
$this->checkHmacDataIntegrity($hmacData, $hmacContent, $sessionToken);
|
||||
$hmacContent[] = $propertyValue;
|
||||
$expectedHash = $this->hashService->hmac(serialize($hmacContent), $sessionToken);
|
||||
return hash_equals($expectedHash, $hmacData['hmac']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the historical value and the hmac hash to ensure the integrity
|
||||
* of the data.
|
||||
* An exception will be thrown if the value is modified.
|
||||
*
|
||||
* @throws PropertyException
|
||||
*/
|
||||
protected function checkHmacDataIntegrity(array $hmacData, array $hmacContent, string $sessionToken)
|
||||
{
|
||||
$hmac = $hmacData['hmac'] ?? null;
|
||||
if (empty($hmac)) {
|
||||
throw new PropertyException('Hmac must not be empty. #1528538222', 1528538222);
|
||||
}
|
||||
$hmacContent[] = $hmacData['value'] ?? '';
|
||||
$expectedHash = $this->hashService->hmac(serialize($hmacContent), $sessionToken);
|
||||
if (!hash_equals($expectedHash, $hmac)) {
|
||||
throw new PropertyException('Unauthorized modification of historical data. #1528538252', 1528538252);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk through all form element properties and checks
|
||||
* if the values matches to their hmac hashes.
|
||||
*/
|
||||
protected function validateAllFormElementPropertyValuesByHmac(
|
||||
array $currentElement,
|
||||
string $sessionToken,
|
||||
ValidationDto $validationDto
|
||||
): void {
|
||||
GeneralUtility::makeInstance(ArrayProcessor::class, $currentElement)->forEach(
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'validateProperties',
|
||||
'^(?!(_orig_.*|.*\._orig_.*)$).*',
|
||||
GeneralUtility::makeInstance(
|
||||
FormElementHmacDataValidator::class,
|
||||
$currentElement,
|
||||
$sessionToken,
|
||||
$validationDto
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk through all property collection properties and checks
|
||||
* if the values matches to their hmac hashes.
|
||||
*/
|
||||
protected function validateAllPropertyCollectionElementValuesByHmac(
|
||||
array $currentElement,
|
||||
string $sessionToken,
|
||||
ValidationDto $validationDto
|
||||
): void {
|
||||
GeneralUtility::makeInstance(ArrayProcessor::class, $currentElement)->forEach(
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'validateProperties',
|
||||
'^(?!(_orig_.*|.*\._orig_.*)$).*',
|
||||
GeneralUtility::makeInstance(
|
||||
PropertyCollectionElementHmacDataValidator::class,
|
||||
$currentElement,
|
||||
$sessionToken,
|
||||
$validationDto
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk through all form element properties and checks
|
||||
* if the property is defined within the form editor setup
|
||||
* or if the property is defined within the "predefinedDefaults" in the form editor setup
|
||||
* and the property value matches the predefined value
|
||||
* or if there is a valid hmac hash for the value.
|
||||
*/
|
||||
protected function validateAllPropertyValuesFromCreatableFormElement(
|
||||
array $currentElement,
|
||||
string $sessionToken,
|
||||
ValidationDto $validationDto
|
||||
): void {
|
||||
GeneralUtility::makeInstance(ArrayProcessor::class, $currentElement)->forEach(
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'validateProperties',
|
||||
'^(?!(_orig_.*|.*\._orig_.*|type|identifier)$).*',
|
||||
GeneralUtility::makeInstance(
|
||||
CreatableFormElementPropertiesValidator::class,
|
||||
$currentElement,
|
||||
$sessionToken,
|
||||
$validationDto
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk through all property collection properties and checks
|
||||
* if the property is defined within the form editor setup
|
||||
* or if the property is defined within the "predefinedDefaults" in the form editor setup
|
||||
* and the property value matches the predefined value
|
||||
* or if there is a valid hmac hash for the value.
|
||||
*/
|
||||
protected function validateAllPropertyValuesFromCreatablePropertyCollectionElement(
|
||||
array $currentElement,
|
||||
string $sessionToken,
|
||||
ValidationDto $validationDto
|
||||
): void {
|
||||
GeneralUtility::makeInstance(ArrayProcessor::class, $currentElement)->forEach(
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'validateProperties',
|
||||
'^(?!(_orig_.*|.*\._orig_.*|identifier)$).*',
|
||||
GeneralUtility::makeInstance(
|
||||
CreatablePropertyCollectionElementPropertiesValidator::class,
|
||||
$currentElement,
|
||||
$sessionToken,
|
||||
$validationDto
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user