Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

share with teams #1465

Merged
merged 1 commit into from
Jan 9, 2025
Merged
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
11 changes: 9 additions & 2 deletions lib/Capabilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

namespace OCA\Tables;

use OCA\Tables\Helper\CircleHelper;
use OCP\App\IAppManager;
use OCP\Capabilities\ICapability;
use OCP\IConfig;
Expand All @@ -18,18 +19,23 @@
*/
class Capabilities implements ICapability {
private IAppManager $appManager;

private LoggerInterface $logger;

private IConfig $config;

public function __construct(IAppManager $appManager, LoggerInterface $logger, IConfig $config) {
private CircleHelper $circleHelper;

public function __construct(IAppManager $appManager, LoggerInterface $logger, IConfig $config, CircleHelper $circleHelper) {
$this->appManager = $appManager;
$this->logger = $logger;
$this->config = $config;
$this->circleHelper = $circleHelper;
}

/**
*
* @return array{tables: array{enabled: bool, version: string, apiVersions: string[], features: string[], column_types: string[]}}
* @return array{tables: array{enabled: bool, version: string, apiVersions: string[], features: string[], isCirclesEnabled: bool, column_types: string[]}}
*
* @inheritDoc
*/
Expand All @@ -52,6 +58,7 @@ public function getCapabilities(): array {
'favorite',
'archive',
],
'isCirclesEnabled' => $this->circleHelper->isCirclesEnabled(),
'column_types' => [
'text-line',
$textColumnVariant,
Expand Down
17 changes: 17 additions & 0 deletions lib/Constants/ShareReceiverType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Tables\Constants;

/**
* Constants for share receiver types
*/
class ShareReceiverType {
public const USER = 'user';
public const GROUP = 'group';
public const CIRCLE = 'circle';
}
7 changes: 6 additions & 1 deletion lib/Db/LegacyRowMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,12 @@ private function buildFilterByColumnType($qb, array $filter, string $filterId):
return null;
}

private function getInnerFilterExpressions($qb, $filterGroup, int $groupIndex): array {
/**
* @param (float|int|string)[][] $filterGroup
*
* @psalm-param list<array{columnId: int, operator: 'begins-with'|'contains'|'ends-with'|'is-empty'|'is-equal'|'is-greater-than'|'is-greater-than-or-equal'|'is-lower-than'|'is-lower-than-or-equal', value: float|int|string}> $filterGroup
*/
private function getInnerFilterExpressions(IQueryBuilder $qb, array $filterGroup, int $groupIndex): array {
$innerFilterExpressions = [];
foreach ($filterGroup as $index => $filter) {
$innerFilterExpressions[] = $this->buildFilterByColumnType($qb, $filter, $groupIndex.$index);
Expand Down
5 changes: 4 additions & 1 deletion lib/Db/RowCellMapperSuper.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ public function formatEntity(Column $column, RowCellSuper $cell) {
* Transform value from a filter rule to the actual query parameter used
* for constructing the view filter query
*/
public function filterValueToQueryParam(Column $column, $value) {
/**
* @param float|string $value
*/
public function filterValueToQueryParam(Column $column, string|float $value) {
return $value;
}

Expand Down
2 changes: 1 addition & 1 deletion lib/Db/Share.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class Share extends Entity implements JsonSerializable {

protected ?string $receiver = null;
protected ?string $receiverDisplayName = null;
protected ?string $receiverType = null; // user, group
protected ?string $receiverType = null; // user, group, circle
protected ?int $nodeId = null;
protected ?string $nodeType = null;
protected ?bool $permissionRead = null;
Expand Down
9 changes: 7 additions & 2 deletions lib/Db/ShareMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,16 +102,21 @@ public function findAllSharesFor(string $nodeType, string $receiver, string $use
* @param string $nodeType
* @param int $nodeId
* @param string $sender
* @param array<string> $excluded receiver types to exclude from results
* @return array
* @throws Exception
*/
public function findAllSharesForNode(string $nodeType, int $nodeId, string $sender): array {
// TODO filter for sender...
public function findAllSharesForNode(string $nodeType, int $nodeId, string $sender, array $excluded = []): array {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->table)
->andWhere($qb->expr()->eq('node_type', $qb->createNamedParameter($nodeType, IQueryBuilder::PARAM_STR)))
->andWhere($qb->expr()->eq('node_id', $qb->createNamedParameter($nodeId, IQueryBuilder::PARAM_INT)));

if (!empty($excluded)) {
$qb->andWhere($qb->expr()->notIn('receiver_type', $qb->createNamedParameter($excluded, IQueryBuilder::PARAM_STR_ARRAY)));
}

return $this->findEntities($qb);
}

Expand Down
85 changes: 85 additions & 0 deletions lib/Helper/CircleHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php

/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Tables\Helper;

use OCA\Circles\CirclesManager;
use OCA\Circles\Model\Member;
use OCA\Circles\Model\Probes\CircleProbe;
use OCP\App\IAppManager;
use OCP\Server;
use Psr\Log\LoggerInterface;
use Throwable;

/**
* @psalm-suppress UndefinedClass
*/
class CircleHelper {
private bool $circlesEnabled;
private ?CirclesManager $circlesManager;

/**
* @psalm-suppress UndefinedClass
*/
public function __construct(
private LoggerInterface $logger,
IAppManager $appManager
) {
$this->circlesEnabled = $appManager->isEnabledForUser('circles');
$this->circlesManager = null;

if ($this->circlesEnabled) {
try {
$this->circlesManager = Server::get(CirclesManager::class);
} catch (Throwable $e) {
$this->logger->warning('Failed to get CirclesManager: ' . $e->getMessage());
$this->circlesEnabled = false;
}
}
}

public function isCirclesEnabled(): bool {
return $this->circlesEnabled;
}

public function getCircleDisplayName(string $circleId, string $userId): string {
if (!$this->circlesEnabled) {
return $circleId;
}

try {
$federatedUser = $this->circlesManager->getFederatedUser($userId, Member::TYPE_USER);
$this->circlesManager->startSession($federatedUser);

$circle = $this->circlesManager->getCircle($circleId);
return $circle ? ($circle->getDisplayName() ?: $circleId) : $circleId;
} catch (Throwable $e) {
$this->logger->warning('Failed to get circle display name: ' . $e->getMessage(), [
'circleId' => $circleId,
'userId' => $userId
]);
return $circleId;
}
}

public function getUserCircles(string $userId): array {
if (!$this->circlesEnabled) {
return [];
}

try {
$federatedUser = $this->circlesManager->getFederatedUser($userId, Member::TYPE_USER);
$this->circlesManager->startSession($federatedUser);
$probe = new CircleProbe();
$probe->mustBeMember();
return $this->circlesManager->getCircles($probe);
} catch (Throwable $e) {
$this->logger->warning('Failed to get user circles: ' . $e->getMessage());
return [];
}
}
}
2 changes: 2 additions & 0 deletions lib/Helper/UserHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ class UserHelper {
private IUserManager $userManager;

private LoggerInterface $logger;

private IGroupManager $groupManager;

public function __construct(IUserManager $userManager, LoggerInterface $logger, IGroupManager $groupManager) {
$this->userManager = $userManager;
$this->logger = $logger;
$this->groupManager = $groupManager;
}

public function getUserDisplayName(string $userId): string {
try {
$user = $this->getUser($userId);
Expand Down
2 changes: 1 addition & 1 deletion lib/Middleware/PermissionMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public function beforeController(Controller $controller, string $methodName) {
$this->assertCanManageContext();
}

protected function assertPermission($controller, $methodName): void {
protected function assertPermission(Controller $controller, string $methodName): void {
$reflectionMethod = new \ReflectionMethod($controller, $methodName);
$permissionReqs = $reflectionMethod->getAttributes(RequirePermission::class);
if ($permissionReqs) {
Expand Down
40 changes: 36 additions & 4 deletions lib/Service/PermissionsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@
use OCA\Tables\Db\ViewMapper;
use OCA\Tables\Errors\InternalError;
use OCA\Tables\Errors\NotFoundError;
use OCA\Tables\Helper\CircleHelper;
use OCA\Tables\Helper\ConversionHelper;
use OCA\Tables\Helper\UserHelper;
use OCA\Tables\Model\Permissions;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\DB\Exception;
use Psr\Log\LoggerInterface;
use Throwable;

class PermissionsService {
private TableMapper $tableMapper;
Expand All @@ -35,11 +37,14 @@ class PermissionsService {

private UserHelper $userHelper;

private CircleHelper $circleHelper;

protected LoggerInterface $logger;

protected ?string $userId = null;

protected bool $isCli = false;

private ContextMapper $contextMapper;

public function __construct(
Expand All @@ -50,6 +55,7 @@ public function __construct(
ShareMapper $shareMapper,
ContextMapper $contextMapper,
UserHelper $userHelper,
CircleHelper $circleHelper,
bool $isCLI
) {
$this->tableMapper = $tableMapper;
Expand All @@ -60,6 +66,7 @@ public function __construct(
$this->userId = $userId;
$this->isCli = $isCLI;
$this->contextMapper = $contextMapper;
$this->circleHelper = $circleHelper;
}


Expand Down Expand Up @@ -420,6 +427,7 @@ public function canReadShare(Share $share, ?string $userId = null): bool {
* @param int $elementId
* @param 'table'|'view' $elementType
* @param string $userId
* @return Permissions
* @throws NotFoundError
*/
public function getSharedPermissionsIfSharedWithMe(int $elementId, string $elementType, string $userId): Permissions {
Expand All @@ -436,16 +444,40 @@ public function getSharedPermissionsIfSharedWithMe(int $elementId, string $eleme
$this->logger->warning('Exception occurred: '.$e->getMessage().' Permission denied.');
return new Permissions();
}
$additionalShares = [];
$groupShares = [];
foreach ($userGroups as $userGroup) {
try {
$additionalShares[] = $this->shareMapper->findAllSharesForNodeFor($elementType, $elementId, $userGroup->getGid(), 'group');
$groupShares[] = $this->shareMapper->findAllSharesForNodeFor($elementType, $elementId, $userGroup->getGid(), 'group');
} catch (Exception $e) {
$this->logger->warning('Exception occurred: '.$e->getMessage().' Permission denied.');
return new Permissions();
}
}
$shares = array_merge($shares, ...$additionalShares);

$shares = array_merge($shares, ...$groupShares);

if ($this->circleHelper->isCirclesEnabled()) {
$circleShares = [];

try {
$userCircles = $this->circleHelper->getUserCircles($userId);
} catch (Throwable $e) {
$this->logger->warning('Exception occurred: ' . $e->getMessage() . ' Permission denied.');
return new Permissions();
}

foreach ($userCircles as $userCircle) {
try {
$circleShares[] = $this->shareMapper->findAllSharesForNodeFor($elementType, $elementId, $userCircle->getSingleId(), 'circle');
} catch (Exception $e) {
$this->logger->warning('Exception occurred: ' . $e->getMessage() . ' Permission denied.');
return new Permissions();
}
}

$shares = array_merge($shares, ...$circleShares);
}

if (count($shares) > 0) {
$read = array_reduce($shares, function ($carry, $share) {
return $carry || ($share->getPermissionRead());
Expand Down Expand Up @@ -520,7 +552,7 @@ private function hasPermission(int $existingPermissions, string $permissionName)
$constantName = 'PERMISSION_' . strtoupper($permissionName);
try {
$permissionBit = constant(Application::class . "::$constantName");
} catch (\Throwable $t) {
} catch (Throwable $t) {
$this->logger->error('Unexpected permission string {permission}', [
'app' => Application::APP_ID,
'permission' => $permissionName,
Expand Down
Loading
Loading