summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorskjnldsv <skjnldsv@protonmail.com>2024-06-06 18:48:02 +0200
committerJohn Molakvoæ <skjnldsv@users.noreply.github.com>2024-06-12 13:23:39 +0200
commitd991eaed92de9ac7380d8e1978c298fb91673b98 (patch)
treee2c33480cefaade7404f289d80df157be398f9e1
parent81e7b599a8984cfce0f7c91aca91bb8b40b3c0c6 (diff)
downloadnextcloud-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.ts5
-rw-r--r--apps/files/src/components/FileEntry.vue10
-rw-r--r--apps/files_sharing/src/actions/sharingStatusAction.ts22
-rw-r--r--apps/files_sharing/src/services/SharingService.spec.ts17
-rw-r--r--apps/files_sharing/src/services/SharingService.ts30
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) {