feat: allow access approvals with user groups

This commit is contained in:
Meet
2024-09-20 01:24:30 +05:30
parent 5740d2b4e4
commit 009be0ded8
18 changed files with 457 additions and 33 deletions
@@ -20,7 +20,13 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
`${TableName.AccessApprovalPolicy}.id`,
`${TableName.AccessApprovalPolicyApprover}.policyId`
)
.leftJoin(
TableName.AccessApprovalPolicyGroupApprover,
`${TableName.AccessApprovalPolicy}.id`,
`${TableName.AccessApprovalPolicyGroupApprover}.policyId`
)
.select(tx.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover))
.select(tx.ref("approverGroupId").withSchema(TableName.AccessApprovalPolicyGroupApprover))
.select(tx.ref("name").withSchema(TableName.Environment).as("envName"))
.select(tx.ref("slug").withSchema(TableName.Environment).as("envSlug"))
.select(tx.ref("id").withSchema(TableName.Environment).as("envId"))
@@ -54,6 +60,13 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
mapper: ({ approverUserId }) => ({
userId: approverUserId
})
},
{
key: "approverGroupId",
label: "groupApprovers" as const,
mapper: ({ approverGroupId }) => ({
groupId: approverGroupId
})
}
]
});
@@ -88,6 +101,13 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
mapper: ({ approverUserId }) => ({
userId: approverUserId
})
},
{
key: "approverGroupId",
label: "groupApprovers" as const,
mapper: ({ approverGroupId }) => ({
groupId: approverGroupId
})
}
]
});
@@ -0,0 +1,12 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TAccessApprovalPolicyGroupApproverDALFactory = ReturnType<
typeof accessApprovalPolicyGroupApproverDALFactory
>;
export const accessApprovalPolicyGroupApproverDALFactory = (db: TDbClient) => {
const accessApprovalPolicyGroupApproverOrm = ormify(db, TableName.AccessApprovalPolicyGroupApprover);
return { ...accessApprovalPolicyGroupApproverOrm };
};
@@ -3,13 +3,16 @@ import { ForbiddenError } from "@casl/ability";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
import { TGroupDALFactory } from "../group/group-dal";
import { TAccessApprovalPolicyApproverDALFactory } from "./access-approval-policy-approver-dal";
import { TAccessApprovalPolicyDALFactory } from "./access-approval-policy-dal";
import { verifyApprovers } from "./access-approval-policy-fns";
import { TAccessApprovalPolicyGroupApproverDALFactory } from "./access-approval-policy-group-approver-dal";
import {
TCreateAccessApprovalPolicy,
TDeleteAccessApprovalPolicy,
@@ -25,13 +28,17 @@ type TSecretApprovalPolicyServiceFactoryDep = {
projectEnvDAL: Pick<TProjectEnvDALFactory, "find" | "findOne">;
accessApprovalPolicyApproverDAL: TAccessApprovalPolicyApproverDALFactory;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find">;
groupDAL: TGroupDALFactory;
accessApprovalPolicyGroupApproverDAL: TAccessApprovalPolicyGroupApproverDALFactory;
};
export type TAccessApprovalPolicyServiceFactory = ReturnType<typeof accessApprovalPolicyServiceFactory>;
export const accessApprovalPolicyServiceFactory = ({
accessApprovalPolicyDAL,
accessApprovalPolicyGroupApproverDAL,
accessApprovalPolicyApproverDAL,
groupDAL,
permissionService,
projectEnvDAL,
projectDAL
@@ -45,6 +52,7 @@ export const accessApprovalPolicyServiceFactory = ({
actorAuthMethod,
approvals,
approvers,
groupApprovers,
projectSlug,
environment,
enforcementLevel
@@ -52,7 +60,11 @@ export const accessApprovalPolicyServiceFactory = ({
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new BadRequestError({ message: "Project not found" });
if (approvals > approvers.length)
if (!groupApprovers && !approvers)
throw new BadRequestError({ message: "Either of approvers or group approvers must be provided" });
// If there is a group approver people might be added to the group later to meet the approvers quota
if (!groupApprovers && approvals > approvers.length)
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
const { permission } = await permissionService.getProjectPermission(
@@ -69,6 +81,35 @@ export const accessApprovalPolicyServiceFactory = ({
const env = await projectEnvDAL.findOne({ slug: environment, projectId: project.id });
if (!env) throw new BadRequestError({ message: "Environment not found" });
const verifyAllApprovers = approvers;
const usersPromises: Promise<
{
id: string;
email: string | null | undefined;
username: string;
firstName: string | null | undefined;
lastName: string | null | undefined;
isPartOfGroup: boolean;
}[]
>[] = [];
for (const groupId of groupApprovers) {
usersPromises.push(groupDAL.findAllGroupMembers({ orgId: actorOrgId, groupId, offset: 0 }));
}
const verifyGroupApprovers = (await Promise.all(usersPromises)).flat().map((user) => user.id);
verifyAllApprovers.push(...verifyGroupApprovers);
logger.info("verifyApproversCreate");
logger.info({
projectId: project.id,
orgId: actorOrgId,
envSlug: environment,
secretPath,
actorAuthMethod,
permissionService,
userIds: verifyAllApprovers
});
await verifyApprovers({
projectId: project.id,
orgId: actorOrgId,
@@ -76,7 +117,7 @@ export const accessApprovalPolicyServiceFactory = ({
secretPath,
actorAuthMethod,
permissionService,
userIds: approvers
userIds: verifyAllApprovers
});
const accessApproval = await accessApprovalPolicyDAL.transaction(async (tx) => {
@@ -97,6 +138,15 @@ export const accessApprovalPolicyServiceFactory = ({
})),
tx
);
await accessApprovalPolicyGroupApproverDAL.insertMany(
groupApprovers.map((groupId) => ({
approverGroupId: groupId,
policyId: doc.id
})),
tx
);
return doc;
});
return { ...accessApproval, environment: env, projectId: project.id };
@@ -129,6 +179,7 @@ export const accessApprovalPolicyServiceFactory = ({
const updateAccessApprovalPolicy = async ({
policyId,
approvers,
groupApprovers,
secretPath,
name,
actorId,
@@ -162,6 +213,16 @@ export const accessApprovalPolicyServiceFactory = ({
tx
);
if (approvers) {
logger.info("verifyApproversPatch");
logger.info({
projectId: accessApprovalPolicy.projectId,
orgId: actorOrgId,
envSlug: accessApprovalPolicy.environment.slug,
secretPath,
actorAuthMethod,
permissionService,
userIds: approvers
});
await verifyApprovers({
projectId: accessApprovalPolicy.projectId,
orgId: actorOrgId,
@@ -171,7 +232,6 @@ export const accessApprovalPolicyServiceFactory = ({
permissionService,
userIds: approvers
});
await accessApprovalPolicyApproverDAL.delete({ policyId: doc.id }, tx);
await accessApprovalPolicyApproverDAL.insertMany(
approvers.map((userId) => ({
@@ -181,6 +241,43 @@ export const accessApprovalPolicyServiceFactory = ({
tx
);
}
if (groupApprovers) {
const usersPromises: Promise<
{
id: string;
email: string | null | undefined;
username: string;
firstName: string | null | undefined;
lastName: string | null | undefined;
isPartOfGroup: boolean;
}[]
>[] = [];
for (const groupId of groupApprovers) {
usersPromises.push(groupDAL.findAllGroupMembers({ orgId: actorOrgId, groupId, offset: 0 }));
}
const verifyGroupApprovers = (await Promise.all(usersPromises)).flat().map((user) => user.id);
await verifyApprovers({
projectId: accessApprovalPolicy.projectId,
orgId: actorOrgId,
envSlug: accessApprovalPolicy.environment.slug,
secretPath: doc.secretPath!,
actorAuthMethod,
permissionService,
userIds: verifyGroupApprovers
});
await accessApprovalPolicyGroupApproverDAL.delete({ policyId: doc.id }, tx);
await accessApprovalPolicyGroupApproverDAL.insertMany(
groupApprovers.map((groupId) => ({
approverGroupId: groupId,
policyId: doc.id
})),
tx
);
}
return doc;
});
return {
@@ -18,6 +18,7 @@ export type TCreateAccessApprovalPolicy = {
secretPath: string;
environment: string;
approvers: string[];
groupApprovers: string[];
projectSlug: string;
name: string;
enforcementLevel: EnforcementLevel;
@@ -27,6 +28,7 @@ export type TUpdateAccessApprovalPolicy = {
policyId: string;
approvals?: number;
approvers?: string[];
groupApprovers?: string[];
secretPath?: string;
name?: string;
enforcementLevel?: EnforcementLevel;