1
0
mirror of https://github.com/bitwarden/server synced 2026-01-28 23:36:12 +00:00

Move transaction up to code layer

This commit is contained in:
Thomas Rittson
2026-01-03 13:49:38 +10:00
parent 2b7caf1e37
commit 2fc0257d1d
4 changed files with 119 additions and 133 deletions

View File

@@ -375,6 +375,7 @@ public class CollectionRepository : Repository<Collection, Guid>, ICollectionRep
await using var connection = new SqlConnection(ConnectionString);
await connection.OpenAsync();
await using var transaction = connection.BeginTransaction();
try
{
@@ -386,12 +387,20 @@ public class CollectionRepository : Repository<Collection, Guid>, ICollectionRep
DefaultCollectionName = defaultCollectionName,
OrganizationUserCollectionIds = organizationUserCollectionIds
},
commandType: CommandType.StoredProcedure);
commandType: CommandType.StoredProcedure,
transaction: transaction);
await transaction.CommitAsync();
}
catch (Exception ex) when (DatabaseExceptionHelpers.IsDuplicateKeyException(ex))
{
await transaction.RollbackAsync();
throw new DuplicateDefaultCollectionException();
}
catch
{
await transaction.RollbackAsync();
}
}
public async Task CreateDefaultCollectionsBulkAsync(Guid organizationId, IEnumerable<Guid> organizationUserIds, string defaultCollectionName)
@@ -417,16 +426,16 @@ public class CollectionRepository : Repository<Collection, Guid>, ICollectionRep
await BulkResourceCreationService.CreateCollectionsAsync(connection, transaction, collections);
await BulkResourceCreationService.CreateCollectionsUsersAsync(connection, transaction, collectionUsers);
transaction.Commit();
await transaction.CommitAsync();
}
catch (Exception ex) when (DatabaseExceptionHelpers.IsDuplicateKeyException(ex))
{
transaction.Rollback();
await transaction.RollbackAsync();
throw new DuplicateDefaultCollectionException();
}
catch
{
transaction.Rollback();
await transaction.RollbackAsync();
throw;
}
}

View File

@@ -819,7 +819,6 @@ public class CollectionRepository : Repository<Core.Entities.Collection, Collect
await dbContext.BulkCopyAsync(Mapper.Map<IEnumerable<Collection>>(collections));
await dbContext.BulkCopyAsync(Mapper.Map<IEnumerable<CollectionUser>>(collectionUsers));
await dbContext.SaveChangesAsync();
await transaction.CommitAsync();
}
catch (Exception ex) when (DatabaseExceptionHelpers.IsDuplicateKeyException(ex))

View File

@@ -1,5 +1,6 @@
-- Creates default user collections for organization users
-- Uses semaphore table to prevent duplicate default collections at database level
-- NOTE: this MUST be executed in a single transaction to obtain semaphore protection
CREATE PROCEDURE [dbo].[Collection_CreateDefaultCollections]
@OrganizationId UNIQUEIDENTIFIER,
@DefaultCollectionName VARCHAR(MAX),
@@ -10,70 +11,58 @@ BEGIN
DECLARE @Now DATETIME2(7) = GETUTCDATE()
BEGIN TRANSACTION;
-- Insert semaphore entries first to obtain the "lock"
-- If this fails due to duplicate key, the entire transaction will be rolled back
INSERT INTO [dbo].[DefaultCollectionSemaphore]
(
[OrganizationUserId],
[CreationDate]
)
SELECT
ids.[Id1], -- OrganizationUserId
@Now
FROM
@OrganizationUserCollectionIds ids;
BEGIN TRY
-- Insert semaphore entries first to obtain the "lock"
-- If this fails due to duplicate key, the entire transaction will be rolled back
INSERT INTO [dbo].[DefaultCollectionSemaphore]
(
[OrganizationUserId],
[CreationDate]
)
SELECT
ids.[Id1], -- OrganizationUserId
@Now
FROM
@OrganizationUserCollectionIds ids;
-- Insert collections for users who obtained semaphore entries
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
@OrganizationUserCollectionIds ids;
-- Insert collections for users who obtained semaphore entries
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
@OrganizationUserCollectionIds 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
@OrganizationUserCollectionIds ids;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION;
THROW;
END CATCH
-- 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
@OrganizationUserCollectionIds ids;
END

View File

@@ -1,5 +1,6 @@
-- Creates default user collections for organization users
-- Uses semaphore table to prevent duplicate default collections at database level
-- NOTE: this MUST be executed in a single transaction to obtain semaphore protection
CREATE OR ALTER PROCEDURE [dbo].[Collection_CreateDefaultCollections]
@OrganizationId UNIQUEIDENTIFIER,
@DefaultCollectionName VARCHAR(MAX),
@@ -10,71 +11,59 @@ BEGIN
DECLARE @Now DATETIME2(7) = GETUTCDATE()
BEGIN TRANSACTION;
-- Insert semaphore entries first to obtain the "lock"
-- If this fails due to duplicate key, the entire transaction will be rolled back
INSERT INTO [dbo].[DefaultCollectionSemaphore]
(
[OrganizationUserId],
[CreationDate]
)
SELECT
ids.[Id1], -- OrganizationUserId
@Now
FROM
@OrganizationUserCollectionIds ids;
BEGIN TRY
-- Insert semaphore entries first to obtain the "lock"
-- If this fails due to duplicate key, the entire transaction will be rolled back
INSERT INTO [dbo].[DefaultCollectionSemaphore]
(
[OrganizationUserId],
[CreationDate]
)
SELECT
ids.[Id1], -- OrganizationUserId
@Now
FROM
@OrganizationUserCollectionIds ids;
-- Insert collections for users who obtained semaphore entries
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
@OrganizationUserCollectionIds ids;
-- Insert collections for users who obtained semaphore entries
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
@OrganizationUserCollectionIds 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
@OrganizationUserCollectionIds ids;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION;
THROW;
END CATCH
-- 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
@OrganizationUserCollectionIds ids;
END
GO