1
0
mirror of https://github.com/bitwarden/server synced 2026-01-09 12:03:21 +00:00

[PM-23987] Fix saving to default collections by updating collection lookup (#6122)

* Refactor ICollectionRepository.GetManyByOrganizationIdAsync logic to include default user collections

* Add stored procedure Collection_ReadSharedCollectionsByOrganizationId to retrieve collections by organization ID, excluding default user collections.

* Add GetManySharedCollectionsByOrganizationIdAsync method to ICollectionRepository and its implementations to retrieve collections excluding default user collections.

* Add unit test for GetManySharedCollectionsByOrganizationIdAsync method in CollectionRepositoryTests to verify retrieval of collections excluding default user collections.

* Refactor controllers to use GetManySharedCollectionsByOrganizationIdAsync for retrieving shared collections

* Update unit tests to use GetManySharedCollectionsByOrganizationIdAsync for verifying shared collections retrieval

* Revert CiphersController.CanEditItemsInCollections to use GetManyByOrganizationIdAsync for retrieving organization collections

* Update stored procedures to retrieve only DefaultUserCollection by modifying the WHERE clause in Collection_ReadSharedCollectionsByOrganizationId.sql and its corresponding migration script.

* Update EF CollectionRepository.GetManySharedCollectionsByOrganizationIdAsync to filter collections by SharedCollection

* Update OrganizationUserRepository.GetManyDetailsByOrganizationAsync_vNext to only include Shared collections

* Update comments in stored procedure and migration script to clarify filtering for SharedCollections only
This commit is contained in:
Rui Tomé
2025-07-29 15:04:00 +01:00
committed by GitHub
parent 52ef3ef7a5
commit 6dea40c868
13 changed files with 213 additions and 19 deletions

View File

@@ -109,7 +109,7 @@ public class CollectionsController : Controller
var readAllAuthorized = (await _authorizationService.AuthorizeAsync(User, CollectionOperations.ReadAll(orgId))).Succeeded;
if (readAllAuthorized)
{
orgCollections = await _collectionRepository.GetManyByOrganizationIdAsync(orgId);
orgCollections = await _collectionRepository.GetManySharedCollectionsByOrganizationIdAsync(orgId);
}
else
{

View File

@@ -65,7 +65,7 @@ public class CollectionsController : Controller
[ProducesResponseType(typeof(ListResponseModel<CollectionResponseModel>), (int)HttpStatusCode.OK)]
public async Task<IActionResult> List()
{
var collections = await _collectionRepository.GetManyByOrganizationIdAsync(
var collections = await _collectionRepository.GetManySharedCollectionsByOrganizationIdAsync(
_currentContext.OrganizationId.Value);
// TODO: Get all CollectionGroup associations for the organization and marry them up here for the response.
var collectionResponses = collections.Select(c => new CollectionResponseModel(c, null));

View File

@@ -16,10 +16,16 @@ public interface ICollectionRepository : IRepository<Collection, Guid>
/// <summary>
/// Return all collections that belong to the organization. Does not include any permission details or group/user
/// access relationships. Excludes default collections (My Items collections).
/// access relationships.
/// </summary>
Task<ICollection<Collection>> GetManyByOrganizationIdAsync(Guid organizationId);
/// <inheritdoc cref="GetManyByOrganizationIdAsync"/>
/// <remarks>
/// Excludes default collections (My Items collections) - used by Admin Console Collections tab.
/// </remarks>
Task<ICollection<Collection>> GetManySharedCollectionsByOrganizationIdAsync(Guid organizationId);
/// <summary>
/// Return all shared collections that belong to the organization. Includes group/user access relationships for each collection.
/// </summary>

View File

@@ -79,6 +79,19 @@ public class CollectionRepository : Repository<Collection, Guid>, ICollectionRep
}
}
public async Task<ICollection<Collection>> GetManySharedCollectionsByOrganizationIdAsync(Guid organizationId)
{
using (var connection = new SqlConnection(ConnectionString))
{
var results = await connection.QueryAsync<Collection>(
$"[{Schema}].[{Table}_ReadSharedCollectionsByOrganizationId]",
new { OrganizationId = organizationId },
commandType: CommandType.StoredProcedure);
return results.ToList();
}
}
public async Task<ICollection<Tuple<Collection, CollectionAccessDetails>>> GetManyByOrganizationIdWithAccessAsync(Guid organizationId)
{
using (var connection = new SqlConnection(ConnectionString))

View File

@@ -441,13 +441,15 @@ public class OrganizationUserRepository : Repository<Core.Entities.OrganizationU
: new List<Guid>(),
Collections = includeCollections
? ou.CollectionUsers.Select(cu => new CollectionAccessSelection
{
Id = cu.CollectionId,
ReadOnly = cu.ReadOnly,
HidePasswords = cu.HidePasswords,
Manage = cu.Manage
}).ToList()
? ou.CollectionUsers
.Where(cu => cu.Collection.Type == CollectionType.SharedCollection)
.Select(cu => new CollectionAccessSelection
{
Id = cu.CollectionId,
ReadOnly = cu.ReadOnly,
HidePasswords = cu.HidePasswords,
Manage = cu.Manage
}).ToList()
: new List<CollectionAccessSelection>()
};

View File

@@ -212,13 +212,26 @@ public class CollectionRepository : Repository<Core.Entities.Collection, Collect
}
public async Task<ICollection<Core.Entities.Collection>> GetManyByOrganizationIdAsync(Guid organizationId)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
var query = from c in dbContext.Collections
where c.OrganizationId == organizationId
select c;
var collections = await query.ToArrayAsync();
return collections;
}
}
public async Task<ICollection<Core.Entities.Collection>> GetManySharedCollectionsByOrganizationIdAsync(Guid organizationId)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
var query = from c in dbContext.Collections
where c.OrganizationId == organizationId &&
c.Type != CollectionType.DefaultUserCollection
c.Type == CollectionType.SharedCollection
select c;
var collections = await query.ToArrayAsync();
return collections;

View File

@@ -9,6 +9,5 @@ BEGIN
FROM
[dbo].[CollectionView]
WHERE
[OrganizationId] = @OrganizationId AND
[Type] != 1 -- Exclude DefaultUserCollection
[OrganizationId] = @OrganizationId
END

View File

@@ -0,0 +1,14 @@
CREATE PROCEDURE [dbo].[Collection_ReadSharedCollectionsByOrganizationId]
@OrganizationId UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
SELECT
*
FROM
[dbo].[CollectionView]
WHERE
[OrganizationId] = @OrganizationId AND
[Type] = 0 -- SharedCollections only
END

View File

@@ -26,6 +26,8 @@ BEGIN
SELECT cu.*
FROM [dbo].[CollectionUser] cu
INNER JOIN [dbo].[OrganizationUser] ou ON cu.OrganizationUserId = ou.Id
WHERE ou.OrganizationId = @OrganizationId
INNER JOIN [dbo].[Collection] c ON cu.CollectionId = c.Id
WHERE ou.OrganizationId = @OrganizationId
AND c.Type = 0 -- SharedCollections only
END
END