diff --git a/src/Infrastructure.EntityFramework/Repositories/CollectionCipherRepository.cs b/src/Infrastructure.EntityFramework/Repositories/CollectionCipherRepository.cs index 6e2805f987..39e3ab8019 100644 --- a/src/Infrastructure.EntityFramework/Repositories/CollectionCipherRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/CollectionCipherRepository.cs @@ -1,4 +1,5 @@ using AutoMapper; +using Bit.Core.Enums; using Bit.Core.Repositories; using Bit.Infrastructure.EntityFramework.Repositories.Queries; using Microsoft.EntityFrameworkCore; @@ -145,9 +146,11 @@ public class CollectionCipherRepository : BaseEntityFrameworkRepository, ICollec using (var scope = ServiceScopeFactory.CreateScope()) { var dbContext = GetDatabaseContext(scope); - var availableCollections = await (from c in dbContext.Collections - where c.OrganizationId == organizationId - select c).ToListAsync(); + + var availableCollectionIds = await (from c in dbContext.Collections + where c.OrganizationId == organizationId + && c.Type != CollectionType.DefaultUserCollection + select c.Id).ToListAsync(); var currentCollectionCiphers = await (from cc in dbContext.CollectionCiphers where cc.CipherId == cipherId @@ -155,6 +158,8 @@ public class CollectionCipherRepository : BaseEntityFrameworkRepository, ICollec foreach (var requestedCollectionId in collectionIds) { + if (!availableCollectionIds.Contains(requestedCollectionId)) continue; + var requestedCollectionCipher = currentCollectionCiphers .FirstOrDefault(cc => cc.CollectionId == requestedCollectionId); @@ -168,7 +173,7 @@ public class CollectionCipherRepository : BaseEntityFrameworkRepository, ICollec } } - dbContext.RemoveRange(currentCollectionCiphers.Where(cc => !collectionIds.Contains(cc.CollectionId))); + dbContext.RemoveRange(currentCollectionCiphers.Where(cc => availableCollectionIds.Contains(cc.CollectionId) && !collectionIds.Contains(cc.CollectionId))); await dbContext.UserBumpAccountRevisionDateByOrganizationIdAsync(organizationId); await dbContext.SaveChangesAsync(); } diff --git a/src/Sql/dbo/Stored Procedures/CollectionCipher_UpdateCollections.sql b/src/Sql/dbo/Stored Procedures/CollectionCipher_UpdateCollections.sql index f3a1d964b5..2282524228 100644 --- a/src/Sql/dbo/Stored Procedures/CollectionCipher_UpdateCollections.sql +++ b/src/Sql/dbo/Stored Procedures/CollectionCipher_UpdateCollections.sql @@ -44,13 +44,13 @@ BEGIN [CollectionId], [CipherId] ) - SELECT + SELECT [Id], @CipherId FROM @CollectionIds WHERE [Id] IN (SELECT [Id] FROM [#TempAvailableCollections]) AND NOT EXISTS ( - SELECT 1 + SELECT 1 FROM [dbo].[CollectionCipher] WHERE [CollectionId] = [@CollectionIds].[Id] AND [CipherId] = @CipherId diff --git a/src/Sql/dbo/Stored Procedures/CollectionCipher_UpdateCollectionsAdmin.sql b/src/Sql/dbo/Stored Procedures/CollectionCipher_UpdateCollectionsAdmin.sql index 5f7b0215d9..1486709f09 100644 --- a/src/Sql/dbo/Stored Procedures/CollectionCipher_UpdateCollectionsAdmin.sql +++ b/src/Sql/dbo/Stored Procedures/CollectionCipher_UpdateCollectionsAdmin.sql @@ -4,46 +4,52 @@ @CollectionIds AS [dbo].[GuidIdArray] READONLY AS BEGIN - SET NOCOUNT ON + SET NOCOUNT ON; - ;WITH [AvailableCollectionsCTE] AS( - SELECT - Id - FROM - [dbo].[Collection] - WHERE - OrganizationId = @OrganizationId - ), - [CollectionCiphersCTE] AS( - SELECT - [CollectionId], - [CipherId] - FROM - [dbo].[CollectionCipher] - WHERE - [CipherId] = @CipherId + -- Available collections for this org, excluding default collections + SELECT + C.[Id] + INTO #TempAvailableCollections + FROM [dbo].[Collection] AS C + WHERE + C.[OrganizationId] = @OrganizationId + AND C.[Type] <> 1; -- exclude DefaultUserCollection + + -- Insert new collection assignments + INSERT INTO [dbo].[CollectionCipher] ( + [CollectionId], + [CipherId] ) - MERGE - [CollectionCiphersCTE] AS [Target] - USING - @CollectionIds AS [Source] - ON - [Target].[CollectionId] = [Source].[Id] - AND [Target].[CipherId] = @CipherId - WHEN NOT MATCHED BY TARGET - AND [Source].[Id] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) THEN - INSERT VALUES - ( - [Source].[Id], - @CipherId - ) - WHEN NOT MATCHED BY SOURCE - AND [Target].[CipherId] = @CipherId THEN - DELETE - ; + SELECT + S.[Id], + @CipherId + FROM @CollectionIds AS S + INNER JOIN #TempAvailableCollections AS A + ON A.[Id] = S.[Id] + WHERE NOT EXISTS ( + SELECT 1 + FROM [dbo].[CollectionCipher] AS CC + WHERE CC.[CollectionId] = S.[Id] + AND CC.[CipherId] = @CipherId + ); + + -- Delete removed collection assignments + DELETE CC + FROM [dbo].[CollectionCipher] AS CC + INNER JOIN #TempAvailableCollections AS A + ON A.[Id] = CC.[CollectionId] + WHERE CC.[CipherId] = @CipherId + AND NOT EXISTS ( + SELECT 1 + FROM @CollectionIds AS S + WHERE S.[Id] = CC.[CollectionId] + ); IF @OrganizationId IS NOT NULL BEGIN - EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId + EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId; END -END \ No newline at end of file + + DROP TABLE #TempAvailableCollections; +END +GO diff --git a/util/Migrator/DbScripts/2025-09-23_00_UpdateCollectionCipher_UpdateCollectionsAdmin.sql b/util/Migrator/DbScripts/2025-09-23_00_UpdateCollectionCipher_UpdateCollectionsAdmin.sql new file mode 100644 index 0000000000..f69fdb30ff --- /dev/null +++ b/util/Migrator/DbScripts/2025-09-23_00_UpdateCollectionCipher_UpdateCollectionsAdmin.sql @@ -0,0 +1,55 @@ +CREATE OR ALTER PROCEDURE [dbo].[CollectionCipher_UpdateCollectionsAdmin] + @CipherId UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @CollectionIds AS [dbo].[GuidIdArray] READONLY +AS +BEGIN + SET NOCOUNT ON; + + -- Available collections for this org, excluding default collections + SELECT + C.[Id] + INTO #TempAvailableCollections + FROM [dbo].[Collection] AS C + WHERE + C.[OrganizationId] = @OrganizationId + AND C.[Type] <> 1; -- exclude DefaultUserCollection + + -- Insert new collection assignments + INSERT INTO [dbo].[CollectionCipher] ( + [CollectionId], + [CipherId] + ) + SELECT + S.[Id], + @CipherId + FROM @CollectionIds AS S + INNER JOIN #TempAvailableCollections AS A + ON A.[Id] = S.[Id] + WHERE NOT EXISTS ( + SELECT 1 + FROM [dbo].[CollectionCipher] AS CC + WHERE CC.[CollectionId] = S.[Id] + AND CC.[CipherId] = @CipherId + ); + + -- Delete removed collection assignments + DELETE CC + FROM [dbo].[CollectionCipher] AS CC + INNER JOIN #TempAvailableCollections AS A + ON A.[Id] = CC.[CollectionId] + WHERE CC.[CipherId] = @CipherId + AND NOT EXISTS ( + SELECT 1 + FROM @CollectionIds AS S + WHERE S.[Id] = CC.[CollectionId] + ); + + IF @OrganizationId IS NOT NULL + BEGIN + EXEC [dbo].[User_BumpAccountRevisionDateByOrganizationId] @OrganizationId; + END + + DROP TABLE #TempAvailableCollections; +END +GO