diff --git a/src/frontend/apps/drive/src/features/explorer/utils/__tests__/itemToPreviewFile.test.ts b/src/frontend/apps/drive/src/features/explorer/utils/__tests__/itemToPreviewFile.test.ts new file mode 100644 index 00000000..23e8ad78 --- /dev/null +++ b/src/frontend/apps/drive/src/features/explorer/utils/__tests__/itemToPreviewFile.test.ts @@ -0,0 +1,112 @@ +import { Item, ItemType } from "@/features/drivers/types"; +import { + getNavigableItem, + isFolderAccessDenied, +} from "@/features/drivers/utils"; +import { itemToPreviewFile } from "../utils"; + +jest.mock("@/features/config/Config", () => ({ getDriver: jest.fn() })); +jest.mock("@/features/i18n/initI18n", () => ({ t: (key: string) => key })); + +const folder = { + id: "folder", + title: "Top.secret.pdf", + type: ItemType.FOLDER, + abilities: {}, +} as Item; + +const restriction: Item = { + ...folder, + id: "restriction", + type: ItemType.RESTRICTION, + target: { + id: folder.id, + title: folder.title, + path: "folder", + is_restricted: true, + can_access: false, + deleted: false, + abilities: folder.abilities, + }, +}; + +describe("folder preview access", () => { + it("previews an inaccessible restriction using its entry ID and full name", () => { + expect(isFolderAccessDenied(restriction)).toBe(true); + expect(getNavigableItem(restriction)).toBeUndefined(); + expect(itemToPreviewFile(restriction)).toMatchObject({ + id: restriction.id, + title: "Top.secret.pdf", + isFolderAccessDenied: true, + mimetype: "", + url: "", + url_preview: "", + size: 0, + }); + }); + + it("does not pass stale content URLs or metadata to a denied folder preview", () => { + expect( + itemToPreviewFile({ + ...restriction, + mimetype: "application/pdf", + url: "/original.pdf", + url_preview: "/preview.pdf", + size: 100, + }), + ).toMatchObject({ + isFolderAccessDenied: true, + mimetype: "", + url: "", + url_preview: "", + size: 0, + }); + }); + + it.each([false, true])( + "keeps normal folder navigation when is_restricted is %s", + (is_restricted) => { + const item = { ...folder, is_restricted }; + expect(isFolderAccessDenied(item)).toBe(false); + expect(itemToPreviewFile(item).isFolderAccessDenied).toBe(false); + expect(getNavigableItem(item)).toBe(item); + }, + ); + + it("navigates to the target of an accessible restriction", () => { + const item = { + ...restriction, + target: { ...restriction.target!, can_access: true }, + }; + expect(isFolderAccessDenied(item)).toBe(false); + expect(getNavigableItem(item)).toMatchObject({ id: folder.id }); + }); + + it.each([null, { ...restriction.target!, deleted: true }])( + "keeps missing or deleted targets unavailable", + (target) => { + const item = { ...restriction, target }; + expect(isFolderAccessDenied(item)).toBe(false); + expect(getNavigableItem(item)).toBeUndefined(); + }, + ); + + it("preserves file metadata and keeps the denied-folder flag off", () => { + const item = { + ...folder, + type: ItemType.FILE, + mimetype: "application/pdf", + url: "/original.pdf", + url_preview: "/preview.pdf", + size: 100, + }; + expect(itemToPreviewFile(item)).toMatchObject({ + id: item.id, + mimetype: item.mimetype, + url: item.url, + url_preview: item.url_preview, + size: item.size, + isFolderAccessDenied: false, + }); + }); +}); diff --git a/src/frontend/apps/drive/src/features/explorer/utils/utils.ts b/src/frontend/apps/drive/src/features/explorer/utils/utils.ts index 54e0982f..2edd9191 100644 --- a/src/frontend/apps/drive/src/features/explorer/utils/utils.ts +++ b/src/frontend/apps/drive/src/features/explorer/utils/utils.ts @@ -9,6 +9,7 @@ import { import i18n from "@/features/i18n/initI18n"; import { FilePreviewType } from "@gouvfr-lasuite/ui-components"; import { DefaultRoute } from "@/utils/defaultRoutes"; +import { isFolderAccessDenied } from "@/features/drivers/utils"; /** * When passing through my-files, favorites, shared with me, etc. we set a fromRoute key in the session storage. @@ -200,15 +201,17 @@ export const getItemTitle = (item: Item) => { return item.title; }; -export const itemToPreviewFile = (item: Item) => { +export const itemToPreviewFile = (item: Item): FilePreviewType => { + const accessDenied = isFolderAccessDenied(item); return { id: item.id, title: item.title, - mimetype: item.mimetype ?? "", - url_preview: item.url_preview ?? "", - url: item.url ?? "", + mimetype: accessDenied ? "" : (item.mimetype ?? ""), + url_preview: accessDenied ? "" : (item.url_preview ?? ""), + url: accessDenied ? "" : (item.url ?? ""), + isFolderAccessDenied: accessDenied, isSuspicious: item.upload_state === ItemUploadState.SUSPICIOUS, is_wopi_supported: item.is_wopi_supported, - size: item.size, - } as FilePreviewType; + size: accessDenied ? 0 : (item.size ?? 0), + }; }; diff --git a/src/frontend/apps/drive/src/features/ui/preview/CustomFilesPreview.tsx b/src/frontend/apps/drive/src/features/ui/preview/CustomFilesPreview.tsx index 1b604e0f..bbcdf842 100644 --- a/src/frontend/apps/drive/src/features/ui/preview/CustomFilesPreview.tsx +++ b/src/frontend/apps/drive/src/features/ui/preview/CustomFilesPreview.tsx @@ -1,4 +1,5 @@ import { Item, ItemType } from "@/features/drivers/types"; +import { isFolderAccessDenied } from "@/features/drivers/utils"; import { FilePreview, FilePreviewType, @@ -41,7 +42,9 @@ export const CustomFilesPreview = ({ const files = useMemo(() => { return items - .filter((item) => item.type === ItemType.FILE) + .filter( + (item) => item.type === ItemType.FILE || isFolderAccessDenied(item), + ) .map(itemToPreviewFile); }, [items]);