diff --git a/src/Infrastructure.Dapper/Vault/Repositories/CipherRepository.cs b/src/Infrastructure.Dapper/Vault/Repositories/CipherRepository.cs index b85f1991f7..3df365330c 100644 --- a/src/Infrastructure.Dapper/Vault/Repositories/CipherRepository.cs +++ b/src/Infrastructure.Dapper/Vault/Repositories/CipherRepository.cs @@ -711,7 +711,7 @@ public class CipherRepository : Repository, ICipherRepository row[creationDateColumn] = cipher.CreationDate; row[revisionDateColumn] = cipher.RevisionDate; row[deletedDateColumn] = cipher.DeletedDate.HasValue ? (object)cipher.DeletedDate : DBNull.Value; - row[repromptColumn] = cipher.Reprompt; + row[repromptColumn] = cipher.Reprompt.HasValue ? cipher.Reprompt.Value : DBNull.Value; row[keyColummn] = cipher.Key; ciphersTable.Rows.Add(row); diff --git a/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs b/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs index e4930cb795..090c36ff29 100644 --- a/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs +++ b/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs @@ -863,8 +863,30 @@ public class CipherRepository : Repository>(ciphers); - await dbContext.BulkCopyAsync(base.DefaultBulkCopyOptions, entities); + var ciphersToUpdate = ciphers.ToDictionary(c => c.Id); + + var existingCiphers = await dbContext.Ciphers + .Where(c => c.UserId == userId && ciphersToUpdate.Keys.Contains(c.Id)) + .ToDictionaryAsync(c => c.Id); + + foreach (var (cipherId, cipher) in ciphersToUpdate) + { + if (!existingCiphers.TryGetValue(cipherId, out var existingCipher)) + { + // The Dapper version does not validate that the same amount of items given where updated. + continue; + } + + existingCipher.UserId = cipher.UserId; + existingCipher.OrganizationId = cipher.OrganizationId; + existingCipher.Type = cipher.Type; + existingCipher.Data = cipher.Data; + existingCipher.Attachments = cipher.Attachments; + existingCipher.RevisionDate = cipher.RevisionDate; + existingCipher.DeletedDate = cipher.DeletedDate; + existingCipher.Key = cipher.Key; + } + await dbContext.UserBumpAccountRevisionDateAsync(userId); await dbContext.SaveChangesAsync(); } diff --git a/test/Infrastructure.IntegrationTest/Vault/Repositories/CipherRepositoryTests.cs b/test/Infrastructure.IntegrationTest/Vault/Repositories/CipherRepositoryTests.cs index 6f02740cf5..fde625e272 100644 --- a/test/Infrastructure.IntegrationTest/Vault/Repositories/CipherRepositoryTests.cs +++ b/test/Infrastructure.IntegrationTest/Vault/Repositories/CipherRepositoryTests.cs @@ -883,4 +883,33 @@ public class CipherRepositoryTests Assert.Contains(user2TaskCiphers, t => t.CipherId == manageCipher1.Id && t.TaskId == securityTasks[0].Id); Assert.Contains(user2TaskCiphers, t => t.CipherId == manageCipher2.Id && t.TaskId == securityTasks[1].Id); } + + [DatabaseTheory, DatabaseData] + public async Task UpdateCiphersAsync_Works(ICipherRepository cipherRepository, IUserRepository userRepository) + { + var user = await userRepository.CreateAsync(new User + { + Name = "Test User", + Email = $"test+{Guid.NewGuid()}@email.com", + ApiKey = "TEST", + SecurityStamp = "stamp", + }); + + var cipher1 = await CreatePersonalCipher(user, cipherRepository); + var cipher2 = await CreatePersonalCipher(user, cipherRepository); + + cipher1.Type = CipherType.SecureNote; + cipher2.Attachments = "new_attachments"; + + await cipherRepository.UpdateCiphersAsync(user.Id, [cipher1, cipher2]); + + var updatedCipher1 = await cipherRepository.GetByIdAsync(cipher1.Id); + var updatedCipher2 = await cipherRepository.GetByIdAsync(cipher2.Id); + + Assert.NotNull(updatedCipher1); + Assert.NotNull(updatedCipher2); + + Assert.Equal(CipherType.SecureNote, updatedCipher1.Type); + Assert.Equal("new_attachments", updatedCipher2.Attachments); + } }