Merge branch 'main' into feat/remindersImprovement

This commit is contained in:
Carlos Monastyrski
2025-07-23 11:27:31 -03:00
619 changed files with 15481 additions and 6720 deletions
@@ -32,7 +32,8 @@ export enum AppConnection {
Railway = "railway",
Bitbucket = "bitbucket",
Checkly = "checkly",
Supabase = "supabase"
Supabase = "supabase",
Okta = "okta"
}
export enum AWSRegion {
@@ -5,6 +5,7 @@ import {
validateOCIConnectionCredentials
} from "@app/ee/services/app-connections/oci";
import { getOracleDBConnectionListItem, OracleDBConnectionMethod } from "@app/ee/services/app-connections/oracledb";
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError } from "@app/lib/errors";
@@ -91,6 +92,7 @@ import { getLdapConnectionListItem, LdapConnectionMethod, validateLdapConnection
import { getMsSqlConnectionListItem, MsSqlConnectionMethod } from "./mssql";
import { MySqlConnectionMethod } from "./mysql/mysql-connection-enums";
import { getMySqlConnectionListItem } from "./mysql/mysql-connection-fns";
import { getOktaConnectionListItem, OktaConnectionMethod, validateOktaConnectionCredentials } from "./okta";
import { getPostgresConnectionListItem, PostgresConnectionMethod } from "./postgres";
import { getRailwayConnectionListItem, validateRailwayConnectionCredentials } from "./railway";
import { RenderConnectionMethod } from "./render/render-connection-enums";
@@ -154,7 +156,8 @@ export const listAppConnectionOptions = () => {
getRailwayConnectionListItem(),
getBitbucketConnectionListItem(),
getChecklyConnectionListItem(),
getSupabaseConnectionListItem()
getSupabaseConnectionListItem(),
getOktaConnectionListItem()
].sort((a, b) => a.name.localeCompare(b.name));
};
@@ -201,7 +204,8 @@ export const decryptAppConnectionCredentials = async ({
};
export const validateAppConnectionCredentials = async (
appConnection: TAppConnectionConfig
appConnection: TAppConnectionConfig,
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
): Promise<TAppConnection["credentials"]> => {
const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TAppConnectionCredentialsValidator> = {
[AppConnection.AWS]: validateAwsConnectionCredentials as TAppConnectionCredentialsValidator,
@@ -239,10 +243,11 @@ export const validateAppConnectionCredentials = async (
[AppConnection.Railway]: validateRailwayConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Bitbucket]: validateBitbucketConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Checkly]: validateChecklyConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Supabase]: validateSupabaseConnectionCredentials as TAppConnectionCredentialsValidator
[AppConnection.Supabase]: validateSupabaseConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Okta]: validateOktaConnectionCredentials as TAppConnectionCredentialsValidator
};
return VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection);
return VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection, gatewayService);
};
export const getAppConnectionMethodName = (method: TAppConnection["method"]) => {
@@ -278,6 +283,7 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
case CloudflareConnectionMethod.APIToken:
case BitbucketConnectionMethod.ApiToken:
case ZabbixConnectionMethod.ApiToken:
case OktaConnectionMethod.ApiToken:
return "API Token";
case PostgresConnectionMethod.UsernameAndPassword:
case MsSqlConnectionMethod.UsernameAndPassword:
@@ -365,7 +371,8 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
[AppConnection.Railway]: platformManagedCredentialsNotSupported,
[AppConnection.Bitbucket]: platformManagedCredentialsNotSupported,
[AppConnection.Checkly]: platformManagedCredentialsNotSupported,
[AppConnection.Supabase]: platformManagedCredentialsNotSupported
[AppConnection.Supabase]: platformManagedCredentialsNotSupported,
[AppConnection.Okta]: platformManagedCredentialsNotSupported
};
export const enterpriseAppCheck = async (
@@ -34,7 +34,8 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
[AppConnection.Railway]: "Railway",
[AppConnection.Bitbucket]: "Bitbucket",
[AppConnection.Checkly]: "Checkly",
[AppConnection.Supabase]: "Supabase"
[AppConnection.Supabase]: "Supabase",
[AppConnection.Okta]: "Okta"
};
export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanType> = {
@@ -71,5 +72,6 @@ export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanTyp
[AppConnection.Railway]: AppConnectionPlanType.Regular,
[AppConnection.Bitbucket]: AppConnectionPlanType.Regular,
[AppConnection.Checkly]: AppConnectionPlanType.Regular,
[AppConnection.Supabase]: AppConnectionPlanType.Regular
[AppConnection.Supabase]: AppConnectionPlanType.Regular,
[AppConnection.Okta]: AppConnectionPlanType.Regular
};
@@ -18,7 +18,7 @@ export const BaseAppConnectionSchema = AppConnectionsSchema.omit({
export const GenericCreateAppConnectionFieldsSchema = (
app: AppConnection,
{ supportsPlatformManagedCredentials = false }: TAppConnectionBaseConfig = {}
{ supportsPlatformManagedCredentials = false, supportsGateways = false }: TAppConnectionBaseConfig = {}
) =>
z.object({
name: slugSchema({ field: "name" }).describe(AppConnections.CREATE(app).name),
@@ -30,12 +30,23 @@ export const GenericCreateAppConnectionFieldsSchema = (
.describe(AppConnections.CREATE(app).description),
isPlatformManagedCredentials: supportsPlatformManagedCredentials
? z.boolean().optional().default(false).describe(AppConnections.CREATE(app).isPlatformManagedCredentials)
: z.literal(false).optional().describe(`Not supported for ${APP_CONNECTION_NAME_MAP[app]} Connections.`)
: z
.literal(false, {
errorMap: () => ({ message: `Not supported for ${APP_CONNECTION_NAME_MAP[app]} Connections` })
})
.optional()
.describe(`Not supported for ${APP_CONNECTION_NAME_MAP[app]} Connections.`),
gatewayId: supportsGateways
? z.string().uuid().nullish().describe("The Gateway ID to use for this connection.")
: z
.undefined({ message: `Not supported for ${APP_CONNECTION_NAME_MAP[app]} Connections` })
.or(z.null({ message: `Not supported for ${APP_CONNECTION_NAME_MAP[app]} Connections` }))
.describe(`Not supported for ${APP_CONNECTION_NAME_MAP[app]} Connections.`)
});
export const GenericUpdateAppConnectionFieldsSchema = (
app: AppConnection,
{ supportsPlatformManagedCredentials = false }: TAppConnectionBaseConfig = {}
{ supportsPlatformManagedCredentials = false, supportsGateways = false }: TAppConnectionBaseConfig = {}
) =>
z.object({
name: slugSchema({ field: "name" }).describe(AppConnections.UPDATE(app).name).optional(),
@@ -47,5 +58,16 @@ export const GenericUpdateAppConnectionFieldsSchema = (
.describe(AppConnections.UPDATE(app).description),
isPlatformManagedCredentials: supportsPlatformManagedCredentials
? z.boolean().optional().describe(AppConnections.UPDATE(app).isPlatformManagedCredentials)
: z.literal(false).optional().describe(`Not supported for ${APP_CONNECTION_NAME_MAP[app]} Connections.`)
: z
.literal(false, {
errorMap: () => ({ message: `Not supported for ${APP_CONNECTION_NAME_MAP[app]} Connections` })
})
.optional()
.describe(`Not supported for ${APP_CONNECTION_NAME_MAP[app]} Connections.`),
gatewayId: supportsGateways
? z.string().uuid().nullish().describe("The Gateway ID to use for this connection.")
: z
.undefined({ message: `Not supported for ${APP_CONNECTION_NAME_MAP[app]} Connections` })
.or(z.null({ message: `Not supported for ${APP_CONNECTION_NAME_MAP[app]} Connections` }))
.describe(`Not supported for ${APP_CONNECTION_NAME_MAP[app]} Connections.`)
});
@@ -3,8 +3,14 @@ import { ForbiddenError, subject } from "@casl/ability";
import { ValidateOCIConnectionCredentialsSchema } from "@app/ee/services/app-connections/oci";
import { ociConnectionService } from "@app/ee/services/app-connections/oci/oci-connection-service";
import { ValidateOracleDBConnectionCredentialsSchema } from "@app/ee/services/app-connections/oracledb";
import { TGatewayDALFactory } from "@app/ee/services/gateway/gateway-dal";
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionAppConnectionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import {
OrgPermissionAppConnectionActions,
OrgPermissionGatewayActions,
OrgPermissionSubjects
} from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { crypto } from "@app/lib/crypto/cryptography";
import { DatabaseErrorCode } from "@app/lib/error-codes";
@@ -73,6 +79,8 @@ import { humanitecConnectionService } from "./humanitec/humanitec-connection-ser
import { ValidateLdapConnectionCredentialsSchema } from "./ldap";
import { ValidateMsSqlConnectionCredentialsSchema } from "./mssql";
import { ValidateMySqlConnectionCredentialsSchema } from "./mysql";
import { ValidateOktaConnectionCredentialsSchema } from "./okta";
import { oktaConnectionService } from "./okta/okta-connection-service";
import { ValidatePostgresConnectionCredentialsSchema } from "./postgres";
import { ValidateRailwayConnectionCredentialsSchema } from "./railway";
import { railwayConnectionService } from "./railway/railway-connection-service";
@@ -96,6 +104,8 @@ export type TAppConnectionServiceFactoryDep = {
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">;
gatewayDAL: Pick<TGatewayDALFactory, "find">;
};
export type TAppConnectionServiceFactory = ReturnType<typeof appConnectionServiceFactory>;
@@ -134,14 +144,17 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
[AppConnection.Railway]: ValidateRailwayConnectionCredentialsSchema,
[AppConnection.Bitbucket]: ValidateBitbucketConnectionCredentialsSchema,
[AppConnection.Checkly]: ValidateChecklyConnectionCredentialsSchema,
[AppConnection.Supabase]: ValidateSupabaseConnectionCredentialsSchema
[AppConnection.Supabase]: ValidateSupabaseConnectionCredentialsSchema,
[AppConnection.Okta]: ValidateOktaConnectionCredentialsSchema
};
export const appConnectionServiceFactory = ({
appConnectionDAL,
permissionService,
kmsService,
licenseService
licenseService,
gatewayService,
gatewayDAL
}: TAppConnectionServiceFactoryDep) => {
const listAppConnectionsByOrg = async (actor: OrgServiceActor, app?: AppConnection) => {
const { permission } = await permissionService.getOrgPermission(
@@ -222,7 +235,7 @@ export const appConnectionServiceFactory = ({
};
const createAppConnection = async (
{ method, app, credentials, ...params }: TCreateAppConnectionDTO,
{ method, app, credentials, gatewayId, ...params }: TCreateAppConnectionDTO,
actor: OrgServiceActor
) => {
const { permission } = await permissionService.getOrgPermission(
@@ -238,6 +251,20 @@ export const appConnectionServiceFactory = ({
OrgPermissionSubjects.AppConnections
);
if (gatewayId) {
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionGatewayActions.AttachGateways,
OrgPermissionSubjects.Gateway
);
const [gateway] = await gatewayDAL.find({ id: gatewayId, orgId: actor.orgId });
if (!gateway) {
throw new NotFoundError({
message: `Gateway with ID ${gatewayId} not found for org`
});
}
}
await enterpriseAppCheck(
licenseService,
app,
@@ -245,12 +272,16 @@ export const appConnectionServiceFactory = ({
"Failed to create app connection due to plan restriction. Upgrade plan to access enterprise app connections."
);
const validatedCredentials = await validateAppConnectionCredentials({
app,
credentials,
method,
orgId: actor.orgId
} as TAppConnectionConfig);
const validatedCredentials = await validateAppConnectionCredentials(
{
app,
credentials,
method,
orgId: actor.orgId,
gatewayId
} as TAppConnectionConfig,
gatewayService
);
try {
const createConnection = async (connectionCredentials: TAppConnection["credentials"]) => {
@@ -265,6 +296,7 @@ export const appConnectionServiceFactory = ({
encryptedCredentials,
method,
app,
gatewayId,
...params
});
};
@@ -277,9 +309,11 @@ export const appConnectionServiceFactory = ({
app,
orgId: actor.orgId,
credentials: validatedCredentials,
method
method,
gatewayId
} as TAppConnectionConfig,
(platformCredentials) => createConnection(platformCredentials)
(platformCredentials) => createConnection(platformCredentials),
gatewayService
);
} else {
connection = await createConnection(validatedCredentials);
@@ -300,7 +334,7 @@ export const appConnectionServiceFactory = ({
};
const updateAppConnection = async (
{ connectionId, credentials, ...params }: TUpdateAppConnectionDTO,
{ connectionId, credentials, gatewayId, ...params }: TUpdateAppConnectionDTO,
actor: OrgServiceActor
) => {
const appConnection = await appConnectionDAL.findById(connectionId);
@@ -327,6 +361,22 @@ export const appConnectionServiceFactory = ({
OrgPermissionSubjects.AppConnections
);
if (gatewayId !== appConnection.gatewayId) {
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionGatewayActions.AttachGateways,
OrgPermissionSubjects.Gateway
);
if (gatewayId) {
const [gateway] = await gatewayDAL.find({ id: gatewayId, orgId: actor.orgId });
if (!gateway) {
throw new NotFoundError({
message: `Gateway with ID ${gatewayId} not found for org`
});
}
}
}
// prevent updating credentials or management status if platform managed
if (appConnection.isPlatformManagedCredentials && (params.isPlatformManagedCredentials === false || credentials)) {
throw new BadRequestError({
@@ -351,12 +401,16 @@ export const appConnectionServiceFactory = ({
} Connection with method ${getAppConnectionMethodName(method)}`
});
updatedCredentials = await validateAppConnectionCredentials({
app,
orgId: actor.orgId,
credentials,
method
} as TAppConnectionConfig);
updatedCredentials = await validateAppConnectionCredentials(
{
app,
orgId: actor.orgId,
credentials,
method,
gatewayId
} as TAppConnectionConfig,
gatewayService
);
if (!updatedCredentials)
throw new BadRequestError({ message: "Unable to validate connection - check credentials" });
@@ -375,6 +429,7 @@ export const appConnectionServiceFactory = ({
return appConnectionDAL.updateById(connectionId, {
orgId: actor.orgId,
encryptedCredentials,
gatewayId,
...params
});
};
@@ -391,9 +446,11 @@ export const appConnectionServiceFactory = ({
app,
orgId: actor.orgId,
credentials: updatedCredentials,
method
method,
gatewayId
} as TAppConnectionConfig,
(platformCredentials) => updateConnection(platformCredentials)
(platformCredentials) => updateConnection(platformCredentials),
gatewayService
);
} else {
updatedConnection = await updateConnection(updatedCredentials);
@@ -549,6 +606,7 @@ export const appConnectionServiceFactory = ({
railway: railwayConnectionService(connectAppConnectionById),
bitbucket: bitbucketConnectionService(connectAppConnectionById),
checkly: checklyConnectionService(connectAppConnectionById),
supabase: supabaseConnectionService(connectAppConnectionById)
supabase: supabaseConnectionService(connectAppConnectionById),
okta: oktaConnectionService(connectAppConnectionById)
};
};
@@ -9,6 +9,7 @@ import {
TOracleDBConnectionInput,
TValidateOracleDBConnectionCredentialsSchema
} from "@app/ee/services/app-connections/oracledb";
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
import { TSqlConnectionConfig } from "@app/services/app-connection/shared/sql/sql-connection-types";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
@@ -142,6 +143,12 @@ import {
} from "./ldap";
import { TMsSqlConnection, TMsSqlConnectionInput, TValidateMsSqlConnectionCredentialsSchema } from "./mssql";
import { TMySqlConnection, TMySqlConnectionInput, TValidateMySqlConnectionCredentialsSchema } from "./mysql";
import {
TOktaConnection,
TOktaConnectionConfig,
TOktaConnectionInput,
TValidateOktaConnectionCredentialsSchema
} from "./okta";
import {
TPostgresConnection,
TPostgresConnectionInput,
@@ -231,6 +238,7 @@ export type TAppConnection = { id: string } & (
| TRailwayConnection
| TChecklyConnection
| TSupabaseConnection
| TOktaConnection
);
export type TAppConnectionRaw = NonNullable<Awaited<ReturnType<TAppConnectionDALFactory["findById"]>>>;
@@ -272,6 +280,7 @@ export type TAppConnectionInput = { id: string } & (
| TRailwayConnectionInput
| TChecklyConnectionInput
| TSupabaseConnectionInput
| TOktaConnectionInput
);
export type TSqlConnectionInput =
@@ -282,7 +291,7 @@ export type TSqlConnectionInput =
export type TCreateAppConnectionDTO = Pick<
TAppConnectionInput,
"credentials" | "method" | "name" | "app" | "description" | "isPlatformManagedCredentials"
"credentials" | "method" | "name" | "app" | "description" | "isPlatformManagedCredentials" | "gatewayId"
>;
export type TUpdateAppConnectionDTO = Partial<Omit<TCreateAppConnectionDTO, "method" | "app">> & {
@@ -320,7 +329,8 @@ export type TAppConnectionConfig =
| TZabbixConnectionConfig
| TRailwayConnectionConfig
| TChecklyConnectionConfig
| TSupabaseConnectionConfig;
| TSupabaseConnectionConfig
| TOktaConnectionConfig;
export type TValidateAppConnectionCredentialsSchema =
| TValidateAwsConnectionCredentialsSchema
@@ -356,7 +366,8 @@ export type TValidateAppConnectionCredentialsSchema =
| TValidateZabbixConnectionCredentialsSchema
| TValidateRailwayConnectionCredentialsSchema
| TValidateChecklyConnectionCredentialsSchema
| TValidateSupabaseConnectionCredentialsSchema;
| TValidateSupabaseConnectionCredentialsSchema
| TValidateOktaConnectionCredentialsSchema;
export type TListAwsConnectionKmsKeys = {
connectionId: string;
@@ -369,14 +380,17 @@ export type TListAwsConnectionIamUsers = {
};
export type TAppConnectionCredentialsValidator = (
appConnection: TAppConnectionConfig
appConnection: TAppConnectionConfig,
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
) => Promise<TAppConnection["credentials"]>;
export type TAppConnectionTransitionCredentialsToPlatform = (
appConnection: TAppConnectionConfig,
callback: (credentials: TAppConnection["credentials"]) => Promise<TAppConnectionRaw>
callback: (credentials: TAppConnection["credentials"]) => Promise<TAppConnectionRaw>,
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
) => Promise<TAppConnectionRaw>;
export type TAppConnectionBaseConfig = {
supportsPlatformManagedCredentials?: boolean;
supportsGateways?: boolean;
};
@@ -9,6 +9,7 @@ import { BitbucketConnectionMethod } from "./bitbucket-connection-enums";
import {
TBitbucketConnection,
TBitbucketConnectionConfig,
TBitbucketEnvironment,
TBitbucketRepo,
TBitbucketWorkspace
} from "./bitbucket-connection-types";
@@ -21,11 +22,15 @@ export const getBitbucketConnectionListItem = () => {
};
};
export const createAuthHeader = (email: string, apiToken: string): string => {
return `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`;
};
export const getBitbucketUser = async ({ email, apiToken }: { email: string; apiToken: string }) => {
try {
const { data } = await request.get<{ username: string }>(`${IntegrationUrls.BITBUCKET_API_URL}/2.0/user`, {
headers: {
Authorization: `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`,
Authorization: createAuthHeader(email, apiToken),
Accept: "application/json"
}
});
@@ -57,7 +62,7 @@ export const listBitbucketWorkspaces = async (appConnection: TBitbucketConnectio
const { email, apiToken } = appConnection.credentials;
const headers = {
Authorization: `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`,
Authorization: createAuthHeader(email, apiToken),
Accept: "application/json"
};
@@ -89,7 +94,7 @@ export const listBitbucketRepositories = async (appConnection: TBitbucketConnect
const { email, apiToken } = appConnection.credentials;
const headers = {
Authorization: `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`,
Authorization: createAuthHeader(email, apiToken),
Accept: "application/json"
};
@@ -115,3 +120,43 @@ export const listBitbucketRepositories = async (appConnection: TBitbucketConnect
return allRepos;
};
export const listBitbucketEnvironments = async (
appConnection: TBitbucketConnection,
workspaceSlug: string,
repositorySlug: string
) => {
const { email, apiToken } = appConnection.credentials;
const headers = {
Authorization: createAuthHeader(email, apiToken),
Accept: "application/json"
};
const environments: TBitbucketEnvironment[] = [];
let hasNextPage = true;
let environmentsUrl = `${IntegrationUrls.BITBUCKET_API_URL}/2.0/repositories/${encodeURIComponent(workspaceSlug)}/${encodeURIComponent(repositorySlug)}/environments?pagelen=100`;
let iterationCount = 0;
// Limit to 10 iterations, fetching at most 10 * 100 = 1000 environments
while (hasNextPage && iterationCount < 10) {
// eslint-disable-next-line no-await-in-loop
const { data }: { data: { values: TBitbucketEnvironment[]; next: string } } = await request.get(environmentsUrl, {
headers
});
if (data?.values.length > 0) {
environments.push(...data.values);
}
if (data.next) {
environmentsUrl = data.next;
} else {
hasNextPage = false;
}
iterationCount += 1;
}
return environments;
};
@@ -1,8 +1,16 @@
import { OrgServiceActor } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums";
import { listBitbucketRepositories, listBitbucketWorkspaces } from "./bitbucket-connection-fns";
import { TBitbucketConnection, TGetBitbucketRepositoriesDTO } from "./bitbucket-connection-types";
import {
listBitbucketEnvironments,
listBitbucketRepositories,
listBitbucketWorkspaces
} from "./bitbucket-connection-fns";
import {
TBitbucketConnection,
TGetBitbucketEnvironmentsDTO,
TGetBitbucketRepositoriesDTO
} from "./bitbucket-connection-types";
type TGetAppConnectionFunc = (
app: AppConnection,
@@ -26,8 +34,18 @@ export const bitbucketConnectionService = (getAppConnection: TGetAppConnectionFu
return repositories;
};
const listEnvironments = async (
{ connectionId, workspaceSlug, repositorySlug }: TGetBitbucketEnvironmentsDTO,
actor: OrgServiceActor
) => {
const appConnection = await getAppConnection(AppConnection.Bitbucket, connectionId, actor);
const environments = await listBitbucketEnvironments(appConnection, workspaceSlug, repositorySlug);
return environments;
};
return {
listWorkspaces,
listRepositories
listRepositories,
listEnvironments
};
};
@@ -38,3 +38,20 @@ export type TBitbucketRepo = {
full_name: string; // workspace-slug/repo-slug
slug: string;
};
export type TGetBitbucketEnvironmentsDTO = {
connectionId: string;
workspaceSlug: string;
repositorySlug: string;
};
export type TBitbucketEnvironment = {
uuid: string;
slug: string;
name: string;
};
export type TBitbucketEnvironmentsResponse = {
values: TBitbucketEnvironment[];
next?: string;
};
@@ -9,6 +9,7 @@ import { getAppConnectionMethodName } from "@app/services/app-connection/app-con
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { AppConnection } from "../app-connection-enums";
import { GithubTokenRespData, isGithubErrorResponse } from "../github/github-connection-fns";
import { GitHubRadarConnectionMethod } from "./github-radar-connection-enums";
import {
TGitHubRadarConnection,
@@ -71,13 +72,6 @@ export const listGitHubRadarRepositories = async (appConnection: TGitHubRadarCon
return repositories;
};
type TokenRespData = {
access_token: string;
scope: string;
token_type: string;
error?: string;
};
export const validateGitHubRadarConnectionCredentials = async (config: TGitHubRadarConnectionConfig) => {
const { credentials, method } = config;
@@ -93,10 +87,10 @@ export const validateGitHubRadarConnectionCredentials = async (config: TGitHubRa
});
}
let tokenResp: AxiosResponse<TokenRespData>;
let tokenResp: AxiosResponse<GithubTokenRespData>;
try {
tokenResp = await request.get<TokenRespData>("https://github.com/login/oauth/access_token", {
tokenResp = await request.get<GithubTokenRespData>("https://github.com/login/oauth/access_token", {
params: {
client_id: INF_APP_CONNECTION_GITHUB_RADAR_APP_CLIENT_ID,
client_secret: INF_APP_CONNECTION_GITHUB_RADAR_APP_CLIENT_SECRET,
@@ -108,19 +102,27 @@ export const validateGitHubRadarConnectionCredentials = async (config: TGitHubRa
"Accept-Encoding": "application/json"
}
});
if (isGithubErrorResponse(tokenResp?.data)) {
throw new BadRequestError({
message: `Unable to validate credentials: GitHub responded with an error: ${tokenResp.data.error} - ${tokenResp.data.error_description}`
});
}
} catch (e: unknown) {
if (e instanceof BadRequestError) {
throw e;
}
throw new BadRequestError({
message: `Unable to validate connection: verify credentials`
});
}
if (tokenResp.status !== 200) {
throw new BadRequestError({
message: `Unable to validate credentials: GitHub responded with a status code of ${tokenResp.status} (${tokenResp.statusText}). Verify credentials and try again.`
});
}
if (method === GitHubRadarConnectionMethod.App) {
if (!tokenResp.data.access_token) {
throw new InternalServerError({ message: `Missing access token: ${tokenResp.data.error}` });
}
const installationsResp = await request.get<{
installations: {
id: number;
@@ -149,10 +151,6 @@ export const validateGitHubRadarConnectionCredentials = async (config: TGitHubRa
}
}
if (!tokenResp.data.access_token) {
throw new InternalServerError({ message: `Missing access token: ${tokenResp.data.error}` });
}
switch (method) {
case GitHubRadarConnectionMethod.App:
return {
@@ -144,14 +144,14 @@ export const getGitHubEnvironments = async (appConnection: TGitHubConnection, ow
}
};
type TokenRespData = {
export type GithubTokenRespData = {
access_token?: string;
scope: string;
token_type: string;
error?: string;
};
function isErrorResponse(data: TokenRespData): data is TokenRespData & {
export function isGithubErrorResponse(data: GithubTokenRespData): data is GithubTokenRespData & {
error: string;
error_description: string;
error_uri: string;
@@ -191,10 +191,10 @@ export const validateGitHubConnectionCredentials = async (config: TGitHubConnect
});
}
let tokenResp: AxiosResponse<TokenRespData>;
let tokenResp: AxiosResponse<GithubTokenRespData>;
try {
tokenResp = await request.get<TokenRespData>("https://github.com/login/oauth/access_token", {
tokenResp = await request.get<GithubTokenRespData>("https://github.com/login/oauth/access_token", {
params: {
client_id: clientId,
client_secret: clientSecret,
@@ -207,7 +207,7 @@ export const validateGitHubConnectionCredentials = async (config: TGitHubConnect
}
});
if (isErrorResponse(tokenResp?.data)) {
if (isGithubErrorResponse(tokenResp?.data)) {
throw new BadRequestError({
message: `Unable to validate credentials: GitHub responded with an error: ${tokenResp.data.error} - ${tokenResp.data.error_description}`
});
@@ -49,7 +49,10 @@ export const ValidateMsSqlConnectionCredentialsSchema = z.discriminatedUnion("me
]);
export const CreateMsSqlConnectionSchema = ValidateMsSqlConnectionCredentialsSchema.and(
GenericCreateAppConnectionFieldsSchema(AppConnection.MsSql, { supportsPlatformManagedCredentials: true })
GenericCreateAppConnectionFieldsSchema(AppConnection.MsSql, {
supportsPlatformManagedCredentials: true,
supportsGateways: true
})
);
export const UpdateMsSqlConnectionSchema = z
@@ -58,7 +61,12 @@ export const UpdateMsSqlConnectionSchema = z
AppConnections.UPDATE(AppConnection.MsSql).credentials
)
})
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.MsSql, { supportsPlatformManagedCredentials: true }));
.and(
GenericUpdateAppConnectionFieldsSchema(AppConnection.MsSql, {
supportsPlatformManagedCredentials: true,
supportsGateways: true
})
);
export const MsSqlConnectionListItemSchema = z.object({
name: z.literal("Microsoft SQL Server"),
@@ -47,7 +47,10 @@ export const ValidateMySqlConnectionCredentialsSchema = z.discriminatedUnion("me
]);
export const CreateMySqlConnectionSchema = ValidateMySqlConnectionCredentialsSchema.and(
GenericCreateAppConnectionFieldsSchema(AppConnection.MySql, { supportsPlatformManagedCredentials: true })
GenericCreateAppConnectionFieldsSchema(AppConnection.MySql, {
supportsPlatformManagedCredentials: true,
supportsGateways: true
})
);
export const UpdateMySqlConnectionSchema = z
@@ -56,7 +59,12 @@ export const UpdateMySqlConnectionSchema = z
AppConnections.UPDATE(AppConnection.MySql).credentials
)
})
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.MySql, { supportsPlatformManagedCredentials: true }));
.and(
GenericUpdateAppConnectionFieldsSchema(AppConnection.MySql, {
supportsPlatformManagedCredentials: true,
supportsGateways: true
})
);
export const MySqlConnectionListItemSchema = z.object({
name: z.literal("MySQL"),
@@ -0,0 +1,4 @@
export * from "./okta-connection-enums";
export * from "./okta-connection-fns";
export * from "./okta-connection-schemas";
export * from "./okta-connection-types";
@@ -0,0 +1,3 @@
export enum OktaConnectionMethod {
ApiToken = "api-token"
}
@@ -0,0 +1,57 @@
import { request } from "@app/lib/config/request";
import { UnauthorizedError } from "@app/lib/errors";
import { removeTrailingSlash } from "@app/lib/fn";
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { OktaConnectionMethod } from "./okta-connection-enums";
import { TOktaApp, TOktaConnection, TOktaConnectionConfig } from "./okta-connection-types";
export const getOktaConnectionListItem = () => {
return {
name: "Okta" as const,
app: AppConnection.Okta as const,
methods: Object.values(OktaConnectionMethod) as [OktaConnectionMethod.ApiToken]
};
};
export const getOktaInstanceUrl = async (config: TOktaConnectionConfig) => {
const instanceUrl = removeTrailingSlash(config.credentials.instanceUrl);
await blockLocalAndPrivateIpAddresses(instanceUrl);
return instanceUrl;
};
export const validateOktaConnectionCredentials = async (config: TOktaConnectionConfig) => {
const { apiToken } = config.credentials;
const instanceUrl = await getOktaInstanceUrl(config);
try {
await request.get(`${instanceUrl}/api/v1/users/me`, {
headers: {
Accept: "application/json",
Authorization: `SSWS ${apiToken}`
},
validateStatus: (status) => status === 200
});
} catch (error: unknown) {
throw new UnauthorizedError({
message: "Unable to validate connection: invalid credentials"
});
}
return config.credentials;
};
export const listOktaApps = async (appConnection: TOktaConnection) => {
const { apiToken } = appConnection.credentials;
const instanceUrl = await getOktaInstanceUrl(appConnection);
const { data } = await request.get<TOktaApp[]>(`${instanceUrl}/api/v1/apps`, {
headers: {
Accept: "application/json",
Authorization: `SSWS ${apiToken}`
}
});
return data.filter((app) => app.status === "ACTIVE" && app.name === "oidc_client");
};
@@ -0,0 +1,69 @@
import RE2 from "re2";
import z from "zod";
import { AppConnections } from "@app/lib/api-docs";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
BaseAppConnectionSchema,
GenericCreateAppConnectionFieldsSchema,
GenericUpdateAppConnectionFieldsSchema
} from "@app/services/app-connection/app-connection-schemas";
import { OktaConnectionMethod } from "./okta-connection-enums";
export const OktaConnectionApiTokenCredentialsSchema = z.object({
instanceUrl: z
.string()
.trim()
.url("Invalid Instance URL")
.min(1, "Instance URL required")
.max(255)
.describe(AppConnections.CREDENTIALS.OKTA.instanceUrl),
apiToken: z
.string()
.trim()
.min(1, "API Token required")
.refine((value) => new RE2("^00[a-zA-Z0-9_-]{40}$").test(value), "Invalid Okta API Token format")
.describe(AppConnections.CREDENTIALS.OKTA.apiToken)
});
const BaseOktaConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.Okta) });
export const OktaConnectionSchema = BaseOktaConnectionSchema.extend({
method: z.literal(OktaConnectionMethod.ApiToken),
credentials: OktaConnectionApiTokenCredentialsSchema
});
export const SanitizedOktaConnectionSchema = z.discriminatedUnion("method", [
BaseOktaConnectionSchema.extend({
method: z.literal(OktaConnectionMethod.ApiToken),
credentials: OktaConnectionApiTokenCredentialsSchema.pick({
instanceUrl: true
})
})
]);
export const ValidateOktaConnectionCredentialsSchema = z.discriminatedUnion("method", [
z.object({
method: z.literal(OktaConnectionMethod.ApiToken).describe(AppConnections.CREATE(AppConnection.Okta).method),
credentials: OktaConnectionApiTokenCredentialsSchema.describe(AppConnections.CREATE(AppConnection.Okta).credentials)
})
]);
export const CreateOktaConnectionSchema = ValidateOktaConnectionCredentialsSchema.and(
GenericCreateAppConnectionFieldsSchema(AppConnection.Okta)
);
export const UpdateOktaConnectionSchema = z
.object({
credentials: OktaConnectionApiTokenCredentialsSchema.optional().describe(
AppConnections.UPDATE(AppConnection.Okta).credentials
)
})
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Okta));
export const OktaConnectionListItemSchema = z.object({
name: z.literal("Okta"),
app: z.literal(AppConnection.Okta),
methods: z.nativeEnum(OktaConnectionMethod).array()
});
@@ -0,0 +1,23 @@
import { OrgServiceActor } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums";
import { listOktaApps } from "./okta-connection-fns";
import { TOktaConnection } from "./okta-connection-types";
type TGetAppConnectionFunc = (
app: AppConnection,
connectionId: string,
actor: OrgServiceActor
) => Promise<TOktaConnection>;
export const oktaConnectionService = (getAppConnection: TGetAppConnectionFunc) => {
const listApps = async (connectionId: string, actor: OrgServiceActor) => {
const appConnection = await getAppConnection(AppConnection.Okta, connectionId, actor);
const apps = await listOktaApps(appConnection);
return apps;
};
return {
listApps
};
};
@@ -0,0 +1,29 @@
import z from "zod";
import { DiscriminativePick } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums";
import {
CreateOktaConnectionSchema,
OktaConnectionSchema,
ValidateOktaConnectionCredentialsSchema
} from "./okta-connection-schemas";
export type TOktaConnection = z.infer<typeof OktaConnectionSchema>;
export type TOktaConnectionInput = z.infer<typeof CreateOktaConnectionSchema> & {
app: AppConnection.Okta;
};
export type TValidateOktaConnectionCredentialsSchema = typeof ValidateOktaConnectionCredentialsSchema;
export type TOktaConnectionConfig = DiscriminativePick<TOktaConnectionInput, "method" | "app" | "credentials"> & {
orgId: string;
};
export type TOktaApp = {
id: string;
label: string;
status: "ACTIVE" | "INACTIVE";
name: "oidc_client"; // "oidc_client" or other types
};
@@ -47,7 +47,10 @@ export const ValidatePostgresConnectionCredentialsSchema = z.discriminatedUnion(
]);
export const CreatePostgresConnectionSchema = ValidatePostgresConnectionCredentialsSchema.and(
GenericCreateAppConnectionFieldsSchema(AppConnection.Postgres, { supportsPlatformManagedCredentials: true })
GenericCreateAppConnectionFieldsSchema(AppConnection.Postgres, {
supportsPlatformManagedCredentials: true,
supportsGateways: true
})
);
export const UpdatePostgresConnectionSchema = z
@@ -56,7 +59,12 @@ export const UpdatePostgresConnectionSchema = z
AppConnections.UPDATE(AppConnection.Postgres).credentials
)
})
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Postgres, { supportsPlatformManagedCredentials: true }));
.and(
GenericUpdateAppConnectionFieldsSchema(AppConnection.Postgres, {
supportsPlatformManagedCredentials: true,
supportsGateways: true
})
);
export const PostgresConnectionListItemSchema = z.object({
name: z.literal("PostgreSQL"),
@@ -1,11 +1,13 @@
import knex, { Knex } from "knex";
import { verifyHostInputValidity } from "@app/ee/services/dynamic-secret/dynamic-secret-fns";
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
import {
TSqlCredentialsRotationGeneratedCredentials,
TSqlCredentialsRotationWithConnection
} from "@app/ee/services/secret-rotation-v2/shared/sql-credentials/sql-credentials-rotation-types";
import { BadRequestError, DatabaseError } from "@app/lib/errors";
import { GatewayProxyProtocol, withGatewayProxy } from "@app/lib/gateway";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { TAppConnectionRaw, TSqlConnection } from "@app/services/app-connection/app-connection-types";
@@ -98,25 +100,80 @@ export const getSqlConnectionClient = async (appConnection: Pick<TSqlConnection,
return client;
};
export const validateSqlConnectionCredentials = async (config: TSqlConnectionConfig) => {
const { credentials, app } = config;
export const executeWithPotentialGateway = async <T>(
config: TSqlConnectionConfig,
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
operation: (client: Knex) => Promise<T>
): Promise<T> => {
const { credentials, app, gatewayId } = config;
let client: Knex | undefined;
if (gatewayId && gatewayService) {
const [targetHost] = await verifyHostInputValidity(credentials.host, true);
const relayDetails = await gatewayService.fnGetGatewayClientTlsByGatewayId(gatewayId);
const [relayHost, relayPort] = relayDetails.relayAddress.split(":");
return withGatewayProxy(
async (proxyPort) => {
const client = knex({
client: SQL_CONNECTION_CLIENT_MAP[app],
connection: {
database: credentials.database,
port: proxyPort,
host: "localhost",
user: credentials.username,
password: credentials.password,
connectionTimeoutMillis: EXTERNAL_REQUEST_TIMEOUT,
...getConnectionConfig({ app, credentials })
}
});
try {
return await operation(client);
} finally {
await client.destroy();
}
},
{
protocol: GatewayProxyProtocol.Tcp,
targetHost,
targetPort: credentials.port,
relayHost,
relayPort: Number(relayPort),
identityId: relayDetails.identityId,
orgId: relayDetails.orgId,
tlsOptions: {
ca: relayDetails.certChain,
cert: relayDetails.certificate,
key: relayDetails.privateKey.toString()
}
}
);
}
// Non-gateway path
const client = await getSqlConnectionClient({ app, credentials });
try {
client = await getSqlConnectionClient({ app, credentials });
return await operation(client);
} finally {
await client.destroy();
}
};
await client.raw(`Select 1`);
return credentials;
export const validateSqlConnectionCredentials = async (
config: TSqlConnectionConfig,
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
) => {
try {
await executeWithPotentialGateway(config, gatewayService, async (client) => {
await client.raw(`Select 1`);
});
return config.credentials;
} catch (error) {
throw new BadRequestError({
message: `Unable to validate connection: ${
(error as Error)?.message?.replaceAll(credentials.password, "********************") ?? "verify credentials"
(error as Error)?.message?.replaceAll(config.credentials.password, "********************") ??
"verify credentials"
}`
});
} finally {
await client?.destroy();
}
};
@@ -132,22 +189,23 @@ export const SQL_CONNECTION_ALTER_LOGIN_STATEMENT: Record<
export const transferSqlConnectionCredentialsToPlatform = async (
config: TSqlConnectionConfig,
callback: (credentials: TSqlConnectionConfig["credentials"]) => Promise<TAppConnectionRaw>
callback: (credentials: TSqlConnectionConfig["credentials"]) => Promise<TAppConnectionRaw>,
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
) => {
const { credentials, app } = config;
const client = await getSqlConnectionClient({ app, credentials });
const newPassword = alphaNumericNanoId(32);
try {
return await client.transaction(async (tx) => {
await tx.raw(
...SQL_CONNECTION_ALTER_LOGIN_STATEMENT[app]({ username: credentials.username, password: newPassword })
);
return callback({
...credentials,
password: newPassword
return await executeWithPotentialGateway(config, gatewayService, (client) => {
return client.transaction(async (tx) => {
await tx.raw(
...SQL_CONNECTION_ALTER_LOGIN_STATEMENT[app]({ username: credentials.username, password: newPassword })
);
return callback({
...credentials,
password: newPassword
});
});
});
} catch (error) {
@@ -161,7 +219,5 @@ export const transferSqlConnectionCredentialsToPlatform = async (
(error as Error)?.message?.replaceAll(newPassword, "********************") ??
"Encountered an error transferring credentials to platform"
});
} finally {
await client.destroy();
}
};
@@ -1,6 +1,9 @@
import { DiscriminativePick } from "@app/lib/types";
import { TSqlConnectionInput } from "@app/services/app-connection/app-connection-types";
export type TSqlConnectionConfig = DiscriminativePick<TSqlConnectionInput, "method" | "app" | "credentials"> & {
export type TSqlConnectionConfig = DiscriminativePick<
TSqlConnectionInput,
"method" | "app" | "credentials" | "gatewayId"
> & {
orgId: string;
};
@@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { TableName } from "@app/db/schemas";
import { ActionProjectType, TableName } 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";
@@ -100,7 +100,8 @@ export const certificateAuthorityServiceFactory = ({
actorId: actor.id,
projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -167,7 +168,8 @@ export const certificateAuthorityServiceFactory = ({
actorId: actor.id,
projectId: certificateAuthority.projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -215,7 +217,8 @@ export const certificateAuthorityServiceFactory = ({
actorId: actor.id,
projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -268,7 +271,8 @@ export const certificateAuthorityServiceFactory = ({
actorId: actor.id,
projectId: certificateAuthority.projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -341,7 +345,8 @@ export const certificateAuthorityServiceFactory = ({
actorId: actor.id,
projectId: certificateAuthority.projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -4,7 +4,7 @@ import * as x509 from "@peculiar/x509";
import slugify from "@sindresorhus/slugify";
import { z } from "zod";
import { TableName, TCertificateAuthorities, TCertificateTemplates } from "@app/db/schemas";
import { ActionProjectType, TableName, TCertificateAuthorities, TCertificateTemplates } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import {
ProjectPermissionActions,
@@ -150,7 +150,8 @@ export const internalCertificateAuthorityServiceFactory = ({
actorId: dto.actorId,
projectId,
actorAuthMethod: dto.actorAuthMethod,
actorOrgId: dto.actorOrgId
actorOrgId: dto.actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -333,7 +334,8 @@ export const internalCertificateAuthorityServiceFactory = ({
actorId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
@@ -357,7 +359,8 @@ export const internalCertificateAuthorityServiceFactory = ({
actorId: dto.actorId,
projectId: ca.projectId,
actorAuthMethod: dto.actorAuthMethod,
actorOrgId: dto.actorOrgId
actorOrgId: dto.actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -389,7 +392,8 @@ export const internalCertificateAuthorityServiceFactory = ({
actorId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -414,7 +418,8 @@ export const internalCertificateAuthorityServiceFactory = ({
actorId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -477,7 +482,8 @@ export const internalCertificateAuthorityServiceFactory = ({
actorId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -763,7 +769,8 @@ export const internalCertificateAuthorityServiceFactory = ({
actorId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -799,7 +806,8 @@ export const internalCertificateAuthorityServiceFactory = ({
actorId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -879,7 +887,8 @@ export const internalCertificateAuthorityServiceFactory = ({
actorId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -1026,7 +1035,8 @@ export const internalCertificateAuthorityServiceFactory = ({
actorId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -1197,7 +1207,8 @@ export const internalCertificateAuthorityServiceFactory = ({
actorId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -1553,7 +1564,8 @@ export const internalCertificateAuthorityServiceFactory = ({
actorId: dto.actorId,
projectId: ca.projectId,
actorAuthMethod: dto.actorAuthMethod,
actorOrgId: dto.actorOrgId
actorOrgId: dto.actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -1920,7 +1932,8 @@ export const internalCertificateAuthorityServiceFactory = ({
actorId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
const certificateTemplates = await certificateTemplateDAL.find({ caId });
@@ -1,7 +1,7 @@
import { ForbiddenError, subject } from "@casl/ability";
import * as x509 from "@peculiar/x509";
import { TCertificateTemplateEstConfigsUpdate } from "@app/db/schemas";
import { ActionProjectType, TCertificateTemplateEstConfigsUpdate } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import {
@@ -76,7 +76,8 @@ export const certificateTemplateServiceFactory = ({
actorId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -137,7 +138,8 @@ export const certificateTemplateServiceFactory = ({
actorId,
projectId: certTemplate.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -201,7 +203,8 @@ export const certificateTemplateServiceFactory = ({
actorId,
projectId: certTemplate.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -227,7 +230,8 @@ export const certificateTemplateServiceFactory = ({
actorId,
projectId: certTemplate.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -268,7 +272,8 @@ export const certificateTemplateServiceFactory = ({
actorId,
projectId: certTemplate.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -350,7 +355,8 @@ export const certificateTemplateServiceFactory = ({
actorId,
projectId: certTemplate.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -429,7 +435,8 @@ export const certificateTemplateServiceFactory = ({
actorId: dto.actorId,
projectId: certTemplate.projectId,
actorAuthMethod: dto.actorAuthMethod,
actorOrgId: dto.actorOrgId
actorOrgId: dto.actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -1,6 +1,7 @@
import { ForbiddenError } from "@casl/ability";
import * as x509 from "@peculiar/x509";
import { ActionProjectType } from "@app/db/schemas";
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import {
@@ -79,7 +80,8 @@ export const certificateServiceFactory = ({
actorId,
projectId: cert.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -109,7 +111,8 @@ export const certificateServiceFactory = ({
actorId,
projectId: cert.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -142,7 +145,8 @@ export const certificateServiceFactory = ({
actorId,
projectId: cert.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -191,7 +195,8 @@ export const certificateServiceFactory = ({
actorId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -239,7 +244,8 @@ export const certificateServiceFactory = ({
actorId,
projectId: cert.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -319,7 +325,8 @@ export const certificateServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -523,7 +530,8 @@ export const certificateServiceFactory = ({
actorId,
projectId: cert.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
+25 -12
View File
@@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionCmekActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { SigningAlgorithm } from "@app/lib/crypto/sign";
@@ -38,7 +39,8 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService }: TC
actorId: actor.id,
projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.KMS
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Create, ProjectPermissionSub.Cmek);
@@ -77,7 +79,8 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService }: TC
actorId: actor.id,
projectId: key.projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.KMS
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Edit, ProjectPermissionSub.Cmek);
@@ -113,7 +116,8 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService }: TC
actorId: actor.id,
projectId: key.projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.KMS
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Delete, ProjectPermissionSub.Cmek);
@@ -129,7 +133,8 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService }: TC
actorId: actor.id,
projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.KMS
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
@@ -151,7 +156,8 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService }: TC
actorId: actor.id,
projectId: key.projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.KMS
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
@@ -172,7 +178,8 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService }: TC
actorId: actor.id,
projectId: key.projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.KMS
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
@@ -194,7 +201,8 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService }: TC
actorId: actor.id,
projectId: key.projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.KMS
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Encrypt, ProjectPermissionSub.Cmek);
@@ -221,7 +229,8 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService }: TC
actorId: actor.id,
projectId: key.projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.KMS
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
@@ -268,7 +277,8 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService }: TC
actorId: actor.id,
projectId: key.projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.KMS
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
@@ -291,7 +301,8 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService }: TC
actorId: actor.id,
projectId: key.projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.KMS
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Sign, ProjectPermissionSub.Cmek);
@@ -325,7 +336,8 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService }: TC
actorId: actor.id,
projectId: key.projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.KMS
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Verify, ProjectPermissionSub.Cmek);
@@ -360,7 +372,8 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService }: TC
actorId: actor.id,
projectId: key.projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.KMS
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Decrypt, ProjectPermissionSub.Cmek);
@@ -2,7 +2,13 @@
import { ForbiddenError } from "@casl/ability";
import { Knex } from "knex";
import { TSecretFolders, TSecretFolderVersions, TSecretV2TagJunctionInsert, TSecretVersionsV2 } from "@app/db/schemas";
import {
ActionProjectType,
TSecretFolders,
TSecretFolderVersions,
TSecretV2TagJunctionInsert,
TSecretVersionsV2
} from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionCommitsActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { getConfig } from "@app/lib/config/env";
@@ -47,6 +53,14 @@ export enum ResourceType {
FOLDER = "folder"
}
export type TCommitResourceChangeDTO = {
type: string;
secretVersionId?: string;
folderVersionId?: string;
isUpdate?: boolean;
folderId?: string;
};
type TCreateCommitDTO = {
actor: {
type: string;
@@ -57,13 +71,7 @@ type TCreateCommitDTO = {
};
message?: string;
folderId: string;
changes: {
type: string;
secretVersionId?: string;
folderVersionId?: string;
isUpdate?: boolean;
folderId?: string;
}[];
changes: TCommitResourceChangeDTO[];
omitIgnoreFilter?: boolean;
};
@@ -217,7 +225,8 @@ export const folderCommitServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCommitsActions.Read, ProjectPermissionSub.Commits);
@@ -2060,7 +2069,8 @@ export const folderCommitServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { ProjectMembershipRole, SecretKeyEncoding, TGroups } from "@app/db/schemas";
import { ActionProjectType, ProjectMembershipRole, SecretKeyEncoding, TGroups } from "@app/db/schemas";
import { TListProjectGroupUsersDTO } from "@app/ee/services/group/group-types";
import {
constructPermissionErrorMessage,
@@ -78,7 +78,8 @@ export const groupProjectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionGroupActions.Create, ProjectPermissionSub.Groups);
@@ -271,7 +272,8 @@ export const groupProjectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionGroupActions.Edit, ProjectPermissionSub.Groups);
@@ -384,7 +386,8 @@ export const groupProjectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionGroupActions.Delete, ProjectPermissionSub.Groups);
@@ -428,7 +431,8 @@ export const groupProjectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionGroupActions.Read, ProjectPermissionSub.Groups);
@@ -455,7 +459,8 @@ export const groupProjectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionGroupActions.Read, ProjectPermissionSub.Groups);
@@ -496,7 +501,8 @@ export const groupProjectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionGroupActions.Read, ProjectPermissionSub.Groups);
@@ -37,7 +37,7 @@ export const validateAccountIds = z
export const validatePrincipalArns = z
.string()
.trim()
.max(2048)
.max(4096)
.default("")
// Custom validation for ARN format
.refine(
@@ -1,6 +1,6 @@
import { ForbiddenError, subject } from "@casl/ability";
import { ProjectMembershipRole } from "@app/db/schemas";
import { ActionProjectType, ProjectMembershipRole } from "@app/db/schemas";
import {
constructPermissionErrorMessage,
validatePrivilegeChangeOperation
@@ -62,7 +62,8 @@ export const identityProjectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Create,
@@ -181,7 +182,8 @@ export const identityProjectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Edit,
@@ -291,7 +293,8 @@ export const identityProjectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Delete,
@@ -319,7 +322,8 @@ export const identityProjectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Read,
@@ -352,7 +356,8 @@ export const identityProjectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -388,7 +393,8 @@ export const identityProjectServiceFactory = ({
actorId,
projectId: membership.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -4,7 +4,13 @@ import { Octokit } from "@octokit/rest";
import { Client as OctopusClient, SpaceRepository as OctopusSpaceRepository } from "@octopusdeploy/api-client";
import AWS from "aws-sdk";
import { SecretEncryptionAlgo, SecretKeyEncoding, TIntegrationAuths, TIntegrationAuthsInsert } from "@app/db/schemas";
import {
ActionProjectType,
SecretEncryptionAlgo,
SecretKeyEncoding,
TIntegrationAuths,
TIntegrationAuthsInsert
} 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 { getConfig } from "@app/lib/config/env";
@@ -97,7 +103,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const authorizations = await integrationAuthDAL.find({ projectId });
@@ -115,7 +122,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId: auth.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
return permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations) ? auth : null;
@@ -138,7 +146,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
return integrationAuth;
@@ -163,7 +172,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
@@ -281,7 +291,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
@@ -435,7 +446,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations);
@@ -732,7 +744,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
@@ -766,7 +779,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
@@ -796,7 +810,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
@@ -837,7 +852,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
@@ -865,7 +881,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
@@ -939,7 +956,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
@@ -986,7 +1004,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
@@ -1020,7 +1039,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
@@ -1078,7 +1098,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
@@ -1114,7 +1135,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
@@ -1155,7 +1177,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
@@ -1195,7 +1218,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
@@ -1235,7 +1259,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
@@ -1274,7 +1299,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
@@ -1314,7 +1340,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
@@ -1382,7 +1409,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
@@ -1456,7 +1484,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
@@ -1506,7 +1535,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
@@ -1554,7 +1584,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
@@ -1622,7 +1653,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
@@ -1663,7 +1695,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
@@ -1775,7 +1808,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations);
@@ -1798,7 +1832,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations);
@@ -1831,7 +1866,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(sourcePermission).throwUnlessCan(
@@ -1844,7 +1880,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(targetPermission).throwUnlessCan(
@@ -1877,7 +1914,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
@@ -1911,7 +1949,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
@@ -1951,7 +1990,8 @@ export const integrationAuthServiceFactory = ({
actorId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
@@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { throwIfMissingSecretReadValueOrDescribePermission } from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import {
@@ -90,7 +91,8 @@ export const integrationServiceFactory = ({
actorId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
@@ -165,7 +167,8 @@ export const integrationServiceFactory = ({
actorId,
projectId: integration.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations);
@@ -228,7 +231,8 @@ export const integrationServiceFactory = ({
actorId,
projectId: integration.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
@@ -255,7 +259,8 @@ export const integrationServiceFactory = ({
actorId,
projectId: integration.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
@@ -297,7 +302,8 @@ export const integrationServiceFactory = ({
actorId,
projectId: integration.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations);
@@ -333,7 +339,8 @@ export const integrationServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
@@ -352,7 +359,8 @@ export const integrationServiceFactory = ({
actorId,
projectId: integration.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
+3 -1
View File
@@ -3,6 +3,7 @@ import slugify from "@sindresorhus/slugify";
import { Knex } from "knex";
import {
ActionProjectType,
OrgMembershipRole,
OrgMembershipStatus,
ProjectMembershipRole,
@@ -981,7 +982,8 @@ export const orgServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(projectPermission).throwUnlessCan(
ProjectPermissionMemberActions.Create,
@@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } 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 { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
@@ -78,7 +79,8 @@ export const pkiAlertServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.PkiAlerts);
@@ -107,7 +109,8 @@ export const pkiAlertServiceFactory = ({
actorId,
projectId: alert.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.PkiAlerts);
@@ -133,7 +136,8 @@ export const pkiAlertServiceFactory = ({
actorId,
projectId: alert.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.PkiAlerts);
@@ -165,7 +169,8 @@ export const pkiAlertServiceFactory = ({
actorId,
projectId: alert.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.PkiAlerts);
@@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { TPkiCollectionItems } from "@app/db/schemas";
import { ActionProjectType, TPkiCollectionItems } 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";
@@ -55,7 +55,8 @@ export const pkiCollectionServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -87,7 +88,8 @@ export const pkiCollectionServiceFactory = ({
actorId,
projectId: pkiCollection.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.PkiCollections);
@@ -111,7 +113,8 @@ export const pkiCollectionServiceFactory = ({
actorId,
projectId: pkiCollection.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.PkiCollections);
@@ -138,7 +141,8 @@ export const pkiCollectionServiceFactory = ({
actorId,
projectId: pkiCollection.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -167,7 +171,8 @@ export const pkiCollectionServiceFactory = ({
actorId,
projectId: pkiCollection.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.PkiCollections);
@@ -210,7 +215,8 @@ export const pkiCollectionServiceFactory = ({
actorId,
projectId: pkiCollection.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -297,7 +303,8 @@ export const pkiCollectionServiceFactory = ({
actorId,
projectId: pkiCollection.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -2,6 +2,7 @@
import { ForbiddenError, subject } from "@casl/ability";
import * as x509 from "@peculiar/x509";
import { ActionProjectType } from "@app/db/schemas";
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import {
@@ -119,7 +120,8 @@ export const pkiSubscriberServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -181,7 +183,8 @@ export const pkiSubscriberServiceFactory = ({
actorId,
projectId: subscriber.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -234,7 +237,8 @@ export const pkiSubscriberServiceFactory = ({
actorId,
projectId: subscriber.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -296,7 +300,8 @@ export const pkiSubscriberServiceFactory = ({
actorId,
projectId: subscriber.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -332,7 +337,8 @@ export const pkiSubscriberServiceFactory = ({
actorId,
projectId: subscriber.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -387,7 +393,8 @@ export const pkiSubscriberServiceFactory = ({
actorId,
projectId: subscriber.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -433,7 +440,8 @@ export const pkiSubscriberServiceFactory = ({
actorId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -691,7 +699,8 @@ export const pkiSubscriberServiceFactory = ({
actorId,
projectId: subscriber.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -738,7 +747,8 @@ export const pkiSubscriberServiceFactory = ({
actorId,
projectId: subscriber.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -3,6 +3,7 @@ import { ForbiddenError, subject } from "@casl/ability";
import * as x509 from "@peculiar/x509";
import RE2 from "re2";
import { ActionProjectType } from "@app/db/schemas";
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import {
@@ -118,7 +119,8 @@ export const pkiTemplatesServiceFactory = ({
actorId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -170,7 +172,8 @@ export const pkiTemplatesServiceFactory = ({
actorId,
projectId: certTemplate.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -233,7 +236,8 @@ export const pkiTemplatesServiceFactory = ({
actorId,
projectId: certTemplate.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -265,7 +269,8 @@ export const pkiTemplatesServiceFactory = ({
actorId,
projectId: certTemplate.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -290,7 +295,8 @@ export const pkiTemplatesServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
const certTemplate = await pkiTemplatesDAL.find({ projectId }, { limit, offset, count: true });
@@ -332,7 +338,8 @@ export const pkiTemplatesServiceFactory = ({
actorId,
projectId: certTemplate.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -378,7 +385,8 @@ export const pkiTemplatesServiceFactory = ({
actorId,
projectId: certTemplate.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { ProjectVersion } from "@app/db/schemas";
import { ActionProjectType, ProjectVersion } 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 { crypto } from "@app/lib/crypto/cryptography";
@@ -45,7 +45,8 @@ export const projectBotServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
@@ -115,7 +116,8 @@ export const projectBotServiceFactory = ({
actorId,
projectId: bot.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations);
@@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
@@ -46,7 +47,8 @@ export const projectEnvServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Environments);
@@ -134,7 +136,8 @@ export const projectEnvServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Environments);
@@ -197,7 +200,8 @@ export const projectEnvServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Environments);
@@ -252,7 +256,8 @@ export const projectEnvServiceFactory = ({
actorId,
projectId: environment.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Environments);
@@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionMemberActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError } from "@app/lib/errors";
@@ -36,7 +37,8 @@ export const projectKeyServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
@@ -65,7 +67,8 @@ export const projectKeyServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
const latestKey = await projectKeyDAL.findLatestProjectKey(actorId, projectId);
return latestKey;
@@ -83,7 +86,8 @@ export const projectKeyServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
return projectKeyDAL.findAllProjectUserPubKeys(projectId);
@@ -1,7 +1,7 @@
/* eslint-disable no-await-in-loop */
import { ForbiddenError } from "@casl/ability";
import { ProjectMembershipRole, ProjectVersion, TableName } from "@app/db/schemas";
import { ActionProjectType, ProjectMembershipRole, ProjectVersion, TableName } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import {
constructPermissionErrorMessage,
@@ -90,7 +90,8 @@ export const projectMembershipServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
@@ -133,7 +134,8 @@ export const projectMembershipServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
@@ -155,7 +157,8 @@ export const projectMembershipServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
@@ -181,7 +184,8 @@ export const projectMembershipServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Create, ProjectPermissionSub.Member);
const orgMembers = await orgDAL.findMembership({
@@ -261,7 +265,8 @@ export const projectMembershipServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
@@ -370,7 +375,8 @@ export const projectMembershipServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Delete, ProjectPermissionSub.Member);
@@ -412,7 +418,8 @@ export const projectMembershipServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Delete, ProjectPermissionSub.Member);
@@ -11,7 +11,7 @@ import {
} from "@app/ee/services/permission/default-roles";
import { TGetPredefinedRolesDTO } from "@app/services/project-role/project-role-types";
export const getPredefinedRoles = ({ projectId, roleFilter }: TGetPredefinedRolesDTO) => {
export const getPredefinedRoles = ({ projectId, projectType, roleFilter }: TGetPredefinedRolesDTO) => {
return [
{
id: uuidv4(),
@@ -75,5 +75,5 @@ export const getPredefinedRoles = ({ projectId, roleFilter }: TGetPredefinedRole
createdAt: new Date(),
updatedAt: new Date()
}
].filter(({ slug }) => !roleFilter || roleFilter === slug);
].filter(({ slug, type }) => (type ? type === projectType : true) && (!roleFilter || roleFilter === slug));
};
@@ -2,7 +2,7 @@ import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
import { requestContext } from "@fastify/request-context";
import { ProjectMembershipRole, TableName, TProjects } from "@app/db/schemas";
import { ActionProjectType, ProjectMembershipRole, ProjectType, TableName, TProjects } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import {
ProjectPermissionActions,
@@ -71,7 +71,8 @@ export const projectRoleServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Role);
const existingRole = await projectRoleDAL.findOne({ slug: data.slug, projectId });
@@ -111,12 +112,14 @@ export const projectRoleServiceFactory = ({
actorId,
projectId: project.id,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
if (roleSlug !== "custom" && Object.values(ProjectMembershipRole).includes(roleSlug as ProjectMembershipRole)) {
const [predefinedRole] = getPredefinedRoles({
projectId: project.id,
projectType: project.type as ProjectType,
roleFilter: roleSlug as ProjectMembershipRole
});
@@ -139,7 +142,8 @@ export const projectRoleServiceFactory = ({
actorId,
projectId: projectRole.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Role);
@@ -169,7 +173,8 @@ export const projectRoleServiceFactory = ({
actorId,
projectId: projectRole.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Role);
@@ -210,14 +215,18 @@ export const projectRoleServiceFactory = ({
actorId,
projectId: project.id,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
const customRoles = await projectRoleDAL.find(
{ projectId: project.id },
{ sort: [[`${TableName.ProjectRoles}.slug` as "slug", "asc"]] }
);
const roles = [...getPredefinedRoles({ projectId: project.id }), ...(customRoles || [])];
const roles = [
...getPredefinedRoles({ projectId: project.id, projectType: project.type as ProjectType }),
...(customRoles || [])
];
return roles;
};
@@ -233,7 +242,8 @@ export const projectRoleServiceFactory = ({
actorId: userId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
// just to satisfy ts
if (!("roles" in membership)) throw new BadRequestError({ message: "Service token not allowed" });
@@ -1,4 +1,4 @@
import { ProjectMembershipRole, TOrgRolesUpdate, TProjectRolesInsert } from "@app/db/schemas";
import { ProjectMembershipRole, ProjectType, TOrgRolesUpdate, TProjectRolesInsert } from "@app/db/schemas";
import { TProjectPermission } from "@app/lib/types";
export enum ProjectRoleServiceIdentifierType {
@@ -37,5 +37,6 @@ export type TListRolesDTO = {
export type TGetPredefinedRolesDTO = {
projectId: string;
projectType: ProjectType;
roleFilter?: ProjectMembershipRole;
};
+28 -3
View File
@@ -3,6 +3,7 @@ import { Knex } from "knex";
import { TDbClient } from "@app/db";
import {
ProjectsSchema,
ProjectType,
ProjectUpgradeStatus,
ProjectVersion,
SortDirection,
@@ -21,12 +22,17 @@ export type TProjectDALFactory = ReturnType<typeof projectDALFactory>;
export const projectDALFactory = (db: TDbClient) => {
const projectOrm = ormify(db, TableName.Project);
const findIdentityProjects = async (identityId: string, orgId: string) => {
const findIdentityProjects = async (identityId: string, orgId: string, projectType?: ProjectType) => {
try {
const workspaces = await db(TableName.IdentityProjectMembership)
.where({ identityId })
.join(TableName.Project, `${TableName.IdentityProjectMembership}.projectId`, `${TableName.Project}.id`)
.where(`${TableName.Project}.orgId`, orgId)
.andWhere((qb) => {
if (projectType) {
void qb.where(`${TableName.Project}.type`, projectType);
}
})
.leftJoin(TableName.Environment, `${TableName.Environment}.projectId`, `${TableName.Project}.id`)
.select(
selectAllTableCols(TableName.Project),
@@ -66,13 +72,18 @@ export const projectDALFactory = (db: TDbClient) => {
}
};
const findUserProjects = async (userId: string, orgId: string) => {
const findUserProjects = async (userId: string, orgId: string, projectType?: ProjectType) => {
try {
const workspaces = await db
.replicaNode()(TableName.ProjectMembership)
.where({ userId })
.join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`)
.where(`${TableName.Project}.orgId`, orgId)
.andWhere((qb) => {
if (projectType) {
void qb.where(`${TableName.Project}.type`, projectType);
}
})
.leftJoin(TableName.Environment, `${TableName.Environment}.projectId`, `${TableName.Project}.id`)
.select(
selectAllTableCols(TableName.Project),
@@ -92,6 +103,11 @@ export const projectDALFactory = (db: TDbClient) => {
.whereIn("groupId", groups)
.join(TableName.Project, `${TableName.GroupProjectMembership}.projectId`, `${TableName.Project}.id`)
.where(`${TableName.Project}.orgId`, orgId)
.andWhere((qb) => {
if (projectType) {
void qb.where(`${TableName.Project}.type`, projectType);
}
})
.whereNotIn(
`${TableName.Project}.id`,
workspaces.map(({ id }) => id)
@@ -161,12 +177,17 @@ export const projectDALFactory = (db: TDbClient) => {
}
};
const findAllProjectsByIdentity = async (identityId: string) => {
const findAllProjectsByIdentity = async (identityId: string, projectType?: ProjectType) => {
try {
const workspaces = await db
.replicaNode()(TableName.IdentityProjectMembership)
.where({ identityId })
.join(TableName.Project, `${TableName.IdentityProjectMembership}.projectId`, `${TableName.Project}.id`)
.andWhere((qb) => {
if (projectType) {
void qb.where(`${TableName.Project}.type`, projectType);
}
})
.leftJoin(TableName.Environment, `${TableName.Environment}.projectId`, `${TableName.Project}.id`)
.select(
selectAllTableCols(TableName.Project),
@@ -372,6 +393,7 @@ export const projectDALFactory = (db: TDbClient) => {
orgId: string;
actor: ActorType;
actorId: string;
type?: ProjectType;
limit?: number;
offset?: number;
name?: string;
@@ -426,6 +448,9 @@ export const projectDALFactory = (db: TDbClient) => {
void query.orderBy([{ column: `${TableName.Project}.name`, order: sortDir }]);
}
if (dto.type) {
void query.where(`${TableName.Project}.type`, dto.type);
}
if (dto.name) {
void query.whereILike(`${TableName.Project}.name`, `%${dto.name}%`);
}
+106 -48
View File
@@ -1,7 +1,14 @@
import { ForbiddenError, subject } from "@casl/ability";
import slugify from "@sindresorhus/slugify";
import { ProjectMembershipRole, ProjectVersion, TableName, TProjectEnvironments } from "@app/db/schemas";
import {
ActionProjectType,
ProjectMembershipRole,
ProjectType,
ProjectVersion,
TableName,
TProjectEnvironments
} from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { throwIfMissingSecretReadValueOrDescribePermission } from "@app/ee/services/permission/permission-fns";
@@ -242,7 +249,8 @@ export const projectServiceFactory = ({
kmsKeyId,
tx: trx,
createDefaultEnvs = true,
template = InfisicalProjectTemplate.Default
template = InfisicalProjectTemplate.Default,
type = ProjectType.SecretManager
}: TCreateProjectDTO) => {
const organization = await orgDAL.findOne({ id: actorOrgId });
const { permission, membership: orgMembership } = await permissionService.getOrgPermission(
@@ -258,7 +266,11 @@ export const projectServiceFactory = ({
await tx.raw("SELECT pg_advisory_xact_lock(?)", [PgSqlLock.CreateProject(organization.id)]);
const plan = await licenseService.getPlan(organization.id);
if (plan.workspaceLimit !== null && plan.workspacesUsed >= plan.workspaceLimit) {
if (
plan.workspaceLimit !== null &&
plan.workspacesUsed >= plan.workspaceLimit &&
type === ProjectType.SecretManager
) {
// case: limit imposed on number of workspaces allowed
// case: number of workspaces used exceeds the number of workspaces allowed
throw new BadRequestError({
@@ -295,6 +307,7 @@ export const projectServiceFactory = ({
const project = await projectDAL.create(
{
name: workspaceName,
type,
description: workspaceDescription,
orgId: organization.id,
slug: projectSlug || slugify(`${workspaceName}-${alphaNumericNanoId(4)}`),
@@ -305,14 +318,16 @@ export const projectServiceFactory = ({
tx
);
await bootstrapSshProject({
projectId: project.id,
sshCertificateAuthorityDAL,
sshCertificateAuthoritySecretDAL,
kmsService,
projectSshConfigDAL,
tx
});
if (type === ProjectType.SSH) {
await bootstrapSshProject({
projectId: project.id,
sshCertificateAuthorityDAL,
sshCertificateAuthoritySecretDAL,
kmsService,
projectSshConfigDAL,
tx
});
}
// set ghost user as admin of project
const projectMembership = await projectMembershipDAL.create(
@@ -512,7 +527,8 @@ export const projectServiceFactory = ({
actorId,
projectId: project.id,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Project);
@@ -570,11 +586,11 @@ export const projectServiceFactory = ({
return deletedProject;
};
const getProjects = async ({ actorId, actor, includeRoles, actorAuthMethod, actorOrgId }: TListProjectsDTO) => {
const getProjects = async ({ actorId, actor, includeRoles, actorAuthMethod, actorOrgId, type }: TListProjectsDTO) => {
const workspaces =
actor === ActorType.IDENTITY
? await projectDAL.findIdentityProjects(actorId, actorOrgId)
: await projectDAL.findUserProjects(actorId, actorOrgId);
? await projectDAL.findIdentityProjects(actorId, actorOrgId, type)
: await projectDAL.findUserProjects(actorId, actorOrgId, type);
if (includeRoles) {
const { permission } = await permissionService.getUserOrgPermission(
@@ -598,7 +614,10 @@ export const projectServiceFactory = ({
workspaces.map(async (workspace) => {
return {
...workspace,
roles: [...(workspaceMappedToRoles[workspace.id] || []), ...getPredefinedRoles({ projectId: workspace.id })]
roles: [
...(workspaceMappedToRoles[workspace.id] || []),
...getPredefinedRoles({ projectId: workspace.id, projectType: workspace.type as ProjectType })
]
};
})
);
@@ -617,7 +636,8 @@ export const projectServiceFactory = ({
actorId,
projectId: project.id,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
return project;
};
@@ -630,7 +650,8 @@ export const projectServiceFactory = ({
actorId,
projectId: project.id,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings);
@@ -674,7 +695,8 @@ export const projectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings);
@@ -699,7 +721,8 @@ export const projectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings);
@@ -728,7 +751,8 @@ export const projectServiceFactory = ({
actorId,
projectId: project.id,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
if (!hasRole(ProjectMembershipRole.Admin))
@@ -759,7 +783,8 @@ export const projectServiceFactory = ({
actorId,
projectId: project.id,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
if (!hasRole(ProjectMembershipRole.Admin)) {
@@ -791,7 +816,8 @@ export const projectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings);
@@ -812,7 +838,8 @@ export const projectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Project);
@@ -882,7 +909,8 @@ export const projectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret);
@@ -920,7 +948,8 @@ export const projectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -966,7 +995,8 @@ export const projectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -1010,7 +1040,8 @@ export const projectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.PkiAlerts);
@@ -1037,7 +1068,8 @@ export const projectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.PkiCollections);
@@ -1064,7 +1096,8 @@ export const projectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
const allowedSubscribers = [];
@@ -1102,7 +1135,8 @@ export const projectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
const certificateTemplates = await certificateTemplateDAL.getCertTemplatesByProjectId(projectId);
@@ -1132,7 +1166,8 @@ export const projectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -1165,7 +1200,8 @@ export const projectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
const allowedHosts = [];
@@ -1204,7 +1240,8 @@ export const projectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SshHostGroups);
@@ -1231,7 +1268,8 @@ export const projectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificates);
@@ -1269,7 +1307,8 @@ export const projectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -1303,7 +1342,8 @@ export const projectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Kms);
@@ -1330,7 +1370,8 @@ export const projectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Kms);
@@ -1359,7 +1400,8 @@ export const projectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Kms);
@@ -1381,7 +1423,8 @@ export const projectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
if (!membership) {
@@ -1413,7 +1456,8 @@ export const projectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Settings);
@@ -1452,7 +1496,8 @@ export const projectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings);
@@ -1535,7 +1580,8 @@ export const projectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Settings);
@@ -1607,7 +1653,8 @@ export const projectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings);
@@ -1684,7 +1731,8 @@ export const projectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings);
@@ -1807,7 +1855,8 @@ export const projectServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Settings);
@@ -1835,7 +1884,15 @@ export const projectServiceFactory = ({
});
};
const searchProjects = async ({ name, offset, permission, limit, orderBy, orderDirection }: TSearchProjectsDTO) => {
const searchProjects = async ({
name,
offset,
permission,
limit,
type,
orderBy,
orderDirection
}: TSearchProjectsDTO) => {
// check user belong to org
await permissionService.getOrgPermission(
permission.type,
@@ -1849,6 +1906,7 @@ export const projectServiceFactory = ({
limit,
offset,
name,
type,
orgId: permission.orgId,
actor: permission.type,
actorId: permission.id,
@@ -1872,7 +1930,7 @@ export const projectServiceFactory = ({
actor: permission.type,
actorId: permission.id,
projectId,
actionProjectType: ActionProjectType.Any,
actorAuthMethod: permission.authMethod,
actorOrgId: permission.orgId
})
@@ -108,7 +108,7 @@ export type TDeleteProjectDTO = {
export type TListProjectsDTO = {
includeRoles: boolean;
type?: ProjectType | "all";
type?: ProjectType;
} & Omit<TProjectPermission, "projectId">;
export type TUpgradeProjectDTO = {
@@ -1,4 +1,4 @@
import { ProjectMembershipRole } from "@app/db/schemas";
import { ActionProjectType, ProjectMembershipRole } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
@@ -36,7 +36,8 @@ export const secretBlindIndexServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const secretCount = await secretBlindIndexDAL.countOfSecretsWithNullSecretBlindIndex(projectId);
@@ -55,7 +56,8 @@ export const secretBlindIndexServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (!hasRole(ProjectMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Insufficient privileges, user must be admin" });
@@ -78,7 +80,8 @@ export const secretBlindIndexServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (!hasRole(ProjectMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Insufficient privileges, user must be admin" });
@@ -1,8 +1,10 @@
/* eslint-disable no-await-in-loop */
import { ForbiddenError, subject } from "@casl/ability";
import { Knex } from "knex";
import path from "path";
import { v4 as uuidv4, validate as uuidValidate } from "uuid";
import { TProjectEnvironments, TSecretFolders, TSecretFoldersInsert } from "@app/db/schemas";
import { ActionProjectType, TProjectEnvironments, TSecretFolders, TSecretFoldersInsert } 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 { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
@@ -12,14 +14,21 @@ import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { OrderByDirection, OrgServiceActor } from "@app/lib/types";
import { buildFolderPath } from "@app/services/secret-folder/secret-folder-fns";
import { ChangeType, CommitType, TFolderCommitServiceFactory } from "../folder-commit/folder-commit-service";
import {
ChangeType,
CommitType,
TCommitResourceChangeDTO,
TFolderCommitServiceFactory
} from "../folder-commit/folder-commit-service";
import { TProjectDALFactory } from "../project/project-dal";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
import { TSecretFolderDALFactory } from "./secret-folder-dal";
import {
TCreateFolderDTO,
TCreateManyFoldersDTO,
TDeleteFolderDTO,
TDeleteManyFoldersDTO,
TGetFolderByIdDTO,
TGetFolderDTO,
TGetFoldersDeepByEnvsDTO,
@@ -69,7 +78,8 @@ export const secretFolderServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -236,21 +246,32 @@ export const secretFolderServiceFactory = ({
actor,
actorId,
projectSlug,
projectId: providedProjectId,
actorAuthMethod,
actorOrgId,
folders
}: TUpdateManyFoldersDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) {
throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
folders,
tx: providedTx,
commitChanges
}: TUpdateManyFoldersDTO & { tx?: Knex; commitChanges?: TCommitResourceChangeDTO[]; projectId?: string }) => {
let projectId = providedProjectId;
if (!projectId && projectSlug) {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) {
throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
}
projectId = project.id;
}
if (!projectId) {
throw new BadRequestError({ message: "Must provide either project slug or projectId" });
}
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId: project.id,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
folders.forEach(({ environment, path: secretPath }) => {
@@ -260,12 +281,12 @@ export const secretFolderServiceFactory = ({
);
});
const result = await folderDAL.transaction(async (tx) =>
Promise.all(
const executeBulkUpdate = async (tx: Knex) => {
return Promise.all(
folders.map(async (newFolder) => {
const { environment, path: secretPath, id, name, description } = newFolder;
const parentFolder = await folderDAL.findBySecretPath(project.id, environment, secretPath);
const parentFolder = await folderDAL.findBySecretPath(projectId as string, environment, secretPath, tx);
if (!parentFolder) {
throw new NotFoundError({
message: `Folder with path '${secretPath}' in environment with slug '${environment}' not found`,
@@ -273,10 +294,10 @@ export const secretFolderServiceFactory = ({
});
}
const env = await projectEnvDAL.findOne({ projectId: project.id, slug: environment });
const env = await projectEnvDAL.findOne({ projectId, slug: environment }, tx);
if (!env) {
throw new NotFoundError({
message: `Environment with slug '${environment}' in project with ID '${project.id}' not found`,
message: `Environment with slug '${environment}' in project with ID '${projectId}' not found`,
name: "UpdateManyFolders"
});
}
@@ -323,26 +344,34 @@ export const secretFolderServiceFactory = ({
},
tx
);
await folderCommitService.createCommit(
{
actor: {
type: actor,
metadata: {
id: actorId
}
if (commitChanges) {
commitChanges.push({
type: CommitType.ADD,
isUpdate: true,
folderVersionId: folderVersion.id
});
} else {
await folderCommitService.createCommit(
{
actor: {
type: actor,
metadata: {
id: actorId
}
},
message: "Folder updated",
folderId: parentFolder.id,
changes: [
{
type: CommitType.ADD,
isUpdate: true,
folderVersionId: folderVersion.id
}
]
},
message: "Folder updated",
folderId: parentFolder.id,
changes: [
{
type: CommitType.ADD,
isUpdate: true,
folderVersionId: folderVersion.id
}
]
},
tx
);
tx
);
}
if (!doc) {
throw new NotFoundError({
message: `Failed to update folder with id '${id}', not found`,
@@ -352,13 +381,16 @@ export const secretFolderServiceFactory = ({
return { oldFolder: folder, newFolder: doc };
})
)
);
);
};
// Execute with provided transaction or create new one
const result = providedTx ? await executeBulkUpdate(providedTx) : await folderDAL.transaction(executeBulkUpdate);
await Promise.all(result.map(async (res) => snapshotService.performSnapshot(res.newFolder.parentId as string)));
return {
projectId: project.id,
projectId,
newFolders: result.map((res) => res.newFolder),
oldFolders: result.map((res) => res.oldFolder)
};
@@ -381,7 +413,8 @@ export const secretFolderServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -582,7 +615,8 @@ export const secretFolderServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -688,7 +722,8 @@ export const secretFolderServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const env = await projectEnvDAL.findOne({ projectId, slug: environment });
@@ -756,7 +791,8 @@ export const secretFolderServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const envs = await projectEnvDAL.findBySlugs(projectId, environments);
@@ -797,7 +833,8 @@ export const secretFolderServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const envs = await projectEnvDAL.findBySlugs(projectId, environments);
@@ -832,7 +869,8 @@ export const secretFolderServiceFactory = ({
actorId,
projectId: folder.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const [folderWithPath] = await folderDAL.findSecretPathByFolderIds(folder.projectId, [folder.id]);
@@ -860,7 +898,8 @@ export const secretFolderServiceFactory = ({
actorId: actor.id,
projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager
});
const envs = await projectEnvDAL.findBySlugs(projectId, environments);
@@ -887,7 +926,8 @@ export const secretFolderServiceFactory = ({
actorId: actor.id,
projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager
});
const environments = await projectEnvDAL.find({ projectId });
@@ -974,6 +1014,363 @@ export const secretFolderServiceFactory = ({
}));
};
const createManyFolders = async ({
projectId,
actor,
actorId,
actorAuthMethod,
actorOrgId,
folders,
tx: providedTx,
commitChanges
}: TCreateManyFoldersDTO & { tx?: Knex; commitChanges?: TCommitResourceChangeDTO[] }) => {
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
folders.forEach(({ environment, path: secretPath }) => {
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
subject(ProjectPermissionSub.SecretFolders, { environment, secretPath })
);
});
const foldersByEnv = folders.reduce(
(acc, folder) => {
if (!acc[folder.environment]) {
acc[folder.environment] = [];
}
acc[folder.environment].push(folder);
return acc;
},
{} as Record<string, typeof folders>
);
const executeBulkCreate = async (tx: Knex) => {
const createdFolders = [];
for (const [environment, envFolders] of Object.entries(foldersByEnv)) {
const env = await projectEnvDAL.findOne({ projectId, slug: environment });
if (!env) {
throw new NotFoundError({
message: `Environment with slug '${environment}' in project with ID '${projectId}' not found`
});
}
await tx.raw("SELECT pg_advisory_xact_lock(?)", [PgSqlLock.CreateFolder(env.id, env.projectId)]);
for (const folderSpec of envFolders) {
const { name, path: secretPath, description } = folderSpec;
const pathWithFolder = path.join(secretPath, name);
const parentFolder = await folderDAL.findClosestFolder(projectId, environment, pathWithFolder, tx);
if (!parentFolder) {
throw new NotFoundError({
message: `Parent folder for path '${pathWithFolder}' not found`
});
}
// Check if the exact folder already exists
const existingFolder = await folderDAL.findOne(
{
envId: env.id,
parentId: parentFolder.id,
name,
isReserved: false
},
tx
);
if (existingFolder) {
createdFolders.push(existingFolder);
// eslint-disable-next-line no-continue
continue;
}
// Handle exact folder case
if (parentFolder.path === pathWithFolder) {
createdFolders.push(parentFolder);
// eslint-disable-next-line no-continue
continue;
}
let currentParentId = parentFolder.id;
// Build the full path we need by processing each segment
if (parentFolder.path !== secretPath) {
const missingSegments = secretPath.substring(parentFolder.path.length).split("/").filter(Boolean);
const newFolders: TSecretFoldersInsert[] = [];
for (const segment of missingSegments) {
const existingSegment = await folderDAL.findOne(
{
name: segment,
parentId: currentParentId,
envId: env.id,
isReserved: false
},
tx
);
if (existingSegment) {
currentParentId = existingSegment.id;
} else {
const newFolder = {
name: segment,
parentId: currentParentId,
id: uuidv4(),
envId: env.id,
version: 1
};
currentParentId = newFolder.id;
newFolders.push(newFolder);
}
}
if (newFolders.length) {
const docs = await folderDAL.insertMany(newFolders, tx);
const folderVersions = await folderVersionDAL.insertMany(
docs.map((doc) => ({
name: doc.name,
envId: doc.envId,
version: doc.version,
folderId: doc.id,
description: doc.description
})),
tx
);
await folderCommitService.createCommit(
{
actor: {
type: actor,
metadata: {
id: actorId
}
},
message: "Folders created (batch)",
folderId: currentParentId,
changes: folderVersions.map((fv) => ({
type: CommitType.ADD,
folderVersionId: fv.id
}))
},
tx
);
}
}
// Create the target folder
const doc = await folderDAL.create(
{ name, envId: env.id, version: 1, parentId: currentParentId, description },
tx
);
const folderVersion = await folderVersionDAL.create(
{
name: doc.name,
envId: doc.envId,
version: doc.version,
folderId: doc.id,
description: doc.description
},
tx
);
if (commitChanges) {
commitChanges.push({
type: CommitType.ADD,
folderVersionId: folderVersion.id
});
} else {
await folderCommitService.createCommit(
{
actor: {
type: actor,
metadata: {
id: actorId
}
},
message: "Folder created (batch)",
folderId: doc.id,
changes: [
{
type: CommitType.ADD,
folderVersionId: folderVersion.id
}
]
},
tx
);
}
createdFolders.push(doc);
}
}
return createdFolders;
};
const result = providedTx ? await executeBulkCreate(providedTx) : await folderDAL.transaction(executeBulkCreate);
const uniqueParentIds = [...new Set(result.map((folder) => folder.parentId).filter(Boolean))];
await Promise.all(uniqueParentIds.map((parentId) => snapshotService.performSnapshot(parentId as string)));
return {
folders: result,
count: result.length
};
};
const deleteManyFolders = async ({
projectId,
actor,
actorId,
actorOrgId,
actorAuthMethod,
folders,
tx: providedTx,
commitChanges
}: TDeleteManyFoldersDTO & { tx?: Knex; commitChanges?: TCommitResourceChangeDTO[] }) => {
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
folders.forEach(({ environment, path: secretPath }) => {
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
subject(ProjectPermissionSub.SecretFolders, { environment, secretPath })
);
});
const foldersByEnv = folders.reduce(
(acc, folder) => {
if (!acc[folder.environment]) {
acc[folder.environment] = [];
}
acc[folder.environment].push(folder);
return acc;
},
{} as Record<string, typeof folders>
);
const executeBulkDelete = async (tx: Knex) => {
const deletedFolders = [];
for (const [environment, envFolders] of Object.entries(foldersByEnv)) {
const env = await projectEnvDAL.findOne({ projectId, slug: environment });
if (!env) {
throw new NotFoundError({
message: `Environment with slug '${environment}' not found`
});
}
for (const folderSpec of envFolders) {
const { path: secretPath, idOrName } = folderSpec;
const parentFolder = await folderDAL.findBySecretPath(projectId, environment, secretPath, tx);
if (!parentFolder) {
throw new NotFoundError({
message: `Folder with path '${secretPath}' in environment with slug '${environment}' not found`
});
}
await $checkFolderPolicy({ projectId, env, parentId: parentFolder.id, idOrName });
let folderToDelete = await folderDAL
.findOne({
envId: env.id,
name: idOrName,
parentId: parentFolder.id,
isReserved: false
})
.catch(() => null);
if (!folderToDelete && uuidValidate(idOrName)) {
folderToDelete = await folderDAL
.findOne({
envId: env.id,
id: idOrName,
parentId: parentFolder.id,
isReserved: false
})
.catch(() => null);
}
if (!folderToDelete) {
throw new NotFoundError({
message: `Folder with ID/name '${idOrName}' not found`
});
}
const [doc] = await folderDAL.delete(
{
envId: env.id,
id: folderToDelete.id,
parentId: parentFolder.id,
isReserved: false
},
tx
);
const folderVersions = await folderVersionDAL.findLatestFolderVersions([doc.id], tx);
if (commitChanges) {
commitChanges.push({
type: CommitType.DELETE,
folderVersionId: folderVersions[doc.id].id,
folderId: doc.id
});
} else {
await folderCommitService.createCommit(
{
actor: {
type: actor,
metadata: {
id: actorId
}
},
message: "Folder deleted (batch)",
folderId: parentFolder.id,
changes: [
{
type: CommitType.DELETE,
folderVersionId: folderVersions[doc.id].id,
folderId: doc.id
}
]
},
tx
);
}
deletedFolders.push(doc);
}
}
return deletedFolders;
};
const result = providedTx ? await executeBulkDelete(providedTx) : await folderDAL.transaction(executeBulkDelete);
const uniqueParentIds = [...new Set(result.map((folder) => folder.parentId).filter(Boolean))];
await Promise.all(uniqueParentIds.map((parentId) => snapshotService.performSnapshot(parentId as string)));
return {
folders: result,
count: result.length
};
};
return {
createFolder,
updateFolder,
@@ -986,6 +1383,8 @@ export const secretFolderServiceFactory = ({
getFoldersDeepByEnvs,
getProjectEnvironmentsFolders,
getFolderVersionsByIds,
getFolderVersions
getFolderVersions,
createManyFolders,
deleteManyFolders
};
};
@@ -1,6 +1,8 @@
import { OrderByDirection, TProjectPermission } from "@app/lib/types";
import { SecretsOrderBy } from "@app/services/secret/secret-types";
import { ActorAuthMethod, ActorType } from "../auth/auth-type";
export enum ReservedFolders {
SecretReplication = "__reserve_replication_"
}
@@ -21,7 +23,7 @@ export type TUpdateFolderDTO = {
} & TProjectPermission;
export type TUpdateManyFoldersDTO = {
projectSlug: string;
projectSlug?: string;
folders: {
environment: string;
path: string;
@@ -62,3 +64,30 @@ export type TGetFoldersDeepByEnvsDTO = {
export type TFindFoldersDeepByParentIdsDTO = {
parentIds: string[];
};
export type TCreateManyFoldersDTO = {
projectId: string;
actor: ActorType;
actorId: string;
actorAuthMethod: ActorAuthMethod;
actorOrgId?: string;
folders: Array<{
name: string;
environment: string;
path: string;
description?: string | null;
}>;
};
export type TDeleteManyFoldersDTO = {
projectId: string;
actor: ActorType;
actorId: string;
actorAuthMethod: ActorAuthMethod;
actorOrgId?: string;
folders: Array<{
environment: string;
path: string;
idOrName: string;
}>;
};
@@ -2,7 +2,7 @@ import path from "node:path";
import { ForbiddenError, subject } from "@casl/ability";
import { TableName } from "@app/db/schemas";
import { ActionProjectType, TableName } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import {
hasSecretReadValueOrDescribePermission,
@@ -87,7 +87,8 @@ export const secretImportServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
// check if user has permission to import into destination path
@@ -204,7 +205,8 @@ export const secretImportServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -301,7 +303,8 @@ export const secretImportServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -375,7 +378,8 @@ export const secretImportServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
// check if user has permission to import into destination path
@@ -451,7 +455,8 @@ export const secretImportServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
@@ -484,7 +489,8 @@ export const secretImportServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const filteredEnvironments = [];
for (const environment of environments) {
@@ -537,7 +543,8 @@ export const secretImportServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
@@ -586,7 +593,8 @@ export const secretImportServiceFactory = ({
actorId,
projectId: folder.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -634,7 +642,8 @@ export const secretImportServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
@@ -669,7 +678,8 @@ export const secretImportServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
@@ -752,7 +762,8 @@ export const secretImportServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const filteredEnvironments = [];
for (const environment of environments) {
@@ -804,7 +815,8 @@ export const secretImportServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (
permission.cannot(
@@ -0,0 +1,10 @@
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import { TSecretSyncListItem } from "@app/services/secret-sync/secret-sync-types";
export const BITBUCKET_SYNC_LIST_OPTION: TSecretSyncListItem = {
name: "Bitbucket",
destination: SecretSync.Bitbucket,
connection: AppConnection.Bitbucket,
canImportSecrets: false
};
@@ -0,0 +1,222 @@
import { request } from "@app/lib/config/request";
import { createAuthHeader } from "@app/services/app-connection/bitbucket";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import {
TBitbucketListVariables,
TBitbucketSyncWithCredentials,
TBitbucketVariable,
TDeleteBitbucketVariable,
TPutBitbucketVariable
} from "@app/services/secret-sync/bitbucket/bitbucket-sync-types";
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
const buildVariablesUrl = (workspace: string, repository: string, environment?: string, uuid?: string): string => {
const baseUrl = `${IntegrationUrls.BITBUCKET_API_URL}/2.0/repositories/${encodeURIComponent(workspace)}/${encodeURIComponent(repository)}`;
if (environment) {
return `${baseUrl}/deployments_config/environments/${environment}/variables/${uuid || ""}`;
}
return `${baseUrl}/pipelines_config/variables/${uuid || ""}`;
};
const listVariables = async ({
workspaceSlug,
repositorySlug,
environmentId,
authHeader
}: TBitbucketListVariables): Promise<TBitbucketVariable[]> => {
const url = buildVariablesUrl(workspaceSlug, repositorySlug, environmentId);
const { data } = await request.get<{ values: TBitbucketVariable[] }>(url, {
headers: {
Authorization: authHeader,
Accept: "application/json"
}
});
return data.values;
};
const upsertVariable = async ({
workspaceSlug,
repositorySlug,
environmentId,
key,
value,
existingVariables,
authHeader
}: {
workspaceSlug: string;
repositorySlug: string;
environmentId?: string;
key: string;
value: string;
existingVariables: TBitbucketVariable[];
authHeader: string;
}) => {
const existingVariable = existingVariables.find((variable) => variable.key === key);
const requestData = { key, value, secured: true };
const headers = {
Authorization: authHeader,
"Content-Type": "application/json"
};
if (existingVariable) {
const url = buildVariablesUrl(workspaceSlug, repositorySlug, environmentId, existingVariable.uuid);
return request.put(url, requestData, { headers });
}
const url = buildVariablesUrl(workspaceSlug, repositorySlug, environmentId);
return request.post(url, requestData, { headers });
};
const putVariables = async ({
workspaceSlug,
repositorySlug,
environmentId,
secretMap,
authHeader
}: TPutBitbucketVariable & { secretMap: TSecretMap; authHeader: string }) => {
const existingVariables = await listVariables({
workspaceSlug,
repositorySlug,
environmentId,
authHeader
});
const promises = Object.entries(secretMap).map(([key, { value }]) =>
upsertVariable({
workspaceSlug,
repositorySlug,
environmentId,
key,
value,
existingVariables,
authHeader
})
);
return Promise.all(promises);
};
const deleteVariables = async ({
workspaceSlug,
repositorySlug,
environmentId,
keys,
authHeader
}: TDeleteBitbucketVariable) => {
const existingVariables = await listVariables({
workspaceSlug,
repositorySlug,
environmentId,
authHeader
});
const variablesToDelete = existingVariables.filter((variable) => keys.includes(variable.key));
const promises = variablesToDelete.map((variable) => {
const url = buildVariablesUrl(workspaceSlug, repositorySlug, environmentId, variable.uuid);
return request.delete(url, {
headers: { Authorization: authHeader }
});
});
return Promise.all(promises);
};
export const BitbucketSyncFns = {
syncSecrets: async (secretSync: TBitbucketSyncWithCredentials, secretMap: TSecretMap) => {
const {
connection,
environment,
destinationConfig: { workspaceSlug, repositorySlug, environmentId }
} = secretSync;
const { email, apiToken } = connection.credentials;
const authHeader = createAuthHeader(email, apiToken);
try {
await putVariables({
workspaceSlug,
repositorySlug,
environmentId,
secretMap,
authHeader
});
} catch (error) {
throw new SecretSyncError({ error });
}
if (secretSync.syncOptions.disableSecretDeletion) return;
try {
const existingVariables = await listVariables({
workspaceSlug,
repositorySlug,
environmentId,
authHeader
});
const keysToDelete = existingVariables
.map((variable) => variable.key)
.filter(
(secret) =>
matchesSchema(secret, environment?.slug || "", secretSync.syncOptions.keySchema) && !(secret in secretMap)
);
if (keysToDelete.length > 0) {
await deleteVariables({
workspaceSlug,
repositorySlug,
environmentId,
keys: keysToDelete,
authHeader
});
}
} catch (error) {
throw new SecretSyncError({ error });
}
},
removeSecrets: async (secretSync: TBitbucketSyncWithCredentials, secretMap: TSecretMap) => {
const {
connection,
destinationConfig: { workspaceSlug, repositorySlug, environmentId }
} = secretSync;
const { email, apiToken } = connection.credentials;
const authHeader = createAuthHeader(email, apiToken);
try {
const existingVariables = await listVariables({
workspaceSlug,
repositorySlug,
environmentId,
authHeader
});
const keysToRemove = existingVariables.map((variable) => variable.key).filter((secret) => secret in secretMap);
if (keysToRemove.length > 0) {
await deleteVariables({
workspaceSlug,
repositorySlug,
environmentId,
keys: keysToRemove,
authHeader
});
}
} catch (error) {
throw new SecretSyncError({ error });
}
},
getSecrets: async (secretSync: TBitbucketSyncWithCredentials): Promise<TSecretMap> => {
throw new Error(`${SECRET_SYNC_NAME_MAP[secretSync.destination]} does not support importing secrets.`);
}
};
@@ -0,0 +1,45 @@
import { z } from "zod";
import { SecretSyncs } from "@app/lib/api-docs";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import {
BaseSecretSyncSchema,
GenericCreateSecretSyncFieldsSchema,
GenericUpdateSecretSyncFieldsSchema
} from "@app/services/secret-sync/secret-sync-schemas";
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
const BitbucketSyncDestinationConfigSchema = z.object({
repositorySlug: z.string().describe(SecretSyncs.DESTINATION_CONFIG.BITBUCKET.repositorySlug),
environmentId: z.string().optional().describe(SecretSyncs.DESTINATION_CONFIG.BITBUCKET.environmentId),
workspaceSlug: z.string().describe(SecretSyncs.DESTINATION_CONFIG.BITBUCKET.workspaceSlug)
});
const BitbucketSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false };
export const BitbucketSyncSchema = BaseSecretSyncSchema(SecretSync.Bitbucket, BitbucketSyncOptionsConfig).extend({
destination: z.literal(SecretSync.Bitbucket),
destinationConfig: BitbucketSyncDestinationConfigSchema
});
export const CreateBitbucketSyncSchema = GenericCreateSecretSyncFieldsSchema(
SecretSync.Bitbucket,
BitbucketSyncOptionsConfig
).extend({
destinationConfig: BitbucketSyncDestinationConfigSchema
});
export const UpdateBitbucketSyncSchema = GenericUpdateSecretSyncFieldsSchema(
SecretSync.Bitbucket,
BitbucketSyncOptionsConfig
).extend({
destinationConfig: BitbucketSyncDestinationConfigSchema.optional()
});
export const BitbucketSyncListItemSchema = z.object({
name: z.literal("Bitbucket"),
connection: z.literal(AppConnection.Bitbucket),
destination: z.literal(SecretSync.Bitbucket),
canImportSecrets: z.literal(false)
});
@@ -0,0 +1,50 @@
import { z } from "zod";
import { TBitbucketConnection } from "@app/services/app-connection/bitbucket";
import { BitbucketSyncListItemSchema, BitbucketSyncSchema, CreateBitbucketSyncSchema } from "./bitbucket-sync-schemas";
export type TBitbucketSync = z.infer<typeof BitbucketSyncSchema>;
export type TBitbucketSyncInput = z.infer<typeof CreateBitbucketSyncSchema>;
export type TBitbucketSyncListItem = z.infer<typeof BitbucketSyncListItemSchema>;
export type TBitbucketSyncWithCredentials = TBitbucketSync & {
connection: TBitbucketConnection;
};
export type TBitbucketVariable = {
key: string;
value?: string;
// Secure variables values are not returned by the API neither are they shown in Bitbucket UI
secured: boolean;
uuid: string;
type: string;
};
export type TBitbucketListVariables = {
workspaceSlug: string;
repositorySlug: string;
environmentId?: string;
authHeader: string;
};
export type TPutBitbucketVariable = {
authHeader: string;
workspaceSlug: string;
repositorySlug: string;
environmentId?: string;
};
export type TDeleteBitbucketVariable = {
authHeader: string;
workspaceSlug: string;
repositorySlug: string;
environmentId?: string;
keys: string[];
};
export type TBitbucketConnectionCredentials = {
authHeader: string;
};
@@ -0,0 +1,4 @@
export * from "./bitbucket-sync-constants";
export * from "./bitbucket-sync-fns";
export * from "./bitbucket-sync-schemas";
export * from "./bitbucket-sync-types";
@@ -1,4 +1,6 @@
/* eslint-disable no-await-in-loop */
import { isAxiosError } from "axios";
import { request } from "@app/lib/config/request";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
@@ -71,7 +73,7 @@ const putEnvironmentSecret = async (secretSync: TRenderSyncWithCredentials, secr
);
};
const deleteEnvironmentSecret = async (secretSync: TRenderSyncWithCredentials, secret: TRenderSecret) => {
const deleteEnvironmentSecret = async (secretSync: TRenderSyncWithCredentials, secret: Pick<TRenderSecret, "key">) => {
const {
destinationConfig,
connection: {
@@ -79,15 +81,24 @@ const deleteEnvironmentSecret = async (secretSync: TRenderSyncWithCredentials, s
}
} = secretSync;
await request.delete(
`${IntegrationUrls.RENDER_API_URL}/v1/services/${destinationConfig.serviceId}/env-vars/${secret.key}`,
{
headers: {
Authorization: `Bearer ${apiKey}`,
Accept: "application/json"
try {
await request.delete(
`${IntegrationUrls.RENDER_API_URL}/v1/services/${destinationConfig.serviceId}/env-vars/${secret.key}`,
{
headers: {
Authorization: `Bearer ${apiKey}`,
Accept: "application/json"
}
}
);
} catch (error) {
if (isAxiosError(error) && error.response?.status === 404) {
// If the secret does not exist, we can ignore this error
return;
}
);
throw error;
}
};
const sleep = async () =>
@@ -99,6 +110,11 @@ export const RenderSyncFns = {
syncSecrets: async (secretSync: TRenderSyncWithCredentials, secretMap: TSecretMap) => {
const renderSecrets = await getRenderEnvironmentSecrets(secretSync);
for await (const key of Object.keys(secretMap)) {
// If value is empty skip it as render does not allow empty variables
if (secretMap[key].value === "") {
// eslint-disable-next-line no-continue
continue;
}
await putEnvironmentSecret(secretSync, secretMap, key);
await sleep();
}
@@ -25,7 +25,8 @@ export enum SecretSync {
Supabase = "supabase",
Zabbix = "zabbix",
Railway = "railway",
Checkly = "checkly"
Checkly = "checkly",
Bitbucket = "bitbucket"
}
export enum SecretSyncInitialSyncBehavior {
@@ -28,6 +28,7 @@ import { ONEPASS_SYNC_LIST_OPTION, OnePassSyncFns } from "./1password";
import { AZURE_APP_CONFIGURATION_SYNC_LIST_OPTION, azureAppConfigurationSyncFactory } from "./azure-app-configuration";
import { AZURE_DEVOPS_SYNC_LIST_OPTION, azureDevOpsSyncFactory } from "./azure-devops";
import { AZURE_KEY_VAULT_SYNC_LIST_OPTION, azureKeyVaultSyncFactory } from "./azure-key-vault";
import { BITBUCKET_SYNC_LIST_OPTION, BitbucketSyncFns } from "./bitbucket";
import { CAMUNDA_SYNC_LIST_OPTION, camundaSyncFactory } from "./camunda";
import { CHECKLY_SYNC_LIST_OPTION } from "./checkly/checkly-sync-constants";
import { ChecklySyncFns } from "./checkly/checkly-sync-fns";
@@ -80,7 +81,8 @@ const SECRET_SYNC_LIST_OPTIONS: Record<SecretSync, TSecretSyncListItem> = {
[SecretSync.Supabase]: SUPABASE_SYNC_LIST_OPTION,
[SecretSync.Zabbix]: ZABBIX_SYNC_LIST_OPTION,
[SecretSync.Railway]: RAILWAY_SYNC_LIST_OPTION,
[SecretSync.Checkly]: CHECKLY_SYNC_LIST_OPTION
[SecretSync.Checkly]: CHECKLY_SYNC_LIST_OPTION,
[SecretSync.Bitbucket]: BITBUCKET_SYNC_LIST_OPTION
};
export const listSecretSyncOptions = () => {
@@ -258,6 +260,8 @@ export const SecretSyncFns = {
return ChecklySyncFns.syncSecrets(secretSync, schemaSecretMap);
case SecretSync.Supabase:
return SupabaseSyncFns.syncSecrets(secretSync, schemaSecretMap);
case SecretSync.Bitbucket:
return BitbucketSyncFns.syncSecrets(secretSync, schemaSecretMap);
default:
throw new Error(
`Unhandled sync destination for sync secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
@@ -365,6 +369,9 @@ export const SecretSyncFns = {
case SecretSync.Supabase:
secretMap = await SupabaseSyncFns.getSecrets(secretSync);
break;
case SecretSync.Bitbucket:
secretMap = await BitbucketSyncFns.getSecrets(secretSync);
break;
default:
throw new Error(
`Unhandled sync destination for get secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
@@ -452,6 +459,8 @@ export const SecretSyncFns = {
return ChecklySyncFns.removeSecrets(secretSync, schemaSecretMap);
case SecretSync.Supabase:
return SupabaseSyncFns.removeSecrets(secretSync, schemaSecretMap);
case SecretSync.Bitbucket:
return BitbucketSyncFns.removeSecrets(secretSync, schemaSecretMap);
default:
throw new Error(
`Unhandled sync destination for remove secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
@@ -28,7 +28,8 @@ export const SECRET_SYNC_NAME_MAP: Record<SecretSync, string> = {
[SecretSync.Supabase]: "Supabase",
[SecretSync.Zabbix]: "Zabbix",
[SecretSync.Railway]: "Railway",
[SecretSync.Checkly]: "Checkly"
[SecretSync.Checkly]: "Checkly",
[SecretSync.Bitbucket]: "Bitbucket"
};
export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
@@ -58,7 +59,8 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
[SecretSync.Supabase]: AppConnection.Supabase,
[SecretSync.Zabbix]: AppConnection.Zabbix,
[SecretSync.Railway]: AppConnection.Railway,
[SecretSync.Checkly]: AppConnection.Checkly
[SecretSync.Checkly]: AppConnection.Checkly,
[SecretSync.Bitbucket]: AppConnection.Bitbucket
};
export const SECRET_SYNC_PLAN_MAP: Record<SecretSync, SecretSyncPlanType> = {
@@ -88,5 +90,6 @@ export const SECRET_SYNC_PLAN_MAP: Record<SecretSync, SecretSyncPlanType> = {
[SecretSync.Supabase]: SecretSyncPlanType.Regular,
[SecretSync.Zabbix]: SecretSyncPlanType.Regular,
[SecretSync.Railway]: SecretSyncPlanType.Regular,
[SecretSync.Checkly]: SecretSyncPlanType.Regular
[SecretSync.Checkly]: SecretSyncPlanType.Regular,
[SecretSync.Bitbucket]: SecretSyncPlanType.Regular
};
@@ -1,5 +1,6 @@
import { ForbiddenError, subject } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { throwIfMissingSecretReadValueOrDescribePermission } from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
@@ -74,7 +75,7 @@ export const secretSyncServiceFactory = ({
actorId: actor.id,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId
});
@@ -110,7 +111,7 @@ export const secretSyncServiceFactory = ({
actorId: actor.id,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId
});
@@ -153,7 +154,7 @@ export const secretSyncServiceFactory = ({
actorId: actor.id,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId: secretSync.projectId
});
@@ -195,7 +196,7 @@ export const secretSyncServiceFactory = ({
actorId: actor.id,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId: secretSync.projectId
});
@@ -233,7 +234,7 @@ export const secretSyncServiceFactory = ({
actorId: actor.id,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId
});
@@ -313,7 +314,7 @@ export const secretSyncServiceFactory = ({
actorId: actor.id,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId: secretSync.projectId
});
@@ -429,7 +430,7 @@ export const secretSyncServiceFactory = ({
actorId: actor.id,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId: secretSync.projectId
});
@@ -506,7 +507,7 @@ export const secretSyncServiceFactory = ({
actorId: actor.id,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId: secretSync.projectId
});
@@ -578,7 +579,7 @@ export const secretSyncServiceFactory = ({
actorId: actor.id,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId: secretSync.projectId
});
@@ -644,7 +645,7 @@ export const secretSyncServiceFactory = ({
actorId: actor.id,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId: secretSync.projectId
});
@@ -72,6 +72,12 @@ import {
TAzureKeyVaultSyncListItem,
TAzureKeyVaultSyncWithCredentials
} from "./azure-key-vault";
import {
TBitbucketSync,
TBitbucketSyncInput,
TBitbucketSyncListItem,
TBitbucketSyncWithCredentials
} from "./bitbucket/bitbucket-sync-types";
import {
TChecklySync,
TChecklySyncInput,
@@ -166,7 +172,8 @@ export type TSecretSync =
| TZabbixSync
| TRailwaySync
| TChecklySync
| TSupabaseSync;
| TSupabaseSync
| TBitbucketSync;
export type TSecretSyncWithCredentials =
| TAwsParameterStoreSyncWithCredentials
@@ -195,7 +202,8 @@ export type TSecretSyncWithCredentials =
| TZabbixSyncWithCredentials
| TRailwaySyncWithCredentials
| TChecklySyncWithCredentials
| TSupabaseSyncWithCredentials;
| TSupabaseSyncWithCredentials
| TBitbucketSyncWithCredentials;
export type TSecretSyncInput =
| TAwsParameterStoreSyncInput
@@ -224,7 +232,8 @@ export type TSecretSyncInput =
| TZabbixSyncInput
| TRailwaySyncInput
| TChecklySyncInput
| TSupabaseSyncInput;
| TSupabaseSyncInput
| TBitbucketSyncInput;
export type TSecretSyncListItem =
| TAwsParameterStoreSyncListItem
@@ -253,7 +262,8 @@ export type TSecretSyncListItem =
| TZabbixSyncListItem
| TRailwaySyncListItem
| TChecklySyncListItem
| TSupabaseSyncListItem;
| TSupabaseSyncListItem
| TBitbucketSyncListItem;
export type TSyncOptionsConfig = {
canImportSecrets: boolean;
@@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } 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";
@@ -28,7 +29,8 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Tags);
@@ -59,7 +61,8 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe
actorId,
projectId: tag.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Tags);
@@ -76,7 +79,8 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe
actorId,
projectId: tag.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Tags);
@@ -93,7 +97,8 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe
actorId,
projectId: tag.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
@@ -109,7 +114,8 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe
actorId,
projectId: tag.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
@@ -122,7 +128,8 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
@@ -67,6 +67,7 @@ export const getAllSecretReferences = (maybeSecretReference: string) => {
export const fnSecretBulkInsert = async ({
// TODO: Pick types here
folderId,
commitChanges,
orgId,
inputSecrets,
secretDAL,
@@ -134,28 +135,32 @@ export const fnSecretBulkInsert = async ({
tx
);
const commitChanges = secretVersions
const changes = secretVersions
.filter(({ type }) => type === SecretType.Shared)
.map((sv) => ({
type: CommitType.ADD,
secretVersionId: sv.id
}));
if (commitChanges.length > 0) {
await folderCommitService.createCommit(
{
actor: {
type: actorType || ActorType.PLATFORM,
metadata: {
id: actor?.actorId
}
if (changes.length > 0) {
if (commitChanges) {
commitChanges.push(...changes);
} else {
await folderCommitService.createCommit(
{
actor: {
type: actorType || ActorType.PLATFORM,
metadata: {
id: actor?.actorId
}
},
message: "Secret Created",
folderId,
changes
},
message: "Secret Created",
folderId,
changes: commitChanges
},
tx
);
tx
);
}
}
await secretDAL.upsertSecretReferences(
@@ -209,6 +214,7 @@ export const fnSecretBulkUpdate = async ({
tx,
inputSecrets,
folderId,
commitChanges,
orgId,
secretDAL,
secretVersionDAL,
@@ -223,7 +229,10 @@ export const fnSecretBulkUpdate = async ({
const actorType = actor?.type || ActorType.PLATFORM;
const sanitizedInputSecrets = inputSecrets.map(
({ filter, data: { skipMultilineEncoding, type, key, encryptedValue, userId, encryptedComment, metadata } }) => ({
({
filter,
data: { skipMultilineEncoding, type, key, encryptedValue, userId, encryptedComment, metadata, secretMetadata }
}) => ({
filter: { ...filter, folderId },
data: {
skipMultilineEncoding,
@@ -231,7 +240,7 @@ export const fnSecretBulkUpdate = async ({
key,
userId,
encryptedComment,
metadata,
metadata: JSON.stringify(metadata || secretMetadata || []),
encryptedValue
}
})
@@ -340,28 +349,32 @@ export const fnSecretBulkUpdate = async ({
{ tx }
);
const commitChanges = secretVersions
const changes = secretVersions
.filter(({ type }) => type === SecretType.Shared)
.map((sv) => ({
type: CommitType.ADD,
isUpdate: true,
secretVersionId: sv.id
}));
if (commitChanges.length > 0) {
await folderCommitService.createCommit(
{
actor: {
type: actorType || ActorType.PLATFORM,
metadata: {
id: actor?.actorId
}
if (changes.length > 0) {
if (commitChanges) {
commitChanges.push(...changes);
} else {
await folderCommitService.createCommit(
{
actor: {
type: actorType || ActorType.PLATFORM,
metadata: {
id: actor?.actorId
}
},
message: "Secret Updated",
folderId,
changes
},
message: "Secret Updated",
folderId,
changes: commitChanges
},
tx
);
tx
);
}
}
return secretsWithTags.map((secret) => ({ ...secret, _id: secret.id }));
@@ -377,7 +390,8 @@ export const fnSecretBulkDelete = async ({
secretQueueService,
folderCommitService,
secretVersionDAL,
projectId
projectId,
commitChanges
}: TFnSecretBulkDelete) => {
const deletedSecrets = await secretDAL.deleteMany(
inputSecrets.map(({ type, secretKey }) => ({
@@ -406,27 +420,31 @@ export const fnSecretBulkDelete = async ({
tx
);
const commitChanges = deletedSecrets
const changes = deletedSecrets
.filter(({ type }) => type === SecretType.Shared)
.map(({ id }) => ({
type: CommitType.DELETE,
secretVersionId: secretVersions[id].id
}));
if (commitChanges.length > 0) {
await folderCommitService.createCommit(
{
actor: {
type: actorType || ActorType.PLATFORM,
metadata: {
id: actorId
}
if (changes.length > 0) {
if (commitChanges) {
commitChanges.push(...changes);
} else {
await folderCommitService.createCommit(
{
actor: {
type: actorType || ActorType.PLATFORM,
metadata: {
id: actorId
}
},
message: "Secret Deleted",
folderId,
changes
},
message: "Secret Deleted",
folderId,
changes: commitChanges
},
tx
);
tx
);
}
}
return deletedSecrets;
@@ -2,7 +2,14 @@ import { ForbiddenError, MongoAbility, subject } from "@casl/ability";
import { Knex } from "knex";
import { z } from "zod";
import { ProjectMembershipRole, SecretsV2Schema, SecretType, TableName, TSecretsV2 } from "@app/db/schemas";
import {
ActionProjectType,
ProjectMembershipRole,
SecretsV2Schema,
SecretType,
TableName,
TSecretsV2
} from "@app/db/schemas";
import {
hasSecretReadValueOrDescribePermission,
throwIfMissingSecretReadValueOrDescribePermission
@@ -28,7 +35,7 @@ import { logger } from "@app/lib/logger";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { ActorType } from "../auth/auth-type";
import { TFolderCommitServiceFactory } from "../folder-commit/folder-commit-service";
import { TCommitResourceChangeDTO, TFolderCommitServiceFactory } from "../folder-commit/folder-commit-service";
import { TKmsServiceFactory } from "../kms/kms-service";
import { KmsDataKey } from "../kms/kms-types";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
@@ -239,7 +246,8 @@ export const secretV2BridgeServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
@@ -394,7 +402,8 @@ export const secretV2BridgeServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (inputSecret.newSecretName === "") {
@@ -620,7 +629,8 @@ export const secretV2BridgeServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
@@ -756,7 +766,8 @@ export const secretV2BridgeServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret);
}
@@ -801,7 +812,8 @@ export const secretV2BridgeServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret);
@@ -900,7 +912,8 @@ export const secretV2BridgeServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (!isInternal) {
throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret);
@@ -953,7 +966,8 @@ export const secretV2BridgeServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret);
@@ -1251,7 +1265,8 @@ export const secretV2BridgeServiceFactory = ({
actorId,
projectId: secret.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, {
@@ -1314,7 +1329,8 @@ export const secretV2BridgeServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
@@ -1519,14 +1535,17 @@ export const secretV2BridgeServiceFactory = ({
actorOrgId,
environment,
projectId,
secrets: inputSecrets
}: TCreateManySecretDTO) => {
secrets: inputSecrets,
tx: providedTx,
commitChanges
}: TCreateManySecretDTO & { tx?: Knex; commitChanges?: TCommitResourceChangeDTO[] }) => {
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
@@ -1603,8 +1622,8 @@ export const secretV2BridgeServiceFactory = ({
const { encryptor: secretManagerEncryptor, decryptor: secretManagerDecryptor } =
await kmsService.createCipherPairWithDataKey({ type: KmsDataKey.SecretManager, projectId });
const newSecrets = await secretDAL.transaction(async (tx) =>
fnSecretBulkInsert({
const executeBulkInsert = async (tx: Knex) => {
return fnSecretBulkInsert({
inputSecrets: inputSecrets.map((el) => {
const references = secretReferencesGroupByInputSecretKey[el.secretKey]?.nestedReferences;
@@ -1626,6 +1645,7 @@ export const secretV2BridgeServiceFactory = ({
};
}),
folderId,
commitChanges,
orgId: actorOrgId,
secretDAL,
resourceMetadataDAL,
@@ -1638,8 +1658,13 @@ export const secretV2BridgeServiceFactory = ({
actorId
},
tx
})
);
});
};
const newSecrets = providedTx
? await executeBulkInsert(providedTx)
: await secretDAL.transaction(executeBulkInsert);
await secretDAL.invalidateSecretCacheByProjectId(projectId);
await snapshotService.performSnapshot(folderId);
await secretQueueService.syncSecrets({
@@ -1686,14 +1711,17 @@ export const secretV2BridgeServiceFactory = ({
projectId,
secretPath: defaultSecretPath = "/",
secrets: inputSecrets,
mode: updateMode
}: TUpdateManySecretDTO) => {
mode: updateMode,
tx: providedTx,
commitChanges
}: TUpdateManySecretDTO & { tx?: Knex; commitChanges?: TCommitResourceChangeDTO[] }) => {
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const secretsToUpdateGroupByPath = groupBy(inputSecrets, (el) => el.secretPath || defaultSecretPath);
@@ -1716,18 +1744,20 @@ export const secretV2BridgeServiceFactory = ({
const { encryptor: secretManagerEncryptor, decryptor: secretManagerDecryptor } =
await kmsService.createCipherPairWithDataKey({ type: KmsDataKey.SecretManager, projectId });
const updatedSecrets: Array<
TSecretsV2 & {
secretPath: string;
tags: {
id: string;
slug: string;
color?: string | null;
name: string;
}[];
}
> = [];
await secretDAL.transaction(async (tx) => {
// Function to execute the bulk update operation
const executeBulkUpdate = async (tx: Knex) => {
const updatedSecrets: Array<
TSecretsV2 & {
secretPath: string;
tags: {
id: string;
slug: string;
color?: string | null;
name: string;
}[];
}
> = [];
for await (const folder of folders) {
if (!folder) throw new NotFoundError({ message: "Folder not found" });
@@ -1846,7 +1876,7 @@ export const secretV2BridgeServiceFactory = ({
{
operator: "eq",
field: `${TableName.SecretV2}.key` as "key",
value: el.secretKey
value: el.newSecretName as string
},
{
operator: "eq",
@@ -1900,6 +1930,7 @@ export const secretV2BridgeServiceFactory = ({
orgId: actorOrgId,
folderCommitService,
tx,
commitChanges,
inputSecrets: secretsToUpdate.map((el) => {
const originalSecret = secretsToUpdateInDBGroupedByKey[el.secretKey][0];
const encryptedValue =
@@ -1977,7 +2008,13 @@ export const secretV2BridgeServiceFactory = ({
updatedSecrets.push(...bulkInsertedSecrets.map((el) => ({ ...el, secretPath: folder.path })));
}
}
});
return updatedSecrets;
};
const updatedSecrets = providedTx
? await executeBulkUpdate(providedTx)
: await secretDAL.transaction(executeBulkUpdate);
await secretDAL.invalidateSecretCacheByProjectId(projectId);
await Promise.allSettled(folders.map((el) => (el?.id ? snapshotService.performSnapshot(el.id) : undefined)));
@@ -2034,14 +2071,17 @@ export const secretV2BridgeServiceFactory = ({
actor,
actorId,
actorAuthMethod,
actorOrgId
}: TDeleteManySecretDTO) => {
actorOrgId,
tx: providedTx,
commitChanges
}: TDeleteManySecretDTO & { tx?: Knex; commitChanges?: TCommitResourceChangeDTO[] }) => {
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
@@ -2094,24 +2134,29 @@ export const secretV2BridgeServiceFactory = ({
);
});
const executeBulkDelete = async (tx: Knex) => {
return fnSecretBulkDelete({
secretDAL,
secretQueueService,
folderCommitService,
secretVersionDAL,
inputSecrets: inputSecrets.map(({ type, secretKey }) => ({
secretKey,
type: type || SecretType.Shared
})),
projectId,
folderId,
actorId,
actorType: actor,
commitChanges,
tx
});
};
try {
const secretsDeleted = await secretDAL.transaction(async (tx) =>
fnSecretBulkDelete({
secretDAL,
secretQueueService,
folderCommitService,
secretVersionDAL,
inputSecrets: inputSecrets.map(({ type, secretKey }) => ({
secretKey,
type: type || SecretType.Shared
})),
projectId,
folderId,
actorId,
actorType: actor,
tx
})
);
const secretsDeleted = providedTx
? await executeBulkDelete(providedTx)
: await secretDAL.transaction(executeBulkDelete);
await secretDAL.invalidateSecretCacheByProjectId(projectId);
await snapshotService.performSnapshot(folderId);
@@ -2197,7 +2242,8 @@ export const secretV2BridgeServiceFactory = ({
actorId,
projectId: folder.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const canRead =
@@ -2262,7 +2308,8 @@ export const secretV2BridgeServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (!hasRole(ProjectMembershipRole.Admin))
@@ -2309,7 +2356,8 @@ export const secretV2BridgeServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const sourceFolder = await folderDAL.findBySecretPath(projectId, sourceEnvironment, sourceSecretPath);
@@ -2693,7 +2741,8 @@ export const secretV2BridgeServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret, {
@@ -2786,7 +2835,8 @@ export const secretV2BridgeServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret, {
environment,
@@ -2910,7 +2960,8 @@ export const secretV2BridgeServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const canRead =
@@ -8,7 +8,7 @@ import { SecretsOrderBy } from "@app/services/secret/secret-types";
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal";
import { TFolderCommitServiceFactory } from "../folder-commit/folder-commit-service";
import { TCommitResourceChangeDTO, TFolderCommitServiceFactory } from "../folder-commit/folder-commit-service";
import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metadata-dal";
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
import { TSecretV2BridgeDALFactory } from "./secret-v2-bridge-dal";
@@ -167,6 +167,7 @@ export type TFnSecretBulkInsert = {
folderId: string;
orgId: string;
tx?: Knex;
commitChanges?: TCommitResourceChangeDTO[];
inputSecrets: Array<
Omit<TSecretsV2Insert, "folderId"> & {
tagIds?: string[];
@@ -214,6 +215,7 @@ export type TFnSecretBulkUpdate = {
actorId?: string;
};
tx?: Knex;
commitChanges?: TCommitResourceChangeDTO[];
};
export type TFnSecretBulkDelete = {
@@ -223,6 +225,7 @@ export type TFnSecretBulkDelete = {
actorId: string;
actorType?: string;
tx?: Knex;
commitChanges?: TCommitResourceChangeDTO[];
secretDAL: Pick<TSecretV2BridgeDALFactory, "deleteMany">;
secretQueueService: {
removeSecretReminder: (data: TRemoveSecretReminderDTO, tx?: Knex) => Promise<void>;
+3 -1
View File
@@ -3,6 +3,7 @@ import path from "path";
import RE2 from "re2";
import {
ActionProjectType,
SecretEncryptionAlgo,
SecretKeyEncoding,
SecretType,
@@ -180,7 +181,8 @@ export const recursivelyGetSecretPaths = ({
actorId: auth.actorId,
projectId,
actorAuthMethod: auth.actorAuthMethod,
actorOrgId: auth.actorOrgId
actorOrgId: auth.actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
// Filter out paths that the user does not have permission to access, and paths that are not in the current path
+31 -15
View File
@@ -3,6 +3,7 @@
import { ForbiddenError, subject } from "@casl/ability";
import {
ActionProjectType,
ProjectMembershipRole,
ProjectUpgradeStatus,
ProjectVersion,
@@ -215,7 +216,8 @@ export const secretServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -332,7 +334,8 @@ export const secretServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -492,7 +495,8 @@ export const secretServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -608,7 +612,8 @@ export const secretServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
let paths: { folderId: string; path: string }[] = [];
@@ -713,7 +718,8 @@ export const secretServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, {
environment,
@@ -818,7 +824,8 @@ export const secretServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionSecretActions.Create,
@@ -904,7 +911,8 @@ export const secretServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -1026,7 +1034,8 @@ export const secretServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionSecretActions.Delete,
@@ -2586,7 +2595,8 @@ export const secretServiceFactory = ({
actorId,
projectId: folder.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
const secretVersions = await secretVersionDAL.findBySecretId(secretId, {
@@ -2678,7 +2688,8 @@ export const secretServiceFactory = ({
actorId,
projectId: project.id,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -2783,7 +2794,8 @@ export const secretServiceFactory = ({
actorId,
projectId: project.id,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
@@ -2889,7 +2901,8 @@ export const secretServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (!hasRole(ProjectMembershipRole.Admin))
@@ -2974,7 +2987,8 @@ export const secretServiceFactory = ({
actorId,
projectId: project.id,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const { botKey } = await projectBotService.getBotKey(project.id);
@@ -3381,7 +3395,8 @@ export const secretServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (!hasRole(ProjectMembershipRole.Admin))
@@ -3409,7 +3424,8 @@ export const secretServiceFactory = ({
actorId: actor.id,
projectId: params.projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager
});
const secrets = secretV2BridgeService.getSecretsByFolderMappings({ ...params, userId: actor.id }, permission);
@@ -547,3 +547,33 @@ export enum SecretProtectionType {
}
export type TStartSecretsV2MigrationDTO = TProjectPermission;
export type TProcessNewCommitRawDTO = {
secrets: {
create?: {
secretKey: string;
secretValue: string;
secretComment?: string;
skipMultilineEncoding?: boolean;
tagIds?: string[];
secretMetadata?: ResourceMetadataDTO;
metadata?: { source?: string };
}[];
update?: {
secretKey: string;
newSecretName?: string;
secretValue?: string;
secretComment?: string;
skipMultilineEncoding?: boolean;
tagIds?: string[];
secretMetadata?: ResourceMetadataDTO;
metadata?: { source?: string };
}[];
delete?: { secretKey: string }[];
};
folders: {
create?: { folderName: string; description?: string }[];
update?: { folderName: string; description?: string | null; id: string }[];
delete?: { folderName: string; id: string }[];
};
};
@@ -1,5 +1,6 @@
import { ForbiddenError, subject } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import {
ProjectPermissionActions,
@@ -65,7 +66,8 @@ export const serviceTokenServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.ServiceTokens);
@@ -120,7 +122,8 @@ export const serviceTokenServiceFactory = ({
actorId,
projectId: serviceToken.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.ServiceTokens);
@@ -154,7 +157,8 @@ export const serviceTokenServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens);
+1 -1
View File
@@ -50,7 +50,7 @@ const buildSlackPayload = (notification: TNotification) => {
*Secret path*: ${payload.secretPath || "/"}
*Secret Key${payload.secretKeys.length > 1 ? "s" : ""}*: ${payload.secretKeys.join(", ")}
View the complete details <${appCfg.SITE_URL}/projects/${payload.projectId}/secret-manager/approval?requestId=${
View the complete details <${appCfg.SITE_URL}/projects/secret-management/${payload.projectId}/approval?requestId=${
payload.requestId
}|here>.`;
@@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { TWebhooksInsert } from "@app/db/schemas";
import { ActionProjectType, TWebhooksInsert } 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 { NotFoundError } from "@app/lib/errors";
@@ -54,7 +54,8 @@ export const webhookServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Webhooks);
const env = await projectEnvDAL.findOne({ projectId, slug: environment });
@@ -92,7 +93,8 @@ export const webhookServiceFactory = ({
actorId,
projectId: webhook.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Webhooks);
@@ -109,7 +111,8 @@ export const webhookServiceFactory = ({
actorId,
projectId: webhook.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Webhooks);
@@ -126,7 +129,8 @@ export const webhookServiceFactory = ({
actorId,
projectId: webhook.projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
const project = await projectDAL.findById(webhook.projectId);
@@ -177,7 +181,8 @@ export const webhookServiceFactory = ({
actorId,
projectId,
actorAuthMethod,
actorOrgId
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);