Skip to content

Commit c722f97

Browse files
Merge pull request #50598 from nextcloud/backport/50270/stable26
[stable26] fix(files_sharing): Respect permissions passed when creating link shares
2 parents 2a8ec35 + 0f5d3cf commit c722f97

File tree

8 files changed

+404
-327
lines changed

8 files changed

+404
-327
lines changed

.github/workflows/cypress.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ jobs:
112112
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
113113

114114
- name: Upload snapshots
115-
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
115+
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
116116
if: always()
117117
with:
118118
name: snapshots_${{ matrix.containers }}
@@ -123,7 +123,7 @@ jobs:
123123
run: docker logs nextcloud-cypress-tests-${{ env.APP_NAME }} > nextcloud.log
124124

125125
- name: Upload NC logs
126-
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
126+
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
127127
if: failure() && matrix.containers != 'component'
128128
with:
129129
name: nc_logs_${{ matrix.containers }}
@@ -134,7 +134,7 @@ jobs:
134134
run: docker exec nextcloud-cypress-tests-server tar -cvjf - data > data.tar
135135

136136
- name: Upload data dir archive
137-
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
137+
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
138138
if: failure() && matrix.containers != 'component'
139139
with:
140140
name: nc_data_${{ matrix.containers }}

.github/workflows/performance.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ jobs:
7979

8080
- name: Upload profiles
8181
if: always()
82-
uses: actions/upload-artifact@v2
82+
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
8383
with:
8484
name: profiles
8585
path: |

apps/files_sharing/lib/Controller/ShareAPIController.php

Lines changed: 109 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
namespace OCA\Files_Sharing\Controller;
4646

4747
use Exception;
48-
use OC\Files\FileInfo;
4948
use OC\Files\Storage\Wrapper\Wrapper;
5049
use OCA\Files\Helper;
5150
use OCA\Files_Sharing\Exceptions\SharingRightsException;
@@ -60,6 +59,7 @@
6059
use OCP\AppFramework\OCSController;
6160
use OCP\AppFramework\QueryException;
6261
use OCP\Constants;
62+
use OCP\Files\File;
6363
use OCP\Files\Folder;
6464
use OCP\Files\InvalidPathException;
6565
use OCP\Files\IRootFolder;
@@ -549,20 +549,21 @@ public function deleteShare(string $id): DataResponse {
549549
/**
550550
* @NoAdminRequired
551551
*
552-
* @param string $path
553-
* @param int $permissions
554-
* @param int $shareType
555-
* @param string $shareWith
556-
* @param string $publicUpload
557-
* @param string $password
558-
* @param string $sendPasswordByTalk
559-
* @param string $expireDate
560-
* @param string $label
561-
* @param string $attributes
552+
* @param string|null $path Path of the share
553+
* @param int|null $permissions Permissions for the share
554+
* @param int $shareType Type of the share
555+
* @param string|null $shareWith The entity this should be shared with
556+
* @param 'true'|'false'|null $publicUpload If public uploading is allowed (deprecated)
557+
* @param string $password Password for the share
558+
* @param string|null $sendPasswordByTalk Send the password for the share over Talk
559+
* @param string $expireDate The expiry date of the share in the user's timezone at 00:00.
560+
* If $expireDate is not supplied or set to `null`, the system default will be used.
561+
* @param string $note Note for the share
562+
* @param string $label Label for the share (only used in link and email)
563+
* @param string|null $attributes Additional attributes for the share
562564
*
563565
* @return DataResponse
564-
* @throws NotFoundException
565-
* @throws OCSBadRequestException
566+
* @throws OCSBadRequestException Unknown share type
566567
* @throws OCSException
567568
* @throws OCSForbiddenException
568569
* @throws OCSNotFoundException
@@ -573,8 +574,8 @@ public function createShare(
573574
string $path = null,
574575
int $permissions = null,
575576
int $shareType = -1,
576-
string $shareWith = null,
577-
string $publicUpload = 'false',
577+
?string $shareWith = null,
578+
?string $publicUpload = null,
578579
string $password = '',
579580
string $sendPasswordByTalk = null,
580581
string $expireDate = '',
@@ -583,16 +584,7 @@ public function createShare(
583584
string $attributes = null
584585
): DataResponse {
585586
$share = $this->shareManager->newShare();
586-
587-
if ($permissions === null) {
588-
if ($shareType === IShare::TYPE_LINK
589-
|| $shareType === IShare::TYPE_EMAIL) {
590-
// to keep legacy default behaviour, we ignore the setting below for link shares
591-
$permissions = Constants::PERMISSION_READ;
592-
} else {
593-
$permissions = (int)$this->config->getAppValue('core', 'shareapi_default_permissions', (string)Constants::PERMISSION_ALL);
594-
}
595-
}
587+
$hasPublicUpload = $this->getLegacyPublicUpload($publicUpload, $permissions);
596588

597589
// Verify path
598590
if ($path === null) {
@@ -611,7 +603,7 @@ public function createShare(
611603
// combine all permissions to determine if the user can share this file
612604
$nodes = $userFolder->getById($node->getId());
613605
foreach ($nodes as $nodeById) {
614-
/** @var FileInfo $fileInfo */
606+
/** @var \OC\Files\FileInfo $fileInfo */
615607
$fileInfo = $node->getFileInfo();
616608
$fileInfo['permissions'] |= $nodeById->getPermissions();
617609
}
@@ -624,17 +616,23 @@ public function createShare(
624616
throw new OCSNotFoundException($this->l->t('Could not create share'));
625617
}
626618

627-
if ($permissions < 0 || $permissions > Constants::PERMISSION_ALL) {
628-
throw new OCSNotFoundException($this->l->t('Invalid permissions'));
619+
// Set permissions
620+
if ($shareType === IShare::TYPE_LINK || $shareType === IShare::TYPE_EMAIL) {
621+
$permissions = $this->getLinkSharePermissions($permissions, $hasPublicUpload);
622+
$this->validateLinkSharePermissions($node, $permissions, $hasPublicUpload);
623+
} else {
624+
// Use default permissions only for non-link shares to keep legacy behavior
625+
if ($permissions === null) {
626+
$permissions = (int)$this->config->getAppValue('core', 'shareapi_default_permissions', (string)Constants::PERMISSION_ALL);
627+
}
628+
// Non-link shares always require read permissions (link shares could be file drop)
629+
$permissions |= Constants::PERMISSION_READ;
629630
}
630631

631-
// Shares always require read permissions
632-
$permissions |= Constants::PERMISSION_READ;
633-
634-
if ($node instanceof \OCP\Files\File) {
635-
// Single file shares should never have delete or create permissions
636-
$permissions &= ~Constants::PERMISSION_DELETE;
637-
$permissions &= ~Constants::PERMISSION_CREATE;
632+
// For legacy reasons the API allows to pass PERMISSIONS_ALL even for single file shares (I look at you Talk)
633+
if ($node instanceof File) {
634+
// if this is a single file share we remove the DELETE and CREATE permissions
635+
$permissions = $permissions & ~(Constants::PERMISSION_DELETE | Constants::PERMISSION_CREATE);
638636
}
639637

640638
/**
@@ -678,28 +676,7 @@ public function createShare(
678676
throw new OCSNotFoundException($this->l->t('Public link sharing is disabled by the administrator'));
679677
}
680678

681-
if ($publicUpload === 'true') {
682-
// Check if public upload is allowed
683-
if (!$this->shareManager->shareApiLinkAllowPublicUpload()) {
684-
throw new OCSForbiddenException($this->l->t('Public upload disabled by the administrator'));
685-
}
686-
687-
// Public upload can only be set for folders
688-
if ($node instanceof \OCP\Files\File) {
689-
throw new OCSNotFoundException($this->l->t('Public upload is only possible for publicly shared folders'));
690-
}
691-
692-
$permissions = Constants::PERMISSION_READ |
693-
Constants::PERMISSION_CREATE |
694-
Constants::PERMISSION_UPDATE |
695-
Constants::PERMISSION_DELETE;
696-
}
697-
698-
// TODO: It might make sense to have a dedicated setting to allow/deny converting link shares into federated ones
699-
if ($this->shareManager->outgoingServer2ServerSharesAllowed()) {
700-
$permissions |= Constants::PERMISSION_SHARE;
701-
}
702-
679+
$this->validateLinkSharePermissions($node, $permissions, $hasPublicUpload);
703680
$share->setPermissions($permissions);
704681

705682
// Set password
@@ -980,6 +957,73 @@ public function getShares(
980957
return new DataResponse($shares);
981958
}
982959

960+
private function getLinkSharePermissions(?int $permissions, ?bool $legacyPublicUpload): int {
961+
$permissions = $permissions ?? Constants::PERMISSION_READ;
962+
963+
// Legacy option handling
964+
if ($legacyPublicUpload !== null) {
965+
$permissions = $legacyPublicUpload
966+
? (Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE)
967+
: Constants::PERMISSION_READ;
968+
}
969+
970+
// TODO: It might make sense to have a dedicated setting to allow/deny converting link shares into federated ones
971+
if ($this->hasPermission($permissions, Constants::PERMISSION_READ)
972+
&& $this->shareManager->outgoingServer2ServerSharesAllowed()) {
973+
$permissions |= Constants::PERMISSION_SHARE;
974+
}
975+
976+
return $permissions;
977+
}
978+
979+
/**
980+
* Helper to check for legacy "publicUpload" handling.
981+
* If the value is set to `true` or `false` then true or false are returned.
982+
* Otherwise null is returned to indicate that the option was not (or wrong) set.
983+
*
984+
* @param null|string $legacyPublicUpload The value of `publicUpload`
985+
*/
986+
private function getLegacyPublicUpload(?string $legacyPublicUpload, ?int $permissions): ?bool {
987+
if ($legacyPublicUpload === 'true') {
988+
return true;
989+
} elseif ($legacyPublicUpload === 'false') {
990+
return false;
991+
} elseif ($legacyPublicUpload === null && $permissions === null) {
992+
return false;
993+
}
994+
// Not set at all
995+
return null;
996+
}
997+
998+
/**
999+
* For link and email shares validate that only allowed combinations are set.
1000+
*
1001+
* @throw OCSBadRequestException If permission combination is invalid.
1002+
* @throw OCSForbiddenException If public upload was forbidden by the administrator.
1003+
*/
1004+
private function validateLinkSharePermissions(Node $node, int $permissions, ?bool $legacyPublicUpload): void {
1005+
if ($legacyPublicUpload && ($node instanceof File)) {
1006+
throw new OCSBadRequestException($this->l->t('Public upload is only possible for publicly shared folders'));
1007+
}
1008+
1009+
// We need at least READ or CREATE (file drop)
1010+
if (!$this->hasPermission($permissions, Constants::PERMISSION_READ)
1011+
&& !$this->hasPermission($permissions, Constants::PERMISSION_CREATE)) {
1012+
throw new OCSBadRequestException($this->l->t('Share must at least have READ or CREATE permissions'));
1013+
}
1014+
1015+
// UPDATE and DELETE require a READ permission
1016+
if (!$this->hasPermission($permissions, Constants::PERMISSION_READ)
1017+
&& ($this->hasPermission($permissions, Constants::PERMISSION_UPDATE) || $this->hasPermission($permissions, Constants::PERMISSION_DELETE))) {
1018+
throw new OCSBadRequestException($this->l->t('Share must have READ permission if UPDATE or DELETE permission is set'));
1019+
}
1020+
1021+
// Check if public uploading was disabled
1022+
if ($this->hasPermission($permissions, Constants::PERMISSION_CREATE)
1023+
&& !$this->shareManager->shareApiLinkAllowPublicUpload()) {
1024+
throw new OCSForbiddenException($this->l->t('Public upload disabled by the administrator'));
1025+
}
1026+
}
9831027

9841028
/**
9851029
* @param string $viewer
@@ -1165,7 +1209,6 @@ private function hasPermission(int $permissionsSet, int $permissionsToCheck): bo
11651209
return ($permissionsSet & $permissionsToCheck) === $permissionsToCheck;
11661210
}
11671211

1168-
11691212
/**
11701213
* @NoAdminRequired
11711214
*
@@ -1238,7 +1281,7 @@ public function updateShare(
12381281
$this->checkInheritedAttributes($share);
12391282

12401283
/**
1241-
* expirationdate, password and publicUpload only make sense for link shares
1284+
* expiration date, password and publicUpload only make sense for link shares
12421285
*/
12431286
if ($share->getShareType() === IShare::TYPE_LINK
12441287
|| $share->getShareType() === IShare::TYPE_EMAIL) {
@@ -1261,58 +1304,13 @@ public function updateShare(
12611304
$share->setHideDownload(false);
12621305
}
12631306

1264-
$newPermissions = null;
1265-
if ($publicUpload === 'true') {
1266-
$newPermissions = Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE;
1267-
} elseif ($publicUpload === 'false') {
1268-
$newPermissions = Constants::PERMISSION_READ;
1269-
}
1270-
1271-
if ($permissions !== null) {
1272-
$newPermissions = $permissions;
1273-
$newPermissions = $newPermissions & ~Constants::PERMISSION_SHARE;
1274-
}
1275-
1276-
if ($newPermissions !== null) {
1277-
if (!$this->hasPermission($newPermissions, Constants::PERMISSION_READ) && !$this->hasPermission($newPermissions, Constants::PERMISSION_CREATE)) {
1278-
throw new OCSBadRequestException($this->l->t('Share must at least have READ or CREATE permissions'));
1279-
}
1280-
1281-
if (!$this->hasPermission($newPermissions, Constants::PERMISSION_READ) && (
1282-
$this->hasPermission($newPermissions, Constants::PERMISSION_UPDATE) || $this->hasPermission($newPermissions, Constants::PERMISSION_DELETE)
1283-
)) {
1284-
throw new OCSBadRequestException($this->l->t('Share must have READ permission if UPDATE or DELETE permission is set'));
1285-
}
1286-
}
1287-
1288-
if (
1289-
// legacy
1290-
$newPermissions === (Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE) ||
1291-
// correct
1292-
$newPermissions === (Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE)
1293-
) {
1294-
if (!$this->shareManager->shareApiLinkAllowPublicUpload()) {
1295-
throw new OCSForbiddenException($this->l->t('Public upload disabled by the administrator'));
1296-
}
1297-
1298-
if (!($share->getNode() instanceof \OCP\Files\Folder)) {
1299-
throw new OCSBadRequestException($this->l->t('Public upload is only possible for publicly shared folders'));
1300-
}
1301-
1302-
// normalize to correct public upload permissions
1303-
if ($publicUpload === 'true') {
1304-
$newPermissions = Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE;
1305-
}
1306-
}
1307-
1308-
if ($newPermissions !== null) {
1309-
// TODO: It might make sense to have a dedicated setting to allow/deny converting link shares into federated ones
1310-
if (($newPermissions & Constants::PERMISSION_READ) && $this->shareManager->outgoingServer2ServerSharesAllowed()) {
1311-
$newPermissions |= Constants::PERMISSION_SHARE;
1312-
}
1313-
1314-
$share->setPermissions($newPermissions);
1315-
$permissions = $newPermissions;
1307+
// If either manual permissions are specified or publicUpload
1308+
// then we need to also update the permissions of the share
1309+
if ($permissions !== null || $publicUpload !== null) {
1310+
$hasPublicUpload = $this->getLegacyPublicUpload($publicUpload, $permissions);
1311+
$permissions = $this->getLinkSharePermissions($permissions ?? Constants::PERMISSION_READ, $hasPublicUpload);
1312+
$this->validateLinkSharePermissions($share->getNode(), $permissions, $hasPublicUpload);
1313+
$share->setPermissions($permissions);
13161314
}
13171315

13181316
if ($password === '') {

0 commit comments

Comments
 (0)