Skip to content

Commit

Permalink
🕹 Manage root users
Browse files Browse the repository at this point in the history
🕹 Manage root users
  • Loading branch information
MontaGhanmy authored Aug 28, 2023
1 parent 832ab8a commit 84270a3
Show file tree
Hide file tree
Showing 29 changed files with 410 additions and 45 deletions.
3 changes: 3 additions & 0 deletions tdrive/backend/node/config/custom-environment-variables.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,5 +102,8 @@
},
"plugins": {
"server": "PLUGINS_SERVER"
},
"drive": {
"rootAdmins": "DRIVE_ROOT_ADMINS"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@ export default class ElasticSearch extends SearchAdapter implements SearchAdapte

public async connect() {
try {
let clientOptions: any = {
const clientOptions: any = {
node: this.configuration.endpoint,
ssl: {
rejectUnauthorized: false,
},
};

if (this.configuration.useAuth) {
logger.info(`Using auth for ES client`);
logger.info("Using auth for ES client");
clientOptions.auth = {
username: this.configuration.username,
password: this.configuration.password,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,26 @@ export const isCompanyGuest = async (context: CompanyExecutionContext): Promise<
return userRole === "guest" || !userRole;
};

/**
* checks the current user is a member
* Company members can access shared drive
*
* @param {CompanyExecutionContext} context
* @returns {Promise<boolean>}
*/
export const isCompanyMember = async (context: CompanyExecutionContext): Promise<boolean> => {
if (await isCompanyApplication(context)) {
return false;
}

const userRole = await globalResolver.services.companies.getUserRole(
context.company.id,
context.user?.id,
);

return userRole === "member" || !userRole;
};

/**
* checks the current user is a admin
*
Expand Down Expand Up @@ -149,8 +169,15 @@ export const getAccessLevel = async (
repository: Repository<DriveFile>,
context: CompanyExecutionContext & { public_token?: string; tdrive_tab_token?: string },
): Promise<DriveFileAccessLevel | "none"> => {
if (!id || id === "root")
return !context?.user?.id || (await isCompanyGuest(context)) ? "none" : "manage";
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"
Expand Down Expand Up @@ -182,6 +209,9 @@ export const getAccessLevel = async (
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import { CrudException, ListResult } from "../../../../core/platform/framework/a
import { File } from "../../../../services/files/entities/file";
import { UploadOptions } from "../../../../services/files/types";
import globalResolver from "../../../../services/global-resolver";
import { PaginationQueryParameters, ResourceWebsocket } from "../../../../utils/types";
import {
PaginationQueryParameters,
ResourceWebsocket,
CompanyUserRole,
} from "../../../../utils/types";
import { DriveFile } from "../../entities/drive-file";
import { FileVersion } from "../../entities/file-version";
import {
Expand All @@ -21,9 +25,21 @@ import {
} from "../../types";
import { DriveFileDTO } from "../dto/drive-file-dto";
import { DriveFileDTOBuilder } from "../../services/drive-file-dto-builder";
import { ExecutionContext } from "../../../../core/platform/framework/api/crud-service";
import gr from "../../../global-resolver";
import config from "config";

export class DocumentsController {
private driveFileDTOBuilder = new DriveFileDTOBuilder();
private rootAdmins: string[] = config.has("drive.rootAdmins")
? config.get("drive.rootAdmins")
: [];

private getCompanyUserRole(companyId: string, userId: string, context?: ExecutionContext) {
return gr.services.companies
.getCompanyUser({ id: companyId }, { id: userId }, context)
.then(a => (a ? a.level : null));
}

/**
* Creates a DriveFile item
Expand Down Expand Up @@ -166,7 +182,7 @@ export class DocumentsController {
onlyUploadedNotByMe: true,
};

return {
const data = {
...(await globalResolver.services.documents.documents.browse(id, options, context)),
websockets: request.currentUser?.id
? globalResolver.platformServices.realtime.sign(
Expand All @@ -175,6 +191,8 @@ export class DocumentsController {
)
: [],
};

return data;
};

sharedWithMe = async (
Expand Down Expand Up @@ -257,6 +275,45 @@ export class DocumentsController {
return await globalResolver.services.documents.documents.update(id, update, context);
};

updateLevel = async (
request: FastifyRequest<{
Params: ItemRequestParams;
Body: Partial<DriveFile> | any;
Querystring: { public_token?: string };
}>,
): Promise<any> => {
const { id } = request.params;
const update = request.body;

if (!id) throw new CrudException("Missing id", 400);

if (!this.rootAdmins.includes(request.currentUser.email)) {
throw new CrudException("Unauthorized access. User is not a root admin.", 401);
}

if (id == "root") {
const companyUser = await globalResolver.services.companies.getCompanyUser(
{ id: update.company_id },
{ id: update.user_id },
);
if (companyUser) {
let level = CompanyUserRole.member;
if (update.level == "manage") {
level = CompanyUserRole.admin;
}
await globalResolver.services.companies.setUserRole(
update.company_id,
update.user_id,
companyUser.role,
level,
);
} else {
throw new CrudException("User is not part of this company.", 406);
}
}
return {};
};

/**
* Create a drive file version.
*
Expand Down
7 changes: 7 additions & 0 deletions tdrive/backend/node/src/services/documents/web/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ const routes: FastifyPluginCallback = (fastify: FastifyInstance, _options, next)
handler: documentsController.update.bind(documentsController),
});

fastify.route({
method: "POST",
url: `${serviceUrl}/:id/level`,
preValidation: [fastify.authenticateOptional],
handler: documentsController.updateLevel.bind(documentsController),
});

fastify.route({
method: "DELETE",
url: `${serviceUrl}/:id`,
Expand Down
4 changes: 4 additions & 0 deletions tdrive/backend/node/src/services/user/services/companies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ export class CompanyServiceImpl {
companyId: uuid,
userId: uuid,
role: CompanyUserRole = "member",
level?: any,
applications: string[] = [],
context?: ExecutionContext,
): Promise<SaveResult<CompanyUser>> {
Expand All @@ -275,6 +276,9 @@ export class CompanyServiceImpl {
}

entity.role = role;
if (level) {
entity.level = level;
}
entity.applications = applications;
await this.companyUserRepository.save(entity, context);

Expand Down
31 changes: 31 additions & 0 deletions tdrive/backend/node/src/services/user/web/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
UserListQueryParameters,
UserObject,
UserParameters,
CompanyUsersParameters,
} from "./types";
import Company from "../entities/company";
import CompanyUser from "../entities/company_user";
Expand Down Expand Up @@ -144,6 +145,36 @@ export class UsersCrudController
};
}

async all(
request: FastifyRequest<{
Querystring: UserListQueryParameters;
Params: CompanyUsersParameters;
}>,
): Promise<ResourceListResponse<UserObject>> {
const companyId = request.params.companyId;

const users = await gr.services.users.search(
new Pagination(request.query.page_token, request.query.limit),
{
search: "",
companyId,
},
);

const resUsers = await Promise.all(
users.getEntities().map(user =>
formatUser(user, {
includeCompanies: true,
}),
),
);

// return users;
return {
resources: resUsers,
};
}

async getUserCompanies(
request: FastifyRequest<{ Params: UserParameters }>,
_reply: FastifyReply,
Expand Down
9 changes: 9 additions & 0 deletions tdrive/backend/node/src/services/user/web/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ const routes: FastifyPluginCallback = (fastify: FastifyInstance, options, next)
handler: usersController.list.bind(usersController),
});

fastify.route({
method: "POST",
url: `${usersUrl}/:companyId/all`,
preHandler: accessControl,
preValidation: [fastify.authenticate],
schema: getUsersSchema,
handler: usersController.all.bind(usersController),
});

// Get a list of companies for a user, only common companies with current user are returned.
fastify.route({
method: "GET",
Expand Down
5 changes: 5 additions & 0 deletions tdrive/backend/node/src/services/user/web/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ export interface UserParameters {
id: string;
}

export interface CompanyUsersParameters {
/* user id */
companyId: string;
}

export interface CompanyParameters {
/* company id */
id: string;
Expand Down
5 changes: 5 additions & 0 deletions tdrive/backend/node/src/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,8 @@ export interface JWTObject {
email: string;
track: boolean;
}

export enum CompanyUserRole {
member = 1,
admin = 0,
}
8 changes: 4 additions & 4 deletions tdrive/backend/node/test/e2e/common/common_test_helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,22 +39,22 @@ export default class TestHelpers {
this.platform = platform
}

private async init(newUser: boolean) {
private async init(newUser: boolean, options?: {}) {
this.dbService = await TestDbService.getInstance(this.platform, true);
if (newUser) {
this.workspace = this.platform.workspace;
const workspacePK = {id: this.workspace.workspace_id, company_id: this.workspace.company_id};
this.user = await this.dbService.createUser([workspacePK], {}, uuidv1());
this.user = await this.dbService.createUser([workspacePK], options, uuidv1());
} else {
this.user = this.platform.currentUser;
this.workspace = this.platform.workspace;
}
this.jwt = this.getJWTTokenForUser(this.user.id);
}

public static async getInstance(platform: TestPlatform, newUser = false): Promise<TestHelpers> {
public static async getInstance(platform: TestPlatform, newUser = false, options?: {}): Promise<TestHelpers> {
const helpers = new TestHelpers(platform);
await helpers.init(newUser)
await helpers.init(newUser, options)
return helpers;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ describe("The Documents Browser Window and API", () => {

it("Should not be visible for other users", async () => {
const myDriveId = "user_" + currentUser.user.id;
const anotherUser = await TestHelpers.getInstance(platform, true);
const anotherUser = await TestHelpers.getInstance(platform, true, {companyRole: "admin"});
await currentUser.uploadAllFilesOneByOne(myDriveId);
await new Promise(r => setTimeout(r, 5000));

Expand Down Expand Up @@ -107,8 +107,8 @@ describe("The Documents Browser Window and API", () => {

it("Should contain files that were shared with the user", async () => {
const sharedWIthMeFolder = "shared_with_me";
const oneUser = await TestHelpers.getInstance(platform, true);
const anotherUser = await TestHelpers.getInstance(platform, true);
const oneUser = await TestHelpers.getInstance(platform, true, {companyRole: "admin"});
const anotherUser = await TestHelpers.getInstance(platform, true, {companyRole: "admin"});
let files = await oneUser.uploadAllFilesOneByOne();
await new Promise(r => setTimeout(r, 5000));

Expand Down
Loading

0 comments on commit 84270a3

Please sign in to comment.