1
0
mirror of https://github.com/bitwarden/server.git synced 2024-12-11 15:17:44 +01:00
bitwarden-server/src/Infrastructure.Dapper/Repositories/EventRepository.cs

227 lines
10 KiB
C#
Raw Normal View History

using System.Data;
2017-12-12 20:22:22 +01:00
using System.Data.SqlClient;
using Bit.Core.Entities;
2021-12-16 15:35:09 +01:00
using Bit.Core.Models.Data;
using Bit.Core.Repositories;
2021-12-16 15:35:09 +01:00
using Bit.Core.Settings;
2017-12-19 05:15:16 +01:00
using Dapper;
2017-12-12 20:22:22 +01:00
namespace Bit.Infrastructure.Dapper.Repositories
2017-12-12 20:22:22 +01:00
{
public class EventRepository : Repository<Event, Guid>, IEventRepository
{
public EventRepository(GlobalSettings globalSettings)
: this(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString)
2017-12-12 20:22:22 +01:00
{ }
public EventRepository(string connectionString, string readOnlyConnectionString)
: base(connectionString, readOnlyConnectionString)
2017-12-12 20:22:22 +01:00
{ }
2017-12-19 05:15:16 +01:00
public async Task<PagedResult<IEvent>> GetManyByUserAsync(Guid userId, DateTime startDate, DateTime endDate,
2017-12-15 21:23:57 +01:00
PageOptions pageOptions)
2017-12-12 20:22:22 +01:00
{
2017-12-19 05:15:16 +01:00
return await GetManyAsync($"[{Schema}].[Event_ReadPageByUserId]",
new Dictionary<string, object>
{
["@UserId"] = userId
}, startDate, endDate, pageOptions);
2017-12-12 20:22:22 +01:00
}
2017-12-19 05:15:16 +01:00
public async Task<PagedResult<IEvent>> GetManyByOrganizationAsync(Guid organizationId,
2017-12-15 21:23:57 +01:00
DateTime startDate, DateTime endDate, PageOptions pageOptions)
2017-12-14 18:33:50 +01:00
{
2017-12-19 05:15:16 +01:00
return await GetManyAsync($"[{Schema}].[Event_ReadPageByOrganizationId]",
new Dictionary<string, object>
{
["@OrganizationId"] = organizationId
}, startDate, endDate, pageOptions);
2017-12-15 21:50:50 +01:00
}
2017-12-19 05:15:16 +01:00
public async Task<PagedResult<IEvent>> GetManyByOrganizationActingUserAsync(Guid organizationId, Guid actingUserId,
2017-12-15 21:50:50 +01:00
DateTime startDate, DateTime endDate, PageOptions pageOptions)
{
2017-12-19 05:15:16 +01:00
return await GetManyAsync($"[{Schema}].[Event_ReadPageByOrganizationIdActingUserId]",
new Dictionary<string, object>
{
["@OrganizationId"] = organizationId,
["@ActingUserId"] = actingUserId
}, startDate, endDate, pageOptions);
2017-12-15 21:50:50 +01:00
}
public async Task<PagedResult<IEvent>> GetManyByProviderAsync(Guid providerId,
DateTime startDate, DateTime endDate, PageOptions pageOptions)
{
return await GetManyAsync($"[{Schema}].[Event_ReadPageByProviderId]",
new Dictionary<string, object>
{
["@ProviderId"] = providerId
}, startDate, endDate, pageOptions);
}
public async Task<PagedResult<IEvent>> GetManyByProviderActingUserAsync(Guid providerId, Guid actingUserId,
DateTime startDate, DateTime endDate, PageOptions pageOptions)
{
return await GetManyAsync($"[{Schema}].[Event_ReadPageByProviderIdActingUserId]",
new Dictionary<string, object>
{
["@ProviderId"] = providerId,
["@ActingUserId"] = actingUserId
}, startDate, endDate, pageOptions);
}
2017-12-19 05:15:16 +01:00
public async Task<PagedResult<IEvent>> GetManyByCipherAsync(Cipher cipher, DateTime startDate, DateTime endDate,
2017-12-15 21:50:50 +01:00
PageOptions pageOptions)
{
2017-12-19 05:15:16 +01:00
return await GetManyAsync($"[{Schema}].[Event_ReadPageByCipherId]",
new Dictionary<string, object>
{
["@OrganizationId"] = cipher.OrganizationId,
["@UserId"] = cipher.UserId,
["@CipherId"] = cipher.Id
}, startDate, endDate, pageOptions);
2017-12-14 18:33:50 +01:00
}
2017-12-12 20:22:22 +01:00
public async Task CreateAsync(IEvent e)
{
if (!(e is Event ev))
2017-12-12 20:22:22 +01:00
{
ev = new Event(e);
}
await base.CreateAsync(ev);
}
Support large organization sync (#1311) * Increase organization max seat size from 30k to 2b (#1274) * Increase organization max seat size from 30k to 2b * PR review. Do not modify unless state matches expected * Organization sync simultaneous event reporting (#1275) * Split up azure messages according to max size * Allow simultaneous login of organization user events * Early resolve small event lists * Clarify logic Co-authored-by: Chad Scharf <3904944+cscharf@users.noreply.github.com> * Improve readability This comes at the cost of multiple serializations, but the improvement in wire-time should more than make up for this on message where serialization time matters Co-authored-by: Chad Scharf <3904944+cscharf@users.noreply.github.com> * Queue emails (#1286) * Extract common Azure queue methods * Do not use internal entity framework namespace * Prefer IEnumerable to IList unless needed All of these implementations were just using `Count == 1`, which is easily replicated. This will be used when abstracting Azure queues * Add model for azure queue message * Abstract Azure queue for reuse * Creat service to enqueue mail messages for later processing Azure queue mail service uses Azure queues. Blocking just blocks until all the work is done -- This is how emailing works today * Provide mail queue service to DI * Queue organization invite emails for later processing All emails can later be added to this queue * Create Admin hosted service to process enqueued mail messages * Prefer constructors to static generators * Mass delete organization users (#1287) * Add delete many to Organization Users * Correct formatting * Remove erroneous migration * Clarify parameter name * Formatting fixes * Simplify bump account revision sproc * Formatting fixes * Match file names to objects * Indicate if large import is expected * Early pull all existing users we were planning on inviting (#1290) * Early pull all existing users we were planning on inviting * Improve sproc name * Batch upsert org users (#1289) * Add UpsertMany sprocs to OrganizationUser * Add method to create TVPs from any object. Uses DbOrder attribute to generate. Sproc will fail unless TVP column order matches that of the db type * Combine migrations * Correct formatting * Include sql objects in sql project * Keep consisten parameter names * Batch deletes for performance * Correct formatting * consolidate migrations * Use batch methods in OrganizationImport * Declare @BatchSize * Transaction names limited to 32 chars Drop sproc before creating it if it exists * Update import tests * Allow for more users in org upgrades * Fix formatting * Improve class hierarchy structure * Use name tuple types * Fix formatting * Front load all reflection * Format constructor * Simplify ToTvp as class-specific extension Co-authored-by: Chad Scharf <3904944+cscharf@users.noreply.github.com>
2021-05-17 16:43:02 +02:00
public async Task CreateManyAsync(IEnumerable<IEvent> entities)
2017-12-12 20:22:22 +01:00
{
if (!entities?.Any() ?? true)
2017-12-12 20:22:22 +01:00
{
return;
}
Support large organization sync (#1311) * Increase organization max seat size from 30k to 2b (#1274) * Increase organization max seat size from 30k to 2b * PR review. Do not modify unless state matches expected * Organization sync simultaneous event reporting (#1275) * Split up azure messages according to max size * Allow simultaneous login of organization user events * Early resolve small event lists * Clarify logic Co-authored-by: Chad Scharf <3904944+cscharf@users.noreply.github.com> * Improve readability This comes at the cost of multiple serializations, but the improvement in wire-time should more than make up for this on message where serialization time matters Co-authored-by: Chad Scharf <3904944+cscharf@users.noreply.github.com> * Queue emails (#1286) * Extract common Azure queue methods * Do not use internal entity framework namespace * Prefer IEnumerable to IList unless needed All of these implementations were just using `Count == 1`, which is easily replicated. This will be used when abstracting Azure queues * Add model for azure queue message * Abstract Azure queue for reuse * Creat service to enqueue mail messages for later processing Azure queue mail service uses Azure queues. Blocking just blocks until all the work is done -- This is how emailing works today * Provide mail queue service to DI * Queue organization invite emails for later processing All emails can later be added to this queue * Create Admin hosted service to process enqueued mail messages * Prefer constructors to static generators * Mass delete organization users (#1287) * Add delete many to Organization Users * Correct formatting * Remove erroneous migration * Clarify parameter name * Formatting fixes * Simplify bump account revision sproc * Formatting fixes * Match file names to objects * Indicate if large import is expected * Early pull all existing users we were planning on inviting (#1290) * Early pull all existing users we were planning on inviting * Improve sproc name * Batch upsert org users (#1289) * Add UpsertMany sprocs to OrganizationUser * Add method to create TVPs from any object. Uses DbOrder attribute to generate. Sproc will fail unless TVP column order matches that of the db type * Combine migrations * Correct formatting * Include sql objects in sql project * Keep consisten parameter names * Batch deletes for performance * Correct formatting * consolidate migrations * Use batch methods in OrganizationImport * Declare @BatchSize * Transaction names limited to 32 chars Drop sproc before creating it if it exists * Update import tests * Allow for more users in org upgrades * Fix formatting * Improve class hierarchy structure * Use name tuple types * Fix formatting * Front load all reflection * Format constructor * Simplify ToTvp as class-specific extension Co-authored-by: Chad Scharf <3904944+cscharf@users.noreply.github.com>
2021-05-17 16:43:02 +02:00
if (!entities.Skip(1).Any())
2017-12-14 23:23:46 +01:00
{
await CreateAsync(entities.First());
return;
}
using (var connection = new SqlConnection(ConnectionString))
2017-12-12 20:22:22 +01:00
{
connection.Open();
using (var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity, null))
2017-12-12 20:22:22 +01:00
{
bulkCopy.DestinationTableName = "[dbo].[Event]";
2020-02-28 04:44:49 +01:00
var dataTable = BuildEventsTable(bulkCopy, entities.Select(e => e is Event ? e as Event : new Event(e)));
2017-12-12 20:22:22 +01:00
await bulkCopy.WriteToServerAsync(dataTable);
}
}
}
2017-12-19 05:15:16 +01:00
private async Task<PagedResult<IEvent>> GetManyAsync(string sprocName,
IDictionary<string, object> sprocParams, DateTime startDate, DateTime endDate, PageOptions pageOptions)
{
DateTime? beforeDate = null;
if (!string.IsNullOrWhiteSpace(pageOptions.ContinuationToken) &&
2017-12-19 05:15:16 +01:00
long.TryParse(pageOptions.ContinuationToken, out var binaryDate))
{
beforeDate = DateTime.SpecifyKind(DateTime.FromBinary(binaryDate), DateTimeKind.Utc);
}
var parameters = new DynamicParameters(sprocParams);
parameters.Add("@PageSize", pageOptions.PageSize, DbType.Int32);
// Explicitly use DbType.DateTime2 for proper precision.
// ref: https://github.com/StackExchange/Dapper/issues/229
parameters.Add("@StartDate", startDate.ToUniversalTime(), DbType.DateTime2, null, 7);
parameters.Add("@EndDate", endDate.ToUniversalTime(), DbType.DateTime2, null, 7);
parameters.Add("@BeforeDate", beforeDate, DbType.DateTime2, null, 7);
using (var connection = new SqlConnection(ConnectionString))
2017-12-19 05:15:16 +01:00
{
var events = (await connection.QueryAsync<Event>(sprocName, parameters,
commandType: CommandType.StoredProcedure)).ToList();
var result = new PagedResult<IEvent>();
if (events.Any() && events.Count >= pageOptions.PageSize)
2017-12-19 05:15:16 +01:00
{
result.ContinuationToken = events.Last().Date.ToBinary().ToString();
}
result.Data.AddRange(events);
return result;
}
}
2020-02-28 04:44:49 +01:00
private DataTable BuildEventsTable(SqlBulkCopy bulkCopy, IEnumerable<Event> events)
2017-12-12 20:22:22 +01:00
{
var e = events.FirstOrDefault();
if (e == null)
2017-12-12 20:22:22 +01:00
{
throw new ApplicationException("Must have some events to bulk import.");
}
var eventsTable = new DataTable("EventDataTable");
var idColumn = new DataColumn(nameof(e.Id), e.Id.GetType());
eventsTable.Columns.Add(idColumn);
var typeColumn = new DataColumn(nameof(e.Type), typeof(int));
eventsTable.Columns.Add(typeColumn);
var userIdColumn = new DataColumn(nameof(e.UserId), typeof(Guid));
eventsTable.Columns.Add(userIdColumn);
var organizationIdColumn = new DataColumn(nameof(e.OrganizationId), typeof(Guid));
eventsTable.Columns.Add(organizationIdColumn);
var cipherIdColumn = new DataColumn(nameof(e.CipherId), typeof(Guid));
eventsTable.Columns.Add(cipherIdColumn);
var collectionIdColumn = new DataColumn(nameof(e.CollectionId), typeof(Guid));
eventsTable.Columns.Add(collectionIdColumn);
2020-01-15 15:43:49 +01:00
var policyIdColumn = new DataColumn(nameof(e.PolicyId), typeof(Guid));
eventsTable.Columns.Add(policyIdColumn);
2017-12-12 21:04:14 +01:00
var groupIdColumn = new DataColumn(nameof(e.GroupId), typeof(Guid));
eventsTable.Columns.Add(groupIdColumn);
2017-12-12 20:22:22 +01:00
var organizationUserIdColumn = new DataColumn(nameof(e.OrganizationUserId), typeof(Guid));
eventsTable.Columns.Add(organizationUserIdColumn);
2017-12-19 05:15:16 +01:00
var actingUserIdColumn = new DataColumn(nameof(e.ActingUserId), typeof(Guid));
eventsTable.Columns.Add(actingUserIdColumn);
var deviceTypeColumn = new DataColumn(nameof(e.DeviceType), typeof(int));
eventsTable.Columns.Add(deviceTypeColumn);
var ipAddressColumn = new DataColumn(nameof(e.IpAddress), typeof(string));
2017-12-19 05:15:16 +01:00
eventsTable.Columns.Add(ipAddressColumn);
var dateColumn = new DataColumn(nameof(e.Date), typeof(DateTime));
2017-12-12 21:04:14 +01:00
eventsTable.Columns.Add(dateColumn);
2017-12-12 20:22:22 +01:00
foreach (DataColumn col in eventsTable.Columns)
2020-02-28 04:44:49 +01:00
{
bulkCopy.ColumnMappings.Add(col.ColumnName, col.ColumnName);
}
2017-12-12 20:22:22 +01:00
var keys = new DataColumn[1];
keys[0] = idColumn;
eventsTable.PrimaryKey = keys;
foreach (var ev in events)
2017-12-12 20:22:22 +01:00
{
ev.SetNewId();
var row = eventsTable.NewRow();
row[idColumn] = ev.Id;
2017-12-12 21:04:14 +01:00
row[typeColumn] = (int)ev.Type;
row[userIdColumn] = ev.UserId.HasValue ? (object)ev.UserId.Value : DBNull.Value;
row[organizationIdColumn] = ev.OrganizationId.HasValue ? (object)ev.OrganizationId.Value : DBNull.Value;
row[cipherIdColumn] = ev.CipherId.HasValue ? (object)ev.CipherId.Value : DBNull.Value;
row[collectionIdColumn] = ev.CollectionId.HasValue ? (object)ev.CollectionId.Value : DBNull.Value;
2020-01-15 15:43:49 +01:00
row[policyIdColumn] = ev.PolicyId.HasValue ? (object)ev.PolicyId.Value : DBNull.Value;
2017-12-19 05:15:16 +01:00
row[groupIdColumn] = ev.GroupId.HasValue ? (object)ev.GroupId.Value : DBNull.Value;
2017-12-12 21:04:14 +01:00
row[organizationUserIdColumn] = ev.OrganizationUserId.HasValue ?
(object)ev.OrganizationUserId.Value : DBNull.Value;
2017-12-19 05:15:16 +01:00
row[actingUserIdColumn] = ev.ActingUserId.HasValue ? (object)ev.ActingUserId.Value : DBNull.Value;
row[deviceTypeColumn] = ev.DeviceType.HasValue ? (object)ev.DeviceType.Value : DBNull.Value;
row[ipAddressColumn] = ev.IpAddress != null ? (object)ev.IpAddress : DBNull.Value;
row[dateColumn] = ev.Date;
2017-12-12 20:22:22 +01:00
eventsTable.Rows.Add(row);
}
return eventsTable;
}
}
}