diff --git a/src/Core/AdminConsole/Services/IEventMessageHandler.cs b/src/Core/AdminConsole/Services/IEventMessageHandler.cs index 5df9544c29..83c5e33ecb 100644 --- a/src/Core/AdminConsole/Services/IEventMessageHandler.cs +++ b/src/Core/AdminConsole/Services/IEventMessageHandler.cs @@ -5,4 +5,6 @@ namespace Bit.Core.Services; public interface IEventMessageHandler { Task HandleEventAsync(EventMessage eventMessage); + + Task HandleManyEventsAsync(IEnumerable eventMessages); } diff --git a/src/Core/AdminConsole/Services/Implementations/AzureServiceBusEventListenerService.cs b/src/Core/AdminConsole/Services/Implementations/AzureServiceBusEventListenerService.cs index 5c329ce8ad..4cd71ae77e 100644 --- a/src/Core/AdminConsole/Services/Implementations/AzureServiceBusEventListenerService.cs +++ b/src/Core/AdminConsole/Services/Implementations/AzureServiceBusEventListenerService.cs @@ -1,4 +1,5 @@ -using System.Text.Json; +using System.Text; +using System.Text.Json; using Azure.Messaging.ServiceBus; using Bit.Core.Models.Data; using Bit.Core.Settings; @@ -29,9 +30,20 @@ public class AzureServiceBusEventListenerService : EventLoggingListenerService { try { - var eventMessage = JsonSerializer.Deserialize(args.Message.Body.ToString()); + using var jsonDocument = JsonDocument.Parse(Encoding.UTF8.GetString(args.Message.Body)); + var root = jsonDocument.RootElement; - await _handler.HandleEventAsync(eventMessage); + if (root.ValueKind == JsonValueKind.Array) + { + var eventMessages = root.Deserialize>(); + await _handler.HandleManyEventsAsync(eventMessages); + } + else if (root.ValueKind == JsonValueKind.Object) + { + var eventMessage = root.Deserialize(); + await _handler.HandleEventAsync(eventMessage); + + } await args.CompleteMessageAsync(args.Message); } catch (Exception exception) diff --git a/src/Core/AdminConsole/Services/Implementations/AzureServiceBusEventWriteService.cs b/src/Core/AdminConsole/Services/Implementations/AzureServiceBusEventWriteService.cs index ed8f45ed55..fc865b327c 100644 --- a/src/Core/AdminConsole/Services/Implementations/AzureServiceBusEventWriteService.cs +++ b/src/Core/AdminConsole/Services/Implementations/AzureServiceBusEventWriteService.cs @@ -29,10 +29,12 @@ public class AzureServiceBusEventWriteService : IEventWriteService, IAsyncDispos public async Task CreateManyAsync(IEnumerable events) { - foreach (var e in events) + var message = new ServiceBusMessage(JsonSerializer.SerializeToUtf8Bytes(events)) { - await CreateAsync(e); - } + ContentType = "application/json" + }; + + await _sender.SendMessageAsync(message); } public async ValueTask DisposeAsync() diff --git a/src/Core/AdminConsole/Services/Implementations/AzureTableStorageEventHandler.cs b/src/Core/AdminConsole/Services/Implementations/AzureTableStorageEventHandler.cs index 2612ba0487..aa545913b1 100644 --- a/src/Core/AdminConsole/Services/Implementations/AzureTableStorageEventHandler.cs +++ b/src/Core/AdminConsole/Services/Implementations/AzureTableStorageEventHandler.cs @@ -11,4 +11,9 @@ public class AzureTableStorageEventHandler( { return eventWriteService.CreateManyAsync(EventTableEntity.IndexEvent(eventMessage)); } + + public Task HandleManyEventsAsync(IEnumerable eventMessages) + { + return eventWriteService.CreateManyAsync(eventMessages.SelectMany(EventTableEntity.IndexEvent)); + } } diff --git a/src/Core/AdminConsole/Services/Implementations/EventRepositoryHandler.cs b/src/Core/AdminConsole/Services/Implementations/EventRepositoryHandler.cs index 6e4158122c..ee3a2d5db2 100644 --- a/src/Core/AdminConsole/Services/Implementations/EventRepositoryHandler.cs +++ b/src/Core/AdminConsole/Services/Implementations/EventRepositoryHandler.cs @@ -11,4 +11,9 @@ public class EventRepositoryHandler( { return eventWriteService.CreateAsync(eventMessage); } + + public Task HandleManyEventsAsync(IEnumerable eventMessages) + { + return eventWriteService.CreateManyAsync(eventMessages); + } } diff --git a/src/Core/AdminConsole/Services/Implementations/RabbitMqEventListenerService.cs b/src/Core/AdminConsole/Services/Implementations/RabbitMqEventListenerService.cs index c302497142..1ee3fa5ea7 100644 --- a/src/Core/AdminConsole/Services/Implementations/RabbitMqEventListenerService.cs +++ b/src/Core/AdminConsole/Services/Implementations/RabbitMqEventListenerService.cs @@ -1,4 +1,5 @@ -using System.Text.Json; +using System.Text; +using System.Text.Json; using Bit.Core.Models.Data; using Bit.Core.Settings; using Microsoft.Extensions.Logging; @@ -62,8 +63,20 @@ public class RabbitMqEventListenerService : EventLoggingListenerService { try { - var eventMessage = JsonSerializer.Deserialize(eventArgs.Body.Span); - await _handler.HandleEventAsync(eventMessage); + using var jsonDocument = JsonDocument.Parse(Encoding.UTF8.GetString(eventArgs.Body.Span)); + var root = jsonDocument.RootElement; + + if (root.ValueKind == JsonValueKind.Array) + { + var eventMessages = root.Deserialize>(); + await _handler.HandleManyEventsAsync(eventMessages); + } + else if (root.ValueKind == JsonValueKind.Object) + { + var eventMessage = root.Deserialize(); + await _handler.HandleEventAsync(eventMessage); + + } } catch (Exception ex) { diff --git a/src/Core/AdminConsole/Services/Implementations/RabbitMqEventWriteService.cs b/src/Core/AdminConsole/Services/Implementations/RabbitMqEventWriteService.cs index d89cf890ac..86abddec58 100644 --- a/src/Core/AdminConsole/Services/Implementations/RabbitMqEventWriteService.cs +++ b/src/Core/AdminConsole/Services/Implementations/RabbitMqEventWriteService.cs @@ -41,12 +41,9 @@ public class RabbitMqEventWriteService : IEventWriteService, IAsyncDisposable using var channel = await connection.CreateChannelAsync(); await channel.ExchangeDeclareAsync(exchange: _exchangeName, type: ExchangeType.Fanout, durable: true); - foreach (var e in events) - { - var body = JsonSerializer.SerializeToUtf8Bytes(e); + var body = JsonSerializer.SerializeToUtf8Bytes(events); - await channel.BasicPublishAsync(exchange: _exchangeName, routingKey: string.Empty, body: body); - } + await channel.BasicPublishAsync(exchange: _exchangeName, routingKey: string.Empty, body: body); } public async ValueTask DisposeAsync() diff --git a/src/Core/AdminConsole/Services/Implementations/WebhookEventHandler.cs b/src/Core/AdminConsole/Services/Implementations/WebhookEventHandler.cs index 60abc198d8..d152f9011b 100644 --- a/src/Core/AdminConsole/Services/Implementations/WebhookEventHandler.cs +++ b/src/Core/AdminConsole/Services/Implementations/WebhookEventHandler.cs @@ -4,25 +4,27 @@ using Bit.Core.Settings; namespace Bit.Core.Services; -public class WebhookEventHandler : IEventMessageHandler +public class WebhookEventHandler( + IHttpClientFactory httpClientFactory, + GlobalSettings globalSettings) + : IEventMessageHandler { - private readonly HttpClient _httpClient; - private readonly string _webhookUrl; + private readonly HttpClient _httpClient = httpClientFactory.CreateClient(HttpClientName); + private readonly string _webhookUrl = globalSettings.EventLogging.WebhookUrl; public const string HttpClientName = "WebhookEventHandlerHttpClient"; - public WebhookEventHandler( - IHttpClientFactory httpClientFactory, - GlobalSettings globalSettings) - { - _httpClient = httpClientFactory.CreateClient(HttpClientName); - _webhookUrl = globalSettings.EventLogging.WebhookUrl; - } - public async Task HandleEventAsync(EventMessage eventMessage) { var content = JsonContent.Create(eventMessage); var response = await _httpClient.PostAsync(_webhookUrl, content); response.EnsureSuccessStatusCode(); } + + public async Task HandleManyEventsAsync(IEnumerable eventMessages) + { + var content = JsonContent.Create(eventMessages); + var response = await _httpClient.PostAsync(_webhookUrl, content); + response.EnsureSuccessStatusCode(); + } } diff --git a/src/Events/Startup.cs b/src/Events/Startup.cs index 431f449708..57af285b03 100644 --- a/src/Events/Startup.cs +++ b/src/Events/Startup.cs @@ -1,4 +1,5 @@ using System.Globalization; +using Bit.Core.AdminConsole.Services.Implementations; using Bit.Core.Context; using Bit.Core.IdentityServer; using Bit.Core.Services; @@ -63,11 +64,29 @@ public class Startup services.AddScoped(); if (!globalSettings.SelfHosted && CoreHelpers.SettingHasValue(globalSettings.Events.ConnectionString)) { - services.AddSingleton(); + if (CoreHelpers.SettingHasValue(globalSettings.EventLogging.AzureServiceBus.ConnectionString) && + CoreHelpers.SettingHasValue(globalSettings.EventLogging.AzureServiceBus.TopicName)) + { + services.AddSingleton(); + } + else + { + services.AddSingleton(); + } } else { - services.AddSingleton(); + if (CoreHelpers.SettingHasValue(globalSettings.EventLogging.RabbitMq.HostName) && + CoreHelpers.SettingHasValue(globalSettings.EventLogging.RabbitMq.Username) && + CoreHelpers.SettingHasValue(globalSettings.EventLogging.RabbitMq.Password) && + CoreHelpers.SettingHasValue(globalSettings.EventLogging.RabbitMq.ExchangeName)) + { + services.AddSingleton(); + } + else + { + services.AddSingleton(); + } } services.AddOptionality(); diff --git a/test/Core.Test/AdminConsole/Services/EventRepositoryHandlerTests.cs b/test/Core.Test/AdminConsole/Services/EventRepositoryHandlerTests.cs index 2b143f5cb8..48c3a143d4 100644 --- a/test/Core.Test/AdminConsole/Services/EventRepositoryHandlerTests.cs +++ b/test/Core.Test/AdminConsole/Services/EventRepositoryHandlerTests.cs @@ -21,4 +21,15 @@ public class EventRepositoryHandlerTests Arg.Is(AssertHelper.AssertPropertyEqual(eventMessage)) ); } + + [Theory, BitAutoData] + public async Task HandleManyEventAsync_WritesEventsToIEventWriteService( + IEnumerable eventMessages, + SutProvider sutProvider) + { + await sutProvider.Sut.HandleManyEventsAsync(eventMessages); + await sutProvider.GetDependency().Received(1).CreateManyAsync( + Arg.Is(AssertHelper.AssertPropertyEqual(eventMessages)) + ); + } } diff --git a/test/Core.Test/AdminConsole/Services/WebhookEventHandlerTests.cs b/test/Core.Test/AdminConsole/Services/WebhookEventHandlerTests.cs index eab0be88a1..6c7d7178c1 100644 --- a/test/Core.Test/AdminConsole/Services/WebhookEventHandlerTests.cs +++ b/test/Core.Test/AdminConsole/Services/WebhookEventHandlerTests.cs @@ -44,10 +44,9 @@ public class WebhookEventHandlerTests } [Theory, BitAutoData] - public async Task HandleEventAsync_PostsEventsToUrl(EventMessage eventMessage) + public async Task HandleEventAsync_PostsEventToUrl(EventMessage eventMessage) { var sutProvider = GetSutProvider(); - var content = JsonContent.Create(eventMessage); await sutProvider.Sut.HandleEventAsync(eventMessage); sutProvider.GetDependency().Received(1).CreateClient( @@ -63,4 +62,24 @@ public class WebhookEventHandlerTests Assert.Equal(_webhookUrl, request.RequestUri.ToString()); AssertHelper.AssertPropertyEqual(eventMessage, returned, new[] { "IdempotencyId" }); } + + [Theory, BitAutoData] + public async Task HandleEventManyAsync_PostsEventsToUrl(IEnumerable eventMessages) + { + var sutProvider = GetSutProvider(); + + await sutProvider.Sut.HandleManyEventsAsync(eventMessages); + sutProvider.GetDependency().Received(1).CreateClient( + Arg.Is(AssertHelper.AssertPropertyEqual(WebhookEventHandler.HttpClientName)) + ); + + Assert.Single(_handler.CapturedRequests); + var request = _handler.CapturedRequests[0]; + Assert.NotNull(request); + var returned = request.Content.ReadFromJsonAsAsyncEnumerable(); + + Assert.Equal(HttpMethod.Post, request.Method); + Assert.Equal(_webhookUrl, request.RequestUri.ToString()); + AssertHelper.AssertPropertyEqual(eventMessages, returned, new[] { "IdempotencyId" }); + } }