1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-22 12:15:36 +01:00

store device type and ip address on events

This commit is contained in:
Kyle Spearrin 2017-12-15 10:50:06 -05:00
parent 39f6516ca8
commit a9f232746e
11 changed files with 123 additions and 44 deletions

View File

@ -3,14 +3,21 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using Bit.Core.Models.Table; using Bit.Core.Models.Table;
using Bit.Core.Enums; using Bit.Core.Enums;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
namespace Bit.Core namespace Bit.Core
{ {
public class CurrentContext public class CurrentContext
{ {
private string _ip;
public virtual HttpContext HttpContext { get; set; }
public virtual Guid? UserId { get; set; } public virtual Guid? UserId { get; set; }
public virtual User User { get; set; } public virtual User User { get; set; }
public virtual string DeviceIdentifier { get; set; } public virtual string DeviceIdentifier { get; set; }
public virtual DeviceType? DeviceType { get; set; }
public virtual string IpAddress => GetRequestIp();
public virtual List<CurrentContentOrganization> Organizations { get; set; } = new List<CurrentContentOrganization>(); public virtual List<CurrentContentOrganization> Organizations { get; set; } = new List<CurrentContentOrganization>();
public virtual Guid? InstallationId { get; set; } public virtual Guid? InstallationId { get; set; }
@ -28,6 +35,31 @@ namespace Bit.Core
return Organizations.Any(o => o.Id == orgId && o.Type == OrganizationUserType.Owner); 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 class CurrentContentOrganization
{ {
public Guid Id { get; set; } public Guid Id { get; set; }

View File

@ -5,6 +5,14 @@ namespace Bit.Core.Models.Data
{ {
public class EventMessage : IEvent public class EventMessage : IEvent
{ {
public EventMessage() { }
public EventMessage(CurrentContext currentContext)
{
IpAddress = currentContext.IpAddress;
DeviceType = currentContext.DeviceType;
}
public DateTime Date { get; set; } public DateTime Date { get; set; }
public EventType Type { get; set; } public EventType Type { get; set; }
public Guid? UserId { get; set; } public Guid? UserId { get; set; }
@ -14,5 +22,7 @@ namespace Bit.Core.Models.Data
public Guid? GroupId { get; set; } public Guid? GroupId { get; set; }
public Guid? OrganizationUserId { get; set; } public Guid? OrganizationUserId { get; set; }
public Guid? ActingUserId { get; set; } public Guid? ActingUserId { get; set; }
public DeviceType? DeviceType { get; set; }
public string IpAddress { get; set; }
} }
} }

View File

@ -21,6 +21,8 @@ namespace Bit.Core.Models.Data
CollectionId = e.CollectionId; CollectionId = e.CollectionId;
GroupId = e.GroupId; GroupId = e.GroupId;
OrganizationUserId = e.OrganizationUserId; OrganizationUserId = e.OrganizationUserId;
DeviceType = e.DeviceType;
IpAddress = e.IpAddress;
ActingUserId = e.ActingUserId; ActingUserId = e.ActingUserId;
} }
@ -32,47 +34,58 @@ namespace Bit.Core.Models.Data
public Guid? CollectionId { get; set; } public Guid? CollectionId { get; set; }
public Guid? GroupId { get; set; } public Guid? GroupId { get; set; }
public Guid? OrganizationUserId { get; set; } public Guid? OrganizationUserId { get; set; }
public DeviceType? DeviceType { get; set; }
public string IpAddress { get; set; }
public Guid? ActingUserId { get; set; } public Guid? ActingUserId { get; set; }
public override IDictionary<string, EntityProperty> WriteEntity(OperationContext operationContext) public override IDictionary<string, EntityProperty> WriteEntity(OperationContext operationContext)
{ {
var result = base.WriteEntity(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 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; return result;
} }
public override void ReadEntity(IDictionary<string, EntityProperty> properties, OperationContext operationContext) public override void ReadEntity(IDictionary<string, EntityProperty> properties, OperationContext operationContext)
{ {
base.ReadEntity(properties, 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<EventTableEntity> IndexEvent(IEvent e) public static List<EventTableEntity> IndexEvent(IEvent e)
{
if(e.OrganizationId.HasValue)
{
return IndexOrgEvent(e);
}
else
{
return new List<EventTableEntity> { IndexUserEvent(e) };
}
}
private static List<EventTableEntity> IndexOrgEvent(IEvent e)
{ {
var uniquifier = Guid.NewGuid(); 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 dateKey = CoreHelpers.DateTimeToTableStorageKey(e.Date);
var entities = new List<EventTableEntity> var entities = new List<EventTableEntity>
@ -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) entities.Add(new EventTableEntity(e)
{ {
@ -104,15 +117,5 @@ namespace Bit.Core.Models.Data
return entities; 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)
};
}
} }
} }

View File

@ -13,6 +13,8 @@ namespace Bit.Core.Models.Data
Guid? GroupId { get; set; } Guid? GroupId { get; set; }
Guid? OrganizationUserId { get; set; } Guid? OrganizationUserId { get; set; }
Guid? ActingUserId { get; set; } Guid? ActingUserId { get; set; }
DeviceType? DeviceType { get; set; }
string IpAddress { get; set; }
DateTime Date { get; set; } DateTime Date { get; set; }
} }
} }

View File

@ -19,6 +19,8 @@ namespace Bit.Core.Models.Table
CollectionId = e.CollectionId; CollectionId = e.CollectionId;
GroupId = e.GroupId; GroupId = e.GroupId;
OrganizationUserId = e.OrganizationUserId; OrganizationUserId = e.OrganizationUserId;
DeviceType = e.DeviceType;
IpAddress = e.IpAddress;
ActingUserId = e.ActingUserId; ActingUserId = e.ActingUserId;
} }
@ -31,6 +33,8 @@ namespace Bit.Core.Models.Table
public Guid? CollectionId { get; set; } public Guid? CollectionId { get; set; }
public Guid? GroupId { get; set; } public Guid? GroupId { get; set; }
public Guid? OrganizationUserId { get; set; } public Guid? OrganizationUserId { get; set; }
public DeviceType? DeviceType { get; set; }
public string IpAddress { get; set; }
public Guid? ActingUserId { get; set; } public Guid? ActingUserId { get; set; }
public void SetNewId() public void SetNewId()

View File

@ -33,7 +33,7 @@ namespace Bit.Core.Services
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
var events = new List<IEvent> var events = new List<IEvent>
{ {
new EventMessage new EventMessage(_currentContext)
{ {
UserId = userId, UserId = userId,
ActingUserId = userId, ActingUserId = userId,
@ -45,7 +45,7 @@ namespace Bit.Core.Services
IEnumerable<IEvent> orgEvents; IEnumerable<IEvent> orgEvents;
if(_currentContext.UserId.HasValue) if(_currentContext.UserId.HasValue)
{ {
orgEvents = _currentContext.Organizations.Select(o => new EventMessage orgEvents = _currentContext.Organizations.Select(o => new EventMessage(_currentContext)
{ {
OrganizationId = o.Id, OrganizationId = o.Id,
UserId = userId, UserId = userId,
@ -58,7 +58,7 @@ namespace Bit.Core.Services
{ {
var orgs = await _organizationUserRepository.GetManyByUserAsync(userId); var orgs = await _organizationUserRepository.GetManyByUserAsync(userId);
orgEvents = orgs.Where(o => o.Status == OrganizationUserStatusType.Confirmed) orgEvents = orgs.Where(o => o.Status == OrganizationUserStatusType.Confirmed)
.Select(o => new EventMessage .Select(o => new EventMessage(_currentContext)
{ {
OrganizationId = o.OrganizationId, OrganizationId = o.OrganizationId,
UserId = userId, UserId = userId,
@ -87,7 +87,7 @@ namespace Bit.Core.Services
return; return;
} }
var e = new EventMessage var e = new EventMessage(_currentContext)
{ {
OrganizationId = cipher.OrganizationId, OrganizationId = cipher.OrganizationId,
UserId = cipher.OrganizationId.HasValue ? null : cipher.UserId, UserId = cipher.OrganizationId.HasValue ? null : cipher.UserId,
@ -101,7 +101,7 @@ namespace Bit.Core.Services
public async Task LogCollectionEventAsync(Collection collection, EventType type) public async Task LogCollectionEventAsync(Collection collection, EventType type)
{ {
var e = new EventMessage var e = new EventMessage(_currentContext)
{ {
OrganizationId = collection.OrganizationId, OrganizationId = collection.OrganizationId,
CollectionId = collection.Id, CollectionId = collection.Id,
@ -114,7 +114,7 @@ namespace Bit.Core.Services
public async Task LogGroupEventAsync(Group group, EventType type) public async Task LogGroupEventAsync(Group group, EventType type)
{ {
var e = new EventMessage var e = new EventMessage(_currentContext)
{ {
OrganizationId = group.OrganizationId, OrganizationId = group.OrganizationId,
GroupId = group.Id, GroupId = group.Id,
@ -127,7 +127,7 @@ namespace Bit.Core.Services
public async Task LogOrganizationUserEventAsync(OrganizationUser organizationUser, EventType type) public async Task LogOrganizationUserEventAsync(OrganizationUser organizationUser, EventType type)
{ {
var e = new EventMessage var e = new EventMessage(_currentContext)
{ {
OrganizationId = organizationUser.OrganizationId, OrganizationId = organizationUser.OrganizationId,
UserId = organizationUser.UserId, UserId = organizationUser.UserId,
@ -141,7 +141,7 @@ namespace Bit.Core.Services
public async Task LogOrganizationEventAsync(Organization organization, EventType type) public async Task LogOrganizationEventAsync(Organization organization, EventType type)
{ {
var e = new EventMessage var e = new EventMessage(_currentContext)
{ {
OrganizationId = organization.Id, OrganizationId = organization.Id,
Type = type, Type = type,

View File

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Http; using Bit.Core.Enums;
using Microsoft.AspNetCore.Http;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -18,7 +19,9 @@ namespace Bit.Core.Utilities
public async Task Invoke(HttpContext httpContext, CurrentContext currentContext) 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 var claimsDict = httpContext.User.Claims
.GroupBy(c => c.Type) .GroupBy(c => c.Type)
@ -48,7 +51,7 @@ namespace Bit.Core.Utilities
new CurrentContext.CurrentContentOrganization new CurrentContext.CurrentContentOrganization
{ {
Id = new Guid(c.Value), Id = new Guid(c.Value),
Type = Core.Enums.OrganizationUserType.Owner Type = OrganizationUserType.Owner
})); }));
} }
@ -58,7 +61,7 @@ namespace Bit.Core.Utilities
new CurrentContext.CurrentContentOrganization new CurrentContext.CurrentContentOrganization
{ {
Id = new Guid(c.Value), Id = new Guid(c.Value),
Type = Core.Enums.OrganizationUserType.Admin Type = OrganizationUserType.Admin
})); }));
} }
@ -68,7 +71,7 @@ namespace Bit.Core.Utilities
new CurrentContext.CurrentContentOrganization new CurrentContext.CurrentContentOrganization
{ {
Id = new Guid(c.Value), 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"]; 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); await _next.Invoke(httpContext);
} }

View File

@ -98,6 +98,9 @@ namespace Bit.Identity
app.UseMiddleware<CustomIpRateLimitMiddleware>(); app.UseMiddleware<CustomIpRateLimitMiddleware>();
} }
// Add current context
app.UseMiddleware<CurrentContextMiddleware>();
// Add IdentityServer to the request pipeline. // Add IdentityServer to the request pipeline.
app.UseIdentityServer(); app.UseIdentityServer();
} }

View File

@ -8,6 +8,8 @@
@GroupId UNIQUEIDENTIFIER, @GroupId UNIQUEIDENTIFIER,
@OrganizationUserId UNIQUEIDENTIFIER, @OrganizationUserId UNIQUEIDENTIFIER,
@ActingUserId UNIQUEIDENTIFIER, @ActingUserId UNIQUEIDENTIFIER,
@DeviceType SMALLINT,
@IpAddress VARCHAR(50),
@Date DATETIME2(7) @Date DATETIME2(7)
AS AS
BEGIN BEGIN
@ -24,6 +26,8 @@ BEGIN
[GroupId], [GroupId],
[OrganizationUserId], [OrganizationUserId],
[ActingUserId], [ActingUserId],
[DeviceType],
[IpAddress],
[Date] [Date]
) )
VALUES VALUES
@ -37,6 +41,8 @@ BEGIN
@GroupId, @GroupId,
@OrganizationUserId, @OrganizationUserId,
@ActingUserId, @ActingUserId,
@DeviceType,
@IpAddress,
@Date @Date
) )
END END

View File

@ -8,6 +8,8 @@
[GroupId] UNIQUEIDENTIFIER NULL, [GroupId] UNIQUEIDENTIFIER NULL,
[OrganizationUserId] UNIQUEIDENTIFIER NULL, [OrganizationUserId] UNIQUEIDENTIFIER NULL,
[ActingUserId] UNIQUEIDENTIFIER NULL, [ActingUserId] UNIQUEIDENTIFIER NULL,
[DeviceType] SMALLINT NULL,
[IpAddress] VARCHAR(50) NULL,
[Date] DATETIME2 (7) NOT NULL, [Date] DATETIME2 (7) NOT NULL,
CONSTRAINT [PK_Event] PRIMARY KEY CLUSTERED ([Id] ASC) CONSTRAINT [PK_Event] PRIMARY KEY CLUSTERED ([Id] ASC)
); );
@ -15,5 +17,5 @@
GO GO
CREATE NONCLUSTERED INDEX [IX_Event_DateOrganizationIdUserId] 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);

View File

@ -259,12 +259,14 @@ BEGIN
[GroupId] UNIQUEIDENTIFIER NULL, [GroupId] UNIQUEIDENTIFIER NULL,
[OrganizationUserId] UNIQUEIDENTIFIER NULL, [OrganizationUserId] UNIQUEIDENTIFIER NULL,
[ActingUserId] UNIQUEIDENTIFIER NULL, [ActingUserId] UNIQUEIDENTIFIER NULL,
[DeviceType] SMALLINT NULL,
[IpAddress] VARCHAR(50) NULL,
[Date] DATETIME2 (7) NOT NULL, [Date] DATETIME2 (7) NOT NULL,
CONSTRAINT [PK_Event] PRIMARY KEY CLUSTERED ([Id] ASC) CONSTRAINT [PK_Event] PRIMARY KEY CLUSTERED ([Id] ASC)
); );
CREATE NONCLUSTERED INDEX [IX_Event_DateOrganizationIdUserId] 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 END
GO GO
@ -284,6 +286,8 @@ CREATE PROCEDURE [dbo].[Event_Create]
@GroupId UNIQUEIDENTIFIER, @GroupId UNIQUEIDENTIFIER,
@OrganizationUserId UNIQUEIDENTIFIER, @OrganizationUserId UNIQUEIDENTIFIER,
@ActingUserId UNIQUEIDENTIFIER, @ActingUserId UNIQUEIDENTIFIER,
@DeviceType SMALLINT,
@IpAddress VARCHAR(50),
@Date DATETIME2(7) @Date DATETIME2(7)
AS AS
BEGIN BEGIN
@ -300,6 +304,8 @@ BEGIN
[GroupId], [GroupId],
[OrganizationUserId], [OrganizationUserId],
[ActingUserId], [ActingUserId],
[DeviceType],
[IpAddress],
[Date] [Date]
) )
VALUES VALUES
@ -313,6 +319,8 @@ BEGIN
@GroupId, @GroupId,
@OrganizationUserId, @OrganizationUserId,
@ActingUserId, @ActingUserId,
@DeviceType,
@IpAddress,
@Date @Date
) )
END END