From 7c4dca2b76d72e5ce8d18e9320654d3ae27209ce Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 15 Apr 2026 21:50:24 +0200 Subject: [PATCH 1/4] fix: redact share token if share has more permissions than the current user Signed-off-by: Robin Appelman --- .../lib/Controller/ShareAPIController.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/files_sharing/lib/Controller/ShareAPIController.php b/apps/files_sharing/lib/Controller/ShareAPIController.php index bac188290a7ba..16c20af8cbb06 100644 --- a/apps/files_sharing/lib/Controller/ShareAPIController.php +++ b/apps/files_sharing/lib/Controller/ShareAPIController.php @@ -239,6 +239,10 @@ protected function formatShare(IShare $share, ?Node $recipientNode = null): arra $result['expiration'] = $expiration->format('Y-m-d H:i:s'); } + $currentUserPermissions = $recipientNode?->getPermissions() ?? Constants::PERMISSION_ALL; + $userHasEnoughPermissions = ($currentUserPermissions & $share->getPermissions()) === $share->getPermissions(); + $token = $userHasEnoughPermissions ? $share->getToken() : null; + if ($share->getShareType() === IShare::TYPE_USER) { $sharedWith = $this->userManager->get($share->getSharedWith()); $result['share_with'] = $share->getSharedWith(); @@ -264,6 +268,7 @@ protected function formatShare(IShare $share, ?Node $recipientNode = null): arra $result['share_with'] = $share->getSharedWith(); $result['share_with_displayname'] = $group !== null ? $group->getDisplayName() : $share->getSharedWith(); } elseif ($share->getShareType() === IShare::TYPE_LINK) { + $url = $token ? $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $token]) : null; // "share_with" and "share_with_displayname" for passwords of link // shares was deprecated in Nextcloud 15, use "password" instead. @@ -274,23 +279,23 @@ protected function formatShare(IShare $share, ?Node $recipientNode = null): arra $result['send_password_by_talk'] = $share->getSendPasswordByTalk(); - $result['token'] = $share->getToken(); - $result['url'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $share->getToken()]); + $result['token'] = $token; + $result['url'] = $url; } elseif ($share->getShareType() === IShare::TYPE_REMOTE) { $result['share_with'] = $share->getSharedWith(); $result['share_with_displayname'] = $this->getCachedFederatedDisplayName($share->getSharedWith()); - $result['token'] = $share->getToken(); + $result['token'] = $token; } elseif ($share->getShareType() === IShare::TYPE_REMOTE_GROUP) { $result['share_with'] = $share->getSharedWith(); $result['share_with_displayname'] = $this->getDisplayNameFromAddressBook($share->getSharedWith(), 'CLOUD'); - $result['token'] = $share->getToken(); + $result['token'] = $token; } elseif ($share->getShareType() === IShare::TYPE_EMAIL) { $result['share_with'] = $share->getSharedWith(); $result['password'] = $share->getPassword(); $result['password_expiration_time'] = $share->getPasswordExpirationTime() !== null ? $share->getPasswordExpirationTime()->format(\DateTime::ATOM) : null; $result['send_password_by_talk'] = $share->getSendPasswordByTalk(); $result['share_with_displayname'] = $this->getDisplayNameFromAddressBook($share->getSharedWith(), 'EMAIL'); - $result['token'] = $share->getToken(); + $result['token'] = $token; } elseif ($share->getShareType() === IShare::TYPE_CIRCLE) { // getSharedWith() returns either "name (type, owner)" or // "name (type, owner) [id]", depending on the Teams app version. From 274e14d33df8083339c2a0faa263af513bf1a7e6 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 15 Apr 2026 23:51:13 +0200 Subject: [PATCH 2/4] test: add test for link reshares with more permissions being redacted Signed-off-by: Robin Appelman --- .../sharing_features/sharing-v1-part2.feature | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/build/integration/sharing_features/sharing-v1-part2.feature b/build/integration/sharing_features/sharing-v1-part2.feature index 0c83975fc39b5..fb391878b9b45 100644 --- a/build/integration/sharing_features/sharing-v1-part2.feature +++ b/build/integration/sharing_features/sharing-v1-part2.feature @@ -23,6 +23,36 @@ Feature: sharing And User "user2" should be included in the response And User "user3" should not be included in the response +Scenario: getting all shares of a file with reshares with link share with less permissions + Given user "user0" exists + And user "user1" exists + When as "user0" creating a share with + | path | textfile0.txt | + | shareType | 0 | + | shareWith | user1 | + | permissions | 17 | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + When as "user0" creating a share with + | path | textfile0.txt | + | shareType | 3 | + | permissions | 19 | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And last link share can be downloaded + When As an "user1" + And sending "GET" to "/apps/files_sharing/api/v1/shares?reshares=true&path=textfile0 (2).txt" + Then the OCS status code should be "100" + And the HTTP status code should be "200" + And User "user1" should not be included in the response + Then the list of returned shares has 1 shares + And share 0 is returned with + | share_type | 3 | + | uid_owner | user0 | + | token | | + | url | | + | permissions | 19 | + Scenario: getting all shares of a file with a received share after revoking the resharing rights Given user "user0" exists And user "user1" exists From c5664e00fcc614edfd46255174b16d1eff6ae399 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 21 Apr 2026 18:18:50 +0200 Subject: [PATCH 3/4] chore: psalm fixes Signed-off-by: Robin Appelman --- apps/files_sharing/lib/Controller/ShareAPIController.php | 1 + apps/files_sharing/lib/ResponseDefinitions.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/files_sharing/lib/Controller/ShareAPIController.php b/apps/files_sharing/lib/Controller/ShareAPIController.php index 16c20af8cbb06..c8831009dcab9 100644 --- a/apps/files_sharing/lib/Controller/ShareAPIController.php +++ b/apps/files_sharing/lib/Controller/ShareAPIController.php @@ -78,6 +78,7 @@ class ShareAPIController extends OCSController { private ?Node $lockedNode = null; + /** @var array $trustedServerCache */ private array $trustedServerCache = []; /** diff --git a/apps/files_sharing/lib/ResponseDefinitions.php b/apps/files_sharing/lib/ResponseDefinitions.php index 1585645241759..f23b662260b5f 100644 --- a/apps/files_sharing/lib/ResponseDefinitions.php +++ b/apps/files_sharing/lib/ResponseDefinitions.php @@ -54,7 +54,7 @@ * token: ?string, * uid_file_owner: string, * uid_owner: string, - * url?: string, + * url?: string|null, * } * * @psalm-type Files_SharingDeletedShare = array{ From 06a7d9b4255567c0d086859da0052d57c273fced Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 21 Apr 2026 19:13:53 +0200 Subject: [PATCH 4/4] chore: update openapi Signed-off-by: Robin Appelman --- apps/files_sharing/openapi.json | 3 ++- openapi.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/files_sharing/openapi.json b/apps/files_sharing/openapi.json index ba15f60870e53..3ecfe58234afa 100644 --- a/apps/files_sharing/openapi.json +++ b/apps/files_sharing/openapi.json @@ -721,7 +721,8 @@ "type": "string" }, "url": { - "type": "string" + "type": "string", + "nullable": true } } }, diff --git a/openapi.json b/openapi.json index b1a8a93dd0818..94522cde73429 100644 --- a/openapi.json +++ b/openapi.json @@ -2852,7 +2852,8 @@ "type": "string" }, "url": { - "type": "string" + "type": "string", + "nullable": true } } },