[TASK] Use null coalescing operator where possible
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Mvc / Controller / MvcPropertyMappingConfigurationService.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Mvc\Controller;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 /**
18 * This is a Service which can generate a request hash and check whether the currently given arguments
19 * fit to the request hash.
20 *
21 * It is used when forms are generated and submitted:
22 * After a form has been generated, the method "generateRequestHash" is called with the names of all form fields.
23 * It cleans up the array of form fields and creates another representation of it, which is then serialized and hashed.
24 *
25 * Both serialized form field list and the added hash form the request hash, which will be sent over the wire (as an argument __hmac).
26 *
27 * On the validation side, the validation happens in two steps:
28 * 1) Check if the request hash is consistent (the hash value fits to the serialized string)
29 * 2) Check that _all_ GET/POST parameters submitted occur inside the form field list of the request hash.
30 *
31 * Note: It is crucially important that a private key is computed into the hash value! This is done inside the HashService.
32 */
33 class MvcPropertyMappingConfigurationService implements \TYPO3\CMS\Core\SingletonInterface
34 {
35 /**
36 * The hash service class to use
37 *
38 * @var \TYPO3\CMS\Extbase\Security\Cryptography\HashService
39 */
40 protected $hashService;
41
42 /**
43 * @param \TYPO3\CMS\Extbase\Security\Cryptography\HashService $hashService
44 */
45 public function injectHashService(\TYPO3\CMS\Extbase\Security\Cryptography\HashService $hashService)
46 {
47 $this->hashService = $hashService;
48 }
49
50 /**
51 * Generate a request hash for a list of form fields
52 *
53 * @param array $formFieldNames Array of form fields
54 * @param string $fieldNamePrefix
55 *
56 * @return string trusted properties token
57 * @throws \TYPO3\CMS\EXTBASE\Security\Exception\InvalidArgumentForHashGenerationException
58 */
59 public function generateTrustedPropertiesToken($formFieldNames, $fieldNamePrefix = '')
60 {
61 $formFieldArray = [];
62 foreach ($formFieldNames as $formField) {
63 $formFieldParts = explode('[', $formField);
64 $currentPosition = &$formFieldArray;
65 $formFieldPartsCount = count($formFieldParts);
66 for ($i = 0; $i < $formFieldPartsCount; $i++) {
67 $formFieldPart = $formFieldParts[$i];
68 $formFieldPart = rtrim($formFieldPart, ']');
69 if (!is_array($currentPosition)) {
70 throw new \TYPO3\CMS\Extbase\Security\Exception\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);
71 }
72 if ($i === $formFieldPartsCount - 1) {
73 if (isset($currentPosition[$formFieldPart]) && is_array($currentPosition[$formFieldPart])) {
74 throw new \TYPO3\CMS\Extbase\Security\Exception\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);
75 }
76 // Last iteration - add a string
77 if ($formFieldPart === '') {
78 $currentPosition[] = 1;
79 } else {
80 $currentPosition[$formFieldPart] = 1;
81 }
82 } else {
83 if ($formFieldPart === '') {
84 throw new \TYPO3\CMS\Extbase\Security\Exception\InvalidArgumentForHashGenerationException('The form field "' . $formField . '" is invalid. Reason: "[]" used not as last argument, but somewhere in the middle (like foo[][bar]).', 1255072832);
85 }
86 if (!isset($currentPosition[$formFieldPart])) {
87 $currentPosition[$formFieldPart] = [];
88 }
89 $currentPosition = &$currentPosition[$formFieldPart];
90 }
91 }
92 }
93 if ($fieldNamePrefix !== '') {
94 $formFieldArray = ($formFieldArray[$fieldNamePrefix] ?? []);
95 }
96 return $this->serializeAndHashFormFieldArray($formFieldArray);
97 }
98
99 /**
100 * Serialize and hash the form field array
101 *
102 * @param array $formFieldArray form field array to be serialized and hashed
103 *
104 * @return string Hash
105 */
106 protected function serializeAndHashFormFieldArray(array $formFieldArray)
107 {
108 $serializedFormFieldArray = serialize($formFieldArray);
109 return $this->hashService->appendHmac($serializedFormFieldArray);
110 }
111
112 /**
113 * Initialize the property mapping configuration in $controllerArguments if
114 * the trusted properties are set inside the request.
115 *
116 * @param \TYPO3\CMS\Extbase\Mvc\Request $request
117 * @param \TYPO3\CMS\Extbase\Mvc\Controller\Arguments $controllerArguments
118 */
119 public function initializePropertyMappingConfigurationFromRequest(\TYPO3\CMS\Extbase\Mvc\Request $request, \TYPO3\CMS\Extbase\Mvc\Controller\Arguments $controllerArguments)
120 {
121 $trustedPropertiesToken = $request->getInternalArgument('__trustedProperties');
122 if (!is_string($trustedPropertiesToken)) {
123 return;
124 }
125
126 $serializedTrustedProperties = $this->hashService->validateAndStripHmac($trustedPropertiesToken);
127 $trustedProperties = unserialize($serializedTrustedProperties);
128 foreach ($trustedProperties as $propertyName => $propertyConfiguration) {
129 if (!$controllerArguments->hasArgument($propertyName)) {
130 continue;
131 }
132 $propertyMappingConfiguration = $controllerArguments->getArgument($propertyName)->getPropertyMappingConfiguration();
133 $this->modifyPropertyMappingConfiguration($propertyConfiguration, $propertyMappingConfiguration);
134 }
135 }
136
137 /**
138 * Modify the passed $propertyMappingConfiguration according to the $propertyConfiguration which
139 * has been generated by Fluid. In detail, if the $propertyConfiguration contains
140 * an __identity field, we allow modification of objects; else we allow creation.
141 *
142 * All other properties are specified as allowed properties.
143 *
144 * @param array $propertyConfiguration
145 * @param \TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration $propertyMappingConfiguration
146 */
147 protected function modifyPropertyMappingConfiguration($propertyConfiguration, \TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration $propertyMappingConfiguration)
148 {
149 if (!is_array($propertyConfiguration)) {
150 return;
151 }
152
153 if (isset($propertyConfiguration['__identity'])) {
154 $propertyMappingConfiguration->setTypeConverterOption(\TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectConverter::class, \TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectConverter::CONFIGURATION_MODIFICATION_ALLOWED, true);
155 unset($propertyConfiguration['__identity']);
156 } else {
157 $propertyMappingConfiguration->setTypeConverterOption(\TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectConverter::class, \TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectConverter::CONFIGURATION_CREATION_ALLOWED, true);
158 }
159
160 foreach ($propertyConfiguration as $innerKey => $innerValue) {
161 if (is_array($innerValue)) {
162 $this->modifyPropertyMappingConfiguration($innerValue, $propertyMappingConfiguration->forProperty($innerKey));
163 }
164 $propertyMappingConfiguration->allowProperties($innerKey);
165 }
166 }
167 }