diff --git a/src/Core/CurrentContext.cs b/src/Core/CurrentContext.cs index 3f6bd78f5e..548d57ac1d 100644 --- a/src/Core/CurrentContext.cs +++ b/src/Core/CurrentContext.cs @@ -3,14 +3,21 @@ using System.Collections.Generic; using System.Linq; using Bit.Core.Models.Table; using Bit.Core.Enums; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; namespace Bit.Core { public class CurrentContext { + private string _ip; + + public virtual HttpContext HttpContext { get; set; } public virtual Guid? UserId { get; set; } public virtual User User { get; set; } public virtual string DeviceIdentifier { get; set; } + public virtual DeviceType? DeviceType { get; set; } + public virtual string IpAddress => GetRequestIp(); public virtual List Organizations { get; set; } = new List(); public virtual Guid? InstallationId { get; set; } @@ -28,6 +35,31 @@ namespace Bit.Core return Organizations.Any(o => o.Id == orgId && o.Type == OrganizationUserType.Owner); } + private string GetRequestIp() + { + if(!string.IsNullOrWhiteSpace(_ip)) + { + return _ip; + } + + if(HttpContext == null) + { + return null; + } + + if(HttpContext.Request?.Headers?.TryGetValue("X-Forwarded-For", out StringValues forwardHeader) ?? false) + { + _ip = forwardHeader.FirstOrDefault()?.Trim(); + } + + if(string.IsNullOrWhiteSpace(_ip)) + { + _ip = HttpContext.Connection?.RemoteIpAddress?.ToString(); + } + + return _ip; + } + public class CurrentContentOrganization { public Guid Id { get; set; } diff --git a/src/Core/Models/Data/EventMessage.cs b/src/Core/Models/Data/EventMessage.cs index 0a1256a0dd..3f75914b74 100644 --- a/src/Core/Models/Data/EventMessage.cs +++ b/src/Core/Models/Data/EventMessage.cs @@ -5,6 +5,14 @@ namespace Bit.Core.Models.Data { public class EventMessage : IEvent { + public EventMessage() { } + + public EventMessage(CurrentContext currentContext) + { + IpAddress = currentContext.IpAddress; + DeviceType = currentContext.DeviceType; + } + public DateTime Date { get; set; } public EventType Type { get; set; } public Guid? UserId { get; set; } @@ -14,5 +22,7 @@ namespace Bit.Core.Models.Data public Guid? GroupId { get; set; } public Guid? OrganizationUserId { get; set; } public Guid? ActingUserId { get; set; } + public DeviceType? DeviceType { get; set; } + public string IpAddress { get; set; } } } diff --git a/src/Core/Models/Data/EventTableEntity.cs b/src/Core/Models/Data/EventTableEntity.cs index 8ba9f4cb3b..dc31698cad 100644 --- a/src/Core/Models/Data/EventTableEntity.cs +++ b/src/Core/Models/Data/EventTableEntity.cs @@ -21,6 +21,8 @@ namespace Bit.Core.Models.Data CollectionId = e.CollectionId; GroupId = e.GroupId; OrganizationUserId = e.OrganizationUserId; + DeviceType = e.DeviceType; + IpAddress = e.IpAddress; ActingUserId = e.ActingUserId; } @@ -32,47 +34,58 @@ namespace Bit.Core.Models.Data public Guid? CollectionId { get; set; } public Guid? GroupId { get; set; } public Guid? OrganizationUserId { get; set; } + public DeviceType? DeviceType { get; set; } + public string IpAddress { get; set; } public Guid? ActingUserId { get; set; } public override IDictionary WriteEntity(OperationContext operationContext) { var result = base.WriteEntity(operationContext); - if(result.ContainsKey(nameof(Type))) + + var typeName = nameof(Type); + if(result.ContainsKey(typeName)) { - result[nameof(Type)] = new EntityProperty((int)Type); + result[typeName] = new EntityProperty((int)Type); } else { - result.Add(nameof(Type), new EntityProperty((int)Type)); + result.Add(typeName, new EntityProperty((int)Type)); } + + var deviceTypeName = nameof(DeviceType); + if(result.ContainsKey(deviceTypeName)) + { + result[deviceTypeName] = new EntityProperty((int?)DeviceType); + } + else + { + result.Add(deviceTypeName, new EntityProperty((int?)DeviceType)); + } + return result; } public override void ReadEntity(IDictionary properties, OperationContext operationContext) { base.ReadEntity(properties, operationContext); - if(properties.ContainsKey(nameof(Type)) && properties[nameof(Type)].Int32Value.HasValue) + + var typeName = nameof(Type); + if(properties.ContainsKey(typeName) && properties[typeName].Int32Value.HasValue) { - Type = (EventType)properties[nameof(Type)].Int32Value; + Type = (EventType)properties[typeName].Int32Value.Value; + } + + var deviceTypeName = nameof(DeviceType); + if(properties.ContainsKey(deviceTypeName) && properties[deviceTypeName].Int32Value.HasValue) + { + DeviceType = (DeviceType)properties[deviceTypeName].Int32Value.Value; } } public static List IndexEvent(IEvent e) - { - if(e.OrganizationId.HasValue) - { - return IndexOrgEvent(e); - } - else - { - return new List { IndexUserEvent(e) }; - } - } - - private static List IndexOrgEvent(IEvent e) { var uniquifier = Guid.NewGuid(); - var pKey = $"OrganizationId={e.OrganizationId}"; + var pKey = e.OrganizationId.HasValue ? $"OrganizationId={e.OrganizationId}" : $"UserId={e.UserId}"; var dateKey = CoreHelpers.DateTimeToTableStorageKey(e.Date); var entities = new List @@ -84,7 +97,7 @@ namespace Bit.Core.Models.Data } }; - if(e.ActingUserId.HasValue) + if(e.OrganizationId.HasValue && e.ActingUserId.HasValue) { entities.Add(new EventTableEntity(e) { @@ -104,15 +117,5 @@ namespace Bit.Core.Models.Data return entities; } - - private static EventTableEntity IndexUserEvent(IEvent e) - { - var uniquifier = Guid.NewGuid(); - return new EventTableEntity(e) - { - PartitionKey = $"UserId={e.UserId}", - RowKey = string.Format("Date={0}__Uniquifier={1}", CoreHelpers.DateTimeToTableStorageKey(e.Date), uniquifier) - }; - } } } diff --git a/src/Core/Models/Data/IEvent.cs b/src/Core/Models/Data/IEvent.cs index 5a975e0865..eba891dd9f 100644 --- a/src/Core/Models/Data/IEvent.cs +++ b/src/Core/Models/Data/IEvent.cs @@ -13,6 +13,8 @@ namespace Bit.Core.Models.Data Guid? GroupId { get; set; } Guid? OrganizationUserId { get; set; } Guid? ActingUserId { get; set; } + DeviceType? DeviceType { get; set; } + string IpAddress { get; set; } DateTime Date { get; set; } } } diff --git a/src/Core/Models/Table/Event.cs b/src/Core/Models/Table/Event.cs index 09584a66b0..2e5c60dd05 100644 --- a/src/Core/Models/Table/Event.cs +++ b/src/Core/Models/Table/Event.cs @@ -19,6 +19,8 @@ namespace Bit.Core.Models.Table CollectionId = e.CollectionId; GroupId = e.GroupId; OrganizationUserId = e.OrganizationUserId; + DeviceType = e.DeviceType; + IpAddress = e.IpAddress; ActingUserId = e.ActingUserId; } @@ -31,6 +33,8 @@ namespace Bit.Core.Models.Table public Guid? CollectionId { get; set; } public Guid? GroupId { get; set; } public Guid? OrganizationUserId { get; set; } + public DeviceType? DeviceType { get; set; } + public string IpAddress { get; set; } public Guid? ActingUserId { get; set; } public void SetNewId() diff --git a/src/Core/Services/Implementations/EventService.cs b/src/Core/Services/Implementations/EventService.cs index 547a3f2f65..bbb366d7e6 100644 --- a/src/Core/Services/Implementations/EventService.cs +++ b/src/Core/Services/Implementations/EventService.cs @@ -33,7 +33,7 @@ namespace Bit.Core.Services var now = DateTime.UtcNow; var events = new List { - new EventMessage + new EventMessage(_currentContext) { UserId = userId, ActingUserId = userId, @@ -45,7 +45,7 @@ namespace Bit.Core.Services IEnumerable orgEvents; if(_currentContext.UserId.HasValue) { - orgEvents = _currentContext.Organizations.Select(o => new EventMessage + orgEvents = _currentContext.Organizations.Select(o => new EventMessage(_currentContext) { OrganizationId = o.Id, UserId = userId, @@ -58,7 +58,7 @@ namespace Bit.Core.Services { var orgs = await _organizationUserRepository.GetManyByUserAsync(userId); orgEvents = orgs.Where(o => o.Status == OrganizationUserStatusType.Confirmed) - .Select(o => new EventMessage + .Select(o => new EventMessage(_currentContext) { OrganizationId = o.OrganizationId, UserId = userId, @@ -87,7 +87,7 @@ namespace Bit.Core.Services return; } - var e = new EventMessage + var e = new EventMessage(_currentContext) { OrganizationId = cipher.OrganizationId, UserId = cipher.OrganizationId.HasValue ? null : cipher.UserId, @@ -101,7 +101,7 @@ namespace Bit.Core.Services public async Task LogCollectionEventAsync(Collection collection, EventType type) { - var e = new EventMessage + var e = new EventMessage(_currentContext) { OrganizationId = collection.OrganizationId, CollectionId = collection.Id, @@ -114,7 +114,7 @@ namespace Bit.Core.Services public async Task LogGroupEventAsync(Group group, EventType type) { - var e = new EventMessage + var e = new EventMessage(_currentContext) { OrganizationId = group.OrganizationId, GroupId = group.Id, @@ -127,7 +127,7 @@ namespace Bit.Core.Services public async Task LogOrganizationUserEventAsync(OrganizationUser organizationUser, EventType type) { - var e = new EventMessage + var e = new EventMessage(_currentContext) { OrganizationId = organizationUser.OrganizationId, UserId = organizationUser.UserId, @@ -141,7 +141,7 @@ namespace Bit.Core.Services public async Task LogOrganizationEventAsync(Organization organization, EventType type) { - var e = new EventMessage + var e = new EventMessage(_currentContext) { OrganizationId = organization.Id, Type = type, diff --git a/src/Core/Utilities/CurrentContextMiddleware.cs b/src/Core/Utilities/CurrentContextMiddleware.cs index 1b12ef5b4c..4dba312582 100644 --- a/src/Core/Utilities/CurrentContextMiddleware.cs +++ b/src/Core/Utilities/CurrentContextMiddleware.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Http; +using Bit.Core.Enums; +using Microsoft.AspNetCore.Http; using System; using System.Collections.Generic; using System.Linq; @@ -18,7 +19,9 @@ namespace Bit.Core.Utilities public async Task Invoke(HttpContext httpContext, CurrentContext currentContext) { - if(httpContext.User != null) + currentContext.HttpContext = httpContext; + + if(httpContext.User != null && httpContext.User.Claims.Any()) { var claimsDict = httpContext.User.Claims .GroupBy(c => c.Type) @@ -48,7 +51,7 @@ namespace Bit.Core.Utilities new CurrentContext.CurrentContentOrganization { Id = new Guid(c.Value), - Type = Core.Enums.OrganizationUserType.Owner + Type = OrganizationUserType.Owner })); } @@ -58,7 +61,7 @@ namespace Bit.Core.Utilities new CurrentContext.CurrentContentOrganization { Id = new Guid(c.Value), - Type = Core.Enums.OrganizationUserType.Admin + Type = OrganizationUserType.Admin })); } @@ -68,7 +71,7 @@ namespace Bit.Core.Utilities new CurrentContext.CurrentContentOrganization { Id = new Guid(c.Value), - Type = Core.Enums.OrganizationUserType.User + Type = OrganizationUserType.User })); } } @@ -78,6 +81,12 @@ namespace Bit.Core.Utilities currentContext.DeviceIdentifier = httpContext.Request.Headers["Device-Identifier"]; } + if(httpContext.Request.Headers.ContainsKey("Device-Type") && + Enum.TryParse(httpContext.Request.Headers["Device-Type"].ToString(), out DeviceType dType)) + { + currentContext.DeviceType = dType; + } + await _next.Invoke(httpContext); } diff --git a/src/Identity/Startup.cs b/src/Identity/Startup.cs index e74e0df05a..be6ad0f979 100644 --- a/src/Identity/Startup.cs +++ b/src/Identity/Startup.cs @@ -98,6 +98,9 @@ namespace Bit.Identity app.UseMiddleware(); } + // Add current context + app.UseMiddleware(); + // Add IdentityServer to the request pipeline. app.UseIdentityServer(); } diff --git a/src/Sql/dbo/Stored Procedures/Event_Create.sql b/src/Sql/dbo/Stored Procedures/Event_Create.sql index 413e5416e1..1e3989f1cf 100644 --- a/src/Sql/dbo/Stored Procedures/Event_Create.sql +++ b/src/Sql/dbo/Stored Procedures/Event_Create.sql @@ -8,6 +8,8 @@ @GroupId UNIQUEIDENTIFIER, @OrganizationUserId UNIQUEIDENTIFIER, @ActingUserId UNIQUEIDENTIFIER, + @DeviceType SMALLINT, + @IpAddress VARCHAR(50), @Date DATETIME2(7) AS BEGIN @@ -24,6 +26,8 @@ BEGIN [GroupId], [OrganizationUserId], [ActingUserId], + [DeviceType], + [IpAddress], [Date] ) VALUES @@ -37,6 +41,8 @@ BEGIN @GroupId, @OrganizationUserId, @ActingUserId, + @DeviceType, + @IpAddress, @Date ) END diff --git a/src/Sql/dbo/Tables/Event.sql b/src/Sql/dbo/Tables/Event.sql index 9623d2afd9..2500793877 100644 --- a/src/Sql/dbo/Tables/Event.sql +++ b/src/Sql/dbo/Tables/Event.sql @@ -8,6 +8,8 @@ [GroupId] UNIQUEIDENTIFIER NULL, [OrganizationUserId] UNIQUEIDENTIFIER NULL, [ActingUserId] UNIQUEIDENTIFIER NULL, + [DeviceType] SMALLINT NULL, + [IpAddress] VARCHAR(50) NULL, [Date] DATETIME2 (7) NOT NULL, CONSTRAINT [PK_Event] PRIMARY KEY CLUSTERED ([Id] ASC) ); @@ -15,5 +17,5 @@ GO CREATE NONCLUSTERED INDEX [IX_Event_DateOrganizationIdUserId] - ON [dbo].[Event]([Date] ASC, [OrganizationId] ASC, [UserId] ASC); + ON [dbo].[Event]([Date] ASC, [OrganizationId] ASC, [UserId] ASC, [CipherId] ASC); diff --git a/util/Setup/DbScripts/2017-12-12_00_Events.sql b/util/Setup/DbScripts/2017-12-12_00_Events.sql index 9181508625..d97450fbbc 100644 --- a/util/Setup/DbScripts/2017-12-12_00_Events.sql +++ b/util/Setup/DbScripts/2017-12-12_00_Events.sql @@ -259,12 +259,14 @@ BEGIN [GroupId] UNIQUEIDENTIFIER NULL, [OrganizationUserId] UNIQUEIDENTIFIER NULL, [ActingUserId] UNIQUEIDENTIFIER NULL, + [DeviceType] SMALLINT NULL, + [IpAddress] VARCHAR(50) NULL, [Date] DATETIME2 (7) NOT NULL, CONSTRAINT [PK_Event] PRIMARY KEY CLUSTERED ([Id] ASC) ); CREATE NONCLUSTERED INDEX [IX_Event_DateOrganizationIdUserId] - ON [dbo].[Event]([Date] ASC, [OrganizationId] ASC, [UserId] ASC); + ON [dbo].[Event]([Date] ASC, [OrganizationId] ASC, [UserId] ASC, [CipherId] ASC); END GO @@ -284,6 +286,8 @@ CREATE PROCEDURE [dbo].[Event_Create] @GroupId UNIQUEIDENTIFIER, @OrganizationUserId UNIQUEIDENTIFIER, @ActingUserId UNIQUEIDENTIFIER, + @DeviceType SMALLINT, + @IpAddress VARCHAR(50), @Date DATETIME2(7) AS BEGIN @@ -300,6 +304,8 @@ BEGIN [GroupId], [OrganizationUserId], [ActingUserId], + [DeviceType], + [IpAddress], [Date] ) VALUES @@ -313,6 +319,8 @@ BEGIN @GroupId, @OrganizationUserId, @ActingUserId, + @DeviceType, + @IpAddress, @Date ) END