From cc6be49d6c9374a1207ab2864a7dbd9a1d1ef338 Mon Sep 17 00:00:00 2001 From: Jannik Stehle Date: Thu, 4 Jul 2024 10:58:50 +0200 Subject: [PATCH] fix: secure view default action Prevents files that have been shared via secure view without having a compatible app to view such (or the file type is not supported) from being clicked. --- .../bugfix-secure-view-default-action | 6 ++ .../components/FilesList/ResourceTable.vue | 36 ++++++---- .../components/FilesList/ResourceTiles.vue | 14 +++- .../src/composables/resources/index.ts | 1 + .../resources/useCanBeOpenedWithSecureView.ts | 13 ++++ .../useCanBeOpenedWithSecureView.spec.ts | 70 +++++++++++++++++++ 6 files changed, 125 insertions(+), 15 deletions(-) create mode 100644 changelog/unreleased/bugfix-secure-view-default-action create mode 100644 packages/web-pkg/src/composables/resources/useCanBeOpenedWithSecureView.ts create mode 100644 packages/web-pkg/tests/unit/composables/resources/useCanBeOpenedWithSecureView.spec.ts diff --git a/changelog/unreleased/bugfix-secure-view-default-action b/changelog/unreleased/bugfix-secure-view-default-action new file mode 100644 index 00000000000..067bdf57334 --- /dev/null +++ b/changelog/unreleased/bugfix-secure-view-default-action @@ -0,0 +1,6 @@ +Bugfix: Secure view default action + +Clicking files that have been shared via secure view without having a compatible app to view such (or the file type is not supported) is no longer possible. This prevents errors and other file actions from falsely registering themselves as default. + +https://github.com/owncloud/web/pull/11139 +https://github.com/owncloud/web/issues/11138 diff --git a/packages/web-pkg/src/components/FilesList/ResourceTable.vue b/packages/web-pkg/src/components/FilesList/ResourceTable.vue index cb3bf127626..5c4224e6373 100644 --- a/packages/web-pkg/src/components/FilesList/ResourceTable.vue +++ b/packages/web-pkg/src/components/FilesList/ResourceTable.vue @@ -258,7 +258,8 @@ import { useConfigStore, useClipboardStore, useResourcesStore, - useRouter + useRouter, + useCanBeOpenedWithSecureView } from '../../composables' import ResourceListItem from './ResourceListItem.vue' import ResourceGhostElement from './ResourceGhostElement.vue' @@ -505,6 +506,7 @@ export default defineComponent({ const router = useRouter() const capabilityStore = useCapabilityStore() const { getMatchingSpace } = useGetMatchingSpace() + const { canBeOpenedWithSecureView } = useCanBeOpenedWithSecureView() const { isLocationPicker, isFilePicker, @@ -559,6 +561,24 @@ export default defineComponent({ ) }) + const isResourceClickable = (resource: Resource) => { + if (!props.areResourcesClickable) { + return false + } + + if (!resource.isFolder) { + if (!resource.canDownload() && !canBeOpenedWithSecureView(resource)) { + return false + } + + if (unref(isEmbedModeEnabled) && !unref(isFilePicker)) { + return false + } + } + + return !unref(disabledResources).includes(resource.id) + } + return { router, configOptions, @@ -593,7 +613,8 @@ export default defineComponent({ isEmbedModeEnabled, toggleSelection, areFileExtensionsShown, - latestSelectedId + latestSelectedId, + isResourceClickable } }, data() { @@ -1057,17 +1078,6 @@ export default defineComponent({ */ this.$emit('fileClick', { space, resources: [resource] }) }, - isResourceClickable(resource: Resource) { - if (!this.areResourcesClickable) { - return false - } - - if (this.isEmbedModeEnabled && !this.isFilePicker && !resource.isFolder) { - return false - } - - return !this.disabledResources.includes(resource.id) - }, getResourceCheckboxLabel(resource: Resource) { if (resource.type === 'folder') { return this.$gettext('Select folder') diff --git a/packages/web-pkg/src/components/FilesList/ResourceTiles.vue b/packages/web-pkg/src/components/FilesList/ResourceTiles.vue index a81b9d094b7..e3199da1683 100644 --- a/packages/web-pkg/src/components/FilesList/ResourceTiles.vue +++ b/packages/web-pkg/src/components/FilesList/ResourceTiles.vue @@ -156,7 +156,8 @@ import { useTileSize, useResourcesStore, useViewSizeMax, - useEmbedMode + useEmbedMode, + useCanBeOpenedWithSecureView } from '../../composables' type ResourceTileRef = ComponentPublicInstance @@ -221,6 +222,7 @@ export default defineComponent({ const { showMessage } = useMessages() const { $gettext } = useGettext() const resourcesStore = useResourcesStore() + const { canBeOpenedWithSecureView } = useCanBeOpenedWithSecureView() const { emit } = context const { isEnabled: isEmbedModeEnabled, @@ -311,7 +313,15 @@ export default defineComponent({ } const isResourceClickable = (resource: Resource) => { - if (unref(isEmbedModeEnabled) && !unref(isFilePicker) && !resource.isFolder) { + if (resource.isFolder) { + return true + } + + if (!resource.canDownload() && !canBeOpenedWithSecureView(resource)) { + return false + } + + if (unref(isEmbedModeEnabled) && !unref(isFilePicker)) { return false } diff --git a/packages/web-pkg/src/composables/resources/index.ts b/packages/web-pkg/src/composables/resources/index.ts index 623db71e764..c54a10d4094 100644 --- a/packages/web-pkg/src/composables/resources/index.ts +++ b/packages/web-pkg/src/composables/resources/index.ts @@ -1,3 +1,4 @@ +export * from './useCanBeOpenedWithSecureView' export * from './useGetResourceContext' export * from './useLoadFileInfoById' export * from './useResourceContents' diff --git a/packages/web-pkg/src/composables/resources/useCanBeOpenedWithSecureView.ts b/packages/web-pkg/src/composables/resources/useCanBeOpenedWithSecureView.ts new file mode 100644 index 00000000000..b220448ab59 --- /dev/null +++ b/packages/web-pkg/src/composables/resources/useCanBeOpenedWithSecureView.ts @@ -0,0 +1,13 @@ +import { Resource } from '@ownclouders/web-client' +import { useAppsStore } from '../piniaStores' + +export const useCanBeOpenedWithSecureView = () => { + const appsStore = useAppsStore() + + const canBeOpenedWithSecureView = (resource: Resource) => { + const secureViewExtensions = appsStore.fileExtensions.filter(({ secureView }) => secureView) + return secureViewExtensions.some(({ mimeType }) => mimeType === resource.mimeType) + } + + return { canBeOpenedWithSecureView } +} diff --git a/packages/web-pkg/tests/unit/composables/resources/useCanBeOpenedWithSecureView.spec.ts b/packages/web-pkg/tests/unit/composables/resources/useCanBeOpenedWithSecureView.spec.ts new file mode 100644 index 00000000000..850a3be0680 --- /dev/null +++ b/packages/web-pkg/tests/unit/composables/resources/useCanBeOpenedWithSecureView.spec.ts @@ -0,0 +1,70 @@ +import { getComposableWrapper } from 'web-test-helpers' +import { mock } from 'vitest-mock-extended' +import { Resource } from '@ownclouders/web-client' +import { useCanBeOpenedWithSecureView } from '../../../../src/composables/resources' +import { ApplicationFileExtension } from '../../../../src/apps/types' + +describe('canBeOpenedWithSecureView', () => { + describe('resource', () => { + it('can be opened if a matching file extension with secure view exists', () => { + getWrapper({ + setup: ({ canBeOpenedWithSecureView }) => { + const canBeOpened = canBeOpenedWithSecureView(mock({ mimeType: 'text/plain' })) + expect(canBeOpened).toBeTruthy() + }, + fileExtensions: [ + mock({ secureView: true, mimeType: 'text/plain' }) + ] + }) + }) + it('can not be opened if no file extension with secure view exists', () => { + getWrapper({ + setup: ({ canBeOpenedWithSecureView }) => { + const canBeOpened = canBeOpenedWithSecureView(mock({ mimeType: 'text/plain' })) + expect(canBeOpened).toBeFalsy() + }, + fileExtensions: [ + mock({ secureView: false, mimeType: 'text/plain' }) + ] + }) + }) + it('can not be opened if no file extension exists', () => { + getWrapper({ + setup: ({ canBeOpenedWithSecureView }) => { + const canBeOpened = canBeOpenedWithSecureView(mock({ mimeType: 'text/plain' })) + expect(canBeOpened).toBeFalsy() + }, + fileExtensions: [] + }) + }) + it("can not be opened if the file extension's mime type doesn't match the one of the resource", () => { + getWrapper({ + setup: ({ canBeOpenedWithSecureView }) => { + const canBeOpened = canBeOpenedWithSecureView(mock({ mimeType: 'text/plain' })) + expect(canBeOpened).toBeFalsy() + }, + fileExtensions: [ + mock({ secureView: true, mimeType: 'image/jpg' }) + ] + }) + }) + }) +}) + +function getWrapper({ + setup, + fileExtensions = [mock()] +}: { + setup: (instance: ReturnType) => void + fileExtensions?: ApplicationFileExtension[] +}) { + return { + wrapper: getComposableWrapper( + () => { + const instance = useCanBeOpenedWithSecureView() + setup(instance) + }, + { pluginOptions: { piniaOptions: { appsState: { fileExtensions } } } } + ) + } +}