1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-14 22:27:32 -05:00

[PM-17562] Add support for extended properties on event integrations (#5755)

* [PM-17562] Add support for extended properties on event integrations

* Clean up IntegrationEventHandlerBase

* Respond to PR feedback
This commit is contained in:
Brant DeBow
2025-05-05 08:04:59 -04:00
committed by GitHub
parent 9511c26683
commit 75a2da3c4b
7 changed files with 445 additions and 63 deletions

View File

@ -0,0 +1,66 @@
using System.Text.Json.Nodes;
using Bit.Core.AdminConsole.Utilities;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Models.Data.Integrations;
using Bit.Core.Repositories;
namespace Bit.Core.Services;
public abstract class IntegrationEventHandlerBase(
IUserRepository userRepository,
IOrganizationRepository organizationRepository,
IOrganizationIntegrationConfigurationRepository configurationRepository)
: IEventMessageHandler
{
public async Task HandleEventAsync(EventMessage eventMessage)
{
var organizationId = eventMessage.OrganizationId ?? Guid.Empty;
var configurations = await configurationRepository.GetConfigurationDetailsAsync(
organizationId,
GetIntegrationType(),
eventMessage.Type);
foreach (var configuration in configurations)
{
var context = await BuildContextAsync(eventMessage, configuration.Template);
var renderedTemplate = IntegrationTemplateProcessor.ReplaceTokens(configuration.Template, context);
await ProcessEventIntegrationAsync(configuration.MergedConfiguration, renderedTemplate);
}
}
public async Task HandleManyEventsAsync(IEnumerable<EventMessage> eventMessages)
{
foreach (var eventMessage in eventMessages)
{
await HandleEventAsync(eventMessage);
}
}
private async Task<IntegrationTemplateContext> BuildContextAsync(EventMessage eventMessage, string template)
{
var context = new IntegrationTemplateContext(eventMessage);
if (IntegrationTemplateProcessor.TemplateRequiresUser(template) && eventMessage.UserId.HasValue)
{
context.User = await userRepository.GetByIdAsync(eventMessage.UserId.Value);
}
if (IntegrationTemplateProcessor.TemplateRequiresActingUser(template) && eventMessage.ActingUserId.HasValue)
{
context.ActingUser = await userRepository.GetByIdAsync(eventMessage.ActingUserId.Value);
}
if (IntegrationTemplateProcessor.TemplateRequiresOrganization(template) && eventMessage.OrganizationId.HasValue)
{
context.Organization = await organizationRepository.GetByIdAsync(eventMessage.OrganizationId.Value);
}
return context;
}
protected abstract IntegrationType GetIntegrationType();
protected abstract Task ProcessEventIntegrationAsync(JsonObject mergedConfiguration, string renderedTemplate);
}

View File

@ -1,46 +1,35 @@
using System.Text.Json;
using Bit.Core.AdminConsole.Utilities;
using System.Text.Json.Nodes;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Models.Data.Integrations;
using Bit.Core.Repositories;
#nullable enable
namespace Bit.Core.Services;
public class SlackEventHandler(
IUserRepository userRepository,
IOrganizationRepository organizationRepository,
IOrganizationIntegrationConfigurationRepository configurationRepository,
ISlackService slackService)
: IEventMessageHandler
: IntegrationEventHandlerBase(userRepository, organizationRepository, configurationRepository)
{
public async Task HandleEventAsync(EventMessage eventMessage)
protected override IntegrationType GetIntegrationType() => IntegrationType.Slack;
protected override async Task ProcessEventIntegrationAsync(JsonObject mergedConfiguration,
string renderedTemplate)
{
var organizationId = eventMessage.OrganizationId ?? Guid.Empty;
var configurations = await configurationRepository.GetConfigurationDetailsAsync(
organizationId,
IntegrationType.Slack,
eventMessage.Type);
foreach (var configuration in configurations)
var config = mergedConfiguration.Deserialize<SlackIntegrationConfigurationDetails>();
if (config is null)
{
var config = configuration.MergedConfiguration.Deserialize<SlackIntegrationConfigurationDetails>();
if (config is null)
{
continue;
}
await slackService.SendSlackMessageByChannelIdAsync(
config.token,
IntegrationTemplateProcessor.ReplaceTokens(configuration.Template, eventMessage),
config.channelId
);
return;
}
}
public async Task HandleManyEventsAsync(IEnumerable<EventMessage> eventMessages)
{
foreach (var eventMessage in eventMessages)
{
await HandleEventAsync(eventMessage);
}
await slackService.SendSlackMessageByChannelIdAsync(
config.token,
renderedTemplate,
config.channelId
);
}
}

View File

@ -1,8 +1,7 @@
using System.Text;
using System.Text.Json;
using Bit.Core.AdminConsole.Utilities;
using System.Text.Json.Nodes;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Models.Data.Integrations;
using Bit.Core.Repositories;
@ -12,46 +11,28 @@ namespace Bit.Core.Services;
public class WebhookEventHandler(
IHttpClientFactory httpClientFactory,
IUserRepository userRepository,
IOrganizationRepository organizationRepository,
IOrganizationIntegrationConfigurationRepository configurationRepository)
: IEventMessageHandler
: IntegrationEventHandlerBase(userRepository, organizationRepository, configurationRepository)
{
private readonly HttpClient _httpClient = httpClientFactory.CreateClient(HttpClientName);
public const string HttpClientName = "WebhookEventHandlerHttpClient";
public async Task HandleEventAsync(EventMessage eventMessage)
protected override IntegrationType GetIntegrationType() => IntegrationType.Webhook;
protected override async Task ProcessEventIntegrationAsync(JsonObject mergedConfiguration,
string renderedTemplate)
{
var organizationId = eventMessage.OrganizationId ?? Guid.Empty;
var configurations = await configurationRepository.GetConfigurationDetailsAsync(
organizationId,
IntegrationType.Webhook,
eventMessage.Type);
foreach (var configuration in configurations)
var config = mergedConfiguration.Deserialize<WebhookIntegrationConfigurationDetils>();
if (config is null || string.IsNullOrEmpty(config.url))
{
var config = configuration.MergedConfiguration.Deserialize<WebhookIntegrationConfigurationDetils>();
if (config is null || string.IsNullOrEmpty(config.url))
{
continue;
}
var content = new StringContent(
IntegrationTemplateProcessor.ReplaceTokens(configuration.Template, eventMessage),
Encoding.UTF8,
"application/json"
);
var response = await _httpClient.PostAsync(
config.url,
content);
response.EnsureSuccessStatusCode();
return;
}
}
public async Task HandleManyEventsAsync(IEnumerable<EventMessage> eventMessages)
{
foreach (var eventMessage in eventMessages)
{
await HandleEventAsync(eventMessage);
}
var content = new StringContent(renderedTemplate, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync(config.url, content);
response.EnsureSuccessStatusCode();
}
}