mirror of
https://github.com/bitwarden/server
synced 2025-12-06 00:03:34 +00:00
[PM-25372] Filter out DefaultUserCollections from CiphersController.GetAssignedOrganizationCiphers (#6274)
Co-authored-by: Jimmy Vo <huynhmaivo82@gmail.com>
This commit is contained in:
@@ -8,6 +8,7 @@ public interface ICollectionCipherRepository
|
||||
{
|
||||
Task<ICollection<CollectionCipher>> GetManyByUserIdAsync(Guid userId);
|
||||
Task<ICollection<CollectionCipher>> GetManyByOrganizationIdAsync(Guid organizationId);
|
||||
Task<ICollection<CollectionCipher>> GetManySharedByOrganizationIdAsync(Guid organizationId);
|
||||
Task<ICollection<CollectionCipher>> GetManyByUserIdCipherIdAsync(Guid userId, Guid cipherId);
|
||||
Task UpdateCollectionsAsync(Guid cipherId, Guid userId, IEnumerable<Guid> collectionIds);
|
||||
Task UpdateCollectionsForAdminAsync(Guid cipherId, Guid organizationId, IEnumerable<Guid> collectionIds);
|
||||
|
||||
@@ -24,7 +24,7 @@ public class OrganizationCiphersQuery : IOrganizationCiphersQuery
|
||||
var orgCiphers = ciphers.Where(c => c.OrganizationId == organizationId).ToList();
|
||||
var orgCipherIds = orgCiphers.Select(c => c.Id);
|
||||
|
||||
var collectionCiphers = await _collectionCipherRepository.GetManyByOrganizationIdAsync(organizationId);
|
||||
var collectionCiphers = await _collectionCipherRepository.GetManySharedByOrganizationIdAsync(organizationId);
|
||||
var collectionCiphersGroupDict = collectionCiphers
|
||||
.Where(c => orgCipherIds.Contains(c.CipherId))
|
||||
.GroupBy(c => c.CipherId).ToDictionary(s => s.Key);
|
||||
|
||||
@@ -45,6 +45,19 @@ public class CollectionCipherRepository : BaseRepository, ICollectionCipherRepos
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ICollection<CollectionCipher>> GetManySharedByOrganizationIdAsync(Guid organizationId)
|
||||
{
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
var results = await connection.QueryAsync<CollectionCipher>(
|
||||
"[dbo].[CollectionCipher_ReadSharedByOrganizationId]",
|
||||
new { OrganizationId = organizationId },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
|
||||
return results.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ICollection<CollectionCipher>> GetManyByUserIdCipherIdAsync(Guid userId, Guid cipherId)
|
||||
{
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
|
||||
@@ -47,6 +47,21 @@ public class CollectionCipherRepository : BaseEntityFrameworkRepository, ICollec
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ICollection<CollectionCipher>> GetManySharedByOrganizationIdAsync(Guid organizationId)
|
||||
{
|
||||
using (var scope = ServiceScopeFactory.CreateScope())
|
||||
{
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
var data = await (from cc in dbContext.CollectionCiphers
|
||||
join c in dbContext.Collections
|
||||
on cc.CollectionId equals c.Id
|
||||
where c.OrganizationId == organizationId
|
||||
&& c.Type == Core.Enums.CollectionType.SharedCollection
|
||||
select cc).ToArrayAsync();
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ICollection<CollectionCipher>> GetManyByUserIdAsync(Guid userId)
|
||||
{
|
||||
using (var scope = ServiceScopeFactory.CreateScope())
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
CREATE PROCEDURE [dbo].[CollectionCipher_ReadSharedByOrganizationId]
|
||||
@OrganizationId UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
SELECT
|
||||
CC.[CollectionId],
|
||||
CC.[CipherId]
|
||||
FROM
|
||||
[dbo].[CollectionCipher] CC
|
||||
INNER JOIN
|
||||
[dbo].[Collection] C ON C.[Id] = CC.[CollectionId]
|
||||
WHERE
|
||||
C.[OrganizationId] = @OrganizationId
|
||||
AND C.[Type] = 0 -- SharedCollections only
|
||||
END
|
||||
@@ -14,6 +14,6 @@ GO
|
||||
|
||||
CREATE NONCLUSTERED INDEX [IX_Collection_OrganizationId_IncludeAll]
|
||||
ON [dbo].[Collection]([OrganizationId] ASC)
|
||||
INCLUDE([CreationDate], [Name], [RevisionDate]);
|
||||
INCLUDE([CreationDate], [Name], [RevisionDate], [Type]);
|
||||
GO
|
||||
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Vault.Entities;
|
||||
using Bit.Core.Vault.Enums;
|
||||
using Bit.Core.Vault.Repositories;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Infrastructure.IntegrationTest.Vault.Repositories;
|
||||
|
||||
public class CollectionCipherRepositoryTests
|
||||
{
|
||||
[Theory, DatabaseData]
|
||||
public async Task GetManySharedByOrganizationIdAsync_OnlyReturnsSharedCollections(
|
||||
IOrganizationRepository organizationRepository,
|
||||
ICollectionRepository collectionRepository,
|
||||
ICipherRepository cipherRepository,
|
||||
ICollectionCipherRepository collectionCipherRepository)
|
||||
{
|
||||
// Arrange
|
||||
var organization = await organizationRepository.CreateAsync(new Organization
|
||||
{
|
||||
Name = "Test Org",
|
||||
PlanType = PlanType.EnterpriseAnnually,
|
||||
Plan = "Enterprise",
|
||||
BillingEmail = "billing@example.com"
|
||||
});
|
||||
|
||||
var sharedCollection = await collectionRepository.CreateAsync(new Collection
|
||||
{
|
||||
Name = "Shared Collection",
|
||||
OrganizationId = organization.Id,
|
||||
Type = CollectionType.SharedCollection
|
||||
});
|
||||
|
||||
var defaultUserCollection = await collectionRepository.CreateAsync(new Collection
|
||||
{
|
||||
Name = "Default User Collection",
|
||||
OrganizationId = organization.Id,
|
||||
Type = CollectionType.DefaultUserCollection
|
||||
});
|
||||
|
||||
var sharedCipher = await cipherRepository.CreateAsync(new Cipher
|
||||
{
|
||||
Type = CipherType.Login,
|
||||
OrganizationId = organization.Id,
|
||||
Data = ""
|
||||
});
|
||||
|
||||
var defaultCipher = await cipherRepository.CreateAsync(new Cipher
|
||||
{
|
||||
Type = CipherType.Login,
|
||||
OrganizationId = organization.Id,
|
||||
Data = ""
|
||||
});
|
||||
|
||||
await collectionCipherRepository.AddCollectionsForManyCiphersAsync(
|
||||
organization.Id,
|
||||
new[] { sharedCipher.Id },
|
||||
new[] { sharedCollection.Id });
|
||||
|
||||
await collectionCipherRepository.AddCollectionsForManyCiphersAsync(
|
||||
organization.Id,
|
||||
new[] { defaultCipher.Id },
|
||||
new[] { defaultUserCollection.Id });
|
||||
|
||||
// Act
|
||||
var result = await collectionCipherRepository.GetManySharedByOrganizationIdAsync(organization.Id);
|
||||
|
||||
// Assert
|
||||
Assert.Single(result);
|
||||
Assert.Equal(sharedCollection.Id, result.First().CollectionId);
|
||||
Assert.DoesNotContain(result, cc => cc.CollectionId == defaultUserCollection.Id);
|
||||
|
||||
// Cleanup
|
||||
await cipherRepository.DeleteAsync(sharedCipher);
|
||||
await cipherRepository.DeleteAsync(defaultCipher);
|
||||
await collectionRepository.DeleteAsync(sharedCollection);
|
||||
await collectionRepository.DeleteAsync(defaultUserCollection);
|
||||
await organizationRepository.DeleteAsync(organization);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
CREATE OR ALTER PROCEDURE [dbo].[CollectionCipher_ReadSharedByOrganizationId]
|
||||
@OrganizationId UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
SELECT
|
||||
CC.[CollectionId],
|
||||
CC.[CipherId]
|
||||
FROM
|
||||
[dbo].[CollectionCipher] CC
|
||||
INNER JOIN
|
||||
[dbo].[Collection] C ON C.[Id] = CC.[CollectionId]
|
||||
WHERE
|
||||
C.[OrganizationId] = @OrganizationId
|
||||
AND C.[Type] = 0 -- SharedCollections only
|
||||
END
|
||||
GO
|
||||
|
||||
-- Update [IX_Collection_OrganizationId_IncludeAll] index to include [Type] column
|
||||
IF EXISTS(SELECT name FROM sys.indexes WHERE name = 'IX_Collection_OrganizationId_IncludeAll' AND object_id = OBJECT_ID('[dbo].[Collection]'))
|
||||
BEGIN
|
||||
DROP INDEX [IX_Collection_OrganizationId_IncludeAll] ON [dbo].[Collection]
|
||||
END
|
||||
GO
|
||||
|
||||
CREATE NONCLUSTERED INDEX [IX_Collection_OrganizationId_IncludeAll]
|
||||
ON [dbo].[Collection]([OrganizationId] ASC)
|
||||
INCLUDE([CreationDate], [Name], [RevisionDate], [Type])
|
||||
GO
|
||||
Reference in New Issue
Block a user