From a7509ae2b8bf9e75cc6abf9a0c1b3c216d00ab0a Mon Sep 17 00:00:00 2001 From: Fauzan Date: Fri, 24 Oct 2025 23:43:00 +0700 Subject: [PATCH] fix(files_sharing): Hide 'Open locally' action This patch ensures that the "Open locally" context menu item is not displayed for files in a share where the "download and sync" permission has not been granted. This prevents user confusion, as the action would fail anyway. The fix adds a permission check before rendering the menu item, and adds a corresponding unit test to verify this behavior. Resolves: #54970 Signed-off-by: Fauzan --- apps/files/src/actions/openLocallyAction.ts | 5 ++-- apps/files/src/utils/permissions.ts | 31 +++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/apps/files/src/actions/openLocallyAction.ts b/apps/files/src/actions/openLocallyAction.ts index 58dc518c6c0e5..8b4c0df11dd53 100644 --- a/apps/files/src/actions/openLocallyAction.ts +++ b/apps/files/src/actions/openLocallyAction.ts @@ -10,12 +10,13 @@ import IconWeb from '@mdi/svg/svg/web.svg?raw' import { getCurrentUser } from '@nextcloud/auth' import axios from '@nextcloud/axios' import { DialogBuilder, showError } from '@nextcloud/dialogs' -import { FileAction, Permission } from '@nextcloud/files' +import { FileAction } from '@nextcloud/files' import { translate as t } from '@nextcloud/l10n' import { encodePath } from '@nextcloud/paths' import { generateOcsUrl } from '@nextcloud/router' import { isPublicShare } from '@nextcloud/sharing/public' import logger from '../logger.ts' +import { isSyncable } from '../utils/permissions.ts' export const action = new FileAction({ id: 'edit-locally', @@ -34,7 +35,7 @@ export const action = new FileAction({ return false } - return (nodes[0].permissions & Permission.UPDATE) !== 0 + return isSyncable(nodes[0]) }, async exec(node: Node) { diff --git a/apps/files/src/utils/permissions.ts b/apps/files/src/utils/permissions.ts index ce0a8b93d723a..dfd7a0224f083 100644 --- a/apps/files/src/utils/permissions.ts +++ b/apps/files/src/utils/permissions.ts @@ -36,3 +36,34 @@ export function isDownloadable(node: Node): boolean { return true } + + +/** + * Check permissions on the node if it can be synced/open locally + * + * @param node The node to check + * @return True if syncable, false otherwise + */ +export function isSyncable(node: Node): boolean { + if ((node.permissions & Permission.UPDATE) === 0) { + return false + } + + // check hide-download property of shares + if (node.attributes['hide-download'] === true + || node.attributes['hide-download'] === 'true' + ) { + return false + } + + // If the mount type is a share, ensure it got download permissions. + if (node.attributes['share-attributes']) { + const shareAttributes = JSON.parse(node.attributes['share-attributes'] || '[]') as Array + const downloadAttribute = shareAttributes.find(({ scope, key }: ShareAttribute) => scope === 'permissions' && key === 'download') + if (downloadAttribute !== undefined) { + return downloadAttribute.value === true + } + } + + return true +} \ No newline at end of file