Skip to content

Commit

Permalink
detailed access list
Browse files Browse the repository at this point in the history
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
  • Loading branch information
ArtificialOwl authored and backportbot[bot] committed Aug 1, 2024
1 parent d40baae commit fae354d
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 18 deletions.
2 changes: 1 addition & 1 deletion lib/Db/CoreQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}


Expand Down
13 changes: 13 additions & 0 deletions lib/Db/ShareTokenRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

use OCA\Circles\Exceptions\ShareTokenNotFoundException;
use OCA\Circles\Model\ShareToken;
use OCP\DB\QueryBuilder\IQueryBuilder;

/**
* Class ShareTokenRequest
Expand Down Expand Up @@ -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);
}
}
10 changes: 7 additions & 3 deletions lib/Db/ShareWrapperRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -281,19 +281,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
Expand Down
11 changes: 11 additions & 0 deletions lib/Model/ShareWrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '';
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -474,6 +484,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))
Expand Down
9 changes: 9 additions & 0 deletions lib/Service/ShareTokenService.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
4 changes: 2 additions & 2 deletions lib/Service/ShareWrapperService.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand Down
125 changes: 119 additions & 6 deletions lib/ShareByCircleProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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);
}
Expand Down Expand Up @@ -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
*
Expand All @@ -660,10 +684,84 @@ public function getAccessList($nodes, $currentAccess): array {
$ids[] = $node->getId();
}

$users = $remote = $mails = [];
$shares = $this->shareWrapperService->getSharesByFileIds($ids, true);

foreach($shares as $share) {
if (!$currentAccess) {
return $this->getAccessListShort($ids);
}

$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()) {
Expand All @@ -673,9 +771,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:
Expand All @@ -694,6 +790,23 @@ public function getAccessList($nodes, $currentAccess): array {
];
}

/**
* @param array $list
* @param array<int, array<string, string>> $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.
Expand Down
17 changes: 11 additions & 6 deletions lib/Tools/Db/ExtendedQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

/**
Expand Down Expand Up @@ -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));
}


Expand Down

0 comments on commit fae354d

Please sign in to comment.