From d94c2a8f504545fa1ad54682c0c78df3fce54c22 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Fri, 1 Dec 2017 10:07:14 -0500 Subject: [PATCH] log user events --- src/Core/Enums/EventType.cs | 3 ++ .../ResourceOwnerPasswordValidator.cs | 24 +++++++++++---- src/Core/Models/Data/CipherEvent.cs | 30 ++++++++++--------- src/Core/Models/Data/EventTableEntity.cs | 2 +- src/Core/Models/Data/OrganizationEvent.cs | 22 +++++++------- src/Core/Models/Data/UserEvent.cs | 22 +++++++------- .../Services/Implementations/UserService.cs | 8 +++++ 7 files changed, 70 insertions(+), 41 deletions(-) diff --git a/src/Core/Enums/EventType.cs b/src/Core/Enums/EventType.cs index 756ce2a14..27cf9e503 100644 --- a/src/Core/Enums/EventType.cs +++ b/src/Core/Enums/EventType.cs @@ -6,6 +6,9 @@ User_ChangedPassword = 1001, User_Enabled2fa = 1002, User_Disabled2fa = 1003, + User_Recovered2fa = 1004, + User_FailedLogIn = 1005, + User_FailedLogIn2fa = 1006, Cipher_Created = 2000, Cipher_Edited = 2001, diff --git a/src/Core/IdentityServer/ResourceOwnerPasswordValidator.cs b/src/Core/IdentityServer/ResourceOwnerPasswordValidator.cs index 068deb268..8b8ac6e8b 100644 --- a/src/Core/IdentityServer/ResourceOwnerPasswordValidator.cs +++ b/src/Core/IdentityServer/ResourceOwnerPasswordValidator.cs @@ -21,17 +21,20 @@ namespace Bit.Core.IdentityServer private readonly IDeviceRepository _deviceRepository; private readonly IDeviceService _deviceService; private readonly IUserService _userService; + private readonly IEventService _eventService; public ResourceOwnerPasswordValidator( UserManager userManager, IDeviceRepository deviceRepository, IDeviceService deviceService, - IUserService userService) + IUserService userService, + IEventService eventService) { _userManager = userManager; _deviceRepository = deviceRepository; _deviceService = deviceService; _userService = userService; + _eventService = eventService; } public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context) @@ -43,14 +46,14 @@ namespace Bit.Core.IdentityServer if(string.IsNullOrWhiteSpace(context.UserName)) { - await BuildErrorResultAsync(false, context); + await BuildErrorResultAsync(false, context, null); return; } var user = await _userManager.FindByEmailAsync(context.UserName.ToLowerInvariant()); if(user == null || !await _userManager.CheckPasswordAsync(user, context.Password)) { - await BuildErrorResultAsync(false, context); + await BuildErrorResultAsync(false, context, user); return; } @@ -66,7 +69,7 @@ namespace Bit.Core.IdentityServer var verified = await VerifyTwoFactor(user, twoFactorProviderType, twoFactorToken); if(!verified && twoFactorProviderType != TwoFactorProviderType.Remember) { - await BuildErrorResultAsync(true, context); + await BuildErrorResultAsync(true, context, user); return; } else if(!verified && twoFactorProviderType == TwoFactorProviderType.Remember) @@ -91,6 +94,8 @@ namespace Bit.Core.IdentityServer private async Task BuildSuccessResultAsync(User user, ResourceOwnerPasswordValidationContext context, Device device, bool sendRememberToken) { + await _eventService.LogUserEventAsync(user.Id, EventType.User_LoggedIn); + var claims = new List(); if(device != null) @@ -128,7 +133,7 @@ namespace Bit.Core.IdentityServer var enabledProviders = user.GetTwoFactorProviders()?.Where(p => user.TwoFactorProviderIsEnabled(p.Key)); if(enabledProviders == null) { - await BuildErrorResultAsync(false, context); + await BuildErrorResultAsync(false, context, user); return; } @@ -153,8 +158,15 @@ namespace Bit.Core.IdentityServer } } - private async Task BuildErrorResultAsync(bool twoFactorRequest, ResourceOwnerPasswordValidationContext context) + private async Task BuildErrorResultAsync(bool twoFactorRequest, + ResourceOwnerPasswordValidationContext context, User user) { + if(user != null) + { + await _eventService.LogUserEventAsync(user.Id, + twoFactorRequest ? EventType.User_FailedLogIn2fa : EventType.User_FailedLogIn); + } + await Task.Delay(2000); // Delay for brute force. context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, customResponse: new Dictionary diff --git a/src/Core/Models/Data/CipherEvent.cs b/src/Core/Models/Data/CipherEvent.cs index 5726127a8..922799c03 100644 --- a/src/Core/Models/Data/CipherEvent.cs +++ b/src/Core/Models/Data/CipherEvent.cs @@ -1,4 +1,5 @@ -using Bit.Core.Enums; +using System; +using Bit.Core.Enums; using Bit.Core.Models.Table; using Bit.Core.Utilities; @@ -8,22 +9,23 @@ namespace Bit.Core.Models.Data { public CipherEvent(Cipher cipher, EventType type) { - if(cipher.OrganizationId.HasValue) - { - PartitionKey = $"OrganizationId={cipher.OrganizationId.Value}"; - } - else - { - PartitionKey = $"UserId={cipher.UserId.Value}"; - } - - RowKey = string.Format("Date={0}__CipherId={1}__Type={2}", - CoreHelpers.DateTimeToTableStorageKey(), cipher.Id, type); - OrganizationId = cipher.OrganizationId; UserId = cipher.UserId; CipherId = cipher.Id; - Type = type; + Type = (int)type; + + Timestamp = DateTime.UtcNow; + if(OrganizationId.HasValue) + { + PartitionKey = $"OrganizationId={OrganizationId}"; + } + else + { + PartitionKey = $"UserId={UserId}"; + } + + 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 398b16bd2..8ec719f81 100644 --- a/src/Core/Models/Data/EventTableEntity.cs +++ b/src/Core/Models/Data/EventTableEntity.cs @@ -7,7 +7,7 @@ namespace Bit.Core.Models.Data { public class EventTableEntity : TableEntity { - public EventType Type { get; set; } + public int Type { get; set; } public Guid? UserId { get; set; } public Guid? OrganizationId { get; set; } public Guid? CipherId { get; set; } diff --git a/src/Core/Models/Data/OrganizationEvent.cs b/src/Core/Models/Data/OrganizationEvent.cs index 05efcf7ae..b62fd5aff 100644 --- a/src/Core/Models/Data/OrganizationEvent.cs +++ b/src/Core/Models/Data/OrganizationEvent.cs @@ -8,23 +8,25 @@ namespace Bit.Core.Models.Data { public OrganizationEvent(Guid organizationId, EventType type) { - PartitionKey = $"OrganizationId={organizationId}"; - RowKey = string.Format("Date={0}__Type={1}", - CoreHelpers.DateTimeToTableStorageKey(), type); - OrganizationId = organizationId; - Type = type; + Type = (int)type; + + 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) { - PartitionKey = $"OrganizationId={organizationId}"; - RowKey = string.Format("Date={0}__UserId={1}__Type={2}", - CoreHelpers.DateTimeToTableStorageKey(), userId, type); - OrganizationId = organizationId; UserId = userId; - Type = type; + Type = (int)type; + + Timestamp = DateTime.UtcNow; + PartitionKey = $"OrganizationId={OrganizationId}"; + RowKey = string.Format("Date={0}__UserId={1}__Type={2}", + CoreHelpers.DateTimeToTableStorageKey(Timestamp.DateTime), UserId, Type); } } } diff --git a/src/Core/Models/Data/UserEvent.cs b/src/Core/Models/Data/UserEvent.cs index 1ced8bf2b..9fa4978b4 100644 --- a/src/Core/Models/Data/UserEvent.cs +++ b/src/Core/Models/Data/UserEvent.cs @@ -8,23 +8,25 @@ namespace Bit.Core.Models.Data { public UserEvent(Guid userId, EventType type) { - PartitionKey = $"UserId={userId}"; - RowKey = string.Format("Date={0}__Type={1}", - CoreHelpers.DateTimeToTableStorageKey(), type); - UserId = userId; - Type = type; + Type = (int)type; + + Timestamp = DateTime.UtcNow; + PartitionKey = $"UserId={UserId}"; + RowKey = string.Format("Date={0}__Type={1}", + CoreHelpers.DateTimeToTableStorageKey(Timestamp.DateTime), Type); } public UserEvent(Guid userId, Guid organizationId, EventType type) { - PartitionKey = $"OrganizationId={organizationId}"; - RowKey = string.Format("Date={0}__UserId={1}__Type={2}", - CoreHelpers.DateTimeToTableStorageKey(), userId, type); - OrganizationId = organizationId; UserId = userId; - Type = type; + Type = (int)type; + + Timestamp = DateTime.UtcNow; + PartitionKey = $"OrganizationId={OrganizationId}"; + RowKey = string.Format("Date={0}__UserId={1}__Type={2}", + CoreHelpers.DateTimeToTableStorageKey(Timestamp.DateTime), UserId, Type); } } } diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index dcdd149e7..292a753a5 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -37,6 +37,7 @@ namespace Bit.Core.Services private readonly IPasswordHasher _passwordHasher; private readonly IEnumerable> _passwordValidators; private readonly ILicensingService _licenseService; + private readonly IEventService _eventService; private readonly CurrentContext _currentContext; private readonly GlobalSettings _globalSettings; @@ -57,6 +58,7 @@ namespace Bit.Core.Services IServiceProvider services, ILogger> logger, ILicensingService licenseService, + IEventService eventService, CurrentContext currentContext, GlobalSettings globalSettings) : base( @@ -81,6 +83,7 @@ namespace Bit.Core.Services _passwordHasher = passwordHasher; _passwordValidators = passwordValidators; _licenseService = licenseService; + _eventService = eventService; _currentContext = currentContext; _globalSettings = globalSettings; } @@ -420,7 +423,9 @@ namespace Bit.Core.Services user.RevisionDate = user.AccountRevisionDate = DateTime.UtcNow; user.Key = key; + await _userRepository.ReplaceAsync(user); + await _eventService.LogUserEventAsync(user.Id, EventType.User_ChangedPassword); return IdentityResult.Success; } @@ -498,6 +503,7 @@ namespace Bit.Core.Services user.TwoFactorRecoveryCode = CoreHelpers.SecureRandomString(32, upper: false, special: false); } await SaveUserAsync(user); + await _eventService.LogUserEventAsync(user.Id, EventType.User_Enabled2fa); } public async Task DisableTwoFactorProviderAsync(User user, TwoFactorProviderType type) @@ -511,6 +517,7 @@ namespace Bit.Core.Services providers.Remove(type); user.SetTwoFactorProviders(providers); await SaveUserAsync(user); + await _eventService.LogUserEventAsync(user.Id, EventType.User_Disabled2fa); } public async Task RecoverTwoFactorAsync(string email, string masterPassword, string recoveryCode) @@ -535,6 +542,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, EventType.User_Recovered2fa); return true; }