178 lines
8.5 KiB
PHP
178 lines
8.5 KiB
PHP
<?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);
|
|
}
|
|
}
|
|
}
|