feat(workflow-integrations): ms-teams audit logs and pagination support

This commit is contained in:
Daniel Hougaard
2025-04-28 04:58:25 +04:00
parent 9aabc3ced7
commit 63a3ce2dba
7 changed files with 265 additions and 42 deletions
@@ -320,7 +320,15 @@ export enum EventType {
DELETE_SECRET_ROTATION = "delete-secret-rotation", DELETE_SECRET_ROTATION = "delete-secret-rotation",
SECRET_ROTATION_ROTATE_SECRETS = "secret-rotation-rotate-secrets", SECRET_ROTATION_ROTATE_SECRETS = "secret-rotation-rotate-secrets",
PROJECT_ACCESS_REQUEST = "project-access-request" PROJECT_ACCESS_REQUEST = "project-access-request",
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_CREATE = "microsoft-teams-workflow-integration-create",
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_DELETE = "microsoft-teams-workflow-integration-delete",
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_UPDATE = "microsoft-teams-workflow-integration-update",
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_CHECK_INSTALLATION_STATUS = "microsoft-teams-workflow-integration-check-installation-status",
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET_TEAMS = "microsoft-teams-workflow-integration-get-teams",
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET = "microsoft-teams-workflow-integration-get",
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_LIST = "microsoft-teams-workflow-integration-list"
} }
export const filterableSecretEvents: EventType[] = [ export const filterableSecretEvents: EventType[] = [
@@ -2517,6 +2525,66 @@ interface RotateSecretRotationEvent {
}; };
} }
interface MicrosoftTeamsWorkflowIntegrationCreateEvent {
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_CREATE;
metadata: {
tenantId: string;
slug: string;
description?: string;
};
}
interface MicrosoftTeamsWorkflowIntegrationDeleteEvent {
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_DELETE;
metadata: {
tenantId: string;
id: string;
slug: string;
};
}
interface MicrosoftTeamsWorkflowIntegrationCheckInstallationStatusEvent {
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_CHECK_INSTALLATION_STATUS;
metadata: {
tenantId: string;
slug: string;
};
}
interface MicrosoftTeamsWorkflowIntegrationGetTeamsEvent {
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET_TEAMS;
metadata: {
tenantId: string;
slug: string;
id: string;
};
}
interface MicrosoftTeamsWorkflowIntegrationGetEvent {
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET;
metadata: {
tenantId: string;
slug: string;
id: string;
};
}
interface MicrosoftTeamsWorkflowIntegrationListEvent {
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_LIST;
metadata: Record<string, string>;
}
interface MicrosoftTeamsWorkflowIntegrationUpdateEvent {
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_UPDATE;
metadata: {
tenantId: string;
slug: string;
id: string;
newSlug?: string;
newDescription?: string;
};
}
export type Event = export type Event =
| GetSecretsEvent | GetSecretsEvent
| GetSecretEvent | GetSecretEvent
@@ -2746,4 +2814,11 @@ export type Event =
| CreateSecretRotationEvent | CreateSecretRotationEvent
| UpdateSecretRotationEvent | UpdateSecretRotationEvent
| DeleteSecretRotationEvent | DeleteSecretRotationEvent
| RotateSecretRotationEvent; | RotateSecretRotationEvent
| MicrosoftTeamsWorkflowIntegrationCreateEvent
| MicrosoftTeamsWorkflowIntegrationDeleteEvent
| MicrosoftTeamsWorkflowIntegrationCheckInstallationStatusEvent
| MicrosoftTeamsWorkflowIntegrationGetTeamsEvent
| MicrosoftTeamsWorkflowIntegrationGetEvent
| MicrosoftTeamsWorkflowIntegrationListEvent
| MicrosoftTeamsWorkflowIntegrationUpdateEvent;
@@ -1,6 +1,7 @@
import { z } from "zod"; import { z } from "zod";
import { MicrosoftTeamsIntegrationsSchema, WorkflowIntegrationsSchema } from "@app/db/schemas"; import { MicrosoftTeamsIntegrationsSchema, WorkflowIntegrationsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { slugSchema } from "@app/server/lib/schemas"; import { slugSchema } from "@app/server/lib/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
@@ -76,6 +77,19 @@ export const registerMicrosoftTeamsRouter = async (server: FastifyZodProvider) =
actorAuthMethod: req.permission.authMethod, actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId actorOrgId: req.permission.orgId
}); });
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
event: {
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_CREATE,
metadata: {
tenantId: req.body.tenantId,
slug: req.body.slug,
description: req.body.description
}
}
});
} }
}); });
@@ -104,6 +118,15 @@ export const registerMicrosoftTeamsRouter = async (server: FastifyZodProvider) =
actorOrgId: req.permission.orgId actorOrgId: req.permission.orgId
}); });
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
event: {
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_LIST,
metadata: {}
}
});
return microsoftTeamsIntegrations; return microsoftTeamsIntegrations;
} }
}); });
@@ -121,13 +144,25 @@ export const registerMicrosoftTeamsRouter = async (server: FastifyZodProvider) =
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
await server.services.microsoftTeams.checkInstallationStatus({ const microsoftTeamsIntegration = await server.services.microsoftTeams.checkInstallationStatus({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod, actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
workflowIntegrationId: req.params.id workflowIntegrationId: req.params.id
}); });
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
event: {
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_CHECK_INSTALLATION_STATUS,
metadata: {
tenantId: microsoftTeamsIntegration.tenantId,
slug: microsoftTeamsIntegration.slug
}
}
});
} }
}); });
@@ -160,6 +195,19 @@ export const registerMicrosoftTeamsRouter = async (server: FastifyZodProvider) =
id: req.params.id id: req.params.id
}); });
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
event: {
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_DELETE,
metadata: {
tenantId: deletedMicrosoftTeamsIntegration.tenantId,
slug: deletedMicrosoftTeamsIntegration.slug,
id: deletedMicrosoftTeamsIntegration.id
}
}
});
return deletedMicrosoftTeamsIntegration; return deletedMicrosoftTeamsIntegration;
} }
}); });
@@ -193,6 +241,19 @@ export const registerMicrosoftTeamsRouter = async (server: FastifyZodProvider) =
id: req.params.id id: req.params.id
}); });
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
event: {
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET,
metadata: {
slug: microsoftTeamsIntegration.slug,
id: microsoftTeamsIntegration.id,
tenantId: microsoftTeamsIntegration.tenantId
}
}
});
return microsoftTeamsIntegration; return microsoftTeamsIntegration;
} }
}); });
@@ -231,6 +292,21 @@ export const registerMicrosoftTeamsRouter = async (server: FastifyZodProvider) =
...req.body ...req.body
}); });
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
event: {
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_UPDATE,
metadata: {
slug: microsoftTeamsIntegration.slug,
id: microsoftTeamsIntegration.id,
tenantId: microsoftTeamsIntegration.tenantId,
newSlug: req.body.slug,
newDescription: req.body.description
}
}
});
return microsoftTeamsIntegration; return microsoftTeamsIntegration;
} }
}); });
@@ -262,7 +338,7 @@ export const registerMicrosoftTeamsRouter = async (server: FastifyZodProvider) =
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const teams = await server.services.microsoftTeams.getTeams({ const microsoftTeamsIntegration = await server.services.microsoftTeams.getTeams({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod, actorAuthMethod: req.permission.authMethod,
@@ -270,7 +346,20 @@ export const registerMicrosoftTeamsRouter = async (server: FastifyZodProvider) =
workflowIntegrationId: req.params.workflowIntegrationId workflowIntegrationId: req.params.workflowIntegrationId
}); });
return teams; await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
event: {
type: EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET_TEAMS,
metadata: {
tenantId: microsoftTeamsIntegration.tenantId,
slug: microsoftTeamsIntegration.slug,
id: microsoftTeamsIntegration.id
}
}
});
return microsoftTeamsIntegration.teams;
} }
}); });
@@ -1,6 +1,7 @@
/* eslint-disable class-methods-use-this */ /* eslint-disable class-methods-use-this */
import axios from "axios"; import axios from "axios";
import { TeamsActivityHandler, TurnContext } from "botbuilder"; import { TeamsActivityHandler, TurnContext } from "botbuilder";
import jwt from "jsonwebtoken";
import { Knex } from "knex"; import { Knex } from "knex";
import { z } from "zod"; import { z } from "zod";
@@ -45,11 +46,10 @@ export const verifyTenantFromCode = async (
}); });
const accessToken = response.data.access_token; const accessToken = response.data.access_token;
const tokenParts = accessToken.split("."); const decodedToken = jwt.decode(accessToken) as { tid: string };
const tokenPayload = JSON.parse(Buffer.from(tokenParts[1], "base64").toString()) as { tid: string };
// the 'tid' claim in the token contains the tenant ID // the 'tid' claim in the token contains the tenant ID
const tenantIdFromToken = tokenPayload.tid; const tenantIdFromToken = decodedToken.tid;
if (tenantIdFromToken !== tenantId) { if (tenantIdFromToken !== tenantId) {
throw new BadRequestError({ throw new BadRequestError({
@@ -562,24 +562,34 @@ export class TeamsBot extends TeamsActivityHandler {
} }
} }
// todo: filter out teams that the bot is not a member of async getTeamsAndChannels(accessToken: string, internalAppId: string) {
async getTeamsAndChannels(accessToken: string, tenantId: string, internalAppId: string) {
try { try {
const teamsResponse = await axios let teamsNextLink: string = "https://graph.microsoft.com/v1.0/teams";
.get<{ value: { displayName: string; id: string }[] }>(`https://graph.microsoft.com/v1.0/teams`, {
headers: { let allTeams: { displayName: string; id: string }[] = [];
Authorization: `Bearer ${accessToken}` while (teamsNextLink?.length) {
} try {
}) // eslint-disable-next-line no-await-in-loop
.catch((error) => { const response = await axios.get<{
value: { displayName: string; id: string }[];
"@odata.nextLink"?: string;
}>(teamsNextLink, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
allTeams = allTeams.concat(response.data.value);
teamsNextLink = response.data["@odata.nextLink"] || "";
} catch (error) {
logger.error(error, "Microsoft Teams Workflow Integration: Failed to fetch teams"); logger.error(error, "Microsoft Teams Workflow Integration: Failed to fetch teams");
throw error; throw error;
}); }
}
const teams = teamsResponse.data.value;
const result = []; const result = [];
for await (const team of teams) { for await (const team of allTeams) {
try { try {
// Get installed apps for this team // Get installed apps for this team
const installedAppsResponse = await axios.get<{ value: { teamsAppDefinition: { teamsAppId: string } }[] }>( const installedAppsResponse = await axios.get<{ value: { teamsAppDefinition: { teamsAppId: string } }[] }>(
@@ -600,21 +610,41 @@ export class TeamsBot extends TeamsActivityHandler {
continue; // skip this team if we can't determine if the bot is installed continue; // skip this team if we can't determine if the bot is installed
} }
const channelsResponse = await axios let allChannels: { displayName: string; id: string }[] = [];
.get<{ value: { displayName: string; id: string }[] }>(
`https://graph.microsoft.com/v1.0/teams/${team.id}/channels`, let channelNextLink: string = `https://graph.microsoft.com/v1.0/teams/${team.id}/channels`;
{
while (channelNextLink?.length) {
// eslint-disable-next-line no-await-in-loop
const resp = await axios
.get<{
value: { displayName: string; id: string }[];
"@odata.nextLink"?: string;
}>(channelNextLink, {
headers: { headers: {
Authorization: `Bearer ${accessToken}` Authorization: `Bearer ${accessToken}`
} }
} })
) .catch((error) => {
.catch((error) => { if (axios.isAxiosError(error)) {
logger.error(error, "Microsoft Teams Workflow Integration: Failed to fetch channels"); logger.error(
throw error; error.response?.data,
}); "getTeamsAndChannels: Axios error, Microsoft Teams Workflow Integration: Failed to fetch channels"
);
} else {
logger.error(
error,
"getTeamsAndChannels: Microsoft Teams Workflow Integration: Failed to fetch channels"
);
}
throw error;
});
const channels = channelsResponse.data.value.map((channel) => ({ allChannels = allChannels.concat(resp.data.value);
channelNextLink = resp.data["@odata.nextLink"] || "";
}
const channels = allChannels.map((channel) => ({
channelName: channel.displayName, channelName: channel.displayName,
channelId: channel.id channelId: channel.id
})); }));
@@ -206,6 +206,8 @@ export const microsoftTeamsServiceFactory = ({
status: WorkflowIntegrationStatus.INSTALLED status: WorkflowIntegrationStatus.INSTALLED
}); });
} }
return microsoftTeamsIntegration;
}; };
const completeMicrosoftTeamsIntegration = async ({ const completeMicrosoftTeamsIntegration = async ({
@@ -556,7 +558,7 @@ export const microsoftTeamsServiceFactory = ({
botAppId: decryptedAppId.toString(), botAppId: decryptedAppId.toString(),
botAppPassword: decryptedAppPassword.toString(), botAppPassword: decryptedAppPassword.toString(),
botId: decryptedBotId.toString(), botId: decryptedBotId.toString(),
orgId: actorOrgId, orgId: microsoftTeamsIntegration.orgId,
kmsService, kmsService,
microsoftTeamsIntegrationDAL, microsoftTeamsIntegrationDAL,
microsoftTeamsIntegrationId: microsoftTeamsIntegration.id microsoftTeamsIntegrationId: microsoftTeamsIntegration.id
@@ -568,9 +570,12 @@ export const microsoftTeamsServiceFactory = ({
}); });
} }
const teams = await teamsBot.getTeamsAndChannels(accessToken, microsoftTeamsIntegration.tenantId, internalId); const teams = await teamsBot.getTeamsAndChannels(accessToken, internalId);
return teams; return {
...microsoftTeamsIntegration,
teams
};
}; };
const handleMessageEndpoint = async (req: FastifyRequest, res: FastifyReply) => { const handleMessageEndpoint = async (req: FastifyRequest, res: FastifyReply) => {
@@ -103,11 +103,15 @@ This guide will provide step by step instructions on how to configure Microsoft
Next we need to get the application client ID and create a new client secret. **Save these values for later, as they're required to configure the Microsoft Teams integration in Infisical.** Next we need to get the application client ID and create a new client secret. **Save these values for later, as they're required to configure the Microsoft Teams integration in Infisical.**
Copy the application client ID from the overview page. Copy the application client ID from the overview page.
![copy-client-id](/images/platform/workflow-integrations/microsoft-teams-integration/azure-copy-client-id.png) ![copy-client-id](/images/platform/workflow-integrations/microsoft-teams-integration/azure-copy-client-id.png)
Create a new client secret, and save the value. Keep the expiry time in mind when you're creating the secret. Create a new client secret, and save the value. Keep the expiry time in mind when you're creating the secret.
![create-client-secret](/images/platform/workflow-integrations/microsoft-teams-integration/azure-create-client-secret.png) ![create-client-secret](/images/platform/workflow-integrations/microsoft-teams-integration/azure-create-client-secret.png)
<Warning>
Remember to rotate your client secret before it expires. Consider setting up a reminder or automated process to replace the secret and update your Infisical configuration before expiration.
</Warning>
</Step> </Step>
<Step title="Create Microsoft Teams Bot"> <Step title="Create Microsoft Teams Bot">
@@ -135,7 +139,7 @@ This guide will provide step by step instructions on how to configure Microsoft
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.11/MicrosoftTeams.schema.json", "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.11/MicrosoftTeams.schema.json",
"version": "3.0.0", "version": "3.0.0",
"manifestVersion": "1.11", "manifestVersion": "1.11",
"id": "f86bbe9c-b853-4f96-8512-7ceba7d2a620", // Keep your existing ID here, this field cannot be changed. "id": "<your-existing-id-from-your-manifest-file>",
"name": { "name": {
"short": "Infisical", "short": "Infisical",
"full": "Infisical Bot" "full": "Infisical Bot"
@@ -157,7 +161,7 @@ This guide will provide step by step instructions on how to configure Microsoft
"accentColor": "#FFFFFF", "accentColor": "#FFFFFF",
"bots": [ "bots": [
{ {
"botId": "01bad7fb-227b-4ac3-b5f3-41e8cdaf65d7", // Replace the botId with the Client ID of the App Registration from the previous step. "botId": "<replace-with-your-client-id-of-your-app-registration-from-previous-step",
"scopes": [ "scopes": [
"team", "team",
"personal", "personal",
+13 -1
View File
@@ -165,7 +165,19 @@ export const eventToNameMap: { [K in EventType]: string } = {
[EventType.CREATE_SECRET_ROTATION]: "Create Secret Rotation", [EventType.CREATE_SECRET_ROTATION]: "Create Secret Rotation",
[EventType.UPDATE_SECRET_ROTATION]: "Update Secret Rotation", [EventType.UPDATE_SECRET_ROTATION]: "Update Secret Rotation",
[EventType.DELETE_SECRET_ROTATION]: "Delete Secret Rotation", [EventType.DELETE_SECRET_ROTATION]: "Delete Secret Rotation",
[EventType.SECRET_ROTATION_ROTATE_SECRETS]: "Secret Rotation secrets rotated" [EventType.SECRET_ROTATION_ROTATE_SECRETS]: "Secret Rotation secrets rotated",
[EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_CREATE]:
"Create Microsoft Teams Workflow Integration",
[EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_DELETE]:
"Delete Microsoft Teams Workflow Integration",
[EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_UPDATE]:
"Update Microsoft Teams Workflow Integration",
[EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_CHECK_INSTALLATION_STATUS]:
"Microsoft Teams Workflow Integration Check Installation Status",
[EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET_TEAMS]: "Get Microsoft Teams tenant teams",
[EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET]: "Get Microsoft Teams Workflow Integration",
[EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_LIST]: "List Microsoft Teams Workflow Integration"
}; };
export const userAgentTTypeoNameMap: { [K in UserAgentType]: string } = { export const userAgentTTypeoNameMap: { [K in UserAgentType]: string } = {
+9 -1
View File
@@ -163,5 +163,13 @@ export enum EventType {
CREATE_SECRET_ROTATION = "create-secret-rotation", CREATE_SECRET_ROTATION = "create-secret-rotation",
UPDATE_SECRET_ROTATION = "update-secret-rotation", UPDATE_SECRET_ROTATION = "update-secret-rotation",
DELETE_SECRET_ROTATION = "delete-secret-rotation", DELETE_SECRET_ROTATION = "delete-secret-rotation",
SECRET_ROTATION_ROTATE_SECRETS = "secret-rotation-rotate-secrets" SECRET_ROTATION_ROTATE_SECRETS = "secret-rotation-rotate-secrets",
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_CREATE = "microsoft-teams-workflow-integration-create",
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_DELETE = "microsoft-teams-workflow-integration-delete",
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_UPDATE = "microsoft-teams-workflow-integration-update",
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_CHECK_INSTALLATION_STATUS = "microsoft-teams-workflow-integration-check-installation-status",
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET_TEAMS = "microsoft-teams-workflow-integration-get-teams",
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET = "microsoft-teams-workflow-integration-get",
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_LIST = "microsoft-teams-workflow-integration-list"
} }