mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
feature: add suport for project scoped app connections
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { dropConstraintIfExists } from "@app/db/migrations/utils/dropConstraintIfExists";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
@@ -13,9 +14,7 @@ export async function up(knex: Knex): Promise<void> {
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
await knex.schema.alterTable(TableName.AppConnection, (t) => {
|
||||
t.dropUnique(["orgId", "name"]);
|
||||
});
|
||||
await dropConstraintIfExists(TableName.AppConnection, "app_connections_orgid_name_unique", knex);
|
||||
|
||||
await knex.schema.alterTable(TableName.SecretSync, (t) => {
|
||||
t.dropUnique(["projectId", "name"]);
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { dropConstraintIfExists } from "@app/db/migrations/utils/dropConstraintIfExists";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
|
||||
const UNIQUE_NAME_ORG_CONNECTION_INDEX = "unique_name_org_app_connection";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
if (await knex.schema.hasTable(TableName.AppConnection)) {
|
||||
// we can't add the constraint back after up since there may be conflicting names so we do if exists
|
||||
await dropConstraintIfExists(TableName.AppConnection, "app_connections_orgid_name_unique", knex);
|
||||
|
||||
if (!(await knex.schema.hasColumn(TableName.AppConnection, "projectId"))) {
|
||||
await knex.schema.alterTable(TableName.AppConnection, (t) => {
|
||||
t.string("projectId").nullable();
|
||||
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
|
||||
// unique name for project-level connections
|
||||
t.unique(["name", "projectId", "orgId"]);
|
||||
});
|
||||
|
||||
// unique name for org-level connections
|
||||
await knex.raw(`
|
||||
CREATE UNIQUE INDEX ${UNIQUE_NAME_ORG_CONNECTION_INDEX}
|
||||
ON ${TableName.AppConnection} ("name", "orgId")
|
||||
WHERE "projectId" IS NULL
|
||||
`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
if (await knex.schema.hasTable(TableName.AppConnection)) {
|
||||
if (await knex.schema.hasColumn(TableName.AppConnection, "projectId")) {
|
||||
await knex.schema.alterTable(TableName.AppConnection, (t) => {
|
||||
t.dropUnique(["name", "projectId", "orgId"]);
|
||||
t.dropColumn("projectId");
|
||||
});
|
||||
await dropConstraintIfExists(TableName.AppConnection, UNIQUE_NAME_ORG_CONNECTION_INDEX, knex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,8 @@ export const AppConnectionsSchema = z.object({
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
isPlatformManagedCredentials: z.boolean().default(false).nullable().optional(),
|
||||
gatewayId: z.string().uuid().nullable().optional()
|
||||
gatewayId: z.string().uuid().nullable().optional(),
|
||||
projectId: z.string().nullable().optional()
|
||||
});
|
||||
|
||||
export type TAppConnections = z.infer<typeof AppConnectionsSchema>;
|
||||
|
||||
@@ -392,6 +392,8 @@ export enum EventType {
|
||||
CREATE_APP_CONNECTION = "create-app-connection",
|
||||
UPDATE_APP_CONNECTION = "update-app-connection",
|
||||
DELETE_APP_CONNECTION = "delete-app-connection",
|
||||
GET_APP_CONNECTION_USAGE = "get-app-connection-usage",
|
||||
MIGRATE_APP_CONNECTION = "migrate-app-connection",
|
||||
CREATE_SHARED_SECRET = "create-shared-secret",
|
||||
CREATE_SECRET_REQUEST = "create-secret-request",
|
||||
DELETE_SHARED_SECRET = "delete-shared-secret",
|
||||
@@ -2781,14 +2783,31 @@ interface GetAppConnectionEvent {
|
||||
};
|
||||
}
|
||||
|
||||
interface GetAppConnectionUsageEvent {
|
||||
type: EventType.GET_APP_CONNECTION_USAGE;
|
||||
metadata: {
|
||||
connectionId: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface MigrateAppConnectionEvent {
|
||||
type: EventType.MIGRATE_APP_CONNECTION;
|
||||
metadata: {
|
||||
connectionId: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface CreateAppConnectionEvent {
|
||||
type: EventType.CREATE_APP_CONNECTION;
|
||||
metadata: Omit<TCreateAppConnectionDTO, "credentials"> & { connectionId: string };
|
||||
metadata: Omit<TCreateAppConnectionDTO, "credentials" | "projectId"> & { connectionId: string };
|
||||
}
|
||||
|
||||
interface UpdateAppConnectionEvent {
|
||||
type: EventType.UPDATE_APP_CONNECTION;
|
||||
metadata: Omit<TUpdateAppConnectionDTO, "credentials"> & { connectionId: string; credentialsUpdated: boolean };
|
||||
metadata: Omit<TUpdateAppConnectionDTO, "credentials" | "projectId"> & {
|
||||
connectionId: string;
|
||||
credentialsUpdated: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface DeleteAppConnectionEvent {
|
||||
@@ -3697,6 +3716,8 @@ export type Event =
|
||||
| CreateAppConnectionEvent
|
||||
| UpdateAppConnectionEvent
|
||||
| DeleteAppConnectionEvent
|
||||
| GetAppConnectionUsageEvent
|
||||
| MigrateAppConnectionEvent
|
||||
| GetSshHostGroupEvent
|
||||
| CreateSshHostGroupEvent
|
||||
| UpdateSshHostGroupEvent
|
||||
|
||||
@@ -2,6 +2,7 @@ import { AbilityBuilder, createMongoAbility, MongoAbility } from "@casl/ability"
|
||||
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionAppConnectionActions,
|
||||
ProjectPermissionAuditLogsActions,
|
||||
ProjectPermissionCertificateActions,
|
||||
ProjectPermissionCmekActions,
|
||||
@@ -264,6 +265,17 @@ const buildAdminPermissionRules = () => {
|
||||
ProjectPermissionSub.SecretEvents
|
||||
);
|
||||
|
||||
can(
|
||||
[
|
||||
ProjectPermissionAppConnectionActions.Create,
|
||||
ProjectPermissionAppConnectionActions.Edit,
|
||||
ProjectPermissionAppConnectionActions.Delete,
|
||||
ProjectPermissionAppConnectionActions.Read,
|
||||
ProjectPermissionAppConnectionActions.Connect
|
||||
],
|
||||
ProjectPermissionSub.AppConnections
|
||||
);
|
||||
|
||||
return rules;
|
||||
};
|
||||
|
||||
@@ -477,6 +489,8 @@ const buildMemberPermissionRules = () => {
|
||||
ProjectPermissionSub.SecretEvents
|
||||
);
|
||||
|
||||
can(ProjectPermissionAppConnectionActions.Connect, ProjectPermissionSub.AppConnections);
|
||||
|
||||
return rules;
|
||||
};
|
||||
|
||||
|
||||
@@ -147,6 +147,14 @@ export enum ProjectPermissionSecretScanningDataSourceActions {
|
||||
ReadResources = "read-data-source-resources"
|
||||
}
|
||||
|
||||
export enum ProjectPermissionAppConnectionActions {
|
||||
Read = "read-app-connections",
|
||||
Create = "create-app-connections",
|
||||
Edit = "edit-app-connections",
|
||||
Delete = "delete-app-connections",
|
||||
Connect = "connect-app-connections"
|
||||
}
|
||||
|
||||
export enum ProjectPermissionSecretScanningFindingActions {
|
||||
Read = "read-findings",
|
||||
Update = "update-findings"
|
||||
@@ -208,7 +216,8 @@ export enum ProjectPermissionSub {
|
||||
SecretScanningDataSources = "secret-scanning-data-sources",
|
||||
SecretScanningFindings = "secret-scanning-findings",
|
||||
SecretScanningConfigs = "secret-scanning-configs",
|
||||
SecretEvents = "secret-events"
|
||||
SecretEvents = "secret-events",
|
||||
AppConnections = "app-connections"
|
||||
}
|
||||
|
||||
export type SecretSubjectFields = {
|
||||
@@ -272,6 +281,10 @@ export type PkiSubscriberSubjectFields = {
|
||||
// (dangtony98): consider adding [commonName] as a subject field in the future
|
||||
};
|
||||
|
||||
export type AppConnectionSubjectFields = {
|
||||
connectionId: string;
|
||||
};
|
||||
|
||||
export type ProjectPermissionSet =
|
||||
| [
|
||||
ProjectPermissionSecretActions,
|
||||
@@ -365,6 +378,13 @@ export type ProjectPermissionSet =
|
||||
| [
|
||||
ProjectPermissionSecretEventActions,
|
||||
ProjectPermissionSub.SecretEvents | (ForcedSubject<ProjectPermissionSub.SecretEvents> & SecretEventSubjectFields)
|
||||
]
|
||||
| [
|
||||
ProjectPermissionAppConnectionActions,
|
||||
(
|
||||
| ProjectPermissionSub.AppConnections
|
||||
| (ForcedSubject<ProjectPermissionSub.AppConnections> & AppConnectionSubjectFields)
|
||||
)
|
||||
];
|
||||
|
||||
const SECRET_PATH_MISSING_SLASH_ERR_MSG = "Invalid Secret Path; it must start with a '/'";
|
||||
@@ -580,6 +600,21 @@ const PkiTemplateConditionSchema = z
|
||||
})
|
||||
.partial();
|
||||
|
||||
const AppConnectionConditionSchema = z
|
||||
.object({
|
||||
connectionId: z.union([
|
||||
z.string(),
|
||||
z
|
||||
.object({
|
||||
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
||||
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
|
||||
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN]
|
||||
})
|
||||
.partial()
|
||||
])
|
||||
})
|
||||
.partial();
|
||||
|
||||
const GeneralPermissionSchema = [
|
||||
z.object({
|
||||
subject: z.literal(ProjectPermissionSub.SecretApproval).describe("The entity this permission pertains to."),
|
||||
@@ -760,6 +795,16 @@ const GeneralPermissionSchema = [
|
||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionSecretScanningConfigActions).describe(
|
||||
"Describe what action an entity can take."
|
||||
)
|
||||
}),
|
||||
z.object({
|
||||
subject: z.literal(ProjectPermissionSub.AppConnections).describe("The entity this permission pertains to."),
|
||||
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionAppConnectionActions).describe(
|
||||
"Describe what action an entity can take."
|
||||
),
|
||||
conditions: AppConnectionConditionSchema.describe(
|
||||
"When specified, only matching conditions will be allowed to access given resource."
|
||||
).optional()
|
||||
})
|
||||
];
|
||||
|
||||
|
||||
@@ -175,7 +175,8 @@ export const ldapPasswordRotationFactory: TRotationFactory<
|
||||
const encryptedCredentials = await encryptAppConnectionCredentials({
|
||||
credentials: updatedCredentials,
|
||||
orgId,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId: connection.projectId
|
||||
});
|
||||
|
||||
await appConnectionDAL.updateById(connection.id, { encryptedCredentials });
|
||||
|
||||
@@ -52,6 +52,7 @@ const baseSecretRotationV2Query = ({
|
||||
db.ref("description").withSchema(TableName.AppConnection).as("connectionDescription"),
|
||||
db.ref("version").withSchema(TableName.AppConnection).as("connectionVersion"),
|
||||
db.ref("gatewayId").withSchema(TableName.AppConnection).as("connectionGatewayId"),
|
||||
db.ref("projectId").withSchema(TableName.AppConnection).as("connectionProjectId"),
|
||||
db.ref("createdAt").withSchema(TableName.AppConnection).as("connectionCreatedAt"),
|
||||
db.ref("updatedAt").withSchema(TableName.AppConnection).as("connectionUpdatedAt"),
|
||||
db
|
||||
@@ -106,6 +107,7 @@ const expandSecretRotation = <T extends Awaited<ReturnType<typeof baseSecretRota
|
||||
connectionUpdatedAt,
|
||||
connectionVersion,
|
||||
connectionGatewayId,
|
||||
connectionProjectId,
|
||||
connectionIsPlatformManagedCredentials,
|
||||
...el
|
||||
} = secretRotation;
|
||||
@@ -126,6 +128,7 @@ const expandSecretRotation = <T extends Awaited<ReturnType<typeof baseSecretRota
|
||||
updatedAt: connectionUpdatedAt,
|
||||
version: connectionVersion,
|
||||
gatewayId: connectionGatewayId,
|
||||
projectId: connectionProjectId,
|
||||
isPlatformManagedCredentials: connectionIsPlatformManagedCredentials
|
||||
},
|
||||
folder: {
|
||||
|
||||
@@ -89,7 +89,7 @@ import { TSecretRotationV2DALFactory } from "./secret-rotation-v2-dal";
|
||||
|
||||
export type TSecretRotationV2ServiceFactoryDep = {
|
||||
secretRotationV2DAL: TSecretRotationV2DALFactory;
|
||||
appConnectionService: Pick<TAppConnectionServiceFactory, "connectAppConnectionById">;
|
||||
appConnectionService: Pick<TAppConnectionServiceFactory, "validateAppConnectionUsageById">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getOrgPermission">;
|
||||
projectBotService: Pick<TProjectBotServiceFactory, "getBotKey">;
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||
@@ -459,7 +459,11 @@ export const secretRotationV2ServiceFactory = ({
|
||||
const typeApp = SECRET_ROTATION_CONNECTION_MAP[payload.type];
|
||||
|
||||
// validates permission to connect and app is valid for rotation type
|
||||
const connection = await appConnectionService.connectAppConnectionById(typeApp, payload.connectionId, actor);
|
||||
const connection = await appConnectionService.validateAppConnectionUsageById(
|
||||
typeApp,
|
||||
{ connectionId: payload.connectionId, projectId },
|
||||
actor
|
||||
);
|
||||
|
||||
const rotationFactory = SECRET_ROTATION_FACTORY_MAP[payload.type](
|
||||
{
|
||||
|
||||
@@ -50,6 +50,7 @@ const baseSecretScanningDataSourceQuery = ({
|
||||
db.ref("description").withSchema(TableName.AppConnection).as("connectionDescription"),
|
||||
db.ref("version").withSchema(TableName.AppConnection).as("connectionVersion"),
|
||||
db.ref("gatewayId").withSchema(TableName.AppConnection).as("connectionGatewayId"),
|
||||
db.ref("projectId").withSchema(TableName.AppConnection).as("connectionProjectId"),
|
||||
db.ref("createdAt").withSchema(TableName.AppConnection).as("connectionCreatedAt"),
|
||||
db.ref("updatedAt").withSchema(TableName.AppConnection).as("connectionUpdatedAt"),
|
||||
db
|
||||
@@ -84,6 +85,7 @@ const expandSecretScanningDataSource = <
|
||||
connectionVersion,
|
||||
connectionIsPlatformManagedCredentials,
|
||||
connectionGatewayId,
|
||||
connectionProjectId,
|
||||
...el
|
||||
} = dataSource;
|
||||
|
||||
@@ -103,7 +105,8 @@ const expandSecretScanningDataSource = <
|
||||
updatedAt: connectionUpdatedAt,
|
||||
version: connectionVersion,
|
||||
isPlatformManagedCredentials: connectionIsPlatformManagedCredentials,
|
||||
gatewayId: connectionGatewayId
|
||||
gatewayId: connectionGatewayId,
|
||||
projectId: connectionProjectId
|
||||
}
|
||||
: undefined
|
||||
};
|
||||
|
||||
@@ -60,7 +60,7 @@ import { TSecretScanningV2QueueServiceFactory } from "./secret-scanning-v2-queue
|
||||
|
||||
export type TSecretScanningV2ServiceFactoryDep = {
|
||||
secretScanningV2DAL: TSecretScanningV2DALFactory;
|
||||
appConnectionService: Pick<TAppConnectionServiceFactory, "connectAppConnectionById">;
|
||||
appConnectionService: Pick<TAppConnectionServiceFactory, "validateAppConnectionUsageById">;
|
||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getOrgPermission">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
@@ -252,9 +252,9 @@ export const secretScanningV2ServiceFactory = ({
|
||||
let connection: TAppConnection | null = null;
|
||||
if (payload.connectionId) {
|
||||
// validates permission to connect and app is valid for data source
|
||||
connection = await appConnectionService.connectAppConnectionById(
|
||||
connection = await appConnectionService.validateAppConnectionUsageById(
|
||||
SECRET_SCANNING_DATA_SOURCE_CONNECTION_MAP[payload.type],
|
||||
payload.connectionId,
|
||||
{ connectionId: payload.connectionId, projectId: payload.projectId },
|
||||
actor
|
||||
);
|
||||
}
|
||||
@@ -373,9 +373,9 @@ export const secretScanningV2ServiceFactory = ({
|
||||
let connection: TAppConnection | null = null;
|
||||
if (dataSource.connectionId) {
|
||||
// validates permission to connect and app is valid for data source
|
||||
connection = await appConnectionService.connectAppConnectionById(
|
||||
connection = await appConnectionService.validateAppConnectionUsageById(
|
||||
SECRET_SCANNING_DATA_SOURCE_CONNECTION_MAP[dataSource.type],
|
||||
dataSource.connectionId,
|
||||
{ connectionId: dataSource.connectionId, projectId: dataSource.projectId },
|
||||
actor
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2185,11 +2185,15 @@ export const CertificateAuthorities = {
|
||||
};
|
||||
|
||||
export const AppConnections = {
|
||||
LIST: (app?: AppConnection) => ({
|
||||
projectId: `The ID of the project to list ${app ? APP_CONNECTION_NAME_MAP[app] : "App"} Connections from.`
|
||||
}),
|
||||
GET_BY_ID: (app: AppConnection) => ({
|
||||
connectionId: `The ID of the ${APP_CONNECTION_NAME_MAP[app]} Connection to retrieve.`
|
||||
}),
|
||||
GET_BY_NAME: (app: AppConnection) => ({
|
||||
connectionName: `The name of the ${APP_CONNECTION_NAME_MAP[app]} Connection to retrieve.`
|
||||
connectionName: `The name of the ${APP_CONNECTION_NAME_MAP[app]} Connection to retrieve.`,
|
||||
projectId: `The project ID of the ${APP_CONNECTION_NAME_MAP[app]} Connection is associated with. Leave unspecified to get organization-level connections.`
|
||||
}),
|
||||
CREATE: (app: AppConnection) => {
|
||||
const appName = APP_CONNECTION_NAME_MAP[app];
|
||||
@@ -2198,7 +2202,8 @@ export const AppConnections = {
|
||||
description: `An optional description for the ${appName} Connection.`,
|
||||
credentials: `The credentials used to connect with ${appName}.`,
|
||||
method: `The method used to authenticate with ${appName}.`,
|
||||
isPlatformManagedCredentials: `Whether or not the ${appName} Connection credentials should be managed by Infisical. Once enabled this cannot be reversed.`
|
||||
isPlatformManagedCredentials: `Whether or not the ${appName} Connection credentials should be managed by Infisical. Once enabled this cannot be reversed.`,
|
||||
projectId: `The ID of the project to create the ${appName} Connection in.`
|
||||
};
|
||||
},
|
||||
UPDATE: (app: AppConnection) => {
|
||||
|
||||
@@ -1837,7 +1837,8 @@ export const registerRoutes = async (
|
||||
gatewayService,
|
||||
gatewayV2Service,
|
||||
gatewayDAL,
|
||||
gatewayV2DAL
|
||||
gatewayV2DAL,
|
||||
projectDAL
|
||||
});
|
||||
|
||||
const secretSyncService = secretSyncServiceFactory({
|
||||
|
||||
@@ -26,6 +26,7 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
||||
description?: string | null;
|
||||
isPlatformManagedCredentials?: boolean;
|
||||
gatewayId?: string | null;
|
||||
projectId?: string;
|
||||
}>;
|
||||
updateSchema: z.ZodType<{
|
||||
name?: string;
|
||||
@@ -47,18 +48,27 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.AppConnections],
|
||||
description: `List the ${appName} Connections for the current organization.`,
|
||||
description: `List the ${appName} Connections for the current organization or project.`,
|
||||
querystring: z.object({
|
||||
projectId: z.string().optional().describe(AppConnections.LIST(app).projectId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({ appConnections: sanitizedResponseSchema.array() })
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const appConnections = (await server.services.appConnection.listAppConnectionsByOrg(req.permission, app)) as T[];
|
||||
const { projectId } = req.query;
|
||||
const appConnections = (await server.services.appConnection.listAppConnections(
|
||||
req.permission,
|
||||
app,
|
||||
projectId
|
||||
)) as T[];
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: req.permission.orgId,
|
||||
projectId,
|
||||
event: {
|
||||
type: EventType.GET_APP_CONNECTIONS,
|
||||
metadata: {
|
||||
@@ -82,14 +92,19 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.AppConnections],
|
||||
description: `List the ${appName} Connections the current user has permission to establish connections with.`,
|
||||
description: `List the ${appName} Connections the current user has permission to establish connections within this project.`,
|
||||
querystring: z.object({
|
||||
projectId: z.string().describe(AppConnections.LIST(app).projectId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
appConnections: z
|
||||
.object({
|
||||
app: z.literal(app),
|
||||
name: z.string(),
|
||||
id: z.string().uuid()
|
||||
id: z.string().uuid(),
|
||||
projectId: z.string().nullish(),
|
||||
orgId: z.string()
|
||||
})
|
||||
.array()
|
||||
})
|
||||
@@ -97,14 +112,17 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const { projectId } = req.query;
|
||||
const appConnections = await server.services.appConnection.listAvailableAppConnectionsForUser(
|
||||
app,
|
||||
req.permission
|
||||
req.permission,
|
||||
projectId
|
||||
);
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: req.permission.orgId,
|
||||
projectId,
|
||||
event: {
|
||||
type: EventType.GET_AVAILABLE_APP_CONNECTIONS_DETAILS,
|
||||
metadata: {
|
||||
@@ -149,6 +167,7 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: req.permission.orgId,
|
||||
projectId: appConnection.projectId ?? undefined,
|
||||
event: {
|
||||
type: EventType.GET_APP_CONNECTION,
|
||||
metadata: {
|
||||
@@ -178,6 +197,9 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
||||
.min(1, "Connection name required")
|
||||
.describe(AppConnections.GET_BY_NAME(app).connectionName)
|
||||
}),
|
||||
querystring: z.object({
|
||||
projectId: z.string().trim().optional().describe(AppConnections.GET_BY_NAME(app).projectId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({ appConnection: sanitizedResponseSchema })
|
||||
}
|
||||
@@ -185,16 +207,21 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const { connectionName } = req.params;
|
||||
const { projectId } = req.query;
|
||||
|
||||
const appConnection = (await server.services.appConnection.findAppConnectionByName(
|
||||
app,
|
||||
connectionName,
|
||||
{
|
||||
connectionName,
|
||||
projectId
|
||||
},
|
||||
req.permission
|
||||
)) as T;
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: req.permission.orgId,
|
||||
projectId: appConnection.projectId ?? undefined,
|
||||
event: {
|
||||
type: EventType.GET_APP_CONNECTION,
|
||||
metadata: {
|
||||
@@ -216,9 +243,7 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.AppConnections],
|
||||
description: `Create ${
|
||||
startsWithVowel(appName) ? "an" : "a"
|
||||
} ${appName} Connection for the current organization.`,
|
||||
description: `Create ${startsWithVowel(appName) ? "an" : "a"} ${appName} Connection.`,
|
||||
body: createSchema,
|
||||
response: {
|
||||
200: z.object({ appConnection: sanitizedResponseSchema })
|
||||
@@ -226,16 +251,17 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const { name, method, credentials, description, isPlatformManagedCredentials, gatewayId } = req.body;
|
||||
const { name, method, credentials, description, isPlatformManagedCredentials, gatewayId, projectId } = req.body;
|
||||
|
||||
const appConnection = (await server.services.appConnection.createAppConnection(
|
||||
{ name, method, app, credentials, description, isPlatformManagedCredentials, gatewayId },
|
||||
{ name, method, app, credentials, description, isPlatformManagedCredentials, gatewayId, projectId },
|
||||
req.permission
|
||||
)) as T;
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: req.permission.orgId,
|
||||
projectId,
|
||||
event: {
|
||||
type: EventType.CREATE_APP_CONNECTION,
|
||||
metadata: {
|
||||
@@ -283,6 +309,7 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: req.permission.orgId,
|
||||
projectId: appConnection.projectId ?? undefined,
|
||||
event: {
|
||||
type: EventType.UPDATE_APP_CONNECTION,
|
||||
metadata: {
|
||||
@@ -329,6 +356,7 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: req.permission.orgId,
|
||||
projectId: appConnection.projectId ?? undefined,
|
||||
event: {
|
||||
type: EventType.DELETE_APP_CONNECTION,
|
||||
metadata: {
|
||||
@@ -340,4 +368,81 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
||||
return { appConnection };
|
||||
}
|
||||
});
|
||||
|
||||
// scott: we will need this once we have individual app connection page and may want to expose to API
|
||||
// server.route({
|
||||
// method: "GET",
|
||||
// url: `/:connectionId/usage`,
|
||||
// config: {
|
||||
// rateLimit: readLimit
|
||||
// },
|
||||
// schema: {
|
||||
// hide: true, // scott: we could expose this in the future but just for UI right now
|
||||
// tags: [ApiDocsTags.AppConnections],
|
||||
// params: z.object({
|
||||
// connectionId: z.string().uuid()
|
||||
// }),
|
||||
// response: {
|
||||
// 200: z.object({
|
||||
// projects: z
|
||||
// .object({
|
||||
// id: z.string(),
|
||||
// name: z.string(),
|
||||
// type: z.nativeEnum(ProjectType),
|
||||
// slug: z.string(),
|
||||
// resources: z.object({
|
||||
// secretSyncs: z
|
||||
// .object({
|
||||
// id: z.string(),
|
||||
// name: z.string()
|
||||
// })
|
||||
// .array(),
|
||||
// secretRotations: z
|
||||
// .object({
|
||||
// id: z.string(),
|
||||
// name: z.string()
|
||||
// })
|
||||
// .array(),
|
||||
// externalCas: z
|
||||
// .object({
|
||||
// id: z.string(),
|
||||
// name: z.string()
|
||||
// })
|
||||
// .array(),
|
||||
// dataSources: z
|
||||
// .object({
|
||||
// id: z.string(),
|
||||
// name: z.string()
|
||||
// })
|
||||
// .array()
|
||||
// })
|
||||
// })
|
||||
// .array()
|
||||
// })
|
||||
// }
|
||||
// },
|
||||
// onRequest: verifyAuth([AuthMode.JWT]),
|
||||
// handler: async (req) => {
|
||||
// const { connectionId } = req.params;
|
||||
//
|
||||
// const projects = await server.services.appConnection.findAppConnectionUsageById(
|
||||
// app,
|
||||
// connectionId,
|
||||
// req.permission
|
||||
// );
|
||||
//
|
||||
// await server.services.auditLog.createAuditLog({
|
||||
// ...req.auditLogInfo,
|
||||
// orgId: req.permission.orgId,
|
||||
// event: {
|
||||
// type: EventType.GET_APP_CONNECTION_USAGE,
|
||||
// metadata: {
|
||||
// connectionId
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// return { projects };
|
||||
// }
|
||||
// });
|
||||
};
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { ProjectType } from "@app/db/schemas";
|
||||
import { OCIConnectionListItemSchema, SanitizedOCIConnectionSchema } from "@app/ee/services/app-connections/oci";
|
||||
import {
|
||||
OracleDBConnectionListItemSchema,
|
||||
SanitizedOracleDBConnectionSchema
|
||||
} from "@app/ee/services/app-connections/oracledb";
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { ApiDocsTags } from "@app/lib/api-docs";
|
||||
import { ApiDocsTags, AppConnections } from "@app/lib/api-docs";
|
||||
import { readLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import {
|
||||
@@ -210,6 +211,9 @@ export const registerAppConnectionRouter = async (server: FastifyZodProvider) =>
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.AppConnections],
|
||||
description: "List the available App Connection Options.",
|
||||
querystring: z.object({
|
||||
projectType: z.nativeEnum(ProjectType).optional()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
appConnectionOptions: AppConnectionOptionsSchema.array()
|
||||
@@ -217,8 +221,8 @@ export const registerAppConnectionRouter = async (server: FastifyZodProvider) =>
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: () => {
|
||||
const appConnectionOptions = server.services.appConnection.listAppConnectionOptions();
|
||||
handler: (req) => {
|
||||
const appConnectionOptions = server.services.appConnection.listAppConnectionOptions(req.query.projectType);
|
||||
return { appConnectionOptions };
|
||||
}
|
||||
});
|
||||
@@ -232,18 +236,27 @@ export const registerAppConnectionRouter = async (server: FastifyZodProvider) =>
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.AppConnections],
|
||||
description: "List all the App Connections for the current organization.",
|
||||
description: "List all the App Connections for the current organization or project.",
|
||||
querystring: z.object({
|
||||
projectId: z.string().optional().describe(AppConnections.LIST().projectId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({ appConnections: SanitizedAppConnectionSchema.array() })
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const appConnections = await server.services.appConnection.listAppConnectionsByOrg(req.permission);
|
||||
const { projectId } = req.query;
|
||||
const appConnections = await server.services.appConnection.listAppConnections(
|
||||
req.permission,
|
||||
undefined,
|
||||
projectId
|
||||
);
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: req.permission.orgId,
|
||||
projectId,
|
||||
event: {
|
||||
type: EventType.GET_APP_CONNECTIONS,
|
||||
metadata: {
|
||||
|
||||
@@ -1,11 +1,115 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
import { TableName, TAppConnections } from "@app/db/schemas";
|
||||
import { buildFindFilter, ormify, prependTableNameToFindFilter, selectAllTableCols } from "@app/lib/knex";
|
||||
import { transformUsageToProjects } from "@app/services/app-connection/app-connection-fns";
|
||||
|
||||
export type TAppConnectionDALFactory = ReturnType<typeof appConnectionDALFactory>;
|
||||
|
||||
type AppConnectionFindFilter = Parameters<typeof buildFindFilter<TAppConnections>>[0];
|
||||
|
||||
export const appConnectionDALFactory = (db: TDbClient) => {
|
||||
const appConnectionOrm = ormify(db, TableName.AppConnection);
|
||||
|
||||
return { ...appConnectionOrm };
|
||||
const findWithProjectDetails = async (filter: AppConnectionFindFilter, tx?: Knex) => {
|
||||
const query = (tx || db.replicaNode())(TableName.AppConnection)
|
||||
.leftJoin(TableName.Project, `${TableName.AppConnection}.projectId`, `${TableName.Project}.id`)
|
||||
.select(selectAllTableCols(TableName.AppConnection))
|
||||
.select(
|
||||
// project
|
||||
db.ref("name").withSchema(TableName.Project).as("projectName"),
|
||||
db.ref("type").withSchema(TableName.Project).as("projectType"),
|
||||
db.ref("slug").withSchema(TableName.Project).as("projectSlug")
|
||||
);
|
||||
|
||||
if (filter) {
|
||||
/* eslint-disable @typescript-eslint/no-misused-promises */
|
||||
void query.where(buildFindFilter(prependTableNameToFindFilter(TableName.AppConnection, filter)));
|
||||
}
|
||||
|
||||
const connections = await query;
|
||||
|
||||
return connections.map(({ projectName, projectSlug, projectType, projectId, ...connection }) => ({
|
||||
...connection,
|
||||
projectId,
|
||||
project: projectId
|
||||
? {
|
||||
name: projectName,
|
||||
type: projectType,
|
||||
slug: projectSlug,
|
||||
id: projectId
|
||||
}
|
||||
: null
|
||||
}));
|
||||
};
|
||||
|
||||
const findAppConnectionUsageById = async (connectionId: string, tx?: Knex) => {
|
||||
const secretSyncs = await (tx || db.replicaNode())(TableName.SecretSync)
|
||||
.where(`${TableName.SecretSync}.connectionId`, connectionId)
|
||||
.join(TableName.Project, `${TableName.SecretSync}.projectId`, `${TableName.Project}.id`)
|
||||
.select(
|
||||
db.ref("name").withSchema(TableName.SecretSync),
|
||||
db.ref("id").withSchema(TableName.SecretSync),
|
||||
db.ref("projectId").withSchema(TableName.SecretSync),
|
||||
db.ref("name").as("projectName").withSchema(TableName.Project),
|
||||
db.ref("slug").as("projectSlug").withSchema(TableName.Project),
|
||||
db.ref("type").as("projectType").withSchema(TableName.Project)
|
||||
);
|
||||
|
||||
const secretRotations = await (tx || db.replicaNode())(TableName.SecretRotationV2)
|
||||
.where(`${TableName.SecretRotationV2}.connectionId`, connectionId)
|
||||
.join(TableName.SecretFolder, `${TableName.SecretRotationV2}.folderId`, `${TableName.SecretFolder}.id`)
|
||||
.join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`)
|
||||
.join(TableName.Project, `${TableName.Environment}.projectId`, `${TableName.Project}.id`)
|
||||
.select(
|
||||
db.ref("name").withSchema(TableName.SecretRotationV2),
|
||||
db.ref("id").withSchema(TableName.SecretRotationV2),
|
||||
db.ref("id").as("projectId").withSchema(TableName.Project),
|
||||
db.ref("name").as("projectName").withSchema(TableName.Project),
|
||||
db.ref("slug").as("projectSlug").withSchema(TableName.Project),
|
||||
db.ref("type").as("projectType").withSchema(TableName.Project)
|
||||
);
|
||||
|
||||
const externalCas = await (tx || db.replicaNode())(TableName.ExternalCertificateAuthority)
|
||||
.where(`${TableName.ExternalCertificateAuthority}.appConnectionId`, connectionId)
|
||||
.orWhere(`${TableName.ExternalCertificateAuthority}.dnsAppConnectionId`, connectionId)
|
||||
.join(
|
||||
TableName.CertificateAuthority,
|
||||
`${TableName.ExternalCertificateAuthority}.caId`,
|
||||
`${TableName.CertificateAuthority}.id`
|
||||
)
|
||||
.join(TableName.Project, `${TableName.CertificateAuthority}.projectId`, `${TableName.Project}.id`)
|
||||
.select(
|
||||
db.ref("name").withSchema(TableName.CertificateAuthority),
|
||||
db.ref("id").withSchema(TableName.ExternalCertificateAuthority),
|
||||
db.ref("appConnectionId").withSchema(TableName.ExternalCertificateAuthority),
|
||||
db.ref("dnsAppConnectionId").withSchema(TableName.ExternalCertificateAuthority),
|
||||
db.ref("id").as("projectId").withSchema(TableName.Project),
|
||||
db.ref("name").as("projectName").withSchema(TableName.Project),
|
||||
db.ref("slug").as("projectSlug").withSchema(TableName.Project),
|
||||
db.ref("type").as("projectType").withSchema(TableName.Project)
|
||||
);
|
||||
|
||||
const dataSources = await (tx || db.replicaNode())(TableName.SecretScanningDataSource)
|
||||
.where(`${TableName.SecretScanningDataSource}.connectionId`, connectionId)
|
||||
.join(TableName.Project, `${TableName.SecretScanningDataSource}.projectId`, `${TableName.Project}.id`)
|
||||
.select(
|
||||
db.ref("name").withSchema(TableName.SecretScanningDataSource),
|
||||
db.ref("id").withSchema(TableName.SecretScanningDataSource),
|
||||
db.ref("id").as("projectId").withSchema(TableName.Project),
|
||||
db.ref("name").as("projectName").withSchema(TableName.Project),
|
||||
db.ref("slug").as("projectSlug").withSchema(TableName.Project),
|
||||
db.ref("type").as("projectType").withSchema(TableName.Project)
|
||||
);
|
||||
|
||||
return transformUsageToProjects({
|
||||
secretSyncs,
|
||||
secretRotations,
|
||||
dataSources,
|
||||
externalCas
|
||||
});
|
||||
};
|
||||
|
||||
return { ...appConnectionOrm, findAppConnectionUsageById, findWithProjectDetails };
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ProjectType } from "@app/db/schemas";
|
||||
import { TAppConnections } from "@app/db/schemas/app-connections";
|
||||
import {
|
||||
getOCIConnectionListItem,
|
||||
@@ -8,6 +9,8 @@ import { getOracleDBConnectionListItem, OracleDBConnectionMethod } from "@app/ee
|
||||
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||
import { TGatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2-service";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { SECRET_ROTATION_CONNECTION_MAP } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-maps";
|
||||
import { SECRET_SCANNING_DATA_SOURCE_CONNECTION_MAP } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-maps";
|
||||
import { crypto } from "@app/lib/crypto/cryptography";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { APP_CONNECTION_NAME_MAP, APP_CONNECTION_PLAN_MAP } from "@app/services/app-connection/app-connection-maps";
|
||||
@@ -16,6 +19,7 @@ import {
|
||||
validateSqlConnectionCredentials
|
||||
} from "@app/services/app-connection/shared/sql";
|
||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||
import { SECRET_SYNC_CONNECTION_MAP } from "@app/services/secret-sync/secret-sync-maps";
|
||||
|
||||
import {
|
||||
getOnePassConnectionListItem,
|
||||
@@ -133,7 +137,19 @@ import {
|
||||
} from "./windmill";
|
||||
import { getZabbixConnectionListItem, validateZabbixConnectionCredentials, ZabbixConnectionMethod } from "./zabbix";
|
||||
|
||||
export const listAppConnectionOptions = () => {
|
||||
const SECRET_SYNC_APP_CONNECTION_MAP = Object.fromEntries(
|
||||
Object.entries(SECRET_SYNC_CONNECTION_MAP).map(([key, value]) => [value, key])
|
||||
);
|
||||
|
||||
const SECRET_ROTATION_APP_CONNECTION_MAP = Object.fromEntries(
|
||||
Object.entries(SECRET_ROTATION_CONNECTION_MAP).map(([key, value]) => [value, key])
|
||||
);
|
||||
|
||||
const SECRET_SCANNING_APP_CONNECTION_MAP = Object.fromEntries(
|
||||
Object.entries(SECRET_SCANNING_DATA_SOURCE_CONNECTION_MAP).map(([key, value]) => [value, key])
|
||||
);
|
||||
|
||||
export const listAppConnectionOptions = (projectType?: ProjectType) => {
|
||||
return [
|
||||
getAwsConnectionListItem(),
|
||||
getGitHubConnectionListItem(),
|
||||
@@ -173,22 +189,55 @@ export const listAppConnectionOptions = () => {
|
||||
getDigitalOceanConnectionListItem(),
|
||||
getNetlifyConnectionListItem(),
|
||||
getOktaConnectionListItem()
|
||||
].sort((a, b) => a.name.localeCompare(b.name));
|
||||
]
|
||||
.filter((option) => {
|
||||
switch (projectType) {
|
||||
case ProjectType.SecretManager:
|
||||
return (
|
||||
Boolean(SECRET_SYNC_APP_CONNECTION_MAP[option.app]) ||
|
||||
Boolean(SECRET_ROTATION_APP_CONNECTION_MAP[option.app])
|
||||
);
|
||||
case ProjectType.SecretScanning:
|
||||
return Boolean(SECRET_SCANNING_APP_CONNECTION_MAP[option.app]);
|
||||
case ProjectType.CertificateManager:
|
||||
return (
|
||||
option.app === AppConnection.AWS ||
|
||||
option.app === AppConnection.Cloudflare ||
|
||||
option.app === AppConnection.AzureADCS
|
||||
);
|
||||
case ProjectType.KMS:
|
||||
return false;
|
||||
case ProjectType.SSH:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
})
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
};
|
||||
|
||||
export const encryptAppConnectionCredentials = async ({
|
||||
orgId,
|
||||
credentials,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId
|
||||
}: {
|
||||
orgId: string;
|
||||
credentials: TAppConnection["credentials"];
|
||||
kmsService: TAppConnectionServiceFactoryDep["kmsService"];
|
||||
projectId: string | null | undefined;
|
||||
}) => {
|
||||
const { encryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
type: KmsDataKey.Organization,
|
||||
orgId
|
||||
});
|
||||
const { encryptor } = await kmsService.createCipherPairWithDataKey(
|
||||
projectId
|
||||
? {
|
||||
type: KmsDataKey.SecretManager,
|
||||
projectId
|
||||
}
|
||||
: {
|
||||
type: KmsDataKey.Organization,
|
||||
orgId
|
||||
}
|
||||
);
|
||||
|
||||
const { cipherTextBlob: encryptedCredentialsBlob } = encryptor({
|
||||
plainText: Buffer.from(JSON.stringify(credentials))
|
||||
@@ -200,16 +249,22 @@ export const encryptAppConnectionCredentials = async ({
|
||||
export const decryptAppConnectionCredentials = async ({
|
||||
orgId,
|
||||
encryptedCredentials,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId
|
||||
}: {
|
||||
orgId: string;
|
||||
encryptedCredentials: Buffer;
|
||||
kmsService: TAppConnectionServiceFactoryDep["kmsService"];
|
||||
projectId: string | null | undefined;
|
||||
}) => {
|
||||
const { decryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
type: KmsDataKey.Organization,
|
||||
orgId
|
||||
});
|
||||
const { decryptor } = await kmsService.createCipherPairWithDataKey(
|
||||
projectId
|
||||
? { type: KmsDataKey.SecretManager, projectId }
|
||||
: {
|
||||
type: KmsDataKey.Organization,
|
||||
orgId
|
||||
}
|
||||
);
|
||||
|
||||
const decryptedPlainTextBlob = decryptor({
|
||||
cipherTextBlob: encryptedCredentials
|
||||
@@ -343,6 +398,7 @@ export const decryptAppConnection = async (
|
||||
credentials: await decryptAppConnectionCredentials({
|
||||
encryptedCredentials: appConnection.encryptedCredentials,
|
||||
orgId: appConnection.orgId,
|
||||
projectId: appConnection.projectId,
|
||||
kmsService
|
||||
}),
|
||||
credentialsHash: crypto.nativeCrypto.createHash("sha256").update(appConnection.encryptedCredentials).digest("hex")
|
||||
@@ -413,3 +469,73 @@ export const enterpriseAppCheck = async (
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
type Resource = {
|
||||
name: string;
|
||||
id: string;
|
||||
projectId: string;
|
||||
projectName: string;
|
||||
projectSlug: string;
|
||||
projectType: string;
|
||||
};
|
||||
|
||||
type UsageData = {
|
||||
secretSyncs: Resource[];
|
||||
secretRotations: Resource[];
|
||||
dataSources: Resource[];
|
||||
externalCas: Resource[];
|
||||
};
|
||||
|
||||
type ResourceSummary = {
|
||||
name: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
type ProjectWithResources = {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
type: ProjectType;
|
||||
resources: {
|
||||
secretSyncs: ResourceSummary[];
|
||||
secretRotations: ResourceSummary[];
|
||||
dataSources: ResourceSummary[];
|
||||
externalCas: (ResourceSummary & { appConnectionId?: string; dnsAppConnectionId?: string })[];
|
||||
};
|
||||
};
|
||||
|
||||
export const transformUsageToProjects = (data: UsageData): ProjectWithResources[] => {
|
||||
const projectMap = new Map<string, ProjectWithResources>();
|
||||
|
||||
Object.entries(data).forEach(([resourceType, resources]) => {
|
||||
resources.forEach((resource) => {
|
||||
const { projectId, projectName, projectSlug, projectType, name, id, ...rest } = resource;
|
||||
|
||||
const projectKey = projectId;
|
||||
|
||||
if (!projectMap.has(projectKey)) {
|
||||
projectMap.set(projectKey, {
|
||||
id: projectId,
|
||||
name: projectName,
|
||||
slug: projectSlug,
|
||||
type: projectType as ProjectType,
|
||||
resources: {
|
||||
secretSyncs: [],
|
||||
secretRotations: [],
|
||||
dataSources: [],
|
||||
externalCas: []
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const project = projectMap.get(projectKey)!;
|
||||
project.resources[resourceType as keyof ProjectWithResources["resources"]].push({
|
||||
name,
|
||||
id,
|
||||
...rest
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return Array.from(projectMap.values());
|
||||
};
|
||||
|
||||
@@ -13,7 +13,15 @@ export const BaseAppConnectionSchema = AppConnectionsSchema.omit({
|
||||
app: true,
|
||||
method: true
|
||||
}).extend({
|
||||
credentialsHash: z.string().optional()
|
||||
credentialsHash: z.string().optional(),
|
||||
project: z
|
||||
.object({
|
||||
name: z.string(),
|
||||
id: z.string(),
|
||||
type: z.string(),
|
||||
slug: z.string()
|
||||
})
|
||||
.nullish()
|
||||
});
|
||||
|
||||
export const GenericCreateAppConnectionFieldsSchema = (
|
||||
@@ -28,6 +36,7 @@ export const GenericCreateAppConnectionFieldsSchema = (
|
||||
.max(256, "Description cannot exceed 256 characters")
|
||||
.nullish()
|
||||
.describe(AppConnections.CREATE(app).description),
|
||||
projectId: z.string().optional().describe(AppConnections.CREATE(app).projectId),
|
||||
isPlatformManagedCredentials: supportsPlatformManagedCredentials
|
||||
? z.boolean().optional().default(false).describe(AppConnections.CREATE(app).isPlatformManagedCredentials)
|
||||
: z
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
|
||||
import { ActionProjectType, TAppConnections } from "@app/db/schemas";
|
||||
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";
|
||||
@@ -14,6 +15,10 @@ import {
|
||||
OrgPermissionSubjects
|
||||
} from "@app/ee/services/permission/org-permission";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import {
|
||||
ProjectPermissionAppConnectionActions,
|
||||
ProjectPermissionSub
|
||||
} from "@app/ee/services/permission/project-permission";
|
||||
import { crypto } from "@app/lib/crypto/cryptography";
|
||||
import { DatabaseErrorCode } from "@app/lib/error-codes";
|
||||
import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors";
|
||||
@@ -27,9 +32,8 @@ import {
|
||||
TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM,
|
||||
validateAppConnectionCredentials
|
||||
} from "@app/services/app-connection/app-connection-fns";
|
||||
import { auth0ConnectionService } from "@app/services/app-connection/auth0/auth0-connection-service";
|
||||
import { githubRadarConnectionService } from "@app/services/app-connection/github-radar/github-radar-connection-service";
|
||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
|
||||
import { ValidateOnePassConnectionCredentialsSchema } from "./1password";
|
||||
import { onePassConnectionService } from "./1password/1password-connection-service";
|
||||
@@ -41,10 +45,13 @@ import {
|
||||
TAppConnectionConfig,
|
||||
TAppConnectionRaw,
|
||||
TCreateAppConnectionDTO,
|
||||
TGetAppConnectionByNameDTO,
|
||||
TUpdateAppConnectionDTO,
|
||||
TValidateAppConnectionCredentialsSchema
|
||||
TValidateAppConnectionCredentialsSchema,
|
||||
TValidateAppConnectionUsageByIdDTO
|
||||
} from "./app-connection-types";
|
||||
import { ValidateAuth0ConnectionCredentialsSchema } from "./auth0";
|
||||
import { auth0ConnectionService } from "./auth0/auth0-connection-service";
|
||||
import { ValidateAwsConnectionCredentialsSchema } from "./aws";
|
||||
import { awsConnectionService } from "./aws/aws-connection-service";
|
||||
import { ValidateAzureADCSConnectionCredentialsSchema } from "./azure-adcs/azure-adcs-connection-schemas";
|
||||
@@ -73,6 +80,7 @@ import { gcpConnectionService } from "./gcp/gcp-connection-service";
|
||||
import { ValidateGitHubConnectionCredentialsSchema } from "./github";
|
||||
import { githubConnectionService } from "./github/github-connection-service";
|
||||
import { ValidateGitHubRadarConnectionCredentialsSchema } from "./github-radar";
|
||||
import { githubRadarConnectionService } from "./github-radar/github-radar-connection-service";
|
||||
import { ValidateGitLabConnectionCredentialsSchema } from "./gitlab";
|
||||
import { gitlabConnectionService } from "./gitlab/gitlab-connection-service";
|
||||
import { ValidateHCVaultConnectionCredentialsSchema } from "./hc-vault";
|
||||
@@ -108,13 +116,14 @@ import { zabbixConnectionService } from "./zabbix/zabbix-connection-service";
|
||||
|
||||
export type TAppConnectionServiceFactoryDep = {
|
||||
appConnectionDAL: TAppConnectionDALFactory;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getProjectPermission">;
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">;
|
||||
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">;
|
||||
gatewayDAL: Pick<TGatewayDALFactory, "find">;
|
||||
gatewayV2DAL: Pick<TGatewayV2DALFactory, "find">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findProjectById">;
|
||||
};
|
||||
|
||||
export type TAppConnectionServiceFactory = ReturnType<typeof appConnectionServiceFactory>;
|
||||
@@ -168,29 +177,64 @@ export const appConnectionServiceFactory = ({
|
||||
gatewayService,
|
||||
gatewayV2Service,
|
||||
gatewayDAL,
|
||||
gatewayV2DAL
|
||||
gatewayV2DAL,
|
||||
projectDAL
|
||||
}: TAppConnectionServiceFactoryDep) => {
|
||||
const listAppConnectionsByOrg = async (actor: OrgServiceActor, app?: AppConnection) => {
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor.type,
|
||||
actor.id,
|
||||
actor.orgId,
|
||||
actor.authMethod,
|
||||
actor.orgId
|
||||
);
|
||||
const listAppConnections = async (actor: OrgServiceActor, app?: AppConnection, projectId?: string) => {
|
||||
let appConnections: TAppConnections[];
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionAppConnectionActions.Read,
|
||||
OrgPermissionSubjects.AppConnections
|
||||
);
|
||||
if (projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor: actor.type,
|
||||
actorId: actor.id,
|
||||
projectId,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
|
||||
const appConnections = await appConnectionDAL.find(
|
||||
app
|
||||
? { orgId: actor.orgId, app }
|
||||
: {
|
||||
orgId: actor.orgId
|
||||
}
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionAppConnectionActions.Read,
|
||||
ProjectPermissionSub.AppConnections
|
||||
);
|
||||
|
||||
appConnections = (
|
||||
await appConnectionDAL.findWithProjectDetails({
|
||||
projectId,
|
||||
...(app ? { app } : {})
|
||||
})
|
||||
).filter((appConnection) =>
|
||||
permission.can(
|
||||
ProjectPermissionAppConnectionActions.Read,
|
||||
subject(ProjectPermissionSub.AppConnections, { connectionId: appConnection.id })
|
||||
)
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor.type,
|
||||
actor.id,
|
||||
actor.orgId,
|
||||
actor.authMethod,
|
||||
actor.orgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionAppConnectionActions.Read,
|
||||
OrgPermissionSubjects.AppConnections
|
||||
);
|
||||
|
||||
appConnections = (
|
||||
await appConnectionDAL.findWithProjectDetails({
|
||||
orgId: actor.orgId,
|
||||
...(app ? { app } : {})
|
||||
})
|
||||
).filter((appConnection) =>
|
||||
permission.can(
|
||||
OrgPermissionAppConnectionActions.Read,
|
||||
subject(OrgPermissionSubjects.AppConnections, { connectionId: appConnection.id })
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.all(
|
||||
appConnections
|
||||
@@ -204,18 +248,34 @@ export const appConnectionServiceFactory = ({
|
||||
|
||||
if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` });
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor.type,
|
||||
actor.id,
|
||||
actor.orgId,
|
||||
actor.authMethod,
|
||||
appConnection.orgId
|
||||
);
|
||||
if (appConnection.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor: actor.type,
|
||||
actorId: actor.id,
|
||||
projectId: appConnection.projectId,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionAppConnectionActions.Read,
|
||||
OrgPermissionSubjects.AppConnections
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionAppConnectionActions.Read,
|
||||
subject(ProjectPermissionSub.AppConnections, { connectionId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor.type,
|
||||
actor.id,
|
||||
actor.orgId,
|
||||
actor.authMethod,
|
||||
appConnection.orgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionAppConnectionActions.Read,
|
||||
subject(OrgPermissionSubjects.AppConnections, { connectionId })
|
||||
);
|
||||
}
|
||||
|
||||
if (appConnection.app !== app)
|
||||
throw new BadRequestError({ message: `App Connection with ID ${connectionId} is not for App "${app}"` });
|
||||
@@ -223,24 +283,49 @@ export const appConnectionServiceFactory = ({
|
||||
return decryptAppConnection(appConnection, kmsService);
|
||||
};
|
||||
|
||||
const findAppConnectionByName = async (app: AppConnection, connectionName: string, actor: OrgServiceActor) => {
|
||||
const appConnection = await appConnectionDAL.findOne({ name: connectionName, orgId: actor.orgId });
|
||||
const findAppConnectionByName = async (
|
||||
app: AppConnection,
|
||||
{ connectionName, projectId }: TGetAppConnectionByNameDTO,
|
||||
actor: OrgServiceActor
|
||||
) => {
|
||||
const appConnection = await appConnectionDAL.findOne({
|
||||
name: connectionName,
|
||||
...(projectId ? { projectId } : { orgId: actor.orgId, projectId: null })
|
||||
});
|
||||
|
||||
if (!appConnection)
|
||||
throw new NotFoundError({ message: `Could not find App Connection with name ${connectionName}` });
|
||||
throw new NotFoundError({
|
||||
message: `Could not find App Connection with name ${connectionName} in ${projectId ? "project" : "organization"} scope.`
|
||||
});
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor.type,
|
||||
actor.id,
|
||||
actor.orgId,
|
||||
actor.authMethod,
|
||||
appConnection.orgId
|
||||
);
|
||||
if (appConnection.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor: actor.type,
|
||||
actorId: actor.id,
|
||||
projectId: appConnection.projectId,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionAppConnectionActions.Read,
|
||||
OrgPermissionSubjects.AppConnections
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionAppConnectionActions.Read,
|
||||
subject(ProjectPermissionSub.AppConnections, { connectionId: appConnection.id })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor.type,
|
||||
actor.id,
|
||||
actor.orgId,
|
||||
actor.authMethod,
|
||||
appConnection.orgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionAppConnectionActions.Read,
|
||||
subject(OrgPermissionSubjects.AppConnections, { connectionId: appConnection.id })
|
||||
);
|
||||
}
|
||||
|
||||
if (appConnection.app !== app)
|
||||
throw new BadRequestError({ message: `App Connection with name ${connectionName} is not for App "${app}"` });
|
||||
@@ -249,10 +334,10 @@ export const appConnectionServiceFactory = ({
|
||||
};
|
||||
|
||||
const createAppConnection = async (
|
||||
{ method, app, credentials, gatewayId, ...params }: TCreateAppConnectionDTO,
|
||||
{ method, app, credentials, gatewayId, projectId, ...params }: TCreateAppConnectionDTO,
|
||||
actor: OrgServiceActor
|
||||
) => {
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
const { permission: orgPermission } = await permissionService.getOrgPermission(
|
||||
actor.type,
|
||||
actor.id,
|
||||
actor.orgId,
|
||||
@@ -260,13 +345,33 @@ export const appConnectionServiceFactory = ({
|
||||
actor.orgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionAppConnectionActions.Create,
|
||||
OrgPermissionSubjects.AppConnections
|
||||
);
|
||||
if (projectId) {
|
||||
const project = await projectDAL.findProjectById(projectId);
|
||||
|
||||
if (!project) throw new BadRequestError({ message: `Could not find project with ID ${projectId}` });
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor: actor.type,
|
||||
actorId: actor.id,
|
||||
projectId,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionAppConnectionActions.Create,
|
||||
ProjectPermissionSub.AppConnections
|
||||
);
|
||||
} else {
|
||||
ForbiddenError.from(orgPermission).throwUnlessCan(
|
||||
OrgPermissionAppConnectionActions.Create,
|
||||
OrgPermissionSubjects.AppConnections
|
||||
);
|
||||
}
|
||||
|
||||
if (gatewayId) {
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ForbiddenError.from(orgPermission).throwUnlessCan(
|
||||
OrgPermissionGatewayActions.AttachGateways,
|
||||
OrgPermissionSubjects.Gateway
|
||||
);
|
||||
@@ -304,7 +409,8 @@ export const appConnectionServiceFactory = ({
|
||||
const encryptedCredentials = await encryptAppConnectionCredentials({
|
||||
credentials: connectionCredentials,
|
||||
orgId: actor.orgId,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId
|
||||
});
|
||||
|
||||
return appConnectionDAL.create({
|
||||
@@ -313,6 +419,7 @@ export const appConnectionServiceFactory = ({
|
||||
method,
|
||||
app,
|
||||
gatewayId,
|
||||
projectId,
|
||||
...params
|
||||
});
|
||||
};
|
||||
@@ -365,7 +472,7 @@ export const appConnectionServiceFactory = ({
|
||||
"Failed to update app connection due to plan restriction. Upgrade plan to access enterprise app connections."
|
||||
);
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
const { permission: orgPermission } = await permissionService.getOrgPermission(
|
||||
actor.type,
|
||||
actor.id,
|
||||
actor.orgId,
|
||||
@@ -373,13 +480,29 @@ export const appConnectionServiceFactory = ({
|
||||
appConnection.orgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionAppConnectionActions.Edit,
|
||||
OrgPermissionSubjects.AppConnections
|
||||
);
|
||||
if (appConnection.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor: actor.type,
|
||||
actorId: actor.id,
|
||||
projectId: appConnection.projectId,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
|
||||
if (gatewayId !== appConnection.gatewayId) {
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionAppConnectionActions.Edit,
|
||||
subject(ProjectPermissionSub.AppConnections, { connectionId })
|
||||
);
|
||||
} else {
|
||||
ForbiddenError.from(orgPermission).throwUnlessCan(
|
||||
OrgPermissionAppConnectionActions.Edit,
|
||||
subject(OrgPermissionSubjects.AppConnections, { connectionId })
|
||||
);
|
||||
}
|
||||
|
||||
if (gatewayId !== undefined && gatewayId !== appConnection.gatewayId) {
|
||||
ForbiddenError.from(orgPermission).throwUnlessCan(
|
||||
OrgPermissionGatewayActions.AttachGateways,
|
||||
OrgPermissionSubjects.Gateway
|
||||
);
|
||||
@@ -441,7 +564,8 @@ export const appConnectionServiceFactory = ({
|
||||
? await encryptAppConnectionCredentials({
|
||||
credentials: connectionCredentials,
|
||||
orgId: actor.orgId,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId: appConnection.projectId
|
||||
})
|
||||
: undefined;
|
||||
|
||||
@@ -491,18 +615,34 @@ export const appConnectionServiceFactory = ({
|
||||
|
||||
if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` });
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor.type,
|
||||
actor.id,
|
||||
actor.orgId,
|
||||
actor.authMethod,
|
||||
appConnection.orgId
|
||||
);
|
||||
if (appConnection.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor: actor.type,
|
||||
actorId: actor.id,
|
||||
projectId: appConnection.projectId,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionAppConnectionActions.Delete,
|
||||
OrgPermissionSubjects.AppConnections
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionAppConnectionActions.Delete,
|
||||
subject(ProjectPermissionSub.AppConnections, { connectionId })
|
||||
);
|
||||
} else {
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor.type,
|
||||
actor.id,
|
||||
actor.orgId,
|
||||
actor.authMethod,
|
||||
appConnection.orgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionAppConnectionActions.Delete,
|
||||
subject(OrgPermissionSubjects.AppConnections, { connectionId })
|
||||
);
|
||||
}
|
||||
|
||||
if (appConnection.app !== app)
|
||||
throw new BadRequestError({ message: `App Connection with ID ${connectionId} is not for App "${app}"` });
|
||||
@@ -544,18 +684,34 @@ export const appConnectionServiceFactory = ({
|
||||
"Failed to connect app due to plan restriction. Upgrade plan to access enterprise app connections."
|
||||
);
|
||||
|
||||
const { permission: orgPermission } = await permissionService.getOrgPermission(
|
||||
actor.type,
|
||||
actor.id,
|
||||
appConnection.orgId,
|
||||
actor.authMethod,
|
||||
actor.orgId
|
||||
);
|
||||
if (appConnection.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor: actor.type,
|
||||
actorId: actor.id,
|
||||
projectId: appConnection.projectId,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
|
||||
ForbiddenError.from(orgPermission).throwUnlessCan(
|
||||
OrgPermissionAppConnectionActions.Connect,
|
||||
subject(OrgPermissionSubjects.AppConnections, { connectionId: appConnection.id })
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionAppConnectionActions.Connect,
|
||||
subject(ProjectPermissionSub.AppConnections, { connectionId })
|
||||
);
|
||||
} else {
|
||||
const { permission: orgPermission } = await permissionService.getOrgPermission(
|
||||
actor.type,
|
||||
actor.id,
|
||||
appConnection.orgId,
|
||||
actor.authMethod,
|
||||
actor.orgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(orgPermission).throwUnlessCan(
|
||||
OrgPermissionAppConnectionActions.Connect,
|
||||
subject(OrgPermissionSubjects.AppConnections, { connectionId })
|
||||
);
|
||||
}
|
||||
|
||||
if (appConnection.app !== app)
|
||||
throw new BadRequestError({
|
||||
@@ -569,7 +725,41 @@ export const appConnectionServiceFactory = ({
|
||||
return connection as T;
|
||||
};
|
||||
|
||||
const listAvailableAppConnectionsForUser = async (app: AppConnection, actor: OrgServiceActor) => {
|
||||
const validateAppConnectionUsageById = async (
|
||||
app: AppConnection,
|
||||
{ connectionId, projectId }: TValidateAppConnectionUsageByIdDTO,
|
||||
actor: OrgServiceActor
|
||||
) => {
|
||||
const appConnection = await connectAppConnectionById(app, connectionId, actor);
|
||||
|
||||
if (appConnection.projectId && appConnection.projectId !== projectId) {
|
||||
throw new BadRequestError({
|
||||
message: `You cannot connect project App Connection with ID "${appConnection.id}" from project with ID "${appConnection.projectId}" to project with ID "${projectId}"`
|
||||
});
|
||||
}
|
||||
|
||||
return appConnection;
|
||||
};
|
||||
|
||||
const listAvailableAppConnectionsForUser = async (app: AppConnection, actor: OrgServiceActor, projectId: string) => {
|
||||
const project = await projectDAL.findProjectById(projectId);
|
||||
|
||||
if (!project) throw new BadRequestError({ message: `Could not find project with ID ${projectId}` });
|
||||
|
||||
const { permission: projectPermission } = await permissionService.getProjectPermission({
|
||||
actor: actor.type,
|
||||
actorId: actor.id,
|
||||
projectId,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
|
||||
ForbiddenError.from(projectPermission).throwUnlessCan(
|
||||
ProjectPermissionAppConnectionActions.Connect,
|
||||
ProjectPermissionSub.AppConnections
|
||||
);
|
||||
|
||||
const { permission: orgPermission } = await permissionService.getOrgPermission(
|
||||
actor.type,
|
||||
actor.id,
|
||||
@@ -578,28 +768,67 @@ export const appConnectionServiceFactory = ({
|
||||
actor.orgId
|
||||
);
|
||||
|
||||
const appConnections = await appConnectionDAL.find({ app, orgId: actor.orgId });
|
||||
const orgAppConnections = await appConnectionDAL.find({ app, orgId: actor.orgId, projectId: null });
|
||||
|
||||
const availableConnections = appConnections.filter((connection) =>
|
||||
const availableOrgConnections = orgAppConnections.filter((connection) =>
|
||||
orgPermission.can(
|
||||
OrgPermissionAppConnectionActions.Connect,
|
||||
subject(OrgPermissionSubjects.AppConnections, { connectionId: connection.id })
|
||||
)
|
||||
);
|
||||
|
||||
return availableConnections as Omit<TAppConnection, "credentials">[];
|
||||
const projectAppConnections = await appConnectionDAL.find({ app, projectId });
|
||||
|
||||
const availableProjectConnections = projectAppConnections.filter((connection) =>
|
||||
projectPermission.can(
|
||||
ProjectPermissionAppConnectionActions.Connect,
|
||||
subject(ProjectPermissionSub.AppConnections, { connectionId: connection.id })
|
||||
)
|
||||
);
|
||||
|
||||
return [...availableOrgConnections, ...availableProjectConnections].sort((a, b) =>
|
||||
a.name.toLowerCase().localeCompare(b.name.toLowerCase())
|
||||
) as Omit<TAppConnection, "credentials">[];
|
||||
};
|
||||
|
||||
const findAppConnectionUsageById = async (app: AppConnection, connectionId: string, actor: OrgServiceActor) => {
|
||||
const appConnection = await appConnectionDAL.findById(connectionId);
|
||||
|
||||
if (!appConnection) throw new NotFoundError({ message: `Could not find App Connection with ID ${connectionId}` });
|
||||
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor.type,
|
||||
actor.id,
|
||||
actor.orgId,
|
||||
actor.authMethod,
|
||||
appConnection.orgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
OrgPermissionAppConnectionActions.Read,
|
||||
OrgPermissionSubjects.AppConnections
|
||||
);
|
||||
|
||||
if (appConnection.app !== app)
|
||||
throw new BadRequestError({ message: `App Connection with ID ${connectionId} is not for App "${app}"` });
|
||||
|
||||
const projectUsage = await appConnectionDAL.findAppConnectionUsageById(connectionId);
|
||||
|
||||
return projectUsage;
|
||||
};
|
||||
|
||||
return {
|
||||
listAppConnectionOptions,
|
||||
listAppConnectionsByOrg,
|
||||
listAppConnections,
|
||||
findAppConnectionById,
|
||||
findAppConnectionByName,
|
||||
createAppConnection,
|
||||
updateAppConnection,
|
||||
deleteAppConnection,
|
||||
connectAppConnectionById,
|
||||
validateAppConnectionUsageById,
|
||||
listAvailableAppConnectionsForUser,
|
||||
findAppConnectionUsageById,
|
||||
github: githubConnectionService(connectAppConnectionById, gatewayService, gatewayV2Service),
|
||||
githubRadar: githubRadarConnectionService(connectAppConnectionById),
|
||||
gcp: gcpConnectionService(connectAppConnectionById),
|
||||
|
||||
@@ -316,13 +316,23 @@ export type TSqlConnectionInput =
|
||||
|
||||
export type TCreateAppConnectionDTO = Pick<
|
||||
TAppConnectionInput,
|
||||
"credentials" | "method" | "name" | "app" | "description" | "isPlatformManagedCredentials" | "gatewayId"
|
||||
"credentials" | "method" | "name" | "app" | "description" | "isPlatformManagedCredentials" | "gatewayId" | "projectId"
|
||||
>;
|
||||
|
||||
export type TUpdateAppConnectionDTO = Partial<Omit<TCreateAppConnectionDTO, "method" | "app">> & {
|
||||
export type TUpdateAppConnectionDTO = Partial<Omit<TCreateAppConnectionDTO, "method" | "app" | "projectId">> & {
|
||||
connectionId: string;
|
||||
};
|
||||
|
||||
export type TGetAppConnectionByNameDTO = {
|
||||
connectionName: string;
|
||||
projectId?: string;
|
||||
};
|
||||
|
||||
export type TValidateAppConnectionUsageByIdDTO = {
|
||||
connectionId: string;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export type TAppConnectionConfig =
|
||||
| TAwsConnectionConfig
|
||||
| TGitHubConnectionConfig
|
||||
|
||||
@@ -51,7 +51,7 @@ const authorizeAuth0Connection = async ({
|
||||
};
|
||||
|
||||
export const getAuth0ConnectionAccessToken = async (
|
||||
{ id, orgId, credentials }: TAuth0Connection,
|
||||
{ id, orgId, credentials, projectId }: TAuth0Connection,
|
||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
||||
) => {
|
||||
@@ -72,7 +72,8 @@ export const getAuth0ConnectionAccessToken = async (
|
||||
const encryptedCredentials = await encryptAppConnectionCredentials({
|
||||
credentials: updatedCredentials,
|
||||
orgId,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId
|
||||
});
|
||||
|
||||
await appConnectionDAL.updateById(id, { encryptedCredentials });
|
||||
|
||||
@@ -352,7 +352,8 @@ export const getAzureADCSConnectionCredentials = async (
|
||||
const credentials = (await decryptAppConnectionCredentials({
|
||||
orgId: appConnection.orgId,
|
||||
kmsService,
|
||||
encryptedCredentials: appConnection.encryptedCredentials
|
||||
encryptedCredentials: appConnection.encryptedCredentials,
|
||||
projectId: appConnection.projectId
|
||||
})) as {
|
||||
username: string;
|
||||
password: string;
|
||||
|
||||
@@ -57,6 +57,7 @@ export const getAzureConnectionAccessToken = async (
|
||||
const credentials = (await decryptAppConnectionCredentials({
|
||||
orgId: appConnection.orgId,
|
||||
kmsService,
|
||||
projectId: appConnection.projectId,
|
||||
encryptedCredentials: appConnection.encryptedCredentials
|
||||
})) as TAzureClientSecretsConnectionCredentials;
|
||||
|
||||
@@ -93,6 +94,7 @@ export const getAzureConnectionAccessToken = async (
|
||||
const encryptedCredentials = await encryptAppConnectionCredentials({
|
||||
credentials: updatedCredentials,
|
||||
orgId: appConnection.orgId,
|
||||
projectId: appConnection.projectId,
|
||||
kmsService
|
||||
});
|
||||
|
||||
@@ -102,6 +104,7 @@ export const getAzureConnectionAccessToken = async (
|
||||
case AzureClientSecretsConnectionMethod.ClientSecret:
|
||||
const accessTokenCredentials = (await decryptAppConnectionCredentials({
|
||||
orgId: appConnection.orgId,
|
||||
projectId: appConnection.projectId,
|
||||
kmsService,
|
||||
encryptedCredentials: appConnection.encryptedCredentials
|
||||
})) as TAzureClientSecretsConnectionClientSecretCredentials;
|
||||
@@ -129,6 +132,7 @@ export const getAzureConnectionAccessToken = async (
|
||||
const encryptedClientCredentials = await encryptAppConnectionCredentials({
|
||||
credentials: updatedClientCredentials,
|
||||
orgId: appConnection.orgId,
|
||||
projectId: appConnection.projectId,
|
||||
kmsService
|
||||
});
|
||||
|
||||
|
||||
@@ -70,7 +70,8 @@ export const getAzureDevopsConnection = async (
|
||||
const oauthCredentials = (await decryptAppConnectionCredentials({
|
||||
orgId: appConnection.orgId,
|
||||
kmsService,
|
||||
encryptedCredentials: appConnection.encryptedCredentials
|
||||
encryptedCredentials: appConnection.encryptedCredentials,
|
||||
projectId: appConnection.projectId
|
||||
})) as TAzureDevOpsConnectionCredentials;
|
||||
|
||||
if (!("refreshToken" in oauthCredentials)) {
|
||||
@@ -100,7 +101,8 @@ export const getAzureDevopsConnection = async (
|
||||
const encryptedOAuthCredentials = await encryptAppConnectionCredentials({
|
||||
credentials: updatedOAuthCredentials,
|
||||
orgId: appConnection.orgId,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId: appConnection.projectId
|
||||
});
|
||||
|
||||
await appConnectionDAL.updateById(appConnection.id, { encryptedCredentials: encryptedOAuthCredentials });
|
||||
@@ -111,7 +113,8 @@ export const getAzureDevopsConnection = async (
|
||||
const accessTokenCredentials = (await decryptAppConnectionCredentials({
|
||||
orgId: appConnection.orgId,
|
||||
kmsService,
|
||||
encryptedCredentials: appConnection.encryptedCredentials
|
||||
encryptedCredentials: appConnection.encryptedCredentials,
|
||||
projectId: appConnection.projectId
|
||||
})) as { accessToken: string };
|
||||
|
||||
if (!("accessToken" in accessTokenCredentials)) {
|
||||
@@ -124,7 +127,8 @@ export const getAzureDevopsConnection = async (
|
||||
const clientSecretCredentials = (await decryptAppConnectionCredentials({
|
||||
orgId: appConnection.orgId,
|
||||
kmsService,
|
||||
encryptedCredentials: appConnection.encryptedCredentials
|
||||
encryptedCredentials: appConnection.encryptedCredentials,
|
||||
projectId: appConnection.projectId
|
||||
})) as TAzureDevOpsConnectionClientSecretCredentials;
|
||||
|
||||
const { accessToken, expiresAt, clientId, clientSecret, tenantId: clientTenantId } = clientSecretCredentials;
|
||||
@@ -153,7 +157,8 @@ export const getAzureDevopsConnection = async (
|
||||
const encryptedClientCredentials = await encryptAppConnectionCredentials({
|
||||
credentials: updatedClientCredentials,
|
||||
orgId: appConnection.orgId,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId: appConnection.projectId
|
||||
});
|
||||
|
||||
await appConnectionDAL.updateById(appConnection.id, { encryptedCredentials: encryptedClientCredentials });
|
||||
|
||||
@@ -58,7 +58,8 @@ export const getAzureConnectionAccessToken = async (
|
||||
const oauthCredentials = (await decryptAppConnectionCredentials({
|
||||
orgId: appConnection.orgId,
|
||||
kmsService,
|
||||
encryptedCredentials: appConnection.encryptedCredentials
|
||||
encryptedCredentials: appConnection.encryptedCredentials,
|
||||
projectId: appConnection.projectId
|
||||
})) as TAzureKeyVaultConnectionCredentials;
|
||||
|
||||
const { data } = await request.post<ExchangeCodeAzureResponse>(
|
||||
@@ -82,7 +83,8 @@ export const getAzureConnectionAccessToken = async (
|
||||
const encryptedOAuthCredentials = await encryptAppConnectionCredentials({
|
||||
credentials: updatedOAuthCredentials,
|
||||
orgId: appConnection.orgId,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId: appConnection.projectId
|
||||
});
|
||||
|
||||
await appConnectionDAL.updateById(appConnection.id, { encryptedCredentials: encryptedOAuthCredentials });
|
||||
@@ -95,7 +97,8 @@ export const getAzureConnectionAccessToken = async (
|
||||
const clientSecretCredentials = (await decryptAppConnectionCredentials({
|
||||
orgId: appConnection.orgId,
|
||||
kmsService,
|
||||
encryptedCredentials: appConnection.encryptedCredentials
|
||||
encryptedCredentials: appConnection.encryptedCredentials,
|
||||
projectId: appConnection.projectId
|
||||
})) as TAzureKeyVaultConnectionClientSecretCredentials;
|
||||
|
||||
const { accessToken, expiresAt, clientId, clientSecret, tenantId } = clientSecretCredentials;
|
||||
@@ -124,7 +127,8 @@ export const getAzureConnectionAccessToken = async (
|
||||
const encryptedClientCredentials = await encryptAppConnectionCredentials({
|
||||
credentials: updatedClientCredentials,
|
||||
orgId: appConnection.orgId,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId: appConnection.projectId
|
||||
});
|
||||
|
||||
await appConnectionDAL.updateById(appConnection.id, { encryptedCredentials: encryptedClientCredentials });
|
||||
|
||||
@@ -40,7 +40,7 @@ const authorizeCamundaConnection = async ({
|
||||
};
|
||||
|
||||
export const getCamundaConnectionAccessToken = async (
|
||||
{ id, orgId, credentials }: TCamundaConnection,
|
||||
{ id, orgId, credentials, projectId }: TCamundaConnection,
|
||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
||||
) => {
|
||||
@@ -61,7 +61,8 @@ export const getCamundaConnectionAccessToken = async (
|
||||
const encryptedCredentials = await encryptAppConnectionCredentials({
|
||||
credentials: updatedCredentials,
|
||||
orgId,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId
|
||||
});
|
||||
|
||||
await appConnectionDAL.updateById(id, { encryptedCredentials });
|
||||
|
||||
@@ -47,7 +47,7 @@ const authorizeDatabricksConnection = async ({
|
||||
};
|
||||
|
||||
export const getDatabricksConnectionAccessToken = async (
|
||||
{ id, orgId, credentials }: TDatabricksConnection,
|
||||
{ id, orgId, credentials, projectId }: TDatabricksConnection,
|
||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
||||
) => {
|
||||
@@ -68,7 +68,8 @@ export const getDatabricksConnectionAccessToken = async (
|
||||
const encryptedCredentials = await encryptAppConnectionCredentials({
|
||||
credentials: updatedCredentials,
|
||||
orgId,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId
|
||||
});
|
||||
|
||||
await appConnectionDAL.updateById(id, { encryptedCredentials });
|
||||
|
||||
@@ -64,6 +64,7 @@ export const refreshGitLabToken = async (
|
||||
refreshToken: string,
|
||||
appId: string,
|
||||
orgId: string,
|
||||
projectId: string | undefined | null,
|
||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">,
|
||||
instanceUrl?: string
|
||||
@@ -105,7 +106,8 @@ export const refreshGitLabToken = async (
|
||||
expiresAt
|
||||
},
|
||||
orgId,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId
|
||||
});
|
||||
|
||||
await appConnectionDAL.updateById(appId, { encryptedCredentials });
|
||||
@@ -238,6 +240,7 @@ export const getGitLabConnectionClient = async (
|
||||
appConnection.credentials.refreshToken,
|
||||
appConnection.id,
|
||||
appConnection.orgId,
|
||||
appConnection.projectId,
|
||||
appConnectionDAL,
|
||||
kmsService,
|
||||
appConnection.credentials.instanceUrl
|
||||
@@ -273,6 +276,7 @@ export const listGitLabProjects = async ({
|
||||
appConnection.credentials.refreshToken,
|
||||
appConnection.id,
|
||||
appConnection.orgId,
|
||||
appConnection.projectId,
|
||||
appConnectionDAL,
|
||||
kmsService,
|
||||
appConnection.credentials.instanceUrl
|
||||
@@ -341,6 +345,7 @@ export const listGitLabGroups = async ({
|
||||
appConnection.credentials.refreshToken,
|
||||
appConnection.id,
|
||||
appConnection.orgId,
|
||||
appConnection.projectId,
|
||||
appConnectionDAL,
|
||||
kmsService,
|
||||
appConnection.credentials.instanceUrl
|
||||
|
||||
@@ -36,6 +36,7 @@ export const refreshHerokuToken = async (
|
||||
refreshToken: string,
|
||||
appId: string,
|
||||
orgId: string,
|
||||
projectId: string | null | undefined,
|
||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
||||
): Promise<string> => {
|
||||
@@ -64,7 +65,8 @@ export const refreshHerokuToken = async (
|
||||
expiresAt: new Date(Date.now() + data.expires_in * 1000 - 60000)
|
||||
},
|
||||
orgId,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId
|
||||
});
|
||||
|
||||
await appConnectionDAL.updateById(appId, { encryptedCredentials });
|
||||
@@ -186,6 +188,7 @@ export const listHerokuApps = async ({
|
||||
appConnection.credentials.refreshToken,
|
||||
appConnection.id,
|
||||
appConnection.orgId,
|
||||
appConnection.projectId,
|
||||
appConnectionDAL,
|
||||
kmsService
|
||||
);
|
||||
|
||||
@@ -42,10 +42,10 @@ import { route53DeleteTxtRecord, route53InsertTxtRecord } from "./dns-providers/
|
||||
|
||||
type TAcmeCertificateAuthorityFnsDeps = {
|
||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById">;
|
||||
appConnectionService: Pick<TAppConnectionServiceFactory, "connectAppConnectionById">;
|
||||
appConnectionService: Pick<TAppConnectionServiceFactory, "validateAppConnectionUsageById">;
|
||||
certificateAuthorityDAL: Pick<
|
||||
TCertificateAuthorityDALFactory,
|
||||
"create" | "transaction" | "findByIdWithAssociatedCa" | "updateById" | "findWithAssociatedCa"
|
||||
"create" | "transaction" | "findByIdWithAssociatedCa" | "updateById" | "findWithAssociatedCa" | "findById"
|
||||
>;
|
||||
externalCertificateAuthorityDAL: Pick<TExternalCertificateAuthorityDALFactory, "create" | "update">;
|
||||
certificateDAL: Pick<TCertificateDALFactory, "create" | "transaction">;
|
||||
@@ -152,7 +152,11 @@ export const AcmeCertificateAuthorityFns = ({
|
||||
}
|
||||
|
||||
// validates permission to connect
|
||||
await appConnectionService.connectAppConnectionById(appConnection.app as AppConnection, dnsAppConnectionId, actor);
|
||||
await appConnectionService.validateAppConnectionUsageById(
|
||||
appConnection.app as AppConnection,
|
||||
{ connectionId: dnsAppConnectionId, projectId },
|
||||
actor
|
||||
);
|
||||
|
||||
const caEntity = await certificateAuthorityDAL.transaction(async (tx) => {
|
||||
try {
|
||||
@@ -242,10 +246,16 @@ export const AcmeCertificateAuthorityFns = ({
|
||||
});
|
||||
}
|
||||
|
||||
const ca = await certificateAuthorityDAL.findById(id);
|
||||
|
||||
if (!ca) {
|
||||
throw new NotFoundError({ message: `Could not find Certificate Authority with ID "${id}"` });
|
||||
}
|
||||
|
||||
// validates permission to connect
|
||||
await appConnectionService.connectAppConnectionById(
|
||||
await appConnectionService.validateAppConnectionUsageById(
|
||||
appConnection.app as AppConnection,
|
||||
dnsAppConnectionId,
|
||||
{ connectionId: dnsAppConnectionId, projectId: ca.projectId },
|
||||
actor
|
||||
);
|
||||
|
||||
|
||||
@@ -41,10 +41,10 @@ import {
|
||||
|
||||
type TAzureAdCsCertificateAuthorityFnsDeps = {
|
||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "updateById">;
|
||||
appConnectionService: Pick<TAppConnectionServiceFactory, "connectAppConnectionById">;
|
||||
appConnectionService: Pick<TAppConnectionServiceFactory, "validateAppConnectionUsageById">;
|
||||
certificateAuthorityDAL: Pick<
|
||||
TCertificateAuthorityDALFactory,
|
||||
"create" | "transaction" | "findByIdWithAssociatedCa" | "updateById" | "findWithAssociatedCa"
|
||||
"create" | "transaction" | "findByIdWithAssociatedCa" | "updateById" | "findWithAssociatedCa" | "findById"
|
||||
>;
|
||||
externalCertificateAuthorityDAL: Pick<TExternalCertificateAuthorityDALFactory, "create" | "update">;
|
||||
certificateDAL: Pick<TCertificateDALFactory, "create" | "transaction">;
|
||||
@@ -621,9 +621,9 @@ export const AzureAdCsCertificateAuthorityFns = ({
|
||||
});
|
||||
}
|
||||
|
||||
await appConnectionService.connectAppConnectionById(
|
||||
await appConnectionService.validateAppConnectionUsageById(
|
||||
appConnection.app as AppConnection,
|
||||
azureAdcsConnectionId,
|
||||
{ connectionId: azureAdcsConnectionId, projectId },
|
||||
actor
|
||||
);
|
||||
|
||||
@@ -705,9 +705,15 @@ export const AzureAdCsCertificateAuthorityFns = ({
|
||||
});
|
||||
}
|
||||
|
||||
await appConnectionService.connectAppConnectionById(
|
||||
const ca = await certificateAuthorityDAL.findById(id);
|
||||
|
||||
if (!ca) {
|
||||
throw new NotFoundError({ message: `Could not find Certificate Authority with ID "${id}"` });
|
||||
}
|
||||
|
||||
await appConnectionService.validateAppConnectionUsageById(
|
||||
appConnection.app as AppConnection,
|
||||
azureAdcsConnectionId,
|
||||
{ connectionId: azureAdcsConnectionId, projectId: ca.projectId },
|
||||
actor
|
||||
);
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ import {
|
||||
type TCertificateAuthorityQueueFactoryDep = {
|
||||
certificateAuthorityDAL: TCertificateAuthorityDALFactory;
|
||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update" | "updateById">;
|
||||
appConnectionService: Pick<TAppConnectionServiceFactory, "connectAppConnectionById">;
|
||||
appConnectionService: Pick<TAppConnectionServiceFactory, "validateAppConnectionUsageById">;
|
||||
externalCertificateAuthorityDAL: Pick<TExternalCertificateAuthorityDALFactory, "create" | "update">;
|
||||
keyStore: Pick<TKeyStoreFactory, "acquireLock" | "setItemWithExpiry" | "getItem">;
|
||||
certificateAuthorityCrlDAL: TCertificateAuthorityCrlDALFactory;
|
||||
|
||||
@@ -43,7 +43,7 @@ import { TCreateInternalCertificateAuthorityDTO } from "./internal/internal-cert
|
||||
|
||||
type TCertificateAuthorityServiceFactoryDep = {
|
||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update" | "updateById">;
|
||||
appConnectionService: Pick<TAppConnectionServiceFactory, "connectAppConnectionById">;
|
||||
appConnectionService: Pick<TAppConnectionServiceFactory, "validateAppConnectionUsageById">;
|
||||
certificateAuthorityDAL: Pick<
|
||||
TCertificateAuthorityDALFactory,
|
||||
| "transaction"
|
||||
|
||||
@@ -53,6 +53,7 @@ const getValidAccessToken = async (
|
||||
connection.credentials.refreshToken,
|
||||
connection.id,
|
||||
connection.orgId,
|
||||
connection.projectId,
|
||||
appConnectionDAL,
|
||||
kmsService,
|
||||
connection.credentials.instanceUrl
|
||||
|
||||
@@ -32,6 +32,7 @@ const getValidAuthToken = async (
|
||||
connection.credentials.refreshToken,
|
||||
connection.id,
|
||||
connection.orgId,
|
||||
connection.projectId,
|
||||
appConnectionDAL,
|
||||
kmsService
|
||||
);
|
||||
|
||||
@@ -31,6 +31,7 @@ const baseSecretSyncQuery = ({ filter, db, tx }: { db: TDbClient; filter?: Secre
|
||||
db.ref("description").withSchema(TableName.AppConnection).as("connectionDescription"),
|
||||
db.ref("version").withSchema(TableName.AppConnection).as("connectionVersion"),
|
||||
db.ref("gatewayId").withSchema(TableName.AppConnection).as("connectionGatewayId"),
|
||||
db.ref("projectId").withSchema(TableName.AppConnection).as("connectionProjectId"),
|
||||
db.ref("createdAt").withSchema(TableName.AppConnection).as("connectionCreatedAt"),
|
||||
db.ref("updatedAt").withSchema(TableName.AppConnection).as("connectionUpdatedAt"),
|
||||
db
|
||||
@@ -67,6 +68,7 @@ const expandSecretSync = (
|
||||
connectionVersion,
|
||||
connectionIsPlatformManagedCredentials,
|
||||
connectionGatewayId,
|
||||
connectionProjectId,
|
||||
...el
|
||||
} = secretSync;
|
||||
|
||||
@@ -86,7 +88,8 @@ const expandSecretSync = (
|
||||
updatedAt: connectionUpdatedAt,
|
||||
version: connectionVersion,
|
||||
isPlatformManagedCredentials: connectionIsPlatformManagedCredentials,
|
||||
gatewayId: connectionGatewayId
|
||||
gatewayId: connectionGatewayId,
|
||||
projectId: connectionProjectId
|
||||
},
|
||||
folder: folder
|
||||
? {
|
||||
|
||||
@@ -484,13 +484,14 @@ export const secretSyncQueueFactory = ({
|
||||
|
||||
try {
|
||||
const {
|
||||
connection: { orgId, encryptedCredentials }
|
||||
connection: { orgId, encryptedCredentials, projectId }
|
||||
} = secretSync;
|
||||
|
||||
const credentials = await decryptAppConnectionCredentials({
|
||||
orgId,
|
||||
encryptedCredentials,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId
|
||||
});
|
||||
|
||||
const secretSyncWithCredentials = {
|
||||
@@ -624,13 +625,14 @@ export const secretSyncQueueFactory = ({
|
||||
|
||||
try {
|
||||
const {
|
||||
connection: { orgId, encryptedCredentials }
|
||||
connection: { orgId, encryptedCredentials, projectId }
|
||||
} = secretSync;
|
||||
|
||||
const credentials = await decryptAppConnectionCredentials({
|
||||
orgId,
|
||||
encryptedCredentials,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId
|
||||
});
|
||||
|
||||
await $importSecrets(
|
||||
@@ -744,13 +746,14 @@ export const secretSyncQueueFactory = ({
|
||||
|
||||
try {
|
||||
const {
|
||||
connection: { orgId, encryptedCredentials }
|
||||
connection: { orgId, encryptedCredentials, projectId }
|
||||
} = secretSync;
|
||||
|
||||
const credentials = await decryptAppConnectionCredentials({
|
||||
orgId,
|
||||
encryptedCredentials,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId
|
||||
});
|
||||
|
||||
const secretMap = await $getInfisicalSecrets(secretSync);
|
||||
|
||||
@@ -41,7 +41,7 @@ import { TSecretSyncQueueFactory } from "./secret-sync-queue";
|
||||
type TSecretSyncServiceFactoryDep = {
|
||||
secretSyncDAL: TSecretSyncDALFactory;
|
||||
secretImportDAL: TSecretImportDALFactory;
|
||||
appConnectionService: Pick<TAppConnectionServiceFactory, "connectAppConnectionById">;
|
||||
appConnectionService: Pick<TAppConnectionServiceFactory, "validateAppConnectionUsageById">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getOrgPermission">;
|
||||
projectBotService: Pick<TProjectBotServiceFactory, "getBotKey">;
|
||||
folderDAL: Pick<TSecretFolderDALFactory, "findByProjectId" | "findById" | "findBySecretPath">;
|
||||
@@ -267,7 +267,11 @@ export const secretSyncServiceFactory = ({
|
||||
const destinationApp = SECRET_SYNC_CONNECTION_MAP[params.destination];
|
||||
|
||||
// validates permission to connect and app is valid for sync destination
|
||||
await appConnectionService.connectAppConnectionById(destinationApp, params.connectionId, actor);
|
||||
await appConnectionService.validateAppConnectionUsageById(
|
||||
destinationApp,
|
||||
{ connectionId: params.connectionId, projectId },
|
||||
actor
|
||||
);
|
||||
|
||||
try {
|
||||
const secretSync = await secretSyncDAL.create({
|
||||
@@ -362,7 +366,11 @@ export const secretSyncServiceFactory = ({
|
||||
const destinationApp = SECRET_SYNC_CONNECTION_MAP[secretSync.destination as SecretSync];
|
||||
|
||||
// validates permission to connect and app is valid for sync destination
|
||||
await appConnectionService.connectAppConnectionById(destinationApp, params.connectionId, actor);
|
||||
await appConnectionService.validateAppConnectionUsageById(
|
||||
destinationApp,
|
||||
{ connectionId: params.connectionId, projectId: secretSync.projectId },
|
||||
actor
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
|
||||
Reference in New Issue
Block a user