Skip to content

Commit

Permalink
Merge pull request #50505 from nextcloud/backport/50270/stable31
Browse files Browse the repository at this point in the history
[stable31] fix(files_sharing): Respect permissions passed when creating link shares
  • Loading branch information
AndyScherzinger authored Jan 28, 2025
2 parents f752a01 + 124db61 commit a2470db
Show file tree
Hide file tree
Showing 7 changed files with 349 additions and 253 deletions.
195 changes: 94 additions & 101 deletions apps/files_sharing/lib/Controller/ShareAPIController.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
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;
Expand Down Expand Up @@ -536,8 +535,8 @@ public function deleteShare(string $id): DataResponse {
* @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.
Expand All @@ -562,7 +561,7 @@ public function createShare(
?int $permissions = null,
int $shareType = -1,
?string $shareWith = null,
string $publicUpload = 'false',
?string $publicUpload = null,
string $password = '',
?string $sendPasswordByTalk = null,
?string $expireDate = null,
Expand All @@ -571,18 +570,10 @@ public function createShare(
?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) {
Expand All @@ -601,7 +592,7 @@ public function createShare(
// 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();
}
Expand All @@ -614,19 +605,23 @@ public function createShare(
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);
}

/**
Expand Down Expand Up @@ -701,28 +696,7 @@ public function createShare(
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
Expand Down Expand Up @@ -974,6 +948,71 @@ public function getShares(
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
Expand Down Expand Up @@ -1154,7 +1193,6 @@ private function hasPermission(int $permissionsSet, int $permissionsToCheck): bo
return ($permissionsSet & $permissionsToCheck) === $permissionsToCheck;
}


/**
* Update a share
*
Expand Down Expand Up @@ -1242,7 +1280,7 @@ public function updateShare(
}

/**
* 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) {
Expand All @@ -1259,58 +1297,13 @@ public function updateShare(
$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 === '') {
Expand Down
8 changes: 6 additions & 2 deletions apps/files_sharing/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1780,8 +1780,12 @@
},
"publicUpload": {
"type": "string",
"default": "false",
"description": "If public uploading is allowed"
"nullable": true,
"enum": [
"true",
"false"
],
"description": "If public uploading is allowed (deprecated)"
},
"password": {
"type": "string",
Expand Down
Loading

0 comments on commit a2470db

Please sign in to comment.