grant permissions

This commit is contained in:
x032205
2025-12-07 22:26:41 -05:00
parent 2fe316c81c
commit bfb682b715
5 changed files with 104 additions and 12 deletions
@@ -4,6 +4,7 @@ import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionAppConnectionActions, ProjectPermissionAppConnectionActions,
ProjectPermissionApprovalRequestActions, ProjectPermissionApprovalRequestActions,
ProjectPermissionApprovalRequestGrantActions,
ProjectPermissionAuditLogsActions, ProjectPermissionAuditLogsActions,
ProjectPermissionCertificateActions, ProjectPermissionCertificateActions,
ProjectPermissionCertificateAuthorityActions, ProjectPermissionCertificateAuthorityActions,
@@ -345,6 +346,11 @@ const buildAdminPermissionRules = () => {
ProjectPermissionSub.ApprovalRequests ProjectPermissionSub.ApprovalRequests
); );
can(
[ProjectPermissionApprovalRequestGrantActions.Read, ProjectPermissionApprovalRequestGrantActions.Revoke],
ProjectPermissionSub.ApprovalRequestGrants
);
return rules; return rules;
}; };
@@ -229,6 +229,11 @@ export enum ProjectPermissionApprovalRequestActions {
Create = "create" Create = "create"
} }
export enum ProjectPermissionApprovalRequestGrantActions {
Read = "read",
Revoke = "revoke"
}
export const isCustomProjectRole = (slug: string) => export const isCustomProjectRole = (slug: string) =>
!Object.values(ProjectMembershipRole).includes(slug as ProjectMembershipRole); !Object.values(ProjectMembershipRole).includes(slug as ProjectMembershipRole);
@@ -280,7 +285,8 @@ export enum ProjectPermissionSub {
PamAccounts = "pam-accounts", PamAccounts = "pam-accounts",
PamSessions = "pam-sessions", PamSessions = "pam-sessions",
CertificateProfiles = "certificate-profiles", CertificateProfiles = "certificate-profiles",
ApprovalRequests = "approval-requests" ApprovalRequests = "approval-requests",
ApprovalRequestGrants = "approval-request-grants"
} }
export type SecretSubjectFields = { export type SecretSubjectFields = {
@@ -507,7 +513,8 @@ export type ProjectPermissionSet =
| (ForcedSubject<ProjectPermissionSub.CertificateProfiles> & CertificateProfileSubjectFields) | (ForcedSubject<ProjectPermissionSub.CertificateProfiles> & CertificateProfileSubjectFields)
) )
] ]
| [ProjectPermissionApprovalRequestActions, ProjectPermissionSub.ApprovalRequests]; | [ProjectPermissionApprovalRequestActions, ProjectPermissionSub.ApprovalRequests]
| [ProjectPermissionApprovalRequestGrantActions, ProjectPermissionSub.ApprovalRequestGrants];
const SECRET_PATH_MISSING_SLASH_ERR_MSG = "Invalid Secret Path; it must start with a '/'"; const SECRET_PATH_MISSING_SLASH_ERR_MSG = "Invalid Secret Path; it must start with a '/'";
const SECRET_PATH_PERMISSION_OPERATOR_SCHEMA = z.union([ const SECRET_PATH_PERMISSION_OPERATOR_SCHEMA = z.union([
@@ -1118,6 +1125,12 @@ const GeneralPermissionSchema = [
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionApprovalRequestActions).describe( action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionApprovalRequestActions).describe(
"Describe what action an entity can take." "Describe what action an entity can take."
) )
}),
z.object({
subject: z.literal(ProjectPermissionSub.ApprovalRequestGrants).describe("The entity this permission pertains to."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionApprovalRequestGrantActions).describe(
"Describe what action an entity can take."
)
}) })
]; ];
@@ -38,6 +38,7 @@ import { ForbiddenError } from "@casl/ability";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionApprovalRequestActions, ProjectPermissionApprovalRequestActions,
ProjectPermissionApprovalRequestGrantActions,
ProjectPermissionSub ProjectPermissionSub
} from "@app/ee/services/permission/project-permission"; } from "@app/ee/services/permission/project-permission";
@@ -87,7 +88,7 @@ export const approvalPolicyServiceFactory = ({
if (userIdsToNotify.size === 0) return; if (userIdsToNotify.size === 0) return;
// TODO: Potentially link to requests in the future? // TODO: Potentially link to requests in the future to support click redirects
await notificationService.createUserNotifications( await notificationService.createUserNotifications(
Array.from(userIdsToNotify).map((userId) => ({ Array.from(userIdsToNotify).map((userId) => ({
userId, userId,
@@ -793,20 +794,44 @@ export const approvalPolicyServiceFactory = ({
}; };
const listGrants = async (policyType: ApprovalPolicyType, projectId: string, actor: OrgServiceActor) => { const listGrants = async (policyType: ApprovalPolicyType, projectId: string, actor: OrgServiceActor) => {
// TODO(andrey): Perm check const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorAuthMethod: actor.authMethod,
actorId: actor.id,
actorOrgId: actor.orgId,
projectId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionApprovalRequestGrantActions.Read,
ProjectPermissionSub.ApprovalRequestGrants
);
const grants = await approvalRequestGrantsDAL.find({ projectId, type: policyType }); const grants = await approvalRequestGrantsDAL.find({ projectId, type: policyType });
return { grants }; return { grants };
}; };
const getGrantById = async (grantId: string, actor: OrgServiceActor) => { const getGrantById = async (grantId: string, actor: OrgServiceActor) => {
// TODO(andrey): Perm check
const grant = await approvalRequestGrantsDAL.findById(grantId); const grant = await approvalRequestGrantsDAL.findById(grantId);
if (!grant) { if (!grant) {
throw new NotFoundError({ message: "Grant not found" }); throw new NotFoundError({ message: "Grant not found" });
} }
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorAuthMethod: actor.authMethod,
actorId: actor.id,
actorOrgId: actor.orgId,
projectId: grant.projectId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionApprovalRequestGrantActions.Read,
ProjectPermissionSub.ApprovalRequestGrants
);
return { grant }; return { grant };
}; };
@@ -815,13 +840,25 @@ export const approvalPolicyServiceFactory = ({
{ revocationReason }: { revocationReason?: string }, { revocationReason }: { revocationReason?: string },
actor: OrgServiceActor actor: OrgServiceActor
) => { ) => {
// TODO(andrey): Perm check
const grant = await approvalRequestGrantsDAL.findById(grantId); const grant = await approvalRequestGrantsDAL.findById(grantId);
if (!grant) { if (!grant) {
throw new NotFoundError({ message: "Grant not found" }); throw new NotFoundError({ message: "Grant not found" });
} }
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorAuthMethod: actor.authMethod,
actorId: actor.id,
actorOrgId: actor.orgId,
projectId: grant.projectId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionApprovalRequestGrantActions.Revoke,
ProjectPermissionSub.ApprovalRequestGrants
);
if (grant.status !== ApprovalRequestGrantStatus.Active) { if (grant.status !== ApprovalRequestGrantStatus.Active) {
throw new BadRequestError({ message: "Grant is not active" }); throw new BadRequestError({ message: "Grant is not active" });
} }
@@ -233,6 +233,11 @@ export enum ProjectPermissionApprovalRequestActions {
Create = "create" Create = "create"
} }
export enum ProjectPermissionApprovalRequestGrantActions {
Read = "read",
Revoke = "revoke"
}
export type IdentityManagementSubjectFields = { export type IdentityManagementSubjectFields = {
identityId: string; identityId: string;
}; };
@@ -345,7 +350,8 @@ export enum ProjectPermissionSub {
PamResources = "pam-resources", PamResources = "pam-resources",
PamAccounts = "pam-accounts", PamAccounts = "pam-accounts",
PamSessions = "pam-sessions", PamSessions = "pam-sessions",
ApprovalRequests = "approval-requests" ApprovalRequests = "approval-requests",
ApprovalRequestGrants = "approval-request-grants"
} }
export type SecretSubjectFields = { export type SecretSubjectFields = {
@@ -584,6 +590,7 @@ export type ProjectPermissionSet =
) )
] ]
| [ProjectPermissionPamSessionActions, ProjectPermissionSub.PamSessions] | [ProjectPermissionPamSessionActions, ProjectPermissionSub.PamSessions]
| [ProjectPermissionApprovalRequestActions, ProjectPermissionSub.ApprovalRequests]; | [ProjectPermissionApprovalRequestActions, ProjectPermissionSub.ApprovalRequests]
| [ProjectPermissionApprovalRequestGrantActions, ProjectPermissionSub.ApprovalRequestGrants];
export type TProjectPermission = MongoAbility<ProjectPermissionSet>; export type TProjectPermission = MongoAbility<ProjectPermissionSet>;
@@ -16,6 +16,7 @@ import {
PermissionConditionOperators, PermissionConditionOperators,
ProjectPermissionAppConnectionActions, ProjectPermissionAppConnectionActions,
ProjectPermissionApprovalRequestActions, ProjectPermissionApprovalRequestActions,
ProjectPermissionApprovalRequestGrantActions,
ProjectPermissionAuditLogsActions, ProjectPermissionAuditLogsActions,
ProjectPermissionCommitsActions, ProjectPermissionCommitsActions,
ProjectPermissionDynamicSecretActions, ProjectPermissionDynamicSecretActions,
@@ -264,6 +265,11 @@ const ApprovalRequestPolicyActionSchema = z.object({
[ProjectPermissionApprovalRequestActions.Create]: z.boolean().optional() [ProjectPermissionApprovalRequestActions.Create]: z.boolean().optional()
}); });
const ApprovalRequestGrantPolicyActionSchema = z.object({
[ProjectPermissionApprovalRequestGrantActions.Read]: z.boolean().optional(),
[ProjectPermissionApprovalRequestGrantActions.Revoke]: z.boolean().optional()
});
const SecretRollbackPolicyActionSchema = z.object({ const SecretRollbackPolicyActionSchema = z.object({
read: z.boolean().optional(), read: z.boolean().optional(),
create: z.boolean().optional() create: z.boolean().optional()
@@ -475,7 +481,11 @@ export const projectRoleFormSchema = z.object({
.array() .array()
.default([]), .default([]),
[ProjectPermissionSub.PamSessions]: PamSessionPolicyActionSchema.array().default([]), [ProjectPermissionSub.PamSessions]: PamSessionPolicyActionSchema.array().default([]),
[ProjectPermissionSub.ApprovalRequests]: ApprovalRequestPolicyActionSchema.array().default([]) [ProjectPermissionSub.ApprovalRequests]: ApprovalRequestPolicyActionSchema.array().default(
[]
),
[ProjectPermissionSub.ApprovalRequestGrants]:
ApprovalRequestGrantPolicyActionSchema.array().default([])
}) })
.partial() .partial()
.optional() .optional()
@@ -1306,6 +1316,17 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
if (canRead) formVal[subject]![0][ProjectPermissionApprovalRequestActions.Read] = true; if (canRead) formVal[subject]![0][ProjectPermissionApprovalRequestActions.Read] = true;
if (canCreate) formVal[subject]![0][ProjectPermissionApprovalRequestActions.Create] = true; if (canCreate) formVal[subject]![0][ProjectPermissionApprovalRequestActions.Create] = true;
} }
if (subject === ProjectPermissionSub.ApprovalRequestGrants) {
const canRead = action.includes(ProjectPermissionApprovalRequestGrantActions.Read);
const canRevoke = action.includes(ProjectPermissionApprovalRequestGrantActions.Revoke);
if (!formVal[subject]) formVal[subject] = [{}];
if (canRead) formVal[subject]![0][ProjectPermissionApprovalRequestGrantActions.Read] = true;
if (canRevoke)
formVal[subject]![0][ProjectPermissionApprovalRequestGrantActions.Revoke] = true;
}
}); });
return formVal; return formVal;
@@ -1982,6 +2003,13 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
{ label: "Read", value: ProjectPermissionApprovalRequestActions.Read }, { label: "Read", value: ProjectPermissionApprovalRequestActions.Read },
{ label: "Create", value: ProjectPermissionApprovalRequestActions.Create } { label: "Create", value: ProjectPermissionApprovalRequestActions.Create }
] ]
},
[ProjectPermissionSub.ApprovalRequestGrants]: {
title: "Approval Request Grants",
actions: [
{ label: "Read", value: ProjectPermissionApprovalRequestGrantActions.Read },
{ label: "Revoke", value: ProjectPermissionApprovalRequestGrantActions.Revoke }
]
} }
}; };
@@ -1993,7 +2021,8 @@ const SharedPermissionSubjects = {
[ProjectPermissionSub.Project]: true, [ProjectPermissionSub.Project]: true,
[ProjectPermissionSub.Role]: true, [ProjectPermissionSub.Role]: true,
[ProjectPermissionSub.Settings]: true, [ProjectPermissionSub.Settings]: true,
[ProjectPermissionSub.ApprovalRequests]: true [ProjectPermissionSub.ApprovalRequests]: true,
[ProjectPermissionSub.ApprovalRequestGrants]: true
}; };
const SecretsManagerPermissionSubjects = (enabled = false) => ({ const SecretsManagerPermissionSubjects = (enabled = false) => ({