Skip to content

Commit

Permalink
🍄 Separate trash for the personal and shared drive (#189)
Browse files Browse the repository at this point in the history
  • Loading branch information
MontaGhanmy authored Oct 6, 2023
1 parent f0dcf34 commit edfd765
Show file tree
Hide file tree
Showing 34 changed files with 441 additions and 186 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { defer, Subject, throwError, timer } from "rxjs";
import { concat, delayWhen, retryWhen, take, tap } from "rxjs/operators";
import { UpsertOptions } from "..";
import { logger } from "../../../../../../framework";
import { getEntityDefinition, unwrapIndexes, unwrapPrimarykey } from "../../utils";
import { getEntityDefinition, unwrapPrimarykey } from "../../utils";
import { EntityDefinition, ColumnDefinition, ObjectType } from "../../types";
import { AbstractConnector } from "../abstract-connector";
import {
Expand Down Expand Up @@ -451,22 +451,6 @@ export class CassandraConnector extends AbstractConnector<
const instance = new (entityType as any)();
const { columnsDefinition, entityDefinition } = getEntityDefinition(instance);

const pk = unwrapPrimarykey(entityDefinition);
const indexes = unwrapIndexes(entityDefinition);

if (
Object.keys(filters).some(key => pk.indexOf(key) < 0) &&
Object.keys(filters).some(key => indexes.indexOf(key) < 0)
) {
//Filter not in primary key
throw new Error(
`All filter parameters must be defined in entity primary key,
got: ${JSON.stringify(Object.keys(filters))}
on table ${entityDefinition.name} but pk is ${JSON.stringify(pk)},
instance was ${JSON.stringify(instance)}`,
);
}

const query = buildSelectQuery<Table>(
entityType as unknown as ObjectType<Table>,
filters,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { FindOptions } from "../../repository/repository";
import { ObjectType } from "../../types";
import { getEntityDefinition, secureOperators } from "../../utils";
import { getEntityDefinition, secureOperators, filteringRequired } from "../../utils";
import { transformValueToDbString } from "./typeTransforms";

export function buildSelectQuery<Entity>(
Expand All @@ -17,12 +17,17 @@ export function buildSelectQuery<Entity>(
): string {
const instance = new (entityType as any)();
const { columnsDefinition, entityDefinition } = getEntityDefinition(instance);
let allowFiltering = false;

const where = Object.keys(filters)
.map(key => {
let result: string;
const filter = filters[key];

if (filteringRequired(key)) {
allowFiltering = true;
}

if (!filter) {
return;
}
Expand Down Expand Up @@ -77,7 +82,7 @@ export function buildSelectQuery<Entity>(
whereClause.trim().length ? "WHERE " + whereClause : ""
} ${orderByClause.trim().length ? "ORDER BY " + orderByClause : ""}`
.trimEnd()
.concat(";");
.concat(allowFiltering ? " ALLOW FILTERING;" : ";");

return query;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { UpsertOptions } from "..";
import { ListResult, Paginable, Pagination } from "../../../../../../framework/api/crud-service";
import { FindOptions } from "../../repository/repository";
import { ColumnDefinition, EntityDefinition, ObjectType } from "../../types";
import { getEntityDefinition, unwrapIndexes, unwrapPrimarykey } from "../../utils";
import { getEntityDefinition, unwrapPrimarykey } from "../../utils";
import { AbstractConnector } from "../abstract-connector";
import { buildSelectQuery } from "./query-builder";
import { transformValueFromDbString, transformValueToDbString } from "./typeTransforms";
Expand Down Expand Up @@ -186,24 +186,6 @@ export class MongoConnector extends AbstractConnector<MongoConnectionOptions, mo
const instance = new (entityType as any)();
const { columnsDefinition, entityDefinition } = getEntityDefinition(instance);

const pk = unwrapPrimarykey(entityDefinition);

const indexes = unwrapIndexes(entityDefinition);
if (
Object.keys(filters).some(key => pk.indexOf(key) < 0) &&
Object.keys(filters).some(key => indexes.indexOf(key) < 0)
) {
//Filter not in primary key
throw Error(
"All filter parameters must be defined in entity primary key, got: " +
JSON.stringify(Object.keys(filters)) +
" on table " +
entityDefinition.name +
" but pk is " +
JSON.stringify(pk),
);
}

const db = await this.getDatabase();
const collection = db.collection(`${entityDefinition.name}`);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,12 @@ export function toMongoDbOrderable(timeuuid?: string): string {
const time_str = [uuid_arr[2], uuid_arr[1], uuid_arr[0], uuid_arr[3], uuid_arr[4]].join("-");
return time_str;
}

/**
* Check if filtering is necessary
* @param {string} key
* @returns {boolean} Returns true if key is "is_in_trash" or "scope", otherwise returns false.
*/
export const filteringRequired = (key: string) => {
return key === "is_in_trash" || key === "scope";
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@ import { FileVersion } from "./file-version";
import search from "./drive-file.search";

export const TYPE = "drive_files";
export type DriveScope = "personal" | "shared";

@Entity(TYPE, {
globalIndexes: [["company_id", "parent_id"]],
globalIndexes: [
["company_id", "parent_id"],
["company_id", "is_in_trash"],
],
primaryKey: [["company_id"], "id"],
type: TYPE,
search,
Expand Down Expand Up @@ -73,6 +77,10 @@ export class DriveFile {

@Column("last_version_cache", "encoded_json")
last_version_cache: Partial<FileVersion>;

@Type(() => String)
@Column("scope", "string")
scope: DriveScope;
}

export type AccessInformation = {
Expand Down
92 changes: 71 additions & 21 deletions tdrive/backend/node/src/services/documents/services/access-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,28 +169,61 @@ export const getAccessLevel = async (
repository: Repository<DriveFile>,
context: CompanyExecutionContext & { public_token?: string; tdrive_tab_token?: string },
): Promise<DriveFileAccessLevel | "none"> => {
const isAdmin = !context?.user?.id || (await isCompanyAdmin(context));
const isMember = !context?.user?.id || (await isCompanyMember(context));

if (!id || id === "root") {
if (!context?.user?.id || (await isCompanyGuest(context))) {
return "none";
} else {
if (isMember) return "read";
return "manage";
}
}
if (id === "trash")
return (await isCompanyGuest(context)) || !context?.user?.id
? "none"
: (await isCompanyAdmin(context))
? "manage"
: "write";
if (id === "shared_with_me") return "read";

//If it is my personal folder, I have full access

if (id.startsWith("user_")) {
if (await isCompanyApplication(context)) return "read";
}

if (context?.user?.id) {
/**
* Drive root directories access management
*/
if (id === "user_" + context.user?.id) return "manage";
if (await isCompanyApplication(context)) return "manage";
return "none";
if (id === "trash_" + context.user?.id) return "manage";

if (id === "root") {
if (isAdmin) return "manage";

return "read";
}

if (id === "trash") {
if (isAdmin) return "manage";

return "read";
}

if (id === "shared_with_me") return "read";

/**
* Entity based access management
*/

if (!item) {
item = await repository.findOne({
id,
company_id: context.company.id,
});
}

if (!item) {
throw Error("Drive item doesn't exist");
}

if (item.scope === "personal" && item.creator == context.user.id) return "manage";

if (item.scope === "shared") {
if (isAdmin) return "manage";
if (isMember) return "read";
}
}

let publicToken = context.public_token;
Expand All @@ -208,13 +241,6 @@ export const getAccessLevel = async (
throw Error("Drive item doesn't exist");
}

if (await isCompanyApplication(context)) {
if (!id.startsWith("user_") && isMember && item.creator != context.user.id) {
return "read";
}
return "manage";
}

/*
* Specific user or channel rule is applied first. Then less restrictive level will be chosen
* between the parent folder and company accesses.
Expand Down Expand Up @@ -383,6 +409,30 @@ export const getSharedByUser = (
return null;
};

export const getItemScope = async (
item: DriveFile | null,
repository: Repository<DriveFile>,
context: CompanyExecutionContext,
): Promise<"personal" | "shared"> => {
let scope: "personal" | "shared";
if (item.parent_id === "user_" + context.user?.id) {
scope = "personal";
} else if (item.parent_id === "root") {
scope = "shared";
} else {
const driveItemParent = await repository.findOne(
{
company_id: context.company.id,
id: item.parent_id,
},
{},
context,
);
scope = driveItemParent.scope;
}
return scope;
};

const getGrantorAndThrowIfEmpty = (entity: AuthEntity) => {
if (!entity.grantor) {
logger.warn(`For the file permissions ${entity.id} grantor is not defined`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export class DriveFileDTOBuilder {
"access_info",
"content_keywords",
"creator",
"scope",
],
],
[
Expand Down
Loading

0 comments on commit edfd765

Please sign in to comment.