From b531d7b5412fe1e96559f58953d27134a3b65076 Mon Sep 17 00:00:00 2001 From: fenn-cs Date: Thu, 22 Aug 2024 23:25:42 +0100 Subject: [PATCH] fix(Federation): Show some icon for federated users on shares Signed-off-by: fenn-cs --- apps/dav/lib/Connector/Sabre/FilesPlugin.php | 6 ++++ apps/files/src/init.ts | 1 + .../src/actions/sharingStatusAction.ts | 30 +++++++------------ apps/files_sharing/src/utils/AccountIcon.ts | 17 +++++++++++ lib/private/User/LazyUser.php | 17 +++++++++++ lib/private/User/User.php | 6 ++++ lib/public/IUser.php | 8 +++++ 7 files changed, 65 insertions(+), 20 deletions(-) create mode 100644 apps/files_sharing/src/utils/AccountIcon.ts diff --git a/apps/dav/lib/Connector/Sabre/FilesPlugin.php b/apps/dav/lib/Connector/Sabre/FilesPlugin.php index 4fed12e6569df..8a50eb12d2f6d 100644 --- a/apps/dav/lib/Connector/Sabre/FilesPlugin.php +++ b/apps/dav/lib/Connector/Sabre/FilesPlugin.php @@ -81,6 +81,7 @@ class FilesPlugin extends ServerPlugin { public const MOUNT_TYPE_PROPERTYNAME = '{http://nextcloud.org/ns}mount-type'; public const MOUNT_ROOT_PROPERTYNAME = '{http://nextcloud.org/ns}is-mount-root'; public const IS_ENCRYPTED_PROPERTYNAME = '{http://nextcloud.org/ns}is-encrypted'; + public const IS_FEDERATED_PROPERTYNAME = '{http://nextcloud.org/ns}is-federated'; public const METADATA_ETAG_PROPERTYNAME = '{http://nextcloud.org/ns}metadata_etag'; public const UPLOAD_TIME_PROPERTYNAME = '{http://nextcloud.org/ns}upload_time'; public const CREATION_TIME_PROPERTYNAME = '{http://nextcloud.org/ns}creation_time'; @@ -149,6 +150,7 @@ public function initialize(Server $server) { $server->protectedProperties[] = self::HAS_PREVIEW_PROPERTYNAME; $server->protectedProperties[] = self::MOUNT_TYPE_PROPERTYNAME; $server->protectedProperties[] = self::IS_ENCRYPTED_PROPERTYNAME; + $server->protectedProperties[] = self::IS_FEDERATED_PROPERTYNAME; $server->protectedProperties[] = self::SHARE_NOTE; // normally these cannot be changed (RFC4918), but we want them modifiable through PROPPATCH @@ -413,6 +415,10 @@ public function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node) $propFind->handle(self::DISPLAYNAME_PROPERTYNAME, function () use ($node) { return $node->getName(); }); + + $propFind->handle(self::IS_FEDERATED_PROPERTYNAME, function () use ($node) { + return $node->getOwner()->isFederated() ? 'true' : 'false'; + }); } if ($node instanceof \OCA\DAV\Connector\Sabre\File) { diff --git a/apps/files/src/init.ts b/apps/files/src/init.ts index c3b4b570e12ca..8816ffe81996d 100644 --- a/apps/files/src/init.ts +++ b/apps/files/src/init.ts @@ -70,5 +70,6 @@ registerPreviewServiceWorker() registerDavProperty('nc:hidden', { nc: 'http://nextcloud.org/ns' }) registerDavProperty('nc:is-mount-root', { nc: 'http://nextcloud.org/ns' }) +registerDavProperty('nc:is-federated', { nc: 'http://nextcloud.org/ns' }) initLivePhotos() diff --git a/apps/files_sharing/src/actions/sharingStatusAction.ts b/apps/files_sharing/src/actions/sharingStatusAction.ts index 4f9648fa27f80..ad6943153cfcc 100644 --- a/apps/files_sharing/src/actions/sharingStatusAction.ts +++ b/apps/files_sharing/src/actions/sharingStatusAction.ts @@ -21,7 +21,7 @@ */ import { Node, View, registerFileAction, FileAction, Permission } from '@nextcloud/files' import { translate as t } from '@nextcloud/l10n' -import { Type } from '@nextcloud/sharing' +import { ShareType } from '@nextcloud/sharing' import AccountGroupSvg from '@mdi/svg/svg/account-group.svg?raw' import AccountPlusSvg from '@mdi/svg/svg/account-plus.svg?raw' @@ -29,23 +29,11 @@ import LinkSvg from '@mdi/svg/svg/link.svg?raw' import CircleSvg from '../../../../core/img/apps/circles.svg?raw' import { action as sidebarAction } from '../../../files/src/actions/sidebarAction' -import { generateUrl } from '@nextcloud/router' import { getCurrentUser } from '@nextcloud/auth' +import { generateAvatarSvg } from '../utils/AccountIcon.ts' import './sharingStatusAction.scss' -const isDarkMode = window?.matchMedia?.('(prefers-color-scheme: dark)')?.matches === true - || document.querySelector('[data-themes*=dark]') !== null - -const generateAvatarSvg = (userId: string, isGuest = false) => { - const url = isDarkMode ? '/avatar/{userId}/32/dark' : '/avatar/{userId}/32' - const avatarUrl = generateUrl(isGuest ? url : url + '?guestFallback=true', { userId }) - return `` -} - const isExternal = (node: Node) => { return node.attributes.remote_id !== undefined } @@ -92,25 +80,27 @@ export const action = new FileAction({ } // Link shares - if (shareTypes.includes(Type.SHARE_TYPE_LINK) - || shareTypes.includes(Type.SHARE_TYPE_EMAIL)) { + if (shareTypes.includes(ShareType.Link) + || shareTypes.includes(ShareType.Email)) { return LinkSvg } // Group shares - if (shareTypes.includes(Type.SHARE_TYPE_GROUP) - || shareTypes.includes(Type.SHARE_TYPE_REMOTE_GROUP)) { + if (shareTypes.includes(ShareType.Group) + || shareTypes.includes(ShareType.RemoteGroup)) { return AccountGroupSvg } // Circle shares - if (shareTypes.includes(Type.SHARE_TYPE_CIRCLE)) { + if (shareTypes.includes(ShareType.Team)) { + // Circles was renamed to Teams return CircleSvg } const ownerId = node?.attributes?.['owner-id'] if (ownerId && (ownerId !== getCurrentUser()?.uid || isExternal(node))) { - return generateAvatarSvg(ownerId, isExternal(node)) + console.debug("is Federated?", node?.attributes?.['is-federated']) + return generateAvatarSvg(ownerId, node?.attributes?.['is-federated']) } return AccountPlusSvg diff --git a/apps/files_sharing/src/utils/AccountIcon.ts b/apps/files_sharing/src/utils/AccountIcon.ts new file mode 100644 index 0000000000000..ac126fb1b35ad --- /dev/null +++ b/apps/files_sharing/src/utils/AccountIcon.ts @@ -0,0 +1,17 @@ +/*! + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import { generateUrl } from '@nextcloud/router' + +const isDarkMode = window?.matchMedia?.('(prefers-color-scheme: dark)')?.matches === true + || document.querySelector('[data-themes*=dark]') !== null + +export const generateAvatarSvg = (userId: string, isGuest = false) => { + const url = isDarkMode ? '/avatar/{userId}/32/dark' : '/avatar/{userId}/32' + const avatarUrl = generateUrl(isGuest ? url : url + '?guestFallback=true', { userId }) + return `` +} diff --git a/lib/private/User/LazyUser.php b/lib/private/User/LazyUser.php index 396d3c252f11d..ae10937a66b09 100644 --- a/lib/private/User/LazyUser.php +++ b/lib/private/User/LazyUser.php @@ -51,6 +51,11 @@ private function getUser(): IUser { $this->user = $this->userManager->get($this->uid); } } + + if ($this->user === null) { + throw new \Exception('User not found'); + } + /** @var IUser */ $user = $this->user; return $user; @@ -167,4 +172,16 @@ public function getManagerUids(): array { public function setManagerUids(array $uids): void { $this->getUser()->setManagerUids($uids); } + + public function isFederated(): bool { + try { + // If getUser succeeds then user is definitely not federated + // This could fail for other reasons (especially a race condition where a user is deleted) + // But it's what we have now + $this->getUser(); + return false; + } catch (\Exception $e) { + return true; + } + } } diff --git a/lib/private/User/User.php b/lib/private/User/User.php index 580c590e6eb54..01f41877406fb 100644 --- a/lib/private/User/User.php +++ b/lib/private/User/User.php @@ -618,4 +618,10 @@ public function triggerChange($feature, $value = null, $oldValue = null) { $this->emitter->emit('\OC\User', 'changeUser', [$this, $feature, $value, $oldValue]); } } + + public function isFederated(): bool { + // Federated users are instantiated via LazyUser + // DAV also only uses LazyUser + return false; + } } diff --git a/lib/public/IUser.php b/lib/public/IUser.php index b326e6192c0c2..52769de210129 100644 --- a/lib/public/IUser.php +++ b/lib/public/IUser.php @@ -287,4 +287,12 @@ public function getManagerUids(): array; * @since 27.0.0 */ public function setManagerUids(array $uids): void; + + /** + * Check if the user is federated (from another server) + * + * @return bool + * @since 28.0.11 + */ + public function isFederated(): bool; }