diff --git a/changelog/unreleased/bugfix-permanent-link-for-shares b/changelog/unreleased/bugfix-permanent-link-for-shares new file mode 100644 index 00000000000..8f8f084b378 --- /dev/null +++ b/changelog/unreleased/bugfix-permanent-link-for-shares @@ -0,0 +1,6 @@ +Bugfix: Permanent link for shares + +We've fixed an issue where the permanent for shares could not be copied. + +https://github.com/owncloud/web/pull/12022 +https://github.com/owncloud/web/issues/12001 diff --git a/packages/web-client/src/helpers/share/functions.ts b/packages/web-client/src/helpers/share/functions.ts index 7ff91707ef0..eeb4be94062 100644 --- a/packages/web-client/src/helpers/share/functions.ts +++ b/packages/web-client/src/helpers/share/functions.ts @@ -91,10 +91,12 @@ export const getShareResourcePermissions = ({ export function buildIncomingShareResource({ driveItem, - graphRoles + graphRoles, + serverUrl }: { driveItem: DriveItem graphRoles: Record + serverUrl: string }): IncomingShareResource { const resourceName = driveItem.name || driveItem.remoteItem.name const storageId = extractStorageId(driveItem.remoteItem.id) @@ -154,6 +156,7 @@ export function buildIncomingShareResource({ shareRoles, sharePermissions, outgoing: false, + privateLink: urlJoin(serverUrl, 'f', driveItem.remoteItem.id), canRename: () => driveItem['@client.synchronize'], canDownload: () => sharePermissions.includes(GraphSharePermission.readContent), canUpload: () => sharePermissions.includes(GraphSharePermission.createUpload), @@ -174,10 +177,12 @@ export function buildIncomingShareResource({ export function buildOutgoingShareResource({ driveItem, - user + user, + serverUrl }: { driveItem: DriveItem user: User + serverUrl: string }): OutgoingShareResource { const storageId = extractStorageId(driveItem.id) const path = urlJoin(driveItem.parentReference.path, driveItem.name) @@ -213,6 +218,7 @@ export function buildOutgoingShareResource({ type: !!driveItem.folder ? 'folder' : 'file', mimeType: driveItem.file?.mimeType || 'httpd/unix-directory', outgoing: true, + privateLink: urlJoin(serverUrl, 'f', driveItem.id), canRename: () => true, canDownload: () => true, canUpload: () => true, diff --git a/packages/web-client/tests/unit/helpers/share/functions.spec.ts b/packages/web-client/tests/unit/helpers/share/functions.spec.ts index b490e8c267c..822711c47d4 100644 --- a/packages/web-client/tests/unit/helpers/share/functions.spec.ts +++ b/packages/web-client/tests/unit/helpers/share/functions.spec.ts @@ -25,6 +25,7 @@ import { UnifiedRoleDefinition, User } from '../../../../src/graph/generated' +import { urlJoin } from '../../../../src' describe('share helper functions', () => { describe('isShareResource', () => { @@ -129,7 +130,7 @@ describe('share helper functions', () => { } it('sets ids based on the drive item, its first permission and parent reference', () => { - const result = buildIncomingShareResource({ driveItem, graphRoles }) + const result = buildIncomingShareResource({ driveItem, graphRoles, serverUrl: '' }) expect(result.id).toEqual(driveItem.id) expect(result.fileId).toEqual(driveItem.remoteItem.id) @@ -140,23 +141,28 @@ describe('share helper functions', () => { it.each([true, false])('correctly detects if the resource is a folder', (isFolder) => { const item = { ...driveItem } item.folder = isFolder ? mock() : undefined - const result = buildIncomingShareResource({ driveItem: item, graphRoles }) + const result = buildIncomingShareResource({ driveItem: item, graphRoles, serverUrl: '' }) expect(result.isFolder).toEqual(isFolder) expect(result.type).toEqual(isFolder ? 'folder' : 'file') }) it('sets outgoing to false', () => { - const result = buildIncomingShareResource({ driveItem, graphRoles }) + const result = buildIncomingShareResource({ driveItem, graphRoles, serverUrl: '' }) expect(result.outgoing).toBeFalsy() }) it('sets sharedBy based on the permission invitation', () => { - const result = buildIncomingShareResource({ driveItem, graphRoles }) + const result = buildIncomingShareResource({ driveItem, graphRoles, serverUrl: '' }) expect(result.sharedBy).toEqual([sharedBy]) }) it('sets sharedBy based on the permission invitation', () => { - const result = buildIncomingShareResource({ driveItem, graphRoles }) + const result = buildIncomingShareResource({ driveItem, graphRoles, serverUrl: '' }) expect(result.sharedWith).toEqual([{ ...sharedWith, shareType: ShareTypes.user.value }]) }) + it('constructs a private link', () => { + const serverUrl = 'https://example.com' + const result = buildIncomingShareResource({ driveItem, graphRoles, serverUrl }) + expect(result.privateLink).toEqual(urlJoin(serverUrl, 'f', driveItem.remoteItem.id)) + }) }) describe('buildOutgoingShareResource', () => { @@ -174,7 +180,7 @@ describe('share helper functions', () => { const user = { id: '1', displayName: 'user1' } as User it('sets ids based on the drive item, its first permission and parent reference', () => { - const result = buildOutgoingShareResource({ driveItem, user }) + const result = buildOutgoingShareResource({ driveItem, user, serverUrl: '' }) expect(result.id).toEqual(driveItem.id) expect(result.fileId).toEqual(driveItem.id) @@ -182,21 +188,26 @@ describe('share helper functions', () => { expect(result.parentFolderId).toEqual(driveItem.parentReference.id) }) it('sets outgoing to true', () => { - const result = buildOutgoingShareResource({ driveItem, user }) + const result = buildOutgoingShareResource({ driveItem, user, serverUrl: '' }) expect(result.outgoing).toBeTruthy() }) it('sets the path based on the parent reference path and the drive item name', () => { - const result = buildOutgoingShareResource({ driveItem, user }) + const result = buildOutgoingShareResource({ driveItem, user, serverUrl: '' }) expect(result.path).toEqual(`${driveItem.parentReference.path}/${driveItem.name}`) }) it.each([true, false])('correctly detects if the resource is a folder', (isFolder) => { const item = { ...driveItem } item.folder = isFolder ? mock() : undefined - const result = buildOutgoingShareResource({ driveItem: item, user }) + const result = buildOutgoingShareResource({ driveItem: item, user, serverUrl: '' }) expect(result.isFolder).toEqual(isFolder) expect(result.type).toEqual(isFolder ? 'folder' : 'file') }) + it('constructs a private link', () => { + const serverUrl = 'https://example.com' + const result = buildOutgoingShareResource({ driveItem, user, serverUrl }) + expect(result.privateLink).toEqual(urlJoin(serverUrl, 'f', driveItem.id)) + }) }) describe('buildCollaboratorShare', () => { diff --git a/packages/web-pkg/src/components/AppTemplates/AppWrapper.vue b/packages/web-pkg/src/components/AppTemplates/AppWrapper.vue index e41bdc6f6e5..ff741275b79 100644 --- a/packages/web-pkg/src/components/AppTemplates/AppWrapper.vue +++ b/packages/web-pkg/src/components/AppTemplates/AppWrapper.vue @@ -306,7 +306,8 @@ export default defineComponent({ ...fileInfo, ...buildIncomingShareResource({ graphRoles: sharesStore.graphRoles, - driveItem: sharedDriveItem + driveItem: sharedDriveItem, + serverUrl: configStore.serverUrl }), tags: fileInfo.tags // tags are always [] in Graph API, hence take them from webdav } diff --git a/packages/web-pkg/src/services/folder/loaders/loaderSharedViaLink.ts b/packages/web-pkg/src/services/folder/loaders/loaderSharedViaLink.ts index 5d1a1d03596..3cef801d502 100644 --- a/packages/web-pkg/src/services/folder/loaders/loaderSharedViaLink.ts +++ b/packages/web-pkg/src/services/folder/loaders/loaderSharedViaLink.ts @@ -39,7 +39,13 @@ export class FolderLoaderSharedViaLink implements FolderLoader { const resources = value .filter((s) => s.permissions.some(({ link }) => !!link)) - .map((driveItem) => buildOutgoingShareResource({ driveItem, user: userStore.user })) + .map((driveItem) => + buildOutgoingShareResource({ + driveItem, + user: userStore.user, + serverUrl: configStore.serverUrl + }) + ) resourcesStore.initResourceList({ currentFolder: null, resources }) }) diff --git a/packages/web-pkg/src/services/folder/loaders/loaderSharedWithMe.ts b/packages/web-pkg/src/services/folder/loaders/loaderSharedWithMe.ts index eb0bee7fdef..9f75102e4dc 100644 --- a/packages/web-pkg/src/services/folder/loaders/loaderSharedWithMe.ts +++ b/packages/web-pkg/src/services/folder/loaders/loaderSharedWithMe.ts @@ -38,7 +38,11 @@ export class FolderLoaderSharedWithMe implements FolderLoader { ) const resources = value.map((driveItem) => - buildIncomingShareResource({ driveItem, graphRoles: sharesStore.graphRoles }) + buildIncomingShareResource({ + driveItem, + graphRoles: sharesStore.graphRoles, + serverUrl: configStore.serverUrl + }) ) resourcesStore.initResourceList({ currentFolder: null, resources }) diff --git a/packages/web-pkg/src/services/folder/loaders/loaderSharedWithOthers.ts b/packages/web-pkg/src/services/folder/loaders/loaderSharedWithOthers.ts index 8ee3f6dc715..e6bf05e50e6 100644 --- a/packages/web-pkg/src/services/folder/loaders/loaderSharedWithOthers.ts +++ b/packages/web-pkg/src/services/folder/loaders/loaderSharedWithOthers.ts @@ -39,7 +39,13 @@ export class FolderLoaderSharedWithOthers implements FolderLoader { const resources = value .filter((s) => s.permissions.some(({ link }) => !link)) - .map((driveItem) => buildOutgoingShareResource({ driveItem, user: userStore.user })) + .map((driveItem) => + buildOutgoingShareResource({ + driveItem, + user: userStore.user, + serverUrl: configStore.serverUrl + }) + ) resourcesStore.initResourceList({ currentFolder: null, resources }) }) diff --git a/packages/web-pkg/src/services/folder/loaders/loaderSpace.ts b/packages/web-pkg/src/services/folder/loaders/loaderSpace.ts index 68d0481781a..47222782291 100644 --- a/packages/web-pkg/src/services/folder/loaders/loaderSpace.ts +++ b/packages/web-pkg/src/services/folder/loaders/loaderSpace.ts @@ -42,7 +42,8 @@ export class FolderLoaderSpace implements FolderLoader { userStore, authService, spacesStore, - sharesStore + sharesStore, + configStore } = context const { webdav, graphAuthenticated: graphClient } = clientService const { replaceInvalidFileRoute } = useFileRouteReplace({ router }) @@ -79,7 +80,8 @@ export class FolderLoaderSpace implements FolderLoader { if (sharedDriveItem) { currentFolder = buildIncomingShareResource({ graphRoles: sharesStore.graphRoles, - driveItem: sharedDriveItem + driveItem: sharedDriveItem, + serverUrl: configStore.serverUrl }) } } else if (!isPersonalSpaceResource(space) && !isPublicSpaceResource(space)) { diff --git a/packages/web-runtime/src/container/bootstrap.ts b/packages/web-runtime/src/container/bootstrap.ts index d507194734b..ff8b1d9117f 100644 --- a/packages/web-runtime/src/container/bootstrap.ts +++ b/packages/web-runtime/src/container/bootstrap.ts @@ -754,6 +754,7 @@ export const registerSSEEventListeners = ({ messageStore, userStore, sharesStore, + configStore, clientService, previewService, language, diff --git a/packages/web-runtime/src/container/sse/shares.ts b/packages/web-runtime/src/container/sse/shares.ts index d064a36b4e0..7865bf370b6 100644 --- a/packages/web-runtime/src/container/sse/shares.ts +++ b/packages/web-runtime/src/container/sse/shares.ts @@ -123,6 +123,7 @@ export const onSSEShareCreatedEvent = async ({ spacesStore, sharesStore, userStore, + configStore, clientService, router }: SSEEventOptions) => { @@ -168,7 +169,11 @@ export const onSSEShareCreatedEvent = async ({ if (!driveItem) { return } - const resource = buildIncomingShareResource({ driveItem, graphRoles: sharesStore.graphRoles }) + const resource = buildIncomingShareResource({ + driveItem, + graphRoles: sharesStore.graphRoles, + serverUrl: configStore.serverUrl + }) return resourcesStore.upsertResource(resource) } @@ -179,7 +184,11 @@ export const onSSEShareCreatedEvent = async ({ if (!driveItem) { return } - const resource = buildOutgoingShareResource({ driveItem, user: userStore.user }) + const resource = buildOutgoingShareResource({ + driveItem, + user: userStore.user, + serverUrl: configStore.serverUrl + }) return resourcesStore.upsertResource(resource) } } @@ -189,6 +198,7 @@ export const onSSEShareUpdatedEvent = async ({ sharesStore, clientService, userStore, + configStore, router }: SSEEventOptions) => { if (sseData.initiatorid === clientService.initiatorId) { @@ -211,7 +221,11 @@ export const onSSEShareUpdatedEvent = async ({ if (!driveItem) { return } - const resource = buildIncomingShareResource({ driveItem, graphRoles: sharesStore.graphRoles }) + const resource = buildIncomingShareResource({ + driveItem, + graphRoles: sharesStore.graphRoles, + serverUrl: configStore.serverUrl + }) return resourcesStore.upsertResource(resource) } } @@ -308,6 +322,7 @@ export const onSSELinkCreatedEvent = async ({ resourcesStore, spacesStore, userStore, + configStore, clientService, router }: SSEEventOptions) => { @@ -353,7 +368,11 @@ export const onSSELinkCreatedEvent = async ({ if (!driveItem) { return } - const resource = buildOutgoingShareResource({ driveItem, user: userStore.user }) + const resource = buildOutgoingShareResource({ + driveItem, + user: userStore.user, + serverUrl: configStore.serverUrl + }) return resourcesStore.upsertResource(resource) } } diff --git a/packages/web-runtime/src/container/sse/types.ts b/packages/web-runtime/src/container/sse/types.ts index 738b707262a..94dc306385d 100644 --- a/packages/web-runtime/src/container/sse/types.ts +++ b/packages/web-runtime/src/container/sse/types.ts @@ -1,6 +1,7 @@ import { z } from 'zod' import { ClientService, + ConfigStore, MessageStore, PreviewService, ResourcesStore, @@ -29,6 +30,7 @@ export interface SSEEventOptions { userStore: UserStore messageStore: MessageStore sharesStore: SharesStore + configStore: ConfigStore clientService: ClientService previewService: PreviewService router: Router diff --git a/packages/web-runtime/tests/unit/container/sse/files.spec.ts b/packages/web-runtime/tests/unit/container/sse/files.spec.ts index ee1490d3bd4..0b3b0102719 100644 --- a/packages/web-runtime/tests/unit/container/sse/files.spec.ts +++ b/packages/web-runtime/tests/unit/container/sse/files.spec.ts @@ -1,6 +1,7 @@ import { ClientService, PreviewService, + useConfigStore, useMessages, useResourcesStore, useSharesStore, @@ -442,6 +443,7 @@ const getMocks = ({ const messageStore = useMessages() const userStore = useUserStore() const sharesStore = useSharesStore() + const configStore = useConfigStore() const clientService = mockDeep({ initiatorId: 'local1' }) const previewService = mockDeep() const router = mockDeep() @@ -457,6 +459,7 @@ const getMocks = ({ messageStore, userStore, sharesStore, + configStore, clientService, previewService, resourceQueue, diff --git a/packages/web-runtime/tests/unit/container/sse/helpers.spec.ts b/packages/web-runtime/tests/unit/container/sse/helpers.spec.ts index ee349be05ec..165f9b2ffed 100644 --- a/packages/web-runtime/tests/unit/container/sse/helpers.spec.ts +++ b/packages/web-runtime/tests/unit/container/sse/helpers.spec.ts @@ -3,6 +3,7 @@ import { createTestingPinia } from '@ownclouders/web-test-helpers' import { ClientService, PreviewService, + useConfigStore, useMessages, useResourcesStore, useSharesStore, @@ -109,6 +110,7 @@ const getMocks = ({ const spacesStore = useSpacesStore() const messageStore = useMessages() const userStore = useUserStore() + const configStore = useConfigStore() const sharesStore = useSharesStore() const clientService = mockDeep() const previewService = mockDeep() @@ -123,6 +125,7 @@ const getMocks = ({ messageStore, userStore, sharesStore, + configStore, clientService, previewService, resourceQueue, diff --git a/packages/web-runtime/tests/unit/container/sse/shares.spec.ts b/packages/web-runtime/tests/unit/container/sse/shares.spec.ts index f184fc6fd4b..a893b157e0b 100644 --- a/packages/web-runtime/tests/unit/container/sse/shares.spec.ts +++ b/packages/web-runtime/tests/unit/container/sse/shares.spec.ts @@ -2,6 +2,7 @@ import { ClientService, eventBus, PreviewService, + useConfigStore, useMessages, useResourcesStore, useSharesStore, @@ -697,6 +698,7 @@ const getMocks = ({ spacesStore.spaces = spaces const messageStore = useMessages() const userStore = useUserStore() + const configStore = useConfigStore() userStore.user = mockDeep({ id: '1' }) const sharesStore = useSharesStore() const clientService = mockDeep({ initiatorId: 'local1' }) @@ -734,6 +736,7 @@ const getMocks = ({ messageStore, userStore, sharesStore, + configStore, clientService, previewService, resourceQueue,