From a8fefb54c46217e55bf27fba923c483d1a0e9373 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Fri, 1 Dec 2017 14:06:16 -0500 Subject: [PATCH] cipher events --- .../Middleware/CurrentContextMiddleware.cs | 9 +++-- src/Core/CurrentContext.cs | 1 + src/Core/Enums/EventType.cs | 23 ++++++++++-- src/Core/Models/Data/CipherEvent.cs | 11 +++--- src/Core/Models/Data/EventTableEntity.cs | 1 + src/Core/Services/IEventService.cs | 3 +- .../Services/Implementations/CipherService.cs | 15 +++++++- .../Services/Implementations/EventService.cs | 36 ++++++++++++------- .../Services/Implementations/UserService.cs | 14 ++++---- .../NoopImplementations/NoopEventService.cs | 5 +-- .../Utilities/ServiceCollectionExtensions.cs | 4 +-- 11 files changed, 86 insertions(+), 36 deletions(-) diff --git a/src/Api/Middleware/CurrentContextMiddleware.cs b/src/Api/Middleware/CurrentContextMiddleware.cs index f6413d7af6..346c30d38a 100644 --- a/src/Api/Middleware/CurrentContextMiddleware.cs +++ b/src/Api/Middleware/CurrentContextMiddleware.cs @@ -25,12 +25,17 @@ namespace Bit.Api.Middleware .GroupBy(c => c.Type) .ToDictionary(c => c.Key, c => c.Select(v => v)); + var subject = GetClaimValue(claimsDict, "sub"); + if(Guid.TryParse(subject, out var subIdGuid)) + { + currentContext.UserId = subIdGuid; + } + var clientId = GetClaimValue(claimsDict, "client_id"); var clientSubject = GetClaimValue(claimsDict, "client_sub"); if((clientId?.StartsWith("installation.") ?? false) && clientSubject != null) { - Guid idGuid; - if(Guid.TryParse(clientSubject, out idGuid)) + if(Guid.TryParse(clientSubject, out var idGuid)) { currentContext.InstallationId = idGuid; } diff --git a/src/Core/CurrentContext.cs b/src/Core/CurrentContext.cs index 3927894e2b..3f6bd78f5e 100644 --- a/src/Core/CurrentContext.cs +++ b/src/Core/CurrentContext.cs @@ -8,6 +8,7 @@ namespace Bit.Core { public class CurrentContext { + public virtual Guid? UserId { get; set; } public virtual User User { get; set; } public virtual string DeviceIdentifier { get; set; } public virtual List Organizations { get; set; } = new List(); diff --git a/src/Core/Enums/EventType.cs b/src/Core/Enums/EventType.cs index 27cf9e5030..f027a3699a 100644 --- a/src/Core/Enums/EventType.cs +++ b/src/Core/Enums/EventType.cs @@ -10,8 +10,25 @@ User_FailedLogIn = 1005, User_FailedLogIn2fa = 1006, - Cipher_Created = 2000, - Cipher_Edited = 2001, - Cipher_Deleted = 2002 + Cipher_Created = 1100, + Cipher_Updated = 1101, + Cipher_Deleted = 1102, + Cipher_AttachmentCreated = 1103, + Cipher_AttachmentDeleted = 1104, + Cipher_Shared = 1105, + Cipher_UpdatedCollections = 1106, + + Collection_Created = 1300, + Collection_Updated = 1301, + Collection_Deleted = 1302, + + Group_Created = 1400, + Group_Updated = 1401, + Group_Deleted = 1402, + + OrganizationUser_Invited = 1500, + OrganizationUser_Confirmed = 1501, + OrganizationUser_Updated = 1502, + OrganizationUser_Removed = 1503 } } diff --git a/src/Core/Models/Data/CipherEvent.cs b/src/Core/Models/Data/CipherEvent.cs index 922799c03c..463ceb7c75 100644 --- a/src/Core/Models/Data/CipherEvent.cs +++ b/src/Core/Models/Data/CipherEvent.cs @@ -7,25 +7,28 @@ namespace Bit.Core.Models.Data { public class CipherEvent : EventTableEntity { - public CipherEvent(Cipher cipher, EventType type) + public CipherEvent(Cipher cipher, EventType type, Guid? actingUserId = null) { OrganizationId = cipher.OrganizationId; UserId = cipher.UserId; CipherId = cipher.Id; Type = (int)type; + ActingUserId = actingUserId; Timestamp = DateTime.UtcNow; if(OrganizationId.HasValue) { + UserId = null; PartitionKey = $"OrganizationId={OrganizationId}"; + RowKey = string.Format("Date={0}__CipherId={1}__ActingUserId={2}__Type={3}", + CoreHelpers.DateTimeToTableStorageKey(Timestamp.DateTime), CipherId, ActingUserId, Type); } else { PartitionKey = $"UserId={UserId}"; + RowKey = string.Format("Date={0}__CipherId={1}__Type={2}", + CoreHelpers.DateTimeToTableStorageKey(Timestamp.DateTime), CipherId, Type); } - - RowKey = string.Format("Date={0}__CipherId={1}__Type={2}", - CoreHelpers.DateTimeToTableStorageKey(Timestamp.DateTime), CipherId, Type); } } } diff --git a/src/Core/Models/Data/EventTableEntity.cs b/src/Core/Models/Data/EventTableEntity.cs index 484df17240..e0e1cf1ece 100644 --- a/src/Core/Models/Data/EventTableEntity.cs +++ b/src/Core/Models/Data/EventTableEntity.cs @@ -11,5 +11,6 @@ namespace Bit.Core.Models.Data public Guid? OrganizationId { get; set; } public Guid? CipherId { get; set; } public ICollection CipherIds { get; set; } + public Guid? ActingUserId { get; set; } } } diff --git a/src/Core/Services/IEventService.cs b/src/Core/Services/IEventService.cs index b42586c157..abc400bf47 100644 --- a/src/Core/Services/IEventService.cs +++ b/src/Core/Services/IEventService.cs @@ -1,12 +1,13 @@ using System; using System.Threading.Tasks; using Bit.Core.Enums; +using Bit.Core.Models.Table; namespace Bit.Core.Services { public interface IEventService { Task LogUserEventAsync(Guid userId, EventType type); - Task LogUserEventAsync(Guid userId, CurrentContext currentContext, EventType type); + Task LogCipherEventAsync(Cipher cipher, EventType type); } } diff --git a/src/Core/Services/Implementations/CipherService.cs b/src/Core/Services/Implementations/CipherService.cs index 4db7b46d65..95436380e9 100644 --- a/src/Core/Services/Implementations/CipherService.cs +++ b/src/Core/Services/Implementations/CipherService.cs @@ -22,6 +22,7 @@ namespace Bit.Core.Services private readonly ICollectionCipherRepository _collectionCipherRepository; private readonly IPushNotificationService _pushService; private readonly IAttachmentStorageService _attachmentStorageService; + private readonly IEventService _eventService; public CipherService( ICipherRepository cipherRepository, @@ -31,7 +32,8 @@ namespace Bit.Core.Services IOrganizationUserRepository organizationUserRepository, ICollectionCipherRepository collectionCipherRepository, IPushNotificationService pushService, - IAttachmentStorageService attachmentStorageService) + IAttachmentStorageService attachmentStorageService, + IEventService eventService) { _cipherRepository = cipherRepository; _folderRepository = folderRepository; @@ -41,6 +43,7 @@ namespace Bit.Core.Services _collectionCipherRepository = collectionCipherRepository; _pushService = pushService; _attachmentStorageService = attachmentStorageService; + _eventService = eventService; } public async Task SaveAsync(Cipher cipher, Guid savingUserId, bool orgAdmin = false) @@ -53,6 +56,7 @@ namespace Bit.Core.Services if(cipher.Id == default(Guid)) { await _cipherRepository.CreateAsync(cipher); + await _eventService.LogCipherEventAsync(cipher, Enums.EventType.Cipher_Created); // push await _pushService.PushSyncCipherCreateAsync(cipher); @@ -61,6 +65,7 @@ namespace Bit.Core.Services { cipher.RevisionDate = DateTime.UtcNow; await _cipherRepository.ReplaceAsync(cipher); + await _eventService.LogCipherEventAsync(cipher, Enums.EventType.Cipher_Updated); // push await _pushService.PushSyncCipherUpdateAsync(cipher); @@ -78,6 +83,7 @@ namespace Bit.Core.Services if(cipher.Id == default(Guid)) { await _cipherRepository.CreateAsync(cipher); + await _eventService.LogCipherEventAsync(cipher, Enums.EventType.Cipher_Created); if(cipher.OrganizationId.HasValue) { @@ -92,6 +98,7 @@ namespace Bit.Core.Services { cipher.RevisionDate = DateTime.UtcNow; await _cipherRepository.ReplaceAsync(cipher); + await _eventService.LogCipherEventAsync(cipher, Enums.EventType.Cipher_Updated); // push await _pushService.PushSyncCipherUpdateAsync(cipher); @@ -159,6 +166,7 @@ namespace Bit.Core.Services }; await _cipherRepository.UpdateAttachmentAsync(attachment); + await _eventService.LogCipherEventAsync(cipher, Enums.EventType.Cipher_AttachmentCreated); cipher.AddAttachment(attachmentId, data); } catch @@ -222,6 +230,7 @@ namespace Bit.Core.Services await _cipherRepository.DeleteAsync(cipher); await _attachmentStorageService.DeleteAttachmentsForCipherAsync(cipher.Id); + await _eventService.LogCipherEventAsync(cipher, Enums.EventType.Cipher_Deleted); // push await _pushService.PushSyncCipherDeleteAsync(cipher); @@ -249,6 +258,7 @@ namespace Bit.Core.Services await _cipherRepository.DeleteAttachmentAsync(cipher.Id, attachmentId); cipher.DeleteAttachment(attachmentId); await _attachmentStorageService.DeleteAttachmentAsync(cipher.Id, attachmentId); + await _eventService.LogCipherEventAsync(cipher, Enums.EventType.Cipher_AttachmentDeleted); // push await _pushService.PushSyncCipherUpdateAsync(cipher); @@ -344,6 +354,7 @@ namespace Bit.Core.Services } updatedCipher = true; + await _eventService.LogCipherEventAsync(cipher, Enums.EventType.Cipher_Shared); if(hasAttachments) { @@ -413,6 +424,8 @@ namespace Bit.Core.Services await _collectionCipherRepository.UpdateCollectionsAsync(cipher.Id, savingUserId, collectionIds); } + await _eventService.LogCipherEventAsync(cipher, Enums.EventType.Cipher_UpdatedCollections); + // push await _pushService.PushSyncCipherUpdateAsync(cipher); } diff --git a/src/Core/Services/Implementations/EventService.cs b/src/Core/Services/Implementations/EventService.cs index 18c6f50a34..03e4959e56 100644 --- a/src/Core/Services/Implementations/EventService.cs +++ b/src/Core/Services/Implementations/EventService.cs @@ -6,6 +6,7 @@ using Bit.Core.Models.Data; using System.Linq; using System.Collections.Generic; using Microsoft.WindowsAzure.Storage.Table; +using Bit.Core.Models.Table; namespace Bit.Core.Services { @@ -13,24 +14,37 @@ namespace Bit.Core.Services { private readonly IEventRepository _eventRepository; private readonly IOrganizationUserRepository _organizationUserRepository; + private readonly CurrentContext _currentContext; private readonly GlobalSettings _globalSettings; public EventService( IEventRepository eventRepository, IOrganizationUserRepository organizationUserRepository, + CurrentContext currentContext, GlobalSettings globalSettings) { _eventRepository = eventRepository; _organizationUserRepository = organizationUserRepository; + _currentContext = currentContext; _globalSettings = globalSettings; } public async Task LogUserEventAsync(Guid userId, EventType type) { var events = new List { new UserEvent(userId, type) }; - var orgs = await _organizationUserRepository.GetManyByUserAsync(userId); - var orgEvents = orgs.Where(o => o.Status == OrganizationUserStatusType.Confirmed) - .Select(o => new UserEvent(userId, o.Id, type)); + + IEnumerable orgEvents; + if(_currentContext.UserId.HasValue) + { + orgEvents = _currentContext.Organizations.Select(o => new UserEvent(userId, o.Id, type)); + } + else + { + var orgs = await _organizationUserRepository.GetManyByUserAsync(userId); + orgEvents = orgs.Where(o => o.Status == OrganizationUserStatusType.Confirmed) + .Select(o => new UserEvent(userId, o.Id, type)); + } + if(orgEvents.Any()) { events.AddRange(orgEvents); @@ -42,19 +56,15 @@ namespace Bit.Core.Services } } - public async Task LogUserEventAsync(Guid userId, CurrentContext currentContext, EventType type) + public async Task LogCipherEventAsync(Cipher cipher, EventType type) { - var events = new List { new UserEvent(userId, type) }; - var orgEvents = currentContext.Organizations.Select(o => new UserEvent(userId, o.Id, type)); - if(orgEvents.Any()) + if(!cipher.OrganizationId.HasValue || (!_currentContext?.UserId.HasValue ?? true)) { - events.AddRange(orgEvents); - await _eventRepository.CreateManyAsync(events); - } - else - { - await _eventRepository.CreateAsync(events.First()); + return; } + + var e = new CipherEvent(cipher, type, _currentContext?.UserId); + await _eventRepository.CreateAsync(e); } } } diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index 06e2ffce7b..b446048e4f 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -90,8 +90,7 @@ namespace Bit.Core.Services public Guid? GetProperUserId(ClaimsPrincipal principal) { - Guid userIdGuid; - if(!Guid.TryParse(GetUserId(principal), out userIdGuid)) + if(!Guid.TryParse(GetUserId(principal), out var userIdGuid)) { return null; } @@ -107,8 +106,7 @@ namespace Bit.Core.Services return _currentContext.User; } - Guid userIdGuid; - if(!Guid.TryParse(userId, out userIdGuid)) + if(!Guid.TryParse(userId, out var userIdGuid)) { return null; } @@ -425,7 +423,7 @@ namespace Bit.Core.Services user.Key = key; await _userRepository.ReplaceAsync(user); - await _eventService.LogUserEventAsync(user.Id, _currentContext, EventType.User_ChangedPassword); + await _eventService.LogUserEventAsync(user.Id, EventType.User_ChangedPassword); return IdentityResult.Success; } @@ -503,7 +501,7 @@ namespace Bit.Core.Services user.TwoFactorRecoveryCode = CoreHelpers.SecureRandomString(32, upper: false, special: false); } await SaveUserAsync(user); - await _eventService.LogUserEventAsync(user.Id, _currentContext, EventType.User_Enabled2fa); + await _eventService.LogUserEventAsync(user.Id, EventType.User_Enabled2fa); } public async Task DisableTwoFactorProviderAsync(User user, TwoFactorProviderType type) @@ -517,7 +515,7 @@ namespace Bit.Core.Services providers.Remove(type); user.SetTwoFactorProviders(providers); await SaveUserAsync(user); - await _eventService.LogUserEventAsync(user.Id, _currentContext, EventType.User_Disabled2fa); + await _eventService.LogUserEventAsync(user.Id, EventType.User_Disabled2fa); } public async Task RecoverTwoFactorAsync(string email, string masterPassword, string recoveryCode) @@ -542,7 +540,7 @@ namespace Bit.Core.Services user.TwoFactorProviders = null; user.TwoFactorRecoveryCode = CoreHelpers.SecureRandomString(32, upper: false, special: false); await SaveUserAsync(user); - await _eventService.LogUserEventAsync(user.Id, _currentContext, EventType.User_Recovered2fa); + await _eventService.LogUserEventAsync(user.Id, EventType.User_Recovered2fa); return true; } diff --git a/src/Core/Services/NoopImplementations/NoopEventService.cs b/src/Core/Services/NoopImplementations/NoopEventService.cs index 89744fb939..d10933ee62 100644 --- a/src/Core/Services/NoopImplementations/NoopEventService.cs +++ b/src/Core/Services/NoopImplementations/NoopEventService.cs @@ -1,17 +1,18 @@ using System; using System.Threading.Tasks; using Bit.Core.Enums; +using Bit.Core.Models.Table; namespace Bit.Core.Services { public class NoopEventService : IEventService { - public Task LogUserEventAsync(Guid userId, EventType type) + public Task LogCipherEventAsync(Cipher cipher, EventType type) { return Task.FromResult(0); } - public Task LogUserEventAsync(Guid userId, CurrentContext currentContext, EventType type) + 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 e9cd87faab..0875267b3c 100644 --- a/src/Core/Utilities/ServiceCollectionExtensions.cs +++ b/src/Core/Utilities/ServiceCollectionExtensions.cs @@ -54,13 +54,13 @@ namespace Bit.Core.Utilities public static void AddBaseServices(this IServiceCollection services) { - services.AddSingleton(); + services.AddScoped(); services.AddScoped(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddScoped(); } public static void AddDefaultServices(this IServiceCollection services, GlobalSettings globalSettings)