1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-09 20:03:47 -05:00

[AC-2972] AC Team ownership: Events (#4647)

* Move Event domain under AC Team ownership
This commit is contained in:
Thomas Rittson
2024-08-19 08:00:17 +10:00
committed by GitHub
parent faa9afbe5e
commit 0230013b20
20 changed files with 2 additions and 0 deletions

View File

@ -0,0 +1,65 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Utilities;
#nullable enable
namespace Bit.Core.Entities;
public class Event : ITableObject<Guid>, IEvent
{
public Event() { }
public Event(IEvent e)
{
Date = e.Date;
Type = e.Type;
UserId = e.UserId;
OrganizationId = e.OrganizationId;
ProviderId = e.ProviderId;
CipherId = e.CipherId;
CollectionId = e.CollectionId;
PolicyId = e.PolicyId;
GroupId = e.GroupId;
OrganizationUserId = e.OrganizationUserId;
InstallationId = e.InstallationId;
ProviderUserId = e.ProviderUserId;
ProviderOrganizationId = e.ProviderOrganizationId;
DeviceType = e.DeviceType;
IpAddress = e.IpAddress;
ActingUserId = e.ActingUserId;
SystemUser = e.SystemUser;
DomainName = e.DomainName;
SecretId = e.SecretId;
ServiceAccountId = e.ServiceAccountId;
}
public Guid Id { get; set; }
public DateTime Date { get; set; }
public EventType Type { get; set; }
public Guid? UserId { get; set; }
public Guid? OrganizationId { get; set; }
public Guid? InstallationId { get; set; }
public Guid? ProviderId { get; set; }
public Guid? CipherId { get; set; }
public Guid? CollectionId { get; set; }
public Guid? PolicyId { get; set; }
public Guid? GroupId { get; set; }
public Guid? OrganizationUserId { get; set; }
public Guid? ProviderUserId { get; set; }
public Guid? ProviderOrganizationId { get; set; }
public DeviceType? DeviceType { get; set; }
[MaxLength(50)]
public string? IpAddress { get; set; }
public Guid? ActingUserId { get; set; }
public EventSystemUser? SystemUser { get; set; }
public string? DomainName { get; set; }
public Guid? SecretId { get; set; }
public Guid? ServiceAccountId { get; set; }
public void SetNewId()
{
Id = CoreHelpers.GenerateComb();
}
}

View File

@ -0,0 +1,7 @@
namespace Bit.Core.Enums;
public enum EventSystemUser : byte
{
SCIM = 1,
DomainVerification = 2
}

View File

@ -0,0 +1,91 @@
namespace Bit.Core.Enums;
// Increment by 100 for each new set of events
public enum EventType : int
{
User_LoggedIn = 1000,
User_ChangedPassword = 1001,
User_Updated2fa = 1002,
User_Disabled2fa = 1003,
User_Recovered2fa = 1004,
User_FailedLogIn = 1005,
User_FailedLogIn2fa = 1006,
User_ClientExportedVault = 1007,
User_UpdatedTempPassword = 1008,
User_MigratedKeyToKeyConnector = 1009,
User_RequestedDeviceApproval = 1010,
User_TdeOffboardingPasswordSet = 1011,
Cipher_Created = 1100,
Cipher_Updated = 1101,
Cipher_Deleted = 1102,
Cipher_AttachmentCreated = 1103,
Cipher_AttachmentDeleted = 1104,
Cipher_Shared = 1105,
Cipher_UpdatedCollections = 1106,
Cipher_ClientViewed = 1107,
Cipher_ClientToggledPasswordVisible = 1108,
Cipher_ClientToggledHiddenFieldVisible = 1109,
Cipher_ClientToggledCardCodeVisible = 1110,
Cipher_ClientCopiedPassword = 1111,
Cipher_ClientCopiedHiddenField = 1112,
Cipher_ClientCopiedCardCode = 1113,
Cipher_ClientAutofilled = 1114,
Cipher_SoftDeleted = 1115,
Cipher_Restored = 1116,
Cipher_ClientToggledCardNumberVisible = 1117,
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,
OrganizationUser_UpdatedGroups = 1504,
OrganizationUser_UnlinkedSso = 1505,
OrganizationUser_ResetPassword_Enroll = 1506,
OrganizationUser_ResetPassword_Withdraw = 1507,
OrganizationUser_AdminResetPassword = 1508,
OrganizationUser_ResetSsoLink = 1509,
OrganizationUser_FirstSsoLogin = 1510,
OrganizationUser_Revoked = 1511,
OrganizationUser_Restored = 1512,
OrganizationUser_ApprovedAuthRequest = 1513,
OrganizationUser_RejectedAuthRequest = 1514,
Organization_Updated = 1600,
Organization_PurgedVault = 1601,
Organization_ClientExportedVault = 1602,
Organization_VaultAccessed = 1603,
Organization_EnabledSso = 1604,
Organization_DisabledSso = 1605,
Organization_EnabledKeyConnector = 1606,
Organization_DisabledKeyConnector = 1607,
Organization_SponsorshipsSynced = 1608,
Organization_CollectionManagement_Updated = 1609,
Policy_Updated = 1700,
ProviderUser_Invited = 1800,
ProviderUser_Confirmed = 1801,
ProviderUser_Updated = 1802,
ProviderUser_Removed = 1803,
ProviderOrganization_Created = 1900,
ProviderOrganization_Added = 1901,
ProviderOrganization_Removed = 1902,
ProviderOrganization_VaultAccessed = 1903,
OrganizationDomain_Added = 2000,
OrganizationDomain_Removed = 2001,
OrganizationDomain_Verified = 2002,
OrganizationDomain_NotVerified = 2003,
Secret_Retrieved = 2100,
}

View File

@ -0,0 +1,38 @@
using Bit.Core.Context;
using Bit.Core.Enums;
namespace Bit.Core.Models.Data;
public class EventMessage : IEvent
{
public EventMessage() { }
public EventMessage(ICurrentContext currentContext)
: base()
{
IpAddress = currentContext.IpAddress;
DeviceType = currentContext.DeviceType;
}
public DateTime Date { get; set; }
public EventType Type { get; set; }
public Guid? UserId { get; set; }
public Guid? OrganizationId { get; set; }
public Guid? InstallationId { get; set; }
public Guid? ProviderId { get; set; }
public Guid? CipherId { get; set; }
public Guid? CollectionId { get; set; }
public Guid? GroupId { get; set; }
public Guid? PolicyId { get; set; }
public Guid? OrganizationUserId { get; set; }
public Guid? ProviderUserId { get; set; }
public Guid? ProviderOrganizationId { get; set; }
public Guid? ActingUserId { get; set; }
public DeviceType? DeviceType { get; set; }
public string IpAddress { get; set; }
public Guid? IdempotencyId { get; private set; } = Guid.NewGuid();
public EventSystemUser? SystemUser { get; set; }
public string DomainName { get; set; }
public Guid? SecretId { get; set; }
public Guid? ServiceAccountId { get; set; }
}

View File

@ -0,0 +1,235 @@
using Azure;
using Azure.Data.Tables;
using Bit.Core.Enums;
using Bit.Core.Utilities;
namespace Bit.Core.Models.Data;
// used solely for interaction with Azure Table Storage
public class AzureEvent : ITableEntity
{
public string PartitionKey { get; set; }
public string RowKey { get; set; }
public DateTimeOffset? Timestamp { get; set; }
public ETag ETag { get; set; }
public DateTime Date { get; set; }
public int Type { get; set; }
public Guid? UserId { get; set; }
public Guid? OrganizationId { get; set; }
public Guid? InstallationId { get; set; }
public Guid? ProviderId { get; set; }
public Guid? CipherId { get; set; }
public Guid? CollectionId { get; set; }
public Guid? PolicyId { get; set; }
public Guid? GroupId { get; set; }
public Guid? OrganizationUserId { get; set; }
public Guid? ProviderUserId { get; set; }
public Guid? ProviderOrganizationId { get; set; }
public int? DeviceType { get; set; }
public string IpAddress { get; set; }
public Guid? ActingUserId { get; set; }
public int? SystemUser { get; set; }
public string DomainName { get; set; }
public Guid? SecretId { get; set; }
public Guid? ServiceAccountId { get; set; }
public EventTableEntity ToEventTableEntity()
{
return new EventTableEntity
{
PartitionKey = PartitionKey,
RowKey = RowKey,
Timestamp = Timestamp,
ETag = ETag,
Date = Date,
Type = (EventType)Type,
UserId = UserId,
OrganizationId = OrganizationId,
InstallationId = InstallationId,
ProviderId = ProviderId,
CipherId = CipherId,
CollectionId = CollectionId,
PolicyId = PolicyId,
GroupId = GroupId,
OrganizationUserId = OrganizationUserId,
ProviderUserId = ProviderUserId,
ProviderOrganizationId = ProviderOrganizationId,
DeviceType = DeviceType.HasValue ? (DeviceType)DeviceType.Value : null,
IpAddress = IpAddress,
ActingUserId = ActingUserId,
SystemUser = SystemUser.HasValue ? (EventSystemUser)SystemUser.Value : null,
DomainName = DomainName,
SecretId = SecretId,
ServiceAccountId = ServiceAccountId
};
}
}
public class EventTableEntity : IEvent
{
public EventTableEntity() { }
private EventTableEntity(IEvent e)
{
Date = e.Date;
Type = e.Type;
UserId = e.UserId;
OrganizationId = e.OrganizationId;
InstallationId = e.InstallationId;
ProviderId = e.ProviderId;
CipherId = e.CipherId;
CollectionId = e.CollectionId;
PolicyId = e.PolicyId;
GroupId = e.GroupId;
OrganizationUserId = e.OrganizationUserId;
ProviderUserId = e.ProviderUserId;
ProviderOrganizationId = e.ProviderOrganizationId;
DeviceType = e.DeviceType;
IpAddress = e.IpAddress;
ActingUserId = e.ActingUserId;
SystemUser = e.SystemUser;
DomainName = e.DomainName;
SecretId = e.SecretId;
ServiceAccountId = e.ServiceAccountId;
}
public string PartitionKey { get; set; }
public string RowKey { get; set; }
public DateTimeOffset? Timestamp { get; set; }
public ETag ETag { get; set; }
public DateTime Date { get; set; }
public EventType Type { get; set; }
public Guid? UserId { get; set; }
public Guid? OrganizationId { get; set; }
public Guid? InstallationId { get; set; }
public Guid? ProviderId { get; set; }
public Guid? CipherId { get; set; }
public Guid? CollectionId { get; set; }
public Guid? PolicyId { get; set; }
public Guid? GroupId { get; set; }
public Guid? OrganizationUserId { get; set; }
public Guid? ProviderUserId { get; set; }
public Guid? ProviderOrganizationId { get; set; }
public DeviceType? DeviceType { get; set; }
public string IpAddress { get; set; }
public Guid? ActingUserId { get; set; }
public EventSystemUser? SystemUser { get; set; }
public string DomainName { get; set; }
public Guid? SecretId { get; set; }
public Guid? ServiceAccountId { get; set; }
public AzureEvent ToAzureEvent()
{
return new AzureEvent
{
PartitionKey = PartitionKey,
RowKey = RowKey,
Timestamp = Timestamp,
ETag = ETag,
Date = Date,
Type = (int)Type,
UserId = UserId,
OrganizationId = OrganizationId,
InstallationId = InstallationId,
ProviderId = ProviderId,
CipherId = CipherId,
CollectionId = CollectionId,
PolicyId = PolicyId,
GroupId = GroupId,
OrganizationUserId = OrganizationUserId,
ProviderUserId = ProviderUserId,
ProviderOrganizationId = ProviderOrganizationId,
DeviceType = DeviceType.HasValue ? (int)DeviceType.Value : null,
IpAddress = IpAddress,
ActingUserId = ActingUserId,
SystemUser = SystemUser.HasValue ? (int)SystemUser.Value : null,
DomainName = DomainName,
SecretId = SecretId,
ServiceAccountId = ServiceAccountId
};
}
public static List<EventTableEntity> IndexEvent(EventMessage e)
{
var uniquifier = e.IdempotencyId.GetValueOrDefault(Guid.NewGuid());
var pKey = GetPartitionKey(e);
var dateKey = CoreHelpers.DateTimeToTableStorageKey(e.Date);
var entities = new List<EventTableEntity>
{
new EventTableEntity(e)
{
PartitionKey = pKey,
RowKey = $"Date={dateKey}__Uniquifier={uniquifier}"
}
};
if (e.OrganizationId.HasValue && e.ActingUserId.HasValue)
{
entities.Add(new EventTableEntity(e)
{
PartitionKey = pKey,
RowKey = $"ActingUserId={e.ActingUserId}__Date={dateKey}__Uniquifier={uniquifier}"
});
}
if (!e.OrganizationId.HasValue && e.ProviderId.HasValue && e.ActingUserId.HasValue)
{
entities.Add(new EventTableEntity(e)
{
PartitionKey = pKey,
RowKey = $"ActingUserId={e.ActingUserId}__Date={dateKey}__Uniquifier={uniquifier}"
});
}
if (e.CipherId.HasValue)
{
entities.Add(new EventTableEntity(e)
{
PartitionKey = pKey,
RowKey = $"CipherId={e.CipherId}__Date={dateKey}__Uniquifier={uniquifier}"
});
}
if (e.OrganizationId.HasValue && e.ServiceAccountId.HasValue)
{
entities.Add(new EventTableEntity(e)
{
PartitionKey = pKey,
RowKey = $"ServiceAccountId={e.ServiceAccountId}__Date={dateKey}__Uniquifier={uniquifier}"
});
}
if (e.SecretId.HasValue)
{
entities.Add(new EventTableEntity(e)
{
PartitionKey = pKey,
RowKey = $"SecretId={e.CipherId}__Date={dateKey}__Uniquifier={uniquifier}"
});
}
return entities;
}
private static string GetPartitionKey(EventMessage e)
{
if (e.OrganizationId.HasValue)
{
return $"OrganizationId={e.OrganizationId}";
}
if (e.ProviderId.HasValue)
{
return $"ProviderId={e.ProviderId}";
}
return $"UserId={e.UserId}";
}
}

View File

@ -0,0 +1,27 @@
using Bit.Core.Enums;
namespace Bit.Core.Models.Data;
public interface IEvent
{
EventType Type { get; set; }
Guid? UserId { get; set; }
Guid? OrganizationId { get; set; }
Guid? InstallationId { get; set; }
Guid? ProviderId { get; set; }
Guid? CipherId { get; set; }
Guid? CollectionId { get; set; }
Guid? GroupId { get; set; }
Guid? PolicyId { get; set; }
Guid? OrganizationUserId { get; set; }
Guid? ProviderUserId { get; set; }
Guid? ProviderOrganizationId { get; set; }
Guid? ActingUserId { get; set; }
DeviceType? DeviceType { get; set; }
string IpAddress { get; set; }
DateTime Date { get; set; }
EventSystemUser? SystemUser { get; set; }
string DomainName { get; set; }
Guid? SecretId { get; set; }
Guid? ServiceAccountId { get; set; }
}

View File

@ -0,0 +1,26 @@
using Bit.Core.Models.Data;
using Bit.Core.Vault.Entities;
#nullable enable
namespace Bit.Core.Repositories;
public interface IEventRepository
{
Task<PagedResult<IEvent>> GetManyByUserAsync(Guid userId, DateTime startDate, DateTime endDate,
PageOptions pageOptions);
Task<PagedResult<IEvent>> GetManyByOrganizationAsync(Guid organizationId, DateTime startDate, DateTime endDate,
PageOptions pageOptions);
Task<PagedResult<IEvent>> GetManyByOrganizationActingUserAsync(Guid organizationId, Guid actingUserId,
DateTime startDate, DateTime endDate, PageOptions pageOptions);
Task<PagedResult<IEvent>> GetManyByProviderAsync(Guid providerId, DateTime startDate, DateTime endDate,
PageOptions pageOptions);
Task<PagedResult<IEvent>> GetManyByProviderActingUserAsync(Guid providerId, Guid actingUserId,
DateTime startDate, DateTime endDate, PageOptions pageOptions);
Task<PagedResult<IEvent>> GetManyByCipherAsync(Cipher cipher, DateTime startDate, DateTime endDate,
PageOptions pageOptions);
Task CreateAsync(IEvent e);
Task CreateManyAsync(IEnumerable<IEvent> e);
Task<PagedResult<IEvent>> GetManyByOrganizationServiceAccountAsync(Guid organizationId, Guid serviceAccountId,
DateTime startDate, DateTime endDate, PageOptions pageOptions);
}

View File

@ -0,0 +1,160 @@
using Azure.Data.Tables;
using Bit.Core.Models.Data;
using Bit.Core.Settings;
using Bit.Core.Utilities;
using Bit.Core.Vault.Entities;
#nullable enable
namespace Bit.Core.Repositories.TableStorage;
public class EventRepository : IEventRepository
{
private readonly TableClient _tableClient;
public EventRepository(GlobalSettings globalSettings)
: this(globalSettings.Events.ConnectionString)
{ }
public EventRepository(string storageConnectionString)
{
var tableClient = new TableServiceClient(storageConnectionString);
_tableClient = tableClient.GetTableClient("event");
}
public async Task<PagedResult<IEvent>> GetManyByUserAsync(Guid userId, DateTime startDate, DateTime endDate,
PageOptions pageOptions)
{
return await GetManyAsync($"UserId={userId}", "Date={{0}}", startDate, endDate, pageOptions);
}
public async Task<PagedResult<IEvent>> GetManyByOrganizationAsync(Guid organizationId,
DateTime startDate, DateTime endDate, PageOptions pageOptions)
{
return await GetManyAsync($"OrganizationId={organizationId}", "Date={0}", startDate, endDate, pageOptions);
}
public async Task<PagedResult<IEvent>> GetManyByOrganizationActingUserAsync(Guid organizationId, Guid actingUserId,
DateTime startDate, DateTime endDate, PageOptions pageOptions)
{
return await GetManyAsync($"OrganizationId={organizationId}",
$"ActingUserId={actingUserId}__Date={{0}}", startDate, endDate, pageOptions);
}
public async Task<PagedResult<IEvent>> GetManyByProviderAsync(Guid providerId,
DateTime startDate, DateTime endDate, PageOptions pageOptions)
{
return await GetManyAsync($"ProviderId={providerId}", "Date={0}", startDate, endDate, pageOptions);
}
public async Task<PagedResult<IEvent>> GetManyByProviderActingUserAsync(Guid providerId, Guid actingUserId,
DateTime startDate, DateTime endDate, PageOptions pageOptions)
{
return await GetManyAsync($"ProviderId={providerId}",
$"ActingUserId={actingUserId}__Date={{0}}", startDate, endDate, pageOptions);
}
public async Task<PagedResult<IEvent>> GetManyByCipherAsync(Cipher cipher, DateTime startDate, DateTime endDate,
PageOptions pageOptions)
{
var partitionKey = cipher.OrganizationId.HasValue ?
$"OrganizationId={cipher.OrganizationId}" : $"UserId={cipher.UserId}";
return await GetManyAsync(partitionKey, $"CipherId={cipher.Id}__Date={{0}}", startDate, endDate, pageOptions);
}
public async Task<PagedResult<IEvent>> GetManyByOrganizationServiceAccountAsync(Guid organizationId,
Guid serviceAccountId, DateTime startDate, DateTime endDate, PageOptions pageOptions)
{
return await GetManyAsync($"OrganizationId={organizationId}",
$"ServiceAccountId={serviceAccountId}__Date={{0}}", startDate, endDate, pageOptions);
}
public async Task CreateAsync(IEvent e)
{
if (!(e is EventTableEntity entity))
{
throw new ArgumentException(nameof(e));
}
await CreateEventAsync(entity);
}
public async Task CreateManyAsync(IEnumerable<IEvent>? e)
{
if (e is null || !e.Any())
{
return;
}
if (!e.Skip(1).Any())
{
await CreateAsync(e.First());
return;
}
var entities = e.OfType<EventTableEntity>();
var entityGroups = entities.GroupBy(ent => ent.PartitionKey);
foreach (var group in entityGroups)
{
var groupEntities = group.ToList();
if (groupEntities.Count == 1)
{
await CreateEventAsync(groupEntities.First());
continue;
}
// A batch insert can only contain 100 entities at a time
var iterations = groupEntities.Count / 100;
for (var i = 0; i <= iterations; i++)
{
var batch = new List<TableTransactionAction>();
var batchEntities = groupEntities.Skip(i * 100).Take(100);
if (!batchEntities.Any())
{
break;
}
foreach (var entity in batchEntities)
{
batch.Add(new TableTransactionAction(TableTransactionActionType.Add,
entity.ToAzureEvent()));
}
await _tableClient.SubmitTransactionAsync(batch);
}
}
}
public async Task<PagedResult<IEvent>> GetManyAsync(string partitionKey, string rowKey,
DateTime startDate, DateTime endDate, PageOptions pageOptions)
{
var start = CoreHelpers.DateTimeToTableStorageKey(startDate);
var end = CoreHelpers.DateTimeToTableStorageKey(endDate);
var filter = MakeFilter(partitionKey, string.Format(rowKey, start), string.Format(rowKey, end));
var result = new PagedResult<IEvent>();
var query = _tableClient.QueryAsync<AzureEvent>(filter, pageOptions.PageSize);
await using (var enumerator = query.AsPages(pageOptions.ContinuationToken,
pageOptions.PageSize).GetAsyncEnumerator())
{
await enumerator.MoveNextAsync();
result.ContinuationToken = enumerator.Current.ContinuationToken;
result.Data.AddRange(enumerator.Current.Values.Select(e => e.ToEventTableEntity()));
}
return result;
}
private async Task CreateEventAsync(EventTableEntity entity)
{
await _tableClient.UpsertEntityAsync(entity.ToAzureEvent());
}
private string MakeFilter(string partitionKey, string rowStart, string rowEnd)
{
return $"PartitionKey eq '{partitionKey}' and RowKey le '{rowStart}' and RowKey ge '{rowEnd}'";
}
}