🛠 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> { async read(path: string): Promise<Readable> {
const fullPath = this.getFullPath(path); const fullPath = this.getFullPath(path);
if (!fs.existsSync(fullPath)) { if (!fs.existsSync(fullPath)) {
throw new Error("File doesn't not exists"); throw new Error("File doesn't exists");
} }
return createReadStream(fullPath); return createReadStream(fullPath);
} }
@@ -11,7 +11,7 @@ export class ApplicationServiceImpl implements TdriveServiceProvider, Initializa
} }
async get(id: string, _: ExecutionContext): Promise<Application> { 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[]> { async list(_: ExecutionContext): Promise<Application[]> {
@@ -18,19 +18,28 @@ export class CompanyApplicationServiceImpl implements TdriveServiceProvider, Ini
pk: Pick<CompanyApplicationPrimaryKey, "company_id" | "application_id"> & { id?: string }, pk: Pick<CompanyApplicationPrimaryKey, "company_id" | "application_id"> & { id?: string },
context?: CompanyExecutionContext, context?: CompanyExecutionContext,
): Promise<CompanyApplicationWithApplication> { ): Promise<CompanyApplicationWithApplication> {
const application = await gr.services.applications.marketplaceApps.get( try {
pk.application_id, const application = await gr.services.applications.marketplaceApps.get(
context, pk.application_id,
); context,
);
return { if (!application?.id) {
...{ return null;
id: pk.application_id, }
company_id: pk.company_id,
application_id: pk.application_id, return {
}, ...{
application: application, 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>( async list<ListOptions>(
@@ -21,6 +21,7 @@ import {
TrashType, TrashType,
CompanyExecutionContext, CompanyExecutionContext,
DriveTdriveTab, DriveTdriveTab,
DriveFileAccessLevel,
} from "../types"; } from "../types";
import { import {
addDriveItemToArchive, 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. * Creates a DriveFile item.
* *
@@ -162,6 +162,20 @@ export const hasAccessLevel = (
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
export const isCompanyGuest = async (context: CompanyExecutionContext): 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( const userRole = await globalResolver.services.companies.getUserRole(
context.company.id, context.company.id,
context.user?.id, context.user?.id,
@@ -177,6 +191,20 @@ export const isCompanyGuest = async (context: CompanyExecutionContext): Promise<
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
export const isCompanyAdmin = async (context: CompanyExecutionContext): 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( const userRole = await globalResolver.services.companies.getUserRole(
context.company.id, context.company.id,
context.user?.id, context.user?.id,
@@ -373,6 +401,20 @@ export const getAccessLevel = async (
throw Error("Drive item doesn't exist"); 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 * Specific user or channel rule is applied first. Then less restrictive level will be chosen
* between the parent folder and company accesses. * between the parent folder and company accesses.
@@ -673,7 +715,7 @@ export const getFileMetadata = async (
company_id: context.company.id, company_id: context.company.id,
}, },
context, context,
{ ...(context.user.server_request ? {} : { waitForThumbnail: true }) }, { ...(context.user?.server_request ? {} : { waitForThumbnail: true }) },
); );
if (!file) { if (!file) {
@@ -10,6 +10,7 @@ import { FileVersion } from "../../entities/file-version";
import { import {
CompanyExecutionContext, CompanyExecutionContext,
DriveExecutionContext, DriveExecutionContext,
DriveFileAccessLevel,
DriveItemDetails, DriveItemDetails,
DriveTdriveTab, DriveTdriveTab,
ItemRequestParams, 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 * Update drive item
* *
@@ -44,6 +44,13 @@ const routes: FastifyPluginCallback = (fastify: FastifyInstance, _options, next)
handler: documentsController.delete.bind(documentsController), 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({ fastify.route({
method: "POST", method: "POST",
url: `${serviceUrl}/:id/version`, url: `${serviceUrl}/:id/version`,