1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-16 02:28:13 -05:00

cipher events

This commit is contained in:
Kyle Spearrin 2017-12-01 14:06:16 -05:00
parent 0662fc2163
commit a8fefb54c4
11 changed files with 86 additions and 36 deletions

View File

@ -25,12 +25,17 @@ namespace Bit.Api.Middleware
.GroupBy(c => c.Type) .GroupBy(c => c.Type)
.ToDictionary(c => c.Key, c => c.Select(v => v)); .ToDictionary(c => c.Key, c => c.Select(v => v));
var subject = GetClaimValue(claimsDict, "sub");
if(Guid.TryParse(subject, out var subIdGuid))
{
currentContext.UserId = subIdGuid;
}
var clientId = GetClaimValue(claimsDict, "client_id"); var clientId = GetClaimValue(claimsDict, "client_id");
var clientSubject = GetClaimValue(claimsDict, "client_sub"); var clientSubject = GetClaimValue(claimsDict, "client_sub");
if((clientId?.StartsWith("installation.") ?? false) && clientSubject != null) if((clientId?.StartsWith("installation.") ?? false) && clientSubject != null)
{ {
Guid idGuid; if(Guid.TryParse(clientSubject, out var idGuid))
if(Guid.TryParse(clientSubject, out idGuid))
{ {
currentContext.InstallationId = idGuid; currentContext.InstallationId = idGuid;
} }

View File

@ -8,6 +8,7 @@ namespace Bit.Core
{ {
public class CurrentContext public class CurrentContext
{ {
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 List<CurrentContentOrganization> Organizations { get; set; } = new List<CurrentContentOrganization>(); public virtual List<CurrentContentOrganization> Organizations { get; set; } = new List<CurrentContentOrganization>();

View File

@ -10,8 +10,25 @@
User_FailedLogIn = 1005, User_FailedLogIn = 1005,
User_FailedLogIn2fa = 1006, User_FailedLogIn2fa = 1006,
Cipher_Created = 2000, Cipher_Created = 1100,
Cipher_Edited = 2001, Cipher_Updated = 1101,
Cipher_Deleted = 2002 Cipher_Deleted = 1102,
Cipher_AttachmentCreated = 1103,
Cipher_AttachmentDeleted = 1104,
Cipher_Shared = 1105,
Cipher_UpdatedCollections = 1106,
Collection_Created = 1300,
Collection_Updated = 1301,
Collection_Deleted = 1302,
Group_Created = 1400,
Group_Updated = 1401,
Group_Deleted = 1402,
OrganizationUser_Invited = 1500,
OrganizationUser_Confirmed = 1501,
OrganizationUser_Updated = 1502,
OrganizationUser_Removed = 1503
} }
} }

View File

@ -7,25 +7,28 @@ namespace Bit.Core.Models.Data
{ {
public class CipherEvent : EventTableEntity public class CipherEvent : EventTableEntity
{ {
public CipherEvent(Cipher cipher, EventType type) public CipherEvent(Cipher cipher, EventType type, Guid? actingUserId = null)
{ {
OrganizationId = cipher.OrganizationId; OrganizationId = cipher.OrganizationId;
UserId = cipher.UserId; UserId = cipher.UserId;
CipherId = cipher.Id; CipherId = cipher.Id;
Type = (int)type; Type = (int)type;
ActingUserId = actingUserId;
Timestamp = DateTime.UtcNow; Timestamp = DateTime.UtcNow;
if(OrganizationId.HasValue) if(OrganizationId.HasValue)
{ {
UserId = null;
PartitionKey = $"OrganizationId={OrganizationId}"; PartitionKey = $"OrganizationId={OrganizationId}";
RowKey = string.Format("Date={0}__CipherId={1}__ActingUserId={2}__Type={3}",
CoreHelpers.DateTimeToTableStorageKey(Timestamp.DateTime), CipherId, ActingUserId, Type);
} }
else else
{ {
PartitionKey = $"UserId={UserId}"; PartitionKey = $"UserId={UserId}";
}
RowKey = string.Format("Date={0}__CipherId={1}__Type={2}", RowKey = string.Format("Date={0}__CipherId={1}__Type={2}",
CoreHelpers.DateTimeToTableStorageKey(Timestamp.DateTime), CipherId, Type); CoreHelpers.DateTimeToTableStorageKey(Timestamp.DateTime), CipherId, Type);
} }
} }
} }
}

View File

@ -11,5 +11,6 @@ namespace Bit.Core.Models.Data
public Guid? OrganizationId { get; set; } public Guid? OrganizationId { get; set; }
public Guid? CipherId { get; set; } public Guid? CipherId { get; set; }
public ICollection<Guid> CipherIds { get; set; } public ICollection<Guid> CipherIds { get; set; }
public Guid? ActingUserId { get; set; }
} }
} }

View File

@ -1,12 +1,13 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Table;
namespace Bit.Core.Services namespace Bit.Core.Services
{ {
public interface IEventService public interface IEventService
{ {
Task LogUserEventAsync(Guid userId, EventType type); Task LogUserEventAsync(Guid userId, EventType type);
Task LogUserEventAsync(Guid userId, CurrentContext currentContext, EventType type); Task LogCipherEventAsync(Cipher cipher, EventType type);
} }
} }

View File

@ -22,6 +22,7 @@ namespace Bit.Core.Services
private readonly ICollectionCipherRepository _collectionCipherRepository; private readonly ICollectionCipherRepository _collectionCipherRepository;
private readonly IPushNotificationService _pushService; private readonly IPushNotificationService _pushService;
private readonly IAttachmentStorageService _attachmentStorageService; private readonly IAttachmentStorageService _attachmentStorageService;
private readonly IEventService _eventService;
public CipherService( public CipherService(
ICipherRepository cipherRepository, ICipherRepository cipherRepository,
@ -31,7 +32,8 @@ namespace Bit.Core.Services
IOrganizationUserRepository organizationUserRepository, IOrganizationUserRepository organizationUserRepository,
ICollectionCipherRepository collectionCipherRepository, ICollectionCipherRepository collectionCipherRepository,
IPushNotificationService pushService, IPushNotificationService pushService,
IAttachmentStorageService attachmentStorageService) IAttachmentStorageService attachmentStorageService,
IEventService eventService)
{ {
_cipherRepository = cipherRepository; _cipherRepository = cipherRepository;
_folderRepository = folderRepository; _folderRepository = folderRepository;
@ -41,6 +43,7 @@ namespace Bit.Core.Services
_collectionCipherRepository = collectionCipherRepository; _collectionCipherRepository = collectionCipherRepository;
_pushService = pushService; _pushService = pushService;
_attachmentStorageService = attachmentStorageService; _attachmentStorageService = attachmentStorageService;
_eventService = eventService;
} }
public async Task SaveAsync(Cipher cipher, Guid savingUserId, bool orgAdmin = false) public async Task SaveAsync(Cipher cipher, Guid savingUserId, bool orgAdmin = false)
@ -53,6 +56,7 @@ namespace Bit.Core.Services
if(cipher.Id == default(Guid)) if(cipher.Id == default(Guid))
{ {
await _cipherRepository.CreateAsync(cipher); await _cipherRepository.CreateAsync(cipher);
await _eventService.LogCipherEventAsync(cipher, Enums.EventType.Cipher_Created);
// push // push
await _pushService.PushSyncCipherCreateAsync(cipher); await _pushService.PushSyncCipherCreateAsync(cipher);
@ -61,6 +65,7 @@ namespace Bit.Core.Services
{ {
cipher.RevisionDate = DateTime.UtcNow; cipher.RevisionDate = DateTime.UtcNow;
await _cipherRepository.ReplaceAsync(cipher); await _cipherRepository.ReplaceAsync(cipher);
await _eventService.LogCipherEventAsync(cipher, Enums.EventType.Cipher_Updated);
// push // push
await _pushService.PushSyncCipherUpdateAsync(cipher); await _pushService.PushSyncCipherUpdateAsync(cipher);
@ -78,6 +83,7 @@ namespace Bit.Core.Services
if(cipher.Id == default(Guid)) if(cipher.Id == default(Guid))
{ {
await _cipherRepository.CreateAsync(cipher); await _cipherRepository.CreateAsync(cipher);
await _eventService.LogCipherEventAsync(cipher, Enums.EventType.Cipher_Created);
if(cipher.OrganizationId.HasValue) if(cipher.OrganizationId.HasValue)
{ {
@ -92,6 +98,7 @@ namespace Bit.Core.Services
{ {
cipher.RevisionDate = DateTime.UtcNow; cipher.RevisionDate = DateTime.UtcNow;
await _cipherRepository.ReplaceAsync(cipher); await _cipherRepository.ReplaceAsync(cipher);
await _eventService.LogCipherEventAsync(cipher, Enums.EventType.Cipher_Updated);
// push // push
await _pushService.PushSyncCipherUpdateAsync(cipher); await _pushService.PushSyncCipherUpdateAsync(cipher);
@ -159,6 +166,7 @@ namespace Bit.Core.Services
}; };
await _cipherRepository.UpdateAttachmentAsync(attachment); await _cipherRepository.UpdateAttachmentAsync(attachment);
await _eventService.LogCipherEventAsync(cipher, Enums.EventType.Cipher_AttachmentCreated);
cipher.AddAttachment(attachmentId, data); cipher.AddAttachment(attachmentId, data);
} }
catch catch
@ -222,6 +230,7 @@ namespace Bit.Core.Services
await _cipherRepository.DeleteAsync(cipher); await _cipherRepository.DeleteAsync(cipher);
await _attachmentStorageService.DeleteAttachmentsForCipherAsync(cipher.Id); await _attachmentStorageService.DeleteAttachmentsForCipherAsync(cipher.Id);
await _eventService.LogCipherEventAsync(cipher, Enums.EventType.Cipher_Deleted);
// push // push
await _pushService.PushSyncCipherDeleteAsync(cipher); await _pushService.PushSyncCipherDeleteAsync(cipher);
@ -249,6 +258,7 @@ namespace Bit.Core.Services
await _cipherRepository.DeleteAttachmentAsync(cipher.Id, attachmentId); await _cipherRepository.DeleteAttachmentAsync(cipher.Id, attachmentId);
cipher.DeleteAttachment(attachmentId); cipher.DeleteAttachment(attachmentId);
await _attachmentStorageService.DeleteAttachmentAsync(cipher.Id, attachmentId); await _attachmentStorageService.DeleteAttachmentAsync(cipher.Id, attachmentId);
await _eventService.LogCipherEventAsync(cipher, Enums.EventType.Cipher_AttachmentDeleted);
// push // push
await _pushService.PushSyncCipherUpdateAsync(cipher); await _pushService.PushSyncCipherUpdateAsync(cipher);
@ -344,6 +354,7 @@ namespace Bit.Core.Services
} }
updatedCipher = true; updatedCipher = true;
await _eventService.LogCipherEventAsync(cipher, Enums.EventType.Cipher_Shared);
if(hasAttachments) if(hasAttachments)
{ {
@ -413,6 +424,8 @@ namespace Bit.Core.Services
await _collectionCipherRepository.UpdateCollectionsAsync(cipher.Id, savingUserId, collectionIds); await _collectionCipherRepository.UpdateCollectionsAsync(cipher.Id, savingUserId, collectionIds);
} }
await _eventService.LogCipherEventAsync(cipher, Enums.EventType.Cipher_UpdatedCollections);
// push // push
await _pushService.PushSyncCipherUpdateAsync(cipher); await _pushService.PushSyncCipherUpdateAsync(cipher);
} }

View File

@ -6,6 +6,7 @@ using Bit.Core.Models.Data;
using System.Linq; using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.WindowsAzure.Storage.Table; using Microsoft.WindowsAzure.Storage.Table;
using Bit.Core.Models.Table;
namespace Bit.Core.Services namespace Bit.Core.Services
{ {
@ -13,24 +14,37 @@ namespace Bit.Core.Services
{ {
private readonly IEventRepository _eventRepository; private readonly IEventRepository _eventRepository;
private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly CurrentContext _currentContext;
private readonly GlobalSettings _globalSettings; private readonly GlobalSettings _globalSettings;
public EventService( public EventService(
IEventRepository eventRepository, IEventRepository eventRepository,
IOrganizationUserRepository organizationUserRepository, IOrganizationUserRepository organizationUserRepository,
CurrentContext currentContext,
GlobalSettings globalSettings) GlobalSettings globalSettings)
{ {
_eventRepository = eventRepository; _eventRepository = eventRepository;
_organizationUserRepository = organizationUserRepository; _organizationUserRepository = organizationUserRepository;
_currentContext = currentContext;
_globalSettings = globalSettings; _globalSettings = globalSettings;
} }
public async Task LogUserEventAsync(Guid userId, EventType type) public async Task LogUserEventAsync(Guid userId, EventType type)
{ {
var events = new List<ITableEntity> { new UserEvent(userId, type) }; var events = new List<ITableEntity> { new UserEvent(userId, type) };
IEnumerable<UserEvent> orgEvents;
if(_currentContext.UserId.HasValue)
{
orgEvents = _currentContext.Organizations.Select(o => new UserEvent(userId, o.Id, type));
}
else
{
var orgs = await _organizationUserRepository.GetManyByUserAsync(userId); var orgs = await _organizationUserRepository.GetManyByUserAsync(userId);
var orgEvents = orgs.Where(o => o.Status == OrganizationUserStatusType.Confirmed) orgEvents = orgs.Where(o => o.Status == OrganizationUserStatusType.Confirmed)
.Select(o => new UserEvent(userId, o.Id, type)); .Select(o => new UserEvent(userId, o.Id, type));
}
if(orgEvents.Any()) if(orgEvents.Any())
{ {
events.AddRange(orgEvents); events.AddRange(orgEvents);
@ -42,19 +56,15 @@ namespace Bit.Core.Services
} }
} }
public async Task LogUserEventAsync(Guid userId, CurrentContext currentContext, EventType type) public async Task LogCipherEventAsync(Cipher cipher, EventType type)
{ {
var events = new List<ITableEntity> { new UserEvent(userId, type) }; if(!cipher.OrganizationId.HasValue || (!_currentContext?.UserId.HasValue ?? true))
var orgEvents = currentContext.Organizations.Select(o => new UserEvent(userId, o.Id, type));
if(orgEvents.Any())
{ {
events.AddRange(orgEvents); return;
await _eventRepository.CreateManyAsync(events);
}
else
{
await _eventRepository.CreateAsync(events.First());
} }
var e = new CipherEvent(cipher, type, _currentContext?.UserId);
await _eventRepository.CreateAsync(e);
} }
} }
} }

View File

@ -90,8 +90,7 @@ namespace Bit.Core.Services
public Guid? GetProperUserId(ClaimsPrincipal principal) public Guid? GetProperUserId(ClaimsPrincipal principal)
{ {
Guid userIdGuid; if(!Guid.TryParse(GetUserId(principal), out var userIdGuid))
if(!Guid.TryParse(GetUserId(principal), out userIdGuid))
{ {
return null; return null;
} }
@ -107,8 +106,7 @@ namespace Bit.Core.Services
return _currentContext.User; return _currentContext.User;
} }
Guid userIdGuid; if(!Guid.TryParse(userId, out var userIdGuid))
if(!Guid.TryParse(userId, out userIdGuid))
{ {
return null; return null;
} }
@ -425,7 +423,7 @@ namespace Bit.Core.Services
user.Key = key; user.Key = key;
await _userRepository.ReplaceAsync(user); await _userRepository.ReplaceAsync(user);
await _eventService.LogUserEventAsync(user.Id, _currentContext, EventType.User_ChangedPassword); await _eventService.LogUserEventAsync(user.Id, EventType.User_ChangedPassword);
return IdentityResult.Success; return IdentityResult.Success;
} }
@ -503,7 +501,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, _currentContext, EventType.User_Enabled2fa); await _eventService.LogUserEventAsync(user.Id, EventType.User_Enabled2fa);
} }
public async Task DisableTwoFactorProviderAsync(User user, TwoFactorProviderType type) public async Task DisableTwoFactorProviderAsync(User user, TwoFactorProviderType type)
@ -517,7 +515,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, _currentContext, EventType.User_Disabled2fa); 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)
@ -542,7 +540,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, _currentContext, EventType.User_Recovered2fa); await _eventService.LogUserEventAsync(user.Id, EventType.User_Recovered2fa);
return true; return true;
} }

View File

@ -1,17 +1,18 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Table;
namespace Bit.Core.Services namespace Bit.Core.Services
{ {
public class NoopEventService : IEventService public class NoopEventService : IEventService
{ {
public Task LogUserEventAsync(Guid userId, EventType type) public Task LogCipherEventAsync(Cipher cipher, EventType type)
{ {
return Task.FromResult(0); return Task.FromResult(0);
} }
public Task LogUserEventAsync(Guid userId, CurrentContext currentContext, EventType type) public Task LogUserEventAsync(Guid userId, EventType type)
{ {
return Task.FromResult(0); return Task.FromResult(0);
} }

View File

@ -54,13 +54,13 @@ namespace Bit.Core.Utilities
public static void AddBaseServices(this IServiceCollection services) public static void AddBaseServices(this IServiceCollection services)
{ {
services.AddSingleton<ICipherService, CipherService>(); services.AddScoped<ICipherService, CipherService>();
services.AddScoped<IUserService, UserService>(); services.AddScoped<IUserService, UserService>();
services.AddSingleton<IDeviceService, DeviceService>(); services.AddSingleton<IDeviceService, DeviceService>();
services.AddSingleton<IOrganizationService, OrganizationService>(); services.AddSingleton<IOrganizationService, OrganizationService>();
services.AddSingleton<ICollectionService, CollectionService>(); services.AddSingleton<ICollectionService, CollectionService>();
services.AddSingleton<IGroupService, GroupService>(); services.AddSingleton<IGroupService, GroupService>();
services.AddSingleton<Services.IEventService, EventService>(); services.AddScoped<Services.IEventService, EventService>();
} }
public static void AddDefaultServices(this IServiceCollection services, GlobalSettings globalSettings) public static void AddDefaultServices(this IServiceCollection services, GlobalSettings globalSettings)