diff --git a/apps/dav/lib/Connector/Sabre/FilesPlugin.php b/apps/dav/lib/Connector/Sabre/FilesPlugin.php index 7b2f144dfa1d8..c91cb6139ec73 100644 --- a/apps/dav/lib/Connector/Sabre/FilesPlugin.php +++ b/apps/dav/lib/Connector/Sabre/FilesPlugin.php @@ -206,10 +206,19 @@ public function checkMove(string $source, string $target): void { // First check copyable (move only needs additional delete permission) $this->checkCopy($source, $target); - // The source needs to be deletable for moving - $sourceNodeFileInfo = $sourceNode->getFileInfo(); - if (!$sourceNodeFileInfo->isDeletable()) { - throw new Forbidden($source . ' cannot be deleted'); + [$sourceDir] = \Sabre\Uri\split($source); + [$destinationDir, ] = \Sabre\Uri\split($target); + + if ($sourceDir === $destinationDir) { + if (!$sourceNode->canRename()) { + throw new Forbidden($source . ' cannot be renamed'); + } + } else { + // The source needs to be deletable for moving + $sourceNodeFileInfo = $sourceNode->getFileInfo(); + if (!$sourceNodeFileInfo->isDeletable()) { + throw new Forbidden($source . ' cannot be deleted'); + } } // The source is not allowed to be the parent of the target diff --git a/apps/dav/lib/Connector/Sabre/Node.php b/apps/dav/lib/Connector/Sabre/Node.php index 14ac7063ace4e..d85ebba8b15b6 100644 --- a/apps/dav/lib/Connector/Sabre/Node.php +++ b/apps/dav/lib/Connector/Sabre/Node.php @@ -103,6 +103,33 @@ public function getPath() { return $this->path; } + /** + * Check if this node can be renamed + */ + public function canRename(): bool { + // the root of a movable mountpoint can be renamed regardless of the file permissions + if ($this->info->getMountPoint() instanceof MoveableMount && $this->info->getInternalPath() === '') { + return true; + } + + // we allow renaming the file if either the file has update permissions + if ($this->info->isUpdateable()) { + return true; + } + // or the file can be deleted and the parent has create permissions + [$parentPath,] = \Sabre\Uri\split($this->path); + if ($parentPath === null) { + // can't rename the users home + return false; + } + $parent = $this->fileView->getFileInfo($parentPath); + if ($this->info->isDeletable() && $parent->isCreatable()) { + return true; + } + + return false; + } + /** * Renames the node * @@ -111,10 +138,8 @@ public function getPath() { * @throws \Sabre\DAV\Exception\Forbidden */ public function setName($name) { - // rename is only allowed if the delete privilege is granted - // (basically rename is a copy with delete of the original node) - if (!($this->info->isDeletable() || ($this->info->getMountPoint() instanceof MoveableMount && $this->info->getInternalPath() === ''))) { - throw new \Sabre\DAV\Exception\Forbidden(); + if (!$this->canRename()) { + throw new \Sabre\DAV\Exception\Forbidden(''); } [$parentPath,] = \Sabre\Uri\split($this->path); diff --git a/apps/files/src/actions/renameAction.ts b/apps/files/src/actions/renameAction.ts index 7bf6b1e6b5e7c..7f9fe7eb20df2 100644 --- a/apps/files/src/actions/renameAction.ts +++ b/apps/files/src/actions/renameAction.ts @@ -34,10 +34,15 @@ export const action = new FileAction({ : filesStore.getNode(dirname(node.source)) const parentPermissions = parentNode?.permissions || Permission.NONE - // Only enable if the node have the delete permission - // and if the parent folder allows creating files - return Boolean(node.permissions & Permission.DELETE) - && Boolean(parentPermissions & Permission.CREATE) + // Enable if the node has update permissions or the node + // has delete permission and the parent folder allows creating files + return ( + ( + Boolean(node.permissions & Permission.DELETE) + && Boolean(parentPermissions & Permission.CREATE) + ) + || Boolean(node.permissions & Permission.UPDATE) + ) }, async exec({ nodes }) {