diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index 930a47bd5a..7d07e0bd62 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -1,4 +1,4 @@ - + netcoreapp2.0;net47 diff --git a/src/Core/Models/Data/CipherEvent.cs b/src/Core/Models/Data/CipherEvent.cs deleted file mode 100644 index 6001ed801f..0000000000 --- a/src/Core/Models/Data/CipherEvent.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using Bit.Core.Enums; -using Bit.Core.Models.Table; -using Bit.Core.Utilities; - -namespace Bit.Core.Models.Data -{ - public class CipherEvent : EventTableEntity - { - public CipherEvent(Cipher cipher, Guid? actingUserId, EventType type) - { - OrganizationId = cipher.OrganizationId; - UserId = cipher.UserId; - CipherId = cipher.Id; - Type = (int)type; - ActingUserId = actingUserId; - Date = DateTime.UtcNow; - - if(OrganizationId.HasValue) - { - UserId = null; - PartitionKey = $"OrganizationId={OrganizationId}"; - RowKey = string.Format("Date={0}__CipherId={1}__ActingUserId={2}__Type={3}", - CoreHelpers.DateTimeToTableStorageKey(Date), CipherId, ActingUserId, Type); - } - else - { - PartitionKey = $"UserId={UserId}"; - RowKey = string.Format("Date={0}__CipherId={1}__Type={2}", - CoreHelpers.DateTimeToTableStorageKey(Date), CipherId, Type); - } - } - } -} diff --git a/src/Core/Models/Data/CollectionEvent.cs b/src/Core/Models/Data/CollectionEvent.cs deleted file mode 100644 index a0a9751bc2..0000000000 --- a/src/Core/Models/Data/CollectionEvent.cs +++ /dev/null @@ -1,23 +0,0 @@ -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; - Date = DateTime.UtcNow; - - PartitionKey = $"OrganizationId={OrganizationId}"; - RowKey = string.Format("Date={0}__ActingUserId={1}__Type={2}", - CoreHelpers.DateTimeToTableStorageKey(Date), ActingUserId, Type); - } - } -} diff --git a/src/Core/Models/Data/Event.cs b/src/Core/Models/Data/Event.cs new file mode 100644 index 0000000000..f4723a8652 --- /dev/null +++ b/src/Core/Models/Data/Event.cs @@ -0,0 +1,18 @@ +using System; +using Bit.Core.Enums; + +namespace Bit.Core.Models.Data +{ + public class Event : IEvent + { + public DateTime Date { get; set; } + public EventType Type { get; set; } + public Guid? UserId { get; set; } + public Guid? OrganizationId { get; set; } + public Guid? CipherId { 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/EventTableEntity.cs b/src/Core/Models/Data/EventTableEntity.cs index d57d59d8bd..d59d580115 100644 --- a/src/Core/Models/Data/EventTableEntity.cs +++ b/src/Core/Models/Data/EventTableEntity.cs @@ -1,20 +1,125 @@ using System; using System.Collections.Generic; +using Bit.Core.Enums; +using Bit.Core.Utilities; +using Microsoft.WindowsAzure.Storage; using Microsoft.WindowsAzure.Storage.Table; namespace Bit.Core.Models.Data { - public class EventTableEntity : TableEntity + public class EventTableEntity : TableEntity, IEvent { + public EventTableEntity() { } + + public EventTableEntity(IEvent e) + { + Date = e.Date; + Type = e.Type; + UserId = e.UserId; + OrganizationId = e.OrganizationId; + CipherId = e.CipherId; + CollectionId = e.CollectionId; + GroupId = e.GroupId; + OrganizationUserId = e.OrganizationUserId; + ActingUserId = e.ActingUserId; + + switch(e.Type) + { + case EventType.User_LoggedIn: + case EventType.User_ChangedPassword: + case EventType.User_Enabled2fa: + case EventType.User_Disabled2fa: + case EventType.User_Recovered2fa: + case EventType.User_FailedLogIn: + case EventType.User_FailedLogIn2fa: + if(e.OrganizationId.HasValue) + { + PartitionKey = $"OrganizationId={OrganizationId}"; + RowKey = string.Format("Date={0}__UserId={1}__Type={2}", + CoreHelpers.DateTimeToTableStorageKey(Date), UserId, Type); + } + else + { + PartitionKey = $"UserId={UserId}"; + RowKey = string.Format("Date={0}__Type={1}", + CoreHelpers.DateTimeToTableStorageKey(Date), Type); + } + break; + case EventType.Cipher_Created: + case EventType.Cipher_Updated: + case EventType.Cipher_Deleted: + case EventType.Cipher_AttachmentCreated: + case EventType.Cipher_AttachmentDeleted: + case EventType.Cipher_Shared: + case EventType.Cipher_UpdatedCollections: + if(OrganizationId.HasValue) + { + PartitionKey = $"OrganizationId={OrganizationId}"; + RowKey = string.Format("Date={0}__CipherId={1}__ActingUserId={2}__Type={3}", + CoreHelpers.DateTimeToTableStorageKey(Date), CipherId, ActingUserId, Type); + } + else + { + PartitionKey = $"UserId={UserId}"; + RowKey = string.Format("Date={0}__CipherId={1}__Type={2}", + CoreHelpers.DateTimeToTableStorageKey(Date), CipherId, Type); + } + break; + case EventType.Collection_Created: + case EventType.Collection_Updated: + case EventType.Collection_Deleted: + PartitionKey = $"OrganizationId={OrganizationId}"; + RowKey = string.Format("Date={0}__ActingUserId={1}__Type={2}", + CoreHelpers.DateTimeToTableStorageKey(Date), ActingUserId, Type); + break; + case EventType.Group_Created: + case EventType.Group_Updated: + case EventType.Group_Deleted: + PartitionKey = $"OrganizationId={OrganizationId}"; + RowKey = string.Format("Date={0}__ActingUserId={1}__Type={2}", + CoreHelpers.DateTimeToTableStorageKey(Date), ActingUserId, Type); + break; + case EventType.OrganizationUser_Invited: + case EventType.OrganizationUser_Confirmed: + case EventType.OrganizationUser_Updated: + case EventType.OrganizationUser_Removed: + case EventType.OrganizationUser_UpdatedGroups: + PartitionKey = $"OrganizationId={OrganizationId}"; + RowKey = string.Format("Date={0}__ActingUserId={1}__Type={2}", + CoreHelpers.DateTimeToTableStorageKey(Date), ActingUserId, Type); + break; + case EventType.Organization_Updated: + PartitionKey = $"OrganizationId={OrganizationId}"; + RowKey = string.Format("Date={0}__ActingUserId={1}__Type={2}", + CoreHelpers.DateTimeToTableStorageKey(Date), ActingUserId, Type); + break; + default: + break; + } + } + public DateTime Date { get; set; } - public int Type { get; set; } + public EventType Type { get; set; } public Guid? UserId { get; set; } public Guid? OrganizationId { get; set; } public Guid? CipherId { get; set; } - public ICollection CipherIds { get; set; } public Guid? CollectionId { get; set; } public Guid? GroupId { get; set; } public Guid? OrganizationUserId { get; set; } public Guid? ActingUserId { get; set; } + + public override IDictionary WriteEntity(OperationContext operationContext) + { + var result = base.WriteEntity(operationContext); + if(result.ContainsKey(nameof(Type))) + { + result[nameof(Type)] = new EntityProperty((int)Type); + } + else + { + result.Add(nameof(Type), new EntityProperty((int)Type)); + } + return result; + } } } diff --git a/src/Core/Models/Data/GroupEvent.cs b/src/Core/Models/Data/GroupEvent.cs deleted file mode 100644 index c07d692ae9..0000000000 --- a/src/Core/Models/Data/GroupEvent.cs +++ /dev/null @@ -1,23 +0,0 @@ -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; - Date = DateTime.UtcNow; - - PartitionKey = $"OrganizationId={OrganizationId}"; - RowKey = string.Format("Date={0}__ActingUserId={1}__Type={2}", - CoreHelpers.DateTimeToTableStorageKey(Date), ActingUserId, Type); - } - } -} diff --git a/src/Core/Models/Data/IEvent.cs b/src/Core/Models/Data/IEvent.cs new file mode 100644 index 0000000000..61b239e0e3 --- /dev/null +++ b/src/Core/Models/Data/IEvent.cs @@ -0,0 +1,18 @@ +using System; +using Bit.Core.Enums; + +namespace Bit.Core.Models.Data +{ + public interface IEvent + { + Guid? ActingUserId { get; set; } + Guid? CipherId { get; set; } + Guid? CollectionId { get; set; } + DateTime Date { get; set; } + Guid? GroupId { get; set; } + Guid? OrganizationId { get; set; } + Guid? OrganizationUserId { get; set; } + EventType Type { get; set; } + Guid? UserId { get; set; } + } +} diff --git a/src/Core/Models/Data/OrganizationEvent.cs b/src/Core/Models/Data/OrganizationEvent.cs deleted file mode 100644 index 89fd98e1c3..0000000000 --- a/src/Core/Models/Data/OrganizationEvent.cs +++ /dev/null @@ -1,22 +0,0 @@ -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(Organization organization, Guid actingUserId, EventType type) - { - OrganizationId = organization.Id; - Type = (int)type; - ActingUserId = actingUserId; - Date = DateTime.UtcNow; - - PartitionKey = $"OrganizationId={OrganizationId}"; - RowKey = string.Format("Date={0}__ActingUserId={1}__Type={2}", - CoreHelpers.DateTimeToTableStorageKey(Date), ActingUserId, Type); - } - } -} diff --git a/src/Core/Models/Data/OrganizationUserEvent.cs b/src/Core/Models/Data/OrganizationUserEvent.cs deleted file mode 100644 index c83b391942..0000000000 --- a/src/Core/Models/Data/OrganizationUserEvent.cs +++ /dev/null @@ -1,24 +0,0 @@ -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; - Date = DateTime.UtcNow; - - PartitionKey = $"OrganizationId={OrganizationId}"; - RowKey = string.Format("Date={0}__ActingUserId={1}__Type={2}", - CoreHelpers.DateTimeToTableStorageKey(Date), ActingUserId, Type); - } - } -} diff --git a/src/Core/Models/Data/UserEvent.cs b/src/Core/Models/Data/UserEvent.cs deleted file mode 100644 index 538f94f2ca..0000000000 --- a/src/Core/Models/Data/UserEvent.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using Bit.Core.Enums; -using Bit.Core.Utilities; - -namespace Bit.Core.Models.Data -{ - public class UserEvent : EventTableEntity - { - public UserEvent(Guid userId, EventType type) - { - UserId = userId; - Type = (int)type; - Date = DateTime.UtcNow; - - PartitionKey = $"UserId={UserId}"; - RowKey = string.Format("Date={0}__Type={1}", - CoreHelpers.DateTimeToTableStorageKey(Date), Type); - } - - public UserEvent(Guid userId, Guid organizationId, EventType type) - { - OrganizationId = organizationId; - UserId = userId; - Type = (int)type; - Date = DateTime.UtcNow; - - PartitionKey = $"OrganizationId={OrganizationId}"; - RowKey = string.Format("Date={0}__UserId={1}__Type={2}", - CoreHelpers.DateTimeToTableStorageKey(Date), UserId, Type); - } - } -} diff --git a/src/Core/Repositories/IEventRepository.cs b/src/Core/Repositories/IEventRepository.cs index f78c185675..dce8a6315f 100644 --- a/src/Core/Repositories/IEventRepository.cs +++ b/src/Core/Repositories/IEventRepository.cs @@ -7,8 +7,8 @@ namespace Bit.Core.Repositories { public interface IEventRepository { - Task> GetManyByUserAsync(Guid userId, DateTime startDate, DateTime endDate); - Task CreateAsync(EventTableEntity entity); - Task CreateManyAsync(IList entities); + Task> GetManyByUserAsync(Guid userId, DateTime startDate, DateTime endDate); + Task CreateAsync(IEvent entity); + Task CreateManyAsync(IList entities); } } diff --git a/src/Core/Repositories/TableStorage/EventRepository.cs b/src/Core/Repositories/TableStorage/EventRepository.cs index af3e57248c..c49fac5aac 100644 --- a/src/Core/Repositories/TableStorage/EventRepository.cs +++ b/src/Core/Repositories/TableStorage/EventRepository.cs @@ -24,7 +24,7 @@ namespace Bit.Core.Repositories.TableStorage protected CloudTable Table { get; set; } - public async Task> GetManyByUserAsync(Guid userId, + public async Task> GetManyByUserAsync(Guid userId, DateTime startDate, DateTime endDate) { var start = CoreHelpers.DateTimeToTableStorageKey(startDate); @@ -50,34 +50,40 @@ namespace Bit.Core.Repositories.TableStorage results.AddRange(queryResults.Results); } while(continuationToken != null); - return results; + return results.Select(r => r as IEvent).ToList(); } - public async Task CreateAsync(EventTableEntity entity) + public async Task CreateAsync(IEvent e) { - await Table.ExecuteAsync(TableOperation.Insert(entity)); + if(!(e is EventTableEntity entity)) + { + throw new ArgumentException(nameof(e)); + } + + await CreateEntityAsync(entity); } - public async Task CreateManyAsync(IList entities) + public async Task CreateManyAsync(IList e) { - if(!entities?.Any() ?? true) + if(!e?.Any() ?? true) { return; } - if(entities.Count == 1) + if(e.Count == 1) { - await CreateAsync(entities.First()); + await CreateAsync(e.First()); return; } - var entityGroups = entities.GroupBy(e => e.PartitionKey); + var entities = e.Where(ev => ev is EventTableEntity).Select(ev => ev as EventTableEntity); + var entityGroups = entities.GroupBy(ent => ent.PartitionKey); foreach(var group in entityGroups) { var groupEntities = group.ToList(); if(groupEntities.Count == 1) { - await CreateAsync(groupEntities.First()); + await CreateEntityAsync(groupEntities.First()); continue; } @@ -101,5 +107,10 @@ namespace Bit.Core.Repositories.TableStorage } } } + + public async Task CreateEntityAsync(ITableEntity entity) + { + await Table.ExecuteAsync(TableOperation.Insert(entity)); + } } } diff --git a/src/Core/Services/IEventWriteService.cs b/src/Core/Services/IEventWriteService.cs index 74552bd6fa..fda65d62de 100644 --- a/src/Core/Services/IEventWriteService.cs +++ b/src/Core/Services/IEventWriteService.cs @@ -6,7 +6,7 @@ namespace Bit.Core.Services { public interface IEventWriteService { - Task CreateAsync(EventTableEntity entity); - Task CreateManyAsync(IList entities); + Task CreateAsync(IEvent e); + Task CreateManyAsync(IList e); } } diff --git a/src/Core/Services/Implementations/AzureQueueEventWriteService.cs b/src/Core/Services/Implementations/AzureQueueEventWriteService.cs index 1eacf11061..3c6aca07b5 100644 --- a/src/Core/Services/Implementations/AzureQueueEventWriteService.cs +++ b/src/Core/Services/Implementations/AzureQueueEventWriteService.cs @@ -29,16 +29,16 @@ namespace Bit.Core.Services _globalSettings = globalSettings; } - public async Task CreateAsync(EventTableEntity entity) + public async Task CreateAsync(IEvent e) { - var json = JsonConvert.SerializeObject(entity, _jsonSettings); + var json = JsonConvert.SerializeObject(e, _jsonSettings); var message = new CloudQueueMessage(json); await _queue.AddMessageAsync(message); } - public async Task CreateManyAsync(IList entities) + public async Task CreateManyAsync(IList e) { - var json = JsonConvert.SerializeObject(entities, _jsonSettings); + var json = JsonConvert.SerializeObject(e, _jsonSettings); var message = new CloudQueueMessage(json); await _queue.AddMessageAsync(message); } diff --git a/src/Core/Services/Implementations/EventService.cs b/src/Core/Services/Implementations/EventService.cs index fde9911b1e..4e86f8d04e 100644 --- a/src/Core/Services/Implementations/EventService.cs +++ b/src/Core/Services/Implementations/EventService.cs @@ -30,18 +30,39 @@ namespace Bit.Core.Services public async Task LogUserEventAsync(Guid userId, EventType type) { - var events = new List { new UserEvent(userId, type) }; + var now = DateTime.UtcNow; + var events = new List + { + new Event + { + UserId = userId, + Type = type, + Date = now + } + }; - IEnumerable orgEvents; + IEnumerable orgEvents; if(_currentContext.UserId.HasValue) { - orgEvents = _currentContext.Organizations.Select(o => new UserEvent(userId, o.Id, type)); + orgEvents = _currentContext.Organizations.Select(o => new Event + { + OrganizationId = o.Id, + UserId = userId, + Type = type, + Date = DateTime.UtcNow + }); } else { var orgs = await _organizationUserRepository.GetManyByUserAsync(userId); orgEvents = orgs.Where(o => o.Status == OrganizationUserStatusType.Confirmed) - .Select(o => new UserEvent(userId, o.Id, type)); + .Select(o => new Event + { + OrganizationId = o.Id, + UserId = userId, + Type = type, + Date = DateTime.UtcNow + }); } if(orgEvents.Any()) @@ -62,31 +83,67 @@ namespace Bit.Core.Services return; } - var e = new CipherEvent(cipher, _currentContext?.UserId, type); + var e = new Event + { + OrganizationId = cipher.OrganizationId, + UserId = cipher.OrganizationId.HasValue ? null : cipher.UserId, + CipherId = cipher.Id, + Type = type, + ActingUserId = _currentContext?.UserId, + Date = DateTime.UtcNow + }; await _eventWriteService.CreateAsync(e); } public async Task LogCollectionEventAsync(Collection collection, EventType type) { - var e = new CollectionEvent(collection, _currentContext.UserId.Value, type); + var e = new Event + { + OrganizationId = collection.OrganizationId, + CollectionId = collection.Id, + Type = type, + ActingUserId = _currentContext.UserId.Value, + Date = DateTime.UtcNow + }; await _eventWriteService.CreateAsync(e); } public async Task LogGroupEventAsync(Group group, EventType type) { - var e = new GroupEvent(group, _currentContext.UserId.Value, type); + var e = new Event + { + OrganizationId = group.OrganizationId, + GroupId = group.Id, + Type = type, + ActingUserId = _currentContext.UserId.Value, + Date = DateTime.UtcNow + }; await _eventWriteService.CreateAsync(e); } public async Task LogOrganizationUserEventAsync(OrganizationUser organizationUser, EventType type) { - var e = new OrganizationUserEvent(organizationUser, _currentContext.UserId.Value, type); + var e = new Event + { + OrganizationId = organizationUser.OrganizationId, + UserId = organizationUser.UserId, + OrganizationUserId = organizationUser.Id, + Type = type, + ActingUserId = _currentContext.UserId.Value, + Date = DateTime.UtcNow + }; await _eventWriteService.CreateAsync(e); } public async Task LogOrganizationEventAsync(Organization organization, EventType type) { - var e = new OrganizationEvent(organization, _currentContext.UserId.Value, type); + var e = new Event + { + OrganizationId = organization.Id, + Type = type, + ActingUserId = _currentContext.UserId.Value, + Date = DateTime.UtcNow + }; await _eventWriteService.CreateAsync(e); } } diff --git a/src/Core/Services/Implementations/RepositoryEventWriteService.cs b/src/Core/Services/Implementations/RepositoryEventWriteService.cs index 9f717ad56b..e9d0a46219 100644 --- a/src/Core/Services/Implementations/RepositoryEventWriteService.cs +++ b/src/Core/Services/Implementations/RepositoryEventWriteService.cs @@ -15,14 +15,14 @@ namespace Bit.Core.Services _eventRepository = eventRepository; } - public async Task CreateAsync(EventTableEntity entity) + public async Task CreateAsync(IEvent e) { - await _eventRepository.CreateAsync(entity); + await _eventRepository.CreateAsync(e); } - public async Task CreateManyAsync(IList entities) + public async Task CreateManyAsync(IList e) { - await _eventRepository.CreateManyAsync(entities); + await _eventRepository.CreateManyAsync(e); } } } diff --git a/src/Core/Services/NoopImplementations/NoopEventWriteService.cs b/src/Core/Services/NoopImplementations/NoopEventWriteService.cs index 7ac6baa01d..782994d6b5 100644 --- a/src/Core/Services/NoopImplementations/NoopEventWriteService.cs +++ b/src/Core/Services/NoopImplementations/NoopEventWriteService.cs @@ -6,12 +6,12 @@ namespace Bit.Core.Services { public class NoopEventWriteService : IEventWriteService { - public Task CreateAsync(EventTableEntity entity) + public Task CreateAsync(IEvent e) { return Task.FromResult(0); } - public Task CreateManyAsync(IList entities) + public Task CreateManyAsync(IList e) { return Task.FromResult(0); } diff --git a/src/EventsProcessor/Functions.cs b/src/EventsProcessor/Functions.cs index b4fca1ea7b..6f03c8fd04 100644 --- a/src/EventsProcessor/Functions.cs +++ b/src/EventsProcessor/Functions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Configuration; using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Bit.Core.Models.Data; @@ -29,7 +30,7 @@ namespace Bit.EventsProcessor } public async static Task ProcessQueueMessageAsync([QueueTrigger("event")] string message, - TextWriter logger, CancellationToken token) + TextWriter logger, CancellationToken cancellationToken) { if(_eventWriteService == null || message == null || message.Length == 0) { @@ -38,16 +39,17 @@ namespace Bit.EventsProcessor try { - var jToken = JToken.Parse(message); - if(jToken is JArray) + var token = JToken.Parse(message); + if(token is JArray) { - var entities = jToken.ToObject>(); - await _eventWriteService.CreateManyAsync(entities); + var events = token.ToObject>() + .Select(e => new EventTableEntity(e) as IEvent).ToList(); + await _eventWriteService.CreateManyAsync(events); } - else if(jToken is JObject) + else if(token is JObject) { - var entity = jToken.ToObject(); - await _eventWriteService.CreateAsync(entity); + var e = token.ToObject(); + await _eventWriteService.CreateAsync(new EventTableEntity(e)); } } catch(JsonReaderException)