🩹 backend: when anonymous user deletes file, update creator to parent folder (#433)

This commit is contained in:
Eric Doughty-Papassideris
2024-05-22 04:35:27 +02:00
committed by ericlinagora
parent c0bd6df482
commit 882120bef0
2 changed files with 90 additions and 16 deletions
@@ -15,6 +15,8 @@ import { hasCompanyAdminLevel } from "../../../utils/company";
import gr from "../../global-resolver"; import gr from "../../global-resolver";
import { DriveFile, TYPE } from "../entities/drive-file"; import { DriveFile, TYPE } from "../entities/drive-file";
import { FileVersion, TYPE as FileVersionType } from "../entities/file-version"; import { FileVersion, TYPE as FileVersionType } from "../entities/file-version";
import User, { TYPE as UserType } from "../../user/entities/user";
import { import {
DriveTdriveTab as DriveTdriveTabEntity, DriveTdriveTab as DriveTdriveTabEntity,
TYPE as DriveTdriveTabRepoType, TYPE as DriveTdriveTabRepoType,
@@ -56,12 +58,14 @@ import {
import archiver from "archiver"; import archiver from "archiver";
import internal from "stream"; import internal from "stream";
import config from "config"; import config from "config";
export class DocumentsService { export class DocumentsService {
version: "1"; version: "1";
repository: Repository<DriveFile>; repository: Repository<DriveFile>;
searchRepository: SearchRepository<DriveFile>; searchRepository: SearchRepository<DriveFile>;
fileVersionRepository: Repository<FileVersion>; fileVersionRepository: Repository<FileVersion>;
driveTdriveTabRepository: Repository<DriveTdriveTabEntity>; driveTdriveTabRepository: Repository<DriveTdriveTabEntity>;
userRepository: Repository<User>;
ROOT: RootType = "root"; ROOT: RootType = "root";
TRASH: TrashType = "trash"; TRASH: TrashType = "trash";
quotaEnabled: boolean = config.has("drive.featureUserQuota") quotaEnabled: boolean = config.has("drive.featureUserQuota")
@@ -88,8 +92,10 @@ export class DocumentsService {
DriveTdriveTabRepoType, DriveTdriveTabRepoType,
DriveTdriveTabEntity, DriveTdriveTabEntity,
); );
this.userRepository = await globalResolver.database.getRepository<User>(UserType, User);
} catch (error) { } catch (error) {
logger.error({ error: `${error}` }, "Error while initializing Documents Service"); logger.error({ error: `${error}` }, "Error while initializing Documents Service");
throw error;
} }
return this; return this;
@@ -670,6 +676,51 @@ export class DocumentsService {
} else { } else {
//This item is not in trash, we move it to trash //This item is not in trash, we move it to trash
item.is_in_trash = true; 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<string, User>();
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 this.update(item.id, item, context);
} }
await updateItemSize(previousParentId, this.repository, context); await updateItemSize(previousParentId, this.repository, context);
@@ -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 {boolean} ignoreAccess If user from context doesn't have
* @param {Repository<DriveFile>} repository * read access to an item, the item is not included and traversing
* @param {boolean} ignoreAccess * towards parents is stopped there.
* @param {CompanyExecutionContext} context * @param {(item: DriveFile) => Promise<boolean>} predicate If set,
* @returns * 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 ( export const getPath = async (
id: string, id: string,
repository: Repository<DriveFile>, repository: Repository<DriveFile>,
ignoreAccess?: boolean, ignoreAccess?: boolean,
context?: DriveExecutionContext, context?: DriveExecutionContext,
predicate?: (item: DriveFile) => Promise<boolean>,
stopAtFirstMatch: boolean = false,
): Promise<DriveFile[]> => { ): Promise<DriveFile[]> => {
id = id || "root"; id = id || "root";
if (isVirtualFolder(id)) if (isVirtualFolder(id)) {
return !context?.user?.public_token_document_id || ignoreAccess const virtualItem = {
? [ id,
{ name: await getVirtualFoldersNames(id, context),
id, } as DriveFile;
name: await getVirtualFoldersNames(id, context), return (!context?.user?.public_token_document_id || ignoreAccess) &&
} as DriveFile, (!predicate || (await predicate(virtualItem)))
] ? [virtualItem]
: []; : [];
}
const item = await repository.findOne({ const item = await repository.findOne({
id, id,
company_id: context.company.id, company_id: context.company.id,
@@ -288,8 +302,17 @@ export const getPath = async (
if (!item || (!(await checkAccess(id, item, "read", repository, context)) && !ignoreAccess)) { if (!item || (!(await checkAccess(id, item, "read", repository, context)) && !ignoreAccess)) {
return []; return [];
} }
const isMatch = !predicate || (await predicate(item));
return [...(await getPath(item.parent_id, repository, ignoreAccess, context)), item]; if (stopAtFirstMatch && isMatch) return [item];
const parents = await getPath(
item.parent_id,
repository,
ignoreAccess,
context,
predicate,
stopAtFirstMatch,
);
return isMatch ? [...parents, item] : parents;
}; };
/** /**