Files
server/test/Infrastructure.IntegrationTest/Vault/Repositories/CipherRepositoryTests.cs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1481 lines
57 KiB
C#
Raw Normal View History

using System.Text.Json;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Billing.Enums;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.NotificationCenter.Entities;
using Bit.Core.NotificationCenter.Repositories;
using Bit.Core.Repositories;
[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.
2025-09-03 14:57:53 +01:00
using Bit.Core.Utilities;
using Bit.Core.Vault.Entities;
using Bit.Core.Vault.Enums;
using Bit.Core.Vault.Models.Data;
using Bit.Core.Vault.Repositories;
using Xunit;
[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.
2025-09-03 14:57:53 +01:00
using CipherType = Bit.Core.Vault.Enums.CipherType;
namespace Bit.Infrastructure.IntegrationTest.Repositories;
public class CipherRepositoryTests
{
[DatabaseTheory, DatabaseData]
public async Task DeleteAsync_UpdatesUserRevisionDate(
IUserRepository userRepository,
ICipherRepository cipherRepository)
{
var user = await userRepository.CreateAsync(new User
{
Name = "Test User",
Email = $"test+{Guid.NewGuid()}@email.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
var cipher = await cipherRepository.CreateAsync(new Cipher
{
Type = CipherType.Login,
UserId = user.Id,
Data = "", // TODO: EF does not enforce this as NOT NULL
});
await cipherRepository.DeleteAsync(cipher);
var deletedCipher = await cipherRepository.GetByIdAsync(cipher.Id);
Assert.Null(deletedCipher);
var updatedUser = await userRepository.GetByIdAsync(user.Id);
Assert.NotNull(updatedUser);
Assert.NotEqual(updatedUser.AccountRevisionDate, user.AccountRevisionDate);
}
[DatabaseTheory, DatabaseData]
[PM-1969] Spellcheck other (#2878) * Fix typo in error message: 'Unkown' -> 'Unknown' * Fix typos in error message * Fix typo in example text: 'licence' -> 'license' * Fix typo in validation: 'Ooganization' -> 'Organization' * Fix typo in text string: 'compatibilty' -> 'compatibility' * Fix typo: 'ProviderDisllowedOrganizationTypes' -> 'ProviderDisallowedOrganizationTypes' * Fix typo: 'NSubstitueVersion' -> 'NSubstituteVersion' * Fix typo: 'CreateIntialInvite' -> 'CreateInitialInvite' * Fix typo: '_queuryScheme' -> '_queryScheme' * Fix typo: 'GetApplicationCacheServiceBusSubcriptionName' -> 'GetApplicationCacheServiceBusSubscriptionName' * Fix typo: 'metaDataRespository' -> 'metaDataRepository' * Fix typo: 'cipherAttachements' -> 'cipherAttachments' * Fix typo: 'savedEmergencyAccesss' -> 'savedEmergencyAccesses' * Fix typo: 'owerOrgUser' -> 'ownerOrgUser' * Fix typo: 'Organiation' -> 'Organization' * Fix typo: 'extistingUser' -> 'existingUser' * Fix typo: 'availibleAccess' -> 'availableAccess' * Fix typo: 'HasEnouphStorage' -> 'HasEnoughStorage' * Fix typo: 'extistingOrg' -> 'existingOrg' * Fix typo: 'subcriber' -> 'subscriber' * Fix typo: 'availibleCollections' -> 'availableCollections' * Fix typo: 'Succes' -> 'Success' * Fix typo: 'CreateAsync_UpdateWithCollecitons_Works' -> 'CreateAsync_UpdateWithCollections_Works' * Fix typo: 'BadInsallationId' -> 'BadInstallationId' * Fix typo: 'OrgNotFamiles' -> 'OrgNotFamilies' * Revert "Fix typo: 'Organiation' -> 'Organization'" This reverts commit 8aadad1c25d853f26ec39029d157ef63e073d3d4. * Revert "Fix typos in error message" This reverts commit 81d201fc09ae4274b7fabe8c6fbcdbb91647bac8. --------- Co-authored-by: Daniel James Smith <djsmith@web.de>
2023-05-17 06:14:36 -04:00
public async Task CreateAsync_UpdateWithCollections_Works(
IUserRepository userRepository,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
ICollectionRepository collectionRepository,
ICipherRepository cipherRepository,
ICollectionCipherRepository collectionCipherRepository)
{
var user = await userRepository.CreateAsync(new User
{
Name = "Test User",
Email = $"test+{Guid.NewGuid()}@email.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
user = await userRepository.GetByIdAsync(user.Id);
Assert.NotNull(user);
var organization = await organizationRepository.CreateAsync(new Organization
{
Name = "Test Organization",
BillingEmail = user.Email,
Plan = "Test" // TODO: EF does not enforce this as NOT NULL
});
var orgUser = await organizationUserRepository.CreateAsync(new OrganizationUser
{
UserId = user.Id,
OrganizationId = organization.Id,
Status = OrganizationUserStatusType.Confirmed,
Type = OrganizationUserType.Owner,
});
var collection = await collectionRepository.CreateAsync(new Collection
{
Name = "Test Collection",
OrganizationId = organization.Id
});
await Task.Delay(100);
await collectionRepository.UpdateUsersAsync(collection.Id, new[]
{
new CollectionAccessSelection
{
Id = orgUser.Id,
HidePasswords = true,
ReadOnly = true,
[AC-1373] Flexible Collections (#3245) * [AC-1117] Add manage permission (#3126) * Update sql files to add Manage permission * Add migration script * Rename collection manage migration file to remove duplicate migration date * Migrations * Add manage to models * Add manage to repository * Add constraint to Manage columns * Migration lint fixes * Add manage to OrganizationUserUserDetails_ReadWithCollectionsById * Add missing manage fields * Add 'Manage' to UserCollectionDetails * Use CREATE OR ALTER where possible * [AC-1374] Limit collection creation/deletion to Owner/Admin (#3145) * feat: update org table with new column, write migration, refs AC-1374 * feat: update views with new column, refs AC-1374 * feat: Alter sprocs (org create/update) to include new column, refs AC-1374 * feat: update entity/data/request/response models to handle new column, refs AC-1374 * feat: update necessary Provider related views during migration, refs AC-1374 * fix: update org create to default new column to false, refs AC-1374 * feat: added new API/request model for collection management and removed property from update request model, refs AC-1374 * fix: renamed migration script to be after secrets manage beta column changes, refs AC-1374 * fix: dotnet format, refs AC-1374 * feat: add ef migrations to reflect mssql changes, refs AC-1374 * fix: dotnet format, refs AC-1374 * feat: update API signature to accept Guid and explain Cd verbiage, refs AC-1374 * fix: merge conflict resolution * [AC-1174] CollectionUser and CollectionGroup authorization handlers (#3194) * [AC-1174] Introduce BulkAuthorizationHandler.cs * [AC-1174] Introduce CollectionUserAuthorizationHandler * [AC-1174] Add CreateForNewCollection CollectionUser requirement * [AC-1174] Add some more details to CollectionCustomization * [AC-1174] Formatting * [AC-1174] Add CollectionGroupOperation.cs * [AC-1174] Introduce CollectionGroupAuthorizationHandler.cs * [AC-1174] Cleanup CollectionFixture customization Implement and use re-usable extension method to support seeded Guids * [AC-1174] Introduce WithValueFromList AutoFixtureExtensions Modify CollectionCustomization to use multiple organization Ids for auto generated test data * [AC-1174] Simplify CollectionUserAuthorizationHandler.cs Modify the authorization handler to only perform authorization logic. Validation logic will need to be handled by any calling commands/controllers instead. * [AC-1174] Introduce shared CollectionAccessAuthorizationHandlerBase A shared base authorization handler was created for both CollectionUser and CollectionGroup resources, as they share the same underlying management authorization logic. * [AC-1174] Update CollectionUserAuthorizationHandler and CollectionGroupAuthorizationHandler to use the new CollectionAccessAuthorizationHandlerBase class * [AC-1174] Formatting * [AC-1174] Cleanup typo and redundant ToList() call * [AC-1174] Add check for provider users * [AC-1174] Reduce nested loops * [AC-1174] Introduce ICollectionAccess.cs * [AC-1174] Remove individual CollectionGroup and CollectionUser auth handlers and use base class instead * [AC-1174] Tweak unit test to fail minimally * [AC-1174] Reorganize authorization handlers in Core project * [AC-1174] Introduce new AddCoreAuthorizationHandlers() extension method * [AC-1174] Move CollectionAccessAuthorizationHandler into Api project * [AC-1174] Move CollectionFixture to Vault folder * [AC-1174] Rename operation to CreateUpdateDelete * [AC-1174] Require single organization for collection access authorization handler - Add requirement that all target collections must belong to the same organization - Simplify logic related to multiple organizations - Update tests and helpers - Use ToHashSet to improve lookup time * [AC-1174] Fix null reference exception * [AC-1174] Throw bad request exception when collections belong to different organizations * [AC-1174] Switch to CollectionAuthorizationHandler instead of CollectionAccessAuthorizationHandler to reduce complexity * Fix improper merge conflict resolution * fix: add permission check for collection management api, refs AC-1647 (#3252) * [AC-1125] Enforce org setting for creating/deleting collections (#3241) * [AC-1117] Add manage permission (#3126) * Update sql files to add Manage permission * Add migration script * Rename collection manage migration file to remove duplicate migration date * Migrations * Add manage to models * Add manage to repository * Add constraint to Manage columns * Migration lint fixes * Add manage to OrganizationUserUserDetails_ReadWithCollectionsById * Add missing manage fields * Add 'Manage' to UserCollectionDetails * Use CREATE OR ALTER where possible * [AC-1374] Limit collection creation/deletion to Owner/Admin (#3145) * feat: update org table with new column, write migration, refs AC-1374 * feat: update views with new column, refs AC-1374 * feat: Alter sprocs (org create/update) to include new column, refs AC-1374 * feat: update entity/data/request/response models to handle new column, refs AC-1374 * feat: update necessary Provider related views during migration, refs AC-1374 * fix: update org create to default new column to false, refs AC-1374 * feat: added new API/request model for collection management and removed property from update request model, refs AC-1374 * fix: renamed migration script to be after secrets manage beta column changes, refs AC-1374 * fix: dotnet format, refs AC-1374 * feat: add ef migrations to reflect mssql changes, refs AC-1374 * fix: dotnet format, refs AC-1374 * feat: update API signature to accept Guid and explain Cd verbiage, refs AC-1374 * feat: created collection auth handler/operations, added LimitCollectionCdOwnerAdmin to CurrentContentOrganization, refs AC-1125 * feat: create vault service collection extensions and register with base services, refs AC-1125 * feat: deprecated CurrentContext.CreateNewCollections, refs AC-1125 * feat: deprecate DeleteAnyCollection for single resource usages, refs AC-1125 * feat: move service registration to api, update references, refs AC-1125 * feat: add bulk delete authorization handler, refs AC-1125 * feat: always assign user and give manage access on create, refs AC-1125 * fix: updated CurrentContextOrganization type, refs AC-1125 * feat: combined existing collection authorization handlers/operations, refs AC-1125 * fix: OrganizationServiceTests -> CurrentContentOrganization typo, refs AC-1125 * fix: format, refs AC-1125 * fix: update collection controller tests, refs AC-1125 * fix: dotnet format, refs AC-1125 * feat: removed extra BulkAuthorizationHandler, refs AC-1125 * fix: dotnet format, refs AC-1125 * fix: change string to guid for org id, update bulk delete request model, refs AC-1125 * fix: remove delete many collection check, refs AC-1125 * fix: clean up collection auth handler, refs AC-1125 * fix: format fix for CollectionOperations, refs AC-1125 * fix: removed unnecessary owner check, add org null check to custom permission validation, refs AC-1125 * fix: remove unused methods in CurrentContext, refs AC-1125 * fix: removed obsolete test, fixed failling delete many test, refs AC-1125 * fix: CollectionAuthorizationHandlerTests fixes, refs AC-1125 * fix: OrganizationServiceTests fix broken test by mocking GetOrganization, refs AC-1125 * fix: CollectionAuthorizationHandler - remove unused repository, refs AC-1125 * feat: moved UserId null check to common method, refs AC-1125 * fix: updated auth handler tests to remove dependency on requirement for common code checks, refs AC-1125 * feat: updated conditionals/comments for create/delete methods within colleciton auth handler, refs AC-1125 * feat: added create/delete collection auth handler success methods, refs AC-1125 * fix: new up permissions to prevent excessive null checks, refs AC-1125 * fix: remove old reference to CreateNewCollections, refs AC-1125 * fix: typo within ViewAssignedCollections method, refs AC-1125 --------- Co-authored-by: Robyn MacCallum <robyntmaccallum@gmail.com> * refactor: remove organizationId from CollectionBulkDeleteRequestModel, refs AC-1649 (#3282) * [AC-1174] Bulk Collection Management (#3229) * [AC-1174] Update SelectionReadOnlyRequestModel to use Guid for Id property * [AC-1174] Introduce initial bulk-access collection endpoint * [AC-1174] Introduce BulkAddCollectionAccessCommand and validation logic/tests * [AC-1174] Add CreateOrUpdateAccessMany method to CollectionRepository * [AC-1174] Add event logs for bulk add collection access command * [AC-1174] Add User_BumpAccountRevisionDateByCollectionIds and database migration script * [AC-1174] Implement EF repository method * [AC-1174] Improve null checks * [AC-1174] Remove unnecessary BulkCollectionAccessRequestModel helpers * [AC-1174] Add unit tests for new controller endpoint * [AC-1174] Fix formatting * [AC-1174] Remove comment * [AC-1174] Remove redundant organizationId parameter * [AC-1174] Ensure user and group Ids are distinct * [AC-1174] Cleanup tests based on PR feedback * [AC-1174] Formatting * [AC-1174] Update CollectionGroup alias in the sproc * [AC-1174] Add some additional comments to SQL sproc * [AC-1174] Add comment explaining additional SaveChangesAsync call --------- Co-authored-by: Thomas Rittson <trittson@bitwarden.com> * [AC-1646] Rename LimitCollectionCdOwnerAdmin column (#3300) * Rename LimitCollectionCdOwnerAdmin -> LimitCollectionCreationDeletion * Rename and bump migration script * [AC-1666] Removed EditAnyCollection from Create/Delete permission checks (#3301) * fix: remove EditAnyCollection from Create/Delete permission check, refs AC-1666 * fix: updated comment, refs AC-1666 * [AC-1669] Bug - Remove obsolete assignUserId from CollectionService.SaveAsync(...) (#3312) * fix: remove AssignUserId from CollectionService.SaveAsync, refs AC-1669 * fix: add manage access conditional before creating collection, refs AC-1669 * fix: move access logic for create/update, fix all tests, refs AC-1669 * fix: add CollectionAccessSelection fixture, update tests, update bad reqeuest message, refs AC-1669 * fix: format, refs AC-1669 * fix: update null params with specific arg.is null checks, refs Ac-1669 * fix: update attribute class name, refs AC-1669 * [AC-1713] [Flexible collections] Add feature flags to server (#3334) * Add feature flags for FlexibleCollections and BulkCollectionAccess * Flag new routes and behaviour --------- Co-authored-by: Rui Tomé <108268980+r-tome@users.noreply.github.com> * Add joint codeownership for auth handlers (#3346) * [AC-1717] Update default values for LimitCollectionCreationDeletion (#3365) * Change default value in organization create sproc to 1 * Drop old column name still present in some QA instances * Set LimitCollectionCreationDeletion value in code based on feature flag * Fix: add missing namespace after merging in master * Fix: add missing namespace after merging in master * [AC-1683] Fix DB migrations for new Manage permission (#3307) * [AC-1683] Update migration script and introduce V2 procedures and types * [AC-1683] Update repository calls to use new V2 procedures / types * [AC-1684] Update bulk add collection migration script to use new V2 type * [AC-1683] Undo Manage changes to more original procedures * [AC-1683] Restore whitespace changes * [AC-1683] Clarify comments regarding explicit column lists * [AC-1683] Update migration script dates * [AC-1683] Split the migration script for readability * [AC-1683] Re-name SelectReadOnlyArray_V2 to CollectionAccessSelectionType * [AC-1648] [Flexible Collections] Bump migration scripts before feature branch merge (#3371) * Bump dates on sql migration scripts * Bump date on ef migrations --------- Co-authored-by: Robyn MacCallum <robyntmaccallum@gmail.com> Co-authored-by: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com> Co-authored-by: Vincent Salucci <vincesalucci21@gmail.com> Co-authored-by: Shane Melton <smelton@bitwarden.com> Co-authored-by: Rui Tomé <108268980+r-tome@users.noreply.github.com>
2023-11-01 19:30:52 +10:00
Manage = true
},
});
await Task.Delay(100);
await cipherRepository.CreateAsync(new CipherDetails
{
Type = CipherType.Login,
OrganizationId = organization.Id,
Data = "", // TODO: EF does not enforce this as NOT NULL
}, new List<Guid>
{
collection.Id,
});
var updatedUser = await userRepository.GetByIdAsync(user.Id);
Assert.NotNull(updatedUser);
Assert.True(updatedUser.AccountRevisionDate - user.AccountRevisionDate > TimeSpan.Zero,
"The AccountRevisionDate is expected to be changed");
var collectionCiphers = await collectionCipherRepository.GetManyByOrganizationIdAsync(organization.Id);
Assert.NotEmpty(collectionCiphers);
}
[DatabaseTheory, DatabaseData]
public async Task ReplaceAsync_SuccessfullyMovesCipherToOrganization(IUserRepository userRepository,
ICipherRepository cipherRepository,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
IFolderRepository folderRepository)
{
// This tests what happens when a cipher is moved into an organizations
var user = await userRepository.CreateAsync(new User
{
Name = "Test User",
Email = $"test+{Guid.NewGuid()}@email.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
user = await userRepository.GetByIdAsync(user.Id);
Assert.NotNull(user);
// Create cipher in personal vault
var createdCipher = await cipherRepository.CreateAsync(new Cipher
{
UserId = user.Id,
Data = "", // TODO: EF does not enforce this as NOT NULL
});
var organization = await organizationRepository.CreateAsync(new Organization
{
Name = "Test Organization",
BillingEmail = user.Email,
Plan = "Test" // TODO: EF does not enforce this as NOT NULL
});
_ = await organizationUserRepository.CreateAsync(new OrganizationUser
{
UserId = user.Id,
OrganizationId = organization.Id,
Status = OrganizationUserStatusType.Confirmed,
Type = OrganizationUserType.Owner,
});
var folder = await folderRepository.CreateAsync(new Folder
{
Name = "FolderName",
UserId = user.Id,
});
// Move cipher to organization vault
await cipherRepository.ReplaceAsync(new CipherDetails
{
Id = createdCipher.Id,
UserId = user.Id,
OrganizationId = organization.Id,
FolderId = folder.Id,
Data = "", // TODO: EF does not enforce this as NOT NULL
});
var updatedCipher = await cipherRepository.GetByIdAsync(createdCipher.Id);
Assert.NotNull(updatedCipher);
Assert.Null(updatedCipher.UserId);
Assert.Equal(organization.Id, updatedCipher.OrganizationId);
Assert.NotNull(updatedCipher.Folders);
using var foldersJsonDocument = JsonDocument.Parse(updatedCipher.Folders);
var foldersJsonElement = foldersJsonDocument.RootElement;
Assert.Equal(JsonValueKind.Object, foldersJsonElement.ValueKind);
// TODO: Should we force similar casing for guids across DB's
// I'd rather we only interact with them as the actual Guid type
var userProperty = foldersJsonElement
.EnumerateObject()
.FirstOrDefault(jp => string.Equals(jp.Name, user.Id.ToString(), StringComparison.OrdinalIgnoreCase));
Assert.NotEqual(default, userProperty);
Assert.Equal(folder.Id, userProperty.Value.GetGuid());
}
[DatabaseTheory, DatabaseData]
public async Task GetCipherPermissionsForOrganizationAsync_Works(
ICipherRepository cipherRepository,
IUserRepository userRepository,
ICollectionCipherRepository collectionCipherRepository,
ICollectionRepository collectionRepository,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
IGroupRepository groupRepository
)
{
var user = await userRepository.CreateAsync(new User
{
Name = "Test User",
Email = $"test+{Guid.NewGuid()}@email.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
var organization = await organizationRepository.CreateAsync(new Organization
{
Name = "Test Organization",
BillingEmail = user.Email,
Plan = "Test"
});
var orgUser = await organizationUserRepository.CreateAsync(new OrganizationUser
{
UserId = user.Id,
OrganizationId = organization.Id,
Status = OrganizationUserStatusType.Confirmed,
Type = OrganizationUserType.Owner,
});
// A group that will be assigned Edit permissions to any collections
var editGroup = await groupRepository.CreateAsync(new Group
{
OrganizationId = organization.Id,
Name = "Edit Group",
});
await groupRepository.UpdateUsersAsync(editGroup.Id, new[] { orgUser.Id });
// MANAGE
var manageCollection = await collectionRepository.CreateAsync(new Collection
{
Name = "Manage Collection",
OrganizationId = organization.Id
});
var manageCipher = await cipherRepository.CreateAsync(new Cipher
{
Type = CipherType.Login,
OrganizationId = organization.Id,
Data = ""
});
await collectionCipherRepository.UpdateCollectionsForAdminAsync(manageCipher.Id, organization.Id,
new List<Guid> { manageCollection.Id });
await collectionRepository.UpdateUsersAsync(manageCollection.Id, new List<CollectionAccessSelection>
{
new()
{
Id = orgUser.Id,
HidePasswords = false,
ReadOnly = false,
Manage = true
}
});
// EDIT
var editCollection = await collectionRepository.CreateAsync(new Collection
{
Name = "Edit Collection",
OrganizationId = organization.Id
});
var editCipher = await cipherRepository.CreateAsync(new Cipher
{
Type = CipherType.Login,
OrganizationId = organization.Id,
Data = ""
});
await collectionCipherRepository.UpdateCollectionsForAdminAsync(editCipher.Id, organization.Id,
new List<Guid> { editCollection.Id });
await collectionRepository.UpdateUsersAsync(editCollection.Id,
new List<CollectionAccessSelection>
{
new() { Id = orgUser.Id, HidePasswords = false, ReadOnly = false, Manage = false }
});
// EDIT EXCEPT PASSWORD
var editExceptPasswordCollection = await collectionRepository.CreateAsync(new Collection
{
Name = "Edit Except Password Collection",
OrganizationId = organization.Id
});
var editExceptPasswordCipher = await cipherRepository.CreateAsync(new Cipher
{
Type = CipherType.Login,
OrganizationId = organization.Id,
Data = ""
});
await collectionCipherRepository.UpdateCollectionsForAdminAsync(editExceptPasswordCipher.Id, organization.Id,
new List<Guid> { editExceptPasswordCollection.Id });
await collectionRepository.UpdateUsersAsync(editExceptPasswordCollection.Id, new List<CollectionAccessSelection>
{
new() { Id = orgUser.Id, HidePasswords = true, ReadOnly = false, Manage = false }
});
// VIEW ONLY
var viewOnlyCollection = await collectionRepository.CreateAsync(new Collection
{
Name = "View Only Collection",
OrganizationId = organization.Id
});
var viewOnlyCipher = await cipherRepository.CreateAsync(new Cipher
{
Type = CipherType.Login,
OrganizationId = organization.Id,
Data = ""
});
await collectionCipherRepository.UpdateCollectionsForAdminAsync(viewOnlyCipher.Id, organization.Id,
new List<Guid> { viewOnlyCollection.Id });
await collectionRepository.UpdateUsersAsync(viewOnlyCollection.Id,
new List<CollectionAccessSelection>
{
new() { Id = orgUser.Id, HidePasswords = false, ReadOnly = true, Manage = false }
});
// Assign the EditGroup to this View Only collection. The user belongs to this group.
// The user permissions specified above (ViewOnly) should take precedence.
await groupRepository.ReplaceAsync(editGroup,
new[]
{
new CollectionAccessSelection
{
Id = viewOnlyCollection.Id, HidePasswords = false, ReadOnly = false, Manage = false
},
});
// VIEW EXCEPT PASSWORD
var viewExceptPasswordCollection = await collectionRepository.CreateAsync(new Collection
{
Name = "View Except Password Collection",
OrganizationId = organization.Id
});
var viewExceptPasswordCipher = await cipherRepository.CreateAsync(new Cipher
{
Type = CipherType.Login,
OrganizationId = organization.Id,
Data = ""
});
await collectionCipherRepository.UpdateCollectionsForAdminAsync(viewExceptPasswordCipher.Id, organization.Id,
new List<Guid> { viewExceptPasswordCollection.Id });
await collectionRepository.UpdateUsersAsync(viewExceptPasswordCollection.Id,
new List<CollectionAccessSelection>
{
new() { Id = orgUser.Id, HidePasswords = true, ReadOnly = true, Manage = false }
});
// UNASSIGNED
var unassignedCipher = await cipherRepository.CreateAsync(new Cipher
{
Type = CipherType.Login,
OrganizationId = organization.Id,
Data = ""
});
var permissions = await cipherRepository.GetCipherPermissionsForOrganizationAsync(organization.Id, user.Id);
Assert.NotEmpty(permissions);
var manageCipherPermission = permissions.FirstOrDefault(c => c.Id == manageCipher.Id);
Assert.NotNull(manageCipherPermission);
Assert.True(manageCipherPermission.Manage);
Assert.True(manageCipherPermission.Edit);
Assert.True(manageCipherPermission.Read);
Assert.True(manageCipherPermission.ViewPassword);
var editCipherPermission = permissions.FirstOrDefault(c => c.Id == editCipher.Id);
Assert.NotNull(editCipherPermission);
Assert.False(editCipherPermission.Manage);
Assert.True(editCipherPermission.Edit);
Assert.True(editCipherPermission.Read);
Assert.True(editCipherPermission.ViewPassword);
var editExceptPasswordCipherPermission = permissions.FirstOrDefault(c => c.Id == editExceptPasswordCipher.Id);
Assert.NotNull(editExceptPasswordCipherPermission);
Assert.False(editExceptPasswordCipherPermission.Manage);
Assert.True(editExceptPasswordCipherPermission.Edit);
Assert.True(editExceptPasswordCipherPermission.Read);
Assert.False(editExceptPasswordCipherPermission.ViewPassword);
var viewOnlyCipherPermission = permissions.FirstOrDefault(c => c.Id == viewOnlyCipher.Id);
Assert.NotNull(viewOnlyCipherPermission);
Assert.False(viewOnlyCipherPermission.Manage);
Assert.False(viewOnlyCipherPermission.Edit);
Assert.True(viewOnlyCipherPermission.Read);
Assert.True(viewOnlyCipherPermission.ViewPassword);
var viewExceptPasswordCipherPermission = permissions.FirstOrDefault(c => c.Id == viewExceptPasswordCipher.Id);
Assert.NotNull(viewExceptPasswordCipherPermission);
Assert.False(viewExceptPasswordCipherPermission.Manage);
Assert.False(viewExceptPasswordCipherPermission.Edit);
Assert.True(viewExceptPasswordCipherPermission.Read);
Assert.False(viewExceptPasswordCipherPermission.ViewPassword);
var unassignedCipherPermission = permissions.FirstOrDefault(c => c.Id == unassignedCipher.Id);
Assert.NotNull(unassignedCipherPermission);
Assert.False(unassignedCipherPermission.Manage);
Assert.False(unassignedCipherPermission.Edit);
Assert.False(unassignedCipherPermission.Read);
Assert.False(unassignedCipherPermission.ViewPassword);
}
[PM-18085] Add Manage property to UserCipherDetails (#5390) * Add Manage permission to UserCipherDetails and CipherDetails_ReadByIdUserId * Add Manage property to CipherDetails and UserCipherDetailsQuery * Add integration test for CipherRepository Manage permission rules * Update CipherDetails_ReadWithoutOrganizationsByUserId to include Manage permission * Refactor UserCipherDetailsQuery to include detailed permission and organization properties * Refactor CipherRepositoryTests to improve test organization and readability - Split large test method into smaller, focused methods - Added helper methods for creating test data and performing assertions - Improved test coverage for cipher permissions in different scenarios - Maintained existing test logic while enhancing code structure * Refactor CipherRepositoryTests to consolidate cipher permission tests - Removed redundant helper methods for permission assertions - Simplified test methods for GetCipherPermissionsForOrganizationAsync, GetManyByUserIdAsync, and GetByIdAsync - Maintained existing test coverage for cipher manage permissions - Improved code readability and reduced code duplication * Add integration test for CipherRepository group collection manage permissions - Added new test method GetCipherPermissionsForOrganizationAsync_ManageProperty_RespectsCollectionGroupRules - Implemented helper method CreateCipherInOrganizationCollectionWithGroup to support group-based collection permission testing - Verified manage permissions are correctly applied based on group collection access settings * Add @Manage parameter to Cipher stored procedures - Updated CipherDetails_Create, CipherDetails_CreateWithCollections, and CipherDetails_Update stored procedures - Added @Manage parameter with comment "-- not used" - Included new stored procedure implementations in migration script - Consistent with previous work on adding Manage property to cipher details * Update UserCipherDetails functions to reorder Manage and ViewPassword columns * Reorder Manage and ViewPassword properties in cipher details queries * Bump date in migration script
2025-02-24 11:40:53 +00:00
[DatabaseTheory, DatabaseData]
public async Task GetCipherPermissionsForOrganizationAsync_ManageProperty_RespectsCollectionUserRules(
ICipherRepository cipherRepository,
IUserRepository userRepository,
ICollectionCipherRepository collectionCipherRepository,
ICollectionRepository collectionRepository,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository)
{
var (user, organization, orgUser) = await CreateTestUserAndOrganization(userRepository, organizationRepository, organizationUserRepository);
var manageCipher = await CreateCipherInOrganizationCollection(
organization, orgUser, cipherRepository, collectionRepository, collectionCipherRepository,
hasManagePermission: true, "Manage Collection");
var nonManageCipher = await CreateCipherInOrganizationCollection(
organization, orgUser, cipherRepository, collectionRepository, collectionCipherRepository,
hasManagePermission: false, "Non-Manage Collection");
var permissions = await cipherRepository.GetCipherPermissionsForOrganizationAsync(organization.Id, user.Id);
Assert.Equal(2, permissions.Count);
var managePermission = permissions.FirstOrDefault(c => c.Id == manageCipher.Id);
Assert.NotNull(managePermission);
Assert.True(managePermission.Manage, "Collection with Manage=true should grant Manage permission");
var nonManagePermission = permissions.FirstOrDefault(c => c.Id == nonManageCipher.Id);
Assert.NotNull(nonManagePermission);
Assert.False(nonManagePermission.Manage, "Collection with Manage=false should not grant Manage permission");
}
[DatabaseTheory, DatabaseData]
public async Task GetCipherPermissionsForOrganizationAsync_ManageProperty_RespectsCollectionGroupRules(
ICipherRepository cipherRepository,
IUserRepository userRepository,
ICollectionCipherRepository collectionCipherRepository,
ICollectionRepository collectionRepository,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
IGroupRepository groupRepository)
{
var (user, organization, orgUser) = await CreateTestUserAndOrganization(userRepository, organizationRepository, organizationUserRepository);
var group = await groupRepository.CreateAsync(new Group
{
OrganizationId = organization.Id,
Name = "Test Group",
});
await groupRepository.UpdateUsersAsync(group.Id, new[] { orgUser.Id });
var (manageCipher, nonManageCipher) = await CreateCipherInOrganizationCollectionWithGroup(
organization, group, cipherRepository, collectionRepository, collectionCipherRepository, groupRepository);
var permissions = await cipherRepository.GetCipherPermissionsForOrganizationAsync(organization.Id, user.Id);
Assert.Equal(2, permissions.Count);
var managePermission = permissions.FirstOrDefault(c => c.Id == manageCipher.Id);
Assert.NotNull(managePermission);
Assert.True(managePermission.Manage, "Collection with Group Manage=true should grant Manage permission");
var nonManagePermission = permissions.FirstOrDefault(c => c.Id == nonManageCipher.Id);
Assert.NotNull(nonManagePermission);
Assert.False(nonManagePermission.Manage, "Collection with Group Manage=false should not grant Manage permission");
}
[DatabaseTheory, DatabaseData]
public async Task GetManyByUserIdAsync_ManageProperty_RespectsCollectionAndOwnershipRules(
ICipherRepository cipherRepository,
IUserRepository userRepository,
ICollectionCipherRepository collectionCipherRepository,
ICollectionRepository collectionRepository,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository)
{
var (user, organization, orgUser) = await CreateTestUserAndOrganization(userRepository, organizationRepository, organizationUserRepository);
var manageCipher = await CreateCipherInOrganizationCollection(
organization, orgUser, cipherRepository, collectionRepository, collectionCipherRepository,
hasManagePermission: true, "Manage Collection");
var nonManageCipher = await CreateCipherInOrganizationCollection(
organization, orgUser, cipherRepository, collectionRepository, collectionCipherRepository,
hasManagePermission: false, "Non-Manage Collection");
var personalCipher = await CreatePersonalCipher(user, cipherRepository);
var userCiphers = await cipherRepository.GetManyByUserIdAsync(user.Id);
Assert.Equal(3, userCiphers.Count);
var managePermission = userCiphers.FirstOrDefault(c => c.Id == manageCipher.Id);
Assert.NotNull(managePermission);
Assert.True(managePermission.Manage, "Collection with Manage=true should grant Manage permission");
var nonManagePermission = userCiphers.FirstOrDefault(c => c.Id == nonManageCipher.Id);
Assert.NotNull(nonManagePermission);
Assert.False(nonManagePermission.Manage, "Collection with Manage=false should not grant Manage permission");
var personalPermission = userCiphers.FirstOrDefault(c => c.Id == personalCipher.Id);
Assert.NotNull(personalPermission);
Assert.True(personalPermission.Manage, "Personal ciphers should always have Manage permission");
}
[DatabaseTheory, DatabaseData]
public async Task GetByIdAsync_ManageProperty_RespectsCollectionAndOwnershipRules(
ICipherRepository cipherRepository,
IUserRepository userRepository,
ICollectionCipherRepository collectionCipherRepository,
ICollectionRepository collectionRepository,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository)
{
var (user, organization, orgUser) = await CreateTestUserAndOrganization(userRepository, organizationRepository, organizationUserRepository);
var manageCipher = await CreateCipherInOrganizationCollection(
organization, orgUser, cipherRepository, collectionRepository, collectionCipherRepository,
hasManagePermission: true, "Manage Collection");
var nonManageCipher = await CreateCipherInOrganizationCollection(
organization, orgUser, cipherRepository, collectionRepository, collectionCipherRepository,
hasManagePermission: false, "Non-Manage Collection");
var personalCipher = await CreatePersonalCipher(user, cipherRepository);
var manageDetails = await cipherRepository.GetByIdAsync(manageCipher.Id, user.Id);
Assert.NotNull(manageDetails);
Assert.True(manageDetails.Manage, "Collection with Manage=true should grant Manage permission");
var nonManageDetails = await cipherRepository.GetByIdAsync(nonManageCipher.Id, user.Id);
Assert.NotNull(nonManageDetails);
Assert.False(nonManageDetails.Manage, "Collection with Manage=false should not grant Manage permission");
var personalDetails = await cipherRepository.GetByIdAsync(personalCipher.Id, user.Id);
Assert.NotNull(personalDetails);
Assert.True(personalDetails.Manage, "Personal ciphers should always have Manage permission");
}
[DatabaseTheory, DatabaseData]
public async Task GetManyByUserIdAsync_WhenOneCipherIsAssignedToTwoCollectionsWithDifferentPermissions_MostPrivilegedAccessIsReturnedOnTheCipher(
ICipherRepository cipherRepository,
IUserRepository userRepository,
ICollectionCipherRepository collectionCipherRepository,
ICollectionRepository collectionRepository,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository)
{
//Arrange
var (user, organization, orgUser) = await CreateTestUserAndOrganization(userRepository, organizationRepository, organizationUserRepository);
var cipher = await cipherRepository.CreateAsync(new Cipher
{
Type = CipherType.Login,
OrganizationId = organization.Id,
Data = ""
});
var managedPermissionsCollection = await collectionRepository.CreateAsync(new Collection
{
Name = "Managed",
OrganizationId = organization.Id
});
var unmanagedPermissionsCollection = await collectionRepository.CreateAsync(new Collection
{
Name = "Unmanaged",
OrganizationId = organization.Id
});
await collectionCipherRepository.UpdateCollectionsForAdminAsync(cipher.Id, organization.Id,
[managedPermissionsCollection.Id, unmanagedPermissionsCollection.Id]);
await collectionRepository.UpdateUsersAsync(managedPermissionsCollection.Id, new List<CollectionAccessSelection>
{
new() { Id = orgUser.Id, HidePasswords = false, ReadOnly = false, Manage = true }
});
await collectionRepository.UpdateUsersAsync(unmanagedPermissionsCollection.Id, new List<CollectionAccessSelection>
{
new() { Id = orgUser.Id, HidePasswords = false, ReadOnly = false, Manage = false }
});
// Act
var ciphers = await cipherRepository.GetManyByUserIdAsync(user.Id);
// Assert
Assert.Single(ciphers);
var deletableCipher = ciphers.SingleOrDefault(x => x.Id == cipher.Id);
Assert.NotNull(deletableCipher);
Assert.True(deletableCipher.Manage);
// Annul
await cipherRepository.DeleteAsync(cipher);
await organizationUserRepository.DeleteAsync(orgUser);
await organizationRepository.DeleteAsync(organization);
await userRepository.DeleteAsync(user);
}
[PM-18085] Add Manage property to UserCipherDetails (#5390) * Add Manage permission to UserCipherDetails and CipherDetails_ReadByIdUserId * Add Manage property to CipherDetails and UserCipherDetailsQuery * Add integration test for CipherRepository Manage permission rules * Update CipherDetails_ReadWithoutOrganizationsByUserId to include Manage permission * Refactor UserCipherDetailsQuery to include detailed permission and organization properties * Refactor CipherRepositoryTests to improve test organization and readability - Split large test method into smaller, focused methods - Added helper methods for creating test data and performing assertions - Improved test coverage for cipher permissions in different scenarios - Maintained existing test logic while enhancing code structure * Refactor CipherRepositoryTests to consolidate cipher permission tests - Removed redundant helper methods for permission assertions - Simplified test methods for GetCipherPermissionsForOrganizationAsync, GetManyByUserIdAsync, and GetByIdAsync - Maintained existing test coverage for cipher manage permissions - Improved code readability and reduced code duplication * Add integration test for CipherRepository group collection manage permissions - Added new test method GetCipherPermissionsForOrganizationAsync_ManageProperty_RespectsCollectionGroupRules - Implemented helper method CreateCipherInOrganizationCollectionWithGroup to support group-based collection permission testing - Verified manage permissions are correctly applied based on group collection access settings * Add @Manage parameter to Cipher stored procedures - Updated CipherDetails_Create, CipherDetails_CreateWithCollections, and CipherDetails_Update stored procedures - Added @Manage parameter with comment "-- not used" - Included new stored procedure implementations in migration script - Consistent with previous work on adding Manage property to cipher details * Update UserCipherDetails functions to reorder Manage and ViewPassword columns * Reorder Manage and ViewPassword properties in cipher details queries * Bump date in migration script
2025-02-24 11:40:53 +00:00
private async Task<(User user, Organization org, OrganizationUser orgUser)> CreateTestUserAndOrganization(
IUserRepository userRepository,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository)
{
var user = await userRepository.CreateAsync(new User
{
Name = "Test User",
Email = $"test+{Guid.NewGuid()}@email.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
var organization = await organizationRepository.CreateAsync(new Organization
{
Name = "Test Organization",
BillingEmail = user.Email,
Plan = "Test"
});
var orgUser = await organizationUserRepository.CreateAsync(new OrganizationUser
{
UserId = user.Id,
OrganizationId = organization.Id,
Status = OrganizationUserStatusType.Confirmed,
Type = OrganizationUserType.Owner,
});
return (user, organization, orgUser);
}
private async Task<Cipher> CreateCipherInOrganizationCollection(
Organization organization,
OrganizationUser orgUser,
ICipherRepository cipherRepository,
ICollectionRepository collectionRepository,
ICollectionCipherRepository collectionCipherRepository,
bool hasManagePermission,
string collectionName)
{
var collection = await collectionRepository.CreateAsync(new Collection
{
Name = collectionName,
OrganizationId = organization.Id,
});
var cipher = await cipherRepository.CreateAsync(new Cipher
{
Type = CipherType.Login,
OrganizationId = organization.Id,
Data = ""
});
await collectionCipherRepository.UpdateCollectionsForAdminAsync(cipher.Id, organization.Id,
new List<Guid> { collection.Id });
await collectionRepository.UpdateUsersAsync(collection.Id, new List<CollectionAccessSelection>
{
new() { Id = orgUser.Id, HidePasswords = false, ReadOnly = false, Manage = hasManagePermission }
});
return cipher;
}
private async Task<(Cipher manageCipher, Cipher nonManageCipher)> CreateCipherInOrganizationCollectionWithGroup(
Organization organization,
Group group,
ICipherRepository cipherRepository,
ICollectionRepository collectionRepository,
ICollectionCipherRepository collectionCipherRepository,
IGroupRepository groupRepository)
{
var manageCollection = await collectionRepository.CreateAsync(new Collection
{
Name = "Group Manage Collection",
OrganizationId = organization.Id,
});
var nonManageCollection = await collectionRepository.CreateAsync(new Collection
{
Name = "Group Non-Manage Collection",
OrganizationId = organization.Id,
});
var manageCipher = await cipherRepository.CreateAsync(new Cipher
{
Type = CipherType.Login,
OrganizationId = organization.Id,
Data = ""
});
var nonManageCipher = await cipherRepository.CreateAsync(new Cipher
{
Type = CipherType.Login,
OrganizationId = organization.Id,
Data = ""
});
await collectionCipherRepository.UpdateCollectionsForAdminAsync(manageCipher.Id, organization.Id,
new List<Guid> { manageCollection.Id });
await collectionCipherRepository.UpdateCollectionsForAdminAsync(nonManageCipher.Id, organization.Id,
new List<Guid> { nonManageCollection.Id });
await groupRepository.ReplaceAsync(group,
new[]
{
new CollectionAccessSelection
{
Id = manageCollection.Id,
HidePasswords = false,
ReadOnly = false,
Manage = true
},
new CollectionAccessSelection
{
Id = nonManageCollection.Id,
HidePasswords = false,
ReadOnly = false,
Manage = false
}
});
return (manageCipher, nonManageCipher);
}
private async Task<Cipher> CreatePersonalCipher(User user, ICipherRepository cipherRepository)
{
return await cipherRepository.CreateAsync(new Cipher
{
Type = CipherType.Login,
UserId = user.Id,
Data = ""
});
}
2025-02-27 08:34:42 -06:00
[DatabaseTheory, DatabaseData]
public async Task GetUserSecurityTasksByCipherIdsAsync_Works(
ICipherRepository cipherRepository,
IUserRepository userRepository,
ICollectionCipherRepository collectionCipherRepository,
ICollectionRepository collectionRepository,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
IGroupRepository groupRepository
)
{
// Users
var user1 = await userRepository.CreateAsync(new User
{
Name = "Test User 1",
Email = $"test+{Guid.NewGuid()}@email.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
var user2 = await userRepository.CreateAsync(new User
{
Name = "Test User 2",
Email = $"test+{Guid.NewGuid()}@email.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
// Organization
var organization = await organizationRepository.CreateAsync(new Organization
{
Name = "Test Organization",
BillingEmail = user1.Email,
Plan = "Test"
});
// Org Users
var orgUser1 = await organizationUserRepository.CreateAsync(new OrganizationUser
{
UserId = user1.Id,
OrganizationId = organization.Id,
Status = OrganizationUserStatusType.Confirmed,
Type = OrganizationUserType.Owner,
});
var orgUser2 = await organizationUserRepository.CreateAsync(new OrganizationUser
{
UserId = user2.Id,
OrganizationId = organization.Id,
Status = OrganizationUserStatusType.Confirmed,
Type = OrganizationUserType.User,
});
// A group that will be assigned Edit permissions to any collections
var editGroup = await groupRepository.CreateAsync(new Group
{
OrganizationId = organization.Id,
Name = "Edit Group",
});
await groupRepository.UpdateUsersAsync(editGroup.Id, new[] { orgUser1.Id });
// Add collections to Org
var manageCollection = await collectionRepository.CreateAsync(new Collection
{
Name = "Manage Collection",
OrganizationId = organization.Id
});
// Use a 2nd collection to differentiate between the two users
var manageCollection2 = await collectionRepository.CreateAsync(new Collection
{
Name = "Manage Collection 2",
OrganizationId = organization.Id
});
var viewOnlyCollection = await collectionRepository.CreateAsync(new Collection
{
Name = "View Only Collection",
OrganizationId = organization.Id
});
// Ciphers
var manageCipher1 = await cipherRepository.CreateAsync(new Cipher
{
Type = CipherType.Login,
OrganizationId = organization.Id,
Data = ""
});
var manageCipher2 = await cipherRepository.CreateAsync(new Cipher
{
Type = CipherType.Login,
OrganizationId = organization.Id,
Data = ""
});
var viewOnlyCipher = await cipherRepository.CreateAsync(new Cipher
{
Type = CipherType.Login,
OrganizationId = organization.Id,
Data = ""
});
await collectionCipherRepository.UpdateCollectionsForAdminAsync(manageCipher1.Id, organization.Id,
new List<Guid> { manageCollection.Id });
await collectionCipherRepository.UpdateCollectionsForAdminAsync(manageCipher2.Id, organization.Id,
new List<Guid> { manageCollection2.Id });
await collectionCipherRepository.UpdateCollectionsForAdminAsync(viewOnlyCipher.Id, organization.Id,
new List<Guid> { viewOnlyCollection.Id });
await collectionRepository.UpdateUsersAsync(manageCollection.Id, new List<CollectionAccessSelection>
{
new()
{
Id = orgUser1.Id,
HidePasswords = false,
ReadOnly = false,
Manage = true
},
new()
{
Id = orgUser2.Id,
HidePasswords = false,
ReadOnly = false,
Manage = true
}
});
// Only add second user to the second manage collection
await collectionRepository.UpdateUsersAsync(manageCollection2.Id, new List<CollectionAccessSelection>
{
new()
{
Id = orgUser2.Id,
HidePasswords = false,
ReadOnly = false,
Manage = true
},
});
await collectionRepository.UpdateUsersAsync(viewOnlyCollection.Id, new List<CollectionAccessSelection>
{
new()
{
Id = orgUser1.Id,
HidePasswords = false,
ReadOnly = false,
Manage = false
}
});
var securityTasks = new List<SecurityTask>
{
new SecurityTask { CipherId = manageCipher1.Id, Id = Guid.NewGuid() },
new SecurityTask { CipherId = manageCipher2.Id, Id = Guid.NewGuid() },
new SecurityTask { CipherId = viewOnlyCipher.Id, Id = Guid.NewGuid() }
};
var userSecurityTaskCiphers = await cipherRepository.GetUserSecurityTasksByCipherIdsAsync(organization.Id, securityTasks);
Assert.NotEmpty(userSecurityTaskCiphers);
Assert.Equal(3, userSecurityTaskCiphers.Count);
var user1TaskCiphers = userSecurityTaskCiphers.Where(t => t.UserId == user1.Id);
Assert.Single(user1TaskCiphers);
Assert.Equal(user1.Email, user1TaskCiphers.First().Email);
Assert.Equal(user1.Id, user1TaskCiphers.First().UserId);
Assert.Equal(manageCipher1.Id, user1TaskCiphers.First().CipherId);
var user2TaskCiphers = userSecurityTaskCiphers.Where(t => t.UserId == user2.Id);
Assert.NotNull(user2TaskCiphers);
Assert.Equal(2, user2TaskCiphers.Count());
Assert.Equal(user2.Email, user2TaskCiphers.Last().Email);
Assert.Equal(user2.Id, user2TaskCiphers.Last().UserId);
Assert.Contains(user2TaskCiphers, t => t.CipherId == manageCipher1.Id && t.TaskId == securityTasks[0].Id);
Assert.Contains(user2TaskCiphers, t => t.CipherId == manageCipher2.Id && t.TaskId == securityTasks[1].Id);
}
[DatabaseTheory, DatabaseData]
public async Task CreateAsync_WithFolders_Works(
[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.
2025-09-03 14:57:53 +01:00
IUserRepository userRepository, ICipherRepository cipherRepository, IFolderRepository folderRepository)
{
// Arrange
var user = await userRepository.CreateAsync(new User
{
Name = "Test User",
Email = $"{Guid.NewGuid()}@example.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
var folder1 = new Folder { Id = CoreHelpers.GenerateComb(), UserId = user.Id, Name = "Test Folder 1" };
var folder2 = new Folder { Id = CoreHelpers.GenerateComb(), UserId = user.Id, Name = "Test Folder 2" };
var cipher1 = new Cipher { Id = CoreHelpers.GenerateComb(), Type = CipherType.Login, UserId = user.Id, Data = "" };
var cipher2 = new Cipher { Id = CoreHelpers.GenerateComb(), Type = CipherType.SecureNote, UserId = user.Id, Data = "" };
// Act
await cipherRepository.CreateAsync(
[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.
2025-09-03 14:57:53 +01:00
userId: user.Id,
ciphers: [cipher1, cipher2],
folders: [folder1, folder2]);
// Assert
var readCipher1 = await cipherRepository.GetByIdAsync(cipher1.Id);
var readCipher2 = await cipherRepository.GetByIdAsync(cipher2.Id);
Assert.NotNull(readCipher1);
Assert.NotNull(readCipher2);
var readFolder1 = await folderRepository.GetByIdAsync(folder1.Id);
var readFolder2 = await folderRepository.GetByIdAsync(folder2.Id);
Assert.NotNull(readFolder1);
Assert.NotNull(readFolder2);
}
[DatabaseTheory, DatabaseData]
public async Task CreateAsync_WithCollectionsAndUsers_Works(
[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.
2025-09-03 14:57:53 +01:00
IOrganizationRepository orgRepository,
IOrganizationUserRepository orgUserRepository,
ICollectionRepository collectionRepository,
ICollectionCipherRepository collectionCipherRepository,
ICipherRepository cipherRepository,
IUserRepository userRepository)
{
// Arrange
var user = await userRepository.CreateAsync(new User
{
Name = "Test User",
Email = $"{Guid.NewGuid()}@example.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
var org = await orgRepository.CreateAsync(new Organization
{
Name = "Test Organization",
BillingEmail = user.Email,
Plan = "Test"
});
var orgUser = await orgUserRepository.CreateAsync(new OrganizationUser
{
UserId = user.Id,
OrganizationId = org.Id,
Status = OrganizationUserStatusType.Confirmed,
Type = OrganizationUserType.Owner,
});
var collection = new Collection { Id = CoreHelpers.GenerateComb(), Name = "Test Collection", OrganizationId = org.Id };
var cipher = new Cipher { Id = CoreHelpers.GenerateComb(), Type = CipherType.Login, OrganizationId = org.Id, Data = "" };
var collectionCipher = new CollectionCipher { CollectionId = collection.Id, CipherId = cipher.Id };
var collectionUser = new CollectionUser
{
CollectionId = collection.Id,
OrganizationUserId = orgUser.Id,
HidePasswords = false,
ReadOnly = false,
Manage = true
};
// Act
await cipherRepository.CreateAsync(
[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.
2025-09-03 14:57:53 +01:00
ciphers: [cipher],
collections: [collection],
collectionCiphers: [collectionCipher],
collectionUsers: [collectionUser]);
// Assert
var orgCiphers = await cipherRepository.GetManyByOrganizationIdAsync(org.Id);
Assert.Contains(orgCiphers, c => c.Id == cipher.Id);
var collCiphers = await collectionCipherRepository.GetManyByOrganizationIdAsync(org.Id);
Assert.Contains(collCiphers, cc => cc.CipherId == cipher.Id && cc.CollectionId == collection.Id);
var collectionsInOrg = await collectionRepository.GetManyByOrganizationIdAsync(org.Id);
Assert.Contains(collectionsInOrg, c => c.Id == collection.Id);
var collectionUsers = await collectionRepository.GetManyUsersByIdAsync(collection.Id);
var foundCollectionUser = collectionUsers.FirstOrDefault(cu => cu.Id == orgUser.Id);
Assert.NotNull(foundCollectionUser);
Assert.True(foundCollectionUser.Manage);
Assert.False(foundCollectionUser.ReadOnly);
Assert.False(foundCollectionUser.HidePasswords);
}
[DatabaseTheory, DatabaseData]
public async Task UpdateCiphersAsync_Works(
[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.
2025-09-03 14:57:53 +01:00
IUserRepository userRepository, ICipherRepository cipherRepository)
{
// Arrange
var expectedNewType = CipherType.SecureNote;
var expectedNewAttachments = "bulk_new_attachments";
var user = await userRepository.CreateAsync(new User
{
Name = "Test User",
Email = $"{Guid.NewGuid()}@example.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
var c1 = new Cipher { Id = CoreHelpers.GenerateComb(), Type = CipherType.Login, UserId = user.Id, Data = "" };
var c2 = new Cipher { Id = CoreHelpers.GenerateComb(), Type = CipherType.Login, UserId = user.Id, Data = "" };
await cipherRepository.CreateAsync(
userId: user.Id,
ciphers: [c1, c2],
folders: []);
c1.Type = expectedNewType;
c2.Attachments = expectedNewAttachments;
// Act
await cipherRepository.UpdateCiphersAsync(user.Id, [c1, c2]);
[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.
2025-09-03 14:57:53 +01:00
// Assert
var updated1 = await cipherRepository.GetByIdAsync(c1.Id);
Assert.NotNull(updated1);
Assert.Equal(c1.Id, updated1.Id);
Assert.Equal(expectedNewType, updated1.Type);
Assert.Equal(c1.UserId, updated1.UserId);
Assert.Equal(c1.Data, updated1.Data);
Assert.Equal(c1.OrganizationId, updated1.OrganizationId);
Assert.Equal(c1.Attachments, updated1.Attachments);
var updated2 = await cipherRepository.GetByIdAsync(c2.Id);
Assert.NotNull(updated2);
Assert.Equal(c2.Id, updated2.Id);
Assert.Equal(c2.Type, updated2.Type);
Assert.Equal(c2.UserId, updated2.UserId);
Assert.Equal(c2.Data, updated2.Data);
Assert.Equal(c2.OrganizationId, updated2.OrganizationId);
Assert.Equal(expectedNewAttachments, updated2.Attachments);
}
[DatabaseTheory, DatabaseData]
public async Task DeleteCipherWithSecurityTaskAsync_Works(
IOrganizationRepository organizationRepository,
IUserRepository userRepository,
ICipherRepository cipherRepository,
ISecurityTaskRepository securityTaskRepository,
INotificationRepository notificationRepository,
INotificationStatusRepository notificationStatusRepository)
{
var organization = await organizationRepository.CreateAsync(new Organization
{
Name = "Test Org",
PlanType = PlanType.EnterpriseAnnually,
Plan = "Test Plan",
BillingEmail = ""
});
var user = await userRepository.CreateAsync(new User
{
Name = "Test User",
Email = $"test+{Guid.NewGuid()}@email.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
var cipher1 = new Cipher { Type = CipherType.Login, OrganizationId = organization.Id, Data = "", };
await cipherRepository.CreateAsync(cipher1);
var cipher2 = new Cipher { Type = CipherType.Login, OrganizationId = organization.Id, Data = "", };
await cipherRepository.CreateAsync(cipher2);
var tasks = new List<SecurityTask>
{
new()
{
OrganizationId = organization.Id,
CipherId = cipher1.Id,
Status = SecurityTaskStatus.Pending,
Type = SecurityTaskType.UpdateAtRiskCredential,
},
new()
{
OrganizationId = organization.Id,
CipherId = cipher2.Id,
Status = SecurityTaskStatus.Completed,
Type = SecurityTaskType.UpdateAtRiskCredential,
}
};
await securityTaskRepository.CreateManyAsync(tasks);
var notification = await notificationRepository.CreateAsync(new Notification
{
OrganizationId = organization.Id,
UserId = user.Id,
TaskId = tasks[1].Id,
CreationDate = DateTime.UtcNow,
RevisionDate = DateTime.UtcNow,
});
await notificationStatusRepository.CreateAsync(new NotificationStatus
{
NotificationId = notification.Id,
UserId = user.Id,
ReadDate = DateTime.UtcNow,
});
// Delete cipher with pending security task
await cipherRepository.DeleteAsync(cipher1);
var deletedCipher1 = await cipherRepository.GetByIdAsync(cipher1.Id);
Assert.Null(deletedCipher1);
// Delete cipher with completed security task
await cipherRepository.DeleteAsync(cipher2);
var deletedCipher2 = await cipherRepository.GetByIdAsync(cipher2.Id);
Assert.Null(deletedCipher2);
}
[DatabaseTheory, DatabaseData]
public async Task ArchiveAsync_Works(
ICipherRepository sutRepository,
IUserRepository userRepository)
{
var user = await userRepository.CreateAsync(new User
{
Name = "Test User",
Email = $"test+{Guid.NewGuid()}@email.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
// Ciphers
var cipher = await sutRepository.CreateAsync(new Cipher
{
Type = CipherType.Login,
Data = "",
UserId = user.Id
});
// Act
await sutRepository.ArchiveAsync(new List<Guid> { cipher.Id }, user.Id);
[PM-27884][PM-27886][PM-27885] - Add Cipher Archives (#6578) * add Archives column to ciphers table * add archives column * update cipher archive/unarchive and cipher deatils query * add migrations * add missing migrations * fixes * update tests. cleanup * syntax fix * fix sql syntax * fix sql * fix CreateWithCollections * fix sql * fix migration file * fix migration * add go * add missing go * fix migrations * add missing proc * fix migrations * implement claude suggestions * fix test * update cipher service and tests * updates to soft delete * update UserCipherDetailsQuery and migration * update migration * update archive ciphers command to allow org ciphers to be archived * updates to archivedDate * revert change to UserCipherDetails * updates to migration and procs * remove archivedDate from Cipher_CreateWithCollections * remove trailing comma * fix syntax errors * fix migration * add double quotes around datetime * fix syntax error * remove archivedDate from cipher entity * re-add ArchivedDate into cipher * fix migration * do not set Cipher.ArchivedDate in CipherRepository * re-add ArchivedDate until removed from the db * set defaults * change to CREATE OR ALTER * fix migration * fix migration file * quote datetime * fix existing archiveAsync test. add additional test * quote datetime * update migration * do not wrap datetime in quotes * do not wrap datetime in quotes * fix migration * clean up archives and archivedDate from procs * fix UserCipherDetailsQuery * fix setting date in JSON_MODIFY * prefer cast over convert * fix cipher response model * re-add ArchivedDate * add new keyword * remove ArchivedDate from entity * use custom parameters for CipherDetails_CreateWithCollections * remove reference to archivedDate * add missing param * add missing param * fix params * fix cipher repository * fix migration file * update request/response models * update migration * remove Archives from Cipher_CreateWithCollections * revert last change * clean up * remove comment * remove column in migration * change language in drop * wrap in brackets * put drop column in separate migration * remove archivedDate column * re-add archivedDate * add refresh module * bump migration name * fix proc and migration * do not require edit permission for archiving ciphers * do not require edit permission for unarchiving ciphers
2026-01-07 09:29:10 -08:00
// Assert per-user view should show an archive date
var archivedCipherForUser = await sutRepository.GetByIdAsync(cipher.Id, user.Id);
Assert.NotNull(archivedCipherForUser);
Assert.NotNull(archivedCipherForUser.ArchivedDate);
}
[DatabaseTheory, DatabaseData]
public async Task ArchiveAsync_IsPerUserForSharedCipher(
ICipherRepository cipherRepository,
IUserRepository userRepository,
IOrganizationRepository organizationRepository,
IOrganizationUserRepository organizationUserRepository,
ICollectionRepository collectionRepository,
ICollectionCipherRepository collectionCipherRepository)
{
// Arrange: two users in the same org, both with access to the same cipher
var user1 = await userRepository.CreateAsync(new User
{
Name = "Test User 1",
Email = $"test+{Guid.NewGuid()}@email.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
var user2 = await userRepository.CreateAsync(new User
{
Name = "Test User 2",
Email = $"test+{Guid.NewGuid()}@email.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
var org = await organizationRepository.CreateAsync(new Organization
{
Name = "Test Organization",
BillingEmail = user1.Email,
Plan = "Test",
});
var orgUser1 = await organizationUserRepository.CreateAsync(new OrganizationUser
{
UserId = user1.Id,
OrganizationId = org.Id,
Status = OrganizationUserStatusType.Confirmed,
Type = OrganizationUserType.Owner,
});
var orgUser2 = await organizationUserRepository.CreateAsync(new OrganizationUser
{
UserId = user2.Id,
OrganizationId = org.Id,
Status = OrganizationUserStatusType.Confirmed,
Type = OrganizationUserType.User,
});
var sharedCollection = await collectionRepository.CreateAsync(new Collection
{
Name = "Shared Collection",
OrganizationId = org.Id,
});
var cipher = await cipherRepository.CreateAsync(new Cipher
{
Type = CipherType.Login,
OrganizationId = org.Id,
Data = "",
});
await collectionCipherRepository.UpdateCollectionsForAdminAsync(
cipher.Id,
org.Id,
new List<Guid> { sharedCollection.Id });
// Give both org users access to the shared collection
await collectionRepository.UpdateUsersAsync(sharedCollection.Id, new List<CollectionAccessSelection>
{
new()
{
Id = orgUser1.Id,
HidePasswords = false,
ReadOnly = false,
Manage = true,
},
new()
{
Id = orgUser2.Id,
HidePasswords = false,
ReadOnly = false,
Manage = true,
},
});
// Act: user1 archives the shared cipher
await cipherRepository.ArchiveAsync(new List<Guid> { cipher.Id }, user1.Id);
// Assert: user1 sees it as archived
var cipherForUser1 = await cipherRepository.GetByIdAsync(cipher.Id, user1.Id);
Assert.NotNull(cipherForUser1);
Assert.NotNull(cipherForUser1.ArchivedDate);
// Assert: user2 still sees it as *not* archived
var cipherForUser2 = await cipherRepository.GetByIdAsync(cipher.Id, user2.Id);
Assert.NotNull(cipherForUser2);
Assert.Null(cipherForUser2.ArchivedDate);
}
[DatabaseTheory, DatabaseData]
public async Task DeleteByOrganizationIdAsync_ExcludesDefaultCollectionCiphers(
IOrganizationRepository organizationRepository,
IUserRepository userRepository,
ICipherRepository cipherRepository,
ICollectionRepository collectionRepository,
ICollectionCipherRepository collectionCipherRepository)
{
var user = await userRepository.CreateAsync(new User
{
Name = "Test User",
Email = $"test+{Guid.NewGuid()}@email.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
var organization = await organizationRepository.CreateAsync(new Organization
{
Name = "Test Organization",
BillingEmail = user.Email,
Plan = "Test"
});
var defaultCollection = await collectionRepository.CreateAsync(new Collection
{
Name = "Default Collection",
OrganizationId = organization.Id,
Type = CollectionType.DefaultUserCollection
});
var sharedCollection = await collectionRepository.CreateAsync(new Collection
{
Name = "Shared Collection",
OrganizationId = organization.Id,
});
async Task<Cipher> CreateOrgCipherAsync() => await cipherRepository.CreateAsync(new Cipher
{
Type = CipherType.Login,
OrganizationId = organization.Id,
Data = ""
});
var cipherInDefaultCollection = await CreateOrgCipherAsync();
var cipherInSharedCollection = await CreateOrgCipherAsync();
var cipherInBothCollections = await CreateOrgCipherAsync();
var unassignedCipher = await CreateOrgCipherAsync();
async Task LinkCollectionCipherAsync(Guid cipherId, Guid collectionId) =>
await collectionCipherRepository.AddCollectionsForManyCiphersAsync(
organization.Id,
new[] { cipherId },
new[] { collectionId });
await LinkCollectionCipherAsync(cipherInDefaultCollection.Id, defaultCollection.Id);
await LinkCollectionCipherAsync(cipherInSharedCollection.Id, sharedCollection.Id);
await LinkCollectionCipherAsync(cipherInBothCollections.Id, defaultCollection.Id);
await LinkCollectionCipherAsync(cipherInBothCollections.Id, sharedCollection.Id);
await cipherRepository.DeleteByOrganizationIdAsync(organization.Id);
var remainingCipherInDefault = await cipherRepository.GetByIdAsync(cipherInDefaultCollection.Id);
var deletedCipherInShared = await cipherRepository.GetByIdAsync(cipherInSharedCollection.Id);
var remainingCipherInBoth = await cipherRepository.GetByIdAsync(cipherInBothCollections.Id);
var deletedUnassignedCipher = await cipherRepository.GetByIdAsync(unassignedCipher.Id);
Assert.Null(deletedCipherInShared);
Assert.Null(deletedUnassignedCipher);
Assert.NotNull(remainingCipherInDefault);
Assert.NotNull(remainingCipherInBoth);
var remainingCollectionCiphers = await collectionCipherRepository.GetManyByOrganizationIdAsync(organization.Id);
// Should still have the default collection cipher relationships
Assert.Contains(remainingCollectionCiphers, cc =>
cc.CipherId == cipherInDefaultCollection.Id && cc.CollectionId == defaultCollection.Id);
Assert.Contains(remainingCollectionCiphers, cc =>
cc.CipherId == cipherInBothCollections.Id && cc.CollectionId == defaultCollection.Id);
// Should not have the shared collection cipher relationships
Assert.DoesNotContain(remainingCollectionCiphers, cc => cc.CollectionId == sharedCollection.Id);
}
[DatabaseTheory, DatabaseData]
public async Task DeleteByOrganizationIdAsync_DeletesAllWhenNoDefaultCollections(
IOrganizationRepository organizationRepository,
IUserRepository userRepository,
ICipherRepository cipherRepository,
ICollectionRepository collectionRepository,
ICollectionCipherRepository collectionCipherRepository)
{
// Arrange
var user = await userRepository.CreateAsync(new User
{
Name = "Test User",
Email = $"test+{Guid.NewGuid()}@email.com",
ApiKey = "TEST",
SecurityStamp = "stamp",
});
var organization = await organizationRepository.CreateAsync(new Organization
{
Name = "Test Organization",
BillingEmail = user.Email,
Plan = "Test"
});
var sharedCollection1 = await collectionRepository.CreateAsync(new Collection
{
Name = "Shared Collection 1",
OrganizationId = organization.Id,
Type = CollectionType.SharedCollection
});
var sharedCollection2 = await collectionRepository.CreateAsync(new Collection
{
Name = "Shared Collection 2",
OrganizationId = organization.Id,
Type = CollectionType.SharedCollection
});
// Create ciphers
var cipherInSharedCollection1 = await cipherRepository.CreateAsync(new Cipher
{
Type = CipherType.Login,
OrganizationId = organization.Id,
Data = ""
});
var cipherInSharedCollection2 = await cipherRepository.CreateAsync(new Cipher
{
Type = CipherType.Login,
OrganizationId = organization.Id,
Data = ""
});
var unassignedCipher = await cipherRepository.CreateAsync(new Cipher
{
Type = CipherType.Login,
OrganizationId = organization.Id,
Data = ""
});
await collectionCipherRepository.UpdateCollectionsForAdminAsync(cipherInSharedCollection1.Id, organization.Id,
new List<Guid> { sharedCollection1.Id });
await collectionCipherRepository.UpdateCollectionsForAdminAsync(cipherInSharedCollection2.Id, organization.Id,
new List<Guid> { sharedCollection2.Id });
await cipherRepository.DeleteByOrganizationIdAsync(organization.Id);
var deletedCipher1 = await cipherRepository.GetByIdAsync(cipherInSharedCollection1.Id);
var deletedCipher2 = await cipherRepository.GetByIdAsync(cipherInSharedCollection2.Id);
var deletedUnassignedCipher = await cipherRepository.GetByIdAsync(unassignedCipher.Id);
Assert.Null(deletedCipher1);
Assert.Null(deletedCipher2);
Assert.Null(deletedUnassignedCipher);
// All collection cipher relationships should be removed
var remainingCollectionCiphers = await collectionCipherRepository.GetManyByOrganizationIdAsync(organization.Id);
Assert.Empty(remainingCollectionCiphers);
}
}