Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[stable29] fix(files_sharing): Respect permissions passed when creating link shares #50504

Merged
merged 3 commits into from
Feb 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 95 additions & 101 deletions apps/files_sharing/lib/Controller/ShareAPIController.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
namespace OCA\Files_Sharing\Controller;

use Exception;
use OC\Files\FileInfo;
use OC\Files\Storage\Wrapper\Wrapper;
use OCA\Files\Helper;
use OCA\Files_Sharing\Exceptions\SharingRightsException;
Expand All @@ -64,6 +63,7 @@
use OCP\AppFramework\OCSController;
use OCP\AppFramework\QueryException;
use OCP\Constants;
use OCP\Files\File;
use OCP\Files\Folder;
use OCP\Files\InvalidPathException;
use OCP\Files\IRootFolder;
Expand Down Expand Up @@ -566,8 +566,8 @@
* @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 @@ -590,7 +590,7 @@
?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 @@ -599,17 +599,7 @@
?string $attributes = null
): DataResponse {
$share = $this->shareManager->newShare();

if ($permissions === null) {
if ($shareType === IShare::TYPE_LINK
|| $shareType === IShare::TYPE_EMAIL) {

// 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);
}
}
$hasPublicUpload = $this->getLegacyPublicUpload($publicUpload);

// Verify path
if ($path === null) {
Expand All @@ -628,7 +618,7 @@
// 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 @@ -641,17 +631,23 @@
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'));
// 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);

Check notice

Code scanning / Psalm

DeprecatedMethod Note

The method OCP\IConfig::getAppValue has been marked as deprecated
}
// Non-link shares always require read permissions (link shares could be file drop)
$permissions |= Constants::PERMISSION_READ;
}

// Shares always require read permissions
$permissions |= Constants::PERMISSION_READ;

if ($node instanceof \OCP\Files\File) {
// Single file shares should never have delete or create permissions
$permissions &= ~Constants::PERMISSION_DELETE;
$permissions &= ~Constants::PERMISSION_CREATE;
// For legacy reasons the API allows to pass PERMISSIONS_ALL even for single file shares (I look at you Talk)
if ($node instanceof File) {
// 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 @@ -712,28 +708,7 @@
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 \OCP\Files\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 @@ -979,6 +954,71 @@
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 @@ -1161,7 +1201,6 @@
return ($permissionsSet & $permissionsToCheck) === $permissionsToCheck;
}


/**
* @NoAdminRequired
*
Expand Down Expand Up @@ -1236,7 +1275,7 @@
$this->checkInheritedAttributes($share);

/**
* 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 @@ -1260,58 +1299,13 @@
$share->setHideDownload(false);
}

$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 \OCP\Files\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 @@ -1762,10 +1762,14 @@
{
"name": "publicUpload",
"in": "query",
"description": "If public uploading is allowed",
"description": "If public uploading is allowed (deprecated)",
"schema": {
"type": "string",
"default": "false"
"nullable": true,
"enum": [
"true",
"false"
]
}
},
{
Expand Down
Loading
Loading