From ca8e3f496e7cf2052e898f3a86cae8ffa7750380 Mon Sep 17 00:00:00 2001 From: Jake Fink Date: Tue, 12 Dec 2023 11:58:34 -0500 Subject: [PATCH] [PM-3797 Part 4] Add Sends to new Key Rotation (#3442) * add send validation * add send repo methods * add send rotation to delegate list * add success test --- .../Auth/Controllers/AccountsController.cs | 6 +- src/Api/Auth/Validators/IRotationValidator.cs | 7 + src/Api/Startup.cs | 12 +- .../Tools/Validators/SendRotationValidator.cs | 57 ++++++ .../Auth/Models/Data/RotateUserKeyData.cs | 2 +- .../UserKey/IRotateUserKeyCommand.cs | 3 + .../Implementations/RotateUserKeyCommand.cs | 23 ++- .../Tools/Repositories/ISendRepository.cs | 9 + .../Tools/Helpers/SendHelpers.cs | 42 ++++ .../Tools/Repositories/SendRepository.cs | 55 ++++++ .../Tools/Repositories/SendRepository.cs | 25 +++ .../Controllers/AccountsControllerTests.cs | 6 +- .../Validators/SendRotationValidatorTests.cs | 184 ++++++++++++++++++ 13 files changed, 424 insertions(+), 7 deletions(-) create mode 100644 src/Api/Tools/Validators/SendRotationValidator.cs create mode 100644 src/Infrastructure.Dapper/Tools/Helpers/SendHelpers.cs create mode 100644 test/Api.Test/Tools/Validators/SendRotationValidatorTests.cs diff --git a/src/Api/Auth/Controllers/AccountsController.cs b/src/Api/Auth/Controllers/AccountsController.cs index e49ce9dd2b..88681f98c2 100644 --- a/src/Api/Auth/Controllers/AccountsController.cs +++ b/src/Api/Auth/Controllers/AccountsController.cs @@ -5,6 +5,7 @@ using Bit.Api.Auth.Validators; using Bit.Api.Models.Request; using Bit.Api.Models.Request.Accounts; using Bit.Api.Models.Response; +using Bit.Api.Tools.Models.Request; using Bit.Api.Utilities; using Bit.Api.Vault.Models.Request; using Bit.Core; @@ -68,6 +69,7 @@ public class AccountsController : Controller private readonly IRotationValidator, IEnumerable> _cipherValidator; private readonly IRotationValidator, IEnumerable> _folderValidator; + private readonly IRotationValidator, IReadOnlyList> _sendValidator; private readonly IRotationValidator, IEnumerable> _emergencyAccessValidator; @@ -92,6 +94,7 @@ public class AccountsController : Controller ICurrentContext currentContext, IRotationValidator, IEnumerable> cipherValidator, IRotationValidator, IEnumerable> folderValidator, + IRotationValidator, IReadOnlyList> sendValidator, IRotationValidator, IEnumerable> emergencyAccessValidator ) @@ -115,6 +118,7 @@ public class AccountsController : Controller _currentContext = currentContext; _cipherValidator = cipherValidator; _folderValidator = folderValidator; + _sendValidator = sendValidator; _emergencyAccessValidator = emergencyAccessValidator; } @@ -423,7 +427,7 @@ public class AccountsController : Controller PrivateKey = model.PrivateKey, Ciphers = await _cipherValidator.ValidateAsync(user, model.Ciphers), Folders = await _folderValidator.ValidateAsync(user, model.Folders), - Sends = new List(), + Sends = await _sendValidator.ValidateAsync(user, model.Sends), EmergencyAccessKeys = await _emergencyAccessValidator.ValidateAsync(user, model.EmergencyAccessKeys), ResetPasswordKeys = new List(), }; diff --git a/src/Api/Auth/Validators/IRotationValidator.cs b/src/Api/Auth/Validators/IRotationValidator.cs index 7360a4570c..fb6534ebee 100644 --- a/src/Api/Auth/Validators/IRotationValidator.cs +++ b/src/Api/Auth/Validators/IRotationValidator.cs @@ -1,4 +1,5 @@ using Bit.Core.Entities; +using Bit.Core.Exceptions; namespace Bit.Api.Auth.Validators; @@ -11,5 +12,11 @@ namespace Bit.Api.Auth.Validators; /// Domain model public interface IRotationValidator { + /// + /// Validates re-encrypted data before being saved to database. + /// + /// Request model + /// Domain model + /// Throws if data fails validation Task ValidateAsync(User user, T data); } diff --git a/src/Api/Startup.cs b/src/Api/Startup.cs index 1b8505e477..4043203309 100644 --- a/src/Api/Startup.cs +++ b/src/Api/Startup.cs @@ -9,6 +9,8 @@ using IdentityModel; using System.Globalization; using Bit.Api.Auth.Models.Request; using Bit.Api.Auth.Validators; +using Bit.Api.Tools.Models.Request; +using Bit.Api.Tools.Validators; using Bit.Api.Vault.Models.Request; using Bit.Api.Vault.Validators; using Bit.Core.Auth.Entities; @@ -23,6 +25,7 @@ using Bit.Core.Auth.Identity; using Bit.Core.Auth.UserFeatures.UserKey; using Bit.Core.Auth.UserFeatures.UserKey.Implementations; using Bit.Core.OrganizationFeatures.OrganizationSubscriptions; +using Bit.Core.Tools.Entities; using Bit.Core.Vault.Entities; #if !OSS @@ -141,15 +144,18 @@ public class Startup // Key Rotation services.AddScoped(); - services - .AddScoped, IEnumerable>, - EmergencyAccessRotationValidator>(); services .AddScoped, IEnumerable>, CipherRotationValidator>(); services .AddScoped, IEnumerable>, FolderRotationValidator>(); + services + .AddScoped, IReadOnlyList>, + SendRotationValidator>(); + services + .AddScoped, IEnumerable>, + EmergencyAccessRotationValidator>(); // Services services.AddBaseServices(globalSettings); diff --git a/src/Api/Tools/Validators/SendRotationValidator.cs b/src/Api/Tools/Validators/SendRotationValidator.cs new file mode 100644 index 0000000000..a8dc493e0d --- /dev/null +++ b/src/Api/Tools/Validators/SendRotationValidator.cs @@ -0,0 +1,57 @@ +using Bit.Api.Auth.Validators; +using Bit.Api.Tools.Models.Request; +using Bit.Core.Entities; +using Bit.Core.Exceptions; +using Bit.Core.Tools.Entities; +using Bit.Core.Tools.Repositories; +using Bit.Core.Tools.Services; + +namespace Bit.Api.Tools.Validators; + +/// +/// Send implementation for +/// +public class SendRotationValidator : IRotationValidator, IReadOnlyList> +{ + private readonly ISendService _sendService; + private readonly ISendRepository _sendRepository; + + /// + /// Instantiates a new + /// + /// Enables conversion of to + /// Retrieves all user s + public SendRotationValidator(ISendService sendService, ISendRepository sendRepository) + { + _sendService = sendService; + _sendRepository = sendRepository; + } + + public async Task> ValidateAsync(User user, IEnumerable sends) + { + var result = new List(); + if (sends == null || !sends.Any()) + { + return result; + } + + var existingSends = await _sendRepository.GetManyByUserIdAsync(user.Id); + if (existingSends == null || !existingSends.Any()) + { + return result; + } + + foreach (var existing in existingSends) + { + var send = sends.FirstOrDefault(c => c.Id == existing.Id); + if (send == null) + { + throw new BadRequestException("All existing folders must be included in the rotation."); + } + + result.Add(send.ToSend(existing, _sendService)); + } + + return result; + } +} diff --git a/src/Core/Auth/Models/Data/RotateUserKeyData.cs b/src/Core/Auth/Models/Data/RotateUserKeyData.cs index 23b6beb710..88cd2d5494 100644 --- a/src/Core/Auth/Models/Data/RotateUserKeyData.cs +++ b/src/Core/Auth/Models/Data/RotateUserKeyData.cs @@ -12,7 +12,7 @@ public class RotateUserKeyData public string PrivateKey { get; set; } public IEnumerable Ciphers { get; set; } public IEnumerable Folders { get; set; } - public IEnumerable Sends { get; set; } + public IReadOnlyList Sends { get; set; } public IEnumerable EmergencyAccessKeys { get; set; } public IEnumerable ResetPasswordKeys { get; set; } } diff --git a/src/Core/Auth/UserFeatures/UserKey/IRotateUserKeyCommand.cs b/src/Core/Auth/UserFeatures/UserKey/IRotateUserKeyCommand.cs index 4ba59ca487..cd2df59645 100644 --- a/src/Core/Auth/UserFeatures/UserKey/IRotateUserKeyCommand.cs +++ b/src/Core/Auth/UserFeatures/UserKey/IRotateUserKeyCommand.cs @@ -5,6 +5,9 @@ using Microsoft.Data.SqlClient; namespace Bit.Core.Auth.UserFeatures.UserKey; +/// +/// Responsible for rotation of a user key and updating database with re-encrypted data +/// public interface IRotateUserKeyCommand { /// diff --git a/src/Core/Auth/UserFeatures/UserKey/Implementations/RotateUserKeyCommand.cs b/src/Core/Auth/UserFeatures/UserKey/Implementations/RotateUserKeyCommand.cs index 34b29a246b..0d31b0c3cf 100644 --- a/src/Core/Auth/UserFeatures/UserKey/Implementations/RotateUserKeyCommand.cs +++ b/src/Core/Auth/UserFeatures/UserKey/Implementations/RotateUserKeyCommand.cs @@ -2,23 +2,37 @@ using Bit.Core.Entities; using Bit.Core.Repositories; using Bit.Core.Services; +using Bit.Core.Tools.Repositories; using Bit.Core.Vault.Repositories; using Microsoft.AspNetCore.Identity; namespace Bit.Core.Auth.UserFeatures.UserKey.Implementations; +/// public class RotateUserKeyCommand : IRotateUserKeyCommand { private readonly IUserService _userService; private readonly IUserRepository _userRepository; private readonly ICipherRepository _cipherRepository; private readonly IFolderRepository _folderRepository; + private readonly ISendRepository _sendRepository; private readonly IEmergencyAccessRepository _emergencyAccessRepository; private readonly IPushNotificationService _pushService; private readonly IdentityErrorDescriber _identityErrorDescriber; + /// + /// Instantiates a new + /// + /// Master password hash validation + /// Updates user keys and re-encrypted data if needed + /// Provides a method to update re-encrypted cipher data + /// Provides a method to update re-encrypted folder data + /// Provides a method to update re-encrypted send data + /// Provides a method to update re-encrypted emergency access data + /// Logs out user from other devices after successful rotation + /// Provides a password mismatch error if master password hash validation fails public RotateUserKeyCommand(IUserService userService, IUserRepository userRepository, - ICipherRepository cipherRepository, IFolderRepository folderRepository, + ICipherRepository cipherRepository, IFolderRepository folderRepository, ISendRepository sendRepository, IEmergencyAccessRepository emergencyAccessRepository, IPushNotificationService pushService, IdentityErrorDescriber errors) { @@ -26,6 +40,7 @@ public class RotateUserKeyCommand : IRotateUserKeyCommand _userRepository = userRepository; _cipherRepository = cipherRepository; _folderRepository = folderRepository; + _sendRepository = sendRepository; _emergencyAccessRepository = emergencyAccessRepository; _pushService = pushService; _identityErrorDescriber = errors; @@ -64,6 +79,12 @@ public class RotateUserKeyCommand : IRotateUserKeyCommand { saveEncryptedDataActions.Add(_folderRepository.UpdateForKeyRotation(user.Id, model.Folders)); } + + if (model.Sends.Any()) + { + saveEncryptedDataActions.Add(_sendRepository.UpdateForKeyRotation(user.Id, model.Sends)); + } + if (model.EmergencyAccessKeys.Any()) { saveEncryptedDataActions.Add( diff --git a/src/Core/Tools/Repositories/ISendRepository.cs b/src/Core/Tools/Repositories/ISendRepository.cs index 421a5f4aaf..2cbcce1f92 100644 --- a/src/Core/Tools/Repositories/ISendRepository.cs +++ b/src/Core/Tools/Repositories/ISendRepository.cs @@ -1,5 +1,6 @@ #nullable enable +using Bit.Core.Auth.UserFeatures.UserKey; using Bit.Core.Repositories; using Bit.Core.Tools.Entities; @@ -33,4 +34,12 @@ public interface ISendRepository : IRepository /// The task's result contains the loaded s. /// Task> GetManyByDeletionDateAsync(DateTime deletionDateBefore); + + /// + /// Updates encrypted data for sends during a key rotation + /// + /// The user that initiated the key rotation + /// A list of sends with updated data + UpdateEncryptedDataForKeyRotation UpdateForKeyRotation(Guid userId, + IEnumerable sends); } diff --git a/src/Infrastructure.Dapper/Tools/Helpers/SendHelpers.cs b/src/Infrastructure.Dapper/Tools/Helpers/SendHelpers.cs new file mode 100644 index 0000000000..6df9000f79 --- /dev/null +++ b/src/Infrastructure.Dapper/Tools/Helpers/SendHelpers.cs @@ -0,0 +1,42 @@ +using System.Data; +using Bit.Core.Tools.Entities; + +namespace Bit.Infrastructure.Dapper.Tools.Helpers; + +/// +/// Dapper helper methods for Sends +/// +public static class SendHelpers +{ + /// + /// Converts an IEnumerable of Sends to a DataTable + /// + /// Contains a hardcoded list of properties and must be updated with model + /// List of sends + /// A data table matching the schema of dbo.Send containing one row mapped from the items in s + public static DataTable ToDataTable(this IEnumerable sends) + { + var sendsTable = new DataTable(); + + var columnData = new List<(string name, Type type, Func getter)> + { + (nameof(Send.Id), typeof(Guid), c => c.Id), + (nameof(Send.UserId), typeof(Guid), c => c.UserId), + (nameof(Send.OrganizationId), typeof(Guid), c => c.OrganizationId), + (nameof(Send.Type), typeof(short), c => c.Type), + (nameof(Send.Data), typeof(string), c => c.Data), + (nameof(Send.Key), typeof(string), c => c.Key), + (nameof(Send.Password), typeof(string), c => c.Password), + (nameof(Send.MaxAccessCount), typeof(int), c => c.MaxAccessCount), + (nameof(Send.AccessCount), typeof(int), c => c.AccessCount), + (nameof(Send.CreationDate), typeof(DateTime), c => c.CreationDate), + (nameof(Send.RevisionDate), typeof(DateTime), c => c.RevisionDate), + (nameof(Send.ExpirationDate), typeof(DateTime), c => c.ExpirationDate), + (nameof(Send.DeletionDate), typeof(DateTime), c => c.DeletionDate), + (nameof(Send.Disabled), typeof(bool), c => c.Disabled), + (nameof(Send.HideEmail), typeof(bool), c => c.HideEmail), + }; + + return sends.BuildTable(sendsTable, columnData); + } +} diff --git a/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs b/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs index 0522cee672..12fbbd4eb6 100644 --- a/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs +++ b/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs @@ -1,10 +1,12 @@ #nullable enable using System.Data; +using Bit.Core.Auth.UserFeatures.UserKey; using Bit.Core.Settings; using Bit.Core.Tools.Entities; using Bit.Core.Tools.Repositories; using Bit.Infrastructure.Dapper.Repositories; +using Bit.Infrastructure.Dapper.Tools.Helpers; using Dapper; using Microsoft.Data.SqlClient; @@ -48,4 +50,57 @@ public class SendRepository : Repository, ISendRepository return results.ToList(); } } + + /// + public UpdateEncryptedDataForKeyRotation UpdateForKeyRotation(Guid userId, IEnumerable sends) + { + return async (connection, transaction) => + { + // Create temp table + var sqlCreateTemp = @" + SELECT TOP 0 * + INTO #TempSend + FROM [dbo].[Send]"; + + await using (var cmd = new SqlCommand(sqlCreateTemp, connection, transaction)) + { + cmd.ExecuteNonQuery(); + } + + // Bulk copy data into temp table + using (var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity, transaction)) + { + bulkCopy.DestinationTableName = "#TempSend"; + var sendsTable = sends.ToDataTable(); + foreach (DataColumn col in sendsTable.Columns) + { + bulkCopy.ColumnMappings.Add(col.ColumnName, col.ColumnName); + } + + sendsTable.PrimaryKey = new DataColumn[] { sendsTable.Columns[0] }; + await bulkCopy.WriteToServerAsync(sendsTable); + } + + // Update send table from temp table + var sql = @" + UPDATE + [dbo].[Send] + SET + [Key] = TS.[Key], + [RevisionDate] = TS.[RevisionDate] + FROM + [dbo].[Send] S + INNER JOIN + #TempSend TS ON S.Id = TS.Id + WHERE + S.[UserId] = @UserId + DROP TABLE #TempSend"; + + await using (var cmd = new SqlCommand(sql, connection, transaction)) + { + cmd.Parameters.Add("@UserId", SqlDbType.UniqueIdentifier).Value = userId; + cmd.ExecuteNonQuery(); + } + }; + } } diff --git a/src/Infrastructure.EntityFramework/Tools/Repositories/SendRepository.cs b/src/Infrastructure.EntityFramework/Tools/Repositories/SendRepository.cs index 4c0c866606..2db07f154b 100644 --- a/src/Infrastructure.EntityFramework/Tools/Repositories/SendRepository.cs +++ b/src/Infrastructure.EntityFramework/Tools/Repositories/SendRepository.cs @@ -1,6 +1,7 @@ #nullable enable using AutoMapper; +using Bit.Core.Auth.UserFeatures.UserKey; using Bit.Core.Tools.Repositories; using Bit.Infrastructure.EntityFramework.Models; using Bit.Infrastructure.EntityFramework.Repositories; @@ -69,4 +70,28 @@ public class SendRepository : Repository, return Mapper.Map>(results); } } + + /// + public UpdateEncryptedDataForKeyRotation UpdateForKeyRotation(Guid userId, + IEnumerable sends) + { + return async (_, _) => + { + var newSends = sends.ToDictionary(s => s.Id); + using var scope = ServiceScopeFactory.CreateScope(); + var dbContext = GetDatabaseContext(scope); + var userSends = await GetDbSet(dbContext) + .Where(s => s.UserId == userId) + .ToListAsync(); + var validSends = userSends + .Where(send => newSends.ContainsKey(send.Id)); + foreach (var send in validSends) + { + send.Key = newSends[send.Id].Key; + } + + await dbContext.SaveChangesAsync(); + }; + } + } diff --git a/test/Api.Test/Auth/Controllers/AccountsControllerTests.cs b/test/Api.Test/Auth/Controllers/AccountsControllerTests.cs index b0a9964007..764d6e0547 100644 --- a/test/Api.Test/Auth/Controllers/AccountsControllerTests.cs +++ b/test/Api.Test/Auth/Controllers/AccountsControllerTests.cs @@ -3,6 +3,7 @@ using Bit.Api.Auth.Controllers; using Bit.Api.Auth.Models.Request; using Bit.Api.Auth.Models.Request.Accounts; using Bit.Api.Auth.Validators; +using Bit.Api.Tools.Models.Request; using Bit.Api.Vault.Models.Request; using Bit.Core; using Bit.Core.AdminConsole.Repositories; @@ -20,6 +21,7 @@ using Bit.Core.Models.Data; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; +using Bit.Core.Tools.Entities; using Bit.Core.Tools.Repositories; using Bit.Core.Tools.Services; using Bit.Core.Vault.Entities; @@ -53,9 +55,9 @@ public class AccountsControllerTests : IDisposable private readonly IFeatureService _featureService; private readonly ICurrentContext _currentContext; - private readonly IRotationValidator, IEnumerable> _cipherValidator; private readonly IRotationValidator, IEnumerable> _folderValidator; + private readonly IRotationValidator, IReadOnlyList> _sendValidator; private readonly IRotationValidator, IEnumerable> _emergencyAccessValidator; @@ -83,6 +85,7 @@ public class AccountsControllerTests : IDisposable Substitute.For, IEnumerable>>(); _folderValidator = Substitute.For, IEnumerable>>(); + _sendValidator = Substitute.For, IReadOnlyList>>(); _emergencyAccessValidator = Substitute.For, IEnumerable>>(); @@ -106,6 +109,7 @@ public class AccountsControllerTests : IDisposable _currentContext, _cipherValidator, _folderValidator, + _sendValidator, _emergencyAccessValidator ); } diff --git a/test/Api.Test/Tools/Validators/SendRotationValidatorTests.cs b/test/Api.Test/Tools/Validators/SendRotationValidatorTests.cs new file mode 100644 index 0000000000..76f938d1c4 --- /dev/null +++ b/test/Api.Test/Tools/Validators/SendRotationValidatorTests.cs @@ -0,0 +1,184 @@ +using System.Text.Json; +using Bit.Api.Tools.Models; +using Bit.Api.Tools.Models.Request; +using Bit.Api.Tools.Validators; +using Bit.Core.Entities; +using Bit.Core.Exceptions; +using Bit.Core.Tools.Entities; +using Bit.Core.Tools.Enums; +using Bit.Core.Tools.Models.Data; +using Bit.Core.Tools.Repositories; +using Bit.Core.Tools.Services; +using Bit.Core.Utilities; +using Bit.Test.Common.AutoFixture.Attributes; +using NSubstitute; +using Xunit; + +namespace Bit.Api.Test.Tools.Validators; + +[SutProviderCustomize] +public class SendRotationValidatorTests +{ + [Fact] + public async Task ValidateAsync_Success() + { + // Arrange + var sendService = Substitute.For(); + var sendRepository = Substitute.For(); + + var sut = new SendRotationValidator( + sendService, + sendRepository + ); + + var user = new User { Id = new Guid() }; + var sends = CreateInputSendRequests(); + + sendRepository.GetManyByUserIdAsync(user.Id).Returns(MockUserSends(user)); + + // Act + var result = await sut.ValidateAsync(user, sends); + + // Assert + var sendIds = new Guid[] + { + new("72e9ac6d-05f4-4227-ae0d-8a5207623a1a"), new("6b55836c-9280-4589-8762-01b0d8172c97"), + new("9a65bbfb-8138-4aa5-a572-e5c0a41b540e"), + }; + Assert.All(result, c => Assert.Contains(c.Id, sendIds)); + } + + [Fact] + public async Task ValidateAsync_SendNotReturnedFromRepository_NotIncludedInOutput() + { + // Arrange + var sendService = Substitute.For(); + var sendRepository = Substitute.For(); + + var sut = new SendRotationValidator( + sendService, + sendRepository + ); + + var user = new User { Id = new Guid() }; + var sends = CreateInputSendRequests(); + + var userSends = MockUserSends(user); + userSends.RemoveAll(c => c.Id == new Guid("72e9ac6d-05f4-4227-ae0d-8a5207623a1a")); + sendRepository.GetManyByUserIdAsync(user.Id).Returns(userSends); + + var result = await sut.ValidateAsync(user, sends); + + Assert.DoesNotContain(result, c => c.Id == new Guid("72e9ac6d-05f4-4227-ae0d-8a5207623a1a")); + } + + [Fact] + public async Task ValidateAsync_InputMissingUserSend_Throws() + { + // Arrange + var sendService = Substitute.For(); + var sendRepository = Substitute.For(); + + var sut = new SendRotationValidator( + sendService, + sendRepository + ); + + var user = new User { Id = new Guid() }; + var sends = CreateInputSendRequests(); + + var userSends = MockUserSends(user); + userSends.Add(new Send { Id = new Guid(), Data = "{}" }); + sendRepository.GetManyByUserIdAsync(user.Id).Returns(userSends); + + // Act, Assert + await Assert.ThrowsAsync(async () => + await sut.ValidateAsync(user, sends)); + } + + private IEnumerable CreateInputSendRequests() + { + return new[] + { + new SendWithIdRequestModel + { + DeletionDate = new DateTime(2080, 12, 31), + Disabled = false, + Id = new Guid("72e9ac6d-05f4-4227-ae0d-8a5207623a1a"), + Key = "Send1Key", + Name = "Send 1", + Type = SendType.Text, + Text = new SendTextModel(new SendTextData("Text name", "Notes", "Encrypted text for Send 1", false)) + }, + new SendWithIdRequestModel + { + DeletionDate = new DateTime(2080, 12, 31), + Disabled = true, + Id = new Guid("6b55836c-9280-4589-8762-01b0d8172c97"), + Key = "Send2Key", + Name = "Send 2", + Type = SendType.Text, + Text = new SendTextModel(new SendTextData("Text name", "Notes", "Encrypted text for Send 2", + false)), + }, + new SendWithIdRequestModel + { + DeletionDate = new DateTime(2080, 12, 31), + Disabled = false, + Id = new Guid("9a65bbfb-8138-4aa5-a572-e5c0a41b540e"), + Key = "Send3Key", + Name = "Send 3", + Type = SendType.File, + File = new SendFileModel(new SendFileData("File name", "Notes", "File name here")), + HideEmail = true + } + }; + } + + private List MockUserSends(User user) + { + return new List(new[] + { + new Send + { + DeletionDate = new DateTime(2080, 12, 31), + Disabled = false, + Id = new Guid("72e9ac6d-05f4-4227-ae0d-8a5207623a1a"), + UserId = user.Id, + Key = "Send1Key", + Type = SendType.Text, + Data = JsonSerializer.Serialize( + new SendTextModel(new SendTextData("Text name", "Notes", "Encrypted text for Send 1", false)), + JsonHelpers.IgnoreWritingNull), + }, + new Send + { + DeletionDate = new DateTime(2080, 12, 31), + Disabled = true, + Id = new Guid("6b55836c-9280-4589-8762-01b0d8172c97"), + UserId = user.Id, + Key = "Send2Key", + Type = SendType.Text, + Data = JsonSerializer.Serialize( + new SendTextModel(new SendTextData("Text name", "Notes", "Encrypted text for Send 2", + false)), + JsonHelpers.IgnoreWritingNull), + }, + new Send + { + DeletionDate = new DateTime(2080, 12, 31), + Disabled = false, + Id = new Guid("9a65bbfb-8138-4aa5-a572-e5c0a41b540e"), + UserId = user.Id, + Key = "Send3Key", + Type = SendType.File, + Data = JsonSerializer.Serialize( + new SendFileModel(new SendFileData("File name", "Notes", "File name here")), + JsonHelpers.IgnoreWritingNull), + HideEmail = true + } + }); + } + + +}