Skip to content

Commit d83395a

Browse files
r-tomeJimmyVo16
andauthored
[PM-25372] Filter out DefaultUserCollections from CiphersController.GetAssignedOrganizationCiphers (#6274)
Co-authored-by: Jimmy Vo <huynhmaivo82@gmail.com>
1 parent 57f891f commit d83395a

8 files changed

Lines changed: 162 additions & 2 deletions

File tree

src/Core/Repositories/ICollectionCipherRepository.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ public interface ICollectionCipherRepository
88
{
99
Task<ICollection<CollectionCipher>> GetManyByUserIdAsync(Guid userId);
1010
Task<ICollection<CollectionCipher>> GetManyByOrganizationIdAsync(Guid organizationId);
11+
Task<ICollection<CollectionCipher>> GetManySharedByOrganizationIdAsync(Guid organizationId);
1112
Task<ICollection<CollectionCipher>> GetManyByUserIdCipherIdAsync(Guid userId, Guid cipherId);
1213
Task UpdateCollectionsAsync(Guid cipherId, Guid userId, IEnumerable<Guid> collectionIds);
1314
Task UpdateCollectionsForAdminAsync(Guid cipherId, Guid organizationId, IEnumerable<Guid> collectionIds);

src/Core/Vault/Queries/OrganizationCiphersQuery.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public async Task<IEnumerable<CipherDetailsWithCollections>> GetOrganizationCiph
2424
var orgCiphers = ciphers.Where(c => c.OrganizationId == organizationId).ToList();
2525
var orgCipherIds = orgCiphers.Select(c => c.Id);
2626

27-
var collectionCiphers = await _collectionCipherRepository.GetManyByOrganizationIdAsync(organizationId);
27+
var collectionCiphers = await _collectionCipherRepository.GetManySharedByOrganizationIdAsync(organizationId);
2828
var collectionCiphersGroupDict = collectionCiphers
2929
.Where(c => orgCipherIds.Contains(c.CipherId))
3030
.GroupBy(c => c.CipherId).ToDictionary(s => s.Key);

src/Infrastructure.Dapper/Repositories/CollectionCipherRepository.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,19 @@ public async Task<ICollection<CollectionCipher>> GetManyByOrganizationIdAsync(Gu
4545
}
4646
}
4747

48+
public async Task<ICollection<CollectionCipher>> GetManySharedByOrganizationIdAsync(Guid organizationId)
49+
{
50+
using (var connection = new SqlConnection(ConnectionString))
51+
{
52+
var results = await connection.QueryAsync<CollectionCipher>(
53+
"[dbo].[CollectionCipher_ReadSharedByOrganizationId]",
54+
new { OrganizationId = organizationId },
55+
commandType: CommandType.StoredProcedure);
56+
57+
return results.ToList();
58+
}
59+
}
60+
4861
public async Task<ICollection<CollectionCipher>> GetManyByUserIdCipherIdAsync(Guid userId, Guid cipherId)
4962
{
5063
using (var connection = new SqlConnection(ConnectionString))

src/Infrastructure.EntityFramework/Repositories/CollectionCipherRepository.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,21 @@ on cc.CollectionId equals c.Id
4747
}
4848
}
4949

50+
public async Task<ICollection<CollectionCipher>> GetManySharedByOrganizationIdAsync(Guid organizationId)
51+
{
52+
using (var scope = ServiceScopeFactory.CreateScope())
53+
{
54+
var dbContext = GetDatabaseContext(scope);
55+
var data = await (from cc in dbContext.CollectionCiphers
56+
join c in dbContext.Collections
57+
on cc.CollectionId equals c.Id
58+
where c.OrganizationId == organizationId
59+
&& c.Type == Core.Enums.CollectionType.SharedCollection
60+
select cc).ToArrayAsync();
61+
return data;
62+
}
63+
}
64+
5065
public async Task<ICollection<CollectionCipher>> GetManyByUserIdAsync(Guid userId)
5166
{
5267
using (var scope = ServiceScopeFactory.CreateScope())
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
CREATE PROCEDURE [dbo].[CollectionCipher_ReadSharedByOrganizationId]
2+
@OrganizationId UNIQUEIDENTIFIER
3+
AS
4+
BEGIN
5+
SET NOCOUNT ON
6+
7+
SELECT
8+
CC.[CollectionId],
9+
CC.[CipherId]
10+
FROM
11+
[dbo].[CollectionCipher] CC
12+
INNER JOIN
13+
[dbo].[Collection] C ON C.[Id] = CC.[CollectionId]
14+
WHERE
15+
C.[OrganizationId] = @OrganizationId
16+
AND C.[Type] = 0 -- SharedCollections only
17+
END

src/Sql/dbo/Tables/Collection.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ GO
1414

1515
CREATE NONCLUSTERED INDEX [IX_Collection_OrganizationId_IncludeAll]
1616
ON [dbo].[Collection]([OrganizationId] ASC)
17-
INCLUDE([CreationDate], [Name], [RevisionDate]);
17+
INCLUDE([CreationDate], [Name], [RevisionDate], [Type]);
1818
GO
1919

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
using Bit.Core.AdminConsole.Entities;
2+
using Bit.Core.Billing.Enums;
3+
using Bit.Core.Entities;
4+
using Bit.Core.Enums;
5+
using Bit.Core.Repositories;
6+
using Bit.Core.Vault.Entities;
7+
using Bit.Core.Vault.Enums;
8+
using Bit.Core.Vault.Repositories;
9+
using Xunit;
10+
11+
namespace Bit.Infrastructure.IntegrationTest.Vault.Repositories;
12+
13+
public class CollectionCipherRepositoryTests
14+
{
15+
[Theory, DatabaseData]
16+
public async Task GetManySharedByOrganizationIdAsync_OnlyReturnsSharedCollections(
17+
IOrganizationRepository organizationRepository,
18+
ICollectionRepository collectionRepository,
19+
ICipherRepository cipherRepository,
20+
ICollectionCipherRepository collectionCipherRepository)
21+
{
22+
// Arrange
23+
var organization = await organizationRepository.CreateAsync(new Organization
24+
{
25+
Name = "Test Org",
26+
PlanType = PlanType.EnterpriseAnnually,
27+
Plan = "Enterprise",
28+
BillingEmail = "billing@example.com"
29+
});
30+
31+
var sharedCollection = await collectionRepository.CreateAsync(new Collection
32+
{
33+
Name = "Shared Collection",
34+
OrganizationId = organization.Id,
35+
Type = CollectionType.SharedCollection
36+
});
37+
38+
var defaultUserCollection = await collectionRepository.CreateAsync(new Collection
39+
{
40+
Name = "Default User Collection",
41+
OrganizationId = organization.Id,
42+
Type = CollectionType.DefaultUserCollection
43+
});
44+
45+
var sharedCipher = await cipherRepository.CreateAsync(new Cipher
46+
{
47+
Type = CipherType.Login,
48+
OrganizationId = organization.Id,
49+
Data = ""
50+
});
51+
52+
var defaultCipher = await cipherRepository.CreateAsync(new Cipher
53+
{
54+
Type = CipherType.Login,
55+
OrganizationId = organization.Id,
56+
Data = ""
57+
});
58+
59+
await collectionCipherRepository.AddCollectionsForManyCiphersAsync(
60+
organization.Id,
61+
new[] { sharedCipher.Id },
62+
new[] { sharedCollection.Id });
63+
64+
await collectionCipherRepository.AddCollectionsForManyCiphersAsync(
65+
organization.Id,
66+
new[] { defaultCipher.Id },
67+
new[] { defaultUserCollection.Id });
68+
69+
// Act
70+
var result = await collectionCipherRepository.GetManySharedByOrganizationIdAsync(organization.Id);
71+
72+
// Assert
73+
Assert.Single(result);
74+
Assert.Equal(sharedCollection.Id, result.First().CollectionId);
75+
Assert.DoesNotContain(result, cc => cc.CollectionId == defaultUserCollection.Id);
76+
77+
// Cleanup
78+
await cipherRepository.DeleteAsync(sharedCipher);
79+
await cipherRepository.DeleteAsync(defaultCipher);
80+
await collectionRepository.DeleteAsync(sharedCollection);
81+
await collectionRepository.DeleteAsync(defaultUserCollection);
82+
await organizationRepository.DeleteAsync(organization);
83+
}
84+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
CREATE OR ALTER PROCEDURE [dbo].[CollectionCipher_ReadSharedByOrganizationId]
2+
@OrganizationId UNIQUEIDENTIFIER
3+
AS
4+
BEGIN
5+
SET NOCOUNT ON
6+
7+
SELECT
8+
CC.[CollectionId],
9+
CC.[CipherId]
10+
FROM
11+
[dbo].[CollectionCipher] CC
12+
INNER JOIN
13+
[dbo].[Collection] C ON C.[Id] = CC.[CollectionId]
14+
WHERE
15+
C.[OrganizationId] = @OrganizationId
16+
AND C.[Type] = 0 -- SharedCollections only
17+
END
18+
GO
19+
20+
-- Update [IX_Collection_OrganizationId_IncludeAll] index to include [Type] column
21+
IF EXISTS(SELECT name FROM sys.indexes WHERE name = 'IX_Collection_OrganizationId_IncludeAll' AND object_id = OBJECT_ID('[dbo].[Collection]'))
22+
BEGIN
23+
DROP INDEX [IX_Collection_OrganizationId_IncludeAll] ON [dbo].[Collection]
24+
END
25+
GO
26+
27+
CREATE NONCLUSTERED INDEX [IX_Collection_OrganizationId_IncludeAll]
28+
ON [dbo].[Collection]([OrganizationId] ASC)
29+
INCLUDE([CreationDate], [Name], [RevisionDate], [Type])
30+
GO

0 commit comments

Comments
 (0)