Browse Source

refactor: bulk check permissions when checking bulk data job permissions

Kristian Vos 8 months ago
parent
commit
373072bbca

+ 120 - 4
backend/src/JobContext.ts

@@ -7,6 +7,10 @@ import { JobDerived } from "./types/JobDerived";
 import assertJobDerived from "./utils/assertJobDerived";
 import { GetModelPermissionsResult } from "./modules/DataModule/models/users/jobs/GetModelPermissions";
 import { GetPermissionsResult } from "./modules/DataModule/models/users/jobs/GetPermissions";
+import { forEachIn } from "@common/utils/forEachIn";
+
+const permissionRegex =
+	/^(?<moduleName>[a-z]+)\.(?<modelOrJobName>[A-z]+)\.(?<jobName>[A-z]+)(?:\.(?<modelId>[A-z0-9]{24}))?(?:\.(?<extra>[A-z]+))?$/;
 
 export default class JobContext {
 	public readonly job: Job;
@@ -92,10 +96,8 @@ export default class JobContext {
 	public async assertPermission(permission: string) {
 		let hasPermission = false;
 
-		const [, moduleName, modelOrJobName, jobName, modelId, extra] =
-			/^([a-z]+)\.([A-z]+)\.([A-z]+)(?:\.([A-z0-9]{24}))?(?:\.([A-z]+))?$/.exec(
-				permission
-			) ?? [];
+		const { moduleName, modelOrJobName, jobName, modelId, extra } =
+			permissionRegex.exec(permission)?.groups ?? {};
 
 		if (moduleName === "data" && modelOrJobName && jobName) {
 			const GetModelPermissions = DataModule.getJob(
@@ -127,4 +129,118 @@ export default class JobContext {
 				`Insufficient permissions for permission ${permission}`
 			);
 	}
+
+	public async assertPermissions(permissions: string[]) {
+		let hasPermission: { [permission: string]: boolean } = {};
+		permissions.forEach(permission => {
+			hasPermission[permission] = false;
+		});
+
+		const permissionData = permissions.map(permission => {
+			const { moduleName, modelOrJobName, jobName, modelId, extra } =
+				permissionRegex.exec(permission)?.groups ?? {};
+
+			return {
+				permission,
+				moduleName,
+				modelOrJobName,
+				jobName,
+				modelId,
+				extra
+			};
+		});
+
+		const dataPermissions = permissionData.filter(
+			({ moduleName, modelOrJobName, jobName }) =>
+				moduleName === "data" && modelOrJobName && jobName
+		);
+		const otherPermissions = permissionData.filter(
+			({ moduleName, modelOrJobName, jobName }) =>
+				!(moduleName === "data" && modelOrJobName && jobName)
+		);
+
+		if (otherPermissions.length > 0) {
+			const GetPermissions = DataModule.getJob("users.getPermissions");
+
+			const permissions = (await this.executeJob(
+				GetPermissions
+			)) as GetPermissionsResult;
+
+			otherPermissions.forEach(({ permission }) => {
+				hasPermission[permission] = permissions[permission];
+			});
+		}
+
+		if (dataPermissions.length > 0) {
+			const dataPermissionsPerModel: any = {};
+			dataPermissions.forEach(dataPermission => {
+				const { modelOrJobName } = dataPermission;
+				if (!Array.isArray(dataPermissionsPerModel[modelOrJobName]))
+					dataPermissionsPerModel[modelOrJobName] = [];
+				dataPermissionsPerModel[modelOrJobName].push(dataPermission);
+			});
+
+			const modelNames = Object.keys(dataPermissionsPerModel);
+
+			const GetModelPermissions = DataModule.getJob(
+				"users.getModelPermissions"
+			);
+
+			await forEachIn(modelNames, async modelName => {
+				const dataPermissionsForThisModel =
+					dataPermissionsPerModel[modelName];
+				const modelIds = dataPermissionsForThisModel.map(
+					({ modelId }: { modelId: string }) => modelId
+				);
+
+				const permissions = (await this.executeJob(
+					GetModelPermissions,
+					{
+						modelName,
+						modelIds
+					}
+				)) as GetModelPermissionsResult;
+
+				dataPermissionsForThisModel.forEach(
+					({
+						modelOrJobName,
+						jobName,
+						modelId,
+						extra,
+						permission
+					}: {
+						modelOrJobName: string;
+						jobName: string;
+						modelId: string;
+						extra?: string;
+						permission: string;
+					}) => {
+						let modelPermission = `data.${modelOrJobName}.${jobName}`;
+
+						if (extra) modelPermission += `.${extra}`;
+
+						const permissionsForModelId = permissions[
+							modelId
+						] as Record<string, boolean>;
+
+						hasPermission[permission] =
+							permissionsForModelId[modelPermission];
+					}
+				);
+			});
+		}
+
+		if (
+			Object.values(hasPermission).some(hasPermission => !hasPermission)
+		) {
+			const missingPermissions = Object.entries(hasPermission)
+				.filter(([, hasPermission]) => !hasPermission)
+				.map(([permission]) => permission);
+			throw new Error(
+				`Insufficient permissions for permission(s) ${missingPermissions.join(
+					", "
+				)}`
+			);
+		}
+	}
 }

+ 1 - 5
backend/src/modules/DataModule/DataModuleJob.ts

@@ -76,11 +76,7 @@ export default abstract class DataModuleJob extends Job {
 			const permissions = modelIds.map(
 				(modelId: string) => `${this.getPath()}.${modelId}`
 			);
-			await Promise.all(
-				permissions.map((permission: string) =>
-					this._context.assertPermission(permission)
-				)
-			);
+			await this._context.assertPermissions(permissions);
 
 			return;
 		}

+ 3 - 1
backend/src/modules/DataModule/models/users/jobs/GetModelPermissions.ts

@@ -6,7 +6,9 @@ import ModuleManager from "@/ModuleManager";
 import GetPermissions, { GetPermissionsResult } from "./GetPermissions";
 import DataModuleJob from "@/modules/DataModule/DataModuleJob";
 
-export type GetModelPermissionsResult = Record<string, boolean>;
+export type GetModelPermissionsResult =
+	| Record<string, boolean>
+	| Record<string, Record<string, boolean>>;
 
 export default class GetModelPermissions extends DataModuleJob {
 	protected static _modelName = "users";