From 081502848da26c9a5bcf591b6b6f8f50ebd09e3a Mon Sep 17 00:00:00 2001 From: Meet Date: Fri, 20 Sep 2024 08:51:48 +0530 Subject: [PATCH] feat: allow secret approvals with user groups --- backend/src/@types/knex.d.ts | 5 ++ ...40919205505_add-group-approvals-secrets.ts | 22 +++++ backend/src/db/schemas/index.ts | 1 + backend/src/db/schemas/models.ts | 1 + ...ecret-approval-policies-group-approvers.ts | 25 ++++++ .../v1/secret-approval-policy-router.ts | 23 +++-- .../v1/secret-approval-request-router.ts | 12 ++- .../secret-approval-policy-dal.ts | 35 +++++++- ...cret-approval-policy-group-approver-dal.ts | 12 +++ .../secret-approval-policy-service.ts | 29 ++++++- .../secret-approval-policy-types.ts | 4 +- .../secret-approval-request-dal.ts | 85 +++++++++++++++++-- backend/src/server/routes/index.ts | 3 + .../src/hooks/api/secretApproval/mutation.tsx | 5 +- .../src/hooks/api/secretApproval/types.ts | 3 + .../components/ApprovalPolicyRow.tsx | 4 +- 16 files changed, 248 insertions(+), 21 deletions(-) create mode 100644 backend/src/db/migrations/20240919205505_add-group-approvals-secrets.ts create mode 100644 backend/src/db/schemas/secret-approval-policies-group-approvers.ts create mode 100644 backend/src/ee/services/secret-approval-policy/secret-approval-policy-group-approver-dal.ts diff --git a/backend/src/@types/knex.d.ts b/backend/src/@types/knex.d.ts index d7b484dfc..81e7c6d4b 100644 --- a/backend/src/@types/knex.d.ts +++ b/backend/src/@types/knex.d.ts @@ -806,5 +806,10 @@ declare module "knex/types/tables" { TAccessApprovalPoliciesGroupApproversInsert, TAccessApprovalPoliciesGroupApproversInsert >; + [TableName.SecretApprovalPolicyGroupApprover]: KnexOriginal.CompositeTableType< + TSecretApprovalPoliciesGroupApprovers, + TSecretApprovalPoliciesGroupApproversInsert, + TSecretApprovalPoliciesGroupApproversInsert + >; } } diff --git a/backend/src/db/migrations/20240919205505_add-group-approvals-secrets.ts b/backend/src/db/migrations/20240919205505_add-group-approvals-secrets.ts new file mode 100644 index 000000000..8a665fc2c --- /dev/null +++ b/backend/src/db/migrations/20240919205505_add-group-approvals-secrets.ts @@ -0,0 +1,22 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; +import { createOnUpdateTrigger } from "../utils"; + +export async function up(knex: Knex): Promise { + if (!(await knex.schema.hasTable(TableName.SecretApprovalPolicyGroupApprover))) { + await knex.schema.createTable(TableName.SecretApprovalPolicyGroupApprover, (t) => { + t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); + t.uuid("approverGroupId").notNullable(); + t.foreign("approverGroupId").references("id").inTable(TableName.Groups).onDelete("CASCADE"); + t.uuid("policyId").notNullable(); + t.foreign("policyId").references("id").inTable(TableName.SecretApprovalPolicy).onDelete("CASCADE"); + t.timestamps(true, true, true); + }); + await createOnUpdateTrigger(knex, TableName.SecretApprovalPolicyGroupApprover); + } +} + +export async function down(knex: Knex): Promise { + await knex.schema.dropTableIfExists(TableName.SecretApprovalPolicyGroupApprover); +} diff --git a/backend/src/db/schemas/index.ts b/backend/src/db/schemas/index.ts index b145684b4..1a3978532 100644 --- a/backend/src/db/schemas/index.ts +++ b/backend/src/db/schemas/index.ts @@ -72,6 +72,7 @@ export * from "./saml-configs"; export * from "./scim-tokens"; export * from "./secret-approval-policies"; export * from "./secret-approval-policies-approvers"; +export * from "./secret-approval-policies-group-approvers"; export * from "./secret-approval-request-secret-tags"; export * from "./secret-approval-request-secret-tags-v2"; export * from "./secret-approval-requests"; diff --git a/backend/src/db/schemas/models.ts b/backend/src/db/schemas/models.ts index 2ed5c0326..b31c0dba6 100644 --- a/backend/src/db/schemas/models.ts +++ b/backend/src/db/schemas/models.ts @@ -78,6 +78,7 @@ export enum TableName { AccessApprovalRequestReviewer = "access_approval_requests_reviewers", SecretApprovalPolicy = "secret_approval_policies", SecretApprovalPolicyApprover = "secret_approval_policies_approvers", + SecretApprovalPolicyGroupApprover = "secret_approval_policies_group_approvers", SecretApprovalRequest = "secret_approval_requests", SecretApprovalRequestReviewer = "secret_approval_requests_reviewers", SecretApprovalRequestSecret = "secret_approval_requests_secrets", diff --git a/backend/src/db/schemas/secret-approval-policies-group-approvers.ts b/backend/src/db/schemas/secret-approval-policies-group-approvers.ts new file mode 100644 index 000000000..6bfec26d2 --- /dev/null +++ b/backend/src/db/schemas/secret-approval-policies-group-approvers.ts @@ -0,0 +1,25 @@ +// Code generated by automation script, DO NOT EDIT. +// Automated by pulling database and generating zod schema +// To update. Just run npm run generate:schema +// Written by akhilmhdh. + +import { z } from "zod"; + +import { TImmutableDBKeys } from "./models"; + +export const SecretApprovalPoliciesGroupApproversSchema = z.object({ + id: z.string().uuid(), + approverGroupId: z.string().uuid(), + policyId: z.string().uuid(), + createdAt: z.date(), + updatedAt: z.date() +}); + +export type TSecretApprovalPoliciesGroupApprovers = z.infer; +export type TSecretApprovalPoliciesGroupApproversInsert = Omit< + z.input, + TImmutableDBKeys +>; +export type TSecretApprovalPoliciesGroupApproversUpdate = Partial< + Omit, TImmutableDBKeys> +>; diff --git a/backend/src/ee/routes/v1/secret-approval-policy-router.ts b/backend/src/ee/routes/v1/secret-approval-policy-router.ts index 25c1bb0b5..8150fb5a5 100644 --- a/backend/src/ee/routes/v1/secret-approval-policy-router.ts +++ b/backend/src/ee/routes/v1/secret-approval-policy-router.ts @@ -27,13 +27,14 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi .nullable() .default("/") .transform((val) => (val ? removeTrailingSlash(val) : val)), - approvers: z.string().array().min(1), + approvers: z.string().array().optional().default([]), + groupApprovers: z.string().array().optional().default([]), approvals: z.number().min(1).default(1), enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard) }) - .refine((data) => data.approvals <= data.approvers.length, { - path: ["approvals"], - message: "The number of approvals should be lower than the number of approvers." + .refine((data) => data.approvers || data.groupApprovers, { + path: ["approvers", "groupApprovers"], + message: "At least one approver should be provided." }), response: { 200: z.object({ @@ -70,8 +71,9 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi body: z .object({ name: z.string().optional(), - approvers: z.string().array().min(1), + approvers: z.string().array().optional().default([]), approvals: z.number().min(1).default(1), + groupApprovers: z.string().array().optional().default([]), secretPath: z .string() .optional() @@ -80,9 +82,9 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi .transform((val) => (val === "" ? "/" : val)), enforcementLevel: z.nativeEnum(EnforcementLevel).optional() }) - .refine((data) => data.approvals <= data.approvers.length, { - path: ["approvals"], - message: "The number of approvals should be lower than the number of approvers." + .refine((data) => data.approvers.length > 0 || data.groupApprovers.length > 0, { + path: ["approvers", "groupApprovers"], + message: "At least one approver should be provided." }), response: { 200: z.object({ @@ -151,6 +153,11 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi .object({ userId: z.string() }) + .array(), + groupApprovers: z + .object({ + groupId: z.string() + }) .array() }) .array() diff --git a/backend/src/ee/routes/v1/secret-approval-request-router.ts b/backend/src/ee/routes/v1/secret-approval-request-router.ts index bcc4a36c5..29287974e 100644 --- a/backend/src/ee/routes/v1/secret-approval-request-router.ts +++ b/backend/src/ee/routes/v1/secret-approval-request-router.ts @@ -46,7 +46,11 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv id: z.string(), name: z.string(), approvals: z.number(), - approvers: z.string().array(), + approvers: z + .object({ + userId: z.string() + }) + .array(), secretPath: z.string().optional().nullable(), enforcementLevel: z.string() }), @@ -54,7 +58,11 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv commits: z.object({ op: z.string(), secretId: z.string().nullable().optional() }).array(), environment: z.string(), reviewers: z.object({ userId: z.string(), status: z.string() }).array(), - approvers: z.string().array() + approvers: z + .object({ + userId: z.string() + }) + .array() }).array() }) } diff --git a/backend/src/ee/services/secret-approval-policy/secret-approval-policy-dal.ts b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-dal.ts index 6d5168f26..51dba2efc 100644 --- a/backend/src/ee/services/secret-approval-policy/secret-approval-policy-dal.ts +++ b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-dal.ts @@ -22,13 +22,29 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => { ) .leftJoin(TableName.Users, `${TableName.SecretApprovalPolicyApprover}.approverUserId`, `${TableName.Users}.id`) - + .leftJoin( + TableName.SecretApprovalPolicyGroupApprover, + `${TableName.SecretApprovalPolicy}.id`, + `${TableName.SecretApprovalPolicyGroupApprover}.policyId` + ) + .leftJoin( + TableName.UserGroupMembership, + `${TableName.SecretApprovalPolicyGroupApprover}.approverGroupId`, + `${TableName.UserGroupMembership}.userId` + ) .select( tx.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover), tx.ref("email").withSchema(TableName.Users).as("approverEmail"), tx.ref("firstName").withSchema(TableName.Users).as("approverFirstName"), tx.ref("lastName").withSchema(TableName.Users).as("approverLastName") ) + .select( + tx.ref("approverGroupId").withSchema(TableName.SecretApprovalPolicyGroupApprover), + tx.ref("userId").withSchema(TableName.UserGroupMembership).as("approverGroupUserId"), + tx.ref("email").withSchema(TableName.Users).as("approverGroupEmail"), + tx.ref("firstName").withSchema(TableName.Users).as("approverGroupFirstName"), + tx.ref("lastName").withSchema(TableName.Users).as("approverGroupLastName") + ) .select( tx.ref("name").withSchema(TableName.Environment).as("envName"), tx.ref("slug").withSchema(TableName.Environment).as("envSlug"), @@ -61,6 +77,16 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => { firstName: approverFirstName, lastName: approverLastName }) + }, + { + key: "approverGroupUserId", + label: "userApprovers" as const, + mapper: ({ approverGroupUserId, approverGroupEmail, approverGroupFirstName, approverGroupLastName }) => ({ + userId: approverGroupUserId, + email: approverGroupEmail, + firstName: approverGroupFirstName, + lastName: approverGroupLastName + }) } ] }); @@ -89,6 +115,13 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => { mapper: ({ approverUserId }) => ({ userId: approverUserId }) + }, + { + key: "approverGroupId", + label: "groupApprovers" as const, + mapper: ({ approverGroupId }) => ({ + groupId: approverGroupId + }) } ] }); diff --git a/backend/src/ee/services/secret-approval-policy/secret-approval-policy-group-approver-dal.ts b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-group-approver-dal.ts new file mode 100644 index 000000000..710d251d5 --- /dev/null +++ b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-group-approver-dal.ts @@ -0,0 +1,12 @@ +import { TDbClient } from "@app/db"; +import { TableName } from "@app/db/schemas"; +import { ormify } from "@app/lib/knex"; + +export type TSecretApprovalPolicyGroupApproverDALFactory = ReturnType< + typeof secretApprovalPolicyGroupApproverDALFactory +>; + +export const secretApprovalPolicyGroupApproverDALFactory = (db: TDbClient) => { + const sapGroupApproverOrm = ormify(db, TableName.SecretApprovalPolicyGroupApprover); + return sapGroupApproverOrm; +}; diff --git a/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts index 8c974b05d..b9a385673 100644 --- a/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts +++ b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-service.ts @@ -11,6 +11,7 @@ import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal import { TLicenseServiceFactory } from "../license/license-service"; import { TSecretApprovalPolicyApproverDALFactory } from "./secret-approval-policy-approver-dal"; import { TSecretApprovalPolicyDALFactory } from "./secret-approval-policy-dal"; +import { TSecretApprovalPolicyGroupApproverDALFactory } from "./secret-approval-policy-group-approver-dal"; import { TCreateSapDTO, TDeleteSapDTO, @@ -29,6 +30,7 @@ type TSecretApprovalPolicyServiceFactoryDep = { secretApprovalPolicyDAL: TSecretApprovalPolicyDALFactory; projectEnvDAL: Pick; secretApprovalPolicyApproverDAL: TSecretApprovalPolicyApproverDALFactory; + secretApprovalPolicyGroupApproverDAL: TSecretApprovalPolicyGroupApproverDALFactory; licenseService: Pick; }; @@ -38,6 +40,7 @@ export const secretApprovalPolicyServiceFactory = ({ secretApprovalPolicyDAL, permissionService, secretApprovalPolicyApproverDAL, + secretApprovalPolicyGroupApproverDAL, projectEnvDAL, licenseService }: TSecretApprovalPolicyServiceFactoryDep) => { @@ -49,12 +52,16 @@ export const secretApprovalPolicyServiceFactory = ({ actorAuthMethod, approvals, approvers, + groupApprovers, projectId, secretPath, environment, enforcementLevel }: TCreateSapDTO) => { - if (approvals > approvers.length) + if (!groupApprovers && !approvers) + throw new BadRequestError({ message: "Either of approvers or group approvers must be provided" }); + + if (!groupApprovers && approvals > approvers.length) throw new BadRequestError({ message: "Approvals cannot be greater than approvers" }); const { permission } = await permissionService.getProjectPermission( @@ -91,6 +98,7 @@ export const secretApprovalPolicyServiceFactory = ({ }, tx ); + await secretApprovalPolicyApproverDAL.insertMany( approvers.map((approverUserId) => ({ approverUserId, @@ -98,6 +106,14 @@ export const secretApprovalPolicyServiceFactory = ({ })), tx ); + + await secretApprovalPolicyGroupApproverDAL.insertMany( + groupApprovers.map((approverGroupId) => ({ + approverGroupId, + policyId: doc.id + })), + tx + ); return doc; }); return { ...secretApproval, environment: env, projectId }; @@ -105,6 +121,7 @@ export const secretApprovalPolicyServiceFactory = ({ const updateSecretApprovalPolicy = async ({ approvers, + groupApprovers, secretPath, name, actorId, @@ -156,6 +173,16 @@ export const secretApprovalPolicyServiceFactory = ({ tx ); } + if (groupApprovers) { + await secretApprovalPolicyGroupApproverDAL.delete({ policyId: doc.id }, tx); + await secretApprovalPolicyGroupApproverDAL.insertMany( + groupApprovers.map((approverGroupId) => ({ + approverGroupId, + policyId: doc.id + })), + tx + ); + } return doc; }); return { diff --git a/backend/src/ee/services/secret-approval-policy/secret-approval-policy-types.ts b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-types.ts index 8e7099c98..5199aff64 100644 --- a/backend/src/ee/services/secret-approval-policy/secret-approval-policy-types.ts +++ b/backend/src/ee/services/secret-approval-policy/secret-approval-policy-types.ts @@ -5,6 +5,7 @@ export type TCreateSapDTO = { secretPath?: string | null; environment: string; approvers: string[]; + groupApprovers: string[]; projectId: string; name: string; enforcementLevel: EnforcementLevel; @@ -14,7 +15,8 @@ export type TUpdateSapDTO = { secretPolicyId: string; approvals?: number; secretPath?: string | null; - approvers: string[]; + approvers?: string[]; + groupApprovers?: string[]; name?: string; enforcementLevel?: EnforcementLevel; } & Omit; diff --git a/backend/src/ee/services/secret-approval-request/secret-approval-request-dal.ts b/backend/src/ee/services/secret-approval-request/secret-approval-request-dal.ts index 54c7563f6..75f19861c 100644 --- a/backend/src/ee/services/secret-approval-request/secret-approval-request-dal.ts +++ b/backend/src/ee/services/secret-approval-request/secret-approval-request-dal.ts @@ -48,16 +48,31 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => { `${TableName.SecretApprovalRequest}.committerUserId`, `committerUser.id` ) - .join( + .leftJoin( TableName.SecretApprovalPolicyApprover, `${TableName.SecretApprovalPolicy}.id`, `${TableName.SecretApprovalPolicyApprover}.policyId` ) - .join( + .leftJoin( db(TableName.Users).as("secretApprovalPolicyApproverUser"), `${TableName.SecretApprovalPolicyApprover}.approverUserId`, "secretApprovalPolicyApproverUser.id" ) + .leftJoin( + TableName.SecretApprovalPolicyGroupApprover, + `${TableName.SecretApprovalPolicy}.id`, + `${TableName.SecretApprovalPolicyGroupApprover}.policyId` + ) + .leftJoin( + TableName.UserGroupMembership, + `${TableName.SecretApprovalPolicyGroupApprover}.approverGroupId`, + `${TableName.UserGroupMembership}.groupId` + ) + .leftJoin( + db(TableName.Users).as("secretApprovalPolicyGroupApproverUser"), + `${TableName.UserGroupMembership}.userId`, + `secretApprovalPolicyGroupApproverUser.id` + ) .leftJoin( TableName.SecretApprovalRequestReviewer, `${TableName.SecretApprovalRequest}.id`, @@ -71,10 +86,15 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => { .select(selectAllTableCols(TableName.SecretApprovalRequest)) .select( tx.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover), + tx.ref("userId").withSchema(TableName.UserGroupMembership).as("approverGroupUserId"), tx.ref("email").withSchema("secretApprovalPolicyApproverUser").as("approverEmail"), + tx.ref("email").withSchema("secretApprovalPolicyGroupApproverUser").as("approverGroupEmail"), tx.ref("username").withSchema("secretApprovalPolicyApproverUser").as("approverUsername"), + tx.ref("username").withSchema("secretApprovalPolicyGroupApproverUser").as("approverGroupUsername"), tx.ref("firstName").withSchema("secretApprovalPolicyApproverUser").as("approverFirstName"), + tx.ref("firstName").withSchema("secretApprovalPolicyGroupApproverUser").as("approverGroupFirstName"), tx.ref("lastName").withSchema("secretApprovalPolicyApproverUser").as("approverLastName"), + tx.ref("lastName").withSchema("secretApprovalPolicyGroupApproverUser").as("approverGroupLastName"), tx.ref("email").withSchema("statusChangedByUser").as("statusChangedByUserEmail"), tx.ref("username").withSchema("statusChangedByUser").as("statusChangedByUserUsername"), tx.ref("firstName").withSchema("statusChangedByUser").as("statusChangedByUserFirstName"), @@ -164,6 +184,23 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => { lastName, username }) + }, + { + key: "approverGroupUserId", + label: "approvers" as const, + mapper: ({ + approverGroupUserId, + approverGroupEmail: email, + approverGroupUsername: username, + approverGroupLastName: lastName, + approverGroupFirstName: firstName + }) => ({ + userId: approverGroupUserId, + email, + firstName, + lastName, + username + }) } ] }); @@ -236,11 +273,21 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => { `${TableName.SecretApprovalRequest}.policyId`, `${TableName.SecretApprovalPolicy}.id` ) - .join( + .leftJoin( TableName.SecretApprovalPolicyApprover, `${TableName.SecretApprovalPolicy}.id`, `${TableName.SecretApprovalPolicyApprover}.policyId` ) + .leftJoin( + TableName.SecretApprovalPolicyGroupApprover, + `${TableName.SecretApprovalPolicy}.id`, + `${TableName.SecretApprovalPolicyGroupApprover}.policyId` + ) + .leftJoin( + TableName.UserGroupMembership, + `${TableName.SecretApprovalPolicyGroupApprover}.approverGroupId`, + `${TableName.UserGroupMembership}.groupId` + ) .join( db(TableName.Users).as("committerUser"), `${TableName.SecretApprovalRequest}.committerUserId`, @@ -269,6 +316,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => { void bd .where(`${TableName.SecretApprovalPolicyApprover}.approverUserId`, userId) .orWhere(`${TableName.SecretApprovalRequest}.committerUserId`, userId) + .orWhere(`${TableName.UserGroupMembership}.userId`, userId) ) .select(selectAllTableCols(TableName.SecretApprovalRequest)) .select( @@ -289,6 +337,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => { db.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"), db.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"), db.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover), + db.ref("userId").withSchema(TableName.UserGroupMembership).as("approverGroupUserId"), db.ref("email").withSchema("committerUser").as("committerUserEmail"), db.ref("username").withSchema("committerUser").as("committerUserUsername"), db.ref("firstName").withSchema("committerUser").as("committerUserFirstName"), @@ -334,7 +383,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => { { key: "approverUserId", label: "approvers" as const, - mapper: ({ approverUserId }) => approverUserId + mapper: ({ approverUserI: userId }) => ({ userId }) }, { key: "commitId", @@ -344,6 +393,11 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => { id, secretId }) + }, + { + key: "approverGroupUserId", + label: "approvers" as const, + mapper: ({ approverGroupUserId: userId }) => ({ userId }) } ] }); @@ -371,11 +425,21 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => { `${TableName.SecretApprovalRequest}.policyId`, `${TableName.SecretApprovalPolicy}.id` ) - .join( + .leftJoin( TableName.SecretApprovalPolicyApprover, `${TableName.SecretApprovalPolicy}.id`, `${TableName.SecretApprovalPolicyApprover}.policyId` ) + .leftJoin( + TableName.SecretApprovalPolicyGroupApprover, + `${TableName.SecretApprovalPolicy}.id`, + `${TableName.SecretApprovalPolicyGroupApprover}.policyId` + ) + .leftJoin( + TableName.UserGroupMembership, + `${TableName.SecretApprovalPolicyGroupApprover}.approverGroupId`, + `${TableName.UserGroupMembership}.groupId` + ) .join( db(TableName.Users).as("committerUser"), `${TableName.SecretApprovalRequest}.committerUserId`, @@ -404,6 +468,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => { void bd .where(`${TableName.SecretApprovalPolicyApprover}.approverUserId`, userId) .orWhere(`${TableName.SecretApprovalRequest}.committerUserId`, userId) + .orWhere(`${TableName.UserGroupMembership}.userId`, userId) ) .select(selectAllTableCols(TableName.SecretApprovalRequest)) .select( @@ -424,6 +489,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => { db.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"), db.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"), db.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover), + db.ref("userId").withSchema(TableName.UserGroupMembership).as("approverGroupUserId"), db.ref("email").withSchema("committerUser").as("committerUserEmail"), db.ref("username").withSchema("committerUser").as("committerUserUsername"), db.ref("firstName").withSchema("committerUser").as("committerUserFirstName"), @@ -469,7 +535,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => { { key: "approverUserId", label: "approvers" as const, - mapper: ({ approverUserId }) => approverUserId + mapper: ({ approverUserId: userId }) => ({ userId }) }, { key: "commitId", @@ -479,6 +545,13 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => { id, secretId }) + }, + { + key: "approverGroupUserId", + label: "approvers" as const, + mapper: ({ approverGroupUserId: userId }) => ({ + userId + }) } ] }); diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index c1fd71494..dbbcf256e 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -52,6 +52,7 @@ import { scimDALFactory } from "@app/ee/services/scim/scim-dal"; import { scimServiceFactory } from "@app/ee/services/scim/scim-service"; import { secretApprovalPolicyApproverDALFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-approver-dal"; import { secretApprovalPolicyDALFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-dal"; +import { secretApprovalPolicyGroupApproverDALFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-group-approver-dal"; import { secretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service"; import { secretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal"; import { secretApprovalRequestReviewerDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-reviewer-dal"; @@ -300,6 +301,7 @@ export const registerRoutes = async ( const accessApprovalRequestReviewerDAL = accessApprovalRequestReviewerDALFactory(db); const sapApproverDAL = secretApprovalPolicyApproverDALFactory(db); + const sapGroupApproverDAL = secretApprovalPolicyGroupApproverDALFactory(db); const secretApprovalPolicyDAL = secretApprovalPolicyDALFactory(db); const secretApprovalRequestDAL = secretApprovalRequestDALFactory(db); const secretApprovalRequestReviewerDAL = secretApprovalRequestReviewerDALFactory(db); @@ -379,6 +381,7 @@ export const registerRoutes = async ( const secretApprovalPolicyService = secretApprovalPolicyServiceFactory({ projectEnvDAL, secretApprovalPolicyApproverDAL: sapApproverDAL, + secretApprovalPolicyGroupApproverDAL: sapGroupApproverDAL, permissionService, secretApprovalPolicyDAL, licenseService diff --git a/frontend/src/hooks/api/secretApproval/mutation.tsx b/frontend/src/hooks/api/secretApproval/mutation.tsx index ceebd3493..9a7a97571 100644 --- a/frontend/src/hooks/api/secretApproval/mutation.tsx +++ b/frontend/src/hooks/api/secretApproval/mutation.tsx @@ -14,6 +14,7 @@ export const useCreateSecretApprovalPolicy = () => { workspaceId, approvals, approvers, + groupApprovers, secretPath, name, enforcementLevel @@ -23,6 +24,7 @@ export const useCreateSecretApprovalPolicy = () => { workspaceId, approvals, approvers, + groupApprovers, secretPath, name, enforcementLevel @@ -39,10 +41,11 @@ export const useUpdateSecretApprovalPolicy = () => { const queryClient = useQueryClient(); return useMutation<{}, {}, TUpdateSecretPolicyDTO>({ - mutationFn: async ({ id, approvers, approvals, secretPath, name, enforcementLevel }) => { + mutationFn: async ({ id, approvers, groupApprovers, approvals, secretPath, name, enforcementLevel }) => { const { data } = await apiRequest.patch(`/api/v1/secret-approvals/${id}`, { approvals, approvers, + groupApprovers, secretPath, name, enforcementLevel diff --git a/frontend/src/hooks/api/secretApproval/types.ts b/frontend/src/hooks/api/secretApproval/types.ts index 06ffad432..68ef8a99b 100644 --- a/frontend/src/hooks/api/secretApproval/types.ts +++ b/frontend/src/hooks/api/secretApproval/types.ts @@ -10,6 +10,7 @@ export type TSecretApprovalPolicy = { secretPath?: string; approvals: number; userApprovers: { userId: string }[]; + groupApprovers: { groupId: string }[]; updatedAt: Date; enforcementLevel: EnforcementLevel; }; @@ -30,6 +31,7 @@ export type TCreateSecretPolicyDTO = { environment: string; secretPath?: string | null; approvers?: string[]; + groupApprovers?: string[]; approvals?: number; enforcementLevel: EnforcementLevel; }; @@ -38,6 +40,7 @@ export type TUpdateSecretPolicyDTO = { id: string; name?: string; approvers?: string[]; + groupApprovers?: string[]; secretPath?: string | null; approvals?: number; enforcementLevel?: EnforcementLevel; diff --git a/frontend/src/views/SecretApprovalPage/components/ApprovalPolicyList/components/ApprovalPolicyRow.tsx b/frontend/src/views/SecretApprovalPage/components/ApprovalPolicyList/components/ApprovalPolicyRow.tsx index b37d9322f..4b4a6a5c8 100644 --- a/frontend/src/views/SecretApprovalPage/components/ApprovalPolicyList/components/ApprovalPolicyRow.tsx +++ b/frontend/src/views/SecretApprovalPage/components/ApprovalPolicyList/components/ApprovalPolicyRow.tsx @@ -89,7 +89,8 @@ export const ApprovalPolicyRow = ({ { workspaceId, id: policy.id, - approvers: selectedApprovers + approvers: selectedApprovers, + groupApprovers: selectedGroupApprovers }, { onSettled: () => { } } ); @@ -164,6 +165,7 @@ export const ApprovalPolicyRow = ({ workspaceId, id: policy.id, approvers: selectedApprovers, + groupApprovers: selectedGroupApprovers }, { onSettled: () => { } } );