Skip to content
Closed
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
178 changes: 86 additions & 92 deletions apps/files_sharing/lib/Controller/ShareAPIController.php
Original file line number Diff line number Diff line change
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 ?string $publicUpload If public uploading is allowed
* @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 @@ -600,17 +600,6 @@
): 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);
}
}

// Verify path
if ($path === null) {
throw new OCSNotFoundException($this->l->t('Please specify a file or folder path'));
Expand Down Expand Up @@ -641,12 +630,21 @@
throw new OCSNotFoundException($this->l->t('Could not create share'));
}

// Validate the permissions
if ($permissions < 0 || $permissions > Constants::PERMISSION_ALL) {
throw new OCSNotFoundException($this->l->t('Invalid permissions'));
}

// Shares always require read permissions
$permissions |= Constants::PERMISSION_READ;
if ($shareType === IShare::TYPE_LINK || $shareType === IShare::TYPE_EMAIL) {
$permissions = $this->getLinkSharePermissions($permissions, $publicUpload);

Check notice

Code scanning / Psalm

PossiblyNullArgument Note

Argument 1 of OCA\Files_Sharing\Controller\ShareAPIController::getLinkSharePermissions cannot be null, possibly null value provided
} 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;
}

if ($node instanceof \OCP\Files\File) {
// Single file shares should never have delete or create permissions
Expand Down Expand Up @@ -712,28 +710,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);
$share->setPermissions($permissions);

// Set password
Expand Down Expand Up @@ -979,6 +956,70 @@
return new DataResponse($shares);
}

private function getLinkSharePermissions(int $permissions, ?string $legacyPublicUpload): int {
// Legacy handling
$hasPublicUpload = $this->getLegacyPublicUpload($legacyPublicUpload);
if ($hasPublicUpload !== null) {
$permissions = $hasPublicUpload
? (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;
}

private function validateLinkSharePermissions(Node $node, int $permissions): void {
// 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'));
}

// TODO: This setting confuses many users as "public upload" does not include file drops but only full upload shares
if (
// legacy
$permissions === (Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE) ||
// correct
$permissions === (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 (!($node instanceof \OCP\Files\Folder)) {
throw new OCSBadRequestException($this->l->t('Public upload is only possible for publicly shared folders'));
}
}
}

/**
* @param string $viewer
Expand Down Expand Up @@ -1161,7 +1202,6 @@
return ($permissionsSet & $permissionsToCheck) === $permissionsToCheck;
}


/**
* @NoAdminRequired
*
Expand Down Expand Up @@ -1236,7 +1276,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 +1300,12 @@
$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) {
$permissions = $this->getLinkSharePermissions($permissions ?? Constants::PERMISSION_READ, $publicUpload);
$this->validateLinkSharePermissions($share->getNode(), $permissions);
$share->setPermissions($permissions);
}

if ($password === '') {
Expand Down
58 changes: 58 additions & 0 deletions build/integration/sharing_features/sharing-v1-part4.feature
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,61 @@ Scenario: Receiving a share of a file without delete permission gives delete per
| path | /welcome (2).txt |
| permissions | 19 |
| item_permissions | 27 |

# This is a regression test as in the past creating a file drop required creating with permissions=5
# and then afterwards update the share to permissions=4
Scenario: Directly create link share with CREATE only permissions (file drop)
Given user "user0" exists
And As an "user0"
And user "user0" created a folder "/TMP"
When creating a share with
| path | TMP |
| shareType | 3 |
| permissions | 4 |
And Getting info of last share
Then Share fields of last share match with
| uid_file_owner | user0 |
| share_type | 3 |
| permissions | 4 |

Scenario: Directly create email share with CREATE only permissions (file drop)
Given user "user0" exists
And As an "user0"
And user "user0" created a folder "/TMP"
When creating a share with
| path | TMP |
| shareType | 4 |
| shareWith | j.doe@example.com |
| permissions | 4 |
And Getting info of last share
Then Share fields of last share match with
| uid_file_owner | user0 |
| share_type | 4 |
| permissions | 4 |

# This ensures the legacy behavior of sharing v1 is kept
Scenario: publicUpload overrides permissions
Given user "user0" exists
And As an "user0"
And parameter "outgoing_server2server_share_enabled" of app "files_sharing" is set to "no"
And user "user0" created a folder "/TMP"
When creating a share with
| path | TMP |
| shareType | 3 |
| permissions | 4 |
| publicUpload | true |
And Getting info of last share
Then Share fields of last share match with
| uid_file_owner | user0 |
| share_type | 3 |
| permissions | 15 |
When creating a share with
| path | TMP |
| shareType | 3 |
| permissions | 4 |
| publicUpload | false |
And Getting info of last share
Then Share fields of last share match with
| uid_file_owner | user0 |
| share_type | 3 |
| permissions | 1 |
Loading