From 882120bef025b187ca7f18bb241b11ae69f719c5 Mon Sep 17 00:00:00 2001 From: Eric Doughty-Papassideris Date: Wed, 22 May 2024 04:35:27 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=A9=B9=20backend:=20when=20anonymous=20us?= =?UTF-8?q?er=20deletes=20file,=20update=20creator=20to=20parent=20folder?= =?UTF-8?q?=20(#433)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/services/documents/services/index.ts | 51 +++++++++++++++++ .../node/src/services/documents/utils.ts | 55 +++++++++++++------ 2 files changed, 90 insertions(+), 16 deletions(-) diff --git a/tdrive/backend/node/src/services/documents/services/index.ts b/tdrive/backend/node/src/services/documents/services/index.ts index 8eef9a4c..6814d42a 100644 --- a/tdrive/backend/node/src/services/documents/services/index.ts +++ b/tdrive/backend/node/src/services/documents/services/index.ts @@ -15,6 +15,8 @@ import { hasCompanyAdminLevel } from "../../../utils/company"; import gr from "../../global-resolver"; import { DriveFile, TYPE } from "../entities/drive-file"; import { FileVersion, TYPE as FileVersionType } from "../entities/file-version"; +import User, { TYPE as UserType } from "../../user/entities/user"; + import { DriveTdriveTab as DriveTdriveTabEntity, TYPE as DriveTdriveTabRepoType, @@ -56,12 +58,14 @@ import { import archiver from "archiver"; import internal from "stream"; import config from "config"; + export class DocumentsService { version: "1"; repository: Repository; searchRepository: SearchRepository; fileVersionRepository: Repository; driveTdriveTabRepository: Repository; + userRepository: Repository; ROOT: RootType = "root"; TRASH: TrashType = "trash"; quotaEnabled: boolean = config.has("drive.featureUserQuota") @@ -88,8 +92,10 @@ export class DocumentsService { DriveTdriveTabRepoType, DriveTdriveTabEntity, ); + this.userRepository = await globalResolver.database.getRepository(UserType, User); } catch (error) { logger.error({ error: `${error}` }, "Error while initializing Documents Service"); + throw error; } return this; @@ -670,6 +676,51 @@ export class DocumentsService { } else { //This item is not in trash, we move it to trash item.is_in_trash = true; + // Check item belongs to someone + if (item.creator !== context?.user?.id) { + const creator = await this.userRepository.findOne({ id: item.creator }); + if (creator.type === "anonymous") { + const loadedCreators = new Map(); + const path = await getPath( + item.id, + this.repository, + true, + context, + async item => { + if (!item.creator) return true; + const user = + loadedCreators.get(item.creator) ?? + (await this.userRepository.findOne({ id: item.creator })); + loadedCreators.set(item.creator, user); + return user.type !== "anonymous"; + }, + true, + ); + const [firstOwnedItem] = path; + if (firstOwnedItem) { + const firstKnownCreator = loadedCreators.get(firstOwnedItem.creator); + const accessEntitiesWithoutUser = item.access_info.entities.filter( + ({ id, type }) => type != "user" || id != firstKnownCreator.id, + ); + item.access_info.entities = [ + ...accessEntitiesWithoutUser, + { + type: "user", + id: firstKnownCreator.id, + level: "manage", + grantor: context.user.id, + }, + ]; + item.creator = firstKnownCreator.id; + } else { + // Move to company trash + item.parent_id = "trash"; + item.scope = "shared"; + } + await this.repository.save(item); + } + } + await this.update(item.id, item, context); } await updateItemSize(previousParentId, this.repository, context); diff --git a/tdrive/backend/node/src/services/documents/utils.ts b/tdrive/backend/node/src/services/documents/utils.ts index c62338ef..0dc909a3 100644 --- a/tdrive/backend/node/src/services/documents/utils.ts +++ b/tdrive/backend/node/src/services/documents/utils.ts @@ -256,30 +256,44 @@ export const updateItemSize = async ( }; /** - * gets the path for the driveitem + * Get a list of parents for the provided DriveFile id, in top-down order, + * but internally iterated towards the top. * - * @param {string} id - * @param {Repository} repository - * @param {boolean} ignoreAccess - * @param {CompanyExecutionContext} context - * @returns + * @param {boolean} ignoreAccess If user from context doesn't have + * read access to an item, the item is not included and traversing + * towards parents is stopped there. + * @param {(item: DriveFile) => Promise} predicate If set, + * returned items in the array include only those for which the + * `predicate`'s result resolved to true. + * @param {boolean?} stopAtFirstMatch If true, the lowest item + * in the hierarchy that matches the `predicate` will be the + * only item in the returned array. + * @returns A promise to an array of DriveFile entries in order + * starting from the root (eg. "My Drive"), and ending in the + * DriveFile matching the provided `id` ; both included. + * + * If `stopAtFirstMatch` is true and `predicate` is provided, the + * result is an array with a single item or an empty array. */ export const getPath = async ( id: string, repository: Repository, ignoreAccess?: boolean, context?: DriveExecutionContext, + predicate?: (item: DriveFile) => Promise, + stopAtFirstMatch: boolean = false, ): Promise => { id = id || "root"; - if (isVirtualFolder(id)) - return !context?.user?.public_token_document_id || ignoreAccess - ? [ - { - id, - name: await getVirtualFoldersNames(id, context), - } as DriveFile, - ] + if (isVirtualFolder(id)) { + const virtualItem = { + id, + name: await getVirtualFoldersNames(id, context), + } as DriveFile; + return (!context?.user?.public_token_document_id || ignoreAccess) && + (!predicate || (await predicate(virtualItem))) + ? [virtualItem] : []; + } const item = await repository.findOne({ id, company_id: context.company.id, @@ -288,8 +302,17 @@ export const getPath = async ( if (!item || (!(await checkAccess(id, item, "read", repository, context)) && !ignoreAccess)) { return []; } - - return [...(await getPath(item.parent_id, repository, ignoreAccess, context)), item]; + const isMatch = !predicate || (await predicate(item)); + if (stopAtFirstMatch && isMatch) return [item]; + const parents = await getPath( + item.parent_id, + repository, + ignoreAccess, + context, + predicate, + stopAtFirstMatch, + ); + return isMatch ? [...parents, item] : parents; }; /**