diff --git a/lib/Helper/CircleHelper.php b/lib/Helper/CircleHelper.php new file mode 100644 index 000000000..6babfde55 --- /dev/null +++ b/lib/Helper/CircleHelper.php @@ -0,0 +1,89 @@ +logger = $logger; + $this->circlesEnabled = $appManager->isEnabledForUser('circles'); + if ($this->circlesEnabled) { + try { + $this->circlesManager = $circlesManager ?? Server::get(CirclesManager::class); + } catch (Throwable $e) { + $this->logger->warning('Failed to get CirclesManager: ' . $e->getMessage()); + $this->circlesManager = null; + $this->circlesEnabled = false; + } + } else { + $this->circlesManager = null; + } + } + + 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 []; + } + } +} diff --git a/lib/Helper/UserHelper.php b/lib/Helper/UserHelper.php index 3596af057..9b98197a2 100644 --- a/lib/Helper/UserHelper.php +++ b/lib/Helper/UserHelper.php @@ -14,17 +14,26 @@ use OCP\IUserManager; use Psr\Log\LoggerInterface; +/** + * @psalm-suppress UndefinedClass + * @psalm-suppress UndefinedDocblockClass + */ class UserHelper { private IUserManager $userManager; private LoggerInterface $logger; + private IGroupManager $groupManager; + /** + * @psalm-suppress UndefinedClass + */ 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); diff --git a/lib/Service/PermissionsService.php b/lib/Service/PermissionsService.php index cacf99abe..7372302bb 100644 --- a/lib/Service/PermissionsService.php +++ b/lib/Service/PermissionsService.php @@ -18,6 +18,7 @@ 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; @@ -25,7 +26,11 @@ use OCP\AppFramework\Db\MultipleObjectsReturnedException; use OCP\DB\Exception; use Psr\Log\LoggerInterface; +use Throwable; +/** + * @psalm-suppress UndefinedDocblockClass + */ class PermissionsService { private TableMapper $tableMapper; @@ -35,11 +40,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( @@ -50,6 +58,7 @@ public function __construct( ShareMapper $shareMapper, ContextMapper $contextMapper, UserHelper $userHelper, + CircleHelper $circleHelper, bool $isCLI ) { $this->tableMapper = $tableMapper; @@ -60,6 +69,7 @@ public function __construct( $this->userId = $userId; $this->isCli = $isCLI; $this->contextMapper = $contextMapper; + $this->circleHelper = $circleHelper; } @@ -420,6 +430,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 { @@ -436,16 +447,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()); @@ -520,7 +555,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, diff --git a/lib/Service/ShareService.php b/lib/Service/ShareService.php index 53f39ae83..033f2a1fc 100644 --- a/lib/Service/ShareService.php +++ b/lib/Service/ShareService.php @@ -24,9 +24,9 @@ use OCA\Tables\Errors\InternalError; use OCA\Tables\Errors\NotFoundError; use OCA\Tables\Errors\PermissionError; +use OCA\Tables\Helper\CircleHelper; use OCA\Tables\Helper\GroupHelper; use OCA\Tables\Helper\UserHelper; - use OCA\Tables\Model\Permissions; use OCA\Tables\ResponseDefinitions; use OCP\AppFramework\Db\DoesNotExistException; @@ -35,9 +35,11 @@ use OCP\DB\Exception; use OCP\IDBConnection; use Psr\Log\LoggerInterface; +use Throwable; /** * @psalm-import-type TablesShare from ResponseDefinitions + * @psalm-suppress UndefinedDocblockClass */ class ShareService extends SuperService { use TTransactional; @@ -51,7 +53,11 @@ class ShareService extends SuperService { protected UserHelper $userHelper; protected GroupHelper $groupHelper; + + protected CircleHelper $circleHelper; + private ContextNavigationMapper $contextNavigationMapper; + private IDBConnection $dbc; public function __construct( @@ -63,6 +69,7 @@ public function __construct( ViewMapper $viewMapper, UserHelper $userHelper, GroupHelper $groupHelper, + CircleHelper $circleHelper, ContextNavigationMapper $contextNavigationMapper, IDBConnection $dbc, ) { @@ -72,6 +79,7 @@ public function __construct( $this->viewMapper = $viewMapper; $this->userHelper = $userHelper; $this->groupHelper = $groupHelper; + $this->circleHelper = $circleHelper; $this->contextNavigationMapper = $contextNavigationMapper; $this->dbc = $dbc; } @@ -163,7 +171,17 @@ private function findElementsSharedWithMe(string $elementType = 'table', ?string $shares = $this->mapper->findAllSharesFor($elementType, $userGroup->getGid(), $userId, 'group'); $elementsSharedWithMe = array_merge($elementsSharedWithMe, $shares); } - } catch (Exception $e) { + + // get all views or tables that are shared with me by circle + if ($this->circleHelper->isCirclesEnabled()) { + $userCircles = $this->circleHelper->getUserCircles($userId); + + foreach ($userCircles as $userCircle) { + $shares = $this->mapper->findAllSharesFor($elementType, $userCircle->getSingleId(), $userId, 'circle'); + $elementsSharedWithMe = array_merge($elementsSharedWithMe, $shares); + } + } + } catch (Throwable $e) { throw new InternalError($e->getMessage()); } foreach ($elementsSharedWithMe as $share) { @@ -398,6 +416,16 @@ private function addReceiverDisplayName(Share $share):Share { $share->setReceiverDisplayName($this->userHelper->getUserDisplayName($share->getReceiver())); } elseif ($share->getReceiverType() === 'group') { $share->setReceiverDisplayName($this->groupHelper->getGroupDisplayName($share->getReceiver())); + } elseif ($share->getReceiverType() === 'circle') { + if ($this->circleHelper->isCirclesEnabled()) { + $share->setReceiverDisplayName($this->circleHelper->getCircleDisplayName($share->getReceiver(), $this->userId)); + } else { + $this->logger->info( + 'Could not get display name for receiver type {type}', + ['type' => $share->getReceiverType()] + ); + $share->setReceiverDisplayName($share->getReceiver()); + } } else { $this->logger->info('can not use receiver type to get display name'); $share->setReceiverDisplayName($share->getReceiver()); diff --git a/src/modules/sidebar/mixins/shareAPI.js b/src/modules/sidebar/mixins/shareAPI.js index a60e32665..9567254ad 100644 --- a/src/modules/sidebar/mixins/shareAPI.js +++ b/src/modules/sidebar/mixins/shareAPI.js @@ -6,8 +6,11 @@ import axios from '@nextcloud/axios' import { generateUrl } from '@nextcloud/router' import '@nextcloud/dialogs/style.css' import displayError from '../../../shared/utils/displayError.js' +import ShareTypes from '../../../shared/mixins/shareTypesMixin.js' export default { + mixins: [ShareTypes], + methods: { async getSharedWithFromBE() { try { @@ -32,7 +35,11 @@ export default { nodeType: this.isView ? 'view' : 'table', nodeId: this.activeElement.id, receiver: share.user, - receiverType: (share.isNoUser) ? 'group' : 'user', + receiverType: share.shareType === this.SHARE_TYPES.SHARE_TYPE_USER + ? 'user' + : share.shareType === this.SHARE_TYPES.SHARE_TYPE_GROUP + ? 'group' + : 'circle', permissionRead: true, permissionCreate: true, permissionUpdate: true, @@ -45,8 +52,11 @@ export default { displayError(e, t('tables', 'Could not create share.')) return false } - if (this.isView) await this.$store.dispatch('setViewHasShares', { viewId: this.activeElement.id, hasShares: true }) - else await this.$store.dispatch('setTableHasShares', { tableId: this.isView ? this.activeElement.tableId : this.activeElement.id, hasShares: true }) + if (this.isView) { + await this.$store.dispatch('setViewHasShares', { viewId: this.activeElement.id, hasShares: true }) + } else { + await this.$store.dispatch('setTableHasShares', { tableId: this.isView ? this.activeElement.tableId : this.activeElement.id, hasShares: true }) + } return true }, async removeShareFromBE(shareId) { diff --git a/src/modules/sidebar/partials/ShareForm.vue b/src/modules/sidebar/partials/ShareForm.vue index 2d5151125..170298ae4 100644 --- a/src/modules/sidebar/partials/ShareForm.vue +++ b/src/modules/sidebar/partials/ShareForm.vue @@ -6,9 +6,9 @@