mirror of
https://github.com/bitwarden/server.git
synced 2025-07-02 16:42:50 -05: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:
@ -221,4 +221,120 @@ public class CollectionsControllerTests
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.DeleteManyAsync((IEnumerable<Collection>)default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task PostBulkCollectionAccess_Success(User actingUser, ICollection<Collection> collections, SutProvider<CollectionsController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
var groupId = Guid.NewGuid();
|
||||
var model = new BulkCollectionAccessRequestModel
|
||||
{
|
||||
CollectionIds = collections.Select(c => c.Id),
|
||||
Users = new[] { new SelectionReadOnlyRequestModel { Id = userId, Manage = true } },
|
||||
Groups = new[] { new SelectionReadOnlyRequestModel { Id = groupId, ReadOnly = true } },
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<ICollectionRepository>()
|
||||
.GetManyByManyIdsAsync(model.CollectionIds)
|
||||
.Returns(collections);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.UserId.Returns(actingUser.Id);
|
||||
|
||||
sutProvider.GetDependency<IAuthorizationService>().AuthorizeAsync(
|
||||
Arg.Any<ClaimsPrincipal>(), ExpectedCollectionAccess(),
|
||||
Arg.Is<IEnumerable<IAuthorizationRequirement>>(
|
||||
r => r.Contains(CollectionOperations.ModifyAccess)
|
||||
))
|
||||
.Returns(AuthorizationResult.Success());
|
||||
|
||||
IEnumerable<Collection> ExpectedCollectionAccess() => Arg.Is<IEnumerable<Collection>>(cols => cols.SequenceEqual(collections));
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.PostBulkCollectionAccess(model);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IAuthorizationService>().Received().AuthorizeAsync(
|
||||
Arg.Any<ClaimsPrincipal>(),
|
||||
ExpectedCollectionAccess(),
|
||||
Arg.Is<IEnumerable<IAuthorizationRequirement>>(
|
||||
r => r.Contains(CollectionOperations.ModifyAccess))
|
||||
);
|
||||
await sutProvider.GetDependency<IBulkAddCollectionAccessCommand>().Received()
|
||||
.AddAccessAsync(
|
||||
Arg.Is<ICollection<Collection>>(g => g.SequenceEqual(collections)),
|
||||
Arg.Is<ICollection<CollectionAccessSelection>>(u => u.All(c => c.Id == userId && c.Manage)),
|
||||
Arg.Is<ICollection<CollectionAccessSelection>>(g => g.All(c => c.Id == groupId && c.ReadOnly)));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task PostBulkCollectionAccess_CollectionsNotFound_Throws(User actingUser, ICollection<Collection> collections, SutProvider<CollectionsController> sutProvider)
|
||||
{
|
||||
var userId = Guid.NewGuid();
|
||||
var groupId = Guid.NewGuid();
|
||||
var model = new BulkCollectionAccessRequestModel
|
||||
{
|
||||
CollectionIds = collections.Select(c => c.Id),
|
||||
Users = new[] { new SelectionReadOnlyRequestModel { Id = userId, Manage = true } },
|
||||
Groups = new[] { new SelectionReadOnlyRequestModel { Id = groupId, ReadOnly = true } },
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.UserId.Returns(actingUser.Id);
|
||||
|
||||
sutProvider.GetDependency<ICollectionRepository>()
|
||||
.GetManyByManyIdsAsync(model.CollectionIds)
|
||||
.Returns(collections.Skip(1).ToList());
|
||||
|
||||
var exception = await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.PostBulkCollectionAccess(model));
|
||||
|
||||
Assert.Equal("One or more collections not found.", exception.Message);
|
||||
await sutProvider.GetDependency<IAuthorizationService>().DidNotReceiveWithAnyArgs().AuthorizeAsync(
|
||||
Arg.Any<ClaimsPrincipal>(),
|
||||
Arg.Any<IEnumerable<Collection>>(),
|
||||
Arg.Any<IEnumerable<IAuthorizationRequirement>>()
|
||||
);
|
||||
await sutProvider.GetDependency<IBulkAddCollectionAccessCommand>().DidNotReceiveWithAnyArgs()
|
||||
.AddAccessAsync(default, default, default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task PostBulkCollectionAccess_AccessDenied_Throws(User actingUser, ICollection<Collection> collections, SutProvider<CollectionsController> sutProvider)
|
||||
{
|
||||
var userId = Guid.NewGuid();
|
||||
var groupId = Guid.NewGuid();
|
||||
var model = new BulkCollectionAccessRequestModel
|
||||
{
|
||||
CollectionIds = collections.Select(c => c.Id),
|
||||
Users = new[] { new SelectionReadOnlyRequestModel { Id = userId, Manage = true } },
|
||||
Groups = new[] { new SelectionReadOnlyRequestModel { Id = groupId, ReadOnly = true } },
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.UserId.Returns(actingUser.Id);
|
||||
|
||||
sutProvider.GetDependency<ICollectionRepository>()
|
||||
.GetManyByManyIdsAsync(model.CollectionIds)
|
||||
.Returns(collections);
|
||||
|
||||
sutProvider.GetDependency<IAuthorizationService>().AuthorizeAsync(
|
||||
Arg.Any<ClaimsPrincipal>(), ExpectedCollectionAccess(),
|
||||
Arg.Is<IEnumerable<IAuthorizationRequirement>>(
|
||||
r => r.Contains(CollectionOperations.ModifyAccess)
|
||||
))
|
||||
.Returns(AuthorizationResult.Failed());
|
||||
|
||||
IEnumerable<Collection> ExpectedCollectionAccess() => Arg.Is<IEnumerable<Collection>>(cols => cols.SequenceEqual(collections));
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.PostBulkCollectionAccess(model));
|
||||
await sutProvider.GetDependency<IAuthorizationService>().Received().AuthorizeAsync(
|
||||
Arg.Any<ClaimsPrincipal>(),
|
||||
ExpectedCollectionAccess(),
|
||||
Arg.Is<IEnumerable<IAuthorizationRequirement>>(
|
||||
r => r.Contains(CollectionOperations.ModifyAccess))
|
||||
);
|
||||
await sutProvider.GetDependency<IBulkAddCollectionAccessCommand>().DidNotReceiveWithAnyArgs()
|
||||
.AddAccessAsync(default, default, default);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user