Skip to content

Commit 1a7b723

Browse files
committed
fix(files_sharing): Drop permissions on unmounted pending and deleted shares
Pending and deleted shares are not mounted into the user's filesystem, so generic file operations like delete or download produced a misleading "file is not available" error. These shares now carry no permissions, so every permission-aware action hides itself automatically, without the files app having to special-case each view. Conversion additionally requires read permission, matching the server-side readability check. Signed-off-by: nfebe <fenn25.fn@gmail.com>
1 parent 0f513e1 commit 1a7b723

3 files changed

Lines changed: 50 additions & 9 deletions

File tree

apps/files/src/actions/convertAction.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ export const registerConvertActions = () => {
4141
}
4242
}
4343

44+
// Conversion reads the source file, so it requires read permission
45+
if (nodes.some((node) => (node.permissions & Permission.READ) === 0)) {
46+
return false
47+
}
48+
4449
// Check that all nodes have the same mime type
4550
return nodes.every(node => from === node.mime)
4651
},

apps/files_sharing/src/services/SharingService.spec.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,34 @@ describe('SharingService share to Node mapping', () => {
533533
expect(file.attributes.favorite).toBe(0)
534534
})
535535

536+
test('Pending share has no permissions', async () => {
537+
axios.get
538+
.mockReturnValueOnce(Promise.resolve({
539+
data: { ocs: { data: [shareFile] } },
540+
}))
541+
.mockReturnValueOnce(Promise.resolve({
542+
data: { ocs: { data: [] } },
543+
}))
544+
545+
const shares = await getContents(false, false, true, false)
546+
547+
expect(axios.get).toHaveBeenCalledTimes(2)
548+
expect(shares.contents).toHaveLength(1)
549+
expect(shares.contents[0].permissions).toBe(0)
550+
})
551+
552+
test('Deleted share has no permissions', async () => {
553+
axios.get.mockReturnValueOnce(Promise.resolve({
554+
data: { ocs: { data: [shareFolder] } },
555+
}))
556+
557+
const shares = await getContents(false, false, false, true)
558+
559+
expect(axios.get).toHaveBeenCalledTimes(1)
560+
expect(shares.contents).toHaveLength(1)
561+
expect(shares.contents[0].permissions).toBe(0)
562+
})
563+
536564
test('Empty', async () => {
537565
vi.spyOn(logger, 'error').mockImplementationOnce(() => {})
538566
axios.get.mockReturnValueOnce(Promise.resolve({

apps/files_sharing/src/services/SharingService.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const headers = {
2121
'Content-Type': 'application/json',
2222
}
2323

24-
const ocsEntryToNode = async function(ocsEntry: any): Promise<Folder | File | null> {
24+
const ocsEntryToNode = async function(ocsEntry: any, unmounted = false): Promise<Folder | File | null> {
2525
try {
2626
// Federated share handling
2727
if (ocsEntry?.remote_id !== undefined) {
@@ -53,6 +53,13 @@ const ocsEntryToNode = async function(ocsEntry: any): Promise<Folder | File | nu
5353
ocsEntry.displayname_owner = ocsEntry.owner
5454
}
5555

56+
// Pending and deleted shares are not mounted into the user's filesystem,
57+
// so no file operation can act on them until they are accepted or restored.
58+
if (unmounted) {
59+
ocsEntry.item_permissions = Permission.NONE
60+
ocsEntry.permissions = Permission.NONE
61+
}
62+
5663
const isFolder = ocsEntry?.item_type === 'folder'
5764
const hasPreview = ocsEntry?.has_preview === true
5865
const Node = isFolder ? Folder : File
@@ -202,24 +209,25 @@ const groupBy = function(nodes: (Folder | File)[], key: string) {
202209
}
203210

204211
export const getContents = async (sharedWithYou = true, sharedWithOthers = true, pendingShares = false, deletedshares = false, filterTypes: number[] = []): Promise<ContentsWithRoot> => {
205-
const promises = [] as AxiosPromise<OCSResponse<any>>[]
212+
const requests = [] as { promise: AxiosPromise<OCSResponse<any>>, unmounted: boolean }[]
206213

207214
if (sharedWithYou) {
208-
promises.push(getSharedWithYou(), getRemoteShares())
215+
requests.push({ promise: getSharedWithYou(), unmounted: false }, { promise: getRemoteShares(), unmounted: false })
209216
}
210217
if (sharedWithOthers) {
211-
promises.push(getSharedWithOthers())
218+
requests.push({ promise: getSharedWithOthers(), unmounted: false })
212219
}
213220
if (pendingShares) {
214-
promises.push(getPendingShares(), getRemotePendingShares())
221+
requests.push({ promise: getPendingShares(), unmounted: true }, { promise: getRemotePendingShares(), unmounted: true })
215222
}
216223
if (deletedshares) {
217-
promises.push(getDeletedShares())
224+
requests.push({ promise: getDeletedShares(), unmounted: true })
218225
}
219226

220-
const responses = await Promise.all(promises)
221-
const data = responses.map((response) => response.data.ocs.data).flat()
222-
let contents = (await Promise.all(data.map(ocsEntryToNode)))
227+
const responses = await Promise.all(requests.map(({ promise }) => promise))
228+
const data = responses.flatMap((response, index) => response.data.ocs.data
229+
.map((entry) => ({ entry, unmounted: requests[index].unmounted })))
230+
let contents = (await Promise.all(data.map(({ entry, unmounted }) => ocsEntryToNode(entry, unmounted))))
223231
.filter((node) => node !== null) as (Folder | File)[]
224232

225233
if (filterTypes.length > 0) {

0 commit comments

Comments
 (0)