diff options
author | skjnldsv <skjnldsv@protonmail.com> | 2024-06-06 18:48:02 +0200 |
---|---|---|
committer | John Molakvoæ <skjnldsv@users.noreply.github.com> | 2024-06-12 13:23:39 +0200 |
commit | d991eaed92de9ac7380d8e1978c298fb91673b98 (patch) | |
tree | e2c33480cefaade7404f289d80df157be398f9e1 | |
parent | 81e7b599a8984cfce0f7c91aca91bb8b40b3c0c6 (diff) | |
download | nextcloud-server-d991eaed92de9ac7380d8e1978c298fb91673b98.tar.gz nextcloud-server-d991eaed92de9ac7380d8e1978c298fb91673b98.zip |
fix(files_sharing): fix parsing of remote shares
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
-rw-r--r-- | apps/files/src/actions/moveOrCopyActionUtils.ts | 5 | ||||
-rw-r--r-- | apps/files/src/components/FileEntry.vue | 10 | ||||
-rw-r--r-- | apps/files_sharing/src/actions/sharingStatusAction.ts | 22 | ||||
-rw-r--r-- | apps/files_sharing/src/services/SharingService.spec.ts | 17 | ||||
-rw-r--r-- | apps/files_sharing/src/services/SharingService.ts | 30 |
5 files changed, 61 insertions, 23 deletions
diff --git a/apps/files/src/actions/moveOrCopyActionUtils.ts b/apps/files/src/actions/moveOrCopyActionUtils.ts index 01614b14d8a..82a12761cb9 100644 --- a/apps/files/src/actions/moveOrCopyActionUtils.ts +++ b/apps/files/src/actions/moveOrCopyActionUtils.ts @@ -70,7 +70,8 @@ export const canDownload = (nodes: Node[]) => { } export const canCopy = (nodes: Node[]) => { - // For now the only restriction is that a shared file - // cannot be copied if the download is disabled + // a shared file cannot be copied if the download is disabled + // it can be copied if the user has at least read permissions return canDownload(nodes) + && !nodes.some(node => node.permissions === Permission.NONE) } diff --git a/apps/files/src/components/FileEntry.vue b/apps/files/src/components/FileEntry.vue index 5d9fcf0e253..a0c0cfe9699 100644 --- a/apps/files/src/components/FileEntry.vue +++ b/apps/files/src/components/FileEntry.vue @@ -82,7 +82,7 @@ class="files-list__row-mtime" data-cy-files-list-row-mtime @click="openDetailsIfAvailable"> - <NcDateTime :timestamp="source.mtime" :ignore-seconds="true" /> + <NcDateTime v-if="source.mtime" :timestamp="source.mtime" :ignore-seconds="true" /> </td> <!-- View columns --> @@ -194,8 +194,8 @@ export default defineComponent({ }, size() { - const size = parseInt(this.source.size, 10) || 0 - if (typeof size !== 'number' || size < 0) { + const size = parseInt(this.source.size, 10) + if (typeof size !== 'number' || isNaN(size) || size < 0) { return this.t('files', 'Pending') } return formatFileSize(size, true) @@ -203,8 +203,8 @@ export default defineComponent({ sizeOpacity() { const maxOpacitySize = 10 * 1024 * 1024 - const size = parseInt(this.source.size, 10) || 0 - if (!size || size < 0) { + const size = parseInt(this.source.size, 10) + if (!size || isNaN(size) || size < 0) { return {} } diff --git a/apps/files_sharing/src/actions/sharingStatusAction.ts b/apps/files_sharing/src/actions/sharingStatusAction.ts index 98a7d3d6112..828408f1846 100644 --- a/apps/files_sharing/src/actions/sharingStatusAction.ts +++ b/apps/files_sharing/src/actions/sharingStatusAction.ts @@ -34,14 +34,18 @@ import { getCurrentUser } from '@nextcloud/auth' import './sharingStatusAction.scss' -const generateAvatarSvg = (userId: string) => { - const avatarUrl = generateUrl('/avatar/{userId}/32', { userId }) +const generateAvatarSvg = (userId: string, isGuest = false) => { + const avatarUrl = generateUrl(isGuest ? '/avatar/guest/{userId}/32' : '/avatar/{userId}/32?guestFallback=true', { userId }) return `<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" class="sharing-status__avatar"> <image href="${avatarUrl}" height="32" width="32" /> </svg>` } +const isExternal = (node: Node) => { + return node.attributes.remote_id !== undefined +} + export const action = new FileAction({ id: 'sharing-status', displayName(nodes: Node[]) { @@ -50,7 +54,7 @@ export const action = new FileAction({ const ownerId = node?.attributes?.['owner-id'] if (shareTypes.length > 0 - || (ownerId && ownerId !== getCurrentUser()?.uid)) { + || (ownerId !== getCurrentUser()?.uid || isExternal(node))) { return t('files_sharing', 'Shared') } @@ -63,11 +67,11 @@ export const action = new FileAction({ const ownerDisplayName = node?.attributes?.['owner-display-name'] // Mixed share types - if (Array.isArray(node.attributes?.['share-types'])) { + if (Array.isArray(node.attributes?.['share-types']) && node.attributes?.['share-types'].length > 1) { return t('files_sharing', 'Shared multiple times with different people') } - if (ownerId && ownerId !== getCurrentUser()?.uid) { + if (ownerId && (ownerId !== getCurrentUser()?.uid || isExternal(node))) { return t('files_sharing', 'Shared by {ownerDisplayName}', { ownerDisplayName }) } @@ -79,7 +83,7 @@ export const action = new FileAction({ const shareTypes = Object.values(node?.attributes?.['share-types'] || {}).flat() as number[] // Mixed share types - if (Array.isArray(node.attributes?.['share-types'])) { + if (Array.isArray(node.attributes?.['share-types']) && node.attributes?.['share-types'].length > 1) { return AccountPlusSvg } @@ -101,8 +105,8 @@ export const action = new FileAction({ } const ownerId = node?.attributes?.['owner-id'] - if (ownerId && ownerId !== getCurrentUser()?.uid) { - return generateAvatarSvg(ownerId) + if (ownerId && (ownerId !== getCurrentUser()?.uid || isExternal(node))) { + return generateAvatarSvg(ownerId, isExternal(node)) } return AccountPlusSvg @@ -124,7 +128,7 @@ export const action = new FileAction({ } // If the node is shared by someone else - if (ownerId && ownerId !== getCurrentUser()?.uid) { + if (ownerId && (ownerId !== getCurrentUser()?.uid || isExternal(node))) { return true } diff --git a/apps/files_sharing/src/services/SharingService.spec.ts b/apps/files_sharing/src/services/SharingService.spec.ts index 54e2355b082..ed3a0cb6590 100644 --- a/apps/files_sharing/src/services/SharingService.spec.ts +++ b/apps/files_sharing/src/services/SharingService.spec.ts @@ -346,12 +346,27 @@ describe('SharingService share to Node mapping', () => { expect(folder.attributes.favorite).toBe(1) }) + test('Empty', async () => { + jest.spyOn(logger, 'error').mockImplementationOnce(() => {}) + jest.spyOn(axios, 'get').mockReturnValueOnce(Promise.resolve({ + data: { + ocs: { + data: [], + }, + }, + })) + + const shares = await getContents(false, true, false, false) + expect(shares.contents).toHaveLength(0) + expect(logger.error).toHaveBeenCalledTimes(0) + }) + test('Error', async () => { jest.spyOn(logger, 'error').mockImplementationOnce(() => {}) jest.spyOn(axios, 'get').mockReturnValueOnce(Promise.resolve({ data: { ocs: { - data: [{}], + data: [null], }, }, })) diff --git a/apps/files_sharing/src/services/SharingService.ts b/apps/files_sharing/src/services/SharingService.ts index 2f167dab535..a4c63bec7b0 100644 --- a/apps/files_sharing/src/services/SharingService.ts +++ b/apps/files_sharing/src/services/SharingService.ts @@ -22,7 +22,7 @@ /* eslint-disable camelcase, n/no-extraneous-import */ import type { AxiosPromise } from 'axios' -import { Folder, File, type ContentsWithRoot } from '@nextcloud/files' +import { Folder, File, type ContentsWithRoot, Permission } from '@nextcloud/files' import { generateOcsUrl, generateRemoteUrl } from '@nextcloud/router' import { getCurrentUser } from '@nextcloud/auth' import axios from '@nextcloud/axios' @@ -46,16 +46,34 @@ const headers = { 'Content-Type': 'application/json', } -const ocsEntryToNode = function(ocsEntry: any): Folder | File | null { +const ocsEntryToNode = async function(ocsEntry: any): Promise<Folder | File | null> { try { + // Federated share handling + if (ocsEntry?.remote_id !== undefined) { + const mime = (await import('mime')).default + // This won't catch files without an extension, but this is the best we can do + ocsEntry.mimetype = mime.getType(ocsEntry.name) + ocsEntry.item_type = ocsEntry.mimetype ? 'file' : 'folder' + + // Need to set permissions to NONE for federated shares + ocsEntry.item_permissions = Permission.NONE + ocsEntry.permissions = Permission.NONE + + ocsEntry.uid_owner = ocsEntry.owner + // TODO: have the real display name stored somewhere + ocsEntry.displayname_owner = ocsEntry.owner + } + const isFolder = ocsEntry?.item_type === 'folder' const hasPreview = ocsEntry?.has_preview === true const Node = isFolder ? Folder : File - const fileid = ocsEntry.file_source + // If this is an external share that is not yet accepted, + // we don't have an id. We can fallback to the row id temporarily + const fileid = ocsEntry.file_source || ocsEntry.id // Generate path and strip double slashes - const path = ocsEntry?.path || ocsEntry.file_target + const path = ocsEntry?.path || ocsEntry.file_target || ocsEntry.name const source = generateRemoteUrl(`dav/${rootPath}/${path}`.replaceAll(/\/\//gm, '/')) // Prefer share time if more recent than item mtime @@ -68,7 +86,7 @@ const ocsEntryToNode = function(ocsEntry: any): Folder | File | null { id: fileid, source, owner: ocsEntry?.uid_owner, - mime: ocsEntry?.mimetype, + mime: ocsEntry?.mimetype || 'application/octet-stream', mtime, size: ocsEntry?.item_size, permissions: ocsEntry?.item_permissions || ocsEntry?.permissions, @@ -177,7 +195,7 @@ export const getContents = async (sharedWithYou = true, sharedWithOthers = true, const responses = await Promise.all(promises) const data = responses.map((response) => response.data.ocs.data).flat() - let contents = data.map(ocsEntryToNode) + let contents = (await Promise.all(data.map(ocsEntryToNode))) .filter((node) => node !== null) as (Folder | File)[] if (filterTypes.length > 0) { |