mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-19 07:33:18 +00:00
feat(pam): PAM Platform V1
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
|
||||
export type TPamFolderDALFactory = ReturnType<typeof pamFolderDALFactory>;
|
||||
export const pamFolderDALFactory = (db: TDbClient) => {
|
||||
const orm = ormify(db, TableName.PamFolder);
|
||||
return { ...orm };
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
import { TPamFolderDALFactory } from "./pam-folder-dal";
|
||||
|
||||
type GetFullFolderPath = {
|
||||
pamFolderDAL: Pick<TPamFolderDALFactory, "find">;
|
||||
folderId?: string | null;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export const getFullPamFolderPath = async ({
|
||||
pamFolderDAL,
|
||||
folderId,
|
||||
projectId
|
||||
}: GetFullFolderPath): Promise<string> => {
|
||||
if (!folderId) return "/";
|
||||
|
||||
const folders = await pamFolderDAL.find({ projectId });
|
||||
const folderMap = new Map(folders.map((folder) => [folder.id, folder]));
|
||||
|
||||
if (!folderMap.has(folderId)) return "";
|
||||
|
||||
const path: string[] = [];
|
||||
let currentFolderId: string | null | undefined = folderId;
|
||||
|
||||
while (currentFolderId) {
|
||||
const folder = folderMap.get(currentFolderId);
|
||||
if (!folder) break;
|
||||
|
||||
path.unshift(folder.name);
|
||||
currentFolderId = folder.parentId;
|
||||
}
|
||||
|
||||
return `/${path.join("/")}`;
|
||||
};
|
||||
@@ -0,0 +1,151 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
import { ActionProjectType, TPamFolders } from "@app/db/schemas";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { OrgServiceActor } from "@app/lib/types";
|
||||
|
||||
import { TLicenseServiceFactory } from "../license/license-service";
|
||||
import { TPamFolderDALFactory } from "./pam-folder-dal";
|
||||
import { TCreateFolderDTO, TUpdateFolderDTO } from "./pam-folder-types";
|
||||
|
||||
type TPamFolderServiceFactoryDep = {
|
||||
pamFolderDAL: TPamFolderDALFactory;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
};
|
||||
|
||||
export type TPamFolderServiceFactory = ReturnType<typeof pamFolderServiceFactory>;
|
||||
|
||||
export const pamFolderServiceFactory = ({
|
||||
pamFolderDAL,
|
||||
permissionService,
|
||||
licenseService
|
||||
}: TPamFolderServiceFactoryDep) => {
|
||||
const createFolder = async ({ name, description, parentId, projectId }: TCreateFolderDTO, actor: OrgServiceActor) => {
|
||||
const orgLicensePlan = await licenseService.getPlan(actor.orgId);
|
||||
if (!orgLicensePlan.pam) {
|
||||
throw new BadRequestError({
|
||||
message: "PAM operation failed due to organization plan restrictions."
|
||||
});
|
||||
}
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor: actor.type,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorId: actor.id,
|
||||
actorOrgId: actor.orgId,
|
||||
projectId,
|
||||
actionProjectType: ActionProjectType.PAM
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.PamFolders);
|
||||
|
||||
if (parentId) {
|
||||
if (!(await pamFolderDAL.findOne({ id: parentId, projectId }))) {
|
||||
throw new NotFoundError({
|
||||
message: `Parent folder '${parentId}' not found for project '${projectId}'`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const existingFolder = await pamFolderDAL.findOne({
|
||||
name,
|
||||
parentId: parentId || null,
|
||||
projectId
|
||||
});
|
||||
|
||||
if (existingFolder) {
|
||||
throw new BadRequestError({
|
||||
message: `Folder with name '${name}' already exists for this parent`
|
||||
});
|
||||
}
|
||||
|
||||
const folder = await pamFolderDAL.create({
|
||||
name,
|
||||
description: description ?? null,
|
||||
parentId: parentId || null,
|
||||
projectId
|
||||
});
|
||||
|
||||
return folder;
|
||||
};
|
||||
|
||||
const updateFolder = async ({ id, name, description }: TUpdateFolderDTO, actor: OrgServiceActor) => {
|
||||
const orgLicensePlan = await licenseService.getPlan(actor.orgId);
|
||||
if (!orgLicensePlan.pam) {
|
||||
throw new BadRequestError({
|
||||
message: "PAM operation failed due to organization plan restrictions."
|
||||
});
|
||||
}
|
||||
|
||||
const folder = await pamFolderDAL.findById(id);
|
||||
if (!folder) throw new NotFoundError({ message: `Folder with ID '${id}' not found` });
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor: actor.type,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorId: actor.id,
|
||||
actorOrgId: actor.orgId,
|
||||
projectId: folder.projectId,
|
||||
actionProjectType: ActionProjectType.PAM
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.PamFolders);
|
||||
|
||||
const updateDoc: Partial<TPamFolders> = {};
|
||||
|
||||
if (name !== undefined) {
|
||||
updateDoc.name = name;
|
||||
}
|
||||
|
||||
if (description !== undefined) {
|
||||
updateDoc.description = description;
|
||||
}
|
||||
|
||||
if (name && name !== folder.name) {
|
||||
const existingFolder = await pamFolderDAL.findOne({
|
||||
name,
|
||||
parentId: folder.parentId || null,
|
||||
projectId: folder.projectId
|
||||
});
|
||||
|
||||
if (existingFolder) {
|
||||
throw new BadRequestError({
|
||||
message: `Folder with name '${name}' already exists for this parent`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(updateDoc).length === 0) {
|
||||
return folder;
|
||||
}
|
||||
|
||||
const updatedFolder = await pamFolderDAL.updateById(id, updateDoc);
|
||||
|
||||
return updatedFolder;
|
||||
};
|
||||
|
||||
const deleteFolder = async (id: string, actor: OrgServiceActor) => {
|
||||
const folder = await pamFolderDAL.findById(id);
|
||||
if (!folder) throw new NotFoundError({ message: `Folder with ID '${id}' not found` });
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor: actor.type,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorId: actor.id,
|
||||
actorOrgId: actor.orgId,
|
||||
projectId: folder.projectId,
|
||||
actionProjectType: ActionProjectType.PAM
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.PamFolders);
|
||||
|
||||
const deletedFolder = await pamFolderDAL.deleteById(id);
|
||||
|
||||
return deletedFolder;
|
||||
};
|
||||
|
||||
return { createFolder, updateFolder, deleteFolder };
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
// DTOs
|
||||
export interface TCreateFolderDTO {
|
||||
projectId: string;
|
||||
parentId?: string | null;
|
||||
name: string;
|
||||
description?: string | null;
|
||||
}
|
||||
|
||||
export interface TUpdateFolderDTO {
|
||||
id: string;
|
||||
name?: string;
|
||||
description?: string | null;
|
||||
}
|
||||
Reference in New Issue
Block a user