From 47bfb819eb41169ef2b36b217900b546abc083de Mon Sep 17 00:00:00 2001 From: John Harrington <84741727+harr1424@users.noreply.github.com> Date: Fri, 23 Jan 2026 16:58:31 -0700 Subject: [PATCH] preserve archived ciphers across org imports --- .../ImportFeatures/ImportCiphersCommand.cs | 10 +++- .../ImportCiphersAsyncCommandTests.cs | 57 +++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/src/Core/Tools/ImportFeatures/ImportCiphersCommand.cs b/src/Core/Tools/ImportFeatures/ImportCiphersCommand.cs index 1ea7e56c5c..3c1fd1e5a3 100644 --- a/src/Core/Tools/ImportFeatures/ImportCiphersCommand.cs +++ b/src/Core/Tools/ImportFeatures/ImportCiphersCommand.cs @@ -141,10 +141,18 @@ public class ImportCiphersCommand : IImportCiphersCommand } } - // Init. ids for ciphers foreach (var cipher in ciphers) { + // Init. ids for ciphers cipher.SetNewId(); + + // Preserve archived status for organizational ciphers + // Archives are stored per-user even for org ciphers + if (cipher.ArchivedDate.HasValue) + { + cipher.Archives = $"{{\"{importingUserId.ToString().ToUpperInvariant()}\":\"" + + $"{cipher.ArchivedDate.Value:yyyy-MM-ddTHH:mm:ss.fffffffZ}\"}}"; + } } var organizationCollectionsIds = (await _collectionRepository.GetManyByOrganizationIdAsync(org.Id)).Select(c => c.Id).ToList(); diff --git a/test/Core.Test/Tools/ImportFeatures/ImportCiphersAsyncCommandTests.cs b/test/Core.Test/Tools/ImportFeatures/ImportCiphersAsyncCommandTests.cs index 57a81521a0..615738bfab 100644 --- a/test/Core.Test/Tools/ImportFeatures/ImportCiphersAsyncCommandTests.cs +++ b/test/Core.Test/Tools/ImportFeatures/ImportCiphersAsyncCommandTests.cs @@ -322,4 +322,61 @@ public class ImportCiphersAsyncCommandTests c[0].Archives.Contains(archivedDate.ToString("yyyy-MM-ddTHH:mm:ss.fffffffZ"))), Arg.Any>()); } + + [Theory, BitAutoData] + public async Task ImportIntoOrganizationalVaultAsync_WithArchivedCiphers_PreservesArchiveStatus( + Organization organization, + Guid importingUserId, + OrganizationUser importingOrganizationUser, + List collections, + List ciphers, + SutProvider sutProvider) + { + var archivedDate = DateTime.UtcNow.AddDays(-1); + organization.MaxCollections = null; + importingOrganizationUser.OrganizationId = organization.Id; + + foreach (var collection in collections) + { + collection.OrganizationId = organization.Id; + } + + foreach (var cipher in ciphers) + { + cipher.OrganizationId = organization.Id; + } + + ciphers[0].ArchivedDate = archivedDate; + + KeyValuePair[] collectionRelationships = { + new(0, 0), + new(1, 1), + new(2, 2) + }; + + sutProvider.GetDependency() + .GetByIdAsync(organization.Id) + .Returns(organization); + + sutProvider.GetDependency() + .GetByOrganizationAsync(organization.Id, importingUserId) + .Returns(importingOrganizationUser); + + sutProvider.GetDependency() + .GetManyByOrganizationIdAsync(organization.Id) + .Returns(new List()); + + await sutProvider.Sut.ImportIntoOrganizationalVaultAsync(collections, ciphers, collectionRelationships, importingUserId); + + await sutProvider.GetDependency() + .Received(1) + .CreateAsync( + Arg.Is>(c => + c[0].Archives != null && + c[0].Archives.Contains(importingUserId.ToString().ToUpperInvariant()) && + c[0].Archives.Contains(archivedDate.ToString("yyyy-MM-ddTHH:mm:ss.fffffffZ"))), + Arg.Any>(), + Arg.Any>(), + Arg.Any>()); + } }