mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-09 19:28:33 +00:00
approval request permissions
This commit is contained in:
@@ -3,6 +3,7 @@ import { AbilityBuilder, createMongoAbility, MongoAbility } from "@casl/ability"
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionAppConnectionActions,
|
||||
ProjectPermissionApprovalRequestActions,
|
||||
ProjectPermissionAuditLogsActions,
|
||||
ProjectPermissionCertificateActions,
|
||||
ProjectPermissionCertificateAuthorityActions,
|
||||
@@ -339,6 +340,11 @@ const buildAdminPermissionRules = () => {
|
||||
|
||||
can([ProjectPermissionPamSessionActions.Read], ProjectPermissionSub.PamSessions);
|
||||
|
||||
can(
|
||||
[ProjectPermissionApprovalRequestActions.Read, ProjectPermissionApprovalRequestActions.Create],
|
||||
ProjectPermissionSub.ApprovalRequests
|
||||
);
|
||||
|
||||
return rules;
|
||||
};
|
||||
|
||||
@@ -586,6 +592,8 @@ const buildMemberPermissionRules = () => {
|
||||
ProjectPermissionSub.PamAccounts
|
||||
);
|
||||
|
||||
can([ProjectPermissionApprovalRequestActions.Create], ProjectPermissionSub.ApprovalRequests);
|
||||
|
||||
return rules;
|
||||
};
|
||||
|
||||
|
||||
@@ -224,6 +224,11 @@ export enum ProjectPermissionPamSessionActions {
|
||||
// Terminate = "terminate"
|
||||
}
|
||||
|
||||
export enum ProjectPermissionApprovalRequestActions {
|
||||
Read = "read",
|
||||
Create = "create"
|
||||
}
|
||||
|
||||
export const isCustomProjectRole = (slug: string) =>
|
||||
!Object.values(ProjectMembershipRole).includes(slug as ProjectMembershipRole);
|
||||
|
||||
@@ -274,7 +279,8 @@ export enum ProjectPermissionSub {
|
||||
PamResources = "pam-resources",
|
||||
PamAccounts = "pam-accounts",
|
||||
PamSessions = "pam-sessions",
|
||||
CertificateProfiles = "certificate-profiles"
|
||||
CertificateProfiles = "certificate-profiles",
|
||||
ApprovalRequests = "approval-requests"
|
||||
}
|
||||
|
||||
export type SecretSubjectFields = {
|
||||
@@ -500,7 +506,8 @@ export type ProjectPermissionSet =
|
||||
| ProjectPermissionSub.CertificateProfiles
|
||||
| (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_PERMISSION_OPERATOR_SCHEMA = z.union([
|
||||
@@ -1105,6 +1112,12 @@ const GeneralPermissionSchema = [
|
||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionPamSessionActions).describe(
|
||||
"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,
|
||||
TUpdatePolicyDTO
|
||||
} from "./approval-policy-types";
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionApprovalRequestActions,
|
||||
ProjectPermissionSub
|
||||
} from "@app/ee/services/permission/project-permission";
|
||||
|
||||
type TApprovalPolicyServiceFactoryDep = {
|
||||
approvalPolicyDAL: TApprovalPolicyDALFactory;
|
||||
@@ -367,7 +373,19 @@ export const approvalPolicyServiceFactory = ({
|
||||
},
|
||||
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);
|
||||
|
||||
@@ -466,9 +484,7 @@ export const approvalPolicyServiceFactory = ({
|
||||
};
|
||||
};
|
||||
|
||||
const getRequestById = async (requestId: string, _actor: OrgServiceActor) => {
|
||||
// TODO(andrey): Perm check
|
||||
|
||||
const getRequestById = async (requestId: string, actor: OrgServiceActor) => {
|
||||
const request = await approvalRequestDAL.findById(requestId);
|
||||
if (!request) {
|
||||
throw new ForbiddenRequestError({ message: "Request not found" });
|
||||
@@ -476,6 +492,38 @@ export const approvalPolicyServiceFactory = ({
|
||||
|
||||
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 {
|
||||
request: { ...request, steps }
|
||||
};
|
||||
@@ -670,11 +718,55 @@ export const approvalPolicyServiceFactory = ({
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
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) => {
|
||||
|
||||
Reference in New Issue
Block a user