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/components/FilesList/ResourceTable.spec.ts b/packages/web-pkg/tests/unit/components/FilesList/ResourceTable.spec.ts index 866e5134af2..68ecaee1cc6 100644 --- a/packages/web-pkg/tests/unit/components/FilesList/ResourceTable.spec.ts +++ b/packages/web-pkg/tests/unit/components/FilesList/ResourceTable.spec.ts @@ -121,7 +121,8 @@ const resourcesWithAllFields = [ sharePermissions: [], shareTypes: [], canRename: vi.fn(), - getDomSelector: () => extractDomSelector('forest') + getDomSelector: () => extractDomSelector('forest'), + canDownload: () => true }, { id: 'notes', @@ -146,7 +147,8 @@ const resourcesWithAllFields = [ sharePermissions: [], shareTypes: [], canRename: vi.fn(), - getDomSelector: () => extractDomSelector('notes') + getDomSelector: () => extractDomSelector('notes'), + canDownload: () => true }, { id: 'documents', @@ -170,7 +172,8 @@ const resourcesWithAllFields = [ sharePermissions: [], shareTypes: [], canRename: vi.fn(), - getDomSelector: () => extractDomSelector('documents') + getDomSelector: () => extractDomSelector('documents'), + canDownload: () => true }, { id: 'another-one==', @@ -194,7 +197,8 @@ const resourcesWithAllFields = [ shareTypes: [], tags: [], canRename: vi.fn(), - getDomSelector: () => extractDomSelector('another-one==') + getDomSelector: () => extractDomSelector('another-one=='), + canDownload: () => true } ] as IncomingShareResource[] @@ -225,6 +229,7 @@ const processingResourcesWithAllFields = [ shareTypes: [], canRename: vi.fn(), getDomSelector: () => extractDomSelector('forest'), + canDownload: () => true, processing: true }, { @@ -251,6 +256,7 @@ const processingResourcesWithAllFields = [ shareTypes: [], canRename: vi.fn(), getDomSelector: () => extractDomSelector('notes'), + canDownload: () => true, processing: true } ] as IncomingShareResource[] diff --git a/packages/web-pkg/tests/unit/components/FilesList/ResourceTiles.spec.ts b/packages/web-pkg/tests/unit/components/FilesList/ResourceTiles.spec.ts index 7902f44a868..0b044572bc8 100644 --- a/packages/web-pkg/tests/unit/components/FilesList/ResourceTiles.spec.ts +++ b/packages/web-pkg/tests/unit/components/FilesList/ResourceTiles.spec.ts @@ -57,7 +57,8 @@ const resources = [ syncEnabled: true, outgoing: false, canRename: vi.fn(), - getDomSelector: () => extractDomSelector('forest') + getDomSelector: () => extractDomSelector('forest'), + canDownload: () => true } ] 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 } } } } + ) + } +}