From ab14091901fc50d6c9120b93d44932b43be25390 Mon Sep 17 00:00:00 2001 From: Maxence Lange Date: Thu, 1 Aug 2024 11:20:19 -0100 Subject: [PATCH] detailed access list Signed-off-by: Maxence Lange --- lib/Db/CoreQueryBuilder.php | 2 +- lib/Db/ShareTokenRequest.php | 13 +++ lib/Db/ShareWrapperRequest.php | 10 ++- lib/Model/ShareWrapper.php | 11 +++ lib/Service/ShareTokenService.php | 9 ++ lib/Service/ShareWrapperService.php | 4 +- lib/ShareByCircleProvider.php | 124 ++++++++++++++++++++++++-- lib/Tools/Db/ExtendedQueryBuilder.php | 17 ++-- 8 files changed, 172 insertions(+), 18 deletions(-) diff --git a/lib/Db/CoreQueryBuilder.php b/lib/Db/CoreQueryBuilder.php index f70c432a6..667ba2ab7 100644 --- a/lib/Db/CoreQueryBuilder.php +++ b/lib/Db/CoreQueryBuilder.php @@ -369,7 +369,7 @@ public function limitToFileSource(int $nodeId): void { * @param array $files */ public function limitToFileSourceArray(array $files): void { - $this->limitArray('file_source', $files); + $this->limitInArray('file_source', $files, type: IQueryBuilder::PARAM_INT_ARRAY); } diff --git a/lib/Db/ShareTokenRequest.php b/lib/Db/ShareTokenRequest.php index 6a543ce6e..b6161d355 100644 --- a/lib/Db/ShareTokenRequest.php +++ b/lib/Db/ShareTokenRequest.php @@ -33,6 +33,7 @@ use OCA\Circles\Exceptions\ShareTokenNotFoundException; use OCA\Circles\Model\ShareToken; +use OCP\DB\QueryBuilder\IQueryBuilder; /** * Class ShareTokenRequest @@ -116,4 +117,16 @@ public function removeTokens(string $singleId, string $circleId) { $qb->execute(); } + + /** + * @param array $shareIds + * + * @return ShareToken[] + */ + public function getTokensFromShares(array $shareIds): array { + $qb = $this->getTokenSelectSql(); + $qb->limitInArray('share_id', $shareIds, type: IQueryBuilder::PARAM_INT_ARRAY); + + return $this->getItemsFromRequest($qb); + } } diff --git a/lib/Db/ShareWrapperRequest.php b/lib/Db/ShareWrapperRequest.php index 50dc7f73f..91ad65a88 100644 --- a/lib/Db/ShareWrapperRequest.php +++ b/lib/Db/ShareWrapperRequest.php @@ -285,19 +285,23 @@ public function getSharesByFileId(int $fileId, bool $getData = false): array { * @return ShareWrapper[] * @throws RequestBuilderException */ - public function getSharesByFileIds(array $fileIds, bool $getData = false): array { + public function getSharesByFileIds(array $fileIds, bool $getData = false, bool $getChild = false): array { $qb = $this->getShareSelectSql(); $qb->limitToFileSourceArray($fileIds); if ($getData) { $qb->setOptions([CoreQueryBuilder::SHARE], ['getData' => $getData]); $qb->leftJoinCircle(CoreQueryBuilder::SHARE, null, 'share_with'); - $qb->limitNull('parent', false); } + if ($getChild) { + $qb->orderBy('parent', 'asc'); + } else { + $qb->limitNull('parent', false); + } return $this->getItemsFromRequest($qb); } - + /** * @param FederatedUser $federatedUser diff --git a/lib/Model/ShareWrapper.php b/lib/Model/ShareWrapper.php index 78b66d9e5..5a2a3b202 100644 --- a/lib/Model/ShareWrapper.php +++ b/lib/Model/ShareWrapper.php @@ -77,6 +77,7 @@ class ShareWrapper extends ManagedModel implements IDeserializable, IQueryRow, J private ?DateTime $expirationDate = null; private string $shareOwner = ''; private int $shareType = 0; + private int $parent = 0; private ?Circle $circle = null; private int $childId = 0; private string $childFileTarget = ''; @@ -252,6 +253,15 @@ public function getShareType(): int { return $this->shareType; } + public function setParent(int $parent): self { + $this->parent = $parent; + return $this; + } + + public function getParent(): int { + return $this->parent; + } + public function setCircle(Circle $circle): self { $this->circle = $circle; @@ -492,6 +502,7 @@ public function import(array $data): IDeserializable { $this->setId($this->get('id', $data)) ->setShareType($this->getInt('shareType', $data)) + ->setParent($data['parent'] ?? 0) ->setPermissions($this->getInt('permissions', $data)) ->setHideDownload($this->getBool('hideDownload', $data)) ->setItemType($this->get('itemType', $data)) diff --git a/lib/Service/ShareTokenService.php b/lib/Service/ShareTokenService.php index 44e1aaeba..2a3ac6b18 100644 --- a/lib/Service/ShareTokenService.php +++ b/lib/Service/ShareTokenService.php @@ -169,4 +169,13 @@ public function removeSharePassword(string $circleId): void { public function removeTokens(string $singleId, string $circleId) { $this->shareTokenRequest->removeTokens($singleId, $circleId); } + + /** + * @param array $shareIds + * + * @return ShareToken[] + */ + public function getTokensFromShares(array $shareIds): array { + return ($shareIds === []) ? [] : $this->shareTokenRequest->getTokensFromShares($shareIds); + } } diff --git a/lib/Service/ShareWrapperService.php b/lib/Service/ShareWrapperService.php index c0b6f7cf6..7373f5a15 100644 --- a/lib/Service/ShareWrapperService.php +++ b/lib/Service/ShareWrapperService.php @@ -200,8 +200,8 @@ public function getSharesByFileId(int $fileId, bool $getData = false): array { * @return ShareWrapper[] * @throws RequestBuilderException */ - public function getSharesByFileIds(array $fileIds, bool $getData = false): array { - return ($fileIds === []) ? [] : $this->shareWrapperRequest->getSharesByFileIds($fileIds, $getData); + public function getSharesByFileIds(array $fileIds, bool $getData = false, bool $getChild = false): array { + return ($fileIds === []) ? [] : $this->shareWrapperRequest->getSharesByFileIds($fileIds, $getData, $getChild); } /** diff --git a/lib/ShareByCircleProvider.php b/lib/ShareByCircleProvider.php index 6fbd4d572..c9ac5c727 100644 --- a/lib/ShareByCircleProvider.php +++ b/lib/ShareByCircleProvider.php @@ -66,6 +66,7 @@ use OCA\Circles\Service\EventService; use OCA\Circles\Service\FederatedEventService; use OCA\Circles\Service\FederatedUserService; +use OCA\Circles\Service\ShareTokenService; use OCA\Circles\Service\ShareWrapperService; use OCA\Circles\Tools\Traits\TArrayTools; use OCA\Circles\Tools\Traits\TNCLogger; @@ -108,6 +109,7 @@ class ShareByCircleProvider implements IShareProvider { private LoggerInterface $logger; private IURLGenerator $urlGenerator; private ShareWrapperService $shareWrapperService; + private ShareTokenService $shareTokenService; private FederatedUserService $federatedUserService; private FederatedEventService $federatedEventService; private CircleService $circleService; @@ -131,6 +133,7 @@ public function __construct( $this->federatedUserService = OC::$server->get(FederatedUserService::class); $this->federatedEventService = OC::$server->get(FederatedEventService::class); $this->shareWrapperService = OC::$server->get(ShareWrapperService::class); + $this->shareTokenService = OC::$server->get(ShareTokenService::class); $this->circleService = OC::$server->get(CircleService::class); $this->eventService = OC::$server->get(EventService::class); } @@ -649,6 +652,27 @@ public function userDeletedFromGroup($uid, $gid): void { /** + * if $currentAccess, returns long version of the access list: + * [ + * 'users' => [ + * 'user1' => ['node_id' => 42, 'node_path' => '/fileA'], + * 'user4' => ['node_id' => 32, 'node_path' => '/folder2'], + * 'user2' => ['node_id' => 23, 'node_path' => '/folder (1)'], + * ], + * 'remote' => [ + * 'user1@server1' => ['node_id' => 42, 'token' => 'SeCr3t'], + * 'user2@server2' => ['node_id' => 23, 'token' => 'FooBaR'], + * ], + * 'public' => bool, + * 'mail' => [ + * 'email1@maildomain1' => ['node_id' => 42, 'token' => 'aBcDeFg'], + * 'email2@maildomain2' => ['node_id' => 23, 'token' => 'hIjKlMn'], + * ] + * ] + * + * + * + * * @param Node[] $nodes * @param bool $currentAccess * @@ -660,10 +684,83 @@ public function getAccessList($nodes, $currentAccess): array { $ids[] = $node->getId(); } - $users = $remote = $mails = []; - $shares = $this->shareWrapperService->getSharesByFileIds($ids, true); + if (!$currentAccess) { + return $this->getAccessListShort($ids); + } - foreach ($shares as $share) { + $shareIds = $knownIds = $users = $remote = $mails = []; + foreach ($this->shareWrapperService->getSharesByFileIds($ids, true, true) as $share) { + $shareIds[] = $share->getId(); + $circle = $share->getCircle(); + foreach ($circle->getInheritedMembers() as $member) { + if ($share->getParent() > 0 && in_array($member->getSingleId(), $knownIds[$share->getFileSource()] ?? [])) { + continue; + } + $knownIds[$share->getFileSource()][] = $member->getSingleId(); + + switch ($member->getUserType()) { + case Member::TYPE_USER: + if ($member->isLocal()) { + $users[$member->getUserId()] = [ + 'node_id' => $share->getFileSource(), + 'node_path' => $share->getFileTarget() + ]; + } else { + // we only store temp value, as token is unknown at this point + $remote[$member->getUserid() . '@' . $member->getInstance()] = [ + 'node_id' => $share->getFileSource(), + 'shareId' => $share->getId(), + 'memberId' => $member->getId(), + ]; + } + break; + case Member::TYPE_MAIL: + // we only store temp value, as token is unknown at this point + $mails[$member->getUserId()] = [ + 'node_id' => $share->getFileSource(), + 'shareId' => $share->getId(), + 'memberId' => $member->getId(), + ]; + break; + } + } + } + + // list share tokens in an indexed array and update details for remote/mail entries with the correct token + $shareTokens = []; + foreach ($this->shareTokenService->getTokensFromShares(array_values(array_unique($shareIds))) as $shareToken) { + $shareTokens[$shareToken->getShareId()][$shareToken->getMemberId()] = $shareToken->getToken(); + } + + return [ + 'users' => $users, + 'remote' => $this->updateAccessListTokens($remote, $shareTokens), + 'email' => $this->updateAccessListTokens($mails, $shareTokens) + ]; + } + + /** + * returns short version of the access list: + * [ + * 'users' => ['user1', 'user2', 'user4'], + * 'remote' => bool, + * 'mail' => ['email1@maildomain1', 'email2@maildomain2'] + * ] + * + * @param array $ids + * + * @return array + * @throws FederatedItemException + * @throws RemoteInstanceException + * @throws RemoteNotFoundException + * @throws RemoteResourceNotFoundException + * @throws RequestBuilderException + * @throws UnknownRemoteException + */ + private function getAccessListShort(array $ids): array { + $users = $mails = []; + $remote = false; + foreach ($this->shareWrapperService->getSharesByFileIds($ids, true) as $share) { $circle = $share->getCircle(); foreach ($circle->getInheritedMembers() as $member) { switch ($member->getUserType()) { @@ -673,9 +770,7 @@ public function getAccessList($nodes, $currentAccess): array { $users[] = $member->getUserId(); } } else { - if (!in_array($member->getUserId(), $remote)) { - $remote[] = $member->getUserid() . '@' . $member->getInstance(); - } + $remote = true; } break; case Member::TYPE_MAIL: @@ -694,6 +789,23 @@ public function getAccessList($nodes, $currentAccess): array { ]; } + /** + * @param array $list + * @param array> $shareTokens + * + * @return array + */ + private function updateAccessListTokens(array $list, array $shareTokens): array { + $result = []; + foreach ($list as $id => $data) { + $result[$id] = [ + 'node_id' => $data['node_id'], + 'token' => $shareTokens[$data['shareId']][$data['memberId']] + ]; + } + + return $result; + } /** * We don't return a thing about children. diff --git a/lib/Tools/Db/ExtendedQueryBuilder.php b/lib/Tools/Db/ExtendedQueryBuilder.php index 31af5d890..82fa1067e 100644 --- a/lib/Tools/Db/ExtendedQueryBuilder.php +++ b/lib/Tools/Db/ExtendedQueryBuilder.php @@ -359,9 +359,10 @@ public function limitArray(string $field, array $value, string $alias = '', bool * @param string $field * @param array $value * @param string $alias + * @param int $type */ - public function limitInArray(string $field, array $value, string $alias = ''): void { - $this->andWhere($this->exprLimitInArray($field, $value, $alias)); + public function limitInArray(string $field, array $value, string $alias = '', int $type = IQueryBuilder::PARAM_STR_ARRAY): void { + $this->andWhere($this->exprLimitInArray($field, $value, $alias, $type)); } /** @@ -540,22 +541,26 @@ public function exprLimitArray( return $andX; } - /** * @param string $field * @param array $values * @param string $alias + * @param int $type * * @return string */ - public function exprLimitInArray(string $field, array $values, string $alias = ''): string { + public function exprLimitInArray( + string $field, + array $values, + string $alias = '', + int $type = IQueryBuilder::PARAM_STR_ARRAY + ): string { if ($this->getType() === DBALQueryBuilder::SELECT) { $field = (($alias === '') ? $this->getDefaultSelectAlias() : $alias) . '.' . $field; } $expr = $this->expr(); - - return $expr->in($field, $this->createNamedParameter($values, IQueryBuilder::PARAM_STR_ARRAY)); + return $expr->in($field, $this->createNamedParameter($values, $type)); }