mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-16 08:14:06 +00:00
272 lines
8.6 KiB
TypeScript
272 lines
8.6 KiB
TypeScript
import { ForbiddenError, subject } from "@casl/ability";
|
|
import picomatch from "picomatch";
|
|
|
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
|
import { BadRequestError } from "@app/lib/errors";
|
|
import { removeTrailingSlash } from "@app/lib/fn";
|
|
import { containsGlobPatterns } from "@app/lib/picomatch";
|
|
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
|
|
|
import { TLicenseServiceFactory } from "../license/license-service";
|
|
import { TSecretApprovalPolicyApproverDALFactory } from "./secret-approval-policy-approver-dal";
|
|
import { TSecretApprovalPolicyDALFactory } from "./secret-approval-policy-dal";
|
|
import {
|
|
TCreateSapDTO,
|
|
TDeleteSapDTO,
|
|
TGetBoardSapDTO,
|
|
TListSapDTO,
|
|
TUpdateSapDTO
|
|
} from "./secret-approval-policy-types";
|
|
|
|
const getPolicyScore = (policy: { secretPath?: string | null }) =>
|
|
// if glob pattern score is 1, if not exist score is 0 and if its not both then its exact path meaning score 2
|
|
// eslint-disable-next-line
|
|
policy.secretPath ? (containsGlobPatterns(policy.secretPath) ? 1 : 2) : 0;
|
|
|
|
type TSecretApprovalPolicyServiceFactoryDep = {
|
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
|
secretApprovalPolicyDAL: TSecretApprovalPolicyDALFactory;
|
|
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
|
secretApprovalPolicyApproverDAL: TSecretApprovalPolicyApproverDALFactory;
|
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
|
};
|
|
|
|
export type TSecretApprovalPolicyServiceFactory = ReturnType<typeof secretApprovalPolicyServiceFactory>;
|
|
|
|
export const secretApprovalPolicyServiceFactory = ({
|
|
secretApprovalPolicyDAL,
|
|
permissionService,
|
|
secretApprovalPolicyApproverDAL,
|
|
projectEnvDAL,
|
|
licenseService
|
|
}: TSecretApprovalPolicyServiceFactoryDep) => {
|
|
const createSecretApprovalPolicy = async ({
|
|
name,
|
|
actor,
|
|
actorId,
|
|
actorOrgId,
|
|
actorAuthMethod,
|
|
approvals,
|
|
approvers,
|
|
projectId,
|
|
secretPath,
|
|
environment,
|
|
enforcementLevel
|
|
}: TCreateSapDTO) => {
|
|
if (approvals > approvers.length)
|
|
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
|
|
|
const { permission } = await permissionService.getProjectPermission(
|
|
actor,
|
|
actorId,
|
|
projectId,
|
|
actorAuthMethod,
|
|
actorOrgId
|
|
);
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
ProjectPermissionActions.Create,
|
|
ProjectPermissionSub.SecretApproval
|
|
);
|
|
|
|
const plan = await licenseService.getPlan(actorOrgId);
|
|
if (!plan.secretApproval) {
|
|
throw new BadRequestError({
|
|
message:
|
|
"Failed to create secret approval policy due to plan restriction. Upgrade plan to create secret approval policy."
|
|
});
|
|
}
|
|
|
|
const env = await projectEnvDAL.findOne({ slug: environment, projectId });
|
|
if (!env) throw new BadRequestError({ message: "Environment not found" });
|
|
|
|
const secretApproval = await secretApprovalPolicyDAL.transaction(async (tx) => {
|
|
const doc = await secretApprovalPolicyDAL.create(
|
|
{
|
|
envId: env.id,
|
|
approvals,
|
|
secretPath,
|
|
name,
|
|
enforcementLevel
|
|
},
|
|
tx
|
|
);
|
|
await secretApprovalPolicyApproverDAL.insertMany(
|
|
approvers.map((approverUserId) => ({
|
|
approverUserId,
|
|
policyId: doc.id
|
|
})),
|
|
tx
|
|
);
|
|
return doc;
|
|
});
|
|
return { ...secretApproval, environment: env, projectId };
|
|
};
|
|
|
|
const updateSecretApprovalPolicy = async ({
|
|
approvers,
|
|
secretPath,
|
|
name,
|
|
actorId,
|
|
actor,
|
|
actorOrgId,
|
|
actorAuthMethod,
|
|
approvals,
|
|
secretPolicyId,
|
|
enforcementLevel
|
|
}: TUpdateSapDTO) => {
|
|
const secretApprovalPolicy = await secretApprovalPolicyDAL.findById(secretPolicyId);
|
|
if (!secretApprovalPolicy) throw new BadRequestError({ message: "Secret approval policy not found" });
|
|
|
|
const { permission } = await permissionService.getProjectPermission(
|
|
actor,
|
|
actorId,
|
|
secretApprovalPolicy.projectId,
|
|
actorAuthMethod,
|
|
actorOrgId
|
|
);
|
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
|
|
|
const plan = await licenseService.getPlan(actorOrgId);
|
|
if (!plan.secretApproval) {
|
|
throw new BadRequestError({
|
|
message:
|
|
"Failed to update secret approval policy due to plan restriction. Upgrade plan to update secret approval policy."
|
|
});
|
|
}
|
|
|
|
const updatedSap = await secretApprovalPolicyDAL.transaction(async (tx) => {
|
|
const doc = await secretApprovalPolicyDAL.updateById(
|
|
secretApprovalPolicy.id,
|
|
{
|
|
approvals,
|
|
secretPath,
|
|
name,
|
|
enforcementLevel
|
|
},
|
|
tx
|
|
);
|
|
if (approvers) {
|
|
await secretApprovalPolicyApproverDAL.delete({ policyId: doc.id }, tx);
|
|
await secretApprovalPolicyApproverDAL.insertMany(
|
|
approvers.map((approverUserId) => ({
|
|
approverUserId,
|
|
policyId: doc.id
|
|
})),
|
|
tx
|
|
);
|
|
}
|
|
return doc;
|
|
});
|
|
return {
|
|
...updatedSap,
|
|
environment: secretApprovalPolicy.environment,
|
|
projectId: secretApprovalPolicy.projectId
|
|
};
|
|
};
|
|
|
|
const deleteSecretApprovalPolicy = async ({
|
|
secretPolicyId,
|
|
actor,
|
|
actorId,
|
|
actorAuthMethod,
|
|
actorOrgId
|
|
}: TDeleteSapDTO) => {
|
|
const sapPolicy = await secretApprovalPolicyDAL.findById(secretPolicyId);
|
|
if (!sapPolicy) throw new BadRequestError({ message: "Secret approval policy not found" });
|
|
|
|
const { permission } = await permissionService.getProjectPermission(
|
|
actor,
|
|
actorId,
|
|
sapPolicy.projectId,
|
|
actorAuthMethod,
|
|
actorOrgId
|
|
);
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
ProjectPermissionActions.Delete,
|
|
ProjectPermissionSub.SecretApproval
|
|
);
|
|
|
|
const plan = await licenseService.getPlan(actorOrgId);
|
|
if (!plan.secretApproval) {
|
|
throw new BadRequestError({
|
|
message:
|
|
"Failed to update secret approval policy due to plan restriction. Upgrade plan to update secret approval policy."
|
|
});
|
|
}
|
|
|
|
await secretApprovalPolicyDAL.deleteById(secretPolicyId);
|
|
return sapPolicy;
|
|
};
|
|
|
|
const getSecretApprovalPolicyByProjectId = async ({
|
|
actorId,
|
|
actor,
|
|
actorOrgId,
|
|
actorAuthMethod,
|
|
projectId
|
|
}: TListSapDTO) => {
|
|
const { permission } = await permissionService.getProjectPermission(
|
|
actor,
|
|
actorId,
|
|
projectId,
|
|
actorAuthMethod,
|
|
actorOrgId
|
|
);
|
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
|
|
|
const sapPolicies = await secretApprovalPolicyDAL.find({ projectId });
|
|
return sapPolicies;
|
|
};
|
|
|
|
const getSecretApprovalPolicy = async (projectId: string, environment: string, path: string) => {
|
|
const secretPath = removeTrailingSlash(path);
|
|
const env = await projectEnvDAL.findOne({ slug: environment, projectId });
|
|
if (!env) throw new BadRequestError({ message: "Environment not found" });
|
|
|
|
const policies = await secretApprovalPolicyDAL.find({ envId: env.id });
|
|
if (!policies.length) return;
|
|
// this will filter policies either without scoped to secret path or the one that matches with secret path
|
|
const policiesFilteredByPath = policies.filter(
|
|
({ secretPath: policyPath }) => !policyPath || picomatch.isMatch(secretPath, policyPath, { strictSlashes: false })
|
|
);
|
|
// now sort by priority. exact secret path gets first match followed by glob followed by just env scoped
|
|
// if that is tie get by first createdAt
|
|
const policiesByPriority = policiesFilteredByPath.sort((a, b) => getPolicyScore(b) - getPolicyScore(a));
|
|
const finalPolicy = policiesByPriority.shift();
|
|
return finalPolicy;
|
|
};
|
|
|
|
const getSecretApprovalPolicyOfFolder = async ({
|
|
projectId,
|
|
actor,
|
|
actorId,
|
|
actorOrgId,
|
|
actorAuthMethod,
|
|
environment,
|
|
secretPath
|
|
}: TGetBoardSapDTO) => {
|
|
const { permission } = await permissionService.getProjectPermission(
|
|
actor,
|
|
actorId,
|
|
projectId,
|
|
actorAuthMethod,
|
|
actorOrgId
|
|
);
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
ProjectPermissionActions.Read,
|
|
subject(ProjectPermissionSub.Secrets, { secretPath, environment })
|
|
);
|
|
return getSecretApprovalPolicy(projectId, environment, secretPath);
|
|
};
|
|
|
|
return {
|
|
createSecretApprovalPolicy,
|
|
updateSecretApprovalPolicy,
|
|
deleteSecretApprovalPolicy,
|
|
getSecretApprovalPolicy,
|
|
getSecretApprovalPolicyByProjectId,
|
|
getSecretApprovalPolicyOfFolder
|
|
};
|
|
};
|