TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
<?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\Extbase\Mvc\Controller;
|
||||
|
||||
use TYPO3\CMS\Core\Crypto\HashAlgo;
|
||||
use TYPO3\CMS\Core\Crypto\HashService;
|
||||
use TYPO3\CMS\Core\Error\Http\BadRequestException;
|
||||
use TYPO3\CMS\Core\Exception\Crypto\InvalidHashStringException;
|
||||
use TYPO3\CMS\Extbase\Mvc\ExtbaseRequestParameters;
|
||||
use TYPO3\CMS\Extbase\Mvc\Request;
|
||||
use TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface;
|
||||
use TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectConverter;
|
||||
use TYPO3\CMS\Extbase\Security\Exception\InvalidArgumentForHashGenerationException;
|
||||
use TYPO3\CMS\Extbase\Security\HashScope;
|
||||
|
||||
/**
|
||||
* This is a service which can generate a request hash and check whether the currently given arguments
|
||||
* fit to the request hash.
|
||||
*
|
||||
* It is used when forms are generated and submitted:
|
||||
* After a form has been generated, the method "generateTrustedPropertiesToken" is called with the names of all form fields.
|
||||
* It cleans up the array of form fields and creates another representation of it, which is then json encoded and a hmac
|
||||
* is appended. This is called the request hash.
|
||||
*
|
||||
* The json encoded form field list and the appended hmac will be submitted with the form (as attribute __trustedProperties).
|
||||
*
|
||||
* On the validation side, the validation happens in two steps:
|
||||
* 1) Check if the request hash is consistent (the hmac value fits to the json encoded field list string)
|
||||
* 2) Check that _all_ GET/POST parameters submitted occur inside the form field list of the request hash.
|
||||
*
|
||||
* Note: It is crucially important that a private key is computed into the hash value! This is done inside the HashService.
|
||||
*
|
||||
* @internal only to be used within Extbase, not part of TYPO3 Core API.
|
||||
*/
|
||||
class MvcPropertyMappingConfigurationService
|
||||
{
|
||||
protected HashService $hashService;
|
||||
|
||||
public function injectHashService(HashService $hashService): void
|
||||
{
|
||||
$this->hashService = $hashService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a request hash for a list of form fields
|
||||
*/
|
||||
public function generateTrustedPropertiesToken(array $formFieldNames, string $fieldNamePrefix = ''): string
|
||||
{
|
||||
$formFieldArray = [];
|
||||
foreach ($formFieldNames as $formField) {
|
||||
$formFieldParts = explode('[', $formField);
|
||||
$currentPosition = &$formFieldArray;
|
||||
$formFieldPartsCount = count($formFieldParts);
|
||||
for ($i = 0; $i < $formFieldPartsCount; $i++) {
|
||||
$formFieldPart = $formFieldParts[$i];
|
||||
$formFieldPart = rtrim($formFieldPart, ']');
|
||||
if (!is_array($currentPosition)) {
|
||||
throw new InvalidArgumentForHashGenerationException('The form field "' . $formField . '" is declared as array, but it collides with a previous form field of the same name which declared the field as string. This is an inconsistency you need to fix inside your Fluid form. (String overridden by Array)', 1255072196);
|
||||
}
|
||||
if ($i === $formFieldPartsCount - 1) {
|
||||
if (isset($currentPosition[$formFieldPart]) && is_array($currentPosition[$formFieldPart])) {
|
||||
throw new InvalidArgumentForHashGenerationException('The form field "' . $formField . '" is declared as string, but it collides with a previous form field of the same name which declared the field as array. This is an inconsistency you need to fix inside your Fluid form. (Array overridden by String)', 1255072587);
|
||||
}
|
||||
// Last iteration - add a string
|
||||
if ($formFieldPart === '') {
|
||||
$currentPosition[] = 1;
|
||||
} else {
|
||||
$currentPosition[$formFieldPart] = 1;
|
||||
}
|
||||
} else {
|
||||
if ($formFieldPart === '') {
|
||||
throw new InvalidArgumentForHashGenerationException('The form field "' . $formField . '" is invalid. Reason: "[]" used not as last argument, but somewhere in the middle (like foo[][bar]).', 1255072832);
|
||||
}
|
||||
if (!isset($currentPosition[$formFieldPart])) {
|
||||
$currentPosition[$formFieldPart] = [];
|
||||
}
|
||||
$currentPosition = &$currentPosition[$formFieldPart];
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($fieldNamePrefix !== '') {
|
||||
$formFieldArray = ($formFieldArray[$fieldNamePrefix] ?? []);
|
||||
}
|
||||
return $this->encodeAndHashFormFieldArray($formFieldArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode and hash the form field array
|
||||
*/
|
||||
protected function encodeAndHashFormFieldArray(array $formFieldArray): string
|
||||
{
|
||||
$encodedFormFieldArray = json_encode($formFieldArray);
|
||||
return $this->hashService->appendHmac($encodedFormFieldArray, HashScope::TrustedProperties->prefix(), HashAlgo::SHA3_256);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the property mapping configuration in $controllerArguments if
|
||||
* the trusted properties are set inside the request.
|
||||
*
|
||||
* @throws BadRequestException
|
||||
*/
|
||||
public function initializePropertyMappingConfigurationFromRequest(Request $request, Arguments $controllerArguments): void
|
||||
{
|
||||
/** @var ExtbaseRequestParameters $extbaseRequestParameters */
|
||||
$extbaseRequestParameters = $request->getAttribute('extbase');
|
||||
$trustedPropertiesToken = $extbaseRequestParameters->getInternalArgument('__trustedProperties');
|
||||
if (!is_string($trustedPropertiesToken)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$encodedTrustedProperties = $this->hashService->validateAndStripHmac($trustedPropertiesToken, HashScope::TrustedProperties->prefix(), HashAlgo::SHA3_256);
|
||||
} catch (InvalidHashStringException $e) {
|
||||
throw new BadRequestException('The HMAC of the form could not be validated.', 1581862822);
|
||||
}
|
||||
$trustedProperties = json_decode($encodedTrustedProperties, true);
|
||||
if (!is_array($trustedProperties)) {
|
||||
if (str_starts_with($encodedTrustedProperties, 'a:')) {
|
||||
throw new BadRequestException('Trusted properties used outdated serialization format instead json.', 1699604555);
|
||||
}
|
||||
throw new BadRequestException('The HMAC of the form could not be utilized.', 1691267306);
|
||||
}
|
||||
|
||||
foreach ($trustedProperties as $propertyName => $propertyConfiguration) {
|
||||
$propertyName = (string)$propertyName;
|
||||
if (!$controllerArguments->hasArgument($propertyName) || !is_array($propertyConfiguration)) {
|
||||
continue;
|
||||
}
|
||||
$propertyMappingConfiguration = $controllerArguments->getArgument($propertyName)->getPropertyMappingConfiguration();
|
||||
$this->modifyPropertyMappingConfiguration($propertyConfiguration, $propertyMappingConfiguration);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the passed $propertyMappingConfiguration according to the $propertyConfiguration which
|
||||
* has been generated by Fluid. In detail, if the $propertyConfiguration contains
|
||||
* an __identity field, we allow modification of objects; else we allow creation.
|
||||
*
|
||||
* All other properties are specified as allowed properties.
|
||||
*/
|
||||
protected function modifyPropertyMappingConfiguration(
|
||||
array $propertyConfiguration,
|
||||
PropertyMappingConfigurationInterface $propertyMappingConfiguration
|
||||
): void {
|
||||
if (isset($propertyConfiguration['__identity'])) {
|
||||
$propertyMappingConfiguration->setTypeConverterOption(PersistentObjectConverter::class, PersistentObjectConverter::CONFIGURATION_MODIFICATION_ALLOWED, true);
|
||||
unset($propertyConfiguration['__identity']);
|
||||
} else {
|
||||
$propertyMappingConfiguration->setTypeConverterOption(PersistentObjectConverter::class, PersistentObjectConverter::CONFIGURATION_CREATION_ALLOWED, true);
|
||||
}
|
||||
|
||||
foreach ($propertyConfiguration as $innerKey => $innerValue) {
|
||||
if (is_array($innerValue)) {
|
||||
$this->modifyPropertyMappingConfiguration(
|
||||
$innerValue,
|
||||
$propertyMappingConfiguration->forProperty((string)$innerKey)
|
||||
);
|
||||
}
|
||||
$propertyMappingConfiguration->allowProperties($innerKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user