diff --git a/src/Core/AdminConsole/OrganizationFeatures/Collections/CollectionUtils.cs b/src/Core/AdminConsole/OrganizationFeatures/Collections/CollectionUtils.cs
new file mode 100644
index 000000000000..116992146f87
--- /dev/null
+++ b/src/Core/AdminConsole/OrganizationFeatures/Collections/CollectionUtils.cs
@@ -0,0 +1,53 @@
+using Bit.Core.Entities;
+using Bit.Core.Enums;
+using Bit.Core.Utilities;
+
+namespace Bit.Core.AdminConsole.OrganizationFeatures.Collections;
+
+public static class CollectionUtils
+{
+ ///
+ /// Arranges Collection and CollectionUser objects to create default user collections.
+ ///
+ /// The organization ID.
+ /// The IDs for organization users who need default collections.
+ /// The encrypted string to use as the default collection name.
+ /// A tuple containing the collections and collection users.
+ public static (ICollection collections, ICollection collectionUsers)
+ BuildDefaultUserCollections(Guid organizationId, IEnumerable organizationUserIds,
+ string defaultCollectionName)
+ {
+ var now = DateTime.UtcNow;
+
+ var collectionUsers = new List();
+ var collections = new List();
+
+ foreach (var orgUserId in organizationUserIds)
+ {
+ var collectionId = CoreHelpers.GenerateComb();
+
+ collections.Add(new Collection
+ {
+ Id = collectionId,
+ OrganizationId = organizationId,
+ Name = defaultCollectionName,
+ CreationDate = now,
+ RevisionDate = now,
+ Type = CollectionType.DefaultUserCollection,
+ DefaultUserCollectionEmail = null
+
+ });
+
+ collectionUsers.Add(new CollectionUser
+ {
+ CollectionId = collectionId,
+ OrganizationUserId = orgUserId,
+ ReadOnly = false,
+ HidePasswords = false,
+ Manage = true,
+ });
+ }
+
+ return (collections, collectionUsers);
+ }
+}
diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/AutomaticallyConfirmOrganizationUserCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/AutomaticallyConfirmOrganizationUserCommand.cs
index 1b488677ae0e..029238185702 100644
--- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/AutomaticallyConfirmOrganizationUserCommand.cs
+++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUser/AutomaticallyConfirmOrganizationUserCommand.cs
@@ -4,9 +4,7 @@
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.OrganizationConfirmation;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
-using Bit.Core.Entities;
using Bit.Core.Enums;
-using Bit.Core.Models.Data;
using Bit.Core.Platform.Push;
using Bit.Core.Repositories;
using Bit.Core.Services;
@@ -83,19 +81,10 @@ private async Task CreateDefaultCollectionsAsync(AutomaticallyConfirmOrganizatio
return;
}
- await collectionRepository.CreateAsync(
- new Collection
- {
- OrganizationId = request.Organization!.Id,
- Name = request.DefaultUserCollectionName,
- Type = CollectionType.DefaultUserCollection
- },
- groups: null,
- [new CollectionAccessSelection
- {
- Id = request.OrganizationUser!.Id,
- Manage = true
- }]);
+ await collectionRepository.CreateDefaultCollectionsAsync(
+ request.Organization!.Id,
+ [request.OrganizationUser!.Id],
+ request.DefaultUserCollectionName);
}
catch (Exception ex)
{
diff --git a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommand.cs b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommand.cs
index 0b82ac7ea441..02f3346ba636 100644
--- a/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommand.cs
+++ b/src/Core/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommand.cs
@@ -14,7 +14,6 @@
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
-using Bit.Core.Models.Data;
using Bit.Core.Platform.Push;
using Bit.Core.Repositories;
using Bit.Core.Services;
@@ -294,21 +293,10 @@ private async Task CreateDefaultCollectionAsync(OrganizationUser organizationUse
return;
}
- var defaultCollection = new Collection
- {
- OrganizationId = organizationUser.OrganizationId,
- Name = defaultUserCollectionName,
- Type = CollectionType.DefaultUserCollection
- };
- var collectionUser = new CollectionAccessSelection
- {
- Id = organizationUser.Id,
- ReadOnly = false,
- HidePasswords = false,
- Manage = true
- };
-
- await _collectionRepository.CreateAsync(defaultCollection, groups: null, users: [collectionUser]);
+ await _collectionRepository.CreateDefaultCollectionsAsync(
+ organizationUser.OrganizationId,
+ [organizationUser.Id],
+ defaultUserCollectionName);
}
///
@@ -339,7 +327,7 @@ private async Task CreateManyDefaultCollectionsAsync(Guid organizationId,
return;
}
- await _collectionRepository.UpsertDefaultCollectionsAsync(organizationId, eligibleOrganizationUserIds, defaultUserCollectionName);
+ await _collectionRepository.CreateDefaultCollectionsAsync(organizationId, eligibleOrganizationUserIds, defaultUserCollectionName);
}
///
diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidator.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidator.cs
index 7a47baa65a78..104a5751fffa 100644
--- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidator.cs
+++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidator.cs
@@ -57,14 +57,15 @@ private async Task UpsertDefaultCollectionsForUsersAsync(PolicyUpdate policyUpda
var userOrgIds = requirements
.Select(requirement => requirement.GetDefaultCollectionRequestOnPolicyEnable(policyUpdate.OrganizationId))
.Where(request => request.ShouldCreateDefaultCollection)
- .Select(request => request.OrganizationUserId);
+ .Select(request => request.OrganizationUserId)
+ .ToList();
if (!userOrgIds.Any())
{
return;
}
- await collectionRepository.UpsertDefaultCollectionsAsync(
+ await collectionRepository.CreateDefaultCollectionsBulkAsync(
policyUpdate.OrganizationId,
userOrgIds,
defaultCollectionName);
diff --git a/src/Core/Repositories/ICollectionRepository.cs b/src/Core/Repositories/ICollectionRepository.cs
index f86147ca7da6..3f3b71d2d5e8 100644
--- a/src/Core/Repositories/ICollectionRepository.cs
+++ b/src/Core/Repositories/ICollectionRepository.cs
@@ -64,11 +64,22 @@ Task CreateOrUpdateAccessForManyAsync(Guid organizationId, IEnumerable col
IEnumerable users, IEnumerable groups);
///
- /// Creates default user collections for the specified organization users if they do not already have one.
+ /// Creates default user collections for the specified organization users.
+ /// Filters internally to only create collections for users who don't already have one.
///
/// The Organization ID.
/// The Organization User IDs to create default collections for.
/// The encrypted string to use as the default collection name.
- ///
- Task UpsertDefaultCollectionsAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName);
+ Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName);
+
+ ///
+ /// Creates default user collections for the specified organization users using bulk insert operations.
+ /// Use this if you need to create collections for > ~1k users.
+ /// Filters internally to only create collections for users who don't already have one.
+ ///
+ /// The Organization ID.
+ /// The Organization User IDs to create default collections for.
+ /// The encrypted string to use as the default collection name.
+ Task CreateDefaultCollectionsBulkAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName);
+
}
diff --git a/src/Infrastructure.Dapper/DapperHelpers.cs b/src/Infrastructure.Dapper/DapperHelpers.cs
index 9a119e1e320d..4384a6f7527b 100644
--- a/src/Infrastructure.Dapper/DapperHelpers.cs
+++ b/src/Infrastructure.Dapper/DapperHelpers.cs
@@ -160,6 +160,21 @@ public static DataTable ToGuidIdArrayTVP(this IEnumerable ids)
return ids.ToArrayTVP("GuidId");
}
+ public static DataTable ToTwoGuidIdArrayTVP(this IEnumerable<(Guid id1, Guid id2)> values)
+ {
+ var table = new DataTable();
+ table.SetTypeName("[dbo].[TwoGuidIdArray]");
+ table.Columns.Add("Id1", typeof(Guid));
+ table.Columns.Add("Id2", typeof(Guid));
+
+ foreach (var value in values)
+ {
+ table.Rows.Add(value.id1, value.id2);
+ }
+
+ return table;
+ }
+
public static DataTable ToArrayTVP(this IEnumerable values, string columnName)
{
var table = new DataTable();
diff --git a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs
index 9985b41d56c4..153170342729 100644
--- a/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs
+++ b/src/Infrastructure.Dapper/Repositories/CollectionRepository.cs
@@ -1,6 +1,7 @@
using System.Data;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
+using Bit.Core.AdminConsole.OrganizationFeatures.Collections;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
@@ -360,7 +361,45 @@ public async Task> GetManyUsersByIdAsync(
}
}
- public async Task UpsertDefaultCollectionsAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName)
+ public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName)
+ {
+ organizationUserIds = organizationUserIds.ToList();
+ if (!organizationUserIds.Any())
+ {
+ return;
+ }
+
+ var organizationUserCollectionIds = organizationUserIds
+ .Select(ou => (ou, CoreHelpers.GenerateComb()))
+ .ToTwoGuidIdArrayTVP();
+
+ await using var connection = new SqlConnection(ConnectionString);
+ await connection.OpenAsync();
+ await using var transaction = connection.BeginTransaction();
+
+ try
+ {
+ await connection.ExecuteAsync(
+ "[dbo].[Collection_CreateDefaultCollections]",
+ new
+ {
+ OrganizationId = organizationId,
+ DefaultCollectionName = defaultCollectionName,
+ OrganizationUserCollectionIds = organizationUserCollectionIds
+ },
+ commandType: CommandType.StoredProcedure,
+ transaction: transaction);
+
+ await transaction.CommitAsync();
+ }
+ catch
+ {
+ await transaction.RollbackAsync();
+ throw;
+ }
+ }
+
+ public async Task CreateDefaultCollectionsBulkAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName)
{
organizationUserIds = organizationUserIds.ToList();
if (!organizationUserIds.Any())
@@ -377,7 +416,8 @@ public async Task UpsertDefaultCollectionsAsync(Guid organizationId, IEnumerable
var missingDefaultCollectionUserIds = organizationUserIds.Except(orgUserIdWithDefaultCollection);
- var (collectionUsers, collections) = BuildDefaultCollectionForUsers(organizationId, missingDefaultCollectionUserIds, defaultCollectionName);
+ var (collections, collectionUsers) =
+ CollectionUtils.BuildDefaultUserCollections(organizationId, missingDefaultCollectionUserIds, defaultCollectionName);
if (!collectionUsers.Any() || !collections.Any())
{
@@ -387,11 +427,11 @@ public async Task UpsertDefaultCollectionsAsync(Guid organizationId, IEnumerable
await BulkResourceCreationService.CreateCollectionsAsync(connection, transaction, collections);
await BulkResourceCreationService.CreateCollectionsUsersAsync(connection, transaction, collectionUsers);
- transaction.Commit();
+ await transaction.CommitAsync();
}
catch
{
- transaction.Rollback();
+ await transaction.RollbackAsync();
throw;
}
}
@@ -421,40 +461,6 @@ INNER JOIN
return organizationUserIds.ToHashSet();
}
- private (List collectionUser, List collection) BuildDefaultCollectionForUsers(Guid organizationId, IEnumerable missingDefaultCollectionUserIds, string defaultCollectionName)
- {
- var collectionUsers = new List();
- var collections = new List();
-
- foreach (var orgUserId in missingDefaultCollectionUserIds)
- {
- var collectionId = CoreHelpers.GenerateComb();
-
- collections.Add(new Collection
- {
- Id = collectionId,
- OrganizationId = organizationId,
- Name = defaultCollectionName,
- CreationDate = DateTime.UtcNow,
- RevisionDate = DateTime.UtcNow,
- Type = CollectionType.DefaultUserCollection,
- DefaultUserCollectionEmail = null
-
- });
-
- collectionUsers.Add(new CollectionUser
- {
- CollectionId = collectionId,
- OrganizationUserId = orgUserId,
- ReadOnly = false,
- HidePasswords = false,
- Manage = true,
- });
- }
-
- return (collectionUsers, collections);
- }
-
public class CollectionWithGroupsAndUsers : Collection
{
public CollectionWithGroupsAndUsers() { }
diff --git a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs
index 5aa156d1f821..74150246b109 100644
--- a/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs
+++ b/src/Infrastructure.EntityFramework/Repositories/CollectionRepository.cs
@@ -1,11 +1,10 @@
using AutoMapper;
+using Bit.Core.AdminConsole.OrganizationFeatures.Collections;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Repositories;
-using Bit.Core.Utilities;
using Bit.Infrastructure.EntityFramework.Models;
using Bit.Infrastructure.EntityFramework.Repositories.Queries;
-using LinqToDB.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
@@ -794,7 +793,7 @@ private static async Task ReplaceCollectionUsersAsync(DatabaseContext dbContext,
// SaveChangesAsync is expected to be called outside this method
}
- public async Task UpsertDefaultCollectionsAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName)
+ public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable organizationUserIds, string defaultCollectionName)
{
organizationUserIds = organizationUserIds.ToList();
if (!organizationUserIds.Any())
@@ -808,15 +807,15 @@ public async Task UpsertDefaultCollectionsAsync(Guid organizationId, IEnumerable
var orgUserIdWithDefaultCollection = await GetOrgUserIdsWithDefaultCollectionAsync(dbContext, organizationId);
var missingDefaultCollectionUserIds = organizationUserIds.Except(orgUserIdWithDefaultCollection);
- var (collectionUsers, collections) = BuildDefaultCollectionForUsers(organizationId, missingDefaultCollectionUserIds, defaultCollectionName);
+ var (collections, collectionUsers) = CollectionUtils.BuildDefaultUserCollections(organizationId, missingDefaultCollectionUserIds, defaultCollectionName);
- if (!collectionUsers.Any() || !collections.Any())
+ if (!collections.Any() || !collectionUsers.Any())
{
return;
}
- await dbContext.BulkCopyAsync(collections);
- await dbContext.BulkCopyAsync(collectionUsers);
+ await dbContext.Collections.AddRangeAsync(Mapper.Map>(collections));
+ await dbContext.CollectionUsers.AddRangeAsync(Mapper.Map>(collectionUsers));
await dbContext.SaveChangesAsync();
}
@@ -844,37 +843,7 @@ private async Task> GetOrgUserIdsWithDefaultCollectionAsync(Databa
return results.ToHashSet();
}
- private (List collectionUser, List collection) BuildDefaultCollectionForUsers(Guid organizationId, IEnumerable missingDefaultCollectionUserIds, string defaultCollectionName)
- {
- var collectionUsers = new List();
- var collections = new List();
-
- foreach (var orgUserId in missingDefaultCollectionUserIds)
- {
- var collectionId = CoreHelpers.GenerateComb();
-
- collections.Add(new Collection
- {
- Id = collectionId,
- OrganizationId = organizationId,
- Name = defaultCollectionName,
- CreationDate = DateTime.UtcNow,
- RevisionDate = DateTime.UtcNow,
- Type = CollectionType.DefaultUserCollection,
- DefaultUserCollectionEmail = null
-
- });
-
- collectionUsers.Add(new CollectionUser
- {
- CollectionId = collectionId,
- OrganizationUserId = orgUserId,
- ReadOnly = false,
- HidePasswords = false,
- Manage = true,
- });
- }
-
- return (collectionUsers, collections);
- }
+ public Task CreateDefaultCollectionsBulkAsync(Guid organizationId, IEnumerable organizationUserIds,
+ string defaultCollectionName) =>
+ CreateDefaultCollectionsAsync(organizationId, organizationUserIds, defaultCollectionName);
}
diff --git a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs
index b748a26db2df..3ddcad55c3d1 100644
--- a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs
+++ b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs
@@ -17,8 +17,6 @@
using DP = Microsoft.AspNetCore.DataProtection;
-#nullable enable
-
namespace Bit.Infrastructure.EntityFramework.Repositories;
public class DatabaseContext : DbContext
diff --git a/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_CreateDefaultCollections.sql b/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_CreateDefaultCollections.sql
new file mode 100644
index 000000000000..4e671bd1e40e
--- /dev/null
+++ b/src/Sql/dbo/AdminConsole/Stored Procedures/Collection_CreateDefaultCollections.sql
@@ -0,0 +1,69 @@
+-- Creates default user collections for organization users
+-- Filters out existing default collections at database level
+CREATE PROCEDURE [dbo].[Collection_CreateDefaultCollections]
+ @OrganizationId UNIQUEIDENTIFIER,
+ @DefaultCollectionName VARCHAR(MAX),
+ @OrganizationUserCollectionIds AS [dbo].[TwoGuidIdArray] READONLY -- OrganizationUserId, CollectionId
+AS
+BEGIN
+ SET NOCOUNT ON
+
+ DECLARE @Now DATETIME2(7) = GETUTCDATE()
+
+ -- Filter to only users who don't have default collections
+ SELECT ids.Id1, ids.Id2
+ INTO #FilteredIds
+ FROM @OrganizationUserCollectionIds ids
+ WHERE NOT EXISTS (
+ SELECT 1
+ FROM [dbo].[CollectionUser] cu
+ INNER JOIN [dbo].[Collection] c ON c.Id = cu.CollectionId
+ WHERE c.OrganizationId = @OrganizationId
+ AND c.[Type] = 1 -- CollectionType.DefaultUserCollection
+ AND cu.OrganizationUserId = ids.Id1
+ );
+
+ -- Insert collections only for users who don't have default collections yet
+ INSERT INTO [dbo].[Collection]
+ (
+ [Id],
+ [OrganizationId],
+ [Name],
+ [CreationDate],
+ [RevisionDate],
+ [Type],
+ [ExternalId],
+ [DefaultUserCollectionEmail]
+ )
+ SELECT
+ ids.Id2, -- CollectionId
+ @OrganizationId,
+ @DefaultCollectionName,
+ @Now,
+ @Now,
+ 1, -- CollectionType.DefaultUserCollection
+ NULL,
+ NULL
+ FROM
+ #FilteredIds ids;
+
+ -- Insert collection user mappings
+ INSERT INTO [dbo].[CollectionUser]
+ (
+ [CollectionId],
+ [OrganizationUserId],
+ [ReadOnly],
+ [HidePasswords],
+ [Manage]
+ )
+ SELECT
+ ids.Id2, -- CollectionId
+ ids.Id1, -- OrganizationUserId
+ 0, -- ReadOnly = false
+ 0, -- HidePasswords = false
+ 1 -- Manage = true
+ FROM
+ #FilteredIds ids;
+
+ DROP TABLE #FilteredIds;
+END
diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUsers/AutomaticallyConfirmUsersCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUsers/AutomaticallyConfirmUsersCommandTests.cs
index 180750a9d0ef..252fb89c87e1 100644
--- a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUsers/AutomaticallyConfirmUsersCommandTests.cs
+++ b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/AutoConfirmUsers/AutomaticallyConfirmUsersCommandTests.cs
@@ -10,7 +10,6 @@
using Bit.Core.AdminConsole.Utilities.v2.Validation;
using Bit.Core.Entities;
using Bit.Core.Enums;
-using Bit.Core.Models.Data;
using Bit.Core.Platform.Push;
using Bit.Core.Repositories;
using Bit.Core.Services;
@@ -204,14 +203,10 @@ public async Task AutomaticallyConfirmOrganizationUserAsync_WithDefaultCollectio
await sutProvider.GetDependency()
.Received(1)
- .CreateAsync(
- Arg.Is(c =>
- c.OrganizationId == organization.Id &&
- c.Name == defaultCollectionName &&
- c.Type == CollectionType.DefaultUserCollection),
- Arg.Is>(groups => groups == null),
- Arg.Is>(access =>
- access.FirstOrDefault(x => x.Id == organizationUser.Id && x.Manage) != null));
+ .CreateDefaultCollectionsAsync(
+ organization.Id,
+ Arg.Is>(ids => ids.Single() == organizationUser.Id),
+ defaultCollectionName);
}
[Theory]
@@ -253,9 +248,7 @@ public async Task AutomaticallyConfirmOrganizationUserAsync_WithDefaultCollectio
await sutProvider.GetDependency()
.DidNotReceive()
- .CreateAsync(Arg.Any(),
- Arg.Any>(),
- Arg.Any>());
+ .CreateDefaultCollectionsAsync(Arg.Any(), Arg.Any>(), Arg.Any());
}
[Theory]
@@ -291,9 +284,7 @@ public async Task AutomaticallyConfirmOrganizationUserAsync_WhenCreateDefaultCol
var collectionException = new Exception("Collection creation failed");
sutProvider.GetDependency()
- .CreateAsync(Arg.Any(),
- Arg.Any>(),
- Arg.Any>())
+ .CreateDefaultCollectionsAsync(Arg.Any(), Arg.Any>(), Arg.Any())
.ThrowsAsync(collectionException);
// Act
diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommandTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommandTests.cs
index 65359b830483..6643f26eb5f4 100644
--- a/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommandTests.cs
+++ b/test/Core.Test/AdminConsole/OrganizationFeatures/OrganizationUsers/ConfirmOrganizationUserCommandTests.cs
@@ -13,7 +13,6 @@
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
-using Bit.Core.Models.Data;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Platform.Push;
using Bit.Core.Repositories;
@@ -493,15 +492,10 @@ public async Task ConfirmUserAsync_WithOrganizationDataOwnershipPolicyApplicable
await sutProvider.GetDependency()
.Received(1)
- .CreateAsync(
- Arg.Is(c =>
- c.Name == collectionName &&
- c.OrganizationId == organization.Id &&
- c.Type == CollectionType.DefaultUserCollection),
- Arg.Any>(),
- Arg.Is>(cu =>
- cu.Single().Id == orgUser.Id &&
- cu.Single().Manage));
+ .CreateDefaultCollectionsAsync(
+ organization.Id,
+ Arg.Is>(ids => ids.Single() == orgUser.Id),
+ collectionName);
}
[Theory, BitAutoData]
@@ -522,7 +516,7 @@ public async Task ConfirmUserAsync_WithOrganizationDataOwnershipPolicyApplicable
await sutProvider.GetDependency()
.DidNotReceive()
- .UpsertDefaultCollectionsAsync(Arg.Any(), Arg.Any>(), Arg.Any());
+ .CreateDefaultCollectionsAsync(Arg.Any(), Arg.Any>(), Arg.Any());
}
[Theory, BitAutoData]
@@ -539,24 +533,15 @@ public async Task ConfirmUserAsync_WithOrganizationDataOwnershipPolicyNotApplica
sutProvider.GetDependency().GetManyAsync(default).ReturnsForAnyArgs(new[] { orgUser });
sutProvider.GetDependency().GetManyAsync(default).ReturnsForAnyArgs(new[] { user });
- var policyDetails = new PolicyDetails
- {
- OrganizationId = org.Id,
- OrganizationUserId = orgUser.Id,
- IsProvider = false,
- OrganizationUserStatus = orgUser.Status,
- OrganizationUserType = orgUser.Type,
- PolicyType = PolicyType.OrganizationDataOwnership
- };
sutProvider.GetDependency()
.GetAsync(orgUser.UserId!.Value)
- .Returns(new OrganizationDataOwnershipPolicyRequirement(OrganizationDataOwnershipState.Disabled, [policyDetails]));
+ .Returns(new OrganizationDataOwnershipPolicyRequirement(OrganizationDataOwnershipState.Disabled, []));
await sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id, collectionName);
await sutProvider.GetDependency()
.DidNotReceive()
- .UpsertDefaultCollectionsAsync(Arg.Any(), Arg.Any>(), Arg.Any());
+ .CreateDefaultCollectionsAsync(Arg.Any(), Arg.Any>(), Arg.Any());
}
[Theory, BitAutoData]
diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidatorTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidatorTests.cs
index 93cbde89ecf2..dd2f1d76e802 100644
--- a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidatorTests.cs
+++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyValidators/OrganizationDataOwnershipPolicyValidatorTests.cs
@@ -38,7 +38,7 @@ public async Task ExecuteSideEffectsAsync_PolicyAlreadyEnabled_DoesNothing(
// Assert
await sutProvider.GetDependency()
.DidNotReceive()
- .UpsertDefaultCollectionsAsync(Arg.Any(), Arg.Any>(), Arg.Any());
+ .CreateDefaultCollectionsBulkAsync(Arg.Any(), Arg.Any>(), Arg.Any());
}
[Theory, BitAutoData]
@@ -60,7 +60,7 @@ public async Task ExecuteSideEffectsAsync_PolicyBeingDisabled_DoesNothing(
// Assert
await sutProvider.GetDependency()
.DidNotReceive()
- .UpsertDefaultCollectionsAsync(Arg.Any(), Arg.Any>(), Arg.Any());
+ .CreateDefaultCollectionsBulkAsync(Arg.Any(), Arg.Any>(), Arg.Any());
}
[Theory, BitAutoData]
@@ -86,7 +86,7 @@ public async Task ExecuteSideEffectsAsync_WhenNoUsersExist_DoNothing(
// Assert
await collectionRepository
.DidNotReceive()
- .UpsertDefaultCollectionsAsync(
+ .CreateDefaultCollectionsBulkAsync(
Arg.Any(),
Arg.Any>(),
Arg.Any());
@@ -172,10 +172,10 @@ public async Task ExecuteSideEffectsAsync_WithRequirements_ShouldUpsertDefaultCo
// Act
await sut.ExecuteSideEffectsAsync(policyRequest, postUpdatedPolicy, previousPolicyState);
- // Assert
+ // Assert - Should call with all user IDs (repository does internal filtering)
await collectionRepository
.Received(1)
- .UpsertDefaultCollectionsAsync(
+ .CreateDefaultCollectionsBulkAsync(
policyUpdate.OrganizationId,
Arg.Is>(ids => ids.Count() == 3),
_defaultUserCollectionName);
@@ -210,7 +210,7 @@ public async Task ExecuteSideEffectsAsync_WhenDefaultCollectionNameIsInvalid_Doe
// Assert
await sutProvider.GetDependency()
.DidNotReceive()
- .UpsertDefaultCollectionsAsync(Arg.Any(), Arg.Any>(), Arg.Any());
+ .CreateDefaultCollectionsBulkAsync(Arg.Any(), Arg.Any>(), Arg.Any());
}
private static IPolicyRepository ArrangePolicyRepository(IEnumerable policyDetails)
@@ -251,7 +251,7 @@ public async Task ExecutePostUpsertSideEffectAsync_PolicyAlreadyEnabled_DoesNoth
// Assert
await sutProvider.GetDependency()
.DidNotReceiveWithAnyArgs()
- .UpsertDefaultCollectionsAsync(default, default, default);
+ .CreateDefaultCollectionsBulkAsync(default, default, default);
}
[Theory, BitAutoData]
@@ -273,7 +273,7 @@ public async Task ExecutePostUpsertSideEffectAsync_PolicyBeingDisabled_DoesNothi
// Assert
await sutProvider.GetDependency()
.DidNotReceiveWithAnyArgs()
- .UpsertDefaultCollectionsAsync(default, default, default);
+ .CreateDefaultCollectionsBulkAsync(default, default, default);
}
[Theory, BitAutoData]
@@ -299,7 +299,7 @@ public async Task ExecutePostUpsertSideEffectAsync_WhenNoUsersExist_DoNothing(
// Assert
await collectionRepository
.DidNotReceiveWithAnyArgs()
- .UpsertDefaultCollectionsAsync(
+ .CreateDefaultCollectionsBulkAsync(
default,
default,
default);
@@ -336,10 +336,10 @@ public async Task ExecutePostUpsertSideEffectAsync_WithRequirements_ShouldUpsert
// Act
await sut.ExecutePostUpsertSideEffectAsync(policyRequest, postUpdatedPolicy, previousPolicyState);
- // Assert
+ // Assert - Should call with all user IDs (repository does internal filtering)
await collectionRepository
.Received(1)
- .UpsertDefaultCollectionsAsync(
+ .CreateDefaultCollectionsBulkAsync(
policyUpdate.OrganizationId,
Arg.Is>(ids => ids.Count() == 3),
_defaultUserCollectionName);
@@ -367,6 +367,6 @@ public async Task ExecutePostUpsertSideEffectAsync_WhenDefaultCollectionNameIsIn
// Assert
await sutProvider.GetDependency()
.DidNotReceiveWithAnyArgs()
- .UpsertDefaultCollectionsAsync(default, default, default);
+ .CreateDefaultCollectionsBulkAsync(default, default, default);
}
}
diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsBulkTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsBulkTests.cs
new file mode 100644
index 000000000000..712ad7d62e38
--- /dev/null
+++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsBulkTests.cs
@@ -0,0 +1,53 @@
+using Bit.Core.Repositories;
+using Xunit;
+
+namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Repositories.CollectionRepository;
+
+
+public class CreateDefaultCollectionsBulkAsyncTests
+{
+ [Theory, DatabaseData]
+ public async Task CreateDefaultCollectionsBulkAsync_CreatesDefaultCollections_Success(
+ IOrganizationRepository organizationRepository,
+ IUserRepository userRepository,
+ IOrganizationUserRepository organizationUserRepository,
+ ICollectionRepository collectionRepository)
+ {
+ await CreateDefaultCollectionsSharedTests.CreatesDefaultCollections_Success(
+ collectionRepository.CreateDefaultCollectionsBulkAsync,
+ organizationRepository,
+ userRepository,
+ organizationUserRepository,
+ collectionRepository);
+ }
+
+ [Theory, DatabaseData]
+ public async Task CreateDefaultCollectionsBulkAsync_CreatesForNewUsersOnly_AndIgnoresExistingUsers(
+ IOrganizationRepository organizationRepository,
+ IUserRepository userRepository,
+ IOrganizationUserRepository organizationUserRepository,
+ ICollectionRepository collectionRepository)
+ {
+ await CreateDefaultCollectionsSharedTests.CreatesForNewUsersOnly_AndIgnoresExistingUsers(
+ collectionRepository.CreateDefaultCollectionsBulkAsync,
+ organizationRepository,
+ userRepository,
+ organizationUserRepository,
+ collectionRepository);
+ }
+
+ [Theory, DatabaseData]
+ public async Task CreateDefaultCollectionsBulkAsync_IgnoresAllExistingUsers(
+ IOrganizationRepository organizationRepository,
+ IUserRepository userRepository,
+ IOrganizationUserRepository organizationUserRepository,
+ ICollectionRepository collectionRepository)
+ {
+ await CreateDefaultCollectionsSharedTests.IgnoresAllExistingUsers(
+ collectionRepository.CreateDefaultCollectionsBulkAsync,
+ organizationRepository,
+ userRepository,
+ organizationUserRepository,
+ collectionRepository);
+ }
+}
diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/UpsertDefaultCollectionsTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsSharedTests.cs
similarity index 69%
rename from test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/UpsertDefaultCollectionsTests.cs
rename to test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsSharedTests.cs
index 64dffa473f1f..0fb4a5b446e1 100644
--- a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/UpsertDefaultCollectionsTests.cs
+++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsSharedTests.cs
@@ -6,10 +6,14 @@
namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Repositories.CollectionRepository;
-public class UpsertDefaultCollectionsTests
+///
+/// Shared tests for CreateDefaultCollections methods - both bulk and non-bulk implementations,
+/// as they share the same behavior. Both test suites call the tests in this class.
+///
+public static class CreateDefaultCollectionsSharedTests
{
- [Theory, DatabaseData]
- public async Task UpsertDefaultCollectionsAsync_ShouldCreateDefaultCollection_WhenUsersDoNotHaveDefaultCollection(
+ public static async Task CreatesDefaultCollections_Success(
+ Func, string, Task> createDefaultCollectionsFunc,
IOrganizationRepository organizationRepository,
IUserRepository userRepository,
IOrganizationUserRepository organizationUserRepository,
@@ -21,14 +25,13 @@ public async Task UpsertDefaultCollectionsAsync_ShouldCreateDefaultCollection_Wh
var resultOrganizationUsers = await Task.WhenAll(
CreateUserForOrgAsync(userRepository, organizationUserRepository, organization),
CreateUserForOrgAsync(userRepository, organizationUserRepository, organization)
- );
-
+ );
- var affectedOrgUserIds = resultOrganizationUsers.Select(organizationUser => organizationUser.Id);
+ var affectedOrgUserIds = resultOrganizationUsers.Select(organizationUser => organizationUser.Id).ToList();
var defaultCollectionName = $"default-name-{organization.Id}";
// Act
- await collectionRepository.UpsertDefaultCollectionsAsync(organization.Id, affectedOrgUserIds, defaultCollectionName);
+ await createDefaultCollectionsFunc(organization.Id, affectedOrgUserIds, defaultCollectionName);
// Assert
await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, resultOrganizationUsers, organization.Id);
@@ -36,8 +39,8 @@ public async Task UpsertDefaultCollectionsAsync_ShouldCreateDefaultCollection_Wh
await CleanupAsync(organizationRepository, userRepository, organization, resultOrganizationUsers);
}
- [Theory, DatabaseData]
- public async Task UpsertDefaultCollectionsAsync_ShouldUpsertCreateDefaultCollection_ForUsersWithAndWithoutDefaultCollectionsExist(
+ public static async Task CreatesForNewUsersOnly_AndIgnoresExistingUsers(
+ Func, string, Task> createDefaultCollectionsFunc,
IOrganizationRepository organizationRepository,
IUserRepository userRepository,
IOrganizationUserRepository organizationUserRepository,
@@ -51,31 +54,30 @@ public async Task UpsertDefaultCollectionsAsync_ShouldUpsertCreateDefaultCollect
CreateUserForOrgAsync(userRepository, organizationUserRepository, organization)
);
- var arrangedOrgUserIds = arrangedOrganizationUsers.Select(organizationUser => organizationUser.Id);
+ var arrangedOrgUserIds = arrangedOrganizationUsers.Select(organizationUser => organizationUser.Id).ToList();
var defaultCollectionName = $"default-name-{organization.Id}";
+ await CreateUsersWithExistingDefaultCollectionsAsync(createDefaultCollectionsFunc, collectionRepository, organization.Id, arrangedOrgUserIds, defaultCollectionName, arrangedOrganizationUsers);
- await CreateUsersWithExistingDefaultCollectionsAsync(collectionRepository, organization.Id, arrangedOrgUserIds, defaultCollectionName, arrangedOrganizationUsers);
-
- var newOrganizationUsers = new List()
+ var newOrganizationUsers = new List
{
await CreateUserForOrgAsync(userRepository, organizationUserRepository, organization)
};
- var affectedOrgUsers = newOrganizationUsers.Concat(arrangedOrganizationUsers);
- var affectedOrgUserIds = affectedOrgUsers.Select(organizationUser => organizationUser.Id);
+ var affectedOrgUsers = newOrganizationUsers.Concat(arrangedOrganizationUsers).ToList();
+ var affectedOrgUserIds = affectedOrgUsers.Select(organizationUser => organizationUser.Id).ToList();
// Act
- await collectionRepository.UpsertDefaultCollectionsAsync(organization.Id, affectedOrgUserIds, defaultCollectionName);
+ await createDefaultCollectionsFunc(organization.Id, affectedOrgUserIds, defaultCollectionName);
// Assert
- await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, arrangedOrganizationUsers, organization.Id);
+ await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, affectedOrgUsers, organization.Id);
await CleanupAsync(organizationRepository, userRepository, organization, affectedOrgUsers);
}
- [Theory, DatabaseData]
- public async Task UpsertDefaultCollectionsAsync_ShouldNotCreateDefaultCollection_WhenUsersAlreadyHaveOne(
+ public static async Task IgnoresAllExistingUsers(
+ Func, string, Task> createDefaultCollectionsFunc,
IOrganizationRepository organizationRepository,
IUserRepository userRepository,
IOrganizationUserRepository organizationUserRepository,
@@ -89,26 +91,29 @@ public async Task UpsertDefaultCollectionsAsync_ShouldNotCreateDefaultCollection
CreateUserForOrgAsync(userRepository, organizationUserRepository, organization)
);
- var affectedOrgUserIds = resultOrganizationUsers.Select(organizationUser => organizationUser.Id);
+ var affectedOrgUserIds = resultOrganizationUsers.Select(organizationUser => organizationUser.Id).ToList();
var defaultCollectionName = $"default-name-{organization.Id}";
+ await CreateUsersWithExistingDefaultCollectionsAsync(createDefaultCollectionsFunc, collectionRepository, organization.Id, affectedOrgUserIds, defaultCollectionName, resultOrganizationUsers);
- await CreateUsersWithExistingDefaultCollectionsAsync(collectionRepository, organization.Id, affectedOrgUserIds, defaultCollectionName, resultOrganizationUsers);
+ // Act - Try to create again, should silently filter and not create duplicates
+ await createDefaultCollectionsFunc(organization.Id, affectedOrgUserIds, defaultCollectionName);
- // Act
- await collectionRepository.UpsertDefaultCollectionsAsync(organization.Id, affectedOrgUserIds, defaultCollectionName);
-
- // Assert
+ // Assert - Original collections should remain unchanged, still only one per user
await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, resultOrganizationUsers, organization.Id);
await CleanupAsync(organizationRepository, userRepository, organization, resultOrganizationUsers);
}
- private static async Task CreateUsersWithExistingDefaultCollectionsAsync(ICollectionRepository collectionRepository,
- Guid organizationId, IEnumerable affectedOrgUserIds, string defaultCollectionName,
+ private static async Task CreateUsersWithExistingDefaultCollectionsAsync(
+ Func, string, Task> createDefaultCollectionsFunc,
+ ICollectionRepository collectionRepository,
+ Guid organizationId,
+ IEnumerable affectedOrgUserIds,
+ string defaultCollectionName,
OrganizationUser[] resultOrganizationUsers)
{
- await collectionRepository.UpsertDefaultCollectionsAsync(organizationId, affectedOrgUserIds, defaultCollectionName);
+ await createDefaultCollectionsFunc(organizationId, affectedOrgUserIds, defaultCollectionName);
await AssertAllUsersHaveOneDefaultCollectionAsync(collectionRepository, resultOrganizationUsers, organizationId);
}
@@ -131,7 +136,6 @@ private static async Task AssertAllUsersHaveOneDefaultCollectionAsync(ICollectio
private static async Task CreateUserForOrgAsync(IUserRepository userRepository,
IOrganizationUserRepository organizationUserRepository, Organization organization)
{
-
var user = await userRepository.CreateTestUserAsync();
var orgUser = await organizationUserRepository.CreateTestOrganizationUserAsync(organization, user);
diff --git a/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs
new file mode 100644
index 000000000000..bd894e9ca59a
--- /dev/null
+++ b/test/Infrastructure.IntegrationTest/AdminConsole/Repositories/CollectionRepository/CreateDefaultCollectionsTests.cs
@@ -0,0 +1,52 @@
+using Bit.Core.Repositories;
+using Xunit;
+
+namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Repositories.CollectionRepository;
+
+public class CreateDefaultCollectionsAsyncTests
+{
+ [Theory, DatabaseData]
+ public async Task CreateDefaultCollectionsAsync_CreatesDefaultCollections_Success(
+ IOrganizationRepository organizationRepository,
+ IUserRepository userRepository,
+ IOrganizationUserRepository organizationUserRepository,
+ ICollectionRepository collectionRepository)
+ {
+ await CreateDefaultCollectionsSharedTests.CreatesDefaultCollections_Success(
+ collectionRepository.CreateDefaultCollectionsAsync,
+ organizationRepository,
+ userRepository,
+ organizationUserRepository,
+ collectionRepository);
+ }
+
+ [Theory, DatabaseData]
+ public async Task CreateDefaultCollectionsAsync_CreatesForNewUsersOnly_AndIgnoresExistingUsers(
+ IOrganizationRepository organizationRepository,
+ IUserRepository userRepository,
+ IOrganizationUserRepository organizationUserRepository,
+ ICollectionRepository collectionRepository)
+ {
+ await CreateDefaultCollectionsSharedTests.CreatesForNewUsersOnly_AndIgnoresExistingUsers(
+ collectionRepository.CreateDefaultCollectionsAsync,
+ organizationRepository,
+ userRepository,
+ organizationUserRepository,
+ collectionRepository);
+ }
+
+ [Theory, DatabaseData]
+ public async Task CreateDefaultCollectionsAsync_IgnoresAllExistingUsers(
+ IOrganizationRepository organizationRepository,
+ IUserRepository userRepository,
+ IOrganizationUserRepository organizationUserRepository,
+ ICollectionRepository collectionRepository)
+ {
+ await CreateDefaultCollectionsSharedTests.IgnoresAllExistingUsers(
+ collectionRepository.CreateDefaultCollectionsAsync,
+ organizationRepository,
+ userRepository,
+ organizationUserRepository,
+ collectionRepository);
+ }
+}
diff --git a/util/Migrator/DbScripts/2026-01-13_00_Collection_CreateDefaultCollections.sql b/util/Migrator/DbScripts/2026-01-13_00_Collection_CreateDefaultCollections.sql
new file mode 100644
index 000000000000..c7935db5e846
--- /dev/null
+++ b/util/Migrator/DbScripts/2026-01-13_00_Collection_CreateDefaultCollections.sql
@@ -0,0 +1,70 @@
+-- Creates default user collections for organization users
+-- Filters out existing default collections at database level
+CREATE OR ALTER PROCEDURE [dbo].[Collection_CreateDefaultCollections]
+ @OrganizationId UNIQUEIDENTIFIER,
+ @DefaultCollectionName VARCHAR(MAX),
+ @OrganizationUserCollectionIds AS [dbo].[TwoGuidIdArray] READONLY -- OrganizationUserId, CollectionId
+AS
+BEGIN
+ SET NOCOUNT ON
+
+ DECLARE @Now DATETIME2(7) = GETUTCDATE()
+
+ -- Filter to only users who don't have default collections
+ SELECT ids.Id1, ids.Id2
+ INTO #FilteredIds
+ FROM @OrganizationUserCollectionIds ids
+ WHERE NOT EXISTS (
+ SELECT 1
+ FROM [dbo].[CollectionUser] cu
+ INNER JOIN [dbo].[Collection] c ON c.Id = cu.CollectionId
+ WHERE c.OrganizationId = @OrganizationId
+ AND c.[Type] = 1 -- CollectionType.DefaultUserCollection
+ AND cu.OrganizationUserId = ids.Id1
+ );
+
+ -- Insert collections only for users who don't have default collections yet
+ INSERT INTO [dbo].[Collection]
+ (
+ [Id],
+ [OrganizationId],
+ [Name],
+ [CreationDate],
+ [RevisionDate],
+ [Type],
+ [ExternalId],
+ [DefaultUserCollectionEmail]
+ )
+ SELECT
+ ids.Id2, -- CollectionId
+ @OrganizationId,
+ @DefaultCollectionName,
+ @Now,
+ @Now,
+ 1, -- CollectionType.DefaultUserCollection
+ NULL,
+ NULL
+ FROM
+ #FilteredIds ids;
+
+ -- Insert collection user mappings
+ INSERT INTO [dbo].[CollectionUser]
+ (
+ [CollectionId],
+ [OrganizationUserId],
+ [ReadOnly],
+ [HidePasswords],
+ [Manage]
+ )
+ SELECT
+ ids.Id2, -- CollectionId
+ ids.Id1, -- OrganizationUserId
+ 0, -- ReadOnly = false
+ 0, -- HidePasswords = false
+ 1 -- Manage = true
+ FROM
+ #FilteredIds ids;
+
+ DROP TABLE #FilteredIds;
+END
+GO