1
0
mirror of https://github.com/bitwarden/server synced 2025-12-31 23:53:17 +00:00

[PM-24233] Use BulkResourceCreationService in CipherRepository (#6201)

* Add constant for CipherRepositoryBulkResourceCreation in FeatureFlagKeys

* Add bulk creation methods for Ciphers, Folders, and CollectionCiphers in BulkResourceCreationService

- Implemented CreateCiphersAsync, CreateFoldersAsync, CreateCollectionCiphersAsync, and CreateTempCiphersAsync methods for bulk insertion.
- Added helper methods to build DataTables for Ciphers, Folders, and CollectionCiphers.
- Enhanced error handling for empty collections during bulk operations.

* Refactor CipherRepository to utilize BulkResourceCreationService

- Introduced IFeatureService to manage feature flag checks for bulk operations.
- Updated methods to conditionally use BulkResourceCreationService for creating Ciphers, Folders, and CollectionCiphers based on feature flag status.
- Enhanced existing bulk copy logic to maintain functionality while integrating feature flag checks.

* Add InlineFeatureService to DatabaseDataAttribute for feature flag management

- Introduced EnabledFeatureFlags property to DatabaseDataAttribute for configuring feature flags.
- Integrated InlineFeatureService to provide feature flag checks within the service collection.
- Enhanced GetData method to utilize feature flags for conditional service registration.

* Add tests for bulk creation of Ciphers in CipherRepositoryTests

- Implemented tests for bulk creation of Ciphers, Folders, and Collections with feature flag checks.
- Added test cases for updating multiple Ciphers to validate bulk update functionality.
- Enhanced existing test structure to ensure comprehensive coverage of bulk operations in the CipherRepository.

* Refactor BulkResourceCreationService to use dynamic types for DataColumns

- Updated DataColumn definitions in BulkResourceCreationService to utilize the actual types of properties from the cipher object instead of hardcoded types.
- Simplified the assignment of nullable properties to directly use their values, improving code readability and maintainability.

* Update BulkResourceCreationService to use specific types for DataColumns

- Changed DataColumn definitions to use specific types (short and string) instead of dynamic types based on cipher properties.
- Improved handling of nullable properties when assigning values to DataTable rows, ensuring proper handling of DBNull for null values.

* Refactor CipherRepositoryTests for improved clarity and consistency

- Renamed test methods to better reflect their purpose and improve readability.
- Updated test data to use more descriptive names for users, folders, and collections.
- Enhanced test structure with clear Arrange, Act, and Assert sections for better understanding of test flow.
- Ensured all tests validate the expected outcomes for bulk operations with feature flag checks.

* Update CipherRepositoryBulkResourceCreation feature flag key

* Refactor DatabaseDataAttribute usage in CipherRepositoryTests to use array syntax for EnabledFeatureFlags

* Update CipherRepositoryTests to use GenerateComb for generating unique IDs

* Refactor CipherRepository methods to accept a boolean parameter for enabling bulk resource creation based on feature flags. Update tests to verify functionality with and without the feature flag enabled.

* Refactor CipherRepository and related services to support new methods for bulk resource creation without boolean parameters.
This commit is contained in:
Rui Tomé
2025-09-03 14:57:53 +01:00
committed by GitHub
parent 99058891d0
commit 1dade9d4b8
11 changed files with 849 additions and 7 deletions

View File

@@ -114,6 +114,7 @@ public static class FeatureFlagKeys
public const string SeparateCustomRolePermissions = "pm-19917-separate-custom-role-permissions";
public const string CreateDefaultLocation = "pm-19467-create-default-location";
public const string DirectoryConnectorPreventUserRemoval = "pm-24592-directory-connector-prevent-user-removal";
public const string CipherRepositoryBulkResourceCreation = "pm-24951-cipher-repository-bulk-resource-creation-service";
/* Auth Team */
public const string TwoFactorExtensionDataPersistence = "pm-9115-two-factor-extension-data-persistence";

View File

@@ -25,6 +25,7 @@ public class RotateUserAccountKeysCommand : IRotateUserAccountKeysCommand
private readonly IdentityErrorDescriber _identityErrorDescriber;
private readonly IWebAuthnCredentialRepository _credentialRepository;
private readonly IPasswordHasher<User> _passwordHasher;
private readonly IFeatureService _featureService;
/// <summary>
/// Instantiates a new <see cref="RotateUserAccountKeysCommand"/>
@@ -45,7 +46,8 @@ public class RotateUserAccountKeysCommand : IRotateUserAccountKeysCommand
IEmergencyAccessRepository emergencyAccessRepository, IOrganizationUserRepository organizationUserRepository,
IDeviceRepository deviceRepository,
IPasswordHasher<User> passwordHasher,
IPushNotificationService pushService, IdentityErrorDescriber errors, IWebAuthnCredentialRepository credentialRepository)
IPushNotificationService pushService, IdentityErrorDescriber errors, IWebAuthnCredentialRepository credentialRepository,
IFeatureService featureService)
{
_userService = userService;
_userRepository = userRepository;
@@ -59,6 +61,7 @@ public class RotateUserAccountKeysCommand : IRotateUserAccountKeysCommand
_identityErrorDescriber = errors;
_credentialRepository = credentialRepository;
_passwordHasher = passwordHasher;
_featureService = featureService;
}
/// <inheritdoc />
@@ -100,7 +103,15 @@ public class RotateUserAccountKeysCommand : IRotateUserAccountKeysCommand
List<UpdateEncryptedDataForKeyRotation> saveEncryptedDataActions = new();
if (model.Ciphers.Any())
{
saveEncryptedDataActions.Add(_cipherRepository.UpdateForKeyRotation(user.Id, model.Ciphers));
var useBulkResourceCreationService = _featureService.IsEnabled(FeatureFlagKeys.CipherRepositoryBulkResourceCreation);
if (useBulkResourceCreationService)
{
saveEncryptedDataActions.Add(_cipherRepository.UpdateForKeyRotation_vNext(user.Id, model.Ciphers));
}
else
{
saveEncryptedDataActions.Add(_cipherRepository.UpdateForKeyRotation(user.Id, model.Ciphers));
}
}
if (model.Folders.Any())

View File

@@ -108,7 +108,15 @@ public class ImportCiphersCommand : IImportCiphersCommand
}
// Create it all
await _cipherRepository.CreateAsync(importingUserId, ciphers, newFolders);
var useBulkResourceCreationService = _featureService.IsEnabled(FeatureFlagKeys.CipherRepositoryBulkResourceCreation);
if (useBulkResourceCreationService)
{
await _cipherRepository.CreateAsync_vNext(importingUserId, ciphers, newFolders);
}
else
{
await _cipherRepository.CreateAsync(importingUserId, ciphers, newFolders);
}
// push
await _pushService.PushSyncVaultAsync(importingUserId);
@@ -183,7 +191,15 @@ public class ImportCiphersCommand : IImportCiphersCommand
}
// Create it all
await _cipherRepository.CreateAsync(ciphers, newCollections, collectionCiphers, newCollectionUsers);
var useBulkResourceCreationService = _featureService.IsEnabled(FeatureFlagKeys.CipherRepositoryBulkResourceCreation);
if (useBulkResourceCreationService)
{
await _cipherRepository.CreateAsync_vNext(ciphers, newCollections, collectionCiphers, newCollectionUsers);
}
else
{
await _cipherRepository.CreateAsync(ciphers, newCollections, collectionCiphers, newCollectionUsers);
}
// push
await _pushService.PushSyncVaultAsync(importingUserId);

View File

@@ -32,12 +32,28 @@ public interface ICipherRepository : IRepository<Cipher, Guid>
Task DeleteByUserIdAsync(Guid userId);
Task DeleteByOrganizationIdAsync(Guid organizationId);
Task UpdateCiphersAsync(Guid userId, IEnumerable<Cipher> ciphers);
/// <inheritdoc cref="UpdateCiphersAsync(Guid, IEnumerable{Cipher})"/>
/// <remarks>
/// This version uses the bulk resource creation service to create the temp table.
/// </remarks>
Task UpdateCiphersAsync_vNext(Guid userId, IEnumerable<Cipher> ciphers);
/// <summary>
/// Create ciphers and folders for the specified UserId. Must not be used to create organization owned items.
/// </summary>
Task CreateAsync(Guid userId, IEnumerable<Cipher> ciphers, IEnumerable<Folder> folders);
/// <inheritdoc cref="CreateAsync(Guid, IEnumerable{Cipher}, IEnumerable{Folder})"/>
/// <remarks>
/// This version uses the bulk resource creation service to create the temp tables.
/// </remarks>
Task CreateAsync_vNext(Guid userId, IEnumerable<Cipher> ciphers, IEnumerable<Folder> folders);
Task CreateAsync(IEnumerable<Cipher> ciphers, IEnumerable<Collection> collections,
IEnumerable<CollectionCipher> collectionCiphers, IEnumerable<CollectionUser> collectionUsers);
/// <inheritdoc cref="CreateAsync(IEnumerable{Cipher}, IEnumerable{Collection}, IEnumerable{CollectionCipher}, IEnumerable{CollectionUser})"/>
/// <remarks>
/// This version uses the bulk resource creation service to create the temp tables.
/// </remarks>
Task CreateAsync_vNext(IEnumerable<Cipher> ciphers, IEnumerable<Collection> collections,
IEnumerable<CollectionCipher> collectionCiphers, IEnumerable<CollectionUser> collectionUsers);
Task SoftDeleteAsync(IEnumerable<Guid> ids, Guid userId);
Task SoftDeleteByIdsOrganizationIdAsync(IEnumerable<Guid> ids, Guid organizationId);
Task<DateTime> RestoreAsync(IEnumerable<Guid> ids, Guid userId);
@@ -68,4 +84,10 @@ public interface ICipherRepository : IRepository<Cipher, Guid>
/// <param name="ciphers">A list of ciphers with updated data</param>
UpdateEncryptedDataForKeyRotation UpdateForKeyRotation(Guid userId,
IEnumerable<Cipher> ciphers);
/// <inheritdoc cref="UpdateForKeyRotation(Guid, IEnumerable{Cipher})"/>
/// <remarks>
/// This version uses the bulk resource creation service to create the temp table.
/// </remarks>
UpdateEncryptedDataForKeyRotation UpdateForKeyRotation_vNext(Guid userId,
IEnumerable<Cipher> ciphers);
}

View File

@@ -642,7 +642,15 @@ public class CipherService : ICipherService
cipherIds.Add(cipher.Id);
}
await _cipherRepository.UpdateCiphersAsync(sharingUserId, cipherInfos.Select(c => c.cipher));
var useBulkResourceCreationService = _featureService.IsEnabled(FeatureFlagKeys.CipherRepositoryBulkResourceCreation);
if (useBulkResourceCreationService)
{
await _cipherRepository.UpdateCiphersAsync_vNext(sharingUserId, cipherInfos.Select(c => c.cipher));
}
else
{
await _cipherRepository.UpdateCiphersAsync(sharingUserId, cipherInfos.Select(c => c.cipher));
}
await _collectionCipherRepository.UpdateCollectionsForCiphersAsync(cipherIds, sharingUserId,
organizationId, collectionIds);