mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Fix root folder issue with folder policies check and multi env error message improvement
This commit is contained in:
@@ -2,7 +2,7 @@ import { ForbiddenError, subject } from "@casl/ability";
|
|||||||
import path from "path";
|
import path from "path";
|
||||||
import { v4 as uuidv4, validate as uuidValidate } from "uuid";
|
import { v4 as uuidv4, validate as uuidValidate } from "uuid";
|
||||||
|
|
||||||
import { TSecretFolders, TSecretFoldersInsert } from "@app/db/schemas";
|
import { TProjectEnvironments, TSecretFolders, TSecretFoldersInsert } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
|
import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
|
||||||
@@ -469,15 +469,28 @@ export const secretFolderServiceFactory = ({
|
|||||||
|
|
||||||
const $checkFolderPolicy = async ({
|
const $checkFolderPolicy = async ({
|
||||||
projectId,
|
projectId,
|
||||||
environment,
|
env,
|
||||||
parentId
|
parentId,
|
||||||
|
idOrName
|
||||||
}: {
|
}: {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
environment: string;
|
env: TProjectEnvironments;
|
||||||
parentId: string;
|
parentId: string;
|
||||||
|
idOrName: string;
|
||||||
}) => {
|
}) => {
|
||||||
|
const targetFolder = await folderDAL.findOne({
|
||||||
|
envId: env.id,
|
||||||
|
[uuidValidate(idOrName) ? "id" : "name"]: idOrName,
|
||||||
|
parentId,
|
||||||
|
isReserved: false
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!targetFolder) {
|
||||||
|
throw new NotFoundError({ message: `Target folder not found` });
|
||||||
|
}
|
||||||
|
|
||||||
// get environment root folder (as it's needed to get all folders under it)
|
// get environment root folder (as it's needed to get all folders under it)
|
||||||
const rootFolder = await folderDAL.findBySecretPath(projectId, environment, "/");
|
const rootFolder = await folderDAL.findBySecretPath(projectId, env.slug, "/");
|
||||||
if (!rootFolder) throw new NotFoundError({ message: `Root folder not found` });
|
if (!rootFolder) throw new NotFoundError({ message: `Root folder not found` });
|
||||||
// get all folders under environment root folder
|
// get all folders under environment root folder
|
||||||
const folderPaths = await folderDAL.findByEnvsDeep({ parentIds: [rootFolder.id] });
|
const folderPaths = await folderDAL.findByEnvsDeep({ parentIds: [rootFolder.id] });
|
||||||
@@ -492,7 +505,13 @@ export const secretFolderServiceFactory = ({
|
|||||||
folderMap.get(normalizeKey(folder.parentId))?.push(folder);
|
folderMap.get(normalizeKey(folder.parentId))?.push(folder);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recursively collect all folders under the given parentId
|
// Find the target folder in the folderPaths to get its full details
|
||||||
|
const targetFolderWithPath = folderPaths.find((f) => f.id === targetFolder.id);
|
||||||
|
if (!targetFolderWithPath) {
|
||||||
|
throw new NotFoundError({ message: `Target folder path not found` });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively collect all folders under the target folder (descendants only)
|
||||||
const collectDescendants = (
|
const collectDescendants = (
|
||||||
id: string
|
id: string
|
||||||
): (TSecretFolders & { path: string; depth: number; environment: string })[] => {
|
): (TSecretFolders & { path: string; depth: number; environment: string })[] => {
|
||||||
@@ -500,23 +519,31 @@ export const secretFolderServiceFactory = ({
|
|||||||
return [...children, ...children.flatMap((child) => collectDescendants(child.id))];
|
return [...children, ...children.flatMap((child) => collectDescendants(child.id))];
|
||||||
};
|
};
|
||||||
|
|
||||||
const foldersUnderParent = collectDescendants(parentId);
|
const targetFolderDescendants = collectDescendants(targetFolder.id);
|
||||||
|
|
||||||
const folderPolicyPaths = foldersUnderParent.map((folder) => ({
|
// Include the target folder itself plus all its descendants
|
||||||
|
const foldersToCheck = [targetFolderWithPath, ...targetFolderDescendants];
|
||||||
|
|
||||||
|
const folderPolicyPaths = foldersToCheck.map((folder) => ({
|
||||||
path: folder.path,
|
path: folder.path,
|
||||||
id: folder.id
|
id: folder.id
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// get secrets under the given folders
|
// get secrets under the given folders
|
||||||
const secrets = await secretV2BridgeDAL.findByFolderIds({ folderIds: folderPolicyPaths.map((p) => p.id) });
|
const secrets = await secretV2BridgeDAL.findByFolderIds({
|
||||||
|
folderIds: folderPolicyPaths.map((p) => p.id)
|
||||||
|
});
|
||||||
|
|
||||||
for await (const folderPolicyPath of folderPolicyPaths) {
|
for await (const folderPolicyPath of folderPolicyPaths) {
|
||||||
// eslint-disable-next-line no-continue
|
// eslint-disable-next-line no-continue
|
||||||
if (!secrets.some((s) => s.folderId === folderPolicyPath.id)) continue;
|
if (!secrets.some((s) => s.folderId === folderPolicyPath.id)) continue;
|
||||||
|
|
||||||
const policy = await secretApprovalPolicyService.getSecretApprovalPolicy(
|
const policy = await secretApprovalPolicyService.getSecretApprovalPolicy(
|
||||||
projectId,
|
projectId,
|
||||||
environment,
|
env.slug,
|
||||||
folderPolicyPath.path
|
folderPolicyPath.path
|
||||||
);
|
);
|
||||||
|
|
||||||
// if there is a policy and there are secrets under the given folder, throw error
|
// if there is a policy and there are secrets under the given folder, throw error
|
||||||
if (policy) {
|
if (policy) {
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
@@ -560,7 +587,7 @@ export const secretFolderServiceFactory = ({
|
|||||||
message: `Folder with path '${secretPath}' in environment with slug '${environment}' not found`
|
message: `Folder with path '${secretPath}' in environment with slug '${environment}' not found`
|
||||||
});
|
});
|
||||||
|
|
||||||
await $checkFolderPolicy({ projectId, environment, parentId: parentFolder.id });
|
await $checkFolderPolicy({ projectId, env, parentId: parentFolder.id, idOrName });
|
||||||
|
|
||||||
const [doc] = await folderDAL.delete(
|
const [doc] = await folderDAL.delete(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -166,23 +166,36 @@ export const SelectionPanel = ({
|
|||||||
secrets: secretsToDelete
|
secrets: secretsToDelete
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
environment: env.slug
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const results = await Promise.allSettled(promises);
|
const results = await Promise.allSettled(promises);
|
||||||
const areEntriesDeleted = results.some((result) => result.status === "fulfilled");
|
const areAllEntriesDeleted = results.every((result) => result.status === "fulfilled");
|
||||||
|
const areSomeEntriesDeleted = results.some((result) => result.status === "fulfilled");
|
||||||
if (processedEntries === 0) {
|
if (processedEntries === 0) {
|
||||||
handlePopUpClose("bulkDeleteEntries");
|
handlePopUpClose("bulkDeleteEntries");
|
||||||
createNotification({
|
createNotification({
|
||||||
type: "info",
|
type: "info",
|
||||||
text: "You don't have access to delete selected items"
|
text: "You don't have access to delete selected items"
|
||||||
});
|
});
|
||||||
} else if (areEntriesDeleted) {
|
} else if (areAllEntriesDeleted) {
|
||||||
handlePopUpClose("bulkDeleteEntries");
|
handlePopUpClose("bulkDeleteEntries");
|
||||||
resetSelectedEntries();
|
resetSelectedEntries();
|
||||||
createNotification({
|
createNotification({
|
||||||
type: "success",
|
type: "success",
|
||||||
text: "Successfully deleted selected secrets and folders"
|
text: "Successfully deleted selected secrets and folders"
|
||||||
});
|
});
|
||||||
|
} else if (areSomeEntriesDeleted) {
|
||||||
|
createNotification({
|
||||||
|
type: "warning",
|
||||||
|
text: `Successfully deleted selected secrets and folders on environments: ${results
|
||||||
|
.filter((result) => result.status === "fulfilled")
|
||||||
|
.map((result) => result.value.environment)
|
||||||
|
.join(", ")} but failed on the other environments`
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
createNotification({
|
createNotification({
|
||||||
type: "error",
|
type: "error",
|
||||||
|
|||||||
Reference in New Issue
Block a user