mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 19:28:09 +00:00
approval request permissions
This commit is contained in:
@@ -3,6 +3,7 @@ import { AbilityBuilder, createMongoAbility, MongoAbility } from "@casl/ability"
|
|||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
ProjectPermissionAppConnectionActions,
|
ProjectPermissionAppConnectionActions,
|
||||||
|
ProjectPermissionApprovalRequestActions,
|
||||||
ProjectPermissionAuditLogsActions,
|
ProjectPermissionAuditLogsActions,
|
||||||
ProjectPermissionCertificateActions,
|
ProjectPermissionCertificateActions,
|
||||||
ProjectPermissionCertificateAuthorityActions,
|
ProjectPermissionCertificateAuthorityActions,
|
||||||
@@ -339,6 +340,11 @@ const buildAdminPermissionRules = () => {
|
|||||||
|
|
||||||
can([ProjectPermissionPamSessionActions.Read], ProjectPermissionSub.PamSessions);
|
can([ProjectPermissionPamSessionActions.Read], ProjectPermissionSub.PamSessions);
|
||||||
|
|
||||||
|
can(
|
||||||
|
[ProjectPermissionApprovalRequestActions.Read, ProjectPermissionApprovalRequestActions.Create],
|
||||||
|
ProjectPermissionSub.ApprovalRequests
|
||||||
|
);
|
||||||
|
|
||||||
return rules;
|
return rules;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -586,6 +592,8 @@ const buildMemberPermissionRules = () => {
|
|||||||
ProjectPermissionSub.PamAccounts
|
ProjectPermissionSub.PamAccounts
|
||||||
);
|
);
|
||||||
|
|
||||||
|
can([ProjectPermissionApprovalRequestActions.Create], ProjectPermissionSub.ApprovalRequests);
|
||||||
|
|
||||||
return rules;
|
return rules;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -224,6 +224,11 @@ export enum ProjectPermissionPamSessionActions {
|
|||||||
// Terminate = "terminate"
|
// Terminate = "terminate"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ProjectPermissionApprovalRequestActions {
|
||||||
|
Read = "read",
|
||||||
|
Create = "create"
|
||||||
|
}
|
||||||
|
|
||||||
export const isCustomProjectRole = (slug: string) =>
|
export const isCustomProjectRole = (slug: string) =>
|
||||||
!Object.values(ProjectMembershipRole).includes(slug as ProjectMembershipRole);
|
!Object.values(ProjectMembershipRole).includes(slug as ProjectMembershipRole);
|
||||||
|
|
||||||
@@ -274,7 +279,8 @@ export enum ProjectPermissionSub {
|
|||||||
PamResources = "pam-resources",
|
PamResources = "pam-resources",
|
||||||
PamAccounts = "pam-accounts",
|
PamAccounts = "pam-accounts",
|
||||||
PamSessions = "pam-sessions",
|
PamSessions = "pam-sessions",
|
||||||
CertificateProfiles = "certificate-profiles"
|
CertificateProfiles = "certificate-profiles",
|
||||||
|
ApprovalRequests = "approval-requests"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SecretSubjectFields = {
|
export type SecretSubjectFields = {
|
||||||
@@ -500,7 +506,8 @@ export type ProjectPermissionSet =
|
|||||||
| ProjectPermissionSub.CertificateProfiles
|
| ProjectPermissionSub.CertificateProfiles
|
||||||
| (ForcedSubject<ProjectPermissionSub.CertificateProfiles> & CertificateProfileSubjectFields)
|
| (ForcedSubject<ProjectPermissionSub.CertificateProfiles> & CertificateProfileSubjectFields)
|
||||||
)
|
)
|
||||||
];
|
]
|
||||||
|
| [ProjectPermissionApprovalRequestActions, ProjectPermissionSub.ApprovalRequests];
|
||||||
|
|
||||||
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([
|
||||||
@@ -1105,6 +1112,12 @@ const GeneralPermissionSchema = [
|
|||||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionPamSessionActions).describe(
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionPamSessionActions).describe(
|
||||||
"Describe what action an entity can take."
|
"Describe what action an entity can take."
|
||||||
)
|
)
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(ProjectPermissionSub.ApprovalRequests).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionApprovalRequestActions).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
|
)
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,12 @@ import {
|
|||||||
TCreateRequestDTO,
|
TCreateRequestDTO,
|
||||||
TUpdatePolicyDTO
|
TUpdatePolicyDTO
|
||||||
} from "./approval-policy-types";
|
} from "./approval-policy-types";
|
||||||
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import {
|
||||||
|
ProjectPermissionActions,
|
||||||
|
ProjectPermissionApprovalRequestActions,
|
||||||
|
ProjectPermissionSub
|
||||||
|
} from "@app/ee/services/permission/project-permission";
|
||||||
|
|
||||||
type TApprovalPolicyServiceFactoryDep = {
|
type TApprovalPolicyServiceFactoryDep = {
|
||||||
approvalPolicyDAL: TApprovalPolicyDALFactory;
|
approvalPolicyDAL: TApprovalPolicyDALFactory;
|
||||||
@@ -367,7 +373,19 @@ export const approvalPolicyServiceFactory = ({
|
|||||||
},
|
},
|
||||||
actor: OrgServiceActor
|
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(
|
||||||
|
ProjectPermissionApprovalRequestActions.Create,
|
||||||
|
ProjectPermissionSub.ApprovalRequests
|
||||||
|
);
|
||||||
|
|
||||||
const fac = APPROVAL_POLICY_FACTORY_MAP[policyType](policyType);
|
const fac = APPROVAL_POLICY_FACTORY_MAP[policyType](policyType);
|
||||||
|
|
||||||
@@ -466,9 +484,7 @@ export const approvalPolicyServiceFactory = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getRequestById = async (requestId: string, _actor: OrgServiceActor) => {
|
const getRequestById = async (requestId: string, actor: OrgServiceActor) => {
|
||||||
// TODO(andrey): Perm check
|
|
||||||
|
|
||||||
const request = await approvalRequestDAL.findById(requestId);
|
const request = await approvalRequestDAL.findById(requestId);
|
||||||
if (!request) {
|
if (!request) {
|
||||||
throw new ForbiddenRequestError({ message: "Request not found" });
|
throw new ForbiddenRequestError({ message: "Request not found" });
|
||||||
@@ -476,6 +492,38 @@ export const approvalPolicyServiceFactory = ({
|
|||||||
|
|
||||||
const steps = await approvalRequestDAL.findStepsByRequestId(requestId);
|
const steps = await approvalRequestDAL.findStepsByRequestId(requestId);
|
||||||
|
|
||||||
|
const isRequester = request.requesterId === actor.id;
|
||||||
|
|
||||||
|
// Check if user is an eligible approver for any step
|
||||||
|
const userGroups = await userGroupMembershipDAL.findGroupMembershipsByUserIdInOrg(actor.id, actor.orgId);
|
||||||
|
const userGroupIds = new Set(userGroups.map((g) => g.groupId));
|
||||||
|
|
||||||
|
const isApprover = steps.some((step) =>
|
||||||
|
step.approvers.some(
|
||||||
|
(approver) =>
|
||||||
|
(approver.type === ApproverType.User && approver.id === actor.id) ||
|
||||||
|
(approver.type === ApproverType.Group && userGroupIds.has(approver.id))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// If user is requester or approver, allow access regardless of role permission
|
||||||
|
if (!isRequester && !isApprover) {
|
||||||
|
// Otherwise, check role permission
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor: actor.type,
|
||||||
|
actorAuthMethod: actor.authMethod,
|
||||||
|
actorId: actor.id,
|
||||||
|
actorOrgId: actor.orgId,
|
||||||
|
projectId: request.projectId,
|
||||||
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionApprovalRequestActions.Read,
|
||||||
|
ProjectPermissionSub.ApprovalRequests
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
request: { ...request, steps }
|
request: { ...request, steps }
|
||||||
};
|
};
|
||||||
@@ -670,11 +718,55 @@ export const approvalPolicyServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const listRequests = async (policyType: ApprovalPolicyType, projectId: string, actor: OrgServiceActor) => {
|
const listRequests = 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
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasReadPermission = permission.can(
|
||||||
|
ProjectPermissionApprovalRequestActions.Read,
|
||||||
|
ProjectPermissionSub.ApprovalRequests
|
||||||
|
);
|
||||||
|
|
||||||
const requests = await approvalRequestDAL.findByProjectId(policyType, projectId);
|
const requests = await approvalRequestDAL.findByProjectId(policyType, projectId);
|
||||||
|
|
||||||
return { requests };
|
// If user has read permission, return all requests
|
||||||
|
if (hasReadPermission) {
|
||||||
|
return { requests };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, filter to only requests where user is requester or approver
|
||||||
|
const userGroups = await userGroupMembershipDAL.findGroupMembershipsByUserIdInOrg(actor.id, actor.orgId);
|
||||||
|
const userGroupIds = new Set(userGroups.map((g) => g.groupId));
|
||||||
|
|
||||||
|
const filteredRequests = [];
|
||||||
|
for (const request of requests) {
|
||||||
|
const isRequester = request.requesterId === actor.id;
|
||||||
|
|
||||||
|
if (isRequester) {
|
||||||
|
filteredRequests.push(request);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user is an eligible approver for any step
|
||||||
|
const isApprover = request.steps.some((step) =>
|
||||||
|
step.approvers.some(
|
||||||
|
(approver) =>
|
||||||
|
(approver.type === ApproverType.User && approver.id === actor.id) ||
|
||||||
|
(approver.type === ApproverType.Group && userGroupIds.has(approver.id))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isApprover) {
|
||||||
|
filteredRequests.push(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { requests: filteredRequests };
|
||||||
};
|
};
|
||||||
|
|
||||||
const cancelRequest = async (requestId: string, actor: OrgServiceActor) => {
|
const cancelRequest = async (requestId: string, actor: OrgServiceActor) => {
|
||||||
|
|||||||
@@ -228,6 +228,11 @@ export enum ProjectPermissionPamSessionActions {
|
|||||||
// Terminate = "terminate"
|
// Terminate = "terminate"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ProjectPermissionApprovalRequestActions {
|
||||||
|
Read = "read",
|
||||||
|
Create = "create"
|
||||||
|
}
|
||||||
|
|
||||||
export type IdentityManagementSubjectFields = {
|
export type IdentityManagementSubjectFields = {
|
||||||
identityId: string;
|
identityId: string;
|
||||||
};
|
};
|
||||||
@@ -339,7 +344,8 @@ export enum ProjectPermissionSub {
|
|||||||
PamFolders = "pam-folders",
|
PamFolders = "pam-folders",
|
||||||
PamResources = "pam-resources",
|
PamResources = "pam-resources",
|
||||||
PamAccounts = "pam-accounts",
|
PamAccounts = "pam-accounts",
|
||||||
PamSessions = "pam-sessions"
|
PamSessions = "pam-sessions",
|
||||||
|
ApprovalRequests = "approval-requests"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SecretSubjectFields = {
|
export type SecretSubjectFields = {
|
||||||
@@ -577,6 +583,7 @@ export type ProjectPermissionSet =
|
|||||||
| (ForcedSubject<ProjectPermissionSub.PamAccounts> & PamAccountSubjectFields)
|
| (ForcedSubject<ProjectPermissionSub.PamAccounts> & PamAccountSubjectFields)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
| [ProjectPermissionPamSessionActions, ProjectPermissionSub.PamSessions];
|
| [ProjectPermissionPamSessionActions, ProjectPermissionSub.PamSessions]
|
||||||
|
| [ProjectPermissionApprovalRequestActions, ProjectPermissionSub.ApprovalRequests];
|
||||||
|
|
||||||
export type TProjectPermission = MongoAbility<ProjectPermissionSet>;
|
export type TProjectPermission = MongoAbility<ProjectPermissionSet>;
|
||||||
|
|||||||
+27
-2
@@ -15,6 +15,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
PermissionConditionOperators,
|
PermissionConditionOperators,
|
||||||
ProjectPermissionAppConnectionActions,
|
ProjectPermissionAppConnectionActions,
|
||||||
|
ProjectPermissionApprovalRequestActions,
|
||||||
ProjectPermissionAuditLogsActions,
|
ProjectPermissionAuditLogsActions,
|
||||||
ProjectPermissionCommitsActions,
|
ProjectPermissionCommitsActions,
|
||||||
ProjectPermissionDynamicSecretActions,
|
ProjectPermissionDynamicSecretActions,
|
||||||
@@ -258,6 +259,11 @@ const PamSessionPolicyActionSchema = z.object({
|
|||||||
[ProjectPermissionPamSessionActions.Read]: z.boolean().optional()
|
[ProjectPermissionPamSessionActions.Read]: z.boolean().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const ApprovalRequestPolicyActionSchema = z.object({
|
||||||
|
[ProjectPermissionApprovalRequestActions.Read]: z.boolean().optional(),
|
||||||
|
[ProjectPermissionApprovalRequestActions.Create]: 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()
|
||||||
@@ -468,7 +474,8 @@ export const projectRoleFormSchema = z.object({
|
|||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
.default([]),
|
.default([]),
|
||||||
[ProjectPermissionSub.PamSessions]: PamSessionPolicyActionSchema.array().default([])
|
[ProjectPermissionSub.PamSessions]: PamSessionPolicyActionSchema.array().default([]),
|
||||||
|
[ProjectPermissionSub.ApprovalRequests]: ApprovalRequestPolicyActionSchema.array().default([])
|
||||||
})
|
})
|
||||||
.partial()
|
.partial()
|
||||||
.optional()
|
.optional()
|
||||||
@@ -1289,6 +1296,16 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
|
|||||||
// Map actions to the keys defined in ApprovalPolicyActionSchema
|
// Map actions to the keys defined in ApprovalPolicyActionSchema
|
||||||
if (canRead) formVal[subject]![0][ProjectPermissionPamAccountActions.Read] = true;
|
if (canRead) formVal[subject]![0][ProjectPermissionPamAccountActions.Read] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (subject === ProjectPermissionSub.ApprovalRequests) {
|
||||||
|
const canRead = action.includes(ProjectPermissionApprovalRequestActions.Read);
|
||||||
|
const canCreate = action.includes(ProjectPermissionApprovalRequestActions.Create);
|
||||||
|
|
||||||
|
if (!formVal[subject]) formVal[subject] = [{}];
|
||||||
|
|
||||||
|
if (canRead) formVal[subject]![0][ProjectPermissionApprovalRequestActions.Read] = true;
|
||||||
|
if (canCreate) formVal[subject]![0][ProjectPermissionApprovalRequestActions.Create] = true;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return formVal;
|
return formVal;
|
||||||
@@ -1958,6 +1975,13 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
|
|||||||
[ProjectPermissionSub.PamSessions]: {
|
[ProjectPermissionSub.PamSessions]: {
|
||||||
title: "Sessions",
|
title: "Sessions",
|
||||||
actions: [{ label: "Read", value: ProjectPermissionPamSessionActions.Read }]
|
actions: [{ label: "Read", value: ProjectPermissionPamSessionActions.Read }]
|
||||||
|
},
|
||||||
|
[ProjectPermissionSub.ApprovalRequests]: {
|
||||||
|
title: "Approval Requests",
|
||||||
|
actions: [
|
||||||
|
{ label: "Read", value: ProjectPermissionApprovalRequestActions.Read },
|
||||||
|
{ label: "Create", value: ProjectPermissionApprovalRequestActions.Create }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1968,7 +1992,8 @@ const SharedPermissionSubjects = {
|
|||||||
[ProjectPermissionSub.Identity]: true,
|
[ProjectPermissionSub.Identity]: true,
|
||||||
[ProjectPermissionSub.Project]: true,
|
[ProjectPermissionSub.Project]: true,
|
||||||
[ProjectPermissionSub.Role]: true,
|
[ProjectPermissionSub.Role]: true,
|
||||||
[ProjectPermissionSub.Settings]: true
|
[ProjectPermissionSub.Settings]: true,
|
||||||
|
[ProjectPermissionSub.ApprovalRequests]: true
|
||||||
};
|
};
|
||||||
|
|
||||||
const SecretsManagerPermissionSubjects = (enabled = false) => ({
|
const SecretsManagerPermissionSubjects = (enabled = false) => ({
|
||||||
|
|||||||
Reference in New Issue
Block a user