mirror of
https://github.com/bitwarden/server.git
synced 2025-07-16 23:27:30 -05:00
refactored data storage to use cipher table. added history table and insert triggers.
This commit is contained in:
@ -1,13 +1,21 @@
|
||||
using System;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Core.Domains
|
||||
{
|
||||
public abstract class Cipher : IDataObject
|
||||
public class Cipher : IDataObject<Guid>
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string UserId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public Guid Id { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
public Guid? FolderId { get; set; }
|
||||
public Enums.CipherType Type { get; set; }
|
||||
public string Data { get; set; }
|
||||
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;
|
||||
public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow;
|
||||
|
||||
public void SetNewId()
|
||||
{
|
||||
Id = CoreHelpers.GenerateComb();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +0,0 @@
|
||||
namespace Bit.Core.Domains
|
||||
{
|
||||
public class Folder : Cipher, IDataObject
|
||||
{
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.Domains
|
||||
{
|
||||
public class Site : Cipher, IDataObject
|
||||
{
|
||||
public string FolderId { get; set; }
|
||||
public string Uri { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
public string Notes { get; set; }
|
||||
}
|
||||
}
|
@ -1,11 +1,12 @@
|
||||
using System;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Core.Domains
|
||||
{
|
||||
public class User : IDataObject
|
||||
public class User : IDataObject<Guid>
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Email { get; set; }
|
||||
public bool EmailVerified { get; set; }
|
||||
@ -18,5 +19,10 @@ namespace Bit.Core.Domains
|
||||
public string AuthenticatorKey { get; set; }
|
||||
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;
|
||||
public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow;
|
||||
|
||||
public void SetNewId()
|
||||
{
|
||||
Id = CoreHelpers.GenerateComb();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace Bit.Core.Enums
|
||||
{
|
||||
public enum CipherType
|
||||
public enum CipherType : short
|
||||
{
|
||||
Folder = 0,
|
||||
Site = 1
|
||||
|
@ -1,7 +1,10 @@
|
||||
namespace Bit.Core
|
||||
using System;
|
||||
|
||||
namespace Bit.Core
|
||||
{
|
||||
public interface IDataObject
|
||||
public interface IDataObject<T> where T : IEquatable<T>
|
||||
{
|
||||
string Id { get; set; }
|
||||
T Id { get; set; }
|
||||
void SetNewId();
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ namespace Bit.Core.Identity
|
||||
var signInManager = context.HttpContext.RequestServices.GetRequiredService<JwtBearerSignInManager>();
|
||||
|
||||
var userId = userManager.GetUserId(context.Ticket.Principal);
|
||||
var user = await userRepository.GetByIdAsync(userId);
|
||||
var user = await userRepository.GetByIdAsync(new Guid(userId));
|
||||
|
||||
// validate security token
|
||||
if(!await signInManager.ValidateSecurityStampAsync(user, context.Ticket.Principal))
|
||||
|
@ -55,12 +55,14 @@ namespace Bit.Core.Identity
|
||||
|
||||
public async Task<User> FindByIdAsync(string userId, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
if(_currentContext?.User != null && _currentContext.User.Id == userId)
|
||||
var id = new Guid(userId);
|
||||
|
||||
if(_currentContext?.User != null && _currentContext.User.Id == id)
|
||||
{
|
||||
return _currentContext.User;
|
||||
}
|
||||
|
||||
return await _userRepository.GetByIdAsync(userId);
|
||||
return await _userRepository.GetByIdAsync(id);
|
||||
}
|
||||
|
||||
public async Task<User> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken = default(CancellationToken))
|
||||
@ -100,7 +102,7 @@ namespace Bit.Core.Identity
|
||||
|
||||
public Task<string> GetUserIdAsync(User user, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
return Task.FromResult(user.Id);
|
||||
return Task.FromResult(user.Id.ToString());
|
||||
}
|
||||
|
||||
public Task<string> GetUserNameAsync(User user, CancellationToken cancellationToken = default(CancellationToken))
|
||||
|
@ -1,12 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Domains;
|
||||
|
||||
namespace Bit.Core.Repositories
|
||||
{
|
||||
public interface ICipherRepository
|
||||
public interface ICipherRepository : IRepository<Cipher, Guid>
|
||||
{
|
||||
Task UpdateUserEmailPasswordAndCiphersAsync(User user, IEnumerable<dynamic> ciphers);
|
||||
Task CreateAsync(IEnumerable<dynamic> ciphers);
|
||||
Task<Cipher> GetByIdAsync(Guid id, Guid userId);
|
||||
Task<ICollection<Cipher>> GetManyByUserIdAsync(Guid userId);
|
||||
Task<ICollection<Cipher>> GetManyByTypeAndUserIdAsync(Enums.CipherType type, Guid userId);
|
||||
Task UpdateUserEmailPasswordAndCiphersAsync(User user, IEnumerable<Cipher> ciphers);
|
||||
Task CreateAsync(IEnumerable<Cipher> ciphers);
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Domains;
|
||||
|
||||
namespace Bit.Core.Repositories
|
||||
{
|
||||
public interface IFolderRepository : IRepository<Folder>
|
||||
{
|
||||
Task<Folder> GetByIdAsync(string id, string userId);
|
||||
Task<ICollection<Folder>> GetManyByUserIdAsync(string userId);
|
||||
}
|
||||
}
|
@ -1,14 +1,15 @@
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bit.Core.Repositories
|
||||
{
|
||||
public interface IRepository<T> where T : IDataObject
|
||||
public interface IRepository<T, TId> where TId : IEquatable<TId> where T : class, IDataObject<TId>
|
||||
{
|
||||
Task<T> GetByIdAsync(string id);
|
||||
Task<T> GetByIdAsync(TId id);
|
||||
Task CreateAsync(T obj);
|
||||
Task ReplaceAsync(T obj);
|
||||
Task UpsertAsync(T obj);
|
||||
Task DeleteByIdAsync(string id);
|
||||
Task DeleteByIdAsync(TId id);
|
||||
Task DeleteAsync(T obj);
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Domains;
|
||||
|
||||
namespace Bit.Core.Repositories
|
||||
{
|
||||
public interface ISiteRepository : IRepository<Site>
|
||||
{
|
||||
Task<Site> GetByIdAsync(string id, string userId);
|
||||
Task<ICollection<Site>> GetManyByUserIdAsync(string userId);
|
||||
}
|
||||
}
|
@ -1,11 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Domains;
|
||||
|
||||
namespace Bit.Core.Repositories
|
||||
{
|
||||
public interface IUserRepository : IRepository<User>
|
||||
public interface IUserRepository : IRepository<User, Guid>
|
||||
{
|
||||
Task<User> GetByEmailAsync(string email);
|
||||
}
|
||||
|
@ -4,8 +4,6 @@ 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))
|
||||
@ -17,36 +15,5 @@ namespace Bit.Core.Repositories.SqlServer
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,23 +3,63 @@ 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;
|
||||
using System.Data;
|
||||
using Dapper;
|
||||
|
||||
namespace Bit.Core.Repositories.SqlServer
|
||||
{
|
||||
public class CipherRepository : BaseRepository, ICipherRepository
|
||||
public class CipherRepository : Repository<Cipher, Guid>, ICipherRepository
|
||||
{
|
||||
public CipherRepository(string connectionString)
|
||||
: base(connectionString)
|
||||
{ }
|
||||
|
||||
public Task UpdateUserEmailPasswordAndCiphersAsync(User user, IEnumerable<dynamic> ciphers)
|
||||
public async Task<Cipher> GetByIdAsync(Guid id, Guid userId)
|
||||
{
|
||||
var cleanedCiphers = ciphers.Where(c => c is Cipher);
|
||||
if(cleanedCiphers.Count() == 0)
|
||||
var cipher = await GetByIdAsync(id);
|
||||
if(cipher == null || cipher.UserId != userId)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return cipher;
|
||||
}
|
||||
|
||||
public async Task<ICollection<Cipher>> GetManyByUserIdAsync(Guid userId)
|
||||
{
|
||||
using(var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
var results = await connection.QueryAsync<Cipher>(
|
||||
$"[{Schema}].[{Table}_ReadByUserId]",
|
||||
new { UserId = userId },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
|
||||
return results.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ICollection<Cipher>> GetManyByTypeAndUserIdAsync(Enums.CipherType type, Guid userId)
|
||||
{
|
||||
using(var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
var results = await connection.QueryAsync<Cipher>(
|
||||
$"[{Schema}].[{Table}_ReadByTypeUserId]",
|
||||
new
|
||||
{
|
||||
Type = type,
|
||||
UserId = userId
|
||||
},
|
||||
commandType: CommandType.StoredProcedure);
|
||||
|
||||
return results.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public Task UpdateUserEmailPasswordAndCiphersAsync(User user, IEnumerable<Cipher> ciphers)
|
||||
{
|
||||
if(ciphers.Count() == 0)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
@ -37,7 +77,7 @@ namespace Bit.Core.Repositories.SqlServer
|
||||
using(var cmd = new SqlCommand("[dbo].[User_UpdateEmailPassword]", connection, transaction))
|
||||
{
|
||||
cmd.CommandType = CommandType.StoredProcedure;
|
||||
cmd.Parameters.Add("@Id", SqlDbType.UniqueIdentifier).Value = new Guid(user.Id);
|
||||
cmd.Parameters.Add("@Id", SqlDbType.UniqueIdentifier).Value = user.Id;
|
||||
cmd.Parameters.Add("@Email", SqlDbType.NVarChar).Value = user.Email;
|
||||
cmd.Parameters.Add("@EmailVerified", SqlDbType.NVarChar).Value = user.EmailVerified;
|
||||
cmd.Parameters.Add("@MasterPassword", SqlDbType.NVarChar).Value = user.MasterPassword;
|
||||
@ -50,12 +90,8 @@ namespace Bit.Core.Repositories.SqlServer
|
||||
|
||||
var sqlCreateTemp = @"
|
||||
SELECT TOP 0 *
|
||||
INTO #TempFolder
|
||||
FROM [dbo].[Folder]
|
||||
|
||||
SELECT TOP 0 *
|
||||
INTO #TempSite
|
||||
FROM [dbo].[Site]";
|
||||
INTO #TempCipher
|
||||
FROM [dbo].[Cipher]";
|
||||
|
||||
using(var cmd = new SqlCommand(sqlCreateTemp, connection, transaction))
|
||||
{
|
||||
@ -66,25 +102,9 @@ namespace Bit.Core.Repositories.SqlServer
|
||||
|
||||
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.DestinationTableName = "#TempCipher";
|
||||
|
||||
var dataTable = ciphers.ToTable(new ClassMapping<Cipher>().AddAllPropertiesAsColumns());
|
||||
bulkCopy.WriteToServer(dataTable);
|
||||
}
|
||||
|
||||
@ -92,44 +112,26 @@ namespace Bit.Core.Repositories.SqlServer
|
||||
|
||||
var sqlUpdate = @"
|
||||
UPDATE
|
||||
[dbo].[Folder]
|
||||
SET
|
||||
-- Do not update [UserId]
|
||||
[Name] = TF.[Name],
|
||||
-- Do not update [CreationDate]
|
||||
[RevisionDate] = TF.[RevisionDate]
|
||||
FROM
|
||||
[dbo].[Folder] F
|
||||
INNER JOIN
|
||||
#TempFolder TF ON F.Id = TF.Id
|
||||
WHERE
|
||||
F.[UserId] = @UserId
|
||||
|
||||
UPDATE
|
||||
[dbo].[Site]
|
||||
[dbo].[Cipher]
|
||||
SET
|
||||
-- Do not update [UserId]
|
||||
-- Do not update [FolderId]
|
||||
[Name] = TS.[Name],
|
||||
[Uri] = TS.[Uri],
|
||||
[Username] = TS.[Username],
|
||||
[Password] = TS.[Password],
|
||||
[Notes] = TS.[Notes],
|
||||
-- Do not update [Type]
|
||||
[Data] = TC.[Data],
|
||||
-- Do not update [CreationDate]
|
||||
[RevisionDate] = TS.[RevisionDate]
|
||||
[RevisionDate] = TC.[RevisionDate]
|
||||
FROM
|
||||
[dbo].[Site] S
|
||||
[dbo].[Cipher] C
|
||||
INNER JOIN
|
||||
#TempSite TS ON S.Id = TS.Id
|
||||
#TempCipher TC ON C.Id = TC.Id
|
||||
WHERE
|
||||
S.[UserId] = @UserId
|
||||
C.[UserId] = @UserId
|
||||
|
||||
DROP TABLE #TempFolder
|
||||
DROP TABLE #TempSite";
|
||||
DROP TABLE #TempCipher";
|
||||
|
||||
using(var cmd = new SqlCommand(sqlUpdate, connection, transaction))
|
||||
{
|
||||
cmd.Parameters.Add("@UserId", SqlDbType.UniqueIdentifier).Value = new Guid(user.Id);
|
||||
cmd.Parameters.Add("@UserId", SqlDbType.UniqueIdentifier).Value = user.Id;
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
@ -146,18 +148,17 @@ namespace Bit.Core.Repositories.SqlServer
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task CreateAsync(IEnumerable<dynamic> ciphers)
|
||||
public Task CreateAsync(IEnumerable<Cipher> ciphers)
|
||||
{
|
||||
var cleanedCiphers = ciphers.Where(c => c is Cipher);
|
||||
if(cleanedCiphers.Count() == 0)
|
||||
if(ciphers.Count() == 0)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
// Generate new Ids for these new ciphers
|
||||
foreach(var cipher in cleanedCiphers)
|
||||
foreach(var cipher in ciphers)
|
||||
{
|
||||
cipher.Id = GenerateComb().ToString();
|
||||
cipher.SetNewId();
|
||||
}
|
||||
|
||||
using(var connection = new SqlConnection(ConnectionString))
|
||||
@ -168,27 +169,10 @@ namespace Bit.Core.Repositories.SqlServer
|
||||
{
|
||||
try
|
||||
{
|
||||
using(var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity, transaction))
|
||||
using(var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity | SqlBulkCopyOptions.FireTriggers, 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.DestinationTableName = "[dbo].[Cipher]";
|
||||
var dataTable = ciphers.ToTable(new ClassMapping<Cipher>().AddAllPropertiesAsColumns());
|
||||
bulkCopy.WriteToServer(dataTable);
|
||||
}
|
||||
|
||||
|
@ -1,43 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.SqlClient;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Domains;
|
||||
using Bit.Core.Repositories.SqlServer.Models;
|
||||
using Dapper;
|
||||
|
||||
namespace Bit.Core.Repositories.SqlServer
|
||||
{
|
||||
public class FolderRepository : Repository<Folder, FolderTableModel>, IFolderRepository
|
||||
{
|
||||
public FolderRepository(string connectionString)
|
||||
: base(connectionString)
|
||||
{ }
|
||||
|
||||
public async Task<Folder> GetByIdAsync(string id, string userId)
|
||||
{
|
||||
var folder = await GetByIdAsync(id);
|
||||
if(folder == null || folder.UserId != userId)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return folder;
|
||||
}
|
||||
|
||||
public async Task<ICollection<Folder>> GetManyByUserIdAsync(string userId)
|
||||
{
|
||||
using(var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
var results = await connection.QueryAsync<FolderTableModel>(
|
||||
$"[{Schema}].[{Table}_ReadByUserId]",
|
||||
new { UserId = new Guid(userId) },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
|
||||
return results.Select(f => f.ToDomain()).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
using System;
|
||||
using Bit.Core.Domains;
|
||||
|
||||
namespace Bit.Core.Repositories.SqlServer.Models
|
||||
{
|
||||
public class FolderTableModel : ITableModel<Folder>
|
||||
{
|
||||
public FolderTableModel() { }
|
||||
|
||||
public FolderTableModel(Folder folder)
|
||||
{
|
||||
Id = new Guid(folder.Id);
|
||||
UserId = new Guid(folder.UserId);
|
||||
Name = folder.Name;
|
||||
CreationDate = folder.CreationDate;
|
||||
RevisionDate = folder.RevisionDate;
|
||||
}
|
||||
|
||||
public Guid Id { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public DateTime CreationDate { get; set; }
|
||||
public DateTime RevisionDate { get; set; }
|
||||
|
||||
public Folder ToDomain()
|
||||
{
|
||||
return new Folder
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
UserId = UserId.ToString(),
|
||||
Name = Name,
|
||||
CreationDate = CreationDate,
|
||||
RevisionDate = RevisionDate
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
namespace Bit.Core.Repositories.SqlServer.Models
|
||||
{
|
||||
public interface ITableModel<T>
|
||||
{
|
||||
T ToDomain();
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
using System;
|
||||
using Bit.Core.Domains;
|
||||
|
||||
namespace Bit.Core.Repositories.SqlServer.Models
|
||||
{
|
||||
public class SiteTableModel : ITableModel<Site>
|
||||
{
|
||||
public SiteTableModel() { }
|
||||
|
||||
public SiteTableModel(Site site)
|
||||
{
|
||||
Id = new Guid(site.Id);
|
||||
UserId = new Guid(site.UserId);
|
||||
FolderId = string.IsNullOrWhiteSpace(site.FolderId) ? (Guid?)null : new Guid(site.FolderId);
|
||||
Name = site.Name;
|
||||
Uri = site.Uri;
|
||||
Username = site.Username;
|
||||
Password = site.Password;
|
||||
Notes = site.Notes;
|
||||
CreationDate = site.CreationDate;
|
||||
RevisionDate = site.RevisionDate;
|
||||
}
|
||||
|
||||
public Guid Id { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
public Guid? FolderId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Uri { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public DateTime CreationDate { get; set; }
|
||||
public DateTime RevisionDate { get; set; }
|
||||
|
||||
public Site ToDomain()
|
||||
{
|
||||
return new Site
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
UserId = UserId.ToString(),
|
||||
FolderId = FolderId.ToString(),
|
||||
Name = Name,
|
||||
Uri = Uri,
|
||||
Username = Username,
|
||||
Password = Password,
|
||||
Notes = Notes,
|
||||
CreationDate = CreationDate,
|
||||
RevisionDate = RevisionDate
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
using System;
|
||||
using Bit.Core.Domains;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.Repositories.SqlServer.Models
|
||||
{
|
||||
public class UserTableModel : ITableModel<User>
|
||||
{
|
||||
public UserTableModel() { }
|
||||
|
||||
public UserTableModel(User user)
|
||||
{
|
||||
Id = new Guid(user.Id);
|
||||
Name = user.Name;
|
||||
Email = user.Email;
|
||||
EmailVerified = user.EmailVerified;
|
||||
MasterPassword = user.MasterPassword;
|
||||
MasterPasswordHint = user.MasterPasswordHint;
|
||||
Culture = user.Culture;
|
||||
SecurityStamp = user.SecurityStamp;
|
||||
TwoFactorEnabled = user.TwoFactorEnabled;
|
||||
TwoFactorProvider = user.TwoFactorProvider;
|
||||
AuthenticatorKey = user.AuthenticatorKey;
|
||||
CreationDate = user.CreationDate;
|
||||
RevisionDate = user.RevisionDate;
|
||||
}
|
||||
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Email { get; set; }
|
||||
public bool EmailVerified { get; set; }
|
||||
public string MasterPassword { get; set; }
|
||||
public string MasterPasswordHint { get; set; }
|
||||
public string Culture { get; set; }
|
||||
public string SecurityStamp { get; set; }
|
||||
public bool TwoFactorEnabled { get; set; }
|
||||
public TwoFactorProvider? TwoFactorProvider { get; set; }
|
||||
public string AuthenticatorKey { get; set; }
|
||||
public DateTime CreationDate { get; set; }
|
||||
public DateTime RevisionDate { get; set; }
|
||||
|
||||
public User ToDomain()
|
||||
{
|
||||
return new User
|
||||
{
|
||||
Id = Id.ToString(),
|
||||
Name = Name,
|
||||
Email = Email,
|
||||
EmailVerified = EmailVerified,
|
||||
MasterPassword = MasterPassword,
|
||||
MasterPasswordHint = MasterPasswordHint,
|
||||
Culture = Culture,
|
||||
SecurityStamp = SecurityStamp,
|
||||
TwoFactorEnabled = TwoFactorEnabled,
|
||||
TwoFactorProvider = TwoFactorProvider,
|
||||
AuthenticatorKey = AuthenticatorKey,
|
||||
CreationDate = CreationDate,
|
||||
RevisionDate = RevisionDate
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -3,12 +3,11 @@ using System.Data;
|
||||
using System.Data.SqlClient;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Repositories.SqlServer.Models;
|
||||
using Dapper;
|
||||
|
||||
namespace Bit.Core.Repositories.SqlServer
|
||||
{
|
||||
public abstract class Repository<T, TModel> : BaseRepository, IRepository<T> where T : IDataObject where TModel : ITableModel<T>
|
||||
public abstract class Repository<T, TId> : BaseRepository, IRepository<T, TId> where TId : IEquatable<TId> where T : class, IDataObject<TId>
|
||||
{
|
||||
public Repository(string connectionString, string schema = null, string table = null)
|
||||
: base(connectionString)
|
||||
@ -27,55 +26,45 @@ namespace Bit.Core.Repositories.SqlServer
|
||||
protected string Schema { get; private set; } = "dbo";
|
||||
protected string Table { get; private set; } = typeof(T).Name;
|
||||
|
||||
public virtual async Task<T> GetByIdAsync(string id)
|
||||
public virtual async Task<T> GetByIdAsync(TId id)
|
||||
{
|
||||
using(var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
var results = await connection.QueryAsync<TModel>(
|
||||
var results = await connection.QueryAsync<T>(
|
||||
$"[{Schema}].[{Table}_ReadById]",
|
||||
new { Id = new Guid(id) },
|
||||
new { Id = id },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
|
||||
var model = results.FirstOrDefault();
|
||||
if(model == null)
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
|
||||
return model.ToDomain();
|
||||
return results.SingleOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual async Task CreateAsync(T obj)
|
||||
{
|
||||
obj.Id = GenerateComb().ToString();
|
||||
var tableModel = (TModel)Activator.CreateInstance(typeof(TModel), obj);
|
||||
|
||||
obj.SetNewId();
|
||||
using(var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
var results = await connection.ExecuteAsync(
|
||||
$"[{Schema}].[{Table}_Create]",
|
||||
tableModel,
|
||||
obj,
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual async Task ReplaceAsync(T obj)
|
||||
{
|
||||
var tableModel = (TModel)Activator.CreateInstance(typeof(TModel), obj);
|
||||
|
||||
using(var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
var results = await connection.ExecuteAsync(
|
||||
$"[{Schema}].[{Table}_Update]",
|
||||
tableModel,
|
||||
obj,
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual async Task UpsertAsync(T obj)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(obj.Id) || obj.Id == "0" || obj.Id == Guid.Empty.ToString())
|
||||
if(obj.Id.Equals(default(TId)))
|
||||
{
|
||||
await CreateAsync(obj);
|
||||
}
|
||||
@ -90,13 +79,13 @@ namespace Bit.Core.Repositories.SqlServer
|
||||
await DeleteByIdAsync(obj.Id);
|
||||
}
|
||||
|
||||
public virtual async Task DeleteByIdAsync(string id)
|
||||
public virtual async Task DeleteByIdAsync(TId id)
|
||||
{
|
||||
using(var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
await connection.ExecuteAsync(
|
||||
$"[{Schema}].[{Table}_DeleteById]",
|
||||
new { Id = new Guid(id) },
|
||||
new { Id = id },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
}
|
||||
|
@ -1,43 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.SqlClient;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Domains;
|
||||
using Bit.Core.Repositories.SqlServer.Models;
|
||||
using Dapper;
|
||||
|
||||
namespace Bit.Core.Repositories.SqlServer
|
||||
{
|
||||
public class SiteRepository : Repository<Site, SiteTableModel>, ISiteRepository
|
||||
{
|
||||
public SiteRepository(string connectionString)
|
||||
: base(connectionString)
|
||||
{ }
|
||||
|
||||
public async Task<Site> GetByIdAsync(string id, string userId)
|
||||
{
|
||||
var site = await GetByIdAsync(id);
|
||||
if(site == null || site.UserId != userId)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return site;
|
||||
}
|
||||
|
||||
public async Task<ICollection<Site>> GetManyByUserIdAsync(string userId)
|
||||
{
|
||||
using(var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
var results = await connection.QueryAsync<SiteTableModel>(
|
||||
$"[{Schema}].[{Table}_ReadByUserId]",
|
||||
new { UserId = new Guid(userId) },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
|
||||
return results.Select(s => s.ToDomain()).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
using System.Data;
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Data.SqlClient;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Core.Domains;
|
||||
using Bit.Core.Repositories.SqlServer.Models;
|
||||
using Dapper;
|
||||
|
||||
namespace Bit.Core.Repositories.SqlServer
|
||||
{
|
||||
public class UserRepository : Repository<User, UserTableModel>, IUserRepository
|
||||
public class UserRepository : Repository<User, Guid>, IUserRepository
|
||||
{
|
||||
public UserRepository(string connectionString)
|
||||
: base(connectionString)
|
||||
@ -18,18 +18,12 @@ namespace Bit.Core.Repositories.SqlServer
|
||||
{
|
||||
using(var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
var results = await connection.QueryAsync<UserTableModel>(
|
||||
var results = await connection.QueryAsync<User>(
|
||||
$"[{Schema}].[{Table}_ReadByEmail]",
|
||||
new { Email = email },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
|
||||
var model = results.FirstOrDefault();
|
||||
if(model == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return model.ToDomain();
|
||||
return results.SingleOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,46 +9,43 @@ namespace Bit.Core.Services
|
||||
{
|
||||
public class CipherService : ICipherService
|
||||
{
|
||||
private readonly IFolderRepository _folderRepository;
|
||||
private readonly ICipherRepository _cipherRepository;
|
||||
|
||||
public CipherService(
|
||||
IFolderRepository folderRepository,
|
||||
ICipherRepository cipherRepository)
|
||||
{
|
||||
_folderRepository = folderRepository;
|
||||
_cipherRepository = cipherRepository;
|
||||
}
|
||||
|
||||
public async Task ImportCiphersAsync(
|
||||
List<Folder> folders,
|
||||
List<Site> sites,
|
||||
IEnumerable<KeyValuePair<int, int>> siteRelationships)
|
||||
List<Cipher> folders,
|
||||
List<Cipher> ciphers,
|
||||
IEnumerable<KeyValuePair<int, int>> folderRelationships)
|
||||
{
|
||||
// create all the folders
|
||||
var folderTasks = new List<Task>();
|
||||
foreach(var folder in folders)
|
||||
{
|
||||
folderTasks.Add(_folderRepository.CreateAsync(folder));
|
||||
folderTasks.Add(_cipherRepository.CreateAsync(folder));
|
||||
}
|
||||
await Task.WhenAll(folderTasks);
|
||||
|
||||
// associate the newly created folders to the sites
|
||||
foreach(var relationship in siteRelationships)
|
||||
// associate the newly created folders to the ciphers
|
||||
foreach(var relationship in folderRelationships)
|
||||
{
|
||||
var site = sites.ElementAtOrDefault(relationship.Key);
|
||||
var cipher = ciphers.ElementAtOrDefault(relationship.Key);
|
||||
var folder = folders.ElementAtOrDefault(relationship.Value);
|
||||
|
||||
if(site == null || folder == null)
|
||||
if(cipher == null || folder == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
site.FolderId = folder.Id;
|
||||
cipher.FolderId = folder.Id;
|
||||
}
|
||||
|
||||
// create all the sites
|
||||
await _cipherRepository.CreateAsync(sites);
|
||||
// create all the ciphers
|
||||
await _cipherRepository.CreateAsync(ciphers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,6 @@ namespace Bit.Core.Services
|
||||
{
|
||||
public interface ICipherService
|
||||
{
|
||||
Task ImportCiphersAsync(List<Folder> folders, List<Site> sites, IEnumerable<KeyValuePair<int, int>> siteRelationships);
|
||||
Task ImportCiphersAsync(List<Cipher> folders, List<Cipher> ciphers, IEnumerable<KeyValuePair<int, int>> folderRelationships);
|
||||
}
|
||||
}
|
||||
|
@ -8,13 +8,13 @@ namespace Bit.Core.Services
|
||||
{
|
||||
public interface IUserService
|
||||
{
|
||||
Task<User> GetUserByIdAsync(string userId);
|
||||
Task<User> GetUserByIdAsync(Guid userId);
|
||||
Task SaveUserAsync(User user);
|
||||
Task<IdentityResult> RegisterUserAsync(User user, string masterPassword);
|
||||
Task SendMasterPasswordHintAsync(string email);
|
||||
Task InitiateEmailChangeAsync(User user, string newEmail);
|
||||
Task<IdentityResult> ChangeEmailAsync(User user, string masterPassword, string newEmail, string newMasterPassword, string token, IEnumerable<dynamic> ciphers);
|
||||
Task<IdentityResult> ChangePasswordAsync(User user, string currentMasterPasswordHash, string newMasterPasswordHash, IEnumerable<dynamic> ciphers);
|
||||
Task<IdentityResult> ChangeEmailAsync(User user, string masterPassword, string newEmail, string newMasterPassword, string token, IEnumerable<Cipher> ciphers);
|
||||
Task<IdentityResult> ChangePasswordAsync(User user, string currentMasterPasswordHash, string newMasterPasswordHash, IEnumerable<Cipher> ciphers);
|
||||
Task<IdentityResult> RefreshSecurityStampAsync(User user, string masterPasswordHash);
|
||||
Task GetTwoFactorAsync(User user, Enums.TwoFactorProvider provider);
|
||||
Task<IdentityResult> DeleteAsync(User user);
|
||||
|
@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
@ -57,14 +56,14 @@ namespace Bit.Core.Services
|
||||
_passwordValidators = passwordValidators;
|
||||
}
|
||||
|
||||
public async Task<User> GetUserByIdAsync(string userId)
|
||||
public async Task<User> GetUserByIdAsync(Guid userId)
|
||||
{
|
||||
return await _userRepository.GetByIdAsync(userId);
|
||||
}
|
||||
|
||||
public async Task SaveUserAsync(User user)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(user.Id))
|
||||
if(user.Id == default(Guid))
|
||||
{
|
||||
throw new ApplicationException("Use register method to create a new user.");
|
||||
}
|
||||
@ -114,7 +113,7 @@ namespace Bit.Core.Services
|
||||
await _mailService.SendChangeEmailEmailAsync(newEmail, token);
|
||||
}
|
||||
|
||||
public async Task<IdentityResult> ChangeEmailAsync(User user, string masterPassword, string newEmail, string newMasterPassword, string token, IEnumerable<dynamic> ciphers)
|
||||
public async Task<IdentityResult> ChangeEmailAsync(User user, string masterPassword, string newEmail, string newMasterPassword, string token, IEnumerable<Cipher> ciphers)
|
||||
{
|
||||
var verifyPasswordResult = _passwordHasher.VerifyHashedPassword(user, user.MasterPassword, masterPassword);
|
||||
if(verifyPasswordResult == PasswordVerificationResult.Failed)
|
||||
@ -151,7 +150,7 @@ namespace Bit.Core.Services
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task<IdentityResult> ChangePasswordAsync(User user, string masterPassword, string newMasterPassword, IEnumerable<dynamic> ciphers)
|
||||
public async Task<IdentityResult> ChangePasswordAsync(User user, string masterPassword, string newMasterPassword, IEnumerable<Cipher> ciphers)
|
||||
{
|
||||
if(user == null)
|
||||
{
|
||||
|
40
src/Core/Utilities/CoreHelpers.cs
Normal file
40
src/Core/Utilities/CoreHelpers.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using System;
|
||||
|
||||
namespace Bit.Core.Utilities
|
||||
{
|
||||
public static class CoreHelpers
|
||||
{
|
||||
private static readonly long _baseDateTicks = new DateTime(1900, 1, 1).Ticks;
|
||||
|
||||
/// <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>
|
||||
public static 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);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user