From 65241ad8bf9a7d4dd37f3d456cd626f0403f7723 Mon Sep 17 00:00:00 2001 From: = Date: Sat, 21 Sep 2024 00:48:01 +0530 Subject: [PATCH] feat: updated backend permission request definition --- .../src/ee/routes/v1/project-role-router.ts | 3 +- ...ty-project-additional-privilege-service.ts | 18 +- .../services/permission/permission-types.ts | 45 +- .../services/permission/project-permission.ts | 566 ++++++++++++------ .../project-role/project-role-service.ts | 9 +- .../ProjectRoleModifySection.utils.ts | 4 +- 6 files changed, 427 insertions(+), 218 deletions(-) diff --git a/backend/src/ee/routes/v1/project-role-router.ts b/backend/src/ee/routes/v1/project-role-router.ts index bbb03f4ae..102e21fc7 100644 --- a/backend/src/ee/routes/v1/project-role-router.ts +++ b/backend/src/ee/routes/v1/project-role-router.ts @@ -6,8 +6,9 @@ import { ProjectMembershipRole, ProjectMembershipsSchema, ProjectRolesSchema } f import { PROJECT_ROLE } from "@app/lib/api-docs"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; -import { ProjectPermissionSchema, SanitizedRoleSchema } from "@app/server/routes/sanitizedSchemas"; import { AuthMode } from "@app/services/auth/auth-type"; +import { ProjectPermissionSchema } from "@app/ee/services/permission/project-permission"; +import { SanitizedRoleSchema } from "@app/server/routes/sanitizedSchemas"; export const registerProjectRoleRouter = async (server: FastifyZodProvider) => { server.route({ diff --git a/backend/src/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service.ts b/backend/src/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service.ts index 7f2798258..f5c1c857a 100644 --- a/backend/src/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service.ts +++ b/backend/src/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service.ts @@ -34,18 +34,12 @@ export type TIdentityProjectAdditionalPrivilegeServiceFactory = ReturnType< // TODO(akhilmhdh): move this to more centralized export const UnpackedPermissionSchema = z.object({ - subject: z.union([z.string().min(1), z.string().array()]).optional(), - action: z.union([z.string().min(1), z.string().array()]), - conditions: z - .object({ - environment: z.string().optional(), - secretPath: z - .object({ - $glob: z.string().min(1) - }) - .optional() - }) - .optional() + subject: z + .union([z.string().min(1), z.string().array()]) + .transform((el) => (typeof el !== "string" ? el[0] : el)) + .optional(), + action: z.union([z.string().min(1), z.string().array()]).transform((el) => (typeof el === "string" ? [el] : el)), + conditions: z.unknown().optional() }); const unpackPermissions = (permissions: unknown) => diff --git a/backend/src/ee/services/permission/permission-types.ts b/backend/src/ee/services/permission/permission-types.ts index 620e7a61c..3bda9f52c 100644 --- a/backend/src/ee/services/permission/permission-types.ts +++ b/backend/src/ee/services/permission/permission-types.ts @@ -1,9 +1,46 @@ +import picomatch from "picomatch"; +import { z } from "zod"; + export type TBuildProjectPermissionDTO = { permissions?: unknown; role: string; }[]; -export type TBuildOrgPermissionDTO = { - permissions?: unknown; - role: string; -}[]; +export enum PermissionConditionOperators { + $IN = "$in", + $ALL = "$all", + $REGEX = "$regex", + $EQ = "$eq", + $NEQ = "$neq", + $GLOB = "$glob" +} + +export const PermissionConditionSchema = { + [PermissionConditionOperators.$IN]: z.string().array(), + [PermissionConditionOperators.$ALL]: z.string().array(), + [PermissionConditionOperators.$REGEX]: z.string().refine( + (el) => { + try { + // eslint-disable-next-line no-new + new RegExp(el); + return true; + } catch { + return false; + } + }, + { message: "Invalid regex pattern" } + ), + [PermissionConditionOperators.$EQ]: z.string(), + [PermissionConditionOperators.$NEQ]: z.string(), + [PermissionConditionOperators.$GLOB]: z.string().refine( + (el) => { + try { + picomatch.parse([el]); + return true; + } catch { + return false; + } + }, + { message: "Invalid glob pattern" } + ) +}; diff --git a/backend/src/ee/services/permission/project-permission.ts b/backend/src/ee/services/permission/project-permission.ts index 60daa14c4..da362576d 100644 --- a/backend/src/ee/services/permission/project-permission.ts +++ b/backend/src/ee/services/permission/project-permission.ts @@ -1,7 +1,8 @@ import { AbilityBuilder, createMongoAbility, ForcedSubject, MongoAbility } from "@casl/ability"; import { conditionsMatcher } from "@app/lib/casl"; -import { BadRequestError } from "@app/lib/errors"; +import { z } from "zod"; +import { PermissionConditionOperators, PermissionConditionSchema } from "./permission-types"; export enum ProjectPermissionActions { Read = "read", @@ -76,128 +77,296 @@ export type ProjectPermissionSet = | [ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback] | [ProjectPermissionActions.Edit, ProjectPermissionSub.Kms]; -export const fullProjectPermissionSet: [ProjectPermissionActions, ProjectPermissionSub][] = [ - [ProjectPermissionActions.Read, ProjectPermissionSub.Secrets], - [ProjectPermissionActions.Create, ProjectPermissionSub.Secrets], - [ProjectPermissionActions.Edit, ProjectPermissionSub.Secrets], - [ProjectPermissionActions.Delete, ProjectPermissionSub.Secrets], +const CASL_ACTION_SCHEMA_NATIVE_ENUM = (actions: ACTION) => + z + .union([z.nativeEnum(actions), z.nativeEnum(actions).array().min(1)]) + .transform((el) => (typeof el === "string" ? [el] : el)); - [ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval], - [ProjectPermissionActions.Create, ProjectPermissionSub.SecretApproval], - [ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval], - [ProjectPermissionActions.Delete, ProjectPermissionSub.SecretApproval], +const CASL_ACTION_SCHEMA_ENUM = (actions: ACTION) => + z.union([z.enum(actions), z.enum(actions).array().min(1)]).transform((el) => (typeof el === "string" ? [el] : el)); - [ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation], - [ProjectPermissionActions.Create, ProjectPermissionSub.SecretRotation], - [ProjectPermissionActions.Edit, ProjectPermissionSub.SecretRotation], - [ProjectPermissionActions.Delete, ProjectPermissionSub.SecretRotation], +const SecretConditionSchema = z + .object({ + environment: z.union([ + z.string(), + z + .object({ + [PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ], + [PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ], + [PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN], + [PermissionConditionOperators.$ALL]: PermissionConditionSchema[PermissionConditionOperators.$ALL], + [PermissionConditionOperators.$REGEX]: PermissionConditionSchema[PermissionConditionOperators.$REGEX], + [PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB] + }) + .partial() + ]), + secretPath: z.union([ + z.string(), + z + .object({ + [PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ], + [PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ], + [PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN], + [PermissionConditionOperators.$ALL]: PermissionConditionSchema[PermissionConditionOperators.$ALL], + [PermissionConditionOperators.$REGEX]: PermissionConditionSchema[PermissionConditionOperators.$REGEX], + [PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB] + }) + .partial() + ]), + secretName: z.union([ + z.string(), + z + .object({ + [PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ], + [PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ], + [PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN], + [PermissionConditionOperators.$ALL]: PermissionConditionSchema[PermissionConditionOperators.$ALL], + [PermissionConditionOperators.$REGEX]: PermissionConditionSchema[PermissionConditionOperators.$REGEX], + [PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB] + }) + .partial() + ]) + }) + .partial(); - [ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback], - [ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback], - - [ProjectPermissionActions.Read, ProjectPermissionSub.Member], - [ProjectPermissionActions.Create, ProjectPermissionSub.Member], - [ProjectPermissionActions.Edit, ProjectPermissionSub.Member], - [ProjectPermissionActions.Delete, ProjectPermissionSub.Member], - - [ProjectPermissionActions.Read, ProjectPermissionSub.Groups], - [ProjectPermissionActions.Create, ProjectPermissionSub.Groups], - [ProjectPermissionActions.Edit, ProjectPermissionSub.Groups], - [ProjectPermissionActions.Delete, ProjectPermissionSub.Groups], - - [ProjectPermissionActions.Read, ProjectPermissionSub.Role], - [ProjectPermissionActions.Create, ProjectPermissionSub.Role], - [ProjectPermissionActions.Edit, ProjectPermissionSub.Role], - [ProjectPermissionActions.Delete, ProjectPermissionSub.Role], - - [ProjectPermissionActions.Read, ProjectPermissionSub.Integrations], - [ProjectPermissionActions.Create, ProjectPermissionSub.Integrations], - [ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations], - [ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations], - - [ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks], - [ProjectPermissionActions.Create, ProjectPermissionSub.Webhooks], - [ProjectPermissionActions.Edit, ProjectPermissionSub.Webhooks], - [ProjectPermissionActions.Delete, ProjectPermissionSub.Webhooks], - - [ProjectPermissionActions.Read, ProjectPermissionSub.Identity], - [ProjectPermissionActions.Create, ProjectPermissionSub.Identity], - [ProjectPermissionActions.Edit, ProjectPermissionSub.Identity], - [ProjectPermissionActions.Delete, ProjectPermissionSub.Identity], - - [ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens], - [ProjectPermissionActions.Create, ProjectPermissionSub.ServiceTokens], - [ProjectPermissionActions.Edit, ProjectPermissionSub.ServiceTokens], - [ProjectPermissionActions.Delete, ProjectPermissionSub.ServiceTokens], - - [ProjectPermissionActions.Read, ProjectPermissionSub.Settings], - [ProjectPermissionActions.Create, ProjectPermissionSub.Settings], - [ProjectPermissionActions.Edit, ProjectPermissionSub.Settings], - [ProjectPermissionActions.Delete, ProjectPermissionSub.Settings], - - [ProjectPermissionActions.Read, ProjectPermissionSub.Environments], - [ProjectPermissionActions.Create, ProjectPermissionSub.Environments], - [ProjectPermissionActions.Edit, ProjectPermissionSub.Environments], - [ProjectPermissionActions.Delete, ProjectPermissionSub.Environments], - - [ProjectPermissionActions.Read, ProjectPermissionSub.Tags], - [ProjectPermissionActions.Create, ProjectPermissionSub.Tags], - [ProjectPermissionActions.Edit, ProjectPermissionSub.Tags], - [ProjectPermissionActions.Delete, ProjectPermissionSub.Tags], - - // TODO(Daniel): Remove the audit logs permissions from project-level permissions. - // TODO: We haven't done this yet because it might break existing roles, since those roles will become "invalid" since the audit log permission defined on those roles, no longer exist in the project-level defined permissions. - [ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs], - [ProjectPermissionActions.Create, ProjectPermissionSub.AuditLogs], - [ProjectPermissionActions.Edit, ProjectPermissionSub.AuditLogs], - [ProjectPermissionActions.Delete, ProjectPermissionSub.AuditLogs], - - [ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList], - [ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList], - [ProjectPermissionActions.Edit, ProjectPermissionSub.IpAllowList], - [ProjectPermissionActions.Delete, ProjectPermissionSub.IpAllowList], - - // double check if all CRUD are needed for CA and Certificates - [ProjectPermissionActions.Read, ProjectPermissionSub.CertificateAuthorities], - [ProjectPermissionActions.Create, ProjectPermissionSub.CertificateAuthorities], - [ProjectPermissionActions.Edit, ProjectPermissionSub.CertificateAuthorities], - [ProjectPermissionActions.Delete, ProjectPermissionSub.CertificateAuthorities], - - [ProjectPermissionActions.Read, ProjectPermissionSub.Certificates], - [ProjectPermissionActions.Create, ProjectPermissionSub.Certificates], - [ProjectPermissionActions.Edit, ProjectPermissionSub.Certificates], - [ProjectPermissionActions.Delete, ProjectPermissionSub.Certificates], - - [ProjectPermissionActions.Read, ProjectPermissionSub.CertificateTemplates], - [ProjectPermissionActions.Create, ProjectPermissionSub.CertificateTemplates], - [ProjectPermissionActions.Edit, ProjectPermissionSub.CertificateTemplates], - [ProjectPermissionActions.Delete, ProjectPermissionSub.CertificateTemplates], - - [ProjectPermissionActions.Read, ProjectPermissionSub.PkiAlerts], - [ProjectPermissionActions.Create, ProjectPermissionSub.PkiAlerts], - [ProjectPermissionActions.Edit, ProjectPermissionSub.PkiAlerts], - [ProjectPermissionActions.Delete, ProjectPermissionSub.PkiAlerts], - - [ProjectPermissionActions.Read, ProjectPermissionSub.PkiCollections], - [ProjectPermissionActions.Create, ProjectPermissionSub.PkiCollections], - [ProjectPermissionActions.Edit, ProjectPermissionSub.PkiCollections], - [ProjectPermissionActions.Delete, ProjectPermissionSub.PkiCollections], - - [ProjectPermissionActions.Edit, ProjectPermissionSub.Project], - [ProjectPermissionActions.Delete, ProjectPermissionSub.Project], - - [ProjectPermissionActions.Edit, ProjectPermissionSub.Kms] -]; +export const ProjectPermissionSchema = z.discriminatedUnion("subject", [ + z.object({ + subject: z + .literal(ProjectPermissionSub.Secrets) + .describe("The entity this permission pertains to. Possible options: secrets, environments"), + action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe( + "Describe what action an entity can take." + ), + conditions: SecretConditionSchema.describe( + "When specified, only matching conditions will be allowed to access given resource." + ).optional() + }), + z.object({ + subject: z + .literal(ProjectPermissionSub.SecretApproval) + .describe("The entity this permission pertains to. Possible options: secrets, environments"), + action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe( + "Describe what action an entity can take." + ) + }), + z.object({ + subject: z + .literal(ProjectPermissionSub.SecretRotation) + .describe("The entity this permission pertains to. Possible options: secrets, environments"), + action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe( + "Describe what action an entity can take." + ) + }), + z.object({ + subject: z + .literal(ProjectPermissionSub.SecretRollback) + .describe("The entity this permission pertains to. Possible options: secrets, environments"), + action: CASL_ACTION_SCHEMA_ENUM([ProjectPermissionActions.Read, ProjectPermissionActions.Create]).describe( + "Describe what action an entity can take." + ) + }), + z.object({ + subject: z + .literal(ProjectPermissionSub.Member) + .describe("The entity this permission pertains to. Possible options: secrets, environments"), + action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe( + "Describe what action an entity can take." + ) + }), + z.object({ + subject: z + .literal(ProjectPermissionSub.Groups) + .describe("The entity this permission pertains to. Possible options: secrets, environments"), + action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe( + "Describe what action an entity can take." + ) + }), + z.object({ + subject: z + .literal(ProjectPermissionSub.Role) + .describe("The entity this permission pertains to. Possible options: secrets, environments"), + action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe( + "Describe what action an entity can take." + ) + }), + z.object({ + subject: z + .literal(ProjectPermissionSub.Integrations) + .describe("The entity this permission pertains to. Possible options: secrets, environments"), + action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe( + "Describe what action an entity can take." + ) + }), + z.object({ + subject: z + .literal(ProjectPermissionSub.Webhooks) + .describe("The entity this permission pertains to. Possible options: secrets, environments"), + action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe( + "Describe what action an entity can take." + ) + }), + z.object({ + subject: z + .literal(ProjectPermissionSub.Identity) + .describe("The entity this permission pertains to. Possible options: secrets, environments"), + action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe( + "Describe what action an entity can take." + ) + }), + z.object({ + subject: z + .literal(ProjectPermissionSub.ServiceTokens) + .describe("The entity this permission pertains to. Possible options: secrets, environments"), + action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe( + "Describe what action an entity can take." + ) + }), + z.object({ + subject: z + .literal(ProjectPermissionSub.Settings) + .describe("The entity this permission pertains to. Possible options: secrets, environments"), + action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe( + "Describe what action an entity can take." + ) + }), + z.object({ + subject: z + .literal(ProjectPermissionSub.Environments) + .describe("The entity this permission pertains to. Possible options: secrets, environments"), + action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe( + "Describe what action an entity can take." + ) + }), + z.object({ + subject: z + .literal(ProjectPermissionSub.Tags) + .describe("The entity this permission pertains to. Possible options: secrets, environments"), + action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe( + "Describe what action an entity can take." + ) + }), + z.object({ + subject: z + .literal(ProjectPermissionSub.AuditLogs) + .describe("The entity this permission pertains to. Possible options: secrets, environments"), + action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe( + "Describe what action an entity can take." + ) + }), + z.object({ + subject: z + .literal(ProjectPermissionSub.IpAllowList) + .describe("The entity this permission pertains to. Possible options: secrets, environments"), + action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe( + "Describe what action an entity can take." + ) + }), + z.object({ + subject: z + .literal(ProjectPermissionSub.CertificateAuthorities) + .describe("The entity this permission pertains to. Possible options: secrets, environments"), + action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe( + "Describe what action an entity can take." + ) + }), + z.object({ + subject: z + .literal(ProjectPermissionSub.Certificates) + .describe("The entity this permission pertains to. Possible options: secrets, environments"), + action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe( + "Describe what action an entity can take." + ) + }), + z.object({ + subject: z + .literal(ProjectPermissionSub.CertificateTemplates) + .describe("The entity this permission pertains to. Possible options: secrets, environments"), + action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe( + "Describe what action an entity can take." + ) + }), + z.object({ + subject: z + .literal(ProjectPermissionSub.PkiAlerts) + .describe("The entity this permission pertains to. Possible options: secrets, environments"), + action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe( + "Describe what action an entity can take." + ) + }), + z.object({ + subject: z + .literal(ProjectPermissionSub.PkiCollections) + .describe("The entity this permission pertains to. Possible options: secrets, environments"), + action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe( + "Describe what action an entity can take." + ) + }), + z.object({ + subject: z + .literal(ProjectPermissionSub.Project) + .describe("The entity this permission pertains to. Possible options: secrets, environments"), + action: CASL_ACTION_SCHEMA_ENUM([ProjectPermissionActions.Edit, ProjectPermissionActions.Delete]).describe( + "Describe what action an entity can take." + ) + }), + z.object({ + subject: z + .literal(ProjectPermissionSub.Kms) + .describe("The entity this permission pertains to. Possible options: secrets, environments"), + action: CASL_ACTION_SCHEMA_ENUM([ProjectPermissionActions.Edit]).describe( + "Describe what action an entity can take." + ) + }), + z.object({ + subject: z + .literal(ProjectPermissionSub.SecretFolders) + .describe("The entity this permission pertains to. Possible options: secrets, environments"), + action: CASL_ACTION_SCHEMA_ENUM([ProjectPermissionActions.Read]).describe( + "Describe what action an entity can take." + ) + }) +]); const buildAdminPermissionRules = () => { const { can, rules } = new AbilityBuilder>(createMongoAbility); // Admins get full access to everything - fullProjectPermissionSet.forEach((permission) => { - const [action, subject] = permission; - can(action, subject); + [ + ProjectPermissionSub.Secrets, + ProjectPermissionSub.SecretApproval, + ProjectPermissionSub.SecretRotation, + ProjectPermissionSub.Member, + ProjectPermissionSub.Groups, + ProjectPermissionSub.Role, + ProjectPermissionSub.Integrations, + ProjectPermissionSub.Webhooks, + ProjectPermissionSub.Identity, + ProjectPermissionSub.ServiceTokens, + ProjectPermissionSub.Settings, + ProjectPermissionSub.Environments, + ProjectPermissionSub.Tags, + ProjectPermissionSub.AuditLogs, + ProjectPermissionSub.IpAllowList, + ProjectPermissionSub.CertificateAuthorities, + ProjectPermissionSub.Certificates, + ProjectPermissionSub.CertificateTemplates, + ProjectPermissionSub.PkiAlerts, + ProjectPermissionSub.PkiCollections + ].forEach((el) => { + can( + [ + ProjectPermissionActions.Read, + ProjectPermissionActions.Edit, + ProjectPermissionActions.Create, + ProjectPermissionActions.Delete + ], + el as ProjectPermissionSub + ); }); + can([ProjectPermissionActions.Edit, ProjectPermissionActions.Delete], ProjectPermissionSub.Project); + can([ProjectPermissionActions.Read, ProjectPermissionActions.Create], ProjectPermissionSub.SecretRollback); + can([ProjectPermissionActions.Edit], ProjectPermissionSub.Kms); return rules; }; @@ -206,73 +375,116 @@ export const projectAdminPermissions = buildAdminPermissionRules(); const buildMemberPermissionRules = () => { const { can, rules } = new AbilityBuilder>(createMongoAbility); - can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets); - can(ProjectPermissionActions.Create, ProjectPermissionSub.Secrets); - can(ProjectPermissionActions.Edit, ProjectPermissionSub.Secrets); - can(ProjectPermissionActions.Delete, ProjectPermissionSub.Secrets); + can( + [ + ProjectPermissionActions.Read, + ProjectPermissionActions.Edit, + ProjectPermissionActions.Create, + ProjectPermissionActions.Delete + ], + ProjectPermissionSub.Secrets + ); - can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval); - can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation); + can([ProjectPermissionActions.Read], ProjectPermissionSub.SecretApproval); + can([ProjectPermissionActions.Read], ProjectPermissionSub.SecretRotation); - can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); - can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback); + can([ProjectPermissionActions.Read, ProjectPermissionActions.Create], ProjectPermissionSub.SecretRollback); - can(ProjectPermissionActions.Read, ProjectPermissionSub.Member); - can(ProjectPermissionActions.Create, ProjectPermissionSub.Member); + can([ProjectPermissionActions.Read, ProjectPermissionActions.Create], ProjectPermissionSub.Member); - can(ProjectPermissionActions.Read, ProjectPermissionSub.Groups); + can([ProjectPermissionActions.Read], ProjectPermissionSub.Groups); - can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); - can(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations); - can(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations); - can(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations); + can( + [ + ProjectPermissionActions.Read, + ProjectPermissionActions.Edit, + ProjectPermissionActions.Create, + ProjectPermissionActions.Delete + ], + ProjectPermissionSub.Integrations + ); - can(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks); - can(ProjectPermissionActions.Create, ProjectPermissionSub.Webhooks); - can(ProjectPermissionActions.Edit, ProjectPermissionSub.Webhooks); - can(ProjectPermissionActions.Delete, ProjectPermissionSub.Webhooks); + can( + [ + ProjectPermissionActions.Read, + ProjectPermissionActions.Edit, + ProjectPermissionActions.Create, + ProjectPermissionActions.Delete + ], + ProjectPermissionSub.Webhooks + ); - can(ProjectPermissionActions.Read, ProjectPermissionSub.Identity); - can(ProjectPermissionActions.Create, ProjectPermissionSub.Identity); - can(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity); - can(ProjectPermissionActions.Delete, ProjectPermissionSub.Identity); + can( + [ + ProjectPermissionActions.Read, + ProjectPermissionActions.Edit, + ProjectPermissionActions.Create, + ProjectPermissionActions.Delete + ], + ProjectPermissionSub.Identity + ); - can(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens); - can(ProjectPermissionActions.Create, ProjectPermissionSub.ServiceTokens); - can(ProjectPermissionActions.Edit, ProjectPermissionSub.ServiceTokens); - can(ProjectPermissionActions.Delete, ProjectPermissionSub.ServiceTokens); + can( + [ + ProjectPermissionActions.Read, + ProjectPermissionActions.Edit, + ProjectPermissionActions.Create, + ProjectPermissionActions.Delete + ], + ProjectPermissionSub.ServiceTokens + ); - can(ProjectPermissionActions.Read, ProjectPermissionSub.Settings); - can(ProjectPermissionActions.Create, ProjectPermissionSub.Settings); - can(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings); - can(ProjectPermissionActions.Delete, ProjectPermissionSub.Settings); + can( + [ + ProjectPermissionActions.Read, + ProjectPermissionActions.Edit, + ProjectPermissionActions.Create, + ProjectPermissionActions.Delete + ], + ProjectPermissionSub.Settings + ); - can(ProjectPermissionActions.Read, ProjectPermissionSub.Environments); - can(ProjectPermissionActions.Create, ProjectPermissionSub.Environments); - can(ProjectPermissionActions.Edit, ProjectPermissionSub.Environments); - can(ProjectPermissionActions.Delete, ProjectPermissionSub.Environments); + can( + [ + ProjectPermissionActions.Read, + ProjectPermissionActions.Edit, + ProjectPermissionActions.Create, + ProjectPermissionActions.Delete + ], + ProjectPermissionSub.Environments + ); - can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags); - can(ProjectPermissionActions.Create, ProjectPermissionSub.Tags); - can(ProjectPermissionActions.Edit, ProjectPermissionSub.Tags); - can(ProjectPermissionActions.Delete, ProjectPermissionSub.Tags); + can( + [ + ProjectPermissionActions.Read, + ProjectPermissionActions.Edit, + ProjectPermissionActions.Create, + ProjectPermissionActions.Delete + ], + ProjectPermissionSub.Tags + ); - can(ProjectPermissionActions.Read, ProjectPermissionSub.Role); - can(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs); - can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList); + can([ProjectPermissionActions.Read], ProjectPermissionSub.Role); + can([ProjectPermissionActions.Read], ProjectPermissionSub.AuditLogs); + can([ProjectPermissionActions.Read], ProjectPermissionSub.IpAllowList); // double check if all CRUD are needed for CA and Certificates - can(ProjectPermissionActions.Read, ProjectPermissionSub.CertificateAuthorities); + can([ProjectPermissionActions.Read], ProjectPermissionSub.CertificateAuthorities); - can(ProjectPermissionActions.Read, ProjectPermissionSub.Certificates); - can(ProjectPermissionActions.Create, ProjectPermissionSub.Certificates); - can(ProjectPermissionActions.Edit, ProjectPermissionSub.Certificates); - can(ProjectPermissionActions.Delete, ProjectPermissionSub.Certificates); + can( + [ + ProjectPermissionActions.Read, + ProjectPermissionActions.Edit, + ProjectPermissionActions.Create, + ProjectPermissionActions.Delete + ], + ProjectPermissionSub.Certificates + ); - can(ProjectPermissionActions.Read, ProjectPermissionSub.CertificateTemplates); + can([ProjectPermissionActions.Read], ProjectPermissionSub.CertificateTemplates); - can(ProjectPermissionActions.Read, ProjectPermissionSub.PkiAlerts); - can(ProjectPermissionActions.Read, ProjectPermissionSub.PkiCollections); + can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiAlerts); + can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiCollections); return rules; }; @@ -382,32 +594,4 @@ export const isAtLeastAsPrivilegedWorkspace = ( return set1.size >= set2.size; }; - -/* - * Case: The user requests to create a role with permissions that are not valid and not supposed to be used ever. - * If we don't check for this, we can run into issues where functions like the `isAtLeastAsPrivileged` will not work as expected, because we compare the size of each permission set. - * If the permission set contains invalid permissions, the size will be different, and result in incorrect results. - */ -export const validateProjectPermissions = (permissions: unknown) => { - const parsedPermissions = - typeof permissions === "string" ? (JSON.parse(permissions) as string[]) : (permissions as string[]); - - const flattenedPermissions = [...parsedPermissions]; - - for (const perm of flattenedPermissions) { - const [action, subject] = perm; - - if ( - !fullProjectPermissionSet.find( - (currentPermission) => currentPermission[0] === action && currentPermission[1] === subject - ) - ) { - throw new BadRequestError({ - message: `Permission action ${action} on subject ${subject} is not valid`, - name: "Create Role" - }); - } - } -}; - /* eslint-enable */ diff --git a/backend/src/services/project-role/project-role-service.ts b/backend/src/services/project-role/project-role-service.ts index a1e22504f..49c4f81c9 100644 --- a/backend/src/services/project-role/project-role-service.ts +++ b/backend/src/services/project-role/project-role-service.ts @@ -7,8 +7,7 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio import { ProjectPermissionActions, ProjectPermissionSet, - ProjectPermissionSub, - validateProjectPermissions + ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; @@ -60,8 +59,6 @@ export const projectRoleServiceFactory = ({ throw new BadRequestError({ name: "Create Role", message: "Project role with same slug already exists" }); } - validateProjectPermissions(data.permissions); - const role = await projectRoleDAL.create({ ...data, projectId @@ -127,10 +124,6 @@ export const projectRoleServiceFactory = ({ throw new BadRequestError({ name: "Update Role", message: "Project role with the same slug already exists" }); } - if (data.permissions) { - validateProjectPermissions(data.permissions); - } - const [updatedRole] = await projectRoleDAL.update( { id: roleId, projectId }, { diff --git a/frontend/src/views/Project/RolePage/components/RolePermissionsSection/ProjectRoleModifySection.utils.ts b/frontend/src/views/Project/RolePage/components/RolePermissionsSection/ProjectRoleModifySection.utils.ts index cc5c677d0..915509d60 100644 --- a/frontend/src/views/Project/RolePage/components/RolePermissionsSection/ProjectRoleModifySection.utils.ts +++ b/frontend/src/views/Project/RolePage/components/RolePermissionsSection/ProjectRoleModifySection.utils.ts @@ -217,7 +217,7 @@ export const formRolePermission2API = (formVal: TFormSchema["permissions"]) => { Object.entries(formVal || {}).forEach(([subject, rules]) => { rules.forEach((actions) => { const caslActions = Object.keys(actions).filter( - (el) => actions?.[el as keyof typeof actions] + (el) => actions?.[el as keyof typeof actions] && el !== "conditions" ); const caslConditions = "conditions" in actions @@ -226,7 +226,7 @@ export const formRolePermission2API = (formVal: TFormSchema["permissions"]) => { permissions.push({ action: caslActions, - subject: [subject], + subject, conditions: caslConditions }); });