1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-05 10:02:47 -05:00

[AC-2084] Include Collection permissions for admin endpoints (#3793)

* [AC-2084] Add documentation to existing collection repository getters

* [AC-2084] Add new CollectionAdminDetails model

* [AC-2084] Add SQL and migration scripts

* [AC-2084] Introduce new repository methods to include permission details for collections

* [AC-2084] Add EF repository methods and integration tests

* [AC-2084] Update CollectionsController and response models

* [AC-2084] Fix failing SqlServer test

* [AC-2084] Clean up admin endpoint response models
- vNext endpoints should now always return CollectionDetailsResponse models
- Update constructors in CollectionDetailsResponseModel to be more explicit and add named static constructors for additional clarity

* [AC-2084] Fix failing tests

* [AC-2084] Fix potential provider/member bug

* [AC-2084] Fix broken collections controller

* [AC-2084] Cleanup collection response model types and constructors

* [AC-2084] Remove redundant authorization check

* [AC-2084] Cleanup ambiguous model name

* [AC-2084] Add GroupBy clause to sprocs

* [AC-2084] Add GroupBy logic to EF repository

* [AC-2084] Update collection repository tests

* [AC-2084] Update migration script date

* Update migration script date

---------

Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
Co-authored-by: kejaeger <138028972+kejaeger@users.noreply.github.com>
This commit is contained in:
Shane Melton
2024-05-03 06:33:06 -07:00
committed by GitHub
parent 25c87214ff
commit d965166a37
14 changed files with 1232 additions and 45 deletions

View File

@ -553,47 +553,38 @@ public class CollectionsController : Controller
private async Task<CollectionAccessDetailsResponseModel> GetDetails_vNext(Guid id)
{
// New flexible collections logic
var (collection, access) = await _collectionRepository.GetByIdWithAccessAsync(id);
var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.ReadWithAccess)).Succeeded;
var collectionAdminDetails =
await _collectionRepository.GetByIdWithPermissionsAsync(id, _currentContext.UserId, true);
var authorized = (await _authorizationService.AuthorizeAsync(User, collectionAdminDetails, BulkCollectionOperations.ReadWithAccess)).Succeeded;
if (!authorized)
{
throw new NotFoundException();
}
return new CollectionAccessDetailsResponseModel(collection, access.Groups, access.Users);
return new CollectionAccessDetailsResponseModel(collectionAdminDetails);
}
private async Task<ListResponseModel<CollectionAccessDetailsResponseModel>> GetManyWithDetails_vNext(Guid orgId)
{
// We always need to know which collections the current user is assigned to
var assignedOrgCollections = await _collectionRepository
.GetManyByUserIdWithAccessAsync(_currentContext.UserId.Value, orgId, true);
var allOrgCollections = await _collectionRepository.GetManyByOrganizationIdWithPermissionsAsync(
orgId, _currentContext.UserId.Value, true);
var readAllAuthorized =
(await _authorizationService.AuthorizeAsync(User, CollectionOperations.ReadAllWithAccess(orgId))).Succeeded;
if (readAllAuthorized)
{
// The user can view all collections, but they may not always be assigned to all of them
var allOrgCollections = await _collectionRepository.GetManyByOrganizationIdWithAccessAsync(orgId);
return new ListResponseModel<CollectionAccessDetailsResponseModel>(allOrgCollections.Select(c =>
new CollectionAccessDetailsResponseModel(c.Item1, c.Item2.Groups, c.Item2.Users)
{
// Manually determine which collections they're assigned to
Assigned = assignedOrgCollections.Any(ac => ac.Item1.Id == c.Item1.Id)
})
return new ListResponseModel<CollectionAccessDetailsResponseModel>(
allOrgCollections.Select(c => new CollectionAccessDetailsResponseModel(c))
);
}
// Filter the assigned collections to only return those where the user has Manage permission
var manageableOrgCollections = assignedOrgCollections.Where(c => c.Item1.Manage).ToList();
// Filter collections to only return those where the user has Manage permission
var manageableOrgCollections = allOrgCollections.Where(c => c.Manage).ToList();
return new ListResponseModel<CollectionAccessDetailsResponseModel>(manageableOrgCollections.Select(c =>
new CollectionAccessDetailsResponseModel(c.Item1, c.Item2.Groups, c.Item2.Users)
{
Assigned = true // Mapping from manageableOrgCollections implies they're all assigned
})
);
new CollectionAccessDetailsResponseModel(c)
));
}
private async Task<ListResponseModel<CollectionResponseModel>> GetByOrgId_vNext(Guid orgId)
@ -629,7 +620,7 @@ public class CollectionsController : Controller
return responses;
}
private async Task<CollectionResponseModel> Post_vNext(Guid orgId, [FromBody] CollectionRequestModel model)
private async Task<CollectionAccessDetailsResponseModel> Post_vNext(Guid orgId, [FromBody] CollectionRequestModel model)
{
var collection = model.ToCollection(orgId);
@ -644,21 +635,18 @@ public class CollectionsController : Controller
await _collectionService.SaveAsync(collection, groups, users);
if (!_currentContext.UserId.HasValue || await _currentContext.ProviderUserForOrgAsync(orgId))
if (!_currentContext.UserId.HasValue || (_currentContext.GetOrganization(orgId) == null && await _currentContext.ProviderUserForOrgAsync(orgId)))
{
return new CollectionResponseModel(collection);
return new CollectionAccessDetailsResponseModel(collection);
}
// If we have a user, fetch the collection to get the latest permission details
var userCollectionDetails = await _collectionRepository.GetByIdAsync(collection.Id,
_currentContext.UserId.Value, await FlexibleCollectionsIsEnabledAsync(collection.OrganizationId));
// If we have a user, fetch the latest collection permission details
var collectionWithPermissions = await _collectionRepository.GetByIdWithPermissionsAsync(collection.Id, _currentContext.UserId.Value, false);
return userCollectionDetails == null
? new CollectionResponseModel(collection)
: new CollectionDetailsResponseModel(userCollectionDetails);
return new CollectionAccessDetailsResponseModel(collectionWithPermissions);
}
private async Task<CollectionResponseModel> Put_vNext(Guid id, CollectionRequestModel model)
private async Task<CollectionAccessDetailsResponseModel> Put_vNext(Guid id, CollectionRequestModel model)
{
var collection = await _collectionRepository.GetByIdAsync(id);
var authorized = (await _authorizationService.AuthorizeAsync(User, collection, BulkCollectionOperations.Update)).Succeeded;
@ -671,17 +659,15 @@ public class CollectionsController : Controller
var users = model.Users?.Select(g => g.ToSelectionReadOnly());
await _collectionService.SaveAsync(model.ToCollection(collection), groups, users);
if (!_currentContext.UserId.HasValue || await _currentContext.ProviderUserForOrgAsync(collection.OrganizationId))
if (!_currentContext.UserId.HasValue || (_currentContext.GetOrganization(collection.OrganizationId) == null && await _currentContext.ProviderUserForOrgAsync(collection.OrganizationId)))
{
return new CollectionResponseModel(collection);
return new CollectionAccessDetailsResponseModel(collection);
}
// If we have a user, fetch the collection details to get the latest permission details for the user
var updatedCollectionDetails = await _collectionRepository.GetByIdAsync(id, _currentContext.UserId.Value, await FlexibleCollectionsIsEnabledAsync(collection.OrganizationId));
// If we have a user, fetch the latest collection permission details
var collectionWithPermissions = await _collectionRepository.GetByIdWithPermissionsAsync(collection.Id, _currentContext.UserId.Value, false);
return updatedCollectionDetails == null
? new CollectionResponseModel(collection)
: new CollectionDetailsResponseModel(updatedCollectionDetails);
return new CollectionAccessDetailsResponseModel(collectionWithPermissions);
}
private async Task PutUsers_vNext(Guid id, IEnumerable<SelectionReadOnlyRequestModel> model)