From 28770d3761657cbcf5a1cbb83ce5bbf643bcc161 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin <kyle.spearrin@gmail.com> Date: Fri, 1 Dec 2017 16:00:30 -0500 Subject: [PATCH] events for collections, groups, and org users --- src/Api/Controllers/CollectionsController.cs | 2 +- src/Api/Controllers/GroupsController.cs | 2 +- .../OrganizationUsersController.cs | 4 +-- src/Core/Enums/EventType.cs | 5 +++- src/Core/Models/Data/CipherEvent.cs | 2 +- src/Core/Models/Data/CollectionEvent.cs | 23 ++++++++++++++++ src/Core/Models/Data/EventTableEntity.cs | 3 +++ src/Core/Models/Data/GroupEvent.cs | 23 ++++++++++++++++ src/Core/Models/Data/OrganizationEvent.cs | 22 +++++----------- src/Core/Models/Data/OrganizationUserEvent.cs | 24 +++++++++++++++++ src/Core/Services/ICollectionService.cs | 2 +- src/Core/Services/IEventService.cs | 4 +++ src/Core/Services/IGroupService.cs | 1 + src/Core/Services/IOrganizationService.cs | 1 + .../Implementations/CollectionService.cs | 13 ++++++++++ .../Services/Implementations/EventService.cs | 26 ++++++++++++++++++- .../Services/Implementations/GroupService.cs | 12 +++++++++ .../Implementations/OrganizationService.cs | 14 ++++++++++ .../NoopImplementations/NoopEventService.cs | 20 ++++++++++++++ .../Utilities/ServiceCollectionExtensions.cs | 6 ++--- 20 files changed, 182 insertions(+), 27 deletions(-) create mode 100644 src/Core/Models/Data/CollectionEvent.cs create mode 100644 src/Core/Models/Data/GroupEvent.cs create mode 100644 src/Core/Models/Data/OrganizationUserEvent.cs diff --git a/src/Api/Controllers/CollectionsController.cs b/src/Api/Controllers/CollectionsController.cs index cf8571c85c..72e5683216 100644 --- a/src/Api/Controllers/CollectionsController.cs +++ b/src/Api/Controllers/CollectionsController.cs @@ -135,7 +135,7 @@ namespace Bit.Api.Controllers throw new NotFoundException(); } - await _collectionRepository.DeleteAsync(collection); + await _collectionService.DeleteAsync(collection); } [HttpDelete("{id}/user/{orgUserId}")] diff --git a/src/Api/Controllers/GroupsController.cs b/src/Api/Controllers/GroupsController.cs index e9eea63a51..b61e0cb5d7 100644 --- a/src/Api/Controllers/GroupsController.cs +++ b/src/Api/Controllers/GroupsController.cs @@ -121,7 +121,7 @@ namespace Bit.Api.Controllers throw new NotFoundException(); } - await _groupRepository.DeleteAsync(group); + await _groupService.DeleteAsync(group); } [HttpDelete("{id}/user/{orgUserId}")] diff --git a/src/Api/Controllers/OrganizationUsersController.cs b/src/Api/Controllers/OrganizationUsersController.cs index ee5da0dfcf..2af90a40a0 100644 --- a/src/Api/Controllers/OrganizationUsersController.cs +++ b/src/Api/Controllers/OrganizationUsersController.cs @@ -178,8 +178,8 @@ namespace Bit.Api.Controllers { throw new BadRequestException("Only owners can update other owners."); } - - await _organizationUserRepository.UpdateGroupsAsync(organizationUser.Id, model.GroupIds.Select(g => new Guid(g))); + + await _organizationService.UpdateUserGroupsAsync(organizationUser, model.GroupIds.Select(g => new Guid(g))); } [HttpDelete("{id}")] diff --git a/src/Core/Enums/EventType.cs b/src/Core/Enums/EventType.cs index f027a3699a..d6d6080194 100644 --- a/src/Core/Enums/EventType.cs +++ b/src/Core/Enums/EventType.cs @@ -29,6 +29,9 @@ OrganizationUser_Invited = 1500, OrganizationUser_Confirmed = 1501, OrganizationUser_Updated = 1502, - OrganizationUser_Removed = 1503 + OrganizationUser_Removed = 1503, + OrganizationUser_UpdatedGroups = 1504, + + Organization_Updated = 1600 } } diff --git a/src/Core/Models/Data/CipherEvent.cs b/src/Core/Models/Data/CipherEvent.cs index 463ceb7c75..ccd0dae962 100644 --- a/src/Core/Models/Data/CipherEvent.cs +++ b/src/Core/Models/Data/CipherEvent.cs @@ -7,7 +7,7 @@ namespace Bit.Core.Models.Data { public class CipherEvent : EventTableEntity { - public CipherEvent(Cipher cipher, EventType type, Guid? actingUserId = null) + public CipherEvent(Cipher cipher, Guid? actingUserId, EventType type) { OrganizationId = cipher.OrganizationId; UserId = cipher.UserId; diff --git a/src/Core/Models/Data/CollectionEvent.cs b/src/Core/Models/Data/CollectionEvent.cs new file mode 100644 index 0000000000..64d1765088 --- /dev/null +++ b/src/Core/Models/Data/CollectionEvent.cs @@ -0,0 +1,23 @@ +using System; +using Bit.Core.Enums; +using Bit.Core.Models.Table; +using Bit.Core.Utilities; + +namespace Bit.Core.Models.Data +{ + public class CollectionEvent : EventTableEntity + { + public CollectionEvent(Collection collection, Guid actingUserId, EventType type) + { + OrganizationId = collection.OrganizationId; + CollectionId = collection.Id; + Type = (int)type; + ActingUserId = actingUserId; + + Timestamp = DateTime.UtcNow; + PartitionKey = $"OrganizationId={OrganizationId}"; + RowKey = string.Format("Date={0}__ActingUserId={1}__Type={2}", + CoreHelpers.DateTimeToTableStorageKey(Timestamp.DateTime), ActingUserId, Type); + } + } +} diff --git a/src/Core/Models/Data/EventTableEntity.cs b/src/Core/Models/Data/EventTableEntity.cs index e0e1cf1ece..4285f24431 100644 --- a/src/Core/Models/Data/EventTableEntity.cs +++ b/src/Core/Models/Data/EventTableEntity.cs @@ -11,6 +11,9 @@ namespace Bit.Core.Models.Data public Guid? OrganizationId { get; set; } public Guid? CipherId { get; set; } public ICollection<Guid> CipherIds { get; set; } + public Guid? CollectionId { get; set; } + public Guid? GroupId { get; set; } + public Guid? OrganizationUserId { get; set; } public Guid? ActingUserId { get; set; } } } diff --git a/src/Core/Models/Data/GroupEvent.cs b/src/Core/Models/Data/GroupEvent.cs new file mode 100644 index 0000000000..14e478abd7 --- /dev/null +++ b/src/Core/Models/Data/GroupEvent.cs @@ -0,0 +1,23 @@ +using System; +using Bit.Core.Enums; +using Bit.Core.Models.Table; +using Bit.Core.Utilities; + +namespace Bit.Core.Models.Data +{ + public class GroupEvent : EventTableEntity + { + public GroupEvent(Group group, Guid actingUserId, EventType type) + { + OrganizationId = group.OrganizationId; + GroupId = group.Id; + Type = (int)type; + ActingUserId = actingUserId; + + Timestamp = DateTime.UtcNow; + PartitionKey = $"OrganizationId={OrganizationId}"; + RowKey = string.Format("Date={0}__ActingUserId={1}__Type={2}", + CoreHelpers.DateTimeToTableStorageKey(Timestamp.DateTime), ActingUserId, Type); + } + } +} diff --git a/src/Core/Models/Data/OrganizationEvent.cs b/src/Core/Models/Data/OrganizationEvent.cs index b62fd5affb..886a8a5f51 100644 --- a/src/Core/Models/Data/OrganizationEvent.cs +++ b/src/Core/Models/Data/OrganizationEvent.cs @@ -1,32 +1,22 @@ using System; using Bit.Core.Enums; +using Bit.Core.Models.Table; using Bit.Core.Utilities; namespace Bit.Core.Models.Data { public class OrganizationEvent : EventTableEntity { - public OrganizationEvent(Guid organizationId, EventType type) + public OrganizationEvent(Organization organization, Guid actingUserId, EventType type) { - OrganizationId = organizationId; + OrganizationId = organization.Id; Type = (int)type; + ActingUserId = actingUserId; Timestamp = DateTime.UtcNow; PartitionKey = $"OrganizationId={OrganizationId}"; - RowKey = string.Format("Date={0}__Type={1}", - CoreHelpers.DateTimeToTableStorageKey(Timestamp.DateTime), Type); - } - - public OrganizationEvent(Guid organizationId, Guid userId, EventType type) - { - OrganizationId = organizationId; - UserId = userId; - Type = (int)type; - - Timestamp = DateTime.UtcNow; - PartitionKey = $"OrganizationId={OrganizationId}"; - RowKey = string.Format("Date={0}__UserId={1}__Type={2}", - CoreHelpers.DateTimeToTableStorageKey(Timestamp.DateTime), UserId, Type); + RowKey = string.Format("Date={0}__ActingUserId={1}__Type={2}", + CoreHelpers.DateTimeToTableStorageKey(Timestamp.DateTime), ActingUserId, Type); } } } diff --git a/src/Core/Models/Data/OrganizationUserEvent.cs b/src/Core/Models/Data/OrganizationUserEvent.cs new file mode 100644 index 0000000000..0026126ebe --- /dev/null +++ b/src/Core/Models/Data/OrganizationUserEvent.cs @@ -0,0 +1,24 @@ +using System; +using Bit.Core.Enums; +using Bit.Core.Models.Table; +using Bit.Core.Utilities; + +namespace Bit.Core.Models.Data +{ + public class OrganizationUserEvent : EventTableEntity + { + public OrganizationUserEvent(OrganizationUser organizationUser, Guid actingUserId, EventType type) + { + OrganizationId = organizationUser.OrganizationId; + UserId = organizationUser.UserId; + OrganizationUserId = organizationUser.Id; + Type = (int)type; + ActingUserId = actingUserId; + + Timestamp = DateTime.UtcNow; + PartitionKey = $"OrganizationId={OrganizationId}"; + RowKey = string.Format("Date={0}__ActingUserId={1}__Type={2}", + CoreHelpers.DateTimeToTableStorageKey(Timestamp.DateTime), ActingUserId, Type); + } + } +} diff --git a/src/Core/Services/ICollectionService.cs b/src/Core/Services/ICollectionService.cs index 8c5363e842..eb72c8fbec 100644 --- a/src/Core/Services/ICollectionService.cs +++ b/src/Core/Services/ICollectionService.cs @@ -1,7 +1,6 @@ using System.Threading.Tasks; using Bit.Core.Models.Table; using System.Collections.Generic; -using System; using Bit.Core.Models.Data; namespace Bit.Core.Services @@ -9,5 +8,6 @@ namespace Bit.Core.Services public interface ICollectionService { Task SaveAsync(Collection collection, IEnumerable<SelectionReadOnly> groups = null); + Task DeleteAsync(Collection collection); } } diff --git a/src/Core/Services/IEventService.cs b/src/Core/Services/IEventService.cs index abc400bf47..61e72be796 100644 --- a/src/Core/Services/IEventService.cs +++ b/src/Core/Services/IEventService.cs @@ -9,5 +9,9 @@ namespace Bit.Core.Services { Task LogUserEventAsync(Guid userId, EventType type); Task LogCipherEventAsync(Cipher cipher, EventType type); + Task LogCollectionEventAsync(Collection collection, EventType type); + Task LogGroupEventAsync(Group group, EventType type); + Task LogOrganizationUserEventAsync(OrganizationUser organizationUser, EventType type); + Task LogOrganizationEventAsync(Organization organization, EventType type); } } diff --git a/src/Core/Services/IGroupService.cs b/src/Core/Services/IGroupService.cs index 602ebc44f5..9be9b43a9b 100644 --- a/src/Core/Services/IGroupService.cs +++ b/src/Core/Services/IGroupService.cs @@ -8,5 +8,6 @@ namespace Bit.Core.Services public interface IGroupService { Task SaveAsync(Group group, IEnumerable<SelectionReadOnly> collections = null); + Task DeleteAsync(Group group); } } diff --git a/src/Core/Services/IOrganizationService.cs b/src/Core/Services/IOrganizationService.cs index fcd6c51ca5..9a9d795ee2 100644 --- a/src/Core/Services/IOrganizationService.cs +++ b/src/Core/Services/IOrganizationService.cs @@ -36,6 +36,7 @@ namespace Bit.Core.Services Task SaveUserAsync(OrganizationUser user, Guid savingUserId, IEnumerable<SelectionReadOnly> collections); Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid deletingUserId); Task DeleteUserAsync(Guid organizationId, Guid userId); + Task UpdateUserGroupsAsync(OrganizationUser organizationUser, IEnumerable<Guid> groupIds); Task<OrganizationLicense> GenerateLicenseAsync(Guid organizationId, Guid installationId); Task<OrganizationLicense> GenerateLicenseAsync(Organization organization, Guid installationId); Task ImportAsync(Guid organizationId, Guid importingUserId, IEnumerable<ImportedGroup> groups, diff --git a/src/Core/Services/Implementations/CollectionService.cs b/src/Core/Services/Implementations/CollectionService.cs index ff5016d0ed..aa4898757a 100644 --- a/src/Core/Services/Implementations/CollectionService.cs +++ b/src/Core/Services/Implementations/CollectionService.cs @@ -10,6 +10,7 @@ namespace Bit.Core.Services { public class CollectionService : ICollectionService { + private readonly IEventService _eventService; private readonly IOrganizationRepository _organizationRepository; private readonly IOrganizationUserRepository _organizationUserRepository; private readonly ICollectionRepository _collectionRepository; @@ -17,12 +18,14 @@ namespace Bit.Core.Services private readonly IMailService _mailService; public CollectionService( + IEventService eventService, IOrganizationRepository organizationRepository, IOrganizationUserRepository organizationUserRepository, ICollectionRepository collectionRepository, IUserRepository userRepository, IMailService mailService) { + _eventService = eventService; _organizationRepository = organizationRepository; _organizationUserRepository = organizationUserRepository; _collectionRepository = collectionRepository; @@ -58,6 +61,8 @@ namespace Bit.Core.Services { await _collectionRepository.CreateAsync(collection, groups); } + + await _eventService.LogCollectionEventAsync(collection, Enums.EventType.Collection_Created); } else { @@ -69,7 +74,15 @@ namespace Bit.Core.Services { await _collectionRepository.ReplaceAsync(collection, groups ?? new List<SelectionReadOnly>()); } + + await _eventService.LogCollectionEventAsync(collection, Enums.EventType.Collection_Updated); } } + + public async Task DeleteAsync(Collection collection) + { + await _collectionRepository.DeleteAsync(collection); + await _eventService.LogCollectionEventAsync(collection, Enums.EventType.Collection_Deleted); + } } } diff --git a/src/Core/Services/Implementations/EventService.cs b/src/Core/Services/Implementations/EventService.cs index 03e4959e56..80e91475c9 100644 --- a/src/Core/Services/Implementations/EventService.cs +++ b/src/Core/Services/Implementations/EventService.cs @@ -63,7 +63,31 @@ namespace Bit.Core.Services return; } - var e = new CipherEvent(cipher, type, _currentContext?.UserId); + var e = new CipherEvent(cipher, _currentContext?.UserId, type); + await _eventRepository.CreateAsync(e); + } + + public async Task LogCollectionEventAsync(Collection collection, EventType type) + { + var e = new CollectionEvent(collection, _currentContext.UserId.Value, type); + await _eventRepository.CreateAsync(e); + } + + public async Task LogGroupEventAsync(Group group, EventType type) + { + var e = new GroupEvent(group, _currentContext.UserId.Value, type); + await _eventRepository.CreateAsync(e); + } + + public async Task LogOrganizationUserEventAsync(OrganizationUser organizationUser, EventType type) + { + var e = new OrganizationUserEvent(organizationUser, _currentContext.UserId.Value, type); + await _eventRepository.CreateAsync(e); + } + + public async Task LogOrganizationEventAsync(Organization organization, EventType type) + { + var e = new OrganizationEvent(organization, _currentContext.UserId.Value, type); await _eventRepository.CreateAsync(e); } } diff --git a/src/Core/Services/Implementations/GroupService.cs b/src/Core/Services/Implementations/GroupService.cs index 0790677449..de8b73cad8 100644 --- a/src/Core/Services/Implementations/GroupService.cs +++ b/src/Core/Services/Implementations/GroupService.cs @@ -10,13 +10,16 @@ namespace Bit.Core.Services { public class GroupService : IGroupService { + private readonly IEventService _eventService; private readonly IOrganizationRepository _organizationRepository; private readonly IGroupRepository _groupRepository; public GroupService( + IEventService eventService, IOrganizationRepository organizationRepository, IGroupRepository groupRepository) { + _eventService = eventService; _organizationRepository = organizationRepository; _groupRepository = groupRepository; } @@ -46,12 +49,21 @@ namespace Bit.Core.Services { await _groupRepository.CreateAsync(group, collections); } + + await _eventService.LogGroupEventAsync(group, Enums.EventType.Group_Created); } else { group.RevisionDate = DateTime.UtcNow; await _groupRepository.ReplaceAsync(group, collections ?? new List<SelectionReadOnly>()); + await _eventService.LogGroupEventAsync(group, Enums.EventType.Group_Updated); } } + + public async Task DeleteAsync(Group group) + { + await _groupRepository.DeleteAsync(group); + await _eventService.LogGroupEventAsync(group, Enums.EventType.Group_Deleted); + } } } diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index b595a0521f..488a2489df 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -29,6 +29,7 @@ namespace Bit.Core.Services private readonly IPushRegistrationService _pushRegistrationService; private readonly IDeviceRepository _deviceRepository; private readonly ILicensingService _licensingService; + private readonly IEventService _eventService; private readonly IInstallationRepository _installationRepository; private readonly StripePaymentService _stripePaymentService; private readonly GlobalSettings _globalSettings; @@ -45,6 +46,7 @@ namespace Bit.Core.Services IPushRegistrationService pushRegistrationService, IDeviceRepository deviceRepository, ILicensingService licensingService, + IEventService eventService, IInstallationRepository installationRepository, GlobalSettings globalSettings) { @@ -59,6 +61,7 @@ namespace Bit.Core.Services _pushRegistrationService = pushRegistrationService; _deviceRepository = deviceRepository; _licensingService = licensingService; + _eventService = eventService; _installationRepository = installationRepository; _stripePaymentService = new StripePaymentService(); _globalSettings = globalSettings; @@ -803,6 +806,7 @@ namespace Bit.Core.Services } await _organizationRepository.ReplaceAsync(organization); + await _eventService.LogOrganizationEventAsync(organization, EventType.Organization_Updated); if(updateBilling && !string.IsNullOrWhiteSpace(organization.GatewayCustomerId)) { @@ -1008,6 +1012,7 @@ namespace Bit.Core.Services orgUser.Key = key; orgUser.Email = null; await _organizationUserRepository.ReplaceAsync(orgUser); + await _eventService.LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_Confirmed); var user = await _userRepository.GetByIdAsync(orgUser.UserId.Value); await _mailService.SendOrganizationConfirmedEmailAsync(org.Name, user.Email); @@ -1057,6 +1062,7 @@ namespace Bit.Core.Services collections = new List<SelectionReadOnly>(); } await _organizationUserRepository.ReplaceAsync(user, collections); + await _eventService.LogOrganizationUserEventAsync(user, EventType.OrganizationUser_Updated); } public async Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid deletingUserId) @@ -1088,6 +1094,7 @@ namespace Bit.Core.Services } await _organizationUserRepository.DeleteAsync(orgUser); + await _eventService.LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_Removed); if(orgUser.UserId.HasValue) { @@ -1112,6 +1119,7 @@ namespace Bit.Core.Services } await _organizationUserRepository.DeleteAsync(orgUser); + await _eventService.LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_Removed); if(orgUser.UserId.HasValue) { @@ -1121,6 +1129,12 @@ namespace Bit.Core.Services } } + public async Task UpdateUserGroupsAsync(OrganizationUser organizationUser, IEnumerable<Guid> groupIds) + { + await _organizationUserRepository.UpdateGroupsAsync(organizationUser.Id, groupIds); + await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_UpdatedGroups); + } + public async Task<OrganizationLicense> GenerateLicenseAsync(Guid organizationId, Guid installationId) { var organization = await _organizationRepository.GetByIdAsync(organizationId); diff --git a/src/Core/Services/NoopImplementations/NoopEventService.cs b/src/Core/Services/NoopImplementations/NoopEventService.cs index d10933ee62..2afd4c5837 100644 --- a/src/Core/Services/NoopImplementations/NoopEventService.cs +++ b/src/Core/Services/NoopImplementations/NoopEventService.cs @@ -12,6 +12,26 @@ namespace Bit.Core.Services return Task.FromResult(0); } + public Task LogCollectionEventAsync(Collection collection, EventType type) + { + return Task.FromResult(0); + } + + public Task LogGroupEventAsync(Group group, EventType type) + { + return Task.FromResult(0); + } + + public Task LogOrganizationEventAsync(Organization organization, EventType type) + { + return Task.FromResult(0); + } + + public Task LogOrganizationUserEventAsync(OrganizationUser organizationUser, EventType type) + { + return Task.FromResult(0); + } + public Task LogUserEventAsync(Guid userId, EventType type) { return Task.FromResult(0); diff --git a/src/Core/Utilities/ServiceCollectionExtensions.cs b/src/Core/Utilities/ServiceCollectionExtensions.cs index 0875267b3c..6aff835a7b 100644 --- a/src/Core/Utilities/ServiceCollectionExtensions.cs +++ b/src/Core/Utilities/ServiceCollectionExtensions.cs @@ -57,9 +57,9 @@ namespace Bit.Core.Utilities services.AddScoped<ICipherService, CipherService>(); services.AddScoped<IUserService, UserService>(); services.AddSingleton<IDeviceService, DeviceService>(); - services.AddSingleton<IOrganizationService, OrganizationService>(); - services.AddSingleton<ICollectionService, CollectionService>(); - services.AddSingleton<IGroupService, GroupService>(); + services.AddScoped<IOrganizationService, OrganizationService>(); + services.AddScoped<ICollectionService, CollectionService>(); + services.AddScoped<IGroupService, GroupService>(); services.AddScoped<Services.IEventService, EventService>(); }