* the Filelist UI to check whether an action is allowed and whether action
* related UI elements should thus be shown (move icon, edit icon, etc.)
*
- * @param string $action action, can be read, write, delete
+ * @param string $action action, can be read, write, delete, editMeta
* @param FileInterface $file
* @return bool
*/
public function checkFileActionPermission($action, FileInterface $file)
{
$isProcessedFile = $file instanceof ProcessedFile;
- // Check 1: Does the user have permission to perform the action? e.g. "readFile"
+ // Check 1: Allow editing meta data of a file if it is in mount boundaries of a writable file mount
+ if ($action === 'editMeta') {
+ return !$isProcessedFile && $this->isWithinFileMountBoundaries($file, true);
+ }
+ // Check 2: Does the user have permission to perform the action? e.g. "readFile"
if (!$isProcessedFile && $this->checkUserActionPermission($action, 'File') === false) {
return false;
}
- // Check 2: No action allowed on files for denied file extensions
+ // Check 3: No action allowed on files for denied file extensions
if (!$this->checkFileExtensionPermission($file->getName())) {
return false;
}
if (in_array($action, ['add', 'write', 'move', 'rename', 'replace', 'delete'], true)) {
$isWriteCheck = true;
}
- // Check 3: Does the user have the right to perform the action?
+ // Check 4: Does the user have the right to perform the action?
// (= is he within the file mount borders)
if (!$isProcessedFile && !$this->isWithinFileMountBoundaries($file, $isWriteCheck)) {
return false;
$isMissing = true;
}
- // Check 4: Check the capabilities of the storage (and the driver)
+ // Check 5: Check the capabilities of the storage (and the driver)
if ($isWriteCheck && ($isMissing || !$this->isWritable())) {
return false;
}
- // Check 5: "File permissions" of the driver (only when file isn't marked as missing)
+ // Check 6: "File permissions" of the driver (only when file isn't marked as missing)
if (!$isMissing) {
$filePermissions = $this->driver->getPermissions($file->getIdentifier());
if ($isReadCheck && !$filePermissions['r']) {
$file = substr($file, strlen('sys_file_'));
}
$fileObject = ResourceFactory::getInstance()->getFileObject((int)$file);
- $accessAllowed = $fileObject->checkActionPermission('write');
+ $accessAllowed = $fileObject->checkActionPermission('editMeta');
}
return $accessAllowed;
}
--- /dev/null
+.. include:: ../../Includes.txt
+
+==========================================================================
+Important: #65636 - File meta data can now be edited on read only storages
+==========================================================================
+
+See :issue:`65636`
+
+Description
+===========
+
+Whether meta data editing of files is allowed or not, must not be bound to whether a file is
+physically writable in a storage, or whether the storage itself is set read only.
+
+Editing meta data should on the other hand be forbidden, when the file is within a read only
+file mount.
+
+Allowing meta data editing on read only storage
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+To allow to distinguish between read access to a file and write access to file meta data,
+a new file action permission `editMeta` is introduced, which is automatically checked and
+enforced when saving a meta data record.
+
+When having to check for editing meta data permission in userland code, it is recommended
+to use the new file action permission instead of the previously used permission `read`.
+
+.. index:: ext:core, NotScanned
/**
* @test
*/
+ public function metaDataEditIsNotAllowedWhenWhenNoFileMountsAreSet(): void
+ {
+ $this->prepareSubject([], false, null, [], ['isWithinProcessingFolder']);
+ $this->subject->setEvaluatePermissions(true);
+ $this->assertFalse($this->subject->checkFileActionPermission('editMeta', new File(['identifier' => '/foo/bar.jpg'], $this->subject)));
+ }
+
+ /**
+ * @test
+ */
+ public function metaDataEditIsAllowedWhenWhenInFileMount(): void
+ {
+ $driverMock = $this->getMockForAbstractClass(AbstractDriver::class, [], '', false);
+ $this->prepareSubject([], false, $driverMock, [], ['isWithinProcessingFolder']);
+
+ $fileStub = new File(['identifier' => '/foo/bar.jpg'], $this->subject);
+ $folderStub = new Folder($this->subject, '/foo/', 'foo');
+ $driverMock->expects($this->once())
+ ->method('isWithin')
+ ->with($folderStub->getIdentifier(), $fileStub->getIdentifier())
+ ->willReturn(true);
+
+ $this->subject->setEvaluatePermissions(true);
+ $fileMounts = [
+ '/foo/' => [
+ 'path' => '/foo/',
+ 'title' => 'Foo',
+ 'folder' => $folderStub,
+ ]
+ ];
+ $this->inject($this->subject, 'fileMounts', $fileMounts);
+ $this->assertTrue($this->subject->checkFileActionPermission('editMeta', $fileStub));
+ }
+
+ /**
+ * @test
+ */
+ public function metaDataEditIsNotAllowedWhenWhenInReadOnlyFileMount(): void
+ {
+ $driverMock = $this->getMockForAbstractClass(AbstractDriver::class, [], '', false);
+ $this->prepareSubject([], false, $driverMock, [], ['isWithinProcessingFolder']);
+
+ $fileStub = new File(['identifier' => '/foo/bar.jpg'], $this->subject);
+ $folderStub = new Folder($this->subject, '/foo/', 'foo');
+ $driverMock->expects($this->once())
+ ->method('isWithin')
+ ->with($folderStub->getIdentifier(), $fileStub->getIdentifier())
+ ->willReturn(true);
+
+ $this->subject->setEvaluatePermissions(true);
+ $fileMounts = [
+ '/foo/' => [
+ 'path' => '/foo/',
+ 'title' => 'Foo',
+ 'folder' => $folderStub,
+ 'read_only' => true,
+ ]
+ ];
+ $this->inject($this->subject, 'fileMounts', $fileMounts);
+ $this->assertFalse($this->subject->checkFileActionPermission('editMeta', $fileStub));
+ }
+
+ /**
+ * @test
+ */
public function getEvaluatePermissionsWhenSetFalse(): void
{
$this->prepareSubject([]);
public function linkWrapFile($code, File $fileObject)
{
try {
- if ($fileObject instanceof File && $fileObject->isIndexed() && $fileObject->checkActionPermission('write') && $this->getBackendUser()->check('tables_modify', 'sys_file_metadata')) {
+ if ($fileObject instanceof File && $fileObject->isIndexed() && $fileObject->checkActionPermission('editMeta') && $this->getBackendUser()->check('tables_modify', 'sys_file_metadata')) {
$metaData = $fileObject->_getMetaData();
$urlParameters = [
'edit' => [
$theData[$field] = $this->makeClip($fileObject);
break;
case '_LOCALIZATION_':
- if (!empty($systemLanguages) && $fileObject->isIndexed() && $fileObject->checkActionPermission('write') && $this->getBackendUser()->check('tables_modify', 'sys_file_metadata')) {
+ if (!empty($systemLanguages) && $fileObject->isIndexed() && $fileObject->checkActionPermission('editMeta') && $this->getBackendUser()->check('tables_modify', 'sys_file_metadata')) {
$metaDataRecord = $fileObject->_getMetaData();
$translations = $this->getTranslationsForMetaData($metaDataRecord);
$languageCode = '';
}
// Edit metadata of file
- if ($fileOrFolderObject instanceof File && $fileOrFolderObject->checkActionPermission('write') && $this->getBackendUser()->check('tables_modify', 'sys_file_metadata')) {
+ if ($fileOrFolderObject instanceof File && $fileOrFolderObject->checkActionPermission('editMeta') && $this->getBackendUser()->check('tables_modify', 'sys_file_metadata')) {
$metaData = $fileOrFolderObject->_getMetaData();
$urlParameters = [
'edit' => [