mirror of
https://github.com/bitwarden/server.git
synced 2025-04-08 14:38:15 -05:00
log user events
This commit is contained in:
parent
f4586002c4
commit
d94c2a8f50
@ -6,6 +6,9 @@
|
|||||||
User_ChangedPassword = 1001,
|
User_ChangedPassword = 1001,
|
||||||
User_Enabled2fa = 1002,
|
User_Enabled2fa = 1002,
|
||||||
User_Disabled2fa = 1003,
|
User_Disabled2fa = 1003,
|
||||||
|
User_Recovered2fa = 1004,
|
||||||
|
User_FailedLogIn = 1005,
|
||||||
|
User_FailedLogIn2fa = 1006,
|
||||||
|
|
||||||
Cipher_Created = 2000,
|
Cipher_Created = 2000,
|
||||||
Cipher_Edited = 2001,
|
Cipher_Edited = 2001,
|
||||||
|
@ -21,17 +21,20 @@ namespace Bit.Core.IdentityServer
|
|||||||
private readonly IDeviceRepository _deviceRepository;
|
private readonly IDeviceRepository _deviceRepository;
|
||||||
private readonly IDeviceService _deviceService;
|
private readonly IDeviceService _deviceService;
|
||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
|
private readonly IEventService _eventService;
|
||||||
|
|
||||||
public ResourceOwnerPasswordValidator(
|
public ResourceOwnerPasswordValidator(
|
||||||
UserManager<User> userManager,
|
UserManager<User> userManager,
|
||||||
IDeviceRepository deviceRepository,
|
IDeviceRepository deviceRepository,
|
||||||
IDeviceService deviceService,
|
IDeviceService deviceService,
|
||||||
IUserService userService)
|
IUserService userService,
|
||||||
|
IEventService eventService)
|
||||||
{
|
{
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_deviceRepository = deviceRepository;
|
_deviceRepository = deviceRepository;
|
||||||
_deviceService = deviceService;
|
_deviceService = deviceService;
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
|
_eventService = eventService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
|
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
|
||||||
@ -43,14 +46,14 @@ namespace Bit.Core.IdentityServer
|
|||||||
|
|
||||||
if(string.IsNullOrWhiteSpace(context.UserName))
|
if(string.IsNullOrWhiteSpace(context.UserName))
|
||||||
{
|
{
|
||||||
await BuildErrorResultAsync(false, context);
|
await BuildErrorResultAsync(false, context, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var user = await _userManager.FindByEmailAsync(context.UserName.ToLowerInvariant());
|
var user = await _userManager.FindByEmailAsync(context.UserName.ToLowerInvariant());
|
||||||
if(user == null || !await _userManager.CheckPasswordAsync(user, context.Password))
|
if(user == null || !await _userManager.CheckPasswordAsync(user, context.Password))
|
||||||
{
|
{
|
||||||
await BuildErrorResultAsync(false, context);
|
await BuildErrorResultAsync(false, context, user);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +69,7 @@ namespace Bit.Core.IdentityServer
|
|||||||
var verified = await VerifyTwoFactor(user, twoFactorProviderType, twoFactorToken);
|
var verified = await VerifyTwoFactor(user, twoFactorProviderType, twoFactorToken);
|
||||||
if(!verified && twoFactorProviderType != TwoFactorProviderType.Remember)
|
if(!verified && twoFactorProviderType != TwoFactorProviderType.Remember)
|
||||||
{
|
{
|
||||||
await BuildErrorResultAsync(true, context);
|
await BuildErrorResultAsync(true, context, user);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if(!verified && twoFactorProviderType == TwoFactorProviderType.Remember)
|
else if(!verified && twoFactorProviderType == TwoFactorProviderType.Remember)
|
||||||
@ -91,6 +94,8 @@ namespace Bit.Core.IdentityServer
|
|||||||
private async Task BuildSuccessResultAsync(User user, ResourceOwnerPasswordValidationContext context,
|
private async Task BuildSuccessResultAsync(User user, ResourceOwnerPasswordValidationContext context,
|
||||||
Device device, bool sendRememberToken)
|
Device device, bool sendRememberToken)
|
||||||
{
|
{
|
||||||
|
await _eventService.LogUserEventAsync(user.Id, EventType.User_LoggedIn);
|
||||||
|
|
||||||
var claims = new List<Claim>();
|
var claims = new List<Claim>();
|
||||||
|
|
||||||
if(device != null)
|
if(device != null)
|
||||||
@ -128,7 +133,7 @@ namespace Bit.Core.IdentityServer
|
|||||||
var enabledProviders = user.GetTwoFactorProviders()?.Where(p => user.TwoFactorProviderIsEnabled(p.Key));
|
var enabledProviders = user.GetTwoFactorProviders()?.Where(p => user.TwoFactorProviderIsEnabled(p.Key));
|
||||||
if(enabledProviders == null)
|
if(enabledProviders == null)
|
||||||
{
|
{
|
||||||
await BuildErrorResultAsync(false, context);
|
await BuildErrorResultAsync(false, context, user);
|
||||||
return;
|
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.
|
await Task.Delay(2000); // Delay for brute force.
|
||||||
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant,
|
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant,
|
||||||
customResponse: new Dictionary<string, object>
|
customResponse: new Dictionary<string, object>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Bit.Core.Enums;
|
using System;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Table;
|
using Bit.Core.Models.Table;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
@ -8,22 +9,23 @@ namespace Bit.Core.Models.Data
|
|||||||
{
|
{
|
||||||
public CipherEvent(Cipher cipher, EventType type)
|
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;
|
OrganizationId = cipher.OrganizationId;
|
||||||
UserId = cipher.UserId;
|
UserId = cipher.UserId;
|
||||||
CipherId = cipher.Id;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ namespace Bit.Core.Models.Data
|
|||||||
{
|
{
|
||||||
public class EventTableEntity : TableEntity
|
public class EventTableEntity : TableEntity
|
||||||
{
|
{
|
||||||
public EventType Type { get; set; }
|
public int Type { get; set; }
|
||||||
public Guid? UserId { get; set; }
|
public Guid? UserId { get; set; }
|
||||||
public Guid? OrganizationId { get; set; }
|
public Guid? OrganizationId { get; set; }
|
||||||
public Guid? CipherId { get; set; }
|
public Guid? CipherId { get; set; }
|
||||||
|
@ -8,23 +8,25 @@ namespace Bit.Core.Models.Data
|
|||||||
{
|
{
|
||||||
public OrganizationEvent(Guid organizationId, EventType type)
|
public OrganizationEvent(Guid organizationId, EventType type)
|
||||||
{
|
{
|
||||||
PartitionKey = $"OrganizationId={organizationId}";
|
|
||||||
RowKey = string.Format("Date={0}__Type={1}",
|
|
||||||
CoreHelpers.DateTimeToTableStorageKey(), type);
|
|
||||||
|
|
||||||
OrganizationId = organizationId;
|
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)
|
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;
|
OrganizationId = organizationId;
|
||||||
UserId = userId;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,23 +8,25 @@ namespace Bit.Core.Models.Data
|
|||||||
{
|
{
|
||||||
public UserEvent(Guid userId, EventType type)
|
public UserEvent(Guid userId, EventType type)
|
||||||
{
|
{
|
||||||
PartitionKey = $"UserId={userId}";
|
|
||||||
RowKey = string.Format("Date={0}__Type={1}",
|
|
||||||
CoreHelpers.DateTimeToTableStorageKey(), type);
|
|
||||||
|
|
||||||
UserId = userId;
|
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)
|
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;
|
OrganizationId = organizationId;
|
||||||
UserId = userId;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,7 @@ namespace Bit.Core.Services
|
|||||||
private readonly IPasswordHasher<User> _passwordHasher;
|
private readonly IPasswordHasher<User> _passwordHasher;
|
||||||
private readonly IEnumerable<IPasswordValidator<User>> _passwordValidators;
|
private readonly IEnumerable<IPasswordValidator<User>> _passwordValidators;
|
||||||
private readonly ILicensingService _licenseService;
|
private readonly ILicensingService _licenseService;
|
||||||
|
private readonly IEventService _eventService;
|
||||||
private readonly CurrentContext _currentContext;
|
private readonly CurrentContext _currentContext;
|
||||||
private readonly GlobalSettings _globalSettings;
|
private readonly GlobalSettings _globalSettings;
|
||||||
|
|
||||||
@ -57,6 +58,7 @@ namespace Bit.Core.Services
|
|||||||
IServiceProvider services,
|
IServiceProvider services,
|
||||||
ILogger<UserManager<User>> logger,
|
ILogger<UserManager<User>> logger,
|
||||||
ILicensingService licenseService,
|
ILicensingService licenseService,
|
||||||
|
IEventService eventService,
|
||||||
CurrentContext currentContext,
|
CurrentContext currentContext,
|
||||||
GlobalSettings globalSettings)
|
GlobalSettings globalSettings)
|
||||||
: base(
|
: base(
|
||||||
@ -81,6 +83,7 @@ namespace Bit.Core.Services
|
|||||||
_passwordHasher = passwordHasher;
|
_passwordHasher = passwordHasher;
|
||||||
_passwordValidators = passwordValidators;
|
_passwordValidators = passwordValidators;
|
||||||
_licenseService = licenseService;
|
_licenseService = licenseService;
|
||||||
|
_eventService = eventService;
|
||||||
_currentContext = currentContext;
|
_currentContext = currentContext;
|
||||||
_globalSettings = globalSettings;
|
_globalSettings = globalSettings;
|
||||||
}
|
}
|
||||||
@ -420,7 +423,9 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
user.RevisionDate = user.AccountRevisionDate = DateTime.UtcNow;
|
user.RevisionDate = user.AccountRevisionDate = DateTime.UtcNow;
|
||||||
user.Key = key;
|
user.Key = key;
|
||||||
|
|
||||||
await _userRepository.ReplaceAsync(user);
|
await _userRepository.ReplaceAsync(user);
|
||||||
|
await _eventService.LogUserEventAsync(user.Id, EventType.User_ChangedPassword);
|
||||||
|
|
||||||
return IdentityResult.Success;
|
return IdentityResult.Success;
|
||||||
}
|
}
|
||||||
@ -498,6 +503,7 @@ namespace Bit.Core.Services
|
|||||||
user.TwoFactorRecoveryCode = CoreHelpers.SecureRandomString(32, upper: false, special: false);
|
user.TwoFactorRecoveryCode = CoreHelpers.SecureRandomString(32, upper: false, special: false);
|
||||||
}
|
}
|
||||||
await SaveUserAsync(user);
|
await SaveUserAsync(user);
|
||||||
|
await _eventService.LogUserEventAsync(user.Id, EventType.User_Enabled2fa);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DisableTwoFactorProviderAsync(User user, TwoFactorProviderType type)
|
public async Task DisableTwoFactorProviderAsync(User user, TwoFactorProviderType type)
|
||||||
@ -511,6 +517,7 @@ namespace Bit.Core.Services
|
|||||||
providers.Remove(type);
|
providers.Remove(type);
|
||||||
user.SetTwoFactorProviders(providers);
|
user.SetTwoFactorProviders(providers);
|
||||||
await SaveUserAsync(user);
|
await SaveUserAsync(user);
|
||||||
|
await _eventService.LogUserEventAsync(user.Id, EventType.User_Disabled2fa);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> RecoverTwoFactorAsync(string email, string masterPassword, string recoveryCode)
|
public async Task<bool> RecoverTwoFactorAsync(string email, string masterPassword, string recoveryCode)
|
||||||
@ -535,6 +542,7 @@ namespace Bit.Core.Services
|
|||||||
user.TwoFactorProviders = null;
|
user.TwoFactorProviders = null;
|
||||||
user.TwoFactorRecoveryCode = CoreHelpers.SecureRandomString(32, upper: false, special: false);
|
user.TwoFactorRecoveryCode = CoreHelpers.SecureRandomString(32, upper: false, special: false);
|
||||||
await SaveUserAsync(user);
|
await SaveUserAsync(user);
|
||||||
|
await _eventService.LogUserEventAsync(user.Id, EventType.User_Recovered2fa);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user