1
0
mirror of https://github.com/bitwarden/server synced 2025-12-27 05:33:17 +00:00

Add bulk default collection creation method (#6075)

This commit is contained in:
Jimmy Vo
2025-07-31 11:24:16 -04:00
committed by GitHub
parent 86ce3a86e9
commit ff5659cc0f
6 changed files with 486 additions and 12 deletions

View File

@@ -2,9 +2,11 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Repositories;
using Bit.Core.Settings;
using Bit.Infrastructure.Dapper.AdminConsole.Helpers;
using Dapper;
using Microsoft.Data.SqlClient;
@@ -222,6 +224,8 @@ public class CollectionRepository : Repository<Collection, Guid>, ICollectionRep
public async Task CreateAsync(Collection obj, IEnumerable<CollectionAccessSelection>? groups, IEnumerable<CollectionAccessSelection>? users)
{
obj.SetNewId();
var objWithGroupsAndUsers = JsonSerializer.Deserialize<CollectionWithGroupsAndUsers>(JsonSerializer.Serialize(obj))!;
objWithGroupsAndUsers.Groups = groups != null ? groups.ToArrayTVP() : Enumerable.Empty<CollectionAccessSelection>().ToArrayTVP();
@@ -322,6 +326,100 @@ public class CollectionRepository : Repository<Collection, Guid>, ICollectionRep
}
}
public async Task CreateDefaultCollectionsAsync(Guid organizationId, IEnumerable<Guid> affectedOrgUserIds, string defaultCollectionName)
{
if (!affectedOrgUserIds.Any())
{
return;
}
await using var connection = new SqlConnection(ConnectionString);
connection.Open();
await using var transaction = connection.BeginTransaction();
try
{
var orgUserIdWithDefaultCollection = await GetOrgUserIdsWithDefaultCollectionAsync(connection, transaction, organizationId);
var missingDefaultCollectionUserIds = affectedOrgUserIds.Except(orgUserIdWithDefaultCollection);
var (collectionUsers, collections) = BuildDefaultCollectionForUsers(organizationId, missingDefaultCollectionUserIds, defaultCollectionName);
if (!collectionUsers.Any() || !collections.Any())
{
return;
}
await BulkResourceCreationService.CreateCollectionsAsync(connection, transaction, collections);
await BulkResourceCreationService.CreateCollectionsUsersAsync(connection, transaction, collectionUsers);
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
private async Task<HashSet<Guid>> GetOrgUserIdsWithDefaultCollectionAsync(SqlConnection connection, SqlTransaction transaction, Guid organizationId)
{
const string sql = @"
SELECT
ou.Id AS OrganizationUserId
FROM
OrganizationUser ou
INNER JOIN
CollectionUser cu ON cu.OrganizationUserId = ou.Id
INNER JOIN
Collection c ON c.Id = cu.CollectionId
WHERE
ou.OrganizationId = @OrganizationId
AND c.Type = @CollectionType;
";
var organizationUserIds = await connection.QueryAsync<Guid>(
sql,
new { OrganizationId = organizationId, CollectionType = CollectionType.DefaultUserCollection },
transaction: transaction
);
return organizationUserIds.ToHashSet();
}
private (List<CollectionUser> collectionUser, List<Collection> collection) BuildDefaultCollectionForUsers(Guid organizationId, IEnumerable<Guid> missingDefaultCollectionUserIds, string defaultCollectionName)
{
var collectionUsers = new List<CollectionUser>();
var collections = new List<Collection>();
foreach (var orgUserId in missingDefaultCollectionUserIds)
{
var collectionId = Guid.NewGuid();
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
{
[DisallowNull]