‪TYPO3CMS  ‪main
FilePersistenceSlot.php
Go to the documentation of this file.
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 
19 
31 
38 {
39  public const ‪COMMAND_FILE_ADD = 'fileAdd';
40  public const ‪COMMAND_FILE_CREATE = 'fileCreate';
41  public const ‪COMMAND_FILE_MOVE = 'fileMove';
42  public const ‪COMMAND_FILE_RENAME = 'fileRename';
43  public const ‪COMMAND_FILE_REPLACE = 'fileReplace';
44  public const ‪COMMAND_FILE_SET_CONTENTS = 'fileSetContents';
45 
49  protected ‪$definedInvocations = [];
50 
54  protected ‪$allowedInvocations = [];
55 
56  public function ‪getContentSignature(string $content): string
57  {
58  return ‪GeneralUtility::hmac($content);
59  }
60 
71  public function ‪defineInvocation(string $command, bool $type = null)
72  {
73  $this->definedInvocations[$command] = $type;
74  if ($type === null) {
75  unset($this->definedInvocations[$command]);
76  }
77  }
78 
86  public function ‪allowInvocation(
87  string $command,
88  string $combinedFileIdentifier,
89  string $contentSignature = null
90  ): bool {
91  $index = $this->‪searchAllowedInvocation(
92  $command,
93  $combinedFileIdentifier,
94  $contentSignature
95  );
96 
97  if ($index !== null) {
98  return false;
99  }
100 
101  $this->allowedInvocations[] = [
102  'command' => $command,
103  'combinedFileIdentifier' => $combinedFileIdentifier,
104  'contentSignature' => $contentSignature,
105  ];
106 
107  return true;
108  }
109 
110  #[AsEventListener('form-framework/creation')]
111  public function ‪onPreFileCreate(‪BeforeFileCreatedEvent $event): void
112  {
113  $combinedFileIdentifier = $this->‪buildCombinedIdentifier(
114  $event->‪getFolder(),
115  $event->‪getFileName()
116  );
117 
118  $this->‪assertFileName(
119  self::COMMAND_FILE_CREATE,
120  $combinedFileIdentifier
121  );
122  }
123 
124  #[AsEventListener('form-framework/add')]
125  public function ‪onPreFileAdd(‪BeforeFileAddedEvent $event): void
126  {
127  $combinedFileIdentifier = $this->‪buildCombinedIdentifier(
128  $event->‪getTargetFolder(),
129  $event->‪getFileName()
130  );
131  // while assertFileName below also checks if it's a form definition
132  // we want an early return here to get rid of the file_get_contents
133  // below which would be triggered on every file add command otherwise
134  if (!$this->‪isFormDefinition($combinedFileIdentifier)) {
135  return;
136  }
137  $this->‪assertFileName(
138  self::COMMAND_FILE_ADD,
139  $combinedFileIdentifier,
140  (string)file_get_contents($event->‪getSourceFilePath())
141  );
142  }
143 
144  #[AsEventListener('form-framework/rename')]
145  public function ‪onPreFileRename(‪BeforeFileRenamedEvent $event): void
146  {
147  $combinedFileIdentifier = $this->‪buildCombinedIdentifier(
148  $event->‪getFile()->getParentFolder(),
149  $event->‪getTargetFileName() ?? ''
150  );
151 
152  $this->‪assertFileName(
153  self::COMMAND_FILE_RENAME,
154  $combinedFileIdentifier
155  );
156  }
157 
158  #[AsEventListener('form-framework/replace')]
159  public function ‪onPreFileReplace(‪BeforeFileReplacedEvent $event): void
160  {
161  $combinedFileIdentifier = $this->‪buildCombinedIdentifier(
162  $event->‪getFile()->getParentFolder(),
163  $event->‪getFile()->getName()
164  );
165 
166  $this->‪assertFileName(
167  self::COMMAND_FILE_REPLACE,
168  $combinedFileIdentifier
169  );
170  }
171 
172  #[AsEventListener('form-framework/move')]
173  public function ‪onPreFileMove(‪BeforeFileMovedEvent $event): void
174  {
175  // Skip check, in case file extension would not change during this
176  // command. In case e.g. "file.txt" shall be renamed to "file.form.yaml"
177  // the invocation still has to be granted.
178  // Any file moved to a recycle folder is accepted as well.
179  if ($this->‪isFormDefinition($event->‪getFile()->getIdentifier())
180  && $this->isFormDefinition($event->‪getTargetFileName())
181  || $this->isRecycleFolder($event->‪getFolder())) {
182  return;
183  }
184 
185  $combinedFileIdentifier = $this->‪buildCombinedIdentifier(
186  $event->‪getFolder(),
187  $event->‪getTargetFileName()
188  );
189 
190  $this->‪assertFileName(
191  self::COMMAND_FILE_MOVE,
192  $combinedFileIdentifier
193  );
194  }
195 
196  #[AsEventListener('form-framework/update-content')]
197  public function ‪onPreFileSetContents(‪BeforeFileContentsSetEvent $event): void
198  {
199  $combinedFileIdentifier = $this->‪buildCombinedIdentifier(
200  $event->‪getFile()->getParentFolder(),
201  $event->‪getFile()->getName()
202  );
203 
204  $this->‪assertFileName(
205  self::COMMAND_FILE_SET_CONTENTS,
206  $combinedFileIdentifier,
207  $event->‪getContent()
208  );
209  }
210 
214  protected function ‪assertFileName(
215  string $command,
216  string $combinedFileIdentifier,
217  string $content = null
218  ): void {
219  if (!$this->‪isFormDefinition($combinedFileIdentifier)) {
220  return;
221  }
222 
223  $definedInvocation = $this->definedInvocations[$command] ?? null;
224  // whitelisted command
225  if ($definedInvocation === true) {
226  return;
227  }
228  // blacklisted command
229  if ($definedInvocation === false) {
230  throw new FormDefinitionPersistenceException(
231  sprintf(
232  'Persisting form definition "%s" is denied',
233  $combinedFileIdentifier
234  ),
235  1530281201
236  );
237  }
238 
239  $contentSignature = null;
240  if ($content !== null) {
241  $contentSignature = $this->‪getContentSignature((string)$content);
242  }
243  $allowedInvocationIndex = $this->‪searchAllowedInvocation(
244  $command,
245  $combinedFileIdentifier,
246  $contentSignature
247  );
248 
249  if ($allowedInvocationIndex === null) {
250  throw new FormDefinitionPersistenceException(
251  sprintf(
252  'Persisting form definition "%s" is denied',
253  $combinedFileIdentifier
254  ),
255  1530281202
256  );
257  }
258  unset($this->allowedInvocations[$allowedInvocationIndex]);
259  }
260 
264  protected function ‪searchAllowedInvocation(
265  string $command,
266  string $combinedFileIdentifier,
267  string $contentSignature = null
268  ): ?int {
269  foreach ($this->allowedInvocations as $index => $allowedInvocation) {
270  if (
271  $command === $allowedInvocation['command']
272  && $combinedFileIdentifier === $allowedInvocation['combinedFileIdentifier']
273  && $contentSignature === $allowedInvocation['contentSignature']
274  ) {
275  return $index;
276  }
277  }
278  return null;
279  }
280 
281  protected function ‪buildCombinedIdentifier(‪FolderInterface $folder, string $fileName): string
282  {
283  return sprintf(
284  '%d:%s%s',
285  $folder->‪getStorage()->getUid(),
286  $folder->‪getIdentifier(),
287  $fileName
288  );
289  }
290 
291  protected function ‪isFormDefinition(string ‪$identifier): bool
292  {
293  return str_ends_with(
296  );
297  }
298 
299  protected function ‪isRecycleFolder(‪FolderInterface $folder): bool
300  {
301  $role = $folder->‪getStorage()->getRole($folder);
302  return $role === ‪FolderInterface::ROLE_RECYCLER;
303  }
304 }
‪TYPO3\CMS\Core\Resource\ResourceInterface\getIdentifier
‪getIdentifier()
‪TYPO3\CMS\Core\Resource\Event\BeforeFileCreatedEvent
Definition: BeforeFileCreatedEvent.php:29
‪TYPO3\CMS\Form\Slot\FilePersistenceSlot\isFormDefinition
‪isFormDefinition(string $identifier)
Definition: FilePersistenceSlot.php:289
‪TYPO3\CMS\Core\Resource\Event\BeforeFileReplacedEvent
Definition: BeforeFileReplacedEvent.php:27
‪TYPO3\CMS\Core\Attribute\AsEventListener
Definition: AsEventListener.php:25
‪TYPO3\CMS\Form\Slot\FilePersistenceSlot\onPreFileMove
‪onPreFileMove(BeforeFileMovedEvent $event)
Definition: FilePersistenceSlot.php:171
‪TYPO3\CMS\Core\Resource\ResourceInterface\getStorage
‪getStorage()
‪TYPO3\CMS\Form\Slot\FilePersistenceSlot\COMMAND_FILE_SET_CONTENTS
‪const COMMAND_FILE_SET_CONTENTS
Definition: FilePersistenceSlot.php:44
‪TYPO3\CMS\Core\Resource\Event\BeforeFileRenamedEvent
Definition: BeforeFileRenamedEvent.php:27
‪TYPO3\CMS\Core\Resource\Event\BeforeFileMovedEvent\getTargetFileName
‪getTargetFileName()
Definition: BeforeFileMovedEvent.php:45
‪TYPO3\CMS\Form\Slot\FilePersistenceSlot\onPreFileSetContents
‪onPreFileSetContents(BeforeFileContentsSetEvent $event)
Definition: FilePersistenceSlot.php:195
‪TYPO3\CMS\Core\Resource\Event\BeforeFileMovedEvent
Definition: BeforeFileMovedEvent.php:28
‪TYPO3\CMS\Form\Slot\FilePersistenceSlot\onPreFileAdd
‪onPreFileAdd(BeforeFileAddedEvent $event)
Definition: FilePersistenceSlot.php:123
‪TYPO3\CMS\Core\Resource\Event\BeforeFileCreatedEvent\getFolder
‪getFolder()
Definition: BeforeFileCreatedEvent.php:37
‪TYPO3\CMS\Form\Slot\FilePersistenceSlot\COMMAND_FILE_MOVE
‪const COMMAND_FILE_MOVE
Definition: FilePersistenceSlot.php:41
‪TYPO3\CMS\Core\Resource\Event\BeforeFileRenamedEvent\getFile
‪getFile()
Definition: BeforeFileRenamedEvent.php:30
‪TYPO3\CMS\Form\Slot\FilePersistenceSlot\COMMAND_FILE_CREATE
‪const COMMAND_FILE_CREATE
Definition: FilePersistenceSlot.php:40
‪TYPO3\CMS\Core\Resource\Event\BeforeFileCreatedEvent\getFileName
‪getFileName()
Definition: BeforeFileCreatedEvent.php:32
‪TYPO3\CMS\Form\Slot\FilePersistenceSlot\COMMAND_FILE_ADD
‪const COMMAND_FILE_ADD
Definition: FilePersistenceSlot.php:39
‪TYPO3\CMS\Form\Slot
Definition: FilePersistenceSlot.php:18
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager
Definition: FormPersistenceManager.php:60
‪TYPO3\CMS\Core\Resource\Event\BeforeFileContentsSetEvent\getContent
‪getContent()
Definition: BeforeFileContentsSetEvent.php:36
‪TYPO3\CMS\Core\Resource\Event\BeforeFileAddedEvent\getSourceFilePath
‪getSourceFilePath()
Definition: BeforeFileAddedEvent.php:49
‪TYPO3\CMS\Form\Slot\FilePersistenceSlot\assertFileName
‪assertFileName(string $command, string $combinedFileIdentifier, string $content=null)
Definition: FilePersistenceSlot.php:212
‪TYPO3\CMS\Core\Resource\Event\BeforeFileReplacedEvent\getFile
‪getFile()
Definition: BeforeFileReplacedEvent.php:30
‪TYPO3\CMS\Form\Slot\FilePersistenceSlot\getContentSignature
‪getContentSignature(string $content)
Definition: FilePersistenceSlot.php:54
‪TYPO3\CMS\Form\Slot\FilePersistenceSlot\isRecycleFolder
‪isRecycleFolder(FolderInterface $folder)
Definition: FilePersistenceSlot.php:297
‪TYPO3\CMS\Form\Slot\FilePersistenceSlot\defineInvocation
‪defineInvocation(string $command, bool $type=null)
Definition: FilePersistenceSlot.php:69
‪TYPO3\CMS\Core\Utility\GeneralUtility\hmac
‪static string hmac($input, $additionalSecret='')
Definition: GeneralUtility.php:473
‪TYPO3\CMS\Form\Slot\FilePersistenceSlot\$definedInvocations
‪array $definedInvocations
Definition: FilePersistenceSlot.php:48
‪TYPO3\CMS\Form\Slot\FilePersistenceSlot\onPreFileRename
‪onPreFileRename(BeforeFileRenamedEvent $event)
Definition: FilePersistenceSlot.php:143
‪TYPO3\CMS\Form\Slot\FilePersistenceSlot\allowInvocation
‪allowInvocation(string $command, string $combinedFileIdentifier, string $contentSignature=null)
Definition: FilePersistenceSlot.php:84
‪TYPO3\CMS\Core\Resource\Event\BeforeFileAddedEvent\getFileName
‪getFileName()
Definition: BeforeFileAddedEvent.php:39
‪TYPO3\CMS\Core\Resource\Event\BeforeFileRenamedEvent\getTargetFileName
‪getTargetFileName()
Definition: BeforeFileRenamedEvent.php:35
‪TYPO3\CMS\Form\Slot\FilePersistenceSlot
Definition: FilePersistenceSlot.php:38
‪TYPO3\CMS\Core\Resource\Event\BeforeFileMovedEvent\getFolder
‪getFolder()
Definition: BeforeFileMovedEvent.php:40
‪TYPO3\CMS\Core\Resource\Event\BeforeFileAddedEvent
Definition: BeforeFileAddedEvent.php:30
‪TYPO3\CMS\Form\Slot\FilePersistenceSlot\onPreFileCreate
‪onPreFileCreate(BeforeFileCreatedEvent $event)
Definition: FilePersistenceSlot.php:109
‪TYPO3\CMS\Form\Slot\FormDefinitionPersistenceException
Definition: FormDefinitionPersistenceException.php:23
‪TYPO3\CMS\Core\Resource\Event\BeforeFileContentsSetEvent\getFile
‪getFile()
Definition: BeforeFileContentsSetEvent.php:31
‪TYPO3\CMS\Form\Slot\FilePersistenceSlot\COMMAND_FILE_REPLACE
‪const COMMAND_FILE_REPLACE
Definition: FilePersistenceSlot.php:43
‪TYPO3\CMS\Core\SingletonInterface
Definition: SingletonInterface.php:22
‪TYPO3\CMS\Core\Resource\Event\BeforeFileMovedEvent\getFile
‪getFile()
Definition: BeforeFileMovedEvent.php:35
‪TYPO3\CMS\Core\Resource\Event\BeforeFileAddedEvent\getTargetFolder
‪getTargetFolder()
Definition: BeforeFileAddedEvent.php:54
‪TYPO3\CMS\Core\Resource\FolderInterface\ROLE_RECYCLER
‪const ROLE_RECYCLER
Definition: FolderInterface.php:29
‪TYPO3\CMS\Core\Resource\FolderInterface
Definition: FolderInterface.php:24
‪TYPO3\CMS\Form\Slot\FilePersistenceSlot\onPreFileReplace
‪onPreFileReplace(BeforeFileReplacedEvent $event)
Definition: FilePersistenceSlot.php:157
‪TYPO3\CMS\Form\Slot\FilePersistenceSlot\searchAllowedInvocation
‪searchAllowedInvocation(string $command, string $combinedFileIdentifier, string $contentSignature=null)
Definition: FilePersistenceSlot.php:262
‪TYPO3\CMS\Form\Slot\FilePersistenceSlot\COMMAND_FILE_RENAME
‪const COMMAND_FILE_RENAME
Definition: FilePersistenceSlot.php:42
‪TYPO3\CMS\Form\Slot\FilePersistenceSlot\buildCombinedIdentifier
‪buildCombinedIdentifier(FolderInterface $folder, string $fileName)
Definition: FilePersistenceSlot.php:279
‪TYPO3\CMS\Form\Slot\FilePersistenceSlot\$allowedInvocations
‪array $allowedInvocations
Definition: FilePersistenceSlot.php:52
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:51
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\FORM_DEFINITION_FILE_EXTENSION
‪const FORM_DEFINITION_FILE_EXTENSION
Definition: FormPersistenceManager.php:61
‪TYPO3\CMS\Core\Resource\Event\BeforeFileContentsSetEvent
Definition: BeforeFileContentsSetEvent.php:28
‪TYPO3\CMS\Webhooks\Message\$identifier
‪identifier readonly string $identifier
Definition: FileAddedMessage.php:37