import { AbilityBuilder, createMongoAbility, ForcedSubject, MongoAbility } from "@casl/ability"; import { conditionsMatcher } from "@app/lib/casl"; export enum ProjectPermissionActions { Read = "read", Create = "create", Edit = "edit", Delete = "delete" } export enum ProjectPermissionSub { Role = "role", Member = "member", Groups = "groups", Settings = "settings", Integrations = "integrations", Webhooks = "webhooks", ServiceTokens = "service-tokens", Environments = "environments", Tags = "tags", AuditLogs = "audit-logs", IpAllowList = "ip-allowlist", Project = "workspace", Secrets = "secrets", SecretRollback = "secret-rollback", SecretApproval = "secret-approval", SecretRotation = "secret-rotation", Identity = "identity" } type SubjectFields = { environment: string; secretPath: string; }; export type ProjectPermissionSet = | [ ProjectPermissionActions, ProjectPermissionSub.Secrets | (ForcedSubject & SubjectFields) ] | [ProjectPermissionActions, ProjectPermissionSub.Role] | [ProjectPermissionActions, ProjectPermissionSub.Tags] | [ProjectPermissionActions, ProjectPermissionSub.Member] | [ProjectPermissionActions, ProjectPermissionSub.Groups] | [ProjectPermissionActions, ProjectPermissionSub.Integrations] | [ProjectPermissionActions, ProjectPermissionSub.Webhooks] | [ProjectPermissionActions, ProjectPermissionSub.AuditLogs] | [ProjectPermissionActions, ProjectPermissionSub.Environments] | [ProjectPermissionActions, ProjectPermissionSub.IpAllowList] | [ProjectPermissionActions, ProjectPermissionSub.Settings] | [ProjectPermissionActions, ProjectPermissionSub.ServiceTokens] | [ProjectPermissionActions, ProjectPermissionSub.SecretApproval] | [ProjectPermissionActions, ProjectPermissionSub.SecretRotation] | [ProjectPermissionActions, ProjectPermissionSub.Identity] | [ProjectPermissionActions.Delete, ProjectPermissionSub.Project] | [ProjectPermissionActions.Edit, ProjectPermissionSub.Project] | [ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback] | [ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback]; const buildAdminPermissionRules = () => { 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, ProjectPermissionSub.SecretApproval); can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretApproval); can(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval); can(ProjectPermissionActions.Delete, ProjectPermissionSub.SecretApproval); can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation); can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretRotation); can(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretRotation); can(ProjectPermissionActions.Delete, ProjectPermissionSub.SecretRotation); can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback); can(ProjectPermissionActions.Read, ProjectPermissionSub.Member); can(ProjectPermissionActions.Create, ProjectPermissionSub.Member); can(ProjectPermissionActions.Edit, ProjectPermissionSub.Member); can(ProjectPermissionActions.Delete, ProjectPermissionSub.Member); can(ProjectPermissionActions.Read, ProjectPermissionSub.Groups); can(ProjectPermissionActions.Create, ProjectPermissionSub.Groups); can(ProjectPermissionActions.Edit, ProjectPermissionSub.Groups); can(ProjectPermissionActions.Delete, ProjectPermissionSub.Groups); can(ProjectPermissionActions.Read, ProjectPermissionSub.Role); can(ProjectPermissionActions.Create, ProjectPermissionSub.Role); can(ProjectPermissionActions.Edit, ProjectPermissionSub.Role); can(ProjectPermissionActions.Delete, ProjectPermissionSub.Role); can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); can(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations); can(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations); can(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, ProjectPermissionSub.Identity); can(ProjectPermissionActions.Create, ProjectPermissionSub.Identity); can(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity); can(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, ProjectPermissionSub.Settings); can(ProjectPermissionActions.Create, ProjectPermissionSub.Settings); can(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings); can(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, ProjectPermissionSub.Tags); can(ProjectPermissionActions.Create, ProjectPermissionSub.Tags); can(ProjectPermissionActions.Edit, ProjectPermissionSub.Tags); can(ProjectPermissionActions.Delete, ProjectPermissionSub.Tags); can(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs); can(ProjectPermissionActions.Create, ProjectPermissionSub.AuditLogs); can(ProjectPermissionActions.Edit, ProjectPermissionSub.AuditLogs); can(ProjectPermissionActions.Delete, ProjectPermissionSub.AuditLogs); can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList); can(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList); can(ProjectPermissionActions.Edit, ProjectPermissionSub.IpAllowList); can(ProjectPermissionActions.Delete, ProjectPermissionSub.IpAllowList); can(ProjectPermissionActions.Edit, ProjectPermissionSub.Project); can(ProjectPermissionActions.Delete, ProjectPermissionSub.Project); return rules; }; 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, ProjectPermissionSub.SecretApproval); can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation); can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback); can(ProjectPermissionActions.Read, ProjectPermissionSub.Member); can(ProjectPermissionActions.Create, ProjectPermissionSub.Member); 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, ProjectPermissionSub.Webhooks); can(ProjectPermissionActions.Create, ProjectPermissionSub.Webhooks); can(ProjectPermissionActions.Edit, ProjectPermissionSub.Webhooks); can(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, ProjectPermissionSub.ServiceTokens); can(ProjectPermissionActions.Create, ProjectPermissionSub.ServiceTokens); can(ProjectPermissionActions.Edit, ProjectPermissionSub.ServiceTokens); can(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, ProjectPermissionSub.Environments); can(ProjectPermissionActions.Create, ProjectPermissionSub.Environments); can(ProjectPermissionActions.Edit, ProjectPermissionSub.Environments); can(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, ProjectPermissionSub.Role); can(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs); can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList); return rules; }; export const projectMemberPermissions = buildMemberPermissionRules(); const buildViewerPermissionRules = () => { const { can, rules } = new AbilityBuilder>(createMongoAbility); can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets); can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval); can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation); can(ProjectPermissionActions.Read, ProjectPermissionSub.Member); can(ProjectPermissionActions.Read, ProjectPermissionSub.Groups); can(ProjectPermissionActions.Read, ProjectPermissionSub.Role); can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); can(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks); can(ProjectPermissionActions.Read, ProjectPermissionSub.Identity); can(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens); can(ProjectPermissionActions.Read, ProjectPermissionSub.Settings); can(ProjectPermissionActions.Read, ProjectPermissionSub.Environments); can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags); can(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs); can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList); return rules; }; export const projectViewerPermission = buildViewerPermissionRules(); const buildNoAccessProjectPermission = () => { const { rules } = new AbilityBuilder>(createMongoAbility); return rules; }; export const buildServiceTokenProjectPermission = ( scopes: Array<{ secretPath: string; environment: string }>, permission: string[] ) => { const canWrite = permission.includes("write"); const canRead = permission.includes("read"); const { can, build } = new AbilityBuilder>(createMongoAbility); scopes.forEach(({ secretPath, environment }) => { if (canWrite) { // TODO: @Akhi // @ts-expect-error type can(ProjectPermissionActions.Edit, ProjectPermissionSub.Secrets, { secretPath: { $glob: secretPath }, environment }); // @ts-expect-error type can(ProjectPermissionActions.Create, ProjectPermissionSub.Secrets, { secretPath: { $glob: secretPath }, environment }); // @ts-expect-error type can(ProjectPermissionActions.Delete, ProjectPermissionSub.Secrets, { secretPath: { $glob: secretPath }, environment }); } if (canRead) { // @ts-expect-error type can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets, { secretPath: { $glob: secretPath }, environment }); } }); return build({ conditionsMatcher }); }; export const projectNoAccessPermissions = buildNoAccessProjectPermission(); /* eslint-disable */ /** * Extracts and formats permissions from a CASL Ability object or a raw permission set. * @param ability * @returns */ const extractPermissions = (ability: any) => ability.A.map((permission: any) => `${permission.action}_${permission.subject}`); /** * Compares two sets of permissions to determine if the first set is at least as privileged as the second set. * The function checks if all permissions in the second set are contained within the first set and if the first set has equal or more permissions. * */ export const isAtLeastAsPrivilegedWorkspace = ( permissions1: MongoAbility | ProjectPermissionSet, permissions2: MongoAbility | ProjectPermissionSet ) => { const set1 = new Set(extractPermissions(permissions1)); const set2 = new Set(extractPermissions(permissions2)); // eslint-disable-next-line for (const perm of set2) { if (!set1.has(perm)) { return false; } } return set1.size >= set2.size; }; /* eslint-enable */