mirror of
https://github.com/bitwarden/server.git
synced 2025-02-22 02:51:33 +01:00
SqlServer cipher repository implementation with bulk copy.
This commit is contained in:
parent
16507022bd
commit
bd6ae4ac17
@ -9,7 +9,7 @@ namespace Bit.Core.Domains
|
||||
internal static string TypeValue = "user";
|
||||
|
||||
[JsonProperty("id")]
|
||||
public string Id { get; set; } = Guid.NewGuid().ToString();
|
||||
public string Id { get; set; }
|
||||
[JsonProperty("type")]
|
||||
public string Type { get; private set; } = TypeValue;
|
||||
|
||||
|
52
src/Core/Repositories/SqlServer/BaseRepository.cs
Normal file
52
src/Core/Repositories/SqlServer/BaseRepository.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using System;
|
||||
|
||||
namespace Bit.Core.Repositories.SqlServer
|
||||
{
|
||||
public abstract class BaseRepository
|
||||
{
|
||||
private static readonly long _baseDateTicks = new DateTime(1900, 1, 1).Ticks;
|
||||
|
||||
public BaseRepository(string connectionString)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(connectionString))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(connectionString));
|
||||
}
|
||||
|
||||
ConnectionString = connectionString;
|
||||
}
|
||||
|
||||
protected string ConnectionString { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Generate sequential Guid for Sql Server.
|
||||
/// ref: https://github.com/nhibernate/nhibernate-core/blob/master/src/NHibernate/Id/GuidCombGenerator.cs
|
||||
/// </summary>
|
||||
/// <returns>A comb Guid.</returns>
|
||||
protected Guid GenerateComb()
|
||||
{
|
||||
var guidArray = Guid.NewGuid().ToByteArray();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
// Get the days and milliseconds which will be used to build the byte string
|
||||
var days = new TimeSpan(now.Ticks - _baseDateTicks);
|
||||
var msecs = now.TimeOfDay;
|
||||
|
||||
// Convert to a byte array
|
||||
// Note that SQL Server is accurate to 1/300th of a millisecond so we divide by 3.333333
|
||||
var daysArray = BitConverter.GetBytes(days.Days);
|
||||
var msecsArray = BitConverter.GetBytes((long)(msecs.TotalMilliseconds / 3.333333));
|
||||
|
||||
// Reverse the bytes to match SQL Servers ordering
|
||||
Array.Reverse(daysArray);
|
||||
Array.Reverse(msecsArray);
|
||||
|
||||
// Copy the bytes into the guid
|
||||
Array.Copy(daysArray, daysArray.Length - 2, guidArray, guidArray.Length - 6, 2);
|
||||
Array.Copy(msecsArray, msecsArray.Length - 4, guidArray, guidArray.Length - 4, 4);
|
||||
|
||||
return new Guid(guidArray);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,27 +1,193 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SqlClient;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Repositories.SqlServer.Models;
|
||||
using DataTableProxy;
|
||||
using Bit.Core.Domains;
|
||||
|
||||
namespace Bit.Core.Repositories.SqlServer
|
||||
{
|
||||
public class CipherRepository : ICipherRepository
|
||||
public class CipherRepository : BaseRepository, ICipherRepository
|
||||
{
|
||||
public CipherRepository(string connectionString)
|
||||
: base(connectionString)
|
||||
{ }
|
||||
|
||||
public Task DirtyCiphersAsync(string userId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task UpdateDirtyCiphersAsync(IEnumerable<dynamic> ciphers)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var cleanedCiphers = ciphers.Where(c => c is Cipher);
|
||||
if(cleanedCiphers.Count() == 0)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
using(var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
connection.Open();
|
||||
|
||||
using(var transaction = connection.BeginTransaction())
|
||||
{
|
||||
try
|
||||
{
|
||||
// 1. Create temp tables to bulk copy into.
|
||||
|
||||
var sqlCreateTemp = @"
|
||||
SELECT TOP 0 *
|
||||
INTO #TempFolder
|
||||
FROM [dbo].[Folder]
|
||||
|
||||
SELECT TOP 0 *
|
||||
INTO #TempSite
|
||||
FROM [dbo].[Site]";
|
||||
|
||||
using(var cmd = new SqlCommand(sqlCreateTemp, connection, transaction))
|
||||
{
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
// 2. Bulk bopy into temp tables.
|
||||
|
||||
using(var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity, transaction))
|
||||
{
|
||||
bulkCopy.DestinationTableName = "#TempFolder";
|
||||
|
||||
var dataTable = cleanedCiphers
|
||||
.Where(c => c is Folder)
|
||||
.Select(c => new FolderTableModel(c as Folder))
|
||||
.ToTable(new ClassMapping<FolderTableModel>().AddAllPropertiesAsColumns());
|
||||
|
||||
bulkCopy.WriteToServer(dataTable);
|
||||
}
|
||||
|
||||
using(var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity, transaction))
|
||||
{
|
||||
bulkCopy.DestinationTableName = "#TempSite";
|
||||
|
||||
var dataTable = cleanedCiphers
|
||||
.Where(c => c is Site)
|
||||
.Select(c => new SiteTableModel(c as Site))
|
||||
.ToTable(new ClassMapping<SiteTableModel>().AddAllPropertiesAsColumns());
|
||||
|
||||
bulkCopy.WriteToServer(dataTable);
|
||||
}
|
||||
|
||||
// 3. Insert into real tables from temp tables and clean up.
|
||||
|
||||
var sqlUpdate = @"
|
||||
UPDATE
|
||||
[dbo].[Folder]
|
||||
SET
|
||||
[UserId] = TF.[UserId],
|
||||
[Name] = TF.[Name],
|
||||
[CreationDate] = TF.[CreationDate],
|
||||
[RevisionDate] = TF.[RevisionDate]
|
||||
FROM
|
||||
[dbo].[Folder] F
|
||||
INNER JOIN
|
||||
#TempFolder TF ON F.Id = TF.Id
|
||||
|
||||
UPDATE
|
||||
[dbo].[Site]
|
||||
SET
|
||||
[UserId] = TS.[UserId],
|
||||
[FolderId] = TS.[FolderId],
|
||||
[Name] = TS.[Name],
|
||||
[Uri] = TS.[Uri],
|
||||
[Username] = TS.[Username],
|
||||
[Password] = TS.[Password],
|
||||
[Notes] = TS.[Notes],
|
||||
[CreationDate] = TS.[CreationDate],
|
||||
[RevisionDate] = TS.[RevisionDate]
|
||||
FROM
|
||||
[dbo].[Site] S
|
||||
INNER JOIN
|
||||
#TempSite TS ON S.Id = TS.Id
|
||||
|
||||
DROP TABLE #TempFolder
|
||||
DROP TABLE #TempSite";
|
||||
|
||||
using(var cmd = new SqlCommand(sqlUpdate, connection, transaction))
|
||||
{
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
transaction.Commit();
|
||||
}
|
||||
catch
|
||||
{
|
||||
transaction.Rollback();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task CreateAsync(IEnumerable<dynamic> ciphers)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var cleanedCiphers = ciphers.Where(c => c is Cipher);
|
||||
if(cleanedCiphers.Count() == 0)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
// Generate new Ids for these new ciphers
|
||||
foreach(var cipher in cleanedCiphers)
|
||||
{
|
||||
cipher.Id = GenerateComb().ToString();
|
||||
}
|
||||
|
||||
using(var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
connection.Open();
|
||||
|
||||
using(var transaction = connection.BeginTransaction())
|
||||
{
|
||||
try
|
||||
{
|
||||
using(var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity, transaction))
|
||||
{
|
||||
bulkCopy.DestinationTableName = "[dbo].[Folder]";
|
||||
|
||||
var dataTable = cleanedCiphers
|
||||
.Where(c => c is Folder)
|
||||
.Select(c => new FolderTableModel(c as Folder))
|
||||
.ToTable(new ClassMapping<FolderTableModel>().AddAllPropertiesAsColumns());
|
||||
|
||||
bulkCopy.WriteToServer(dataTable);
|
||||
}
|
||||
|
||||
using(var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity, transaction))
|
||||
{
|
||||
bulkCopy.DestinationTableName = "[dbo].[Site]";
|
||||
|
||||
var dataTable = cleanedCiphers
|
||||
.Where(c => c is Site)
|
||||
.Select(c => new SiteTableModel(c as Site))
|
||||
.ToTable(new ClassMapping<SiteTableModel>().AddAllPropertiesAsColumns());
|
||||
|
||||
bulkCopy.WriteToServer(dataTable);
|
||||
}
|
||||
|
||||
transaction.Commit();
|
||||
}
|
||||
catch
|
||||
{
|
||||
transaction.Rollback();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,19 +8,11 @@ using Dapper;
|
||||
|
||||
namespace Bit.Core.Repositories.SqlServer
|
||||
{
|
||||
public abstract class Repository<T, TModel> : IRepository<T> where T : IDataObject where TModel : ITableModel<T>
|
||||
public abstract class Repository<T, TModel> : BaseRepository, IRepository<T> where T : IDataObject where TModel : ITableModel<T>
|
||||
{
|
||||
private static readonly long _baseDateTicks = new DateTime(1900, 1, 1).Ticks;
|
||||
|
||||
public Repository(string connectionString, string schema = null, string table = null)
|
||||
: base(connectionString)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(connectionString))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(connectionString));
|
||||
}
|
||||
|
||||
ConnectionString = connectionString;
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(table))
|
||||
{
|
||||
Table = table;
|
||||
@ -32,7 +24,6 @@ namespace Bit.Core.Repositories.SqlServer
|
||||
}
|
||||
}
|
||||
|
||||
protected string ConnectionString { get; private set; }
|
||||
protected string Schema { get; private set; } = "dbo";
|
||||
protected string Table { get; private set; } = typeof(T).Name;
|
||||
|
||||
@ -109,36 +100,5 @@ namespace Bit.Core.Repositories.SqlServer
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate sequential Guid for Sql Server.
|
||||
/// ref: https://github.com/nhibernate/nhibernate-core/blob/master/src/NHibernate/Id/GuidCombGenerator.cs
|
||||
/// </summary>
|
||||
/// <returns>A comb Guid.</returns>
|
||||
protected Guid GenerateComb()
|
||||
{
|
||||
var guidArray = Guid.NewGuid().ToByteArray();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
// Get the days and milliseconds which will be used to build the byte string
|
||||
var days = new TimeSpan(now.Ticks - _baseDateTicks);
|
||||
var msecs = now.TimeOfDay;
|
||||
|
||||
// Convert to a byte array
|
||||
// Note that SQL Server is accurate to 1/300th of a millisecond so we divide by 3.333333
|
||||
var daysArray = BitConverter.GetBytes(days.Days);
|
||||
var msecsArray = BitConverter.GetBytes((long)(msecs.TotalMilliseconds / 3.333333));
|
||||
|
||||
// Reverse the bytes to match SQL Servers ordering
|
||||
Array.Reverse(daysArray);
|
||||
Array.Reverse(msecsArray);
|
||||
|
||||
// Copy the bytes into the guid
|
||||
Array.Copy(daysArray, daysArray.Length - 2, guidArray, guidArray.Length - 6, 2);
|
||||
Array.Copy(msecsArray, msecsArray.Length - 4, guidArray, guidArray.Length - 4, 4);
|
||||
|
||||
return new Guid(guidArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,8 @@
|
||||
"Microsoft.AspNet.DataProtection.Extensions": "1.0.0-rc1-final",
|
||||
"Microsoft.Azure.DocumentDB": "1.5.2",
|
||||
"Newtonsoft.Json": "8.0.1",
|
||||
"Dapper": "1.42.0"
|
||||
"Dapper": "1.42.0",
|
||||
"DataTableProxy": "1.2.0"
|
||||
},
|
||||
|
||||
"frameworks": {
|
||||
|
Loading…
Reference in New Issue
Block a user