Skip to content

Commit 5480cda

Browse files
Merge pull request #63036 from nextcloud/unified-sharing-native-owner
allow every with direct access to a shared file to modify all shares for the file
2 parents fa8cc98 + 27c549c commit 5480cda

7 files changed

Lines changed: 469 additions & 216 deletions

File tree

apps/files/lib/Sharing/Source/NodeShareSourceType.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424
use OCP\Files\Cache\IFileAccess;
2525
use OCP\Files\Events\Node\NodeDeletedEvent;
2626
use OCP\Files\IRootFolder;
27+
use OCP\Files\Mount\IShareOwnerlessMount;
2728
use OCP\Files\Node;
29+
use OCP\Files\Storage\ISharedStorage;
2830
use OCP\IDBConnection;
2931
use OCP\Interaction\InteractionResource;
3032
use OCP\Interaction\Resources\NodeResource;
@@ -95,4 +97,18 @@ public function handle(Event $event): void {
9597
throw $exception;
9698
}
9799
}
100+
101+
#[\Override]
102+
public function userHasDirectSharingAccessToSource(IUser $user, string $source): bool {
103+
// TODO: cache nodes by id?
104+
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
105+
$nodes = $userFolder->getById((int)$source);
106+
foreach ($nodes as $node) {
107+
if (!$node->getStorage() instanceof ISharedStorage && $node->isShareable() && $node->getMountPoint() instanceof IShareOwnerlessMount) {
108+
return true;
109+
}
110+
}
111+
112+
return false;
113+
}
98114
}

lib/private/Sharing/SharingBackend.php

Lines changed: 75 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ public function updateShareRecipientSecret(string $id, ShareRecipient $recipient
358358
}
359359

360360
#[\Override]
361-
public function createShareProperty(string $id, ShareProperty $property): void {
361+
public function createShareProperty(string $id, ShareProperty $property): ?string {
362362
$value = $property->value;
363363

364364
$propertyType = $this->registry->getPropertyTypes()[$property->class];
@@ -377,6 +377,8 @@ public function createShareProperty(string $id, ShareProperty $property): void {
377377
'property_value' => $qb->createNamedParameter($value),
378378
])
379379
->executeStatement();
380+
381+
return $value;
380382
} catch (\OCP\DB\Exception $exception) {
381383
if ($exception->getReason() === \OCP\DB\Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) {
382384
throw new RuntimeException('The property already exists: ' . $property->class, $exception->getCode(), $exception);
@@ -387,7 +389,7 @@ public function createShareProperty(string $id, ShareProperty $property): void {
387389
}
388390

389391
#[\Override]
390-
public function updateShareProperty(string $id, ShareProperty $property): void {
392+
public function updateShareProperty(string $id, ShareProperty $property): ?string {
391393
$value = $property->value;
392394

393395
$propertyType = $this->registry->getPropertyTypes()[$property->class];
@@ -425,6 +427,8 @@ public function updateShareProperty(string $id, ShareProperty $property): void {
425427
if ($rowCount === 0) {
426428
throw new ShareNotFoundException();
427429
}
430+
431+
return $value;
428432
}
429433

430434
#[\Override]
@@ -588,9 +592,22 @@ private function hideDisabledUserShares(): bool {
588592
* @return list<Share>
589593
*/
590594
private function list(
591-
ShareAccessContext $accessContext, ?string $filterShareID, ?string $filterSourceTypeClass, ?string $filterSourceTypeValue, ?string $lastShareID,
595+
ShareAccessContext $accessContext,
596+
?string $filterShareID,
597+
?string $filterSourceTypeClass,
598+
?string $filterSourceTypeValue,
599+
?string $lastShareID,
592600
?int $limit,
593601
): array {
602+
if ($filterSourceTypeClass) {
603+
$filterSourceType = $this->registry->getSourceTypes()[$filterSourceTypeClass] ?? null;
604+
if ($filterSourceType === null) {
605+
throw new RuntimeException('The source type is not registered: ' . $filterSourceTypeClass);
606+
}
607+
} else {
608+
$filterSourceType = null;
609+
}
610+
594611
/** @var array<class-string<IShareRecipientType>, list<string>> $recipientTypeValues */
595612
$recipientTypeValues = [];
596613

@@ -601,7 +618,11 @@ private function list(
601618
} else {
602619
if ($accessContext->currentUser instanceof IUser) {
603620
$qb = $this->connection->getQueryBuilder();
604-
$qb->where($qb->expr()->eq('s.owner_user_id', $qb->createNamedParameter($accessContext->currentUser->getUID())));
621+
// if we're filtering by source or id, we need to also check for non-owned shares
622+
if ($filterSourceTypeValue === null && $filterShareID === null) {
623+
$qb->where($qb->expr()->eq('s.owner_user_id', $qb->createNamedParameter($accessContext->currentUser->getUID())));
624+
}
625+
605626
$queries[] = $qb;
606627
}
607628

@@ -613,6 +634,7 @@ private function list(
613634
}
614635

615636
// Do not add a query if no recipients matched, otherwise all shares will be returned.
637+
// If the user has "direct" access, we already get all the shares, so no need to run an extra query for recipients
616638
if ($recipientTypeValues !== []) {
617639
$qb = $this->connection->getQueryBuilder();
618640
$qb->innerJoin(
@@ -672,7 +694,7 @@ private function list(
672694
$qb->andWhere($qb->expr()->eq('s.id', $qb->createNamedParameter($filterShareID)));
673695
}
674696

675-
if ($filterSourceTypeClass !== null) {
697+
if ($filterSourceType !== null && $filterSourceTypeClass !== null) {
676698
$sourceTypeFilters = [
677699
$qb->expr()->eq('s.id', 'ss.share_id'),
678700
$qb->expr()->eq(
@@ -867,8 +889,10 @@ private function list(
867889

868890
// Some recipients might have been removed if the initiator was disabled, so check again if this share can be accessed by the current user as a recipient.
869891
// This logic is a bit duplicated with the SQL logic that selects shares based on the secret and the recipient type values, but neither can be removed.
892+
/** @var array<string, bool> $hasRecipientAccess */
893+
$hasRecipientAccess = [];
870894
if (!$accessContext->overrideChecks) {
871-
foreach ($shares as $id => &$share) {
895+
foreach ($shares as &$share) {
872896
if ($share['owner']->isCurrentUser($accessContext)) {
873897
continue;
874898
}
@@ -904,9 +928,7 @@ private function list(
904928

905929
unset($recipient);
906930

907-
if (!$isAnyMatchingRecipient) {
908-
unset($shares[$id]);
909-
}
931+
$hasRecipientAccess[$share['id']] = $isAnyMatchingRecipient && $share['state'] === ShareState::Active;
910932
}
911933

912934
unset($share);
@@ -1016,6 +1038,41 @@ private function list(
10161038
$share['permissions'],
10171039
), $shares);
10181040

1041+
// when listing shares for a source, we also return any non-owned share if the user has "direct" access to the source
1042+
// but we do need to validate that the user has "direct" access to *all* of the sources in the share, not just one
1043+
$hasSourceAccess = [];
1044+
if (!$accessContext->overrideChecks && $accessContext->currentUser instanceof IUser) {
1045+
foreach ($shares as $share) {
1046+
if ($share->owner->isCurrentUser($accessContext)) {
1047+
continue;
1048+
}
1049+
1050+
if ($hasRecipientAccess[$share->id]) {
1051+
continue;
1052+
}
1053+
1054+
if ($share->sources === []) {
1055+
$hasSourceAccess[$share->id] = false;
1056+
continue;
1057+
}
1058+
1059+
$hasSourceAccess[$share->id] = true;
1060+
foreach ($share->sources as $source) {
1061+
$sourceType = $this->registry->getSourceTypes()[$source->class];
1062+
if (!$sourceType->userHasDirectSharingAccessToSource($accessContext->currentUser, $source->value)) {
1063+
$hasSourceAccess[$share->id] = false;
1064+
}
1065+
}
1066+
}
1067+
}
1068+
1069+
if (!$accessContext->overrideChecks) {
1070+
$shares = array_filter(
1071+
$shares,
1072+
fn (Share $share): bool => $share->owner->isCurrentUser($accessContext) || $hasRecipientAccess[$share->id] || $hasSourceAccess[$share->id]
1073+
);
1074+
}
1075+
10191076
if (!$accessContext->overrideChecks) {
10201077
$filterPropertyTypes = array_filter(
10211078
$registryPropertyTypes, static fn (ISharePropertyType $propertyType): bool => $propertyType instanceof ISharePropertyTypeFilter
@@ -1102,7 +1159,15 @@ public function createSharePropertyDefaultValue(Share $share, string $propertyTy
11021159

11031160
$property = new ShareProperty($propertyTypeClass, $propertyType->getDefaultValue($share));
11041161

1105-
$this->createShareProperty($share->id, $property);
1162+
$value = $this->createShareProperty($share->id, $property);
1163+
if ($propertyType instanceof ISharePropertyTypeModifyValue) {
1164+
$value = $propertyType->modifyValueOnLoad($value);
1165+
}
1166+
1167+
$property = new ShareProperty(
1168+
$property->class,
1169+
$value,
1170+
);
11061171

11071172
$properties = $share->properties;
11081173
$properties[$propertyTypeClass] = $property;

0 commit comments

Comments
 (0)