Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/Core/AdminConsole/Services/IEventService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,14 @@ namespace Bit.Core.Services;

public interface IEventService
{
Task LogUserEventAsync(Guid userId, EventType type, DateTime? date = null);
/// <summary>
/// Logs a user event and creates organization-scoped copies for each org the user belongs to.
/// </summary>
/// <param name="includeAcceptedStatusOrgs">When true, also includes organizations where the user
/// has Accepted (not yet Confirmed) status. Use for flows where the user may not be fully confirmed
/// (e.g. device approval, TDE onboarding).
/// </param>
Task LogUserEventAsync(Guid userId, EventType type, DateTime? date = null, bool includeAcceptedStatusOrgs = false);
Task LogCipherEventAsync(Cipher cipher, EventType type, DateTime? date = null);
Task LogCipherEventsAsync(IEnumerable<Tuple<Cipher, EventType, DateTime?>> events);
Task LogCollectionEventAsync(Collection collection, EventType type, DateTime? date = null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ public async Task<AuthRequest> CreateAuthRequestAsync(AuthRequestCreateRequestMo

Debug.Assert(user is not null, "user should have been validated to be non-null and thrown if it's not.");
// A user event will automatically create logs for each organization/provider this user belongs to.
await _eventService.LogUserEventAsync(user.Id, EventType.User_RequestedDeviceApproval);
await _eventService.LogUserEventAsync(user.Id, EventType.User_RequestedDeviceApproval, includeAcceptedStatusOrgs: true);

AuthRequest? firstAuthRequest = null;
foreach (var organizationUser in organizationUsers)
Expand Down
44 changes: 33 additions & 11 deletions src/Core/Dirt/Services/Implementations/EventService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ public EventService(
_globalSettings = globalSettings;
}

public async Task LogUserEventAsync(Guid userId, EventType type, DateTime? date = null)
public async Task LogUserEventAsync(Guid userId, EventType type, DateTime? date = null,
bool includeAcceptedStatusOrgs = false)
{
var events = new List<IEvent>
{
Expand All @@ -58,16 +59,37 @@ public async Task LogUserEventAsync(Guid userId, EventType type, DateTime? date
};

var orgAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync();
var orgs = await _currentContext.OrganizationMembershipAsync(_organizationUserRepository, userId);
var orgEvents = orgs.Where(o => CanUseEvents(orgAbilities, o.Id))
.Select(o => new EventMessage(_currentContext)
{
OrganizationId = o.Id,
UserId = userId,
ActingUserId = userId,
Type = type,
Date = DateTime.UtcNow
});

IEnumerable<EventMessage> orgEvents;
if (includeAcceptedStatusOrgs)
{
var orgUsers = await _organizationUserRepository.GetManyByUserAsync(userId);
orgEvents = orgUsers
.Where(ou => ou.Status is OrganizationUserStatusType.Confirmed
or OrganizationUserStatusType.Accepted)
.Where(ou => CanUseEvents(orgAbilities, ou.OrganizationId))
.Select(ou => new EventMessage(_currentContext)
{
OrganizationId = ou.OrganizationId,
UserId = userId,
ActingUserId = userId,
Type = type,
Date = DateTime.UtcNow
});
}
else
{
var orgs = await _currentContext.OrganizationMembershipAsync(_organizationUserRepository, userId);
orgEvents = orgs.Where(o => CanUseEvents(orgAbilities, o.Id))
.Select(o => new EventMessage(_currentContext)
{
OrganizationId = o.Id,
UserId = userId,
ActingUserId = userId,
Type = type,
Date = DateTime.UtcNow
});
}

var providerAbilities = await _applicationCacheService.GetProviderAbilitiesAsync();
var providers = await _currentContext.ProviderMembershipAsync(_providerUserRepository, userId);
Expand Down
6 changes: 4 additions & 2 deletions test/Core.Test/Auth/Services/AuthRequestServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,8 @@ await sutProvider.GetDependency<IAuthRequestRepository>()

await sutProvider.GetDependency<IEventService>()
.Received(1)
.LogUserEventAsync(user.Id, EventType.User_RequestedDeviceApproval);
.LogUserEventAsync(user.Id, EventType.User_RequestedDeviceApproval,
includeAcceptedStatusOrgs: true);

await sutProvider.GetDependency<IMailService>()
.Received(1)
Expand Down Expand Up @@ -457,7 +458,8 @@ await sutProvider.GetDependency<IAuthRequestRepository>()

await sutProvider.GetDependency<IEventService>()
.Received(1)
.LogUserEventAsync(user.Id, EventType.User_RequestedDeviceApproval);
.LogUserEventAsync(user.Id, EventType.User_RequestedDeviceApproval,
includeAcceptedStatusOrgs: true);

await sutProvider.GetDependency<IMailService>()
.Received(0)
Expand Down
77 changes: 76 additions & 1 deletion test/Core.Test/Dirt/Services/EventServiceTests.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
ο»Ώusing Bit.Core.AdminConsole.Entities;
ο»Ώusing Bit.Core.AdminConsole.Context;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Entities.Provider;
using Bit.Core.AdminConsole.Models.Data.Provider;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Models.Data.Organizations;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
Expand Down Expand Up @@ -271,4 +274,76 @@ public async Task LogProviderOrganizationEventsAsync_LogsRequiredInfo(Provider p

await sutProvider.GetDependency<IEventWriteService>().Received(1).CreateManyAsync(Arg.Is(AssertHelper.AssertPropertyEqual<IEvent>(expected, new[] { "IdempotencyId" })));
}

[Theory, BitAutoData]
public async Task LogUserEvent_IncludeAcceptedStatusOrgs_AcceptedOrgUser_CreatesOrgScopedEvent(
Guid userId, EventType eventType, OrganizationUser orgUser, SutProvider<EventService> sutProvider)
{
orgUser.UserId = userId;
orgUser.Status = OrganizationUserStatusType.Accepted;

var orgAbilities = new Dictionary<Guid, OrganizationAbility>
{
{ orgUser.OrganizationId, new OrganizationAbility { UseEvents = true, Enabled = true } }
};

sutProvider.GetDependency<IApplicationCacheService>()
.GetOrganizationAbilitiesAsync()
.Returns(orgAbilities);
sutProvider.GetDependency<IApplicationCacheService>()
.GetProviderAbilitiesAsync()
.Returns(new Dictionary<Guid, ProviderAbility>());
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByUserAsync(userId)
.Returns(new List<OrganizationUser> { orgUser });
sutProvider.GetDependency<ICurrentContext>()
.ProviderMembershipAsync(Arg.Any<IProviderUserRepository>(), userId)
.Returns(new List<CurrentContextProvider>());

await sutProvider.Sut.LogUserEventAsync(userId, eventType, includeAcceptedStatusOrgs: true);

await sutProvider.GetDependency<IEventWriteService>()
.Received(1)
.CreateManyAsync(Arg.Is<IEnumerable<IEvent>>(events =>
events.Count() == 2
&& events.Any(e => e.OrganizationId == null && e.UserId == userId && e.Type == eventType)
&& events.Any(e => e.OrganizationId == orgUser.OrganizationId && e.UserId == userId && e.Type == eventType)));
}

[Theory, BitAutoData]
public async Task LogUserEvent_IncludeAcceptedStatusOrgs_InvitedOrgUser_DoesNotCreateOrgScopedEvent(
Guid userId, EventType eventType, OrganizationUser orgUser, SutProvider<EventService> sutProvider)
{
orgUser.UserId = userId;
orgUser.Status = OrganizationUserStatusType.Invited;

var orgAbilities = new Dictionary<Guid, OrganizationAbility>
{
{ orgUser.OrganizationId, new OrganizationAbility { UseEvents = true, Enabled = true } }
};

sutProvider.GetDependency<IApplicationCacheService>()
.GetOrganizationAbilitiesAsync()
.Returns(orgAbilities);
sutProvider.GetDependency<IApplicationCacheService>()
.GetProviderAbilitiesAsync()
.Returns(new Dictionary<Guid, ProviderAbility>());
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByUserAsync(userId)
.Returns(new List<OrganizationUser> { orgUser });
sutProvider.GetDependency<ICurrentContext>()
.ProviderMembershipAsync(
Arg.Any<IProviderUserRepository>(), userId)
.Returns(new List<CurrentContextProvider>());

await sutProvider.Sut.LogUserEventAsync(userId, eventType, includeAcceptedStatusOrgs: true);

await sutProvider.GetDependency<IEventWriteService>()
.Received(1)
.CreateAsync(Arg.Is<IEvent>(e =>
e.OrganizationId == null && e.UserId == userId && e.Type == eventType));
await sutProvider.GetDependency<IEventWriteService>()
.DidNotReceiveWithAnyArgs()
.CreateManyAsync(default);
}
}
Loading