From 89e524e1e4c58be7f4bcde535f1d40328846a83c Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Wed, 8 Jun 2016 20:40:20 -0400 Subject: [PATCH] added cipher history API for data syncing with client databases --- src/Api/Controllers/CiphersController.cs | 8 +++++ .../Response/CipherHistoryResponseModel.cs | 30 +++++++++++++++++++ src/Core/Repositories/ICipherRepository.cs | 2 ++ .../SqlServer/CipherRepository.cs | 21 +++++++++++++ src/Sql/Sql.sqlproj | 1 + ...er_ReadByRevisionDateWithDeleteHistory.sql | 24 +++++++++++++++ 6 files changed, 86 insertions(+) create mode 100644 src/Api/Models/Response/CipherHistoryResponseModel.cs create mode 100644 src/Sql/dbo/Stored Procedures/Cipher_ReadByRevisionDateWithDeleteHistory.sql diff --git a/src/Api/Controllers/CiphersController.cs b/src/Api/Controllers/CiphersController.cs index 57d0710fd7..8d9b82bf1a 100644 --- a/src/Api/Controllers/CiphersController.cs +++ b/src/Api/Controllers/CiphersController.cs @@ -50,6 +50,14 @@ namespace Bit.Api.Controllers return new ListResponseModel(responses); } + [HttpGet("history")] + public async Task Get(DateTime since) + { + var history = await _cipherRepository.GetManySinceRevisionDateAndUserIdWithDeleteHistoryAsync( + since, new Guid(_userManager.GetUserId(User))); + return new CipherHistoryResponseModel(history.Item1, history.Item2); + } + [HttpPost("import")] public async Task PostImport([FromBody]ImportRequestModel model) { diff --git a/src/Api/Models/Response/CipherHistoryResponseModel.cs b/src/Api/Models/Response/CipherHistoryResponseModel.cs new file mode 100644 index 0000000000..d2fab805b1 --- /dev/null +++ b/src/Api/Models/Response/CipherHistoryResponseModel.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Bit.Core.Domains; + +namespace Bit.Api.Models +{ + public class CipherHistoryResponseModel : ResponseModel + { + public CipherHistoryResponseModel(IEnumerable revisedCiphers, IEnumerable deletedIds) + : base("cipherHistory") + { + if(revisedCiphers == null) + { + throw new ArgumentNullException(nameof(revisedCiphers)); + } + + if(deletedIds == null) + { + throw new ArgumentNullException(nameof(deletedIds)); + } + + Revised = revisedCiphers.Select(c => new CipherResponseModel(c)); + Deleted = deletedIds.Select(id => id.ToString()); + } + + public IEnumerable Revised { get; set; } + public IEnumerable Deleted { get; set; } + } +} diff --git a/src/Core/Repositories/ICipherRepository.cs b/src/Core/Repositories/ICipherRepository.cs index f16f70c16f..612bcfb537 100644 --- a/src/Core/Repositories/ICipherRepository.cs +++ b/src/Core/Repositories/ICipherRepository.cs @@ -10,6 +10,8 @@ namespace Bit.Core.Repositories Task GetByIdAsync(Guid id, Guid userId); Task> GetManyByUserIdAsync(Guid userId); Task> GetManyByTypeAndUserIdAsync(Enums.CipherType type, Guid userId); + Task, ICollection>> + GetManySinceRevisionDateAndUserIdWithDeleteHistoryAsync(DateTime sinceRevisionDate, Guid userId); Task UpdateUserEmailPasswordAndCiphersAsync(User user, IEnumerable ciphers); Task CreateAsync(IEnumerable ciphers); } diff --git a/src/Core/Repositories/SqlServer/CipherRepository.cs b/src/Core/Repositories/SqlServer/CipherRepository.cs index ebe4598296..037f594cf3 100644 --- a/src/Core/Repositories/SqlServer/CipherRepository.cs +++ b/src/Core/Repositories/SqlServer/CipherRepository.cs @@ -57,6 +57,27 @@ namespace Bit.Core.Repositories.SqlServer } } + public async Task, ICollection>> + GetManySinceRevisionDateAndUserIdWithDeleteHistoryAsync(DateTime sinceRevisionDate, Guid userId) + { + using(var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.QueryMultipleAsync( + $"[{Schema}].[{Table}_ReadByRevisionDateUserWithDeleteHistory]", + new + { + SinceRevisionDate = sinceRevisionDate, + UserId = userId + }, + commandType: CommandType.StoredProcedure); + + var ciphers = await results.ReadAsync(); + var deletes = await results.ReadAsync(); + + return new Tuple, ICollection>(ciphers.ToList(), deletes.ToList()); + } + } + public Task UpdateUserEmailPasswordAndCiphersAsync(User user, IEnumerable ciphers) { if(ciphers.Count() == 0) diff --git a/src/Sql/Sql.sqlproj b/src/Sql/Sql.sqlproj index 51d4ffd9a9..5d3a6f852b 100644 --- a/src/Sql/Sql.sqlproj +++ b/src/Sql/Sql.sqlproj @@ -84,5 +84,6 @@ + \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/Cipher_ReadByRevisionDateWithDeleteHistory.sql b/src/Sql/dbo/Stored Procedures/Cipher_ReadByRevisionDateWithDeleteHistory.sql new file mode 100644 index 0000000000..8f5d2df1f8 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/Cipher_ReadByRevisionDateWithDeleteHistory.sql @@ -0,0 +1,24 @@ +CREATE PROCEDURE [dbo].[Cipher_ReadByRevisionDateUserWithDeleteHistory] + @SinceRevisionDate DATETIME2(7), + @UserId UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + SELECT + * + FROM + [dbo].[CipherView] + WHERE + [RevisionDate] > @SinceRevisionDate + AND [UserId] = @UserId + + SELECT + [CipherId] + FROM + [dbo].[History] + WHERE + [Date] > @SinceRevisionDate + AND [Event] = 2 -- Only cipher delete events. + AND [UserId] = @UserId +END