diff --git a/packages/web-app-office-settings/src/App.vue b/packages/web-app-office-settings/src/App.vue index 61befe7e882..683a65444fe 100644 --- a/packages/web-app-office-settings/src/App.vue +++ b/packages/web-app-office-settings/src/App.vue @@ -15,7 +15,7 @@
= 0 ) }, + canListVersions: function () { + return !this.isFolder + }, isMounted: function () { return this.permissions.indexOf(DavPermission.Mounted) >= 0 }, @@ -259,6 +262,7 @@ export function buildDeletedResource(resource: WebDavResponseResource): TrashRes isReceivedShare: () => false, hasPreview: () => false, isShareRoot: () => false, + canListVersions: () => false, getDomSelector: () => extractDomSelector(id) } } diff --git a/packages/web-client/src/helpers/resource/types.ts b/packages/web-client/src/helpers/resource/types.ts index 87a5bd43d8b..25f12af9c9c 100644 --- a/packages/web-client/src/helpers/resource/types.ts +++ b/packages/web-client/src/helpers/resource/types.ts @@ -83,6 +83,7 @@ export interface Resource { canRename?(args?: { user?: User; ability?: Ability }): boolean canBeDeleted?(args?: { user?: User; ability?: Ability }): boolean canEditTags?(): boolean + canListVersions?(args?: { user?: User }): boolean getDomSelector?(): string diff --git a/packages/web-client/src/helpers/share/functions.ts b/packages/web-client/src/helpers/share/functions.ts index d6b369e5fc3..b2b3909a502 100644 --- a/packages/web-client/src/helpers/share/functions.ts +++ b/packages/web-client/src/helpers/share/functions.ts @@ -163,6 +163,8 @@ export function buildIncomingShareResource({ canCreate: () => sharePermissions.includes(GraphSharePermission.createChildren), canBeDeleted: () => sharePermissions.includes(GraphSharePermission.deleteStandard), canEditTags: () => sharePermissions.includes(GraphSharePermission.createUpload), + canListVersions: () => + !driveItem.folder && sharePermissions.includes(GraphSharePermission.readVersions), isMounted: () => false, isReceivedShare: () => true, canShare: () => false, @@ -225,6 +227,7 @@ export function buildOutgoingShareResource({ canCreate: () => true, canBeDeleted: () => true, canEditTags: () => true, + canListVersions: () => !driveItem.folder, isMounted: () => false, isShareRoot: () => false, isReceivedShare: () => true, diff --git a/packages/web-client/src/helpers/space/functions.ts b/packages/web-client/src/helpers/space/functions.ts index a1437d2fe0f..80a16c63351 100644 --- a/packages/web-client/src/helpers/space/functions.ts +++ b/packages/web-client/src/helpers/space/functions.ts @@ -116,12 +116,14 @@ export function buildShareSpaceResource({ driveAliasPrefix, id, shareName, - serverUrl + serverUrl, + graphPermissions }: { driveAliasPrefix: 'share' | 'ocm-share' id: string shareName: string serverUrl: string + graphPermissions?: GraphSharePermission[] }): ShareSpaceResource { const space = buildSpace({ id, @@ -134,6 +136,10 @@ export function buildShareSpaceResource({ space.driveAlias = `${driveAliasPrefix}/${newName}` space.name = newName } + if (graphPermissions) { + space.graphPermissions = graphPermissions + } + return space } @@ -281,11 +287,8 @@ export function buildSpace( // FIXME: server permissions are a mess currently: https://github.com/opencloud-eu/opencloud/issues/10 return this.graphPermissions?.includes(GraphSharePermission.deletePermissions) }, - canListVersions: function ({ user }: { user?: User } = {}) { - if (isPersonalSpaceResource(this) && this.isOwner(user)) { - return true - } - return this.graphPermissions?.includes(GraphSharePermission.readVersions) + canListVersions: function () { + return false }, canCreate: function () { return true diff --git a/packages/web-client/src/helpers/space/types.ts b/packages/web-client/src/helpers/space/types.ts index 9eae5544cfa..70d252195c6 100644 --- a/packages/web-client/src/helpers/space/types.ts +++ b/packages/web-client/src/helpers/space/types.ts @@ -35,7 +35,6 @@ export interface SpaceResource extends Resource { canRestore(args?: { user?: User; ability?: Ability }): boolean canDeleteFromTrashBin(args?: { user?: User }): boolean canRestoreFromTrashbin(args?: { user?: User }): boolean - canListVersions(args?: { user?: User }): boolean getWebDavUrl({ path }: { path: string }): string getWebDavTrashUrl({ path }: { path: string }): string diff --git a/packages/web-pkg/src/components/AppTemplates/AppWrapper.vue b/packages/web-pkg/src/components/AppTemplates/AppWrapper.vue index 156d5a77e91..4702fdb6247 100644 --- a/packages/web-pkg/src/components/AppTemplates/AppWrapper.vue +++ b/packages/web-pkg/src/components/AppTemplates/AppWrapper.vue @@ -89,10 +89,15 @@ import { HttpError } from '@opencloud-eu/web-client' import { dirname } from 'path' import { useFileActionsOpenWithApp } from '../../composables' import { UnsavedChangesModal } from '../Modals' -import { formatFileSize, getSharedDriveItem } from '../../helpers' +import { + formatFileSize, + getSharedDriveItem, + setCurrentUserShareSpacePermissions +} from '../../helpers' import toNumber from 'lodash-es/toNumber' import { useIsMobile } from '@opencloud-eu/design-system/composables' import { storeToRefs } from 'pinia' +import { DriveItem } from '@opencloud-eu/web-client/graph/generated' const { applicationId, @@ -301,17 +306,30 @@ const loadResourceTask = useTask(function* (signal) { // FIXME: As soon the backend exposes oc-remote-id via webdav, remove the assignment below unref(resource).remoteItemId = unref(space).id - if (unref(resource).id === unref(resource).remoteItemId) { - // use graph api to build incoming share resource - const sharedDriveItem = yield* call( + let sharedDriveItem: DriveItem + const resourceIsShareSpaceRoot = unref(resource).id === unref(resource).remoteItemId + if (!unref(space).graphPermissions || resourceIsShareSpaceRoot) { + // we need to load the driveItem either for the share space permissions if not yet loaded, + // or for building the incoming share resource if it is a share space root + sharedDriveItem = yield* call( getSharedDriveItem({ graphClient: clientService.graphAuthenticated, spacesStore, space: unref(space) }) ) + } + + if (sharedDriveItem) { + // set graph permissions on the current share space + setCurrentUserShareSpacePermissions({ + sharesStore, + spacesStore, + space: unref(space), + sharedDriveItem + }) - if (sharedDriveItem) { + if (resourceIsShareSpaceRoot) { resource.value = { ...fileInfo, ...buildIncomingShareResource({ diff --git a/packages/web-pkg/src/composables/piniaStores/spaces.ts b/packages/web-pkg/src/composables/piniaStores/spaces.ts index 53a11281424..ca7741cf0f7 100644 --- a/packages/web-pkg/src/composables/piniaStores/spaces.ts +++ b/packages/web-pkg/src/composables/piniaStores/spaces.ts @@ -7,7 +7,11 @@ import { } from '@opencloud-eu/web-client' import { Graph } from '@opencloud-eu/web-client/graph' import { isPersonalSpaceResource, isProjectSpaceResource } from '@opencloud-eu/web-client' -import type { CollaboratorShare, MountPointSpaceResource } from '@opencloud-eu/web-client' +import type { + CollaboratorShare, + GraphSharePermission, + MountPointSpaceResource +} from '@opencloud-eu/web-client' import { useUserStore } from './user' import { useConfigStore } from './config' import { useSharesStore } from './shares' @@ -118,17 +122,20 @@ export const useSpacesStore = defineStore('spaces', () => { const createShareSpace = ({ driveAliasPrefix, id, - shareName + shareName, + graphPermissions }: { driveAliasPrefix: 'share' | 'ocm-share' id: string shareName: string + graphPermissions?: GraphSharePermission[] }) => { const space = buildShareSpaceResource({ driveAliasPrefix, id, shareName, - serverUrl: configStore.serverUrl + serverUrl: configStore.serverUrl, + graphPermissions }) addSpaces([space]) return space diff --git a/packages/web-pkg/src/composables/resources/useCanListVersions.ts b/packages/web-pkg/src/composables/resources/useCanListVersions.ts index 6bd1addceb4..1ae68029e85 100644 --- a/packages/web-pkg/src/composables/resources/useCanListVersions.ts +++ b/packages/web-pkg/src/composables/resources/useCanListVersions.ts @@ -1,20 +1,20 @@ import { useUserStore } from '../piniaStores' -import { isSpaceResource, isTrashResource, Resource, SpaceResource } from '@opencloud-eu/web-client' +import { + GraphSharePermission, + isPersonalSpaceResource, + Resource, + SpaceResource +} from '@opencloud-eu/web-client' export const useCanListVersions = () => { const userStore = useUserStore() const canListVersions = ({ space, resource }: { space: SpaceResource; resource: Resource }) => { - if (resource.type === 'folder') { - return false - } - if (isSpaceResource(resource)) { - return false - } - if (isTrashResource(resource)) { - return false - } - return space?.canListVersions({ user: userStore.user }) + const spaceAllowsListingVersions = + (isPersonalSpaceResource(space) && space.isOwner(userStore.user)) || + space.graphPermissions?.includes(GraphSharePermission.readVersions) + + return spaceAllowsListingVersions && resource.canListVersions?.({ user: userStore.user }) } return { diff --git a/packages/web-pkg/src/composables/spaces/useGetMatchingSpace.ts b/packages/web-pkg/src/composables/spaces/useGetMatchingSpace.ts index 60c80a8d56f..f777839e6f2 100644 --- a/packages/web-pkg/src/composables/spaces/useGetMatchingSpace.ts +++ b/packages/web-pkg/src/composables/spaces/useGetMatchingSpace.ts @@ -62,7 +62,8 @@ export const useGetMatchingSpace = (options?: GetMatchingSpaceOptions) => { spacesStore.createShareSpace({ driveAliasPrefix, id: resource.remoteItemId, - shareName + shareName, + graphPermissions: isIncomingShareResource(resource) ? resource.sharePermissions : [] }) ) } diff --git a/packages/web-pkg/tests/unit/composables/resources/useCanListVersions.spec.ts b/packages/web-pkg/tests/unit/composables/resources/useCanListVersions.spec.ts index dfabf77bb4f..db8e689513d 100644 --- a/packages/web-pkg/tests/unit/composables/resources/useCanListVersions.spec.ts +++ b/packages/web-pkg/tests/unit/composables/resources/useCanListVersions.spec.ts @@ -1,55 +1,75 @@ import { getComposableWrapper } from '@opencloud-eu/web-test-helpers' import { mock } from 'vitest-mock-extended' -import { Resource, SpaceResource, TrashResource } from '@opencloud-eu/web-client' +import { GraphSharePermission, Resource, SpaceResource } from '@opencloud-eu/web-client' import { useCanListVersions } from '../../../../src/composables/resources' describe('useCanListVersions', () => { describe('canListVersions', () => { - it('returns true for files when user has sufficient permissions in space', () => { + it('returns true for a personal space owned by the user', () => { getWrapper({ setup: ({ canListVersions }) => { - const space = mock({ canListVersions: () => true }) - const resource = mock({ type: 'file' }) + const space = mock({ + driveType: 'personal', + isOwner: () => true, + graphPermissions: [] + }) + const resource = mock({ canListVersions: () => true }) const canList = canListVersions({ space, resource }) expect(canList).toBeTruthy() } }) }) - it('returns false for folders', () => { + it('returns true when the space grants the readVersions permission', () => { getWrapper({ setup: ({ canListVersions }) => { - const space = mock({ canListVersions: () => true }) - const resource = mock({ type: 'folder' }) + const space = mock({ + driveType: 'project', + isOwner: () => false, + graphPermissions: [GraphSharePermission.readVersions] + }) + const resource = mock({ canListVersions: () => true }) const canList = canListVersions({ space, resource }) - expect(canList).toBeFalsy() + expect(canList).toBeTruthy() } }) }) - it('returns false for space resources', () => { + it('returns false for a personal space not owned by the user', () => { getWrapper({ setup: ({ canListVersions }) => { - const space = mock({ canListVersions: () => true }) - const resource = mock({ type: 'space' }) + const space = mock({ + driveType: 'personal', + isOwner: () => false, + graphPermissions: [] + }) + const resource = mock({ canListVersions: () => true }) const canList = canListVersions({ space, resource }) expect(canList).toBeFalsy() } }) }) - it('returns false for trash resources', () => { + it('returns false when the space does not allow listing versions', () => { getWrapper({ setup: ({ canListVersions }) => { - const space = mock({ canListVersions: () => true }) - const resource = mock({ type: 'file', ddate: '' }) + const space = mock({ + driveType: 'project', + isOwner: () => false, + graphPermissions: [] + }) + const resource = mock({ canListVersions: () => true }) const canList = canListVersions({ space, resource }) expect(canList).toBeFalsy() } }) }) - it('returns false when user does not have sufficient permissions in space', () => { + it('returns false when the resource does not allow listing versions', () => { getWrapper({ setup: ({ canListVersions }) => { - const space = mock({ canListVersions: () => false }) - const resource = mock({ type: 'file' }) + const space = mock({ + driveType: 'personal', + isOwner: () => true, + graphPermissions: [GraphSharePermission.readVersions] + }) + const resource = mock({ canListVersions: () => false }) const canList = canListVersions({ space, resource }) expect(canList).toBeFalsy() }