mirror of
https://github.com/bitwarden/server
synced 2025-12-25 12:43:14 +00:00
[AC-1174] Bulk Collection Management (#3229)
* [AC-1174] Update SelectionReadOnlyRequestModel to use Guid for Id property * [AC-1174] Introduce initial bulk-access collection endpoint * [AC-1174] Introduce BulkAddCollectionAccessCommand and validation logic/tests * [AC-1174] Add CreateOrUpdateAccessMany method to CollectionRepository * [AC-1174] Add event logs for bulk add collection access command * [AC-1174] Add User_BumpAccountRevisionDateByCollectionIds and database migration script * [AC-1174] Implement EF repository method * [AC-1174] Improve null checks * [AC-1174] Remove unnecessary BulkCollectionAccessRequestModel helpers * [AC-1174] Add unit tests for new controller endpoint * [AC-1174] Fix formatting * [AC-1174] Remove comment * [AC-1174] Remove redundant organizationId parameter * [AC-1174] Ensure user and group Ids are distinct * [AC-1174] Cleanup tests based on PR feedback * [AC-1174] Formatting * [AC-1174] Update CollectionGroup alias in the sproc * [AC-1174] Add some additional comments to SQL sproc * [AC-1174] Add comment explaining additional SaveChangesAsync call --------- Co-authored-by: Thomas Rittson <trittson@bitwarden.com>
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationCollections.Interfaces;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
|
||||
namespace Bit.Core.OrganizationFeatures.OrganizationCollections;
|
||||
|
||||
public class BulkAddCollectionAccessCommand : IBulkAddCollectionAccessCommand
|
||||
{
|
||||
private readonly ICollectionRepository _collectionRepository;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly IGroupRepository _groupRepository;
|
||||
private readonly IEventService _eventService;
|
||||
|
||||
public BulkAddCollectionAccessCommand(
|
||||
ICollectionRepository collectionRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IGroupRepository groupRepository,
|
||||
IEventService eventService)
|
||||
{
|
||||
_collectionRepository = collectionRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
_groupRepository = groupRepository;
|
||||
_eventService = eventService;
|
||||
}
|
||||
|
||||
public async Task AddAccessAsync(ICollection<Collection> collections,
|
||||
ICollection<CollectionAccessSelection> users,
|
||||
ICollection<CollectionAccessSelection> groups)
|
||||
{
|
||||
await ValidateRequestAsync(collections, users, groups);
|
||||
|
||||
await _collectionRepository.CreateOrUpdateAccessForManyAsync(
|
||||
collections.First().OrganizationId,
|
||||
collections.Select(c => c.Id),
|
||||
users,
|
||||
groups
|
||||
);
|
||||
|
||||
await _eventService.LogCollectionEventsAsync(collections.Select(c =>
|
||||
(c, EventType.Collection_Updated, (DateTime?)DateTime.UtcNow)));
|
||||
}
|
||||
|
||||
private async Task ValidateRequestAsync(ICollection<Collection> collections, ICollection<CollectionAccessSelection> usersAccess, ICollection<CollectionAccessSelection> groupsAccess)
|
||||
{
|
||||
if (collections == null || collections.Count == 0)
|
||||
{
|
||||
throw new BadRequestException("No collections were provided.");
|
||||
}
|
||||
|
||||
var orgId = collections.First().OrganizationId;
|
||||
|
||||
if (collections.Any(c => c.OrganizationId != orgId))
|
||||
{
|
||||
throw new BadRequestException("All collections must belong to the same organization.");
|
||||
}
|
||||
|
||||
var collectionUserIds = usersAccess?.Select(u => u.Id).Distinct().ToList();
|
||||
|
||||
if (collectionUserIds is { Count: > 0 })
|
||||
{
|
||||
var users = await _organizationUserRepository.GetManyAsync(collectionUserIds);
|
||||
|
||||
if (users.Count != collectionUserIds.Count)
|
||||
{
|
||||
throw new BadRequestException("One or more users do not exist.");
|
||||
}
|
||||
|
||||
if (users.Any(u => u.OrganizationId != orgId))
|
||||
{
|
||||
throw new BadRequestException("One or more users do not belong to the same organization as the collection being assigned.");
|
||||
}
|
||||
}
|
||||
|
||||
var collectionGroupIds = groupsAccess?.Select(g => g.Id).Distinct().ToList();
|
||||
|
||||
if (collectionGroupIds is { Count: > 0 })
|
||||
{
|
||||
var groups = await _groupRepository.GetManyByManyIds(collectionGroupIds);
|
||||
|
||||
if (groups.Count != collectionGroupIds.Count)
|
||||
{
|
||||
throw new BadRequestException("One or more groups do not exist.");
|
||||
}
|
||||
|
||||
if (groups.Any(g => g.OrganizationId != orgId))
|
||||
{
|
||||
throw new BadRequestException("One or more groups do not belong to the same organization as the collection being assigned.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user