From 05dc452d1790b5c40caa91dde61b4ade3a73a85c Mon Sep 17 00:00:00 2001 From: John Harrington <84741727+harr1424@users.noreply.github.com> Date: Tue, 27 Jan 2026 07:58:41 -0700 Subject: [PATCH] remove archive preservation across org import to enforce per user archive status --- .../Controllers/ImportCiphersController.cs | 5 -- .../ImportFeatures/ImportCiphersCommand.cs | 11 ++-- .../ImportCiphersControllerTests.cs | 57 ------------------- .../ImportCiphersAsyncCommandTests.cs | 12 ++-- 4 files changed, 12 insertions(+), 73 deletions(-) diff --git a/src/Api/Tools/Controllers/ImportCiphersController.cs b/src/Api/Tools/Controllers/ImportCiphersController.cs index 8b3ec5e26c..bebf7cbf29 100644 --- a/src/Api/Tools/Controllers/ImportCiphersController.cs +++ b/src/Api/Tools/Controllers/ImportCiphersController.cs @@ -74,11 +74,6 @@ public class ImportCiphersController : Controller throw new BadRequestException("You cannot import this much data at once."); } - if (model.Ciphers.Any(c => c.ArchivedDate.HasValue)) - { - throw new BadRequestException("You cannot import archived items into an organization."); - } - var orgId = new Guid(organizationId); var collections = model.Collections.Select(c => c.ToCollection(orgId)).ToList(); diff --git a/src/Core/Tools/ImportFeatures/ImportCiphersCommand.cs b/src/Core/Tools/ImportFeatures/ImportCiphersCommand.cs index 328ba04d26..8cf8989c3d 100644 --- a/src/Core/Tools/ImportFeatures/ImportCiphersCommand.cs +++ b/src/Core/Tools/ImportFeatures/ImportCiphersCommand.cs @@ -146,13 +146,10 @@ public class ImportCiphersCommand : IImportCiphersCommand // 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}\"}}"; - } + /* + * Archive functionality is a per-user function and should only ever be presented to the user who set the archive + * bit to ON for the item. No admin, other user or task should mark items as archived for other users. + */ } var organizationCollectionsIds = (await _collectionRepository.GetManyByOrganizationIdAsync(org.Id)).Select(c => c.Id).ToList(); diff --git a/test/Api.Test/Tools/Controllers/ImportCiphersControllerTests.cs b/test/Api.Test/Tools/Controllers/ImportCiphersControllerTests.cs index 9ca641a28e..a8465ed0f6 100644 --- a/test/Api.Test/Tools/Controllers/ImportCiphersControllerTests.cs +++ b/test/Api.Test/Tools/Controllers/ImportCiphersControllerTests.cs @@ -806,63 +806,6 @@ public class ImportCiphersControllerTests Arg.Any()); } - [Theory, BitAutoData] - public async Task PostImportOrganization_ThrowsException_WhenAnyCipherIsArchived( - SutProvider sutProvider, - IFixture fixture, - User user - ) - { - var orgId = Guid.NewGuid(); - - sutProvider.GetDependency() - .SelfHosted = false; - sutProvider.GetDependency() - .ImportCiphersLimitation = _organizationCiphersLimitations; - - SetupUserService(sutProvider, user); - - var ciphers = fixture.Build() - .With(_ => _.ArchivedDate, DateTime.UtcNow) - .CreateMany(2).ToArray(); - - var request = new ImportOrganizationCiphersRequestModel - { - Collections = new List().ToArray(), - Ciphers = ciphers, - CollectionRelationships = new List>().ToArray(), - }; - - sutProvider.GetDependency() - .AccessImportExport(Arg.Any()) - .Returns(false); - - sutProvider.GetDependency() - .AuthorizeAsync(Arg.Any(), - Arg.Any>(), - Arg.Is>(reqs => - reqs.Contains(BulkCollectionOperations.ImportCiphers))) - .Returns(AuthorizationResult.Failed()); - - sutProvider.GetDependency() - .AuthorizeAsync(Arg.Any(), - Arg.Any>(), - Arg.Is>(reqs => - reqs.Contains(BulkCollectionOperations.Create))) - .Returns(AuthorizationResult.Success()); - - sutProvider.GetDependency() - .GetManyByOrganizationIdAsync(orgId) - .Returns(new List()); - - var exception = await Assert.ThrowsAsync(async () => - { - await sutProvider.Sut.PostImportOrganization(orgId.ToString(), request); - }); - - Assert.Equal("You cannot import archived items into an organization.", exception.Message); - } - private static void SetupUserService(SutProvider sutProvider, User user) { // This is a workaround for the NSubstitute issue with ambiguous arguments diff --git a/test/Core.Test/Tools/ImportFeatures/ImportCiphersAsyncCommandTests.cs b/test/Core.Test/Tools/ImportFeatures/ImportCiphersAsyncCommandTests.cs index df24378811..1b6e49f130 100644 --- a/test/Core.Test/Tools/ImportFeatures/ImportCiphersAsyncCommandTests.cs +++ b/test/Core.Test/Tools/ImportFeatures/ImportCiphersAsyncCommandTests.cs @@ -360,8 +360,12 @@ public class ImportCiphersAsyncCommandTests Arg.Any>()); } + /* + * Archive functionality is a per-user function and should only ever be presented to the user who set the archive + * bit to ON for the item. No admin, other user or task should mark items as archived for other users + */ [Theory, BitAutoData] - public async Task ImportIntoOrganizationalVaultAsync_WithArchivedCiphers_PreservesArchiveStatus( + public async Task ImportIntoOrganizationalVaultAsync_WithArchivedCiphers_DoesNotSetArchives( Organization organization, Guid importingUserId, OrganizationUser importingOrganizationUser, @@ -384,6 +388,7 @@ public class ImportCiphersAsyncCommandTests } ciphers[0].ArchivedDate = archivedDate; + ciphers[0].Archives = null; KeyValuePair[] collectionRelationships = { new(0, 0), @@ -409,9 +414,8 @@ public class ImportCiphersAsyncCommandTests .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"))), + c[0].ArchivedDate == archivedDate && + c[0].Archives == null), Arg.Any>(), Arg.Any>(), Arg.Any>());