diff --git a/src/Infrastructure.Dapper/Vault/Repositories/CipherRepository.cs b/src/Infrastructure.Dapper/Vault/Repositories/CipherRepository.cs index e0a89b1685..9c75295f3f 100644 --- a/src/Infrastructure.Dapper/Vault/Repositories/CipherRepository.cs +++ b/src/Infrastructure.Dapper/Vault/Repositories/CipherRepository.cs @@ -98,7 +98,10 @@ public class CipherRepository : Repository, ICipherRepository return results .GroupBy(c => c.Id) - .Select(g => g.OrderByDescending(og => og.Edit).ThenByDescending(og => og.ViewPassword).First()) + .Select(g => + g.OrderByDescending(og => og.Manage) + .ThenByDescending(og => og.Edit) + .ThenByDescending(og => og.ViewPassword).First()) .ToList(); } } diff --git a/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs b/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs index befb835e26..a560e8e107 100644 --- a/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs +++ b/src/Infrastructure.EntityFramework/Vault/Repositories/CipherRepository.cs @@ -457,7 +457,7 @@ public class CipherRepository : Repository cipherDetailsView = withOrganizations ? + var cipherDetailsView = withOrganizations ? new UserCipherDetailsQuery(userId).Run(dbContext) : new CipherDetailsQuery(userId).Run(dbContext); if (!withOrganizations) @@ -485,8 +485,15 @@ public class CipherRepository : Repository c.Id) + .Select(g => g.OrderByDescending(c => c.Manage) + .ThenByDescending(c => c.Edit) + .ThenByDescending(c => c.ViewPassword) + .First()) + .ToList(); } } diff --git a/test/Infrastructure.IntegrationTest/Vault/Repositories/CipherRepositoryTests.cs b/test/Infrastructure.IntegrationTest/Vault/Repositories/CipherRepositoryTests.cs index b7feeaa79b..288752011f 100644 --- a/test/Infrastructure.IntegrationTest/Vault/Repositories/CipherRepositoryTests.cs +++ b/test/Infrastructure.IntegrationTest/Vault/Repositories/CipherRepositoryTests.cs @@ -571,6 +571,65 @@ public class CipherRepositoryTests 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 + { + new() { Id = orgUser.Id, HidePasswords = false, ReadOnly = false, Manage = true } + }); + + await collectionRepository.UpdateUsersAsync(unmanagedPermissionsCollection.Id, new List + { + 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); + } + private async Task<(User user, Organization org, OrganizationUser orgUser)> CreateTestUserAndOrganization( IUserRepository userRepository, IOrganizationRepository organizationRepository,