From 79e6b8d7a206de2039158414237485fa6c6512ee Mon Sep 17 00:00:00 2001 From: jaasen-livefront Date: Fri, 20 Jun 2025 15:45:03 -0700 Subject: [PATCH] add GetAllOrganizationCiphersExcludingDefaultUserCollections --- .../Vault/Controllers/CiphersController.cs | 12 +++++-- .../Queries/IOrganizationCiphersQuery.cs | 6 ++++ .../Vault/Queries/OrganizationCiphersQuery.cs | 31 ++++++++++++++-- .../Queries/OrganizationCiphersQueryTests.cs | 36 +++++++++++++++++++ 4 files changed, 80 insertions(+), 5 deletions(-) diff --git a/src/Api/Vault/Controllers/CiphersController.cs b/src/Api/Vault/Controllers/CiphersController.cs index 5991d0babb..a709d8f7da 100644 --- a/src/Api/Vault/Controllers/CiphersController.cs +++ b/src/Api/Vault/Controllers/CiphersController.cs @@ -45,6 +45,7 @@ public class CiphersController : Controller private readonly IOrganizationCiphersQuery _organizationCiphersQuery; private readonly IApplicationCacheService _applicationCacheService; private readonly ICollectionRepository _collectionRepository; + private readonly IFeatureService _featureService; public CiphersController( ICipherRepository cipherRepository, @@ -58,7 +59,8 @@ public class CiphersController : Controller GlobalSettings globalSettings, IOrganizationCiphersQuery organizationCiphersQuery, IApplicationCacheService applicationCacheService, - ICollectionRepository collectionRepository) + ICollectionRepository collectionRepository, + IFeatureService featureService) { _cipherRepository = cipherRepository; _collectionCipherRepository = collectionCipherRepository; @@ -72,6 +74,7 @@ public class CiphersController : Controller _organizationCiphersQuery = organizationCiphersQuery; _applicationCacheService = applicationCacheService; _collectionRepository = collectionRepository; + _featureService = featureService; } [HttpGet("{id}")] @@ -306,8 +309,11 @@ public class CiphersController : Controller { throw new NotFoundException(); } - - var allOrganizationCiphers = await _organizationCiphersQuery.GetAllOrganizationCiphers(organizationId); + var allOrganizationCiphers = _featureService.IsEnabled(FeatureFlagKeys.CreateDefaultLocation) + ? + await _organizationCiphersQuery.GetAllOrganizationCiphersExcludingDefaultUserCollections(organizationId) + : + await _organizationCiphersQuery.GetAllOrganizationCiphers(organizationId); var allOrganizationCipherResponses = allOrganizationCiphers.Select(c => diff --git a/src/Core/Vault/Queries/IOrganizationCiphersQuery.cs b/src/Core/Vault/Queries/IOrganizationCiphersQuery.cs index 1756cad3c7..44a56eac48 100644 --- a/src/Core/Vault/Queries/IOrganizationCiphersQuery.cs +++ b/src/Core/Vault/Queries/IOrganizationCiphersQuery.cs @@ -37,4 +37,10 @@ public interface IOrganizationCiphersQuery /// public Task> GetOrganizationCiphersByCollectionIds( Guid organizationId, IEnumerable collectionIds); + + /// + /// Returns all organization ciphers except those in default user collections. + /// + public Task> + GetAllOrganizationCiphersExcludingDefaultUserCollections(Guid organizationId); } diff --git a/src/Core/Vault/Queries/OrganizationCiphersQuery.cs b/src/Core/Vault/Queries/OrganizationCiphersQuery.cs index deed121216..ba291a41b7 100644 --- a/src/Core/Vault/Queries/OrganizationCiphersQuery.cs +++ b/src/Core/Vault/Queries/OrganizationCiphersQuery.cs @@ -1,4 +1,5 @@ -using Bit.Core.Repositories; +using Bit.Core.Enums; +using Bit.Core.Repositories; using Bit.Core.Vault.Models.Data; using Bit.Core.Vault.Repositories; @@ -8,11 +9,13 @@ public class OrganizationCiphersQuery : IOrganizationCiphersQuery { private readonly ICipherRepository _cipherRepository; private readonly ICollectionCipherRepository _collectionCipherRepository; + private readonly ICollectionRepository _collectionRepository; - public OrganizationCiphersQuery(ICipherRepository cipherRepository, ICollectionCipherRepository collectionCipherRepository) + public OrganizationCiphersQuery(ICipherRepository cipherRepository, ICollectionCipherRepository collectionCipherRepository, ICollectionRepository collectionRepository) { _cipherRepository = cipherRepository; _collectionCipherRepository = collectionCipherRepository; + _collectionRepository = collectionRepository; } /// @@ -61,4 +64,28 @@ public class OrganizationCiphersQuery : IOrganizationCiphersQuery var allOrganizationCiphers = await GetAllOrganizationCiphers(organizationId); return allOrganizationCiphers.Where(c => c.CollectionIds.Intersect(managedCollectionIds).Any()); } + + public async Task> + GetAllOrganizationCiphersExcludingDefaultUserCollections(Guid orgId) + { + var defaultCollIds = (await _collectionRepository + .GetManyByOrganizationIdAsync(orgId)) + .Where(col => col.Type == CollectionType.DefaultUserCollection) + .Select(col => col.Id) + .ToHashSet(); + + var cipherGroups = (await _collectionCipherRepository + .GetManyByOrganizationIdAsync(orgId)) + .GroupBy(cc => cc.CipherId) + .ToDictionary(g => g.Key, g => g); + + return (await _cipherRepository + .GetManyOrganizationDetailsByOrganizationIdAsync(orgId)) + .Where(c => + // select ciphers with no collections or none of its collections are default + !cipherGroups.TryGetValue(c.Id, out var grp) + || !grp.Any(cc => defaultCollIds.Contains(cc.CollectionId)) + ) + .Select(c => new CipherOrganizationDetailsWithCollections(c, cipherGroups)); + } } diff --git a/test/Core.Test/Vault/Queries/OrganizationCiphersQueryTests.cs b/test/Core.Test/Vault/Queries/OrganizationCiphersQueryTests.cs index 01539fe7d7..b80dfcd05c 100644 --- a/test/Core.Test/Vault/Queries/OrganizationCiphersQueryTests.cs +++ b/test/Core.Test/Vault/Queries/OrganizationCiphersQueryTests.cs @@ -1,5 +1,6 @@ using AutoFixture; using Bit.Core.Entities; +using Bit.Core.Enums; using Bit.Core.Repositories; using Bit.Core.Vault.Models.Data; using Bit.Core.Vault.Queries; @@ -89,4 +90,39 @@ public class OrganizationCiphersQueryTests c.CollectionIds.Any(cId => cId == targetCollectionId) && c.CollectionIds.Any(cId => cId == otherCollectionId)); } + + [Theory, BitAutoData] + public async Task GetAllOrganizationCiphersExcludingDefaultUserCollections_ExcludesCiphersInDefaultCollections( + Guid organizationId, SutProvider sutProvider) + { + var defaultColId = Guid.NewGuid(); + var otherColId = Guid.NewGuid(); + var cipherDefault = new CipherOrganizationDetails { Id = Guid.NewGuid(), OrganizationId = organizationId, OrganizationUseTotp = false }; + var cipherOther = new CipherOrganizationDetails { Id = Guid.NewGuid(), OrganizationId = organizationId, OrganizationUseTotp = false }; + var ciphers = new[] { cipherDefault, cipherOther }; + + var collections = new[] { + new Collection { Id = defaultColId, OrganizationId = organizationId, Type = CollectionType.DefaultUserCollection }, + new Collection { Id = otherColId, OrganizationId = organizationId, Type = CollectionType.SharedCollection } + }; + + var collectionsCiphers = new[] { + new CollectionCipher { CollectionId = defaultColId, CipherId = cipherDefault.Id }, + new CollectionCipher { CollectionId = otherColId, CipherId = cipherOther.Id } + }; + + sutProvider.GetDependency().GetManyOrganizationDetailsByOrganizationIdAsync(organizationId) + .Returns(ciphers); + sutProvider.GetDependency().GetManyByOrganizationIdAsync(organizationId) + .Returns(collections); + sutProvider.GetDependency().GetManyByOrganizationIdAsync(organizationId) + .Returns(collectionsCiphers); + + var result = (await sutProvider.Sut + .GetAllOrganizationCiphersExcludingDefaultUserCollections(organizationId)) + .ToList(); + + Assert.Single(result); + Assert.Equal(cipherOther.Id, result.Single().Id); + } }