mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 15:42:48 -05:00
[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
This commit is contained in:
@ -433,4 +433,275 @@ public class CipherRepositoryTests
|
||||
Assert.False(unassignedCipherPermission.Read);
|
||||
Assert.False(unassignedCipherPermission.ViewPassword);
|
||||
}
|
||||
|
||||
[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");
|
||||
}
|
||||
|
||||
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 = ""
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user