Skip to content

Commit

Permalink
Bulk send push notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
addisonbeck committed May 20, 2024
1 parent ac451cb commit b03ea39
Show file tree
Hide file tree
Showing 5 changed files with 32 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ public class AuthRequestUpdateProcessor<T> where T : AuthRequest
private OrganizationAuthRequestUpdate _updates { get; }
private AuthRequestUpdateProcessorConfiguration _configuration { get; }

public EventType OrganizationEventType => ProcessedAuthRequest.Approved.Value ?
EventType.OrganizationUser_ApprovedAuthRequest :
EventType.OrganizationUser_RejectedAuthRequest;

public AuthRequestUpdateProcessor(
T authRequest,
OrganizationAuthRequestUpdate updates,
Expand Down Expand Up @@ -76,19 +80,6 @@ public async Task<AuthRequestUpdateProcessor<T>> SendNewDeviceEmail(Func<T, stri
return this;
}

public async Task<AuthRequestUpdateProcessor<T>> SendEventLog(Func<T, EventType, Task> callback)
{
if (!ProcessedAuthRequest?.Approved == null || callback == null)
{
return this;
}
var eventType = _updates.Approved ?
EventType.OrganizationUser_ApprovedAuthRequest :
EventType.OrganizationUser_RejectedAuthRequest;
await callback(ProcessedAuthRequest, eventType);
return this;
}

private AuthRequestUpdateProcessor<T> Approve()
{
if (string.IsNullOrWhiteSpace(_updates.Key))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ public async Task<BatchAuthRequestUpdateProcessor<T>> Save(Func<IEnumerable<T>,
return this;
}

// Currently events like notifications, emails, and event logs are still
// done per-request in a loop, which is different than saving updates to
// the database. Saving can be done in bulk all the way through to the
// repository.
// Currently push notifications and emails are still done per-request in
// a loop, which is different than saving updates to the database and
// raising organization events. These can be done in bulk all the way
// through to the repository.
//
// Perhaps these operations should be extended to be more batch-friendly
// as well.
Expand All @@ -76,11 +76,14 @@ public async Task<BatchAuthRequestUpdateProcessor<T>> SendNewDeviceEmails(Func<T
return this;
}

public async Task<BatchAuthRequestUpdateProcessor<T>> SendEventLogs(Func<T, EventType, Task> callback)
public async Task<BatchAuthRequestUpdateProcessor<T>> SendEventLogs(Func<IEnumerable<(T, EventType)>, Task> callback)
{
foreach (var processor in _processed)
if (_processed.Any())
{
await processor.SendEventLog(callback);
await callback(_processed.Select(p =>
{
return (p.ProcessedAuthRequest, p.OrganizationEventType);
}));
}
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using Bit.Core.Auth.Models.Api.Request.AuthRequest;
using Bit.Core.Auth.Models.Data;
using Bit.Core.Auth.Services;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Repositories;
using Bit.Core.Services;
Expand Down Expand Up @@ -93,7 +92,7 @@ public async Task UpdateAsync(Guid organizationId, IEnumerable<OrganizationAuthR
.Save((IEnumerable<OrganizationAdminAuthRequest> authRequests) => _authRequestRepository.UpdateManyAsync(authRequests))
.Then(p => p.SendPushNotifications((ar) => _pushNotificationService.PushAuthRequestResponseAsync(ar)))
.Then(p => p.SendNewDeviceEmails(PushTrustedDeviceEmail))
.Then(p => p.SendEventLogs(PushAuthRequestEventLog));
.Then(p => p.SendEventLogs(SendOrganizationEventLogs));
}

async Task<ICollection<OrganizationAdminAuthRequest>> FetchManyOrganizationAuthRequestsFromTheDatabase(Guid organizationId, IEnumerable<Guid> authRequestIds)
Expand Down Expand Up @@ -126,22 +125,15 @@ await _authRequestRepository
);
}

async Task<OrganizationUser> FetchOrganizationUserFromTheDatabase(Guid organizationId, Guid userId)
async Task SendOrganizationEventLogs(IEnumerable<(OrganizationAdminAuthRequest AuthRequest, EventType EventType)> events)
{
return await _organizationUserRepository.GetByOrganizationAsync(organizationId, userId);
}

async Task PushAuthRequestEventLog<T>(T authRequest, EventType eventType) where T : AuthRequest
{
var organizationUser = await FetchOrganizationUserFromTheDatabase(authRequest.OrganizationId.Value, authRequest.UserId);

// This should be impossible
if (organizationUser == null)
{
_logger.LogError($"An organization user was not found while processing auth request {authRequest.Id}. Event logs can not be posted for this request.");
return;
}

await _eventService.LogOrganizationUserEventAsync(organizationUser, eventType);
var organizationUsers = await _organizationUserRepository.GetManyAsync(events.Select(e => e.AuthRequest.OrganizationUserId));
await _eventService.LogOrganizationUserEventsAsync(
organizationUsers.Select(ou =>
{
var e = events.FirstOrDefault(e => e.AuthRequest.OrganizationId == ou.Id);
return (ou, e.EventType, e.AuthRequest.ResponseDate);
})
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -200,39 +200,6 @@ AuthRequestUpdateProcessorConfiguration processorConfiguration
await callback.Received()(sut.ProcessedAuthRequest, "iOS - device-id");
}

[Theory]
[BitAutoData]
public async Task SendEventLog_RequestIsApproved_Sends(
OrganizationAdminAuthRequest authRequest,
OrganizationAuthRequestUpdate update,
AuthRequestUpdateProcessorConfiguration processorConfiguration
)
{
(authRequest, processorConfiguration) = UnrespondAndEnsureValid(authRequest, update, processorConfiguration);
update.Approved = true;
update.Key = "key";
var sut = new AuthRequestUpdateProcessor<OrganizationAdminAuthRequest>(authRequest, update, processorConfiguration);
var callback = Substitute.For<Func<OrganizationAdminAuthRequest, EventType, Task>>();
await sut.Process().SendEventLog(callback);
await callback.Received()(sut.ProcessedAuthRequest, EventType.OrganizationUser_ApprovedAuthRequest);
}

[Theory]
[BitAutoData]
public async Task SendEventLog_RequestIsDenied_Sends(
OrganizationAdminAuthRequest authRequest,
OrganizationAuthRequestUpdate update,
AuthRequestUpdateProcessorConfiguration processorConfiguration
)
{
(authRequest, processorConfiguration) = UnrespondAndEnsureValid(authRequest, update, processorConfiguration);
update.Approved = false;
var sut = new AuthRequestUpdateProcessor<OrganizationAdminAuthRequest>(authRequest, update, processorConfiguration);
var callback = Substitute.For<Func<OrganizationAdminAuthRequest, EventType, Task>>();
await sut.Process().SendEventLog(callback);
await callback.Received()(sut.ProcessedAuthRequest, EventType.OrganizationUser_RejectedAuthRequest);
}

private static T Approve<T>(T authRequest) where T : AuthRequest
{
authRequest.Key = "key";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,20 +149,22 @@ Action<Exception> errorHandler

[Theory]
[BitAutoData]
public async Task SendEventLogs_NoProcessors_IsHandled
public async Task SendEventLogs_NoProcessedAuthRequests_IsHandled
(
List<OrganizationAuthRequestUpdate> updates,
AuthRequestUpdateProcessorConfiguration configuration,
Func<AuthRequest, EventType, Task> callback
AuthRequestUpdateProcessorConfiguration configuration
)
{
var sut = new BatchAuthRequestUpdateProcessor<OrganizationAdminAuthRequest>(null, updates, configuration);
var callback = Substitute.For<Func<IEnumerable<(OrganizationAdminAuthRequest, EventType)>, Task>>();
Assert.Empty(sut.Processors);
await sut.SendEventLogs(callback);
await callback.DidNotReceiveWithAnyArgs()(Arg.Any<IEnumerable<(OrganizationAdminAuthRequest, EventType)>>());
}

[Theory]
[BitAutoData]
public async Task SendEventLogs_HasProcessors_Sends
public async Task SendEventLogs_HasProcessedAuthRequests_IsHandled
(
List<OrganizationAdminAuthRequest> authRequests,
List<OrganizationAuthRequestUpdate> updates,
Expand All @@ -172,9 +174,9 @@ Action<Exception> errorHandler
{
(authRequests[0], updates[0], configuration) = UnrespondAndEnsureValid(authRequests[0], updates[0], configuration);
var sut = new BatchAuthRequestUpdateProcessor<OrganizationAdminAuthRequest>(authRequests, updates, configuration);
var callback = Substitute.For<Func<OrganizationAdminAuthRequest, EventType, Task>>();
var callback = Substitute.For<Func<IEnumerable<(OrganizationAdminAuthRequest, EventType)>, Task>>();
await sut.Process(errorHandler).SendEventLogs(callback);
await callback.ReceivedWithAnyArgs()(Arg.Any<OrganizationAdminAuthRequest>(), Arg.Any<EventType>());
await callback.ReceivedWithAnyArgs()(Arg.Any<IEnumerable<(OrganizationAdminAuthRequest, EventType)>>());
}

private (
Expand Down

0 comments on commit b03ea39

Please sign in to comment.