🕹 Manage root users
🕹 Manage root users
This commit is contained in:
@@ -102,5 +102,8 @@
|
||||
},
|
||||
"plugins": {
|
||||
"server": "PLUGINS_SERVER"
|
||||
},
|
||||
"drive": {
|
||||
"rootAdmins": "DRIVE_ROOT_ADMINS"
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -40,7 +40,7 @@ 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,
|
||||
@@ -48,7 +48,7 @@ export default class ElasticSearch extends SearchAdapter implements SearchAdapte
|
||||
};
|
||||
|
||||
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,
|
||||
|
||||
@@ -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
|
||||
*
|
||||
@@ -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"
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
@@ -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
|
||||
@@ -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(
|
||||
@@ -175,6 +191,8 @@ export class DocumentsController {
|
||||
)
|
||||
: [],
|
||||
};
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
sharedWithMe = async (
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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`,
|
||||
|
||||
@@ -261,6 +261,7 @@ export class CompanyServiceImpl {
|
||||
companyId: uuid,
|
||||
userId: uuid,
|
||||
role: CompanyUserRole = "member",
|
||||
level?: any,
|
||||
applications: string[] = [],
|
||||
context?: ExecutionContext,
|
||||
): Promise<SaveResult<CompanyUser>> {
|
||||
@@ -275,6 +276,9 @@ export class CompanyServiceImpl {
|
||||
}
|
||||
|
||||
entity.role = role;
|
||||
if (level) {
|
||||
entity.level = level;
|
||||
}
|
||||
entity.applications = applications;
|
||||
await this.companyUserRepository.save(entity, context);
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
UserListQueryParameters,
|
||||
UserObject,
|
||||
UserParameters,
|
||||
CompanyUsersParameters,
|
||||
} from "./types";
|
||||
import Company from "../entities/company";
|
||||
import CompanyUser from "../entities/company_user";
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -13,6 +13,11 @@ export interface UserParameters {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface CompanyUsersParameters {
|
||||
/* user id */
|
||||
companyId: string;
|
||||
}
|
||||
|
||||
export interface CompanyParameters {
|
||||
/* company id */
|
||||
id: string;
|
||||
|
||||
@@ -130,3 +130,8 @@ export interface JWTObject {
|
||||
email: string;
|
||||
track: boolean;
|
||||
}
|
||||
|
||||
export enum CompanyUserRole {
|
||||
member = 1,
|
||||
admin = 0,
|
||||
}
|
||||
|
||||
@@ -39,12 +39,12 @@ 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;
|
||||
@@ -52,9 +52,9 @@ export default class TestHelpers {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -147,7 +147,7 @@ describe("the Drive feature", () => {
|
||||
});
|
||||
|
||||
it("did search for an item", async () => {
|
||||
jest.setTimeout(10000);
|
||||
// jest.setTimeout(10000);
|
||||
const createItemResult = await createItem();
|
||||
|
||||
expect(createItemResult.id).toBeDefined();
|
||||
@@ -171,10 +171,10 @@ describe("the Drive feature", () => {
|
||||
});
|
||||
|
||||
it("did search for an item and check that all the fields for 'shared_with_me' view", async () => {
|
||||
jest.setTimeout(20000);
|
||||
// jest.setTimeout(20000);
|
||||
//given:: user uploaded one doc and give permission to another user
|
||||
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"});
|
||||
//upload files
|
||||
const doc = await oneUser.uploadRandomFileAndCreateDocument();
|
||||
await new Promise(r => setTimeout(r, 3000));
|
||||
@@ -212,10 +212,10 @@ describe("the Drive feature", () => {
|
||||
});
|
||||
|
||||
it("'shared_with_me' shouldn't return files uploaded by me", async () => {
|
||||
jest.setTimeout(20000);
|
||||
// jest.setTimeout(20000);
|
||||
//given:: user uploaded one doc and give permission to another user
|
||||
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"});
|
||||
const doc = await oneUser.uploadRandomFileAndCreateDocument();
|
||||
await new Promise(r => setTimeout(r, 3000));
|
||||
//give permissions to the file
|
||||
@@ -277,7 +277,7 @@ describe("the Drive feature", () => {
|
||||
});
|
||||
|
||||
it("did search by mime type", async () => {
|
||||
jest.setTimeout(10000);
|
||||
// jest.setTimeout(10000);
|
||||
// given:: all the sample files uploaded and documents for them created
|
||||
await currentUser.uploadAllFilesOneByOne();
|
||||
|
||||
@@ -295,8 +295,8 @@ describe("the Drive feature", () => {
|
||||
});
|
||||
|
||||
it("did search by last modified", async () => {
|
||||
jest.setTimeout(10000);
|
||||
const user = await TestHelpers.getInstance(platform, true);
|
||||
// jest.setTimeout(10000);
|
||||
const user = await TestHelpers.getInstance(platform, true, {companyRole: "admin"});
|
||||
// given:: all the sample files uploaded and documents for them created
|
||||
const start = new Date().getTime();
|
||||
await user.uploadAllFilesOneByOne();
|
||||
@@ -320,8 +320,8 @@ describe("the Drive feature", () => {
|
||||
|
||||
it("did search a file shared by another user", async () => {
|
||||
//given:
|
||||
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"});
|
||||
//upload files
|
||||
let files = await oneUser.uploadAllFilesOneByOne();
|
||||
|
||||
@@ -348,10 +348,10 @@ describe("the Drive feature", () => {
|
||||
}, 30000000);
|
||||
|
||||
it("did search a file by file owner", async () => {
|
||||
jest.setTimeout(30000);
|
||||
// jest.setTimeout(30000);
|
||||
//given:
|
||||
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"});
|
||||
//upload files
|
||||
let files = await oneUser.uploadAllFilesOneByOne();
|
||||
await anotherUser.uploadAllFilesOneByOne();
|
||||
@@ -386,8 +386,8 @@ describe("the Drive feature", () => {
|
||||
});
|
||||
|
||||
it("did search by 'added' date", async () => {
|
||||
jest.setTimeout(10000);
|
||||
const user = await TestHelpers.getInstance(platform, true);
|
||||
// jest.setTimeout(10000);
|
||||
const user = await TestHelpers.getInstance(platform, true, {companyRole: "admin"});
|
||||
// given:: all the sample files uploaded and documents for them created
|
||||
await user.uploadRandomFileAndCreateDocument();
|
||||
const start = new Date().getTime();
|
||||
@@ -411,7 +411,7 @@ describe("the Drive feature", () => {
|
||||
});
|
||||
|
||||
it("did search order by name", async () => {
|
||||
const user = await TestHelpers.getInstance(platform, true);
|
||||
const user = await TestHelpers.getInstance(platform, true, {companyRole: "admin"});
|
||||
// given:: all the sample files uploaded and documents for them created
|
||||
await user.uploadAllFilesAndCreateDocuments();
|
||||
//wait for putting docs to elastic and its indexing
|
||||
@@ -430,8 +430,8 @@ describe("the Drive feature", () => {
|
||||
}, 30000);
|
||||
|
||||
it("did search order by name desc", async () => {
|
||||
jest.setTimeout(10000);
|
||||
const user = await TestHelpers.getInstance(platform, true);
|
||||
// jest.setTimeout(10000);
|
||||
const user = await TestHelpers.getInstance(platform, true, {companyRole: "admin"});
|
||||
// given:: all the sample files uploaded and documents for them created
|
||||
await user.uploadAllFilesOneByOne();
|
||||
//wait for putting docs to elastic and its indexing
|
||||
@@ -450,8 +450,8 @@ describe("the Drive feature", () => {
|
||||
});
|
||||
|
||||
it("did search order by added date", async () => {
|
||||
jest.setTimeout(10000);
|
||||
const user = await TestHelpers.getInstance(platform, true);
|
||||
// jest.setTimeout(10000);
|
||||
const user = await TestHelpers.getInstance(platform, true, {companyRole: "admin"});
|
||||
// given:: all the sample files uploaded and documents for them created
|
||||
await user.uploadAllFilesOneByOne();
|
||||
//wait for putting docs to elastic and its indexing
|
||||
@@ -470,8 +470,8 @@ describe("the Drive feature", () => {
|
||||
});
|
||||
|
||||
it("did search order by added date desc", async () => {
|
||||
jest.setTimeout(10000);
|
||||
const user = await TestHelpers.getInstance(platform, true);
|
||||
// jest.setTimeout(10000);
|
||||
const user = await TestHelpers.getInstance(platform, true, {companyRole: "admin"});
|
||||
// given:: all the sample files uploaded and documents for them created
|
||||
await user.uploadAllFilesOneByOne();
|
||||
//wait for putting docs to elastic and its indexing
|
||||
|
||||
@@ -66,7 +66,7 @@ describe("the public links feature", () => {
|
||||
describe("Basic Flow", () => {
|
||||
|
||||
const createItem = async (): Promise<DriveFileMockClass> => {
|
||||
await TestDbService.getInstance(platform, true);
|
||||
await TestHelpers.getInstance(platform, true, {companyRole: "admin"});
|
||||
|
||||
const item = {
|
||||
name: "public file",
|
||||
|
||||
Reference in New Issue
Block a user