🛠 Allow applications to manage the drive (#59)

* Now applications can manage the drive as they wish

* Fix typo

* Fix the access computation

* Test if my code is the issue

* Fix test if app exists

* Fix test if app exists

* Fix test if app exists
This commit is contained in:
Romaric Mourgues
2023-05-22 13:16:51 +02:00
committed by GitHub
parent 4af8e43bb2
commit 62f3b562c0
7 changed files with 124 additions and 15 deletions
@@ -45,7 +45,7 @@ export default class LocalConnectorService implements StorageConnectorAPI {
async read(path: string): Promise<Readable> {
const fullPath = this.getFullPath(path);
if (!fs.existsSync(fullPath)) {
throw new Error("File doesn't not exists");
throw new Error("File doesn't exists");
}
return createReadStream(fullPath);
}
@@ -11,7 +11,7 @@ export class ApplicationServiceImpl implements TdriveServiceProvider, Initializa
}
async get(id: string, _: ExecutionContext): Promise<Application> {
return (await this.list(_)).find((app: Application) => app.id === id);
return (await this.list(_)).find((app: Application) => app?.id === id);
}
async list(_: ExecutionContext): Promise<Application[]> {
@@ -18,19 +18,28 @@ export class CompanyApplicationServiceImpl implements TdriveServiceProvider, Ini
pk: Pick<CompanyApplicationPrimaryKey, "company_id" | "application_id"> & { id?: string },
context?: CompanyExecutionContext,
): Promise<CompanyApplicationWithApplication> {
const application = await gr.services.applications.marketplaceApps.get(
pk.application_id,
context,
);
try {
const application = await gr.services.applications.marketplaceApps.get(
pk.application_id,
context,
);
return {
...{
id: pk.application_id,
company_id: pk.company_id,
application_id: pk.application_id,
},
application: application,
};
if (!application?.id) {
return null;
}
return {
...{
id: pk.application_id,
company_id: pk.company_id,
application_id: pk.application_id,
},
application: application,
};
} catch (err) {
console.error(err);
return null;
}
}
async list<ListOptions>(
@@ -21,6 +21,7 @@ import {
TrashType,
CompanyExecutionContext,
DriveTdriveTab,
DriveFileAccessLevel,
} from "../types";
import {
addDriveItemToArchive,
@@ -183,6 +184,28 @@ export class DocumentsService {
};
};
getAccess = async (
id: string,
userId: string,
context: DriveExecutionContext,
): Promise<DriveFileAccessLevel | "none" | null> => {
if (!context) {
this.logger.error("invalid context");
return null;
}
id = id || this.ROOT;
//Get requested entity
const myAccessLevel = await getAccessLevel(id, null, this.repository, context);
if (myAccessLevel !== "none") {
return await getAccessLevel(id, null, this.repository, { ...context, user: { id: userId } });
}
return null;
};
/**
* Creates a DriveFile item.
*
@@ -162,6 +162,20 @@ export const hasAccessLevel = (
* @returns {Promise<boolean>}
*/
export const isCompanyGuest = async (context: CompanyExecutionContext): Promise<boolean> => {
if (context.user?.application_id) {
//Applications do everything (if they are added to the company)
if (
!!(
await globalResolver.services.applications.companyApps.get({
company_id: context.company.id,
application_id: context.user?.application_id,
})
)?.application?.id
) {
return false;
}
}
const userRole = await globalResolver.services.companies.getUserRole(
context.company.id,
context.user?.id,
@@ -177,6 +191,20 @@ export const isCompanyGuest = async (context: CompanyExecutionContext): Promise<
* @returns {Promise<boolean>}
*/
export const isCompanyAdmin = async (context: CompanyExecutionContext): Promise<boolean> => {
if (context.user?.application_id) {
//Applications do everything (if they are added to the company)
if (
!!(
await globalResolver.services.applications.companyApps.get({
company_id: context.company.id,
application_id: context.user?.application_id,
})
)?.application?.id
) {
return true;
}
}
const userRole = await globalResolver.services.companies.getUserRole(
context.company.id,
context.user?.id,
@@ -373,6 +401,20 @@ export const getAccessLevel = async (
throw Error("Drive item doesn't exist");
}
if (context.user?.application_id) {
//Applications do everything (if they are added to the company)
if (
!!(
await globalResolver.services.applications.companyApps.get({
company_id: context.company.id,
application_id: context.user?.application_id,
})
)?.application?.id
) {
return "manage";
}
}
/*
* Specific user or channel rule is applied first. Then less restrictive level will be chosen
* between the parent folder and company accesses.
@@ -673,7 +715,7 @@ export const getFileMetadata = async (
company_id: context.company.id,
},
context,
{ ...(context.user.server_request ? {} : { waitForThumbnail: true }) },
{ ...(context.user?.server_request ? {} : { waitForThumbnail: true }) },
);
if (!file) {
@@ -10,6 +10,7 @@ import { FileVersion } from "../../entities/file-version";
import {
CompanyExecutionContext,
DriveExecutionContext,
DriveFileAccessLevel,
DriveItemDetails,
DriveTdriveTab,
ItemRequestParams,
@@ -133,6 +134,33 @@ export class DocumentsController {
};
};
/**
* Return access level of a given user on a given item
*/
getAccess = async (
request: FastifyRequest<{
Params: ItemRequestParams & { user_id: string };
}>,
): Promise<{ access: DriveFileAccessLevel | "none" }> => {
const context = getDriveExecutionContext(request);
const { id } = request.params;
const { user_id } = request.params;
const access = await globalResolver.services.documents.documents.getAccess(
id,
user_id,
context,
);
if (!access) {
throw new CrudException("Item not found", 404);
}
return {
access,
};
};
/**
* Update drive item
*
@@ -44,6 +44,13 @@ const routes: FastifyPluginCallback = (fastify: FastifyInstance, _options, next)
handler: documentsController.delete.bind(documentsController),
});
fastify.route({
method: "GET",
url: `${serviceUrl}/:id/user/:user_id/access`,
preValidation: [fastify.authenticateOptional],
handler: documentsController.getAccess.bind(documentsController),
});
fastify.route({
method: "POST",
url: `${serviceUrl}/:id/version`,