Skip to content

Commit 22c95b8

Browse files
Merge pull request #59241 from nextcloud/backport/57374/stable33
[stable33] fix: allow renaming files with just update permissions
2 parents e681b9b + 745c999 commit 22c95b8

8 files changed

Lines changed: 83 additions & 25 deletions

File tree

apps/dav/lib/BulkUpload/BulkUploadPlugin.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public function httpPost(RequestInterface $request, ResponseInterface $response)
7676
'error' => false,
7777
'etag' => $node->getETag(),
7878
'fileid' => DavUtil::getDavFileId($node->getId()),
79-
'permissions' => DavUtil::getDavPermissions($node),
79+
'permissions' => DavUtil::getDavPermissions($node, $node->getParent()),
8080
];
8181
} catch (\Exception $e) {
8282
$this->logger->error($e->getMessage(), ['path' => $headers['x-file-path']]);

apps/dav/lib/Connector/Sabre/FilesPlugin.php

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -207,10 +207,19 @@ public function checkMove(string $source, string $target): void {
207207
// First check copyable (move only needs additional delete permission)
208208
$this->checkCopy($source, $target);
209209

210-
// The source needs to be deletable for moving
211-
$sourceNodeFileInfo = $sourceNode->getFileInfo();
212-
if (!$sourceNodeFileInfo->isDeletable()) {
213-
throw new Forbidden($source . ' cannot be deleted');
210+
[$sourceDir] = \Sabre\Uri\split($source);
211+
[$destinationDir, ] = \Sabre\Uri\split($target);
212+
213+
if ($sourceDir === $destinationDir) {
214+
if (!$sourceNode->canRename()) {
215+
throw new Forbidden($source . ' cannot be renamed');
216+
}
217+
} else {
218+
// The source needs to be deletable for moving
219+
$sourceNodeFileInfo = $sourceNode->getFileInfo();
220+
if (!$sourceNodeFileInfo->isDeletable()) {
221+
throw new Forbidden($source . ' cannot be deleted');
222+
}
214223
}
215224

216225
// The source is not allowed to be the parent of the target

apps/dav/lib/Connector/Sabre/Node.php

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use OCP\Server;
2424
use OCP\Share\Exceptions\ShareNotFound;
2525
use OCP\Share\IManager;
26+
use Sabre\DAV\Exception\Forbidden;
2627

2728
abstract class Node implements \Sabre\DAV\INode {
2829
/**
@@ -103,6 +104,13 @@ public function getPath() {
103104
return $this->path;
104105
}
105106

107+
/**
108+
* Check if this node can be renamed
109+
*/
110+
public function canRename(): bool {
111+
return DavUtil::canRename($this->node, $this->node->getParent());
112+
}
113+
106114
/**
107115
* Renames the node
108116
*
@@ -111,10 +119,8 @@ public function getPath() {
111119
* @throws \Sabre\DAV\Exception\Forbidden
112120
*/
113121
public function setName($name) {
114-
// rename is only allowed if the delete privilege is granted
115-
// (basically rename is a copy with delete of the original node)
116-
if (!($this->info->isDeletable() || ($this->info->getMountPoint() instanceof MoveableMount && $this->info->getInternalPath() === ''))) {
117-
throw new \Sabre\DAV\Exception\Forbidden();
122+
if (!$this->canRename()) {
123+
throw new Forbidden('');
118124
}
119125

120126
[$parentPath,] = \Sabre\Uri\split($this->path);
@@ -330,11 +336,8 @@ public function getNoteFromShare(?string $user): ?string {
330336
return null;
331337
}
332338

333-
/**
334-
* @return string
335-
*/
336-
public function getDavPermissions() {
337-
return DavUtil::getDavPermissions($this->info);
339+
public function getDavPermissions(): string {
340+
return DavUtil::getDavPermissions($this->info, $this->node->getParent());
338341
}
339342

340343
public function getOwner() {

apps/dav/tests/unit/Connector/Sabre/NodeTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public static function davPermissionsProvider(): array {
4242
[Constants::PERMISSION_ALL, 'file', true, Constants::PERMISSION_ALL, true, '' , 'SRMGDNVW'],
4343
[Constants::PERMISSION_ALL, 'file', true, Constants::PERMISSION_ALL - Constants::PERMISSION_UPDATE, true, '' , 'SRMGDNV'],
4444
[Constants::PERMISSION_ALL - Constants::PERMISSION_SHARE, 'file', true, Constants::PERMISSION_ALL, false, 'test', 'SGDNVW'],
45-
[Constants::PERMISSION_ALL - Constants::PERMISSION_UPDATE, 'file', false, Constants::PERMISSION_ALL, false, 'test', 'RGD'],
45+
[Constants::PERMISSION_ALL - Constants::PERMISSION_UPDATE, 'file', false, Constants::PERMISSION_ALL, false, 'test', 'RGDN'],
4646
[Constants::PERMISSION_ALL - Constants::PERMISSION_DELETE, 'file', false, Constants::PERMISSION_ALL, false, 'test', 'RGNVW'],
4747
[Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE, 'file', false, Constants::PERMISSION_ALL, false, 'test', 'RGDNVW'],
4848
[Constants::PERMISSION_ALL - Constants::PERMISSION_READ, 'file', false, Constants::PERMISSION_ALL, false, 'test', 'RDNVW'],

apps/files/src/actions/renameAction.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,15 @@ export const action: IFileAction = {
3737
: filesStore.getNode(dirname(node.source))
3838
const parentPermissions = parentNode?.permissions || Permission.NONE
3939

40-
// Only enable if the node have the delete permission
41-
// and if the parent folder allows creating files
42-
return Boolean(node.permissions & Permission.DELETE)
43-
&& Boolean(parentPermissions & Permission.CREATE)
40+
// Enable if the node has update permissions or the node
41+
// has delete permission and the parent folder allows creating files
42+
return (
43+
(
44+
Boolean(node.permissions & Permission.DELETE)
45+
&& Boolean(parentPermissions & Permission.CREATE)
46+
)
47+
|| Boolean(node.permissions & Permission.UPDATE)
48+
)
4449
},
4550

4651
async exec({ nodes }) {

dist/files-init.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/files-init.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/public/Files/DavUtil.php

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
namespace OCP\Files;
99

10+
use OC\Files\Mount\MoveableMount;
1011
use OCP\Constants;
1112
use OCP\Files\Mount\IMovableMount;
1213

@@ -33,7 +34,7 @@ public static function getDavFileId(int $id): string {
3334
*
3435
* @since 25.0.0
3536
*/
36-
public static function getDavPermissions(FileInfo $info): string {
37+
public static function getDavPermissions(FileInfo $info, ?FileInfo $parent = null): string {
3738
$permissions = $info->getPermissions();
3839
$p = '';
3940
if ($info->isShared()) {
@@ -51,8 +52,17 @@ public static function getDavPermissions(FileInfo $info): string {
5152
if ($permissions & Constants::PERMISSION_DELETE) {
5253
$p .= 'D';
5354
}
54-
if ($permissions & Constants::PERMISSION_UPDATE) {
55-
$p .= 'NV'; // Renameable, Movable
55+
if ($parent) {
56+
if (self::canRename($info, $parent)) {
57+
$p .= 'N'; // Renamable
58+
}
59+
if ($permissions & Constants::PERMISSION_UPDATE) {
60+
$p .= 'V'; // Movable
61+
}
62+
} else {
63+
if ($permissions & Constants::PERMISSION_UPDATE) {
64+
$p .= 'NV'; // Renamable, Movable
65+
}
5666
}
5767

5868
// since we always add update permissions for the root of movable mounts
@@ -76,4 +86,35 @@ public static function getDavPermissions(FileInfo $info): string {
7686
}
7787
return $p;
7888
}
89+
90+
/**
91+
* Check if a node has rename permissions.
92+
*
93+
* We allow renaming the file if either the file has update permissions
94+
* or the file can be deleted and the parent has create permissions
95+
*
96+
* @param FileInfo $info
97+
* @param FileInfo $parent
98+
* @return bool
99+
* @since 33.0.4
100+
*/
101+
public static function canRename(FileInfo $info, FileInfo $parent): bool {
102+
// the root of a movable mountpoint can be renamed regardless of the file permissions
103+
if ($info->getMountPoint() instanceof MoveableMount && $info->getInternalPath() === '') {
104+
return true;
105+
}
106+
107+
// we allow renaming the file if either the file has update permissions
108+
if ($info->isUpdateable()) {
109+
return true;
110+
}
111+
112+
// or the file can be deleted and the parent has create permissions
113+
if ($info->getStorage() instanceof IHomeStorage && $info->getInternalPath() === 'files') {
114+
// can't rename the users home
115+
return false;
116+
}
117+
118+
return $info->isDeletable() && $parent->isCreatable();
119+
}
79120
}

0 commit comments

Comments
 (0)