diff --git a/src/Api/Controllers/AccountsController.cs b/src/Api/Controllers/AccountsController.cs index 7b92432e05..8fe3619b64 100644 --- a/src/Api/Controllers/AccountsController.cs +++ b/src/Api/Controllers/AccountsController.cs @@ -78,7 +78,7 @@ namespace Bit.Api.Controllers { // NOTE: It is assumed that the eventual repository call will make sure the updated // ciphers belong to user making this call. Therefore, no check is done here. - var ciphers = CipherRequestModel.ToDynamicCiphers(model.Ciphers, _userManager.GetUserId(User)); + var ciphers = model.Ciphers.Select(c => c.ToCipher(_userManager.GetUserId(User))); var result = await _userService.ChangeEmailAsync( _currentContext.User, @@ -107,7 +107,7 @@ namespace Bit.Api.Controllers { // NOTE: It is assumed that the eventual repository call will make sure the updated // ciphers belong to user making this call. Therefore, no check is done here. - var ciphers = CipherRequestModel.ToDynamicCiphers(model.Ciphers, _userManager.GetUserId(User)); + var ciphers = model.Ciphers.Select(c => c.ToCipher(_userManager.GetUserId(User))); var result = await _userService.ChangePasswordAsync( _currentContext.User, @@ -206,8 +206,8 @@ namespace Bit.Api.Controllers public async Task PostImport([FromBody]ImportRequestModel model) { await _cipherService.ImportCiphersAsync( - model.Folders.Select(f => f.ToFolder(_userManager.GetUserId(User))).ToList(), - model.Sites.Select(s => s.ToSite(_userManager.GetUserId(User))).ToList(), + model.Folders.Select(f => f.ToCipher(_userManager.GetUserId(User))).ToList(), + model.Sites.Select(s => s.ToCipher(_userManager.GetUserId(User))).ToList(), model.SiteRelationships); } diff --git a/src/Api/Controllers/FoldersController.cs b/src/Api/Controllers/FoldersController.cs index d20ba8ff22..721807d78f 100644 --- a/src/Api/Controllers/FoldersController.cs +++ b/src/Api/Controllers/FoldersController.cs @@ -16,22 +16,22 @@ namespace Bit.Api.Controllers [Authorize("Application")] public class FoldersController : Controller { - private readonly IFolderRepository _folderRepository; + private readonly ICipherRepository _cipherRepository; private readonly UserManager _userManager; public FoldersController( - IFolderRepository folderRepository, + ICipherRepository cipherRepository, UserManager userManager) { - _folderRepository = folderRepository; + _cipherRepository = cipherRepository; _userManager = userManager; } [HttpGet("{id}")] public async Task Get(string id) { - var folder = await _folderRepository.GetByIdAsync(id, _userManager.GetUserId(User)); - if(folder == null) + var folder = await _cipherRepository.GetByIdAsync(new Guid(id), new Guid(_userManager.GetUserId(User))); + if(folder == null || folder.Type != Core.Enums.CipherType.Folder) { throw new NotFoundException(); } @@ -42,7 +42,7 @@ namespace Bit.Api.Controllers [HttpGet("")] public async Task> Get() { - ICollection folders = await _folderRepository.GetManyByUserIdAsync(_userManager.GetUserId(User)); + ICollection folders = await _cipherRepository.GetManyByTypeAndUserIdAsync(Core.Enums.CipherType.Folder, new Guid(_userManager.GetUserId(User))); var responses = folders.Select(f => new FolderResponseModel(f)); return new ListResponseModel(responses); } @@ -50,34 +50,34 @@ namespace Bit.Api.Controllers [HttpPost("")] public async Task Post([FromBody]FolderRequestModel model) { - var folder = model.ToFolder(_userManager.GetUserId(User)); - await _folderRepository.CreateAsync(folder); + var folder = model.ToCipher(_userManager.GetUserId(User)); + await _cipherRepository.CreateAsync(folder); return new FolderResponseModel(folder); } [HttpPut("{id}")] public async Task Put(string id, [FromBody]FolderRequestModel model) { - var folder = await _folderRepository.GetByIdAsync(id, _userManager.GetUserId(User)); - if(folder == null) + var folder = await _cipherRepository.GetByIdAsync(new Guid(id), new Guid(_userManager.GetUserId(User))); + if(folder == null || folder.Type != Core.Enums.CipherType.Folder) { throw new NotFoundException(); } - await _folderRepository.ReplaceAsync(model.ToFolder(folder)); + await _cipherRepository.ReplaceAsync(model.ToCipher(folder)); return new FolderResponseModel(folder); } [HttpDelete("{id}")] public async Task Delete(string id) { - var folder = await _folderRepository.GetByIdAsync(id, _userManager.GetUserId(User)); - if(folder == null) + var folder = await _cipherRepository.GetByIdAsync(new Guid(id), new Guid(_userManager.GetUserId(User))); + if(folder == null || folder.Type != Core.Enums.CipherType.Folder) { throw new NotFoundException(); } - await _folderRepository.DeleteAsync(folder); + await _cipherRepository.DeleteAsync(folder); } } } diff --git a/src/Api/Controllers/SitesController.cs b/src/Api/Controllers/SitesController.cs index 5b9cb6ecbd..e43fdbefe4 100644 --- a/src/Api/Controllers/SitesController.cs +++ b/src/Api/Controllers/SitesController.cs @@ -16,25 +16,22 @@ namespace Bit.Api.Controllers [Authorize("Application")] public class SitesController : Controller { - private readonly ISiteRepository _siteRepository; - private readonly IFolderRepository _folderRepository; + private readonly ICipherRepository _cipherRepository; private readonly UserManager _userManager; public SitesController( - ISiteRepository siteRepository, - IFolderRepository folderRepository, + ICipherRepository cipherRepository, UserManager userManager) { - _siteRepository = siteRepository; - _folderRepository = folderRepository; + _cipherRepository = cipherRepository; _userManager = userManager; } [HttpGet("{id}")] public async Task Get(string id, string[] expand = null) { - var site = await _siteRepository.GetByIdAsync(id, _userManager.GetUserId(User)); - if(site == null) + var site = await _cipherRepository.GetByIdAsync(new Guid(id), new Guid(_userManager.GetUserId(User))); + if(site == null || site.Type != Core.Enums.CipherType.Site) { throw new NotFoundException(); } @@ -47,7 +44,7 @@ namespace Bit.Api.Controllers [HttpGet("")] public async Task> Get(string[] expand = null) { - ICollection sites = await _siteRepository.GetManyByUserIdAsync(_userManager.GetUserId(User)); + ICollection sites = await _cipherRepository.GetManyByTypeAndUserIdAsync(Core.Enums.CipherType.Site, new Guid(_userManager.GetUserId(User))); var responses = sites.Select(s => new SiteResponseModel(s)).ToList(); await ExpandManyAsync(sites, responses, expand, null); return new ListResponseModel(responses); @@ -56,8 +53,8 @@ namespace Bit.Api.Controllers [HttpPost("")] public async Task Post([FromBody]SiteRequestModel model, string[] expand = null) { - var site = model.ToSite(_userManager.GetUserId(User)); - await _siteRepository.CreateAsync(site); + var site = model.ToCipher(_userManager.GetUserId(User)); + await _cipherRepository.CreateAsync(site); var response = new SiteResponseModel(site); await ExpandAsync(site, response, expand, null); @@ -67,13 +64,13 @@ namespace Bit.Api.Controllers [HttpPut("{id}")] public async Task Put(string id, [FromBody]SiteRequestModel model, string[] expand = null) { - var site = await _siteRepository.GetByIdAsync(id, _userManager.GetUserId(User)); - if(site == null) + var site = await _cipherRepository.GetByIdAsync(new Guid(id), new Guid(_userManager.GetUserId(User))); + if(site == null || site.Type != Core.Enums.CipherType.Site) { throw new NotFoundException(); } - await _siteRepository.ReplaceAsync(model.ToSite(site)); + await _cipherRepository.ReplaceAsync(model.ToCipher(site)); var response = new SiteResponseModel(site); await ExpandAsync(site, response, expand, null); @@ -83,34 +80,34 @@ namespace Bit.Api.Controllers [HttpDelete("{id}")] public async Task Delete(string id) { - var site = await _siteRepository.GetByIdAsync(id, _userManager.GetUserId(User)); - if(site == null) + var site = await _cipherRepository.GetByIdAsync(new Guid(id), new Guid(_userManager.GetUserId(User))); + if(site == null || site.Type != Core.Enums.CipherType.Site) { throw new NotFoundException(); } - await _siteRepository.DeleteAsync(site); + await _cipherRepository.DeleteAsync(site); } - private async Task ExpandAsync(Site site, SiteResponseModel response, string[] expand, Folder folder) + private async Task ExpandAsync(Cipher site, SiteResponseModel response, string[] expand, Cipher folder) { if(expand == null || expand.Count() == 0) { return; } - if(expand.Any(e => e.ToLower() == "folder")) + if(expand.Any(e => e.ToLower() == "folder") && site.FolderId.HasValue) { if(folder == null) { - folder = await _folderRepository.GetByIdAsync(site.FolderId); + folder = await _cipherRepository.GetByIdAsync(site.FolderId.Value); } response.Folder = new FolderResponseModel(folder); } } - private async Task ExpandManyAsync(IEnumerable sites, ICollection responses, string[] expand, IEnumerable folders) + private async Task ExpandManyAsync(IEnumerable sites, ICollection responses, string[] expand, IEnumerable folders) { if(expand == null || expand.Count() == 0) { @@ -121,14 +118,14 @@ namespace Bit.Api.Controllers { if(folders == null) { - folders = await _folderRepository.GetManyByUserIdAsync(_userManager.GetUserId(User)); + folders = await _cipherRepository.GetManyByTypeAndUserIdAsync(Core.Enums.CipherType.Folder, new Guid(_userManager.GetUserId(User))); } if(folders != null && folders.Count() > 0) { foreach(var response in responses) { - var site = sites.SingleOrDefault(s => s.Id == response.Id); + var site = sites.SingleOrDefault(s => s.Id.ToString() == response.Id); if(site == null) { continue; diff --git a/src/Api/Models/CipherDataModel.cs b/src/Api/Models/CipherDataModel.cs new file mode 100644 index 0000000000..11498769a2 --- /dev/null +++ b/src/Api/Models/CipherDataModel.cs @@ -0,0 +1,37 @@ +namespace Bit.Api.Models +{ + public class CipherDataModel + { + public CipherDataModel() { } + + public CipherDataModel(CipherRequestModel cipher) + { + Name = cipher.Name; + Uri = cipher.Uri; + Username = cipher.Username; + Password = cipher.Password; + Notes = cipher.Notes; + } + + public CipherDataModel(SiteRequestModel site) + { + Name = site.Name; + Uri = site.Uri; + Username = site.Username; + Password = site.Password; + Notes = site.Notes; + } + + public CipherDataModel(FolderRequestModel folder) + { + Name = folder.Name; + } + + 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; } + + } +} diff --git a/src/Api/Models/Request/CipherRequestModel.cs b/src/Api/Models/Request/CipherRequestModel.cs index 15fee5e09a..c060a82da6 100644 --- a/src/Api/Models/Request/CipherRequestModel.cs +++ b/src/Api/Models/Request/CipherRequestModel.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using Bit.Api.Utilities; using Bit.Core.Domains; -using System.Linq; using Bit.Core.Enums; +using Newtonsoft.Json; namespace Bit.Api.Models { @@ -34,42 +34,18 @@ namespace Bit.Api.Models [StringLength(5000)] public string Notes { get; set; } - public virtual Site ToSite(string userId = null) + public virtual Cipher ToCipher(string userId = null) { - return new Site + return new Cipher { - Id = Id, - UserId = userId, - FolderId = string.IsNullOrWhiteSpace(FolderId) ? null : FolderId, - Name = Name, - Uri = Uri, - Username = Username, - Password = Password, - Notes = string.IsNullOrWhiteSpace(Notes) ? null : Notes + Id = new Guid(Id), + UserId = new Guid(userId), + FolderId = string.IsNullOrWhiteSpace(FolderId) ? null : (Guid?)new Guid(FolderId), + Type = Type, + Data = JsonConvert.SerializeObject(new CipherDataModel(this), new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }) }; } - public Folder ToFolder(string userId = null) - { - return new Folder - { - Id = Id, - UserId = userId, - Name = Name - }; - } - - public static IEnumerable ToDynamicCiphers(CipherRequestModel[] models, string userId) - { - var sites = models.Where(m => m.Type == CipherType.Site).Select(m => m.ToSite(userId)).ToList(); - var folders = models.Where(m => m.Type == CipherType.Folder).Select(m => m.ToFolder(userId)).ToList(); - - var ciphers = new List(); - ciphers.AddRange(sites); - ciphers.AddRange(folders); - return ciphers; - } - public IEnumerable Validate(ValidationContext validationContext) { if(Type == CipherType.Site) diff --git a/src/Api/Models/Request/FolderRequestModel.cs b/src/Api/Models/Request/FolderRequestModel.cs index b1e3967ee1..f543e31228 100644 --- a/src/Api/Models/Request/FolderRequestModel.cs +++ b/src/Api/Models/Request/FolderRequestModel.cs @@ -2,6 +2,7 @@ using System.ComponentModel.DataAnnotations; using Bit.Api.Utilities; using Bit.Core.Domains; +using Newtonsoft.Json; namespace Bit.Api.Models { @@ -12,18 +13,20 @@ namespace Bit.Api.Models [StringLength(300)] public string Name { get; set; } - public Folder ToFolder(string userId = null) + public Cipher ToCipher(string userId = null) { - return new Folder + return new Cipher { - UserId = userId, - Name = Name + UserId = new Guid(userId), + Data = JsonConvert.SerializeObject(new CipherDataModel(this), new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }), + Type = Core.Enums.CipherType.Folder }; } - public Folder ToFolder(Folder existingFolder) + public Cipher ToCipher(Cipher existingFolder) { - existingFolder.Name = Name; + existingFolder.Data = JsonConvert.SerializeObject(new CipherDataModel(this), new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + existingFolder.Type = Core.Enums.CipherType.Folder; return existingFolder; } diff --git a/src/Api/Models/Request/SiteRequestModel.cs b/src/Api/Models/Request/SiteRequestModel.cs index 7579d0d979..bce7182455 100644 --- a/src/Api/Models/Request/SiteRequestModel.cs +++ b/src/Api/Models/Request/SiteRequestModel.cs @@ -2,6 +2,7 @@ using System.ComponentModel.DataAnnotations; using Bit.Api.Utilities; using Bit.Core.Domains; +using Newtonsoft.Json; namespace Bit.Api.Models { @@ -28,28 +29,22 @@ namespace Bit.Api.Models [StringLength(5000)] public string Notes { get; set; } - public Site ToSite(string userId = null) + public Cipher ToCipher(string userId = null) { - return new Site + return new Cipher { - UserId = userId, - FolderId = string.IsNullOrWhiteSpace(FolderId) ? null : FolderId, - Name = Name, - Uri = Uri, - Username = string.IsNullOrWhiteSpace(Username) ? null : Username, - Password = Password, - Notes = string.IsNullOrWhiteSpace(Notes) ? null : Notes + UserId = new Guid(userId), + FolderId = string.IsNullOrWhiteSpace(FolderId) ? null : (Guid?)new Guid(FolderId), + Data = JsonConvert.SerializeObject(new CipherDataModel(this), new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }), + Type = Core.Enums.CipherType.Site }; } - public Site ToSite(Site existingSite) + public Cipher ToCipher(Cipher existingSite) { - existingSite.FolderId = string.IsNullOrWhiteSpace(FolderId) ? null : FolderId; - existingSite.Name = Name; - existingSite.Uri = Uri; - existingSite.Username = string.IsNullOrWhiteSpace(Username) ? null : Username; - existingSite.Password = Password; - existingSite.Notes = string.IsNullOrWhiteSpace(Notes) ? null : Notes; + existingSite.FolderId = string.IsNullOrWhiteSpace(FolderId) ? null : (Guid?)new Guid(FolderId); + existingSite.Data = JsonConvert.SerializeObject(new CipherDataModel(this), new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + existingSite.Type = Core.Enums.CipherType.Site; return existingSite; } diff --git a/src/Api/Models/Response/FolderResponseModel.cs b/src/Api/Models/Response/FolderResponseModel.cs index 639d2f7ec8..6a0434d5ea 100644 --- a/src/Api/Models/Response/FolderResponseModel.cs +++ b/src/Api/Models/Response/FolderResponseModel.cs @@ -1,21 +1,29 @@ using System; using Bit.Core.Domains; +using Newtonsoft.Json; namespace Bit.Api.Models { public class FolderResponseModel : ResponseModel { - public FolderResponseModel(Folder folder) + public FolderResponseModel(Cipher cipher) : base("folder") { - if(folder == null) + if(cipher == null) { - throw new ArgumentNullException(nameof(folder)); + throw new ArgumentNullException(nameof(cipher)); } - Id = folder.Id; - Name = folder.Name; - RevisionDate = folder.RevisionDate; + if(cipher.Type != Core.Enums.CipherType.Folder) + { + throw new ArgumentException(nameof(cipher.Type)); + } + + var data = JsonConvert.DeserializeObject(cipher.Data); + + Id = cipher.Id.ToString(); + Name = data.Name; + RevisionDate = cipher.RevisionDate; } public string Id { get; set; } diff --git a/src/Api/Models/Response/ProfileResponseModel.cs b/src/Api/Models/Response/ProfileResponseModel.cs index 207f3af2fb..d3b3569732 100644 --- a/src/Api/Models/Response/ProfileResponseModel.cs +++ b/src/Api/Models/Response/ProfileResponseModel.cs @@ -13,7 +13,7 @@ namespace Bit.Api.Models throw new ArgumentNullException(nameof(user)); } - Id = user.Id; + Id = user.Id.ToString(); Name = user.Name; Email = user.Email; MasterPasswordHint = string.IsNullOrWhiteSpace(user.MasterPasswordHint) ? null : user.MasterPasswordHint; diff --git a/src/Api/Models/Response/SiteResponseModel.cs b/src/Api/Models/Response/SiteResponseModel.cs index c70d6e35a7..034ea8c7b6 100644 --- a/src/Api/Models/Response/SiteResponseModel.cs +++ b/src/Api/Models/Response/SiteResponseModel.cs @@ -1,26 +1,34 @@ using System; using Bit.Core.Domains; +using Newtonsoft.Json; namespace Bit.Api.Models { public class SiteResponseModel : ResponseModel { - public SiteResponseModel(Site site) + public SiteResponseModel(Cipher cipher) : base("site") { - if(site == null) + if(cipher == null) { - throw new ArgumentNullException(nameof(site)); + throw new ArgumentNullException(nameof(cipher)); } - Id = site.Id; - FolderId = string.IsNullOrWhiteSpace(site.FolderId) ? null : site.FolderId; - Name = site.Name; - Uri = site.Uri; - Username = site.Username; - Password = site.Password; - Notes = site.Notes; - RevisionDate = site.RevisionDate; + if(cipher.Type != Core.Enums.CipherType.Site) + { + throw new ArgumentException(nameof(cipher.Type)); + } + + var data = JsonConvert.DeserializeObject(cipher.Data); + + Id = cipher.Id.ToString(); + FolderId = cipher.FolderId?.ToString(); + Name = data.Name; + Uri = data.Uri; + Username = data.Username; + Password = data.Password; + Notes = data.Notes; + RevisionDate = cipher.RevisionDate; } public string Id { get; set; } diff --git a/src/Api/Startup.cs b/src/Api/Startup.cs index 1b9ca14592..8163ca0065 100644 --- a/src/Api/Startup.cs +++ b/src/Api/Startup.cs @@ -57,8 +57,6 @@ namespace Bit.Api // Repositories services.AddSingleton(s => new Repos.UserRepository(globalSettings.SqlServer.ConnectionString)); - services.AddSingleton(s => new Repos.SiteRepository(globalSettings.SqlServer.ConnectionString)); - services.AddSingleton(s => new Repos.FolderRepository(globalSettings.SqlServer.ConnectionString)); services.AddSingleton(s => new Repos.CipherRepository(globalSettings.SqlServer.ConnectionString)); // Context diff --git a/src/Core/Domains/Cipher.cs b/src/Core/Domains/Cipher.cs index 36d1a1a34a..4a85934925 100644 --- a/src/Core/Domains/Cipher.cs +++ b/src/Core/Domains/Cipher.cs @@ -1,13 +1,21 @@ using System; +using Bit.Core.Utilities; namespace Bit.Core.Domains { - public abstract class Cipher : IDataObject + public class Cipher : IDataObject { - 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(); + } } } diff --git a/src/Core/Domains/Folder.cs b/src/Core/Domains/Folder.cs deleted file mode 100644 index 65e9d1d6cc..0000000000 --- a/src/Core/Domains/Folder.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Bit.Core.Domains -{ - public class Folder : Cipher, IDataObject - { - } -} diff --git a/src/Core/Domains/Site.cs b/src/Core/Domains/Site.cs deleted file mode 100644 index f3b7373d0d..0000000000 --- a/src/Core/Domains/Site.cs +++ /dev/null @@ -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; } - } -} diff --git a/src/Core/Domains/User.cs b/src/Core/Domains/User.cs index 1f4fcab88f..0a8bda587d 100644 --- a/src/Core/Domains/User.cs +++ b/src/Core/Domains/User.cs @@ -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 { - 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(); + } } } diff --git a/src/Core/Enums/CipherType.cs b/src/Core/Enums/CipherType.cs index 6e9bb3d8c9..b38ebf4552 100644 --- a/src/Core/Enums/CipherType.cs +++ b/src/Core/Enums/CipherType.cs @@ -1,6 +1,6 @@ namespace Bit.Core.Enums { - public enum CipherType + public enum CipherType : short { Folder = 0, Site = 1 diff --git a/src/Core/IDataObject.cs b/src/Core/IDataObject.cs index dc32a1f1e3..4796c75771 100644 --- a/src/Core/IDataObject.cs +++ b/src/Core/IDataObject.cs @@ -1,7 +1,10 @@ -namespace Bit.Core +using System; + +namespace Bit.Core { - public interface IDataObject + public interface IDataObject where T : IEquatable { - string Id { get; set; } + T Id { get; set; } + void SetNewId(); } } diff --git a/src/Core/Identity/JwtBearerEventImplementations.cs b/src/Core/Identity/JwtBearerEventImplementations.cs index 3ac80112ee..0045c6ed5f 100644 --- a/src/Core/Identity/JwtBearerEventImplementations.cs +++ b/src/Core/Identity/JwtBearerEventImplementations.cs @@ -26,7 +26,7 @@ namespace Bit.Core.Identity var signInManager = context.HttpContext.RequestServices.GetRequiredService(); 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)) diff --git a/src/Core/Identity/UserStore.cs b/src/Core/Identity/UserStore.cs index 60220bf8ea..9a1565de70 100644 --- a/src/Core/Identity/UserStore.cs +++ b/src/Core/Identity/UserStore.cs @@ -55,12 +55,14 @@ namespace Bit.Core.Identity public async Task 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 FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken = default(CancellationToken)) @@ -100,7 +102,7 @@ namespace Bit.Core.Identity public Task GetUserIdAsync(User user, CancellationToken cancellationToken = default(CancellationToken)) { - return Task.FromResult(user.Id); + return Task.FromResult(user.Id.ToString()); } public Task GetUserNameAsync(User user, CancellationToken cancellationToken = default(CancellationToken)) diff --git a/src/Core/Repositories/ICipherRepository.cs b/src/Core/Repositories/ICipherRepository.cs index 02db3f9190..f16f70c16f 100644 --- a/src/Core/Repositories/ICipherRepository.cs +++ b/src/Core/Repositories/ICipherRepository.cs @@ -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 { - Task UpdateUserEmailPasswordAndCiphersAsync(User user, IEnumerable ciphers); - Task CreateAsync(IEnumerable ciphers); + Task GetByIdAsync(Guid id, Guid userId); + Task> GetManyByUserIdAsync(Guid userId); + Task> GetManyByTypeAndUserIdAsync(Enums.CipherType type, Guid userId); + Task UpdateUserEmailPasswordAndCiphersAsync(User user, IEnumerable ciphers); + Task CreateAsync(IEnumerable ciphers); } } diff --git a/src/Core/Repositories/IFolderRepository.cs b/src/Core/Repositories/IFolderRepository.cs deleted file mode 100644 index 3d4ce6ade9..0000000000 --- a/src/Core/Repositories/IFolderRepository.cs +++ /dev/null @@ -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 - { - Task GetByIdAsync(string id, string userId); - Task> GetManyByUserIdAsync(string userId); - } -} diff --git a/src/Core/Repositories/IRepository.cs b/src/Core/Repositories/IRepository.cs index 0ac498d599..71d0f15a59 100644 --- a/src/Core/Repositories/IRepository.cs +++ b/src/Core/Repositories/IRepository.cs @@ -1,14 +1,15 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; namespace Bit.Core.Repositories { - public interface IRepository where T : IDataObject + public interface IRepository where TId : IEquatable where T : class, IDataObject { - Task GetByIdAsync(string id); + Task 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); } } diff --git a/src/Core/Repositories/ISiteRepository.cs b/src/Core/Repositories/ISiteRepository.cs deleted file mode 100644 index f9aeeba8ab..0000000000 --- a/src/Core/Repositories/ISiteRepository.cs +++ /dev/null @@ -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 - { - Task GetByIdAsync(string id, string userId); - Task> GetManyByUserIdAsync(string userId); - } -} diff --git a/src/Core/Repositories/IUserRepository.cs b/src/Core/Repositories/IUserRepository.cs index 2278c9ab8e..a92175dda5 100644 --- a/src/Core/Repositories/IUserRepository.cs +++ b/src/Core/Repositories/IUserRepository.cs @@ -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 + public interface IUserRepository : IRepository { Task GetByEmailAsync(string email); } diff --git a/src/Core/Repositories/SqlServer/BaseRepository.cs b/src/Core/Repositories/SqlServer/BaseRepository.cs index 7ad8feba11..fd25e41812 100644 --- a/src/Core/Repositories/SqlServer/BaseRepository.cs +++ b/src/Core/Repositories/SqlServer/BaseRepository.cs @@ -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; } - - /// - /// Generate sequential Guid for Sql Server. - /// ref: https://github.com/nhibernate/nhibernate-core/blob/master/src/NHibernate/Id/GuidCombGenerator.cs - /// - /// A comb Guid. - 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); - } } } diff --git a/src/Core/Repositories/SqlServer/CipherRepository.cs b/src/Core/Repositories/SqlServer/CipherRepository.cs index 71dd4c0c09..ebe4598296 100644 --- a/src/Core/Repositories/SqlServer/CipherRepository.cs +++ b/src/Core/Repositories/SqlServer/CipherRepository.cs @@ -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, ICipherRepository { public CipherRepository(string connectionString) : base(connectionString) { } - public Task UpdateUserEmailPasswordAndCiphersAsync(User user, IEnumerable ciphers) + public async Task 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> GetManyByUserIdAsync(Guid userId) + { + using(var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.QueryAsync( + $"[{Schema}].[{Table}_ReadByUserId]", + new { UserId = userId }, + commandType: CommandType.StoredProcedure); + + return results.ToList(); + } + } + + public async Task> GetManyByTypeAndUserIdAsync(Enums.CipherType type, Guid userId) + { + using(var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.QueryAsync( + $"[{Schema}].[{Table}_ReadByTypeUserId]", + new + { + Type = type, + UserId = userId + }, + commandType: CommandType.StoredProcedure); + + return results.ToList(); + } + } + + public Task UpdateUserEmailPasswordAndCiphersAsync(User user, IEnumerable 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().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().AddAllPropertiesAsColumns()); + bulkCopy.DestinationTableName = "#TempCipher"; + var dataTable = ciphers.ToTable(new ClassMapping().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 ciphers) + public Task CreateAsync(IEnumerable 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().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().AddAllPropertiesAsColumns()); - + bulkCopy.DestinationTableName = "[dbo].[Cipher]"; + var dataTable = ciphers.ToTable(new ClassMapping().AddAllPropertiesAsColumns()); bulkCopy.WriteToServer(dataTable); } diff --git a/src/Core/Repositories/SqlServer/FolderRepository.cs b/src/Core/Repositories/SqlServer/FolderRepository.cs deleted file mode 100644 index fb1b9b9083..0000000000 --- a/src/Core/Repositories/SqlServer/FolderRepository.cs +++ /dev/null @@ -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, IFolderRepository - { - public FolderRepository(string connectionString) - : base(connectionString) - { } - - public async Task GetByIdAsync(string id, string userId) - { - var folder = await GetByIdAsync(id); - if(folder == null || folder.UserId != userId) - { - return null; - } - - return folder; - } - - public async Task> GetManyByUserIdAsync(string userId) - { - using(var connection = new SqlConnection(ConnectionString)) - { - var results = await connection.QueryAsync( - $"[{Schema}].[{Table}_ReadByUserId]", - new { UserId = new Guid(userId) }, - commandType: CommandType.StoredProcedure); - - return results.Select(f => f.ToDomain()).ToList(); - } - } - } -} diff --git a/src/Core/Repositories/SqlServer/Models/FolderTableModel.cs b/src/Core/Repositories/SqlServer/Models/FolderTableModel.cs deleted file mode 100644 index f19618aa19..0000000000 --- a/src/Core/Repositories/SqlServer/Models/FolderTableModel.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using Bit.Core.Domains; - -namespace Bit.Core.Repositories.SqlServer.Models -{ - public class FolderTableModel : ITableModel - { - 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 - }; - } - } -} diff --git a/src/Core/Repositories/SqlServer/Models/ITableModel.cs b/src/Core/Repositories/SqlServer/Models/ITableModel.cs deleted file mode 100644 index ac581e78c2..0000000000 --- a/src/Core/Repositories/SqlServer/Models/ITableModel.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Bit.Core.Repositories.SqlServer.Models -{ - public interface ITableModel - { - T ToDomain(); - } -} diff --git a/src/Core/Repositories/SqlServer/Models/SiteTableModel.cs b/src/Core/Repositories/SqlServer/Models/SiteTableModel.cs deleted file mode 100644 index 7ecb968eab..0000000000 --- a/src/Core/Repositories/SqlServer/Models/SiteTableModel.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using Bit.Core.Domains; - -namespace Bit.Core.Repositories.SqlServer.Models -{ - public class SiteTableModel : ITableModel - { - 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 - }; - } - } -} diff --git a/src/Core/Repositories/SqlServer/Models/UserTableModel.cs b/src/Core/Repositories/SqlServer/Models/UserTableModel.cs deleted file mode 100644 index 9aba406ee5..0000000000 --- a/src/Core/Repositories/SqlServer/Models/UserTableModel.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using Bit.Core.Domains; -using Bit.Core.Enums; - -namespace Bit.Core.Repositories.SqlServer.Models -{ - public class UserTableModel : ITableModel - { - 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 - }; - } - } -} diff --git a/src/Core/Repositories/SqlServer/Repository.cs b/src/Core/Repositories/SqlServer/Repository.cs index 0594ab4ced..d807b395ae 100644 --- a/src/Core/Repositories/SqlServer/Repository.cs +++ b/src/Core/Repositories/SqlServer/Repository.cs @@ -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 : BaseRepository, IRepository where T : IDataObject where TModel : ITableModel + public abstract class Repository : BaseRepository, IRepository where TId : IEquatable where T : class, IDataObject { 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 GetByIdAsync(string id) + public virtual async Task GetByIdAsync(TId id) { using(var connection = new SqlConnection(ConnectionString)) { - var results = await connection.QueryAsync( + var results = await connection.QueryAsync( $"[{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); } } diff --git a/src/Core/Repositories/SqlServer/SiteRepository.cs b/src/Core/Repositories/SqlServer/SiteRepository.cs deleted file mode 100644 index 8ec07ba453..0000000000 --- a/src/Core/Repositories/SqlServer/SiteRepository.cs +++ /dev/null @@ -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, ISiteRepository - { - public SiteRepository(string connectionString) - : base(connectionString) - { } - - public async Task GetByIdAsync(string id, string userId) - { - var site = await GetByIdAsync(id); - if(site == null || site.UserId != userId) - { - return null; - } - - return site; - } - - public async Task> GetManyByUserIdAsync(string userId) - { - using(var connection = new SqlConnection(ConnectionString)) - { - var results = await connection.QueryAsync( - $"[{Schema}].[{Table}_ReadByUserId]", - new { UserId = new Guid(userId) }, - commandType: CommandType.StoredProcedure); - - return results.Select(s => s.ToDomain()).ToList(); - } - } - } -} diff --git a/src/Core/Repositories/SqlServer/UserRepository.cs b/src/Core/Repositories/SqlServer/UserRepository.cs index dbfe044994..5d9fda57c1 100644 --- a/src/Core/Repositories/SqlServer/UserRepository.cs +++ b/src/Core/Repositories/SqlServer/UserRepository.cs @@ -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, IUserRepository + public class UserRepository : Repository, 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( + var results = await connection.QueryAsync( $"[{Schema}].[{Table}_ReadByEmail]", new { Email = email }, commandType: CommandType.StoredProcedure); - var model = results.FirstOrDefault(); - if(model == null) - { - return null; - } - - return model.ToDomain(); + return results.SingleOrDefault(); } } } diff --git a/src/Core/Services/CipherService.cs b/src/Core/Services/CipherService.cs index 954692bfb9..6a568a6435 100644 --- a/src/Core/Services/CipherService.cs +++ b/src/Core/Services/CipherService.cs @@ -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 folders, - List sites, - IEnumerable> siteRelationships) + List folders, + List ciphers, + IEnumerable> folderRelationships) { // create all the folders var folderTasks = new List(); 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); } } } diff --git a/src/Core/Services/ICipherService.cs b/src/Core/Services/ICipherService.cs index 32d7f0c48a..16f9cc8dc8 100644 --- a/src/Core/Services/ICipherService.cs +++ b/src/Core/Services/ICipherService.cs @@ -6,6 +6,6 @@ namespace Bit.Core.Services { public interface ICipherService { - Task ImportCiphersAsync(List folders, List sites, IEnumerable> siteRelationships); + Task ImportCiphersAsync(List folders, List ciphers, IEnumerable> folderRelationships); } } diff --git a/src/Core/Services/IUserService.cs b/src/Core/Services/IUserService.cs index 0c86f32a97..3e45c78fa3 100644 --- a/src/Core/Services/IUserService.cs +++ b/src/Core/Services/IUserService.cs @@ -8,13 +8,13 @@ namespace Bit.Core.Services { public interface IUserService { - Task GetUserByIdAsync(string userId); + Task GetUserByIdAsync(Guid userId); Task SaveUserAsync(User user); Task RegisterUserAsync(User user, string masterPassword); Task SendMasterPasswordHintAsync(string email); Task InitiateEmailChangeAsync(User user, string newEmail); - Task ChangeEmailAsync(User user, string masterPassword, string newEmail, string newMasterPassword, string token, IEnumerable ciphers); - Task ChangePasswordAsync(User user, string currentMasterPasswordHash, string newMasterPasswordHash, IEnumerable ciphers); + Task ChangeEmailAsync(User user, string masterPassword, string newEmail, string newMasterPassword, string token, IEnumerable ciphers); + Task ChangePasswordAsync(User user, string currentMasterPasswordHash, string newMasterPasswordHash, IEnumerable ciphers); Task RefreshSecurityStampAsync(User user, string masterPasswordHash); Task GetTwoFactorAsync(User user, Enums.TwoFactorProvider provider); Task DeleteAsync(User user); diff --git a/src/Core/Services/UserService.cs b/src/Core/Services/UserService.cs index 15c2fe7d66..e1a2a88d80 100644 --- a/src/Core/Services/UserService.cs +++ b/src/Core/Services/UserService.cs @@ -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 GetUserByIdAsync(string userId) + public async Task 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 ChangeEmailAsync(User user, string masterPassword, string newEmail, string newMasterPassword, string token, IEnumerable ciphers) + public async Task ChangeEmailAsync(User user, string masterPassword, string newEmail, string newMasterPassword, string token, IEnumerable 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 ChangePasswordAsync(User user, string masterPassword, string newMasterPassword, IEnumerable ciphers) + public async Task ChangePasswordAsync(User user, string masterPassword, string newMasterPassword, IEnumerable ciphers) { if(user == null) { diff --git a/src/Core/Utilities/CoreHelpers.cs b/src/Core/Utilities/CoreHelpers.cs new file mode 100644 index 0000000000..e1d1d9951f --- /dev/null +++ b/src/Core/Utilities/CoreHelpers.cs @@ -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; + + /// + /// Generate sequential Guid for Sql Server. + /// ref: https://github.com/nhibernate/nhibernate-core/blob/master/src/NHibernate/Id/GuidCombGenerator.cs + /// + /// A comb Guid. + 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); + } + } +} diff --git a/src/Sql/Sql.sqlproj b/src/Sql/Sql.sqlproj index 3eebc0ec43..51d4ffd9a9 100644 --- a/src/Sql/Sql.sqlproj +++ b/src/Sql/Sql.sqlproj @@ -76,7 +76,7 @@ - + diff --git a/src/Sql/dbo/Stored Procedures/Cipher_Create.sql b/src/Sql/dbo/Stored Procedures/Cipher_Create.sql index 569dc71e8c..999bc4874f 100644 --- a/src/Sql/dbo/Stored Procedures/Cipher_Create.sql +++ b/src/Sql/dbo/Stored Procedures/Cipher_Create.sql @@ -8,6 +8,8 @@ @RevisionDate DATETIME2(7) AS BEGIN + SET NOCOUNT ON + INSERT INTO [dbo].[Cipher] ( [Id], diff --git a/src/Sql/dbo/Stored Procedures/Cipher_DeleteById.sql b/src/Sql/dbo/Stored Procedures/Cipher_DeleteById.sql index 29b81210ef..3439c5bf67 100644 --- a/src/Sql/dbo/Stored Procedures/Cipher_DeleteById.sql +++ b/src/Sql/dbo/Stored Procedures/Cipher_DeleteById.sql @@ -2,6 +2,8 @@ @Id UNIQUEIDENTIFIER AS BEGIN + SET NOCOUNT ON + BEGIN TRANSACTION Cipher_DeleteById UPDATE diff --git a/src/Sql/dbo/Stored Procedures/Cipher_ReadById.sql b/src/Sql/dbo/Stored Procedures/Cipher_ReadById.sql index f098517ca4..66ea3bceb8 100644 --- a/src/Sql/dbo/Stored Procedures/Cipher_ReadById.sql +++ b/src/Sql/dbo/Stored Procedures/Cipher_ReadById.sql @@ -2,6 +2,8 @@ @Id UNIQUEIDENTIFIER AS BEGIN + SET NOCOUNT ON + SELECT * FROM diff --git a/src/Sql/dbo/Stored Procedures/Cipher_ReadByTypeUserId.sql b/src/Sql/dbo/Stored Procedures/Cipher_ReadByTypeUserId.sql new file mode 100644 index 0000000000..9e7d8ce652 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/Cipher_ReadByTypeUserId.sql @@ -0,0 +1,15 @@ +CREATE PROCEDURE [dbo].[Cipher_ReadByTypeUserId] + @Type TINYINT, + @UserId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + SELECT + * + FROM + [dbo].[CipherView] + WHERE + [Type] = @Type + AND [UserId] = @UserId +END diff --git a/src/Sql/dbo/Stored Procedures/Cipher_ReadByUserId.sql b/src/Sql/dbo/Stored Procedures/Cipher_ReadByUserId.sql index c9e1528750..18bc10285b 100644 --- a/src/Sql/dbo/Stored Procedures/Cipher_ReadByUserId.sql +++ b/src/Sql/dbo/Stored Procedures/Cipher_ReadByUserId.sql @@ -2,6 +2,8 @@ @UserId UNIQUEIDENTIFIER AS BEGIN + SET NOCOUNT ON + SELECT * FROM diff --git a/src/Sql/dbo/Stored Procedures/Cipher_Update.sql b/src/Sql/dbo/Stored Procedures/Cipher_Update.sql index c2cd7d7f52..2ba9b699a8 100644 --- a/src/Sql/dbo/Stored Procedures/Cipher_Update.sql +++ b/src/Sql/dbo/Stored Procedures/Cipher_Update.sql @@ -8,6 +8,8 @@ @RevisionDate DATETIME2(7) AS BEGIN + SET NOCOUNT ON + UPDATE [dbo].[Cipher] SET diff --git a/src/Sql/dbo/Stored Procedures/History_Create.sql b/src/Sql/dbo/Stored Procedures/History_Create.sql deleted file mode 100644 index 81d65673fa..0000000000 --- a/src/Sql/dbo/Stored Procedures/History_Create.sql +++ /dev/null @@ -1,22 +0,0 @@ -CREATE PROCEDURE [dbo].[History_Create] - @UserId UNIQUEIDENTIFIER, - @CipherId UNIQUEIDENTIFIER, - @Event TINYINT, - @Date DATETIME2(7) -AS -BEGIN - INSERT INTO [dbo].[History] - ( - [UserId], - [CipherId], - [Event], - [Date] - ) - VALUES - ( - @UserId, - @CipherId, - @Event, - @Date - ) -END diff --git a/src/Sql/dbo/Stored Procedures/History_ReadById.sql b/src/Sql/dbo/Stored Procedures/History_ReadById.sql index 48b528e63d..53343f12b3 100644 --- a/src/Sql/dbo/Stored Procedures/History_ReadById.sql +++ b/src/Sql/dbo/Stored Procedures/History_ReadById.sql @@ -2,6 +2,8 @@ @Id BIGINT AS BEGIN + SET NOCOUNT ON + SELECT * FROM diff --git a/src/Sql/dbo/Stored Procedures/User_Create.sql b/src/Sql/dbo/Stored Procedures/User_Create.sql index 8a6b71e255..dc31c299db 100644 --- a/src/Sql/dbo/Stored Procedures/User_Create.sql +++ b/src/Sql/dbo/Stored Procedures/User_Create.sql @@ -14,6 +14,8 @@ @RevisionDate DATETIME2(7) AS BEGIN + SET NOCOUNT ON + INSERT INTO [dbo].[User] ( [Id], diff --git a/src/Sql/dbo/Stored Procedures/User_DeleteById.sql b/src/Sql/dbo/Stored Procedures/User_DeleteById.sql index c2d1c93f52..84223bb56c 100644 --- a/src/Sql/dbo/Stored Procedures/User_DeleteById.sql +++ b/src/Sql/dbo/Stored Procedures/User_DeleteById.sql @@ -2,6 +2,8 @@ @Id UNIQUEIDENTIFIER AS BEGIN + SET NOCOUNT ON + BEGIN TRANSACTION User_DeleteById DELETE diff --git a/src/Sql/dbo/Stored Procedures/User_ReadByEmail.sql b/src/Sql/dbo/Stored Procedures/User_ReadByEmail.sql index d84fad0bd4..59be670171 100644 --- a/src/Sql/dbo/Stored Procedures/User_ReadByEmail.sql +++ b/src/Sql/dbo/Stored Procedures/User_ReadByEmail.sql @@ -2,6 +2,8 @@ @Email NVARCHAR(50) AS BEGIN + SET NOCOUNT ON + SELECT * FROM diff --git a/src/Sql/dbo/Stored Procedures/User_ReadById.sql b/src/Sql/dbo/Stored Procedures/User_ReadById.sql index 0ae4d05c3d..da50844752 100644 --- a/src/Sql/dbo/Stored Procedures/User_ReadById.sql +++ b/src/Sql/dbo/Stored Procedures/User_ReadById.sql @@ -2,6 +2,8 @@ @Id UNIQUEIDENTIFIER AS BEGIN + SET NOCOUNT ON + SELECT * FROM diff --git a/src/Sql/dbo/Stored Procedures/User_Update.sql b/src/Sql/dbo/Stored Procedures/User_Update.sql index d2d5583b46..9bacb1813f 100644 --- a/src/Sql/dbo/Stored Procedures/User_Update.sql +++ b/src/Sql/dbo/Stored Procedures/User_Update.sql @@ -14,6 +14,8 @@ @RevisionDate DATETIME2(7) AS BEGIN + SET NOCOUNT ON + UPDATE [dbo].[User] SET diff --git a/src/Sql/dbo/Stored Procedures/User_UpdateEmailPassword.sql b/src/Sql/dbo/Stored Procedures/User_UpdateEmailPassword.sql index b83d81619e..42fb059e76 100644 --- a/src/Sql/dbo/Stored Procedures/User_UpdateEmailPassword.sql +++ b/src/Sql/dbo/Stored Procedures/User_UpdateEmailPassword.sql @@ -7,6 +7,8 @@ @RevisionDate DATETIME2(7) AS BEGIN + SET NOCOUNT ON + UPDATE [dbo].[User] SET diff --git a/src/Sql/dbo/Tables/Cipher.sql b/src/Sql/dbo/Tables/Cipher.sql index 9e981e6878..b74560448f 100644 --- a/src/Sql/dbo/Tables/Cipher.sql +++ b/src/Sql/dbo/Tables/Cipher.sql @@ -22,40 +22,75 @@ CREATE TRIGGER [dbo].[Cipher_Inserted] ON [dbo].[Cipher] AFTER INSERT AS BEGIN - SET NOCOUNT ON - - DECLARE @UserId UNIQUEIDENTIFIER = (SELECT [UserId] FROM INSERTED) - DECLARE @CipherId UNIQUEIDENTIFIER = (SELECT [Id] FROM INSERTED) - DECLARE @Date DATETIME2(7) = (SELECT [CreationDate] FROM INSERTED) - - EXEC [dbo].[History_Create] @UserId, @CipherId, 0 /* Insert */, @Date -END + DECLARE @Count INT = (SELECT COUNT(1) FROM INSERTED) + IF @Count = 0 RETURN + SET NOCOUNT ON + + INSERT INTO [dbo].[History] + ( + [UserId], + [CipherId], + [Event], + [Date] + ) + SELECT + [UserId], + [Id], + 0, --Insert + [CreationDate] + FROM + INSERTED +END GO CREATE TRIGGER [dbo].[Cipher_Updated] ON [dbo].[Cipher] AFTER UPDATE AS BEGIN - SET NOCOUNT ON - - DECLARE @UserId UNIQUEIDENTIFIER = (SELECT [UserId] FROM INSERTED) - DECLARE @CipherId UNIQUEIDENTIFIER = (SELECT [Id] FROM INSERTED) - DECLARE @Date DATETIME2(7) = (SELECT [RevisionDate] FROM INSERTED) - - EXEC [dbo].[History_Create] @UserId, @CipherId, 1 /* Update */, @Date -END + DECLARE @Count INT = (SELECT COUNT(1) FROM INSERTED) + IF @Count = 0 RETURN + SET NOCOUNT ON + + INSERT INTO [dbo].[History] + ( + [UserId], + [CipherId], + [Event], + [Date] + ) + SELECT + [UserId], + [Id], + 1, --Update + [RevisionDate] + FROM + INSERTED +END GO CREATE TRIGGER [dbo].[Cipher_Deleted] ON [dbo].[Cipher] AFTER DELETE AS BEGIN + DECLARE @Count INT = (SELECT COUNT(1) FROM DELETED) + IF @Count = 0 RETURN + SET NOCOUNT ON - - DECLARE @UserId UNIQUEIDENTIFIER = (SELECT [UserId] FROM DELETED) - DECLARE @CipherId UNIQUEIDENTIFIER = (SELECT [Id] FROM DELETED) - - EXEC [dbo].[History_Create] @UserId, @CipherId, 2 /* Delete */, GETUTCDATE + + INSERT INTO [dbo].[History] + ( + [UserId], + [CipherId], + [Event], + [Date] + ) + SELECT + [UserId], + [Id], + 2, --Delete + GETUTCDATE() + FROM + DELETED END diff --git a/src/Sql/dbo/Tables/History.sql b/src/Sql/dbo/Tables/History.sql index c5e994aa98..e86b6d30d2 100644 --- a/src/Sql/dbo/Tables/History.sql +++ b/src/Sql/dbo/Tables/History.sql @@ -4,8 +4,6 @@ [CipherId] UNIQUEIDENTIFIER NOT NULL, [Event] TINYINT NOT NULL, [Date] DATETIME2 (7) NOT NULL, - CONSTRAINT [PK_CipherHistory] PRIMARY KEY CLUSTERED ([Id] ASC), - CONSTRAINT [FK_CipherHistory_Cipher] FOREIGN KEY ([CipherId]) REFERENCES [dbo].[Cipher] ([Id]), - CONSTRAINT [FK_CipherHistory_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]) + CONSTRAINT [PK_CipherHistory] PRIMARY KEY CLUSTERED ([Id] ASC) );