Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🍄 Separate trash for the personal and shared drive #189

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8dd1dc9
✨init model
shepilov Sep 4, 2023
4f861db
feat: updated document service and personal trash
MontaGhanmy Sep 11, 2023
4623daa
feat: updated item deletion and restore
MontaGhanmy Sep 12, 2023
e61edcf
feat: update scope when moving item
MontaGhanmy Sep 12, 2023
66c86a3
ref: refactoring item scope
MontaGhanmy Sep 15, 2023
6f462ca
ref: updated trash size logic
MontaGhanmy Sep 15, 2023
3363e60
feat: trash path switcher ui
MontaGhanmy Sep 15, 2023
6e68088
fix: public link tests
MontaGhanmy Sep 15, 2023
9c8a4cb
ref: document tests
MontaGhanmy Sep 15, 2023
46d9858
fix: e2e tests
MontaGhanmy Sep 18, 2023
dc6be2a
fix: cassandra orm
MontaGhanmy Sep 19, 2023
b4f0174
feat: updated cassandra query builder
MontaGhanmy Sep 19, 2023
cc933e2
feat: updated browse query
MontaGhanmy Sep 19, 2023
9b28b32
fix: object filtering
MontaGhanmy Sep 20, 2023
350c2de
feat: updated query tests
MontaGhanmy Sep 20, 2023
215b0bb
ref: updated orm query builder
MontaGhanmy Sep 20, 2023
85bc0ad
fix: orm test
MontaGhanmy Sep 20, 2023
75c676b
fix: updated query builder
MontaGhanmy Sep 20, 2023
9565442
feat: added is_in_trash index + ref
MontaGhanmy Sep 21, 2023
e9354d7
fix: trash ui bugs
MontaGhanmy Sep 22, 2023
0d7bbc0
🌐russian translation
shepilov Sep 25, 2023
7fa08a4
fix: context menu
MontaGhanmy Sep 26, 2023
3ead34a
merge: conflict
MontaGhanmy Sep 26, 2023
539bcd7
feat: restore/restore sub item/fix: submenu
MontaGhanmy Oct 4, 2023
35c055a
fix: bugs with navigation and restore
MontaGhanmy Oct 6, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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