1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-05 13:08:17 -05:00

Log events from the import organization flow (#4632)

* Log events from the import organization flow

* Use an interface for the `OrganizationUser` object used to log events

* Log import events as being from the public api if they are

* Add logging for created groups

* Log proper group ids

* Fix tests

* Also log update events for groups

* Remove private API `import` endpoint

* Make `eventSystemUser` non-nullable for `ImportAsync`

* Fix tests

* Delete `ImportOrganizationUsersRequestModel`

* Fix tests
This commit is contained in:
Addison Beck 2024-08-27 18:19:48 -04:00 committed by GitHub
parent 6764131934
commit acb71d87d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 85 additions and 143 deletions

View File

@ -1,5 +1,4 @@
using System.Text.Json; using System.Text.Json;
using Bit.Api.AdminConsole.Models.Request;
using Bit.Api.AdminConsole.Models.Request.Organizations; using Bit.Api.AdminConsole.Models.Request.Organizations;
using Bit.Api.AdminConsole.Models.Response; using Bit.Api.AdminConsole.Models.Response;
using Bit.Api.AdminConsole.Models.Response.Organizations; using Bit.Api.AdminConsole.Models.Response.Organizations;
@ -312,31 +311,6 @@ public class OrganizationsController : Controller
await _organizationService.DeleteAsync(organization); await _organizationService.DeleteAsync(organization);
} }
[HttpPost("{id}/import")]
public async Task Import(string id, [FromBody] ImportOrganizationUsersRequestModel model)
{
if (!_globalSettings.SelfHosted && !model.LargeImport &&
(model.Groups.Count() > 2000 || model.Users.Count(u => !u.Deleted) > 2000))
{
throw new BadRequestException("You cannot import this much data at once.");
}
var orgIdGuid = new Guid(id);
if (!await _currentContext.OrganizationAdmin(orgIdGuid))
{
throw new NotFoundException();
}
var userId = _userService.GetProperUserId(User);
await _organizationService.ImportAsync(
orgIdGuid,
userId.Value,
model.Groups.Select(g => g.ToImportedGroup(orgIdGuid)),
model.Users.Where(u => !u.Deleted).Select(u => u.ToImportedOrganizationUser()),
model.Users.Where(u => u.Deleted).Select(u => u.ExternalId),
model.OverwriteExisting);
}
[HttpPost("{id}/api-key")] [HttpPost("{id}/api-key")]
public async Task<ApiKeyResponseModel> ApiKey(string id, [FromBody] OrganizationApiKeyRequestModel model) public async Task<ApiKeyResponseModel> ApiKey(string id, [FromBody] OrganizationApiKeyRequestModel model)
{ {

View File

@ -1,70 +0,0 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.AdminConsole.Models.Business;
using Bit.Core.Models.Business;
namespace Bit.Api.AdminConsole.Models.Request;
public class ImportOrganizationUsersRequestModel
{
public Group[] Groups { get; set; }
public User[] Users { get; set; }
public bool OverwriteExisting { get; set; }
public bool LargeImport { get; set; }
public class Group
{
[Required]
[StringLength(100)]
public string Name { get; set; }
[Required]
[StringLength(300)]
public string ExternalId { get; set; }
public IEnumerable<string> Users { get; set; }
public ImportedGroup ToImportedGroup(Guid organizationId)
{
var importedGroup = new ImportedGroup
{
Group = new Core.AdminConsole.Entities.Group
{
OrganizationId = organizationId,
Name = Name,
ExternalId = ExternalId
},
ExternalUserIds = new HashSet<string>(Users)
};
return importedGroup;
}
}
public class User : IValidatableObject
{
[EmailAddress]
[StringLength(256)]
public string Email { get; set; }
public bool Deleted { get; set; }
[Required]
[StringLength(300)]
public string ExternalId { get; set; }
public ImportedOrganizationUser ToImportedOrganizationUser()
{
var importedUser = new ImportedOrganizationUser
{
Email = Email.ToLowerInvariant(),
ExternalId = ExternalId
};
return importedUser;
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (string.IsNullOrWhiteSpace(Email) && !Deleted)
{
yield return new ValidationResult("Email is required for enabled users.", new string[] { nameof(Email) });
}
}
}
}

View File

@ -3,6 +3,7 @@ using Bit.Api.AdminConsole.Public.Models.Request;
using Bit.Api.AdminConsole.Public.Models.Response; using Bit.Api.AdminConsole.Public.Models.Response;
using Bit.Api.Models.Public.Response; using Bit.Api.Models.Public.Response;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Settings; using Bit.Core.Settings;
@ -49,11 +50,11 @@ public class OrganizationController : Controller
await _organizationService.ImportAsync( await _organizationService.ImportAsync(
_currentContext.OrganizationId.Value, _currentContext.OrganizationId.Value,
null,
model.Groups.Select(g => g.ToImportedGroup(_currentContext.OrganizationId.Value)), model.Groups.Select(g => g.ToImportedGroup(_currentContext.OrganizationId.Value)),
model.Members.Where(u => !u.Deleted).Select(u => u.ToImportedOrganizationUser()), model.Members.Where(u => !u.Deleted).Select(u => u.ToImportedOrganizationUser()),
model.Members.Where(u => u.Deleted).Select(u => u.ExternalId), model.Members.Where(u => u.Deleted).Select(u => u.ExternalId),
model.OverwriteExisting.GetValueOrDefault()); model.OverwriteExisting.GetValueOrDefault(),
EventSystemUser.PublicApi);
return new OkResult(); return new OkResult();
} }
} }

View File

@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Bit.Core.AdminConsole.Interfaces;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models; using Bit.Core.Models;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
@ -8,7 +9,7 @@ using Bit.Core.Utilities;
namespace Bit.Core.Entities; namespace Bit.Core.Entities;
public class OrganizationUser : ITableObject<Guid>, IExternal public class OrganizationUser : ITableObject<Guid>, IExternal, IOrganizationUser
{ {
public Guid Id { get; set; } public Guid Id { get; set; }
public Guid OrganizationId { get; set; } public Guid OrganizationId { get; set; }

View File

@ -3,5 +3,6 @@
public enum EventSystemUser : byte public enum EventSystemUser : byte
{ {
SCIM = 1, SCIM = 1,
DomainVerification = 2 DomainVerification = 2,
PublicApi = 3,
} }

View File

@ -0,0 +1,8 @@
namespace Bit.Core.AdminConsole.Interfaces;
public interface IOrganizationUser
{
public Guid Id { get; set; }
public Guid OrganizationId { get; set; }
public Guid? UserId { get; set; }
}

View File

@ -1,11 +1,12 @@
using Bit.Core.Auth.Enums; using Bit.Core.AdminConsole.Interfaces;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Models; using Bit.Core.Auth.Models;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Utilities; using Bit.Core.Utilities;
namespace Bit.Core.Models.Data.Organizations.OrganizationUsers; namespace Bit.Core.Models.Data.Organizations.OrganizationUsers;
public class OrganizationUserUserDetails : IExternal, ITwoFactorProvidersUser public class OrganizationUserUserDetails : IExternal, ITwoFactorProvidersUser, IOrganizationUser
{ {
private Dictionary<TwoFactorProviderType, TwoFactorProvider> _twoFactorProviders; private Dictionary<TwoFactorProviderType, TwoFactorProvider> _twoFactorProviders;

View File

@ -1,5 +1,6 @@
using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.AdminConsole.Entities.Provider;
using Bit.Core.AdminConsole.Interfaces;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Entities;
@ -18,10 +19,10 @@ public interface IEventService
Task LogGroupEventAsync(Group group, EventType type, EventSystemUser systemUser, DateTime? date = null); Task LogGroupEventAsync(Group group, EventType type, EventSystemUser systemUser, DateTime? date = null);
Task LogGroupEventsAsync(IEnumerable<(Group group, EventType type, EventSystemUser? systemUser, DateTime? date)> events); Task LogGroupEventsAsync(IEnumerable<(Group group, EventType type, EventSystemUser? systemUser, DateTime? date)> events);
Task LogPolicyEventAsync(Policy policy, EventType type, DateTime? date = null); Task LogPolicyEventAsync(Policy policy, EventType type, DateTime? date = null);
Task LogOrganizationUserEventAsync(OrganizationUser organizationUser, EventType type, DateTime? date = null); Task LogOrganizationUserEventAsync<T>(T organizationUser, EventType type, DateTime? date = null) where T : IOrganizationUser;
Task LogOrganizationUserEventAsync(OrganizationUser organizationUser, EventType type, EventSystemUser systemUser, DateTime? date = null); Task LogOrganizationUserEventAsync<T>(T organizationUser, EventType type, EventSystemUser systemUser, DateTime? date = null) where T : IOrganizationUser;
Task LogOrganizationUserEventsAsync(IEnumerable<(OrganizationUser, EventType, DateTime?)> events); Task LogOrganizationUserEventsAsync<T>(IEnumerable<(T, EventType, DateTime?)> events) where T : IOrganizationUser;
Task LogOrganizationUserEventsAsync(IEnumerable<(OrganizationUser, EventType, EventSystemUser, DateTime?)> events); Task LogOrganizationUserEventsAsync<T>(IEnumerable<(T, EventType, EventSystemUser, DateTime?)> events) where T : IOrganizationUser;
Task LogOrganizationEventAsync(Organization organization, EventType type, DateTime? date = null); Task LogOrganizationEventAsync(Organization organization, EventType type, DateTime? date = null);
Task LogProviderUserEventAsync(ProviderUser providerUser, EventType type, DateTime? date = null); Task LogProviderUserEventAsync(ProviderUser providerUser, EventType type, DateTime? date = null);
Task LogProviderUsersEventAsync(IEnumerable<(ProviderUser, EventType, DateTime?)> events); Task LogProviderUsersEventAsync(IEnumerable<(ProviderUser, EventType, DateTime?)> events);

View File

@ -63,9 +63,9 @@ public interface IOrganizationService
Task<List<Tuple<OrganizationUser, string>>> DeleteUsersAsync(Guid organizationId, Task<List<Tuple<OrganizationUser, string>>> DeleteUsersAsync(Guid organizationId,
IEnumerable<Guid> organizationUserIds, Guid? deletingUserId); IEnumerable<Guid> organizationUserIds, Guid? deletingUserId);
Task UpdateUserResetPasswordEnrollmentAsync(Guid organizationId, Guid userId, string resetPasswordKey, Guid? callingUserId); Task UpdateUserResetPasswordEnrollmentAsync(Guid organizationId, Guid userId, string resetPasswordKey, Guid? callingUserId);
Task ImportAsync(Guid organizationId, Guid? importingUserId, IEnumerable<ImportedGroup> groups, Task ImportAsync(Guid organizationId, IEnumerable<ImportedGroup> groups,
IEnumerable<ImportedOrganizationUser> newUsers, IEnumerable<string> removeUserExternalIds, IEnumerable<ImportedOrganizationUser> newUsers, IEnumerable<string> removeUserExternalIds,
bool overwriteExisting); bool overwriteExisting, EventSystemUser eventSystemUser);
Task DeleteSsoUserAsync(Guid userId, Guid? organizationId); Task DeleteSsoUserAsync(Guid userId, Guid? organizationId);
Task<Organization> UpdateOrganizationKeysAsync(Guid orgId, string publicKey, string privateKey); Task<Organization> UpdateOrganizationKeysAsync(Guid orgId, string publicKey, string privateKey);
Task<bool> HasConfirmedOwnersExceptAsync(Guid organizationId, IEnumerable<Guid> organizationUsersId, bool includeProvider = true); Task<bool> HasConfirmedOwnersExceptAsync(Guid organizationId, IEnumerable<Guid> organizationUsersId, bool includeProvider = true);

View File

@ -1,5 +1,6 @@
using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.AdminConsole.Entities.Provider;
using Bit.Core.AdminConsole.Interfaces;
using Bit.Core.AdminConsole.Models.Data.Provider; using Bit.Core.AdminConsole.Models.Data.Provider;
using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Context; using Bit.Core.Context;
@ -229,27 +230,27 @@ public class EventService : IEventService
await _eventWriteService.CreateAsync(e); await _eventWriteService.CreateAsync(e);
} }
public async Task LogOrganizationUserEventAsync(OrganizationUser organizationUser, EventType type, public async Task LogOrganizationUserEventAsync<T>(T organizationUser, EventType type,
DateTime? date = null) => DateTime? date = null) where T : IOrganizationUser =>
await CreateLogOrganizationUserEventsAsync(new (OrganizationUser, EventType, EventSystemUser?, DateTime?)[] { (organizationUser, type, null, date) }); await CreateLogOrganizationUserEventsAsync(new (T, EventType, EventSystemUser?, DateTime?)[] { (organizationUser, type, null, date) });
public async Task LogOrganizationUserEventAsync(OrganizationUser organizationUser, EventType type, public async Task LogOrganizationUserEventAsync<T>(T organizationUser, EventType type,
EventSystemUser systemUser, DateTime? date = null) => EventSystemUser systemUser, DateTime? date = null) where T : IOrganizationUser =>
await CreateLogOrganizationUserEventsAsync(new (OrganizationUser, EventType, EventSystemUser?, DateTime?)[] { (organizationUser, type, systemUser, date) }); await CreateLogOrganizationUserEventsAsync(new (T, EventType, EventSystemUser?, DateTime?)[] { (organizationUser, type, systemUser, date) });
public async Task LogOrganizationUserEventsAsync( public async Task LogOrganizationUserEventsAsync<T>(
IEnumerable<(OrganizationUser, EventType, DateTime?)> events) IEnumerable<(T, EventType, DateTime?)> events) where T : IOrganizationUser
{ {
await CreateLogOrganizationUserEventsAsync(events.Select(e => (e.Item1, e.Item2, (EventSystemUser?)null, e.Item3))); await CreateLogOrganizationUserEventsAsync(events.Select(e => (e.Item1, e.Item2, (EventSystemUser?)null, e.Item3)));
} }
public async Task LogOrganizationUserEventsAsync( public async Task LogOrganizationUserEventsAsync<T>(
IEnumerable<(OrganizationUser, EventType, EventSystemUser, DateTime?)> events) IEnumerable<(T, EventType, EventSystemUser, DateTime?)> events) where T : IOrganizationUser
{ {
await CreateLogOrganizationUserEventsAsync(events.Select(e => (e.Item1, e.Item2, (EventSystemUser?)e.Item3, e.Item4))); await CreateLogOrganizationUserEventsAsync(events.Select(e => (e.Item1, e.Item2, (EventSystemUser?)e.Item3, e.Item4)));
} }
private async Task CreateLogOrganizationUserEventsAsync(IEnumerable<(OrganizationUser, EventType, EventSystemUser?, DateTime?)> events) private async Task CreateLogOrganizationUserEventsAsync<T>(IEnumerable<(T, EventType, EventSystemUser?, DateTime?)> events) where T : IOrganizationUser
{ {
var orgAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync(); var orgAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync();
var eventMessages = new List<IEvent>(); var eventMessages = new List<IEvent>();

View File

@ -21,6 +21,7 @@ using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Bit.Core.Models.Business; using Bit.Core.Models.Business;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Models.Mail; using Bit.Core.Models.Mail;
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface; using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
using Bit.Core.Repositories; using Bit.Core.Repositories;
@ -1776,11 +1777,12 @@ public class OrganizationService : IOrganizationService
} }
public async Task ImportAsync(Guid organizationId, public async Task ImportAsync(Guid organizationId,
Guid? importingUserId,
IEnumerable<ImportedGroup> groups, IEnumerable<ImportedGroup> groups,
IEnumerable<ImportedOrganizationUser> newUsers, IEnumerable<ImportedOrganizationUser> newUsers,
IEnumerable<string> removeUserExternalIds, IEnumerable<string> removeUserExternalIds,
bool overwriteExisting) bool overwriteExisting,
EventSystemUser eventSystemUser
)
{ {
var organization = await GetOrgById(organizationId); var organization = await GetOrgById(organizationId);
if (organization == null) if (organization == null)
@ -1800,16 +1802,24 @@ public class OrganizationService : IOrganizationService
// Users // Users
var events = new List<(OrganizationUserUserDetails ou, EventType e, DateTime? d)>();
// Remove Users // Remove Users
if (removeUserExternalIds?.Any() ?? false) if (removeUserExternalIds?.Any() ?? false)
{ {
var removeUsersSet = new HashSet<string>(removeUserExternalIds);
var existingUsersDict = existingExternalUsers.ToDictionary(u => u.ExternalId); var existingUsersDict = existingExternalUsers.ToDictionary(u => u.ExternalId);
var removeUsersSet = new HashSet<string>(removeUserExternalIds)
await _organizationUserRepository.DeleteManyAsync(removeUsersSet
.Except(newUsersSet) .Except(newUsersSet)
.Where(u => existingUsersDict.ContainsKey(u) && existingUsersDict[u].Type != OrganizationUserType.Owner) .Where(u => existingUsersDict.ContainsKey(u) && existingUsersDict[u].Type != OrganizationUserType.Owner)
.Select(u => existingUsersDict[u].Id)); .Select(u => existingUsersDict[u]);
await _organizationUserRepository.DeleteManyAsync(removeUsersSet.Select(u => u.Id));
events.AddRange(removeUsersSet.Select(u => (
u,
EventType.OrganizationUser_Removed,
(DateTime?)DateTime.UtcNow
))
);
} }
if (overwriteExisting) if (overwriteExisting)
@ -1820,6 +1830,12 @@ public class OrganizationService : IOrganizationService
!newUsersSet.Contains(u.ExternalId) && !newUsersSet.Contains(u.ExternalId) &&
existingExternalUsersIdDict.ContainsKey(u.ExternalId)); existingExternalUsersIdDict.ContainsKey(u.ExternalId));
await _organizationUserRepository.DeleteManyAsync(usersToDelete.Select(u => u.Id)); await _organizationUserRepository.DeleteManyAsync(usersToDelete.Select(u => u.Id));
events.AddRange(usersToDelete.Select(u => (
u,
EventType.OrganizationUser_Removed,
(DateTime?)DateTime.UtcNow
))
);
foreach (var deletedUser in usersToDelete) foreach (var deletedUser in usersToDelete)
{ {
existingExternalUsersIdDict.Remove(deletedUser.ExternalId); existingExternalUsersIdDict.Remove(deletedUser.ExternalId);
@ -1889,7 +1905,7 @@ public class OrganizationService : IOrganizationService
} }
} }
var invitedUsers = await InviteUsersAsync(organizationId, importingUserId, systemUser: null, userInvites); var invitedUsers = await InviteUsersAsync(organizationId, invitingUserId: null, systemUser: eventSystemUser, userInvites);
foreach (var invitedUser in invitedUsers) foreach (var invitedUser in invitedUsers)
{ {
existingExternalUsersIdDict.Add(invitedUser.ExternalId, invitedUser.Id); existingExternalUsersIdDict.Add(invitedUser.ExternalId, invitedUser.Id);
@ -1913,17 +1929,21 @@ public class OrganizationService : IOrganizationService
var newGroups = groups var newGroups = groups
.Where(g => !existingExternalGroupsDict.ContainsKey(g.Group.ExternalId)) .Where(g => !existingExternalGroupsDict.ContainsKey(g.Group.ExternalId))
.Select(g => g.Group); .Select(g => g.Group).ToList();
var savedGroups = new List<Group>();
foreach (var group in newGroups) foreach (var group in newGroups)
{ {
group.CreationDate = group.RevisionDate = DateTime.UtcNow; group.CreationDate = group.RevisionDate = DateTime.UtcNow;
await _groupRepository.CreateAsync(group); savedGroups.Add(await _groupRepository.CreateAsync(group));
await UpdateUsersAsync(group, groupsDict[group.ExternalId].ExternalUserIds, await UpdateUsersAsync(group, groupsDict[group.ExternalId].ExternalUserIds,
existingExternalUsersIdDict); existingExternalUsersIdDict);
} }
await _eventService.LogGroupEventsAsync(
savedGroups.Select(g => (g, EventType.Group_Created, (EventSystemUser?)eventSystemUser, (DateTime?)DateTime.UtcNow)));
var updateGroups = existingExternalGroups var updateGroups = existingExternalGroups
.Where(g => groupsDict.ContainsKey(g.ExternalId)) .Where(g => groupsDict.ContainsKey(g.ExternalId))
.ToList(); .ToList();
@ -1949,10 +1969,15 @@ public class OrganizationService : IOrganizationService
await UpdateUsersAsync(group, groupsDict[group.ExternalId].ExternalUserIds, await UpdateUsersAsync(group, groupsDict[group.ExternalId].ExternalUserIds,
existingExternalUsersIdDict, existingExternalUsersIdDict,
existingGroupUsers.ContainsKey(group.Id) ? existingGroupUsers[group.Id] : null); existingGroupUsers.ContainsKey(group.Id) ? existingGroupUsers[group.Id] : null);
} }
await _eventService.LogGroupEventsAsync(
updateGroups.Select(g => (g, EventType.Group_Updated, (EventSystemUser?)eventSystemUser, (DateTime?)DateTime.UtcNow)));
} }
} }
await _eventService.LogOrganizationUserEventsAsync(events.Select(e => (e.ou, e.e, eventSystemUser, e.d)));
await _referenceEventService.RaiseEventAsync( await _referenceEventService.RaiseEventAsync(
new ReferenceEvent(ReferenceEventType.DirectorySynced, organization, _currentContext)); new ReferenceEvent(ReferenceEventType.DirectorySynced, organization, _currentContext));
} }

View File

@ -1,5 +1,6 @@
using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.AdminConsole.Entities.Provider;
using Bit.Core.AdminConsole.Interfaces;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.SecretsManager.Entities; using Bit.Core.SecretsManager.Entities;
@ -89,23 +90,23 @@ public class NoopEventService : IEventService
return Task.FromResult(0); return Task.FromResult(0);
} }
public Task LogOrganizationUserEventAsync(OrganizationUser organizationUser, EventType type, DateTime? date = null) public Task LogOrganizationUserEventAsync<T>(T organizationUser, EventType type, DateTime? date = null) where T : IOrganizationUser
{ {
return Task.FromResult(0); return Task.FromResult(0);
} }
public Task LogOrganizationUserEventAsync(OrganizationUser organizationUser, EventType type, public Task LogOrganizationUserEventAsync<T>(T organizationUser, EventType type,
EventSystemUser systemUser, DateTime? date = null) EventSystemUser systemUser, DateTime? date = null) where T : IOrganizationUser
{ {
return Task.FromResult(0); return Task.FromResult(0);
} }
public Task LogOrganizationUserEventsAsync(IEnumerable<(OrganizationUser, EventType, DateTime?)> events) public Task LogOrganizationUserEventsAsync<T>(IEnumerable<(T, EventType, DateTime?)> events) where T : IOrganizationUser
{ {
return Task.FromResult(0); return Task.FromResult(0);
} }
public Task LogOrganizationUserEventsAsync(IEnumerable<(OrganizationUser, EventType, EventSystemUser, DateTime?)> events) public Task LogOrganizationUserEventsAsync<T>(IEnumerable<(T, EventType, EventSystemUser, DateTime?)> events) where T : IOrganizationUser
{ {
return Task.FromResult(0); return Task.FromResult(0);
} }

View File

@ -84,6 +84,6 @@ public class GroupServiceTests
await sutProvider.GetDependency<IGroupRepository>().DidNotReceiveWithAnyArgs() await sutProvider.GetDependency<IGroupRepository>().DidNotReceiveWithAnyArgs()
.DeleteUserAsync(default, default); .DeleteUserAsync(default, default);
await sutProvider.GetDependency<IEventService>().DidNotReceiveWithAnyArgs() await sutProvider.GetDependency<IEventService>().DidNotReceiveWithAnyArgs()
.LogOrganizationUserEventAsync(default, default); .LogOrganizationUserEventAsync<OrganizationUser>(default, default);
} }
} }

View File

@ -52,8 +52,7 @@ public class OrganizationServiceTests
private readonly IDataProtectorTokenFactory<OrgUserInviteTokenable> _orgUserInviteTokenDataFactory = new FakeDataProtectorTokenFactory<OrgUserInviteTokenable>(); private readonly IDataProtectorTokenFactory<OrgUserInviteTokenable> _orgUserInviteTokenDataFactory = new FakeDataProtectorTokenFactory<OrgUserInviteTokenable>();
[Theory, PaidOrganizationCustomize, BitAutoData] [Theory, PaidOrganizationCustomize, BitAutoData]
public async Task OrgImportCreateNewUsers(SutProvider<OrganizationService> sutProvider, Guid userId, public async Task OrgImportCreateNewUsers(SutProvider<OrganizationService> sutProvider, Organization org, List<OrganizationUserUserDetails> existingUsers, List<ImportedOrganizationUser> newUsers)
Organization org, List<OrganizationUserUserDetails> existingUsers, List<ImportedOrganizationUser> newUsers)
{ {
// Setup FakeDataProtectorTokenFactory for creating new tokens - this must come first in order to avoid resetting mocks // Setup FakeDataProtectorTokenFactory for creating new tokens - this must come first in order to avoid resetting mocks
sutProvider.SetDependency(_orgUserInviteTokenDataFactory, "orgUserInviteTokenDataFactory"); sutProvider.SetDependency(_orgUserInviteTokenDataFactory, "orgUserInviteTokenDataFactory");
@ -93,7 +92,7 @@ public class OrganizationServiceTests
} }
); );
await sutProvider.Sut.ImportAsync(org.Id, userId, null, newUsers, null, false); await sutProvider.Sut.ImportAsync(org.Id, null, newUsers, null, false, EventSystemUser.PublicApi);
await sutProvider.GetDependency<IOrganizationUserRepository>().DidNotReceiveWithAnyArgs() await sutProvider.GetDependency<IOrganizationUserRepository>().DidNotReceiveWithAnyArgs()
.UpsertAsync(default); .UpsertAsync(default);
@ -112,7 +111,7 @@ public class OrganizationServiceTests
// Send events // Send events
await sutProvider.GetDependency<IEventService>().Received(1) await sutProvider.GetDependency<IEventService>().Received(1)
.LogOrganizationUserEventsAsync(Arg.Is<IEnumerable<(OrganizationUser, EventType, DateTime?)>>(events => .LogOrganizationUserEventsAsync(Arg.Is<IEnumerable<(OrganizationUser, EventType, EventSystemUser, DateTime?)>>(events =>
events.Count() == expectedNewUsersCount)); events.Count() == expectedNewUsersCount));
await sutProvider.GetDependency<IReferenceEventService>().Received(1) await sutProvider.GetDependency<IReferenceEventService>().Received(1)
.RaiseEventAsync(Arg.Is<ReferenceEvent>(referenceEvent => .RaiseEventAsync(Arg.Is<ReferenceEvent>(referenceEvent =>
@ -121,8 +120,7 @@ public class OrganizationServiceTests
} }
[Theory, PaidOrganizationCustomize, BitAutoData] [Theory, PaidOrganizationCustomize, BitAutoData]
public async Task OrgImportCreateNewUsersAndMarryExistingUser(SutProvider<OrganizationService> sutProvider, public async Task OrgImportCreateNewUsersAndMarryExistingUser(SutProvider<OrganizationService> sutProvider, Organization org, List<OrganizationUserUserDetails> existingUsers,
Guid userId, Organization org, List<OrganizationUserUserDetails> existingUsers,
List<ImportedOrganizationUser> newUsers) List<ImportedOrganizationUser> newUsers)
{ {
// Setup FakeDataProtectorTokenFactory for creating new tokens - this must come first in order to avoid resetting mocks // Setup FakeDataProtectorTokenFactory for creating new tokens - this must come first in order to avoid resetting mocks
@ -168,7 +166,7 @@ public class OrganizationServiceTests
} }
); );
await sutProvider.Sut.ImportAsync(org.Id, userId, null, newUsers, null, false); await sutProvider.Sut.ImportAsync(org.Id, null, newUsers, null, false, EventSystemUser.PublicApi);
await sutProvider.GetDependency<IOrganizationUserRepository>().DidNotReceiveWithAnyArgs() await sutProvider.GetDependency<IOrganizationUserRepository>().DidNotReceiveWithAnyArgs()
.UpsertAsync(default); .UpsertAsync(default);
@ -191,7 +189,7 @@ public class OrganizationServiceTests
// Sent events // Sent events
await sutProvider.GetDependency<IEventService>().Received(1) await sutProvider.GetDependency<IEventService>().Received(1)
.LogOrganizationUserEventsAsync(Arg.Is<IEnumerable<(OrganizationUser, EventType, DateTime?)>>(events => .LogOrganizationUserEventsAsync(Arg.Is<IEnumerable<(OrganizationUser, EventType, EventSystemUser, DateTime?)>>(events =>
events.Where(e => e.Item2 == EventType.OrganizationUser_Invited).Count() == expectedNewUsersCount)); events.Where(e => e.Item2 == EventType.OrganizationUser_Invited).Count() == expectedNewUsersCount));
await sutProvider.GetDependency<IReferenceEventService>().Received(1) await sutProvider.GetDependency<IReferenceEventService>().Received(1)
.RaiseEventAsync(Arg.Is<ReferenceEvent>(referenceEvent => .RaiseEventAsync(Arg.Is<ReferenceEvent>(referenceEvent =>

View File

@ -174,7 +174,7 @@ public class CollectionServiceTest
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteUserAsync(collection, Guid.NewGuid())); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.DeleteUserAsync(collection, Guid.NewGuid()));
await sutProvider.GetDependency<ICollectionRepository>().DidNotReceiveWithAnyArgs().DeleteUserAsync(default, default); await sutProvider.GetDependency<ICollectionRepository>().DidNotReceiveWithAnyArgs().DeleteUserAsync(default, default);
await sutProvider.GetDependency<IEventService>().DidNotReceiveWithAnyArgs() await sutProvider.GetDependency<IEventService>().DidNotReceiveWithAnyArgs()
.LogOrganizationUserEventAsync(default, default); .LogOrganizationUserEventAsync<OrganizationUser>(default, default);
} }
[Theory, BitAutoData] [Theory, BitAutoData]