feat: updated backend permission request definition

This commit is contained in:
=
2024-10-02 00:39:43 +05:30
parent 6a7760f33f
commit 65241ad8bf
6 changed files with 427 additions and 218 deletions
@@ -6,8 +6,9 @@ import { ProjectMembershipRole, ProjectMembershipsSchema, ProjectRolesSchema } f
import { PROJECT_ROLE } from "@app/lib/api-docs"; import { PROJECT_ROLE } from "@app/lib/api-docs";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; 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 { 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) => { export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
server.route({ server.route({
@@ -34,18 +34,12 @@ export type TIdentityProjectAdditionalPrivilegeServiceFactory = ReturnType<
// TODO(akhilmhdh): move this to more centralized // TODO(akhilmhdh): move this to more centralized
export const UnpackedPermissionSchema = z.object({ export const UnpackedPermissionSchema = z.object({
subject: z.union([z.string().min(1), z.string().array()]).optional(), subject: z
action: z.union([z.string().min(1), z.string().array()]), .union([z.string().min(1), z.string().array()])
conditions: z .transform((el) => (typeof el !== "string" ? el[0] : el))
.object({ .optional(),
environment: z.string().optional(), action: z.union([z.string().min(1), z.string().array()]).transform((el) => (typeof el === "string" ? [el] : el)),
secretPath: z conditions: z.unknown().optional()
.object({
$glob: z.string().min(1)
})
.optional()
})
.optional()
}); });
const unpackPermissions = (permissions: unknown) => const unpackPermissions = (permissions: unknown) =>
@@ -1,9 +1,46 @@
import picomatch from "picomatch";
import { z } from "zod";
export type TBuildProjectPermissionDTO = { export type TBuildProjectPermissionDTO = {
permissions?: unknown; permissions?: unknown;
role: string; role: string;
}[]; }[];
export type TBuildOrgPermissionDTO = { export enum PermissionConditionOperators {
permissions?: unknown; $IN = "$in",
role: string; $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" }
)
};
@@ -1,7 +1,8 @@
import { AbilityBuilder, createMongoAbility, ForcedSubject, MongoAbility } from "@casl/ability"; import { AbilityBuilder, createMongoAbility, ForcedSubject, MongoAbility } from "@casl/ability";
import { conditionsMatcher } from "@app/lib/casl"; 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 { export enum ProjectPermissionActions {
Read = "read", Read = "read",
@@ -76,128 +77,296 @@ export type ProjectPermissionSet =
| [ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback] | [ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback]
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Kms]; | [ProjectPermissionActions.Edit, ProjectPermissionSub.Kms];
export const fullProjectPermissionSet: [ProjectPermissionActions, ProjectPermissionSub][] = [ const CASL_ACTION_SCHEMA_NATIVE_ENUM = <ACTION extends z.EnumLike>(actions: ACTION) =>
[ProjectPermissionActions.Read, ProjectPermissionSub.Secrets], z
[ProjectPermissionActions.Create, ProjectPermissionSub.Secrets], .union([z.nativeEnum(actions), z.nativeEnum(actions).array().min(1)])
[ProjectPermissionActions.Edit, ProjectPermissionSub.Secrets], .transform((el) => (typeof el === "string" ? [el] : el));
[ProjectPermissionActions.Delete, ProjectPermissionSub.Secrets],
[ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval], const CASL_ACTION_SCHEMA_ENUM = <ACTION extends z.EnumValues>(actions: ACTION) =>
[ProjectPermissionActions.Create, ProjectPermissionSub.SecretApproval], z.union([z.enum(actions), z.enum(actions).array().min(1)]).transform((el) => (typeof el === "string" ? [el] : el));
[ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval],
[ProjectPermissionActions.Delete, ProjectPermissionSub.SecretApproval],
[ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation], const SecretConditionSchema = z
[ProjectPermissionActions.Create, ProjectPermissionSub.SecretRotation], .object({
[ProjectPermissionActions.Edit, ProjectPermissionSub.SecretRotation], environment: z.union([
[ProjectPermissionActions.Delete, ProjectPermissionSub.SecretRotation], 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], export const ProjectPermissionSchema = z.discriminatedUnion("subject", [
[ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback], z.object({
subject: z
[ProjectPermissionActions.Read, ProjectPermissionSub.Member], .literal(ProjectPermissionSub.Secrets)
[ProjectPermissionActions.Create, ProjectPermissionSub.Member], .describe("The entity this permission pertains to. Possible options: secrets, environments"),
[ProjectPermissionActions.Edit, ProjectPermissionSub.Member], action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
[ProjectPermissionActions.Delete, ProjectPermissionSub.Member], "Describe what action an entity can take."
),
[ProjectPermissionActions.Read, ProjectPermissionSub.Groups], conditions: SecretConditionSchema.describe(
[ProjectPermissionActions.Create, ProjectPermissionSub.Groups], "When specified, only matching conditions will be allowed to access given resource."
[ProjectPermissionActions.Edit, ProjectPermissionSub.Groups], ).optional()
[ProjectPermissionActions.Delete, ProjectPermissionSub.Groups], }),
z.object({
[ProjectPermissionActions.Read, ProjectPermissionSub.Role], subject: z
[ProjectPermissionActions.Create, ProjectPermissionSub.Role], .literal(ProjectPermissionSub.SecretApproval)
[ProjectPermissionActions.Edit, ProjectPermissionSub.Role], .describe("The entity this permission pertains to. Possible options: secrets, environments"),
[ProjectPermissionActions.Delete, ProjectPermissionSub.Role], action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
"Describe what action an entity can take."
[ProjectPermissionActions.Read, ProjectPermissionSub.Integrations], )
[ProjectPermissionActions.Create, ProjectPermissionSub.Integrations], }),
[ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations], z.object({
[ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations], subject: z
.literal(ProjectPermissionSub.SecretRotation)
[ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks], .describe("The entity this permission pertains to. Possible options: secrets, environments"),
[ProjectPermissionActions.Create, ProjectPermissionSub.Webhooks], action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
[ProjectPermissionActions.Edit, ProjectPermissionSub.Webhooks], "Describe what action an entity can take."
[ProjectPermissionActions.Delete, ProjectPermissionSub.Webhooks], )
}),
[ProjectPermissionActions.Read, ProjectPermissionSub.Identity], z.object({
[ProjectPermissionActions.Create, ProjectPermissionSub.Identity], subject: z
[ProjectPermissionActions.Edit, ProjectPermissionSub.Identity], .literal(ProjectPermissionSub.SecretRollback)
[ProjectPermissionActions.Delete, ProjectPermissionSub.Identity], .describe("The entity this permission pertains to. Possible options: secrets, environments"),
action: CASL_ACTION_SCHEMA_ENUM([ProjectPermissionActions.Read, ProjectPermissionActions.Create]).describe(
[ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens], "Describe what action an entity can take."
[ProjectPermissionActions.Create, ProjectPermissionSub.ServiceTokens], )
[ProjectPermissionActions.Edit, ProjectPermissionSub.ServiceTokens], }),
[ProjectPermissionActions.Delete, ProjectPermissionSub.ServiceTokens], z.object({
subject: z
[ProjectPermissionActions.Read, ProjectPermissionSub.Settings], .literal(ProjectPermissionSub.Member)
[ProjectPermissionActions.Create, ProjectPermissionSub.Settings], .describe("The entity this permission pertains to. Possible options: secrets, environments"),
[ProjectPermissionActions.Edit, ProjectPermissionSub.Settings], action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
[ProjectPermissionActions.Delete, ProjectPermissionSub.Settings], "Describe what action an entity can take."
)
[ProjectPermissionActions.Read, ProjectPermissionSub.Environments], }),
[ProjectPermissionActions.Create, ProjectPermissionSub.Environments], z.object({
[ProjectPermissionActions.Edit, ProjectPermissionSub.Environments], subject: z
[ProjectPermissionActions.Delete, ProjectPermissionSub.Environments], .literal(ProjectPermissionSub.Groups)
.describe("The entity this permission pertains to. Possible options: secrets, environments"),
[ProjectPermissionActions.Read, ProjectPermissionSub.Tags], action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
[ProjectPermissionActions.Create, ProjectPermissionSub.Tags], "Describe what action an entity can take."
[ProjectPermissionActions.Edit, ProjectPermissionSub.Tags], )
[ProjectPermissionActions.Delete, ProjectPermissionSub.Tags], }),
z.object({
// TODO(Daniel): Remove the audit logs permissions from project-level permissions. subject: z
// 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. .literal(ProjectPermissionSub.Role)
[ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs], .describe("The entity this permission pertains to. Possible options: secrets, environments"),
[ProjectPermissionActions.Create, ProjectPermissionSub.AuditLogs], action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
[ProjectPermissionActions.Edit, ProjectPermissionSub.AuditLogs], "Describe what action an entity can take."
[ProjectPermissionActions.Delete, ProjectPermissionSub.AuditLogs], )
}),
[ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList], z.object({
[ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList], subject: z
[ProjectPermissionActions.Edit, ProjectPermissionSub.IpAllowList], .literal(ProjectPermissionSub.Integrations)
[ProjectPermissionActions.Delete, ProjectPermissionSub.IpAllowList], .describe("The entity this permission pertains to. Possible options: secrets, environments"),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
// double check if all CRUD are needed for CA and Certificates "Describe what action an entity can take."
[ProjectPermissionActions.Read, ProjectPermissionSub.CertificateAuthorities], )
[ProjectPermissionActions.Create, ProjectPermissionSub.CertificateAuthorities], }),
[ProjectPermissionActions.Edit, ProjectPermissionSub.CertificateAuthorities], z.object({
[ProjectPermissionActions.Delete, ProjectPermissionSub.CertificateAuthorities], subject: z
.literal(ProjectPermissionSub.Webhooks)
[ProjectPermissionActions.Read, ProjectPermissionSub.Certificates], .describe("The entity this permission pertains to. Possible options: secrets, environments"),
[ProjectPermissionActions.Create, ProjectPermissionSub.Certificates], action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
[ProjectPermissionActions.Edit, ProjectPermissionSub.Certificates], "Describe what action an entity can take."
[ProjectPermissionActions.Delete, ProjectPermissionSub.Certificates], )
}),
[ProjectPermissionActions.Read, ProjectPermissionSub.CertificateTemplates], z.object({
[ProjectPermissionActions.Create, ProjectPermissionSub.CertificateTemplates], subject: z
[ProjectPermissionActions.Edit, ProjectPermissionSub.CertificateTemplates], .literal(ProjectPermissionSub.Identity)
[ProjectPermissionActions.Delete, ProjectPermissionSub.CertificateTemplates], .describe("The entity this permission pertains to. Possible options: secrets, environments"),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
[ProjectPermissionActions.Read, ProjectPermissionSub.PkiAlerts], "Describe what action an entity can take."
[ProjectPermissionActions.Create, ProjectPermissionSub.PkiAlerts], )
[ProjectPermissionActions.Edit, ProjectPermissionSub.PkiAlerts], }),
[ProjectPermissionActions.Delete, ProjectPermissionSub.PkiAlerts], z.object({
subject: z
[ProjectPermissionActions.Read, ProjectPermissionSub.PkiCollections], .literal(ProjectPermissionSub.ServiceTokens)
[ProjectPermissionActions.Create, ProjectPermissionSub.PkiCollections], .describe("The entity this permission pertains to. Possible options: secrets, environments"),
[ProjectPermissionActions.Edit, ProjectPermissionSub.PkiCollections], action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
[ProjectPermissionActions.Delete, ProjectPermissionSub.PkiCollections], "Describe what action an entity can take."
)
[ProjectPermissionActions.Edit, ProjectPermissionSub.Project], }),
[ProjectPermissionActions.Delete, ProjectPermissionSub.Project], z.object({
subject: z
[ProjectPermissionActions.Edit, ProjectPermissionSub.Kms] .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 buildAdminPermissionRules = () => {
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility); const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
// Admins get full access to everything // Admins get full access to everything
fullProjectPermissionSet.forEach((permission) => { [
const [action, subject] = permission; ProjectPermissionSub.Secrets,
can(action, subject); 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; return rules;
}; };
@@ -206,73 +375,116 @@ export const projectAdminPermissions = buildAdminPermissionRules();
const buildMemberPermissionRules = () => { const buildMemberPermissionRules = () => {
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility); const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets); can(
can(ProjectPermissionActions.Create, ProjectPermissionSub.Secrets); [
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Secrets); ProjectPermissionActions.Read,
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Secrets); ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.Secrets
);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval); can([ProjectPermissionActions.Read], ProjectPermissionSub.SecretApproval);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation); can([ProjectPermissionActions.Read], ProjectPermissionSub.SecretRotation);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); can([ProjectPermissionActions.Read, ProjectPermissionActions.Create], ProjectPermissionSub.SecretRollback);
can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Member); can([ProjectPermissionActions.Read, ProjectPermissionActions.Create], ProjectPermissionSub.Member);
can(ProjectPermissionActions.Create, ProjectPermissionSub.Member);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Groups); can([ProjectPermissionActions.Read], ProjectPermissionSub.Groups);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); can(
can(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations); [
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations); ProjectPermissionActions.Read,
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations); ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.Integrations
);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks); can(
can(ProjectPermissionActions.Create, ProjectPermissionSub.Webhooks); [
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Webhooks); ProjectPermissionActions.Read,
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Webhooks); ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.Webhooks
);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Identity); can(
can(ProjectPermissionActions.Create, ProjectPermissionSub.Identity); [
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity); ProjectPermissionActions.Read,
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Identity); ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.Identity
);
can(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens); can(
can(ProjectPermissionActions.Create, ProjectPermissionSub.ServiceTokens); [
can(ProjectPermissionActions.Edit, ProjectPermissionSub.ServiceTokens); ProjectPermissionActions.Read,
can(ProjectPermissionActions.Delete, ProjectPermissionSub.ServiceTokens); ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.ServiceTokens
);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Settings); can(
can(ProjectPermissionActions.Create, ProjectPermissionSub.Settings); [
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings); ProjectPermissionActions.Read,
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Settings); ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.Settings
);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Environments); can(
can(ProjectPermissionActions.Create, ProjectPermissionSub.Environments); [
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Environments); ProjectPermissionActions.Read,
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Environments); ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.Environments
);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags); can(
can(ProjectPermissionActions.Create, ProjectPermissionSub.Tags); [
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Tags); ProjectPermissionActions.Read,
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Tags); ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.Tags
);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role); can([ProjectPermissionActions.Read], ProjectPermissionSub.Role);
can(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs); can([ProjectPermissionActions.Read], ProjectPermissionSub.AuditLogs);
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList); can([ProjectPermissionActions.Read], ProjectPermissionSub.IpAllowList);
// double check if all CRUD are needed for CA and Certificates // 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(
can(ProjectPermissionActions.Create, ProjectPermissionSub.Certificates); [
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Certificates); ProjectPermissionActions.Read,
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Certificates); 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.PkiAlerts);
can(ProjectPermissionActions.Read, ProjectPermissionSub.PkiCollections); can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiCollections);
return rules; return rules;
}; };
@@ -382,32 +594,4 @@ export const isAtLeastAsPrivilegedWorkspace = (
return set1.size >= set2.size; 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 */ /* eslint-enable */
@@ -7,8 +7,7 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSet, ProjectPermissionSet,
ProjectPermissionSub, ProjectPermissionSub
validateProjectPermissions
} from "@app/ee/services/permission/project-permission"; } from "@app/ee/services/permission/project-permission";
import { BadRequestError, NotFoundError } from "@app/lib/errors"; 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" }); throw new BadRequestError({ name: "Create Role", message: "Project role with same slug already exists" });
} }
validateProjectPermissions(data.permissions);
const role = await projectRoleDAL.create({ const role = await projectRoleDAL.create({
...data, ...data,
projectId projectId
@@ -127,10 +124,6 @@ export const projectRoleServiceFactory = ({
throw new BadRequestError({ name: "Update Role", message: "Project role with the same slug already exists" }); 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( const [updatedRole] = await projectRoleDAL.update(
{ id: roleId, projectId }, { id: roleId, projectId },
{ {
@@ -217,7 +217,7 @@ export const formRolePermission2API = (formVal: TFormSchema["permissions"]) => {
Object.entries(formVal || {}).forEach(([subject, rules]) => { Object.entries(formVal || {}).forEach(([subject, rules]) => {
rules.forEach((actions) => { rules.forEach((actions) => {
const caslActions = Object.keys(actions).filter( const caslActions = Object.keys(actions).filter(
(el) => actions?.[el as keyof typeof actions] (el) => actions?.[el as keyof typeof actions] && el !== "conditions"
); );
const caslConditions = const caslConditions =
"conditions" in actions "conditions" in actions
@@ -226,7 +226,7 @@ export const formRolePermission2API = (formVal: TFormSchema["permissions"]) => {
permissions.push({ permissions.push({
action: caslActions, action: caslActions,
subject: [subject], subject,
conditions: caslConditions conditions: caslConditions
}); });
}); });