|
|
|
@@ -1,9 +1,12 @@
|
|
|
|
|
import path from "node:path";
|
|
|
|
|
|
|
|
|
|
import { ForbiddenError, subject } from "@casl/ability";
|
|
|
|
|
|
|
|
|
|
import { TableName } from "@app/db/schemas";
|
|
|
|
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
|
|
|
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
|
|
|
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
|
|
|
|
import { getReplicationFolderName } from "@app/ee/services/secret-replication/secret-replication-service";
|
|
|
|
|
import { BadRequestError } from "@app/lib/errors";
|
|
|
|
|
|
|
|
|
|
import { TProjectDALFactory } from "../project/project-dal";
|
|
|
|
@@ -57,7 +60,7 @@ export const secretImportServiceFactory = ({
|
|
|
|
|
actorAuthMethod,
|
|
|
|
|
projectId,
|
|
|
|
|
isReplication,
|
|
|
|
|
path
|
|
|
|
|
path: secretPath
|
|
|
|
|
}: TCreateSecretImportDTO) => {
|
|
|
|
|
const { permission } = await permissionService.getProjectPermission(
|
|
|
|
|
actor,
|
|
|
|
@@ -70,7 +73,7 @@ export const secretImportServiceFactory = ({
|
|
|
|
|
// check if user has permission to import into destination path
|
|
|
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
|
|
|
ProjectPermissionActions.Create,
|
|
|
|
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
|
|
|
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// check if user has permission to import from target path
|
|
|
|
@@ -92,7 +95,7 @@ export const secretImportServiceFactory = ({
|
|
|
|
|
|
|
|
|
|
await projectDAL.checkProjectUpgradeStatus(projectId);
|
|
|
|
|
|
|
|
|
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
|
|
|
|
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
|
|
|
|
if (!folder) throw new BadRequestError({ message: "Folder not found", name: "Create import" });
|
|
|
|
|
|
|
|
|
|
const [importEnv] = await projectEnvDAL.findBySlugs(projectId, [data.environment]);
|
|
|
|
@@ -103,14 +106,14 @@ export const secretImportServiceFactory = ({
|
|
|
|
|
const existingImport = await secretImportDAL.findOne({
|
|
|
|
|
folderId: sourceFolder.id,
|
|
|
|
|
importEnv: folder.environment.id,
|
|
|
|
|
importPath: path
|
|
|
|
|
importPath: secretPath
|
|
|
|
|
});
|
|
|
|
|
if (existingImport) throw new BadRequestError({ message: "Cyclic import not allowed" });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const secImport = await secretImportDAL.transaction(async (tx) => {
|
|
|
|
|
const lastPos = await secretImportDAL.findLastImportPosition(folder.id, tx);
|
|
|
|
|
return secretImportDAL.create(
|
|
|
|
|
const doc = await secretImportDAL.create(
|
|
|
|
|
{
|
|
|
|
|
folderId: folder.id,
|
|
|
|
|
position: lastPos + 1,
|
|
|
|
@@ -120,6 +123,19 @@ export const secretImportServiceFactory = ({
|
|
|
|
|
},
|
|
|
|
|
tx
|
|
|
|
|
);
|
|
|
|
|
if (doc.isReplication) {
|
|
|
|
|
await secretImportDAL.create(
|
|
|
|
|
{
|
|
|
|
|
folderId: folder.id,
|
|
|
|
|
position: lastPos + 2,
|
|
|
|
|
isReserved: true,
|
|
|
|
|
importEnv: folder.environment.id,
|
|
|
|
|
importPath: path.join(secretPath, getReplicationFolderName(doc.id))
|
|
|
|
|
},
|
|
|
|
|
tx
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return doc;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (secImport.isReplication && sourceFolder) {
|
|
|
|
@@ -148,7 +164,7 @@ export const secretImportServiceFactory = ({
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const updateImport = async ({
|
|
|
|
|
path,
|
|
|
|
|
path: secretPath,
|
|
|
|
|
environment,
|
|
|
|
|
projectId,
|
|
|
|
|
actor,
|
|
|
|
@@ -167,10 +183,10 @@ export const secretImportServiceFactory = ({
|
|
|
|
|
);
|
|
|
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
|
|
|
ProjectPermissionActions.Edit,
|
|
|
|
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
|
|
|
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
|
|
|
|
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
|
|
|
|
if (!folder) throw new BadRequestError({ message: "Folder not found", name: "Update import" });
|
|
|
|
|
|
|
|
|
|
const secImpDoc = await secretImportDAL.findOne({ folderId: folder.id, id });
|
|
|
|
@@ -190,7 +206,7 @@ export const secretImportServiceFactory = ({
|
|
|
|
|
const existingImport = await secretImportDAL.findOne({
|
|
|
|
|
folderId: sourceFolder.id,
|
|
|
|
|
importEnv: folder.environment.id,
|
|
|
|
|
importPath: path
|
|
|
|
|
importPath: secretPath
|
|
|
|
|
});
|
|
|
|
|
if (existingImport) throw new BadRequestError({ message: "Cyclic import not allowed" });
|
|
|
|
|
}
|
|
|
|
@@ -199,12 +215,31 @@ export const secretImportServiceFactory = ({
|
|
|
|
|
const secImp = await secretImportDAL.findOne({ folderId: folder.id, id });
|
|
|
|
|
if (!secImp) throw ERR_SEC_IMP_NOT_FOUND;
|
|
|
|
|
if (data.position) {
|
|
|
|
|
await secretImportDAL.updateAllPosition(folder.id, secImp.position, data.position, tx);
|
|
|
|
|
if (secImp.isReplication) {
|
|
|
|
|
await secretImportDAL.updateAllPosition(folder.id, secImp.position, data.position, 2, tx);
|
|
|
|
|
} else {
|
|
|
|
|
await secretImportDAL.updateAllPosition(folder.id, secImp.position, data.position, 1, tx);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (secImp.isReplication) {
|
|
|
|
|
const replicationFolderPath = path.join(secretPath, getReplicationFolderName(secImp.id));
|
|
|
|
|
await secretImportDAL.update(
|
|
|
|
|
{
|
|
|
|
|
folderId: folder.id,
|
|
|
|
|
importEnv: folder.environment.id,
|
|
|
|
|
importPath: replicationFolderPath,
|
|
|
|
|
isReserved: true
|
|
|
|
|
},
|
|
|
|
|
{ position: data?.position ? data.position + 1 : undefined },
|
|
|
|
|
tx
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
const [doc] = await secretImportDAL.update(
|
|
|
|
|
{ id, folderId: folder.id },
|
|
|
|
|
{
|
|
|
|
|
position: data?.position,
|
|
|
|
|
// when moving replicated import, the position is meant for reserved import
|
|
|
|
|
// replicated one should always be behind the reserved import
|
|
|
|
|
position: data.position,
|
|
|
|
|
importEnv: data?.environment ? importedEnv.id : undefined,
|
|
|
|
|
importPath: data?.path
|
|
|
|
|
},
|
|
|
|
@@ -216,7 +251,7 @@ export const secretImportServiceFactory = ({
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const deleteImport = async ({
|
|
|
|
|
path,
|
|
|
|
|
path: secretPath,
|
|
|
|
|
environment,
|
|
|
|
|
projectId,
|
|
|
|
|
actor,
|
|
|
|
@@ -234,16 +269,34 @@ export const secretImportServiceFactory = ({
|
|
|
|
|
);
|
|
|
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
|
|
|
ProjectPermissionActions.Delete,
|
|
|
|
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
|
|
|
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
|
|
|
|
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
|
|
|
|
if (!folder) throw new BadRequestError({ message: "Folder not found", name: "Delete import" });
|
|
|
|
|
|
|
|
|
|
const secImport = await secretImportDAL.transaction(async (tx) => {
|
|
|
|
|
const [doc] = await secretImportDAL.delete({ folderId: folder.id, id }, tx);
|
|
|
|
|
if (!doc) throw new BadRequestError({ name: "Sec imp del", message: "Secret import doc not found" });
|
|
|
|
|
await secretImportDAL.updateAllPosition(folder.id, doc.position, -1, tx);
|
|
|
|
|
if (doc.isReplication) {
|
|
|
|
|
const replicationFolderPath = path.join(secretPath, getReplicationFolderName(doc.id));
|
|
|
|
|
const replicatedFolder = await folderDAL.findBySecretPath(projectId, environment, replicationFolderPath, tx);
|
|
|
|
|
if (replicatedFolder) {
|
|
|
|
|
await secretImportDAL.delete(
|
|
|
|
|
{
|
|
|
|
|
folderId: folder.id,
|
|
|
|
|
importEnv: folder.environment.id,
|
|
|
|
|
importPath: replicationFolderPath,
|
|
|
|
|
isReserved: true
|
|
|
|
|
},
|
|
|
|
|
tx
|
|
|
|
|
);
|
|
|
|
|
await folderDAL.deleteById(replicatedFolder.id, tx);
|
|
|
|
|
}
|
|
|
|
|
await secretImportDAL.updateAllPosition(folder.id, doc.position, -1, 2, tx);
|
|
|
|
|
} else {
|
|
|
|
|
await secretImportDAL.updateAllPosition(folder.id, doc.position, -1, 1, tx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const importEnv = await projectEnvDAL.findById(doc.importEnv);
|
|
|
|
|
if (!importEnv) throw new BadRequestError({ error: "Imported env not found", name: "Create import" });
|
|
|
|
@@ -251,7 +304,7 @@ export const secretImportServiceFactory = ({
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await secretQueueService.syncSecrets({
|
|
|
|
|
secretPath: path,
|
|
|
|
|
secretPath,
|
|
|
|
|
projectId,
|
|
|
|
|
environmentSlug: environment,
|
|
|
|
|
excludeReplication: true
|
|
|
|
@@ -267,7 +320,7 @@ export const secretImportServiceFactory = ({
|
|
|
|
|
actorOrgId,
|
|
|
|
|
actorAuthMethod,
|
|
|
|
|
projectId,
|
|
|
|
|
path,
|
|
|
|
|
path: secretPath,
|
|
|
|
|
id: secretImportDocId
|
|
|
|
|
}: TResyncSecretImportReplicationDTO) => {
|
|
|
|
|
const { permission, membership } = await permissionService.getProjectPermission(
|
|
|
|
@@ -281,7 +334,7 @@ export const secretImportServiceFactory = ({
|
|
|
|
|
// check if user has permission to import into destination path
|
|
|
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
|
|
|
ProjectPermissionActions.Create,
|
|
|
|
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
|
|
|
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const plan = await licenseService.getPlan(actorOrgId);
|
|
|
|
@@ -291,7 +344,7 @@ export const secretImportServiceFactory = ({
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
|
|
|
|
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
|
|
|
|
if (!folder) throw new BadRequestError({ message: "Folder not found", name: "Update import" });
|
|
|
|
|
|
|
|
|
|
const [secretImportDoc] = await secretImportDAL.find({
|
|
|
|
@@ -338,7 +391,7 @@ export const secretImportServiceFactory = ({
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getImports = async ({
|
|
|
|
|
path,
|
|
|
|
|
path: secretPath,
|
|
|
|
|
environment,
|
|
|
|
|
projectId,
|
|
|
|
|
actor,
|
|
|
|
@@ -355,10 +408,10 @@ export const secretImportServiceFactory = ({
|
|
|
|
|
);
|
|
|
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
|
|
|
ProjectPermissionActions.Read,
|
|
|
|
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
|
|
|
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
|
|
|
|
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
|
|
|
|
if (!folder) throw new BadRequestError({ message: "Folder not found", name: "Get imports" });
|
|
|
|
|
|
|
|
|
|
const secImports = await secretImportDAL.find({ folderId: folder.id });
|
|
|
|
@@ -366,7 +419,7 @@ export const secretImportServiceFactory = ({
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getSecretsFromImports = async ({
|
|
|
|
|
path,
|
|
|
|
|
path: secretPath,
|
|
|
|
|
environment,
|
|
|
|
|
projectId,
|
|
|
|
|
actor,
|
|
|
|
@@ -383,9 +436,9 @@ export const secretImportServiceFactory = ({
|
|
|
|
|
);
|
|
|
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
|
|
|
ProjectPermissionActions.Read,
|
|
|
|
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
|
|
|
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
|
|
|
|
);
|
|
|
|
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
|
|
|
|
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
|
|
|
|
if (!folder) return [];
|
|
|
|
|
// this will already order by position
|
|
|
|
|
// so anything based on this order will also be in right position
|
|
|
|
|