aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files_sharing/lib/Controller
diff options
context:
space:
mode:
authorFerdinand Thiessen <opensource@fthiessen.de>2025-01-20 13:03:45 +0100
committerFerdinand Thiessen <opensource@fthiessen.de>2025-01-28 16:40:50 +0100
commit253f4345f19a3dcc1ec1feb4ba0b7035149f9ba1 (patch)
tree3e5d8637c2d0b1a056ba102f08c3e7d8babd3cf8 /apps/files_sharing/lib/Controller
parent2c773033bcf44268cad1e427fffdb9bbcc6a0327 (diff)
downloadnextcloud-server-253f4345f19a3dcc1ec1feb4ba0b7035149f9ba1.tar.gz
nextcloud-server-253f4345f19a3dcc1ec1feb4ba0b7035149f9ba1.zip
fix(files_sharing): Respect permissions passed when creating link shares
Given: User creates a link or email share with permissions=4 (create only = file drop). Problem: Currently the permissions are automatically extended to permissions = 5 (READ + CREATE). Work around was to create the share and directly update it. Solution: Respect what the user is requesting, create a file drop share. Co-authored-by: Ferdinand Thiessen <opensource@fthiessen.de> Co-authored-by: Côme Chilliet <91878298+come-nc@users.noreply.github.com> Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
Diffstat (limited to 'apps/files_sharing/lib/Controller')
-rw-r--r--apps/files_sharing/lib/Controller/ShareAPIController.php195
1 files changed, 94 insertions, 101 deletions
diff --git a/apps/files_sharing/lib/Controller/ShareAPIController.php b/apps/files_sharing/lib/Controller/ShareAPIController.php
index e7b4a8d6bb8..1fb62480049 100644
--- a/apps/files_sharing/lib/Controller/ShareAPIController.php
+++ b/apps/files_sharing/lib/Controller/ShareAPIController.php
@@ -10,7 +10,6 @@ declare(strict_types=1);
namespace OCA\Files_Sharing\Controller;
use Exception;
-use OC\Files\FileInfo;
use OC\Files\Storage\Wrapper\Wrapper;
use OCA\Circles\Api\v1\Circles;
use OCA\Files\Helper;
@@ -536,8 +535,8 @@ class ShareAPIController extends OCSController {
* @param string|null $path Path of the share
* @param int|null $permissions Permissions for the share
* @param int $shareType Type of the share
- * @param string|null $shareWith The entity this should be shared with
- * @param string $publicUpload If public uploading is allowed
+ * @param ?string $shareWith The entity this should be shared with
+ * @param 'true'|'false'|null $publicUpload If public uploading is allowed (deprecated)
* @param string $password Password for the share
* @param string|null $sendPasswordByTalk Send the password for the share over Talk
* @param ?string $expireDate The expiry date of the share in the user's timezone at 00:00.
@@ -562,7 +561,7 @@ class ShareAPIController extends OCSController {
?int $permissions = null,
int $shareType = -1,
?string $shareWith = null,
- string $publicUpload = 'false',
+ ?string $publicUpload = null,
string $password = '',
?string $sendPasswordByTalk = null,
?string $expireDate = null,
@@ -571,18 +570,10 @@ class ShareAPIController extends OCSController {
?string $attributes = null,
?string $sendMail = null,
): DataResponse {
- $share = $this->shareManager->newShare();
-
- if ($permissions === null) {
- if ($shareType === IShare::TYPE_LINK
- || $shareType === IShare::TYPE_EMAIL) {
+ assert($this->userId !== null);
- // to keep legacy default behaviour, we ignore the setting below for link shares
- $permissions = Constants::PERMISSION_READ;
- } else {
- $permissions = (int)$this->config->getAppValue('core', 'shareapi_default_permissions', (string)Constants::PERMISSION_ALL);
- }
- }
+ $share = $this->shareManager->newShare();
+ $hasPublicUpload = $this->getLegacyPublicUpload($publicUpload);
// Verify path
if ($path === null) {
@@ -601,7 +592,7 @@ class ShareAPIController extends OCSController {
// combine all permissions to determine if the user can share this file
$nodes = $userFolder->getById($node->getId());
foreach ($nodes as $nodeById) {
- /** @var FileInfo $fileInfo */
+ /** @var \OC\Files\FileInfo $fileInfo */
$fileInfo = $node->getFileInfo();
$fileInfo['permissions'] |= $nodeById->getPermissions();
}
@@ -614,19 +605,23 @@ class ShareAPIController extends OCSController {
throw new OCSNotFoundException($this->l->t('Could not create share'));
}
- if ($permissions < 0 || $permissions > Constants::PERMISSION_ALL) {
- throw new OCSNotFoundException($this->l->t('Invalid permissions'));
- }
-
- // Shares always require read permissions OR create permissions
- if (($permissions & Constants::PERMISSION_READ) === 0 && ($permissions & Constants::PERMISSION_CREATE) === 0) {
+ // Set permissions
+ if ($shareType === IShare::TYPE_LINK || $shareType === IShare::TYPE_EMAIL) {
+ $permissions = $this->getLinkSharePermissions($permissions, $hasPublicUpload);
+ $this->validateLinkSharePermissions($node, $permissions, $hasPublicUpload);
+ } else {
+ // Use default permissions only for non-link shares to keep legacy behavior
+ if ($permissions === null) {
+ $permissions = (int)$this->config->getAppValue('core', 'shareapi_default_permissions', (string)Constants::PERMISSION_ALL);
+ }
+ // Non-link shares always require read permissions (link shares could be file drop)
$permissions |= Constants::PERMISSION_READ;
}
+ // For legacy reasons the API allows to pass PERMISSIONS_ALL even for single file shares (I look at you Talk)
if ($node instanceof File) {
- // Single file shares should never have delete or create permissions
- $permissions &= ~Constants::PERMISSION_DELETE;
- $permissions &= ~Constants::PERMISSION_CREATE;
+ // if this is a single file share we remove the DELETE and CREATE permissions
+ $permissions = $permissions & ~(Constants::PERMISSION_DELETE | Constants::PERMISSION_CREATE);
}
/**
@@ -701,28 +696,7 @@ class ShareAPIController extends OCSController {
throw new OCSNotFoundException($this->l->t('Public link sharing is disabled by the administrator'));
}
- if ($publicUpload === 'true') {
- // Check if public upload is allowed
- if (!$this->shareManager->shareApiLinkAllowPublicUpload()) {
- throw new OCSForbiddenException($this->l->t('Public upload disabled by the administrator'));
- }
-
- // Public upload can only be set for folders
- if ($node instanceof File) {
- throw new OCSNotFoundException($this->l->t('Public upload is only possible for publicly shared folders'));
- }
-
- $permissions = Constants::PERMISSION_READ |
- Constants::PERMISSION_CREATE |
- Constants::PERMISSION_UPDATE |
- Constants::PERMISSION_DELETE;
- }
-
- // TODO: It might make sense to have a dedicated setting to allow/deny converting link shares into federated ones
- if ($this->shareManager->outgoingServer2ServerSharesAllowed()) {
- $permissions |= Constants::PERMISSION_SHARE;
- }
-
+ $this->validateLinkSharePermissions($node, $permissions, $hasPublicUpload);
$share->setPermissions($permissions);
// Set password
@@ -974,6 +948,71 @@ class ShareAPIController extends OCSController {
return new DataResponse($shares);
}
+ private function getLinkSharePermissions(?int $permissions, ?bool $legacyPublicUpload): int {
+ $permissions = $permissions ?? Constants::PERMISSION_READ;
+
+ // Legacy option handling
+ if ($legacyPublicUpload !== null) {
+ $permissions = $legacyPublicUpload
+ ? (Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE)
+ : Constants::PERMISSION_READ;
+ }
+
+ // TODO: It might make sense to have a dedicated setting to allow/deny converting link shares into federated ones
+ if ($this->hasPermission($permissions, Constants::PERMISSION_READ)
+ && $this->shareManager->outgoingServer2ServerSharesAllowed()) {
+ $permissions |= Constants::PERMISSION_SHARE;
+ }
+
+ return $permissions;
+ }
+
+ /**
+ * Helper to check for legacy "publicUpload" handling.
+ * If the value is set to `true` or `false` then true or false are returned.
+ * Otherwise null is returned to indicate that the option was not (or wrong) set.
+ *
+ * @param null|string $legacyPublicUpload The value of `publicUpload`
+ */
+ private function getLegacyPublicUpload(?string $legacyPublicUpload): ?bool {
+ if ($legacyPublicUpload === 'true') {
+ return true;
+ } elseif ($legacyPublicUpload === 'false') {
+ return false;
+ }
+ // Not set at all
+ return null;
+ }
+
+ /**
+ * For link and email shares validate that only allowed combinations are set.
+ *
+ * @throw OCSBadRequestException If permission combination is invalid.
+ * @throw OCSForbiddenException If public upload was forbidden by the administrator.
+ */
+ private function validateLinkSharePermissions(Node $node, int $permissions, ?bool $legacyPublicUpload): void {
+ if ($legacyPublicUpload && ($node instanceof File)) {
+ throw new OCSBadRequestException($this->l->t('Public upload is only possible for publicly shared folders'));
+ }
+
+ // We need at least READ or CREATE (file drop)
+ if (!$this->hasPermission($permissions, Constants::PERMISSION_READ)
+ && !$this->hasPermission($permissions, Constants::PERMISSION_CREATE)) {
+ throw new OCSBadRequestException($this->l->t('Share must at least have READ or CREATE permissions'));
+ }
+
+ // UPDATE and DELETE require a READ permission
+ if (!$this->hasPermission($permissions, Constants::PERMISSION_READ)
+ && ($this->hasPermission($permissions, Constants::PERMISSION_UPDATE) || $this->hasPermission($permissions, Constants::PERMISSION_DELETE))) {
+ throw new OCSBadRequestException($this->l->t('Share must have READ permission if UPDATE or DELETE permission is set'));
+ }
+
+ // Check if public uploading was disabled
+ if ($this->hasPermission($permissions, Constants::PERMISSION_CREATE)
+ && !$this->shareManager->shareApiLinkAllowPublicUpload()) {
+ throw new OCSForbiddenException($this->l->t('Public upload disabled by the administrator'));
+ }
+ }
/**
* @param string $viewer
@@ -1154,7 +1193,6 @@ class ShareAPIController extends OCSController {
return ($permissionsSet & $permissionsToCheck) === $permissionsToCheck;
}
-
/**
* Update a share
*
@@ -1242,7 +1280,7 @@ class ShareAPIController extends OCSController {
}
/**
- * expirationdate, password and publicUpload only make sense for link shares
+ * expiration date, password and publicUpload only make sense for link shares
*/
if ($share->getShareType() === IShare::TYPE_LINK
|| $share->getShareType() === IShare::TYPE_EMAIL) {
@@ -1259,58 +1297,13 @@ class ShareAPIController extends OCSController {
$share->setAttributes($attributes);
- $newPermissions = null;
- if ($publicUpload === 'true') {
- $newPermissions = Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE;
- } elseif ($publicUpload === 'false') {
- $newPermissions = Constants::PERMISSION_READ;
- }
-
- if ($permissions !== null) {
- $newPermissions = $permissions;
- $newPermissions = $newPermissions & ~Constants::PERMISSION_SHARE;
- }
-
- if ($newPermissions !== null) {
- if (!$this->hasPermission($newPermissions, Constants::PERMISSION_READ) && !$this->hasPermission($newPermissions, Constants::PERMISSION_CREATE)) {
- throw new OCSBadRequestException($this->l->t('Share must at least have READ or CREATE permissions'));
- }
-
- if (!$this->hasPermission($newPermissions, Constants::PERMISSION_READ) && (
- $this->hasPermission($newPermissions, Constants::PERMISSION_UPDATE) || $this->hasPermission($newPermissions, Constants::PERMISSION_DELETE)
- )) {
- throw new OCSBadRequestException($this->l->t('Share must have READ permission if UPDATE or DELETE permission is set'));
- }
- }
-
- if (
- // legacy
- $newPermissions === (Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE) ||
- // correct
- $newPermissions === (Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE)
- ) {
- if (!$this->shareManager->shareApiLinkAllowPublicUpload()) {
- throw new OCSForbiddenException($this->l->t('Public upload disabled by the administrator'));
- }
-
- if (!($share->getNode() instanceof Folder)) {
- throw new OCSBadRequestException($this->l->t('Public upload is only possible for publicly shared folders'));
- }
-
- // normalize to correct public upload permissions
- if ($publicUpload === 'true') {
- $newPermissions = Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE;
- }
- }
-
- if ($newPermissions !== null) {
- // TODO: It might make sense to have a dedicated setting to allow/deny converting link shares into federated ones
- if (($newPermissions & Constants::PERMISSION_READ) && $this->shareManager->outgoingServer2ServerSharesAllowed()) {
- $newPermissions |= Constants::PERMISSION_SHARE;
- }
-
- $share->setPermissions($newPermissions);
- $permissions = $newPermissions;
+ // If either manual permissions are specified or publicUpload
+ // then we need to also update the permissions of the share
+ if ($permissions !== null || $publicUpload !== null) {
+ $hasPublicUpload = $this->getLegacyPublicUpload($publicUpload);
+ $permissions = $this->getLinkSharePermissions($permissions ?? Constants::PERMISSION_READ, $hasPublicUpload);
+ $this->validateLinkSharePermissions($share->getNode(), $permissions, $hasPublicUpload);
+ $share->setPermissions($permissions);
}
if ($password === '') {