feat: added type for project and validation check for secret manager specific endpoints

This commit is contained in:
=
2024-12-13 21:38:42 +05:30
parent dc715cc238
commit 695c499448
23 changed files with 211 additions and 78 deletions
@@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { ProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
@@ -86,13 +87,15 @@ export const accessApprovalPolicyServiceFactory = ({
if (!groupApprovers && approvals > userApprovers.length + userApproverNames.length)
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
const { permission } = await permissionService.getProjectPermission(
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
actor,
actorId,
project.id,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SecretApproval
@@ -190,14 +193,7 @@ export const accessApprovalPolicyServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
// Anyone in the project should be able to get the policies.
/* const { permission } = */ await permissionService.getProjectPermission(
actor,
actorId,
project.id,
actorAuthMethod,
actorOrgId
);
// ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
await permissionService.getProjectPermission(actor, actorId, project.id, actorAuthMethod, actorOrgId);
const accessApprovalPolicies = await accessApprovalPolicyDAL.find({ projectId: project.id, deletedAt: null });
return accessApprovalPolicies;
@@ -241,13 +237,14 @@ export const accessApprovalPolicyServiceFactory = ({
if (!accessApprovalPolicy) {
throw new NotFoundError({ message: `Secret approval policy with ID '${policyId}' not found` });
}
const { permission } = await permissionService.getProjectPermission(
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
actor,
actorId,
accessApprovalPolicy.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
@@ -324,13 +321,14 @@ export const accessApprovalPolicyServiceFactory = ({
const policy = await accessApprovalPolicyDAL.findById(policyId);
if (!policy) throw new NotFoundError({ message: `Secret approval policy with ID '${policyId}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
actor,
actorId,
policy.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.SecretApproval
@@ -1,7 +1,7 @@
import { ForbiddenError, subject } from "@casl/ability";
import ms from "ms";
import { SecretKeyEncoding } from "@app/db/schemas";
import { ProjectType, SecretKeyEncoding } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import {
@@ -67,13 +67,14 @@ export const dynamicSecretLeaseServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const { permission } = await permissionService.getProjectPermission(
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
@@ -146,13 +147,14 @@ export const dynamicSecretLeaseServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const { permission } = await permissionService.getProjectPermission(
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
@@ -225,13 +227,14 @@ export const dynamicSecretLeaseServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const { permission } = await permissionService.getProjectPermission(
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
@@ -1,6 +1,6 @@
import { ForbiddenError, subject } from "@casl/ability";
import { SecretKeyEncoding } from "@app/db/schemas";
import { ProjectType, SecretKeyEncoding } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import {
@@ -73,13 +73,14 @@ export const dynamicSecretServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const { permission } = await permissionService.getProjectPermission(
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.CreateRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
@@ -144,13 +145,14 @@ export const dynamicSecretServiceFactory = ({
const projectId = project.id;
const { permission } = await permissionService.getProjectPermission(
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.EditRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
@@ -227,13 +229,14 @@ export const dynamicSecretServiceFactory = ({
const projectId = project.id;
const { permission } = await permissionService.getProjectPermission(
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.DeleteRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
@@ -269,6 +269,7 @@ export const permissionDALFactory = (db: TDbClient) => {
db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue"),
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
db.ref("orgId").withSchema(TableName.Project),
db.ref("type").withSchema(TableName.Project).as("projectType"),
db.ref("id").withSchema(TableName.Project).as("projectId")
);
@@ -284,13 +285,15 @@ export const permissionDALFactory = (db: TDbClient) => {
membershipCreatedAt,
groupMembershipCreatedAt,
groupMembershipUpdatedAt,
membershipUpdatedAt
membershipUpdatedAt,
projectType
}) => ({
orgId,
orgAuthEnforced,
userId,
projectId,
username,
projectType,
id: membershipId || groupMembershipId,
createdAt: membershipCreatedAt || groupMembershipCreatedAt,
updatedAt: membershipUpdatedAt || groupMembershipUpdatedAt
@@ -449,6 +452,7 @@ export const permissionDALFactory = (db: TDbClient) => {
db.ref("id").withSchema(TableName.IdentityProjectMembership).as("membershipId"),
db.ref("name").withSchema(TableName.Identity).as("identityName"),
db.ref("orgId").withSchema(TableName.Project).as("orgId"), // Now you can select orgId from Project
db.ref("type").withSchema(TableName.Project).as("projectType"),
db.ref("createdAt").withSchema(TableName.IdentityProjectMembership).as("membershipCreatedAt"),
db.ref("updatedAt").withSchema(TableName.IdentityProjectMembership).as("membershipUpdatedAt"),
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug"),
@@ -480,7 +484,14 @@ export const permissionDALFactory = (db: TDbClient) => {
const permission = sqlNestRelationships({
data: docs,
key: "membershipId",
parentMapper: ({ membershipId, membershipCreatedAt, membershipUpdatedAt, orgId, identityName }) => ({
parentMapper: ({
membershipId,
membershipCreatedAt,
membershipUpdatedAt,
orgId,
identityName,
projectType
}) => ({
id: membershipId,
identityId,
username: identityName,
@@ -488,6 +499,7 @@ export const permissionDALFactory = (db: TDbClient) => {
createdAt: membershipCreatedAt,
updatedAt: membershipUpdatedAt,
orgId,
projectType,
// just a prefilled value
orgAuthEnforced: false
}),
@@ -6,6 +6,7 @@ import handlebars from "handlebars";
import {
OrgMembershipRole,
ProjectMembershipRole,
ProjectType,
ServiceTokenScopes,
TIdentityProjectMemberships,
TProjectMemberships
@@ -255,6 +256,13 @@ export const permissionServiceFactory = ({
return {
permission,
membership: userProjectPermission,
ForbidOnInvalidProjectType: (productType: ProjectType) => {
if (productType !== userProjectPermission.projectType) {
throw new BadRequestError({
message: `The project is of type ${userProjectPermission.projectType}. Operations of type ${productType} are not allowed.`
});
}
},
hasRole: (role: string) =>
userProjectPermission.roles.findIndex(
({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug
@@ -323,6 +331,13 @@ export const permissionServiceFactory = ({
return {
permission,
membership: identityProjectPermission,
ForbidOnInvalidProjectType: (productType: ProjectType) => {
if (productType !== identityProjectPermission.projectType) {
throw new BadRequestError({
message: `The project is of type ${identityProjectPermission.projectType}. Operations of type ${productType} are not allowed.`
});
}
},
hasRole: (role: string) =>
identityProjectPermission.roles.findIndex(
({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug
@@ -361,7 +376,14 @@ export const permissionServiceFactory = ({
const scopes = ServiceTokenScopes.parse(serviceToken.scopes || []);
return {
permission: buildServiceTokenProjectPermission(scopes, serviceToken.permissions),
membership: undefined
membership: undefined,
ForbidOnInvalidProjectType: (productType: ProjectType) => {
if (productType !== serviceTokenProject.type) {
throw new BadRequestError({
message: `The project is of type ${serviceTokenProject.type}. Operations of type ${productType} are not allowed.`
});
}
}
};
};
@@ -370,6 +392,7 @@ export const permissionServiceFactory = ({
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
membership: undefined;
hasRole: (arg: string) => boolean;
ForbidOnInvalidProjectType: (type: ProjectType) => void;
} // service token doesn't have both membership and roles
: {
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
@@ -379,6 +402,7 @@ export const permissionServiceFactory = ({
roles: Array<{ role: string }>;
};
hasRole: (role: string) => boolean;
ForbidOnInvalidProjectType: (type: ProjectType) => void;
};
const getProjectPermission = async <T extends ActorType>(
@@ -1,6 +1,7 @@
import { ForbiddenError } from "@casl/ability";
import picomatch from "picomatch";
import { ProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
@@ -78,13 +79,14 @@ export const secretApprovalPolicyServiceFactory = ({
if (!groupApprovers.length && approvals > approvers.length)
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
const { permission } = await permissionService.getProjectPermission(
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SecretApproval
@@ -191,13 +193,14 @@ export const secretApprovalPolicyServiceFactory = ({
});
}
const { permission } = await permissionService.getProjectPermission(
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
actor,
actorId,
secretApprovalPolicy.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
const plan = await licenseService.getPlan(actorOrgId);
@@ -285,13 +288,14 @@ export const secretApprovalPolicyServiceFactory = ({
if (!sapPolicy)
throw new NotFoundError({ message: `Secret approval policy with ID '${secretPolicyId}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
actor,
actorId,
sapPolicy.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.SecretApproval
@@ -2,6 +2,7 @@ import { ForbiddenError, subject } from "@casl/ability";
import {
ProjectMembershipRole,
ProjectType,
SecretEncryptionAlgo,
SecretKeyEncoding,
SecretType,
@@ -875,13 +876,14 @@ export const secretApprovalRequestServiceFactory = ({
}: TGenerateSecretApprovalRequestDTO) => {
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
const { permission } = await permissionService.getProjectPermission(
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
@@ -1155,14 +1157,14 @@ export const secretApprovalRequestServiceFactory = ({
if (actor === ActorType.SERVICE || actor === ActorType.Machine)
throw new BadRequestError({ message: "Cannot use service token or machine token over protected branches" });
const { permission } = await permissionService.getProjectPermission(
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
if (!folder)
throw new NotFoundError({
@@ -1,7 +1,7 @@
import { ForbiddenError, subject } from "@casl/ability";
import Ajv from "ajv";
import { ProjectVersion, TableName } from "@app/db/schemas";
import { ProjectType, ProjectVersion, TableName } from "@app/db/schemas";
import { decryptSymmetric128BitHexKeyUTF8, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { TProjectPermission } from "@app/lib/types";
@@ -53,13 +53,14 @@ export const secretRotationServiceFactory = ({
actorAuthMethod,
projectId
}: TProjectPermission) => {
const { permission } = await permissionService.getProjectPermission(
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
return {
@@ -81,13 +82,14 @@ export const secretRotationServiceFactory = ({
secretPath,
environment
}: TCreateSecretRotationDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SecretRotation
@@ -234,13 +236,14 @@ export const secretRotationServiceFactory = ({
message: "Failed to add secret rotation due to plan restriction. Upgrade plan to add secret rotation."
});
const { permission } = await permissionService.getProjectPermission(
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
actor,
actorId,
doc.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretRotation);
await secretRotationQueue.removeFromQueue(doc.id, doc.interval);
await secretRotationQueue.addToQueue(doc.id, doc.interval);
@@ -251,13 +254,14 @@ export const secretRotationServiceFactory = ({
const doc = await secretRotationDAL.findById(rotationId);
if (!doc) throw new NotFoundError({ message: `Rotation with ID '${rotationId}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
actor,
actorId,
doc.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.SecretRotation
@@ -1,6 +1,6 @@
import { ForbiddenError, subject } from "@casl/ability";
import { TableName, TSecretTagJunctionInsert, TSecretV2TagJunctionInsert } from "@app/db/schemas";
import { ProjectType, TableName, TSecretTagJunctionInsert, TSecretV2TagJunctionInsert } from "@app/db/schemas";
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
import { InternalServerError, NotFoundError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn";
@@ -322,13 +322,14 @@ export const secretSnapshotServiceFactory = ({
if (!snapshot) throw new NotFoundError({ message: `Snapshot with ID '${snapshotId}' not found` });
const shouldUseBridge = snapshot.projectVersion === 3;
const { permission } = await permissionService.getProjectPermission(
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
actor,
actorId,
snapshot.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SecretRollback