1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-01 08:02:49 -05:00
Files
bitwarden/test/Core.Test/OrganizationFeatures/OrganizationCollections/BulkAddCollectionAccessCommandTests.cs
Shane Melton 5d431adbd4 [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>
2023-09-26 09:30:07 -07:00

272 lines
12 KiB
C#

using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Data;
using Bit.Core.OrganizationFeatures.OrganizationCollections;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Test.Vault.AutoFixture;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.OrganizationFeatures.OrganizationCollections;
[SutProviderCustomize]
public class BulkAddCollectionAccessCommandTests
{
[Theory, BitAutoData, CollectionCustomization]
public async Task AddAccessAsync_Success(SutProvider<BulkAddCollectionAccessCommand> sutProvider,
Organization org,
ICollection<Collection> collections,
ICollection<OrganizationUser> organizationUsers,
ICollection<Group> groups,
IEnumerable<CollectionUser> collectionUsers,
IEnumerable<CollectionGroup> collectionGroups)
{
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyAsync(
Arg.Is<IEnumerable<Guid>>(ids => ids.SequenceEqual(collectionUsers.Select(u => u.OrganizationUserId)))
)
.Returns(organizationUsers);
sutProvider.GetDependency<IGroupRepository>()
.GetManyByManyIds(
Arg.Is<IEnumerable<Guid>>(ids => ids.SequenceEqual(collectionGroups.Select(u => u.GroupId)))
)
.Returns(groups);
var userAccessSelections = ToAccessSelection(collectionUsers);
var groupAccessSelections = ToAccessSelection(collectionGroups);
await sutProvider.Sut.AddAccessAsync(collections,
userAccessSelections,
groupAccessSelections
);
await sutProvider.GetDependency<IOrganizationUserRepository>().Received().GetManyAsync(
Arg.Is<IEnumerable<Guid>>(ids => ids.SequenceEqual(userAccessSelections.Select(u => u.Id)))
);
await sutProvider.GetDependency<IGroupRepository>().Received().GetManyByManyIds(
Arg.Is<IEnumerable<Guid>>(ids => ids.SequenceEqual(groupAccessSelections.Select(g => g.Id)))
);
await sutProvider.GetDependency<ICollectionRepository>().Received().CreateOrUpdateAccessForManyAsync(
org.Id,
Arg.Is<IEnumerable<Guid>>(ids => ids.SequenceEqual(collections.Select(c => c.Id))),
userAccessSelections,
groupAccessSelections);
await sutProvider.GetDependency<IEventService>().Received().LogCollectionEventsAsync(
Arg.Is<IEnumerable<(Collection, EventType, DateTime?)>>(
events => events.All(e =>
collections.Contains(e.Item1) &&
e.Item2 == EventType.Collection_Updated &&
e.Item3.HasValue
)
)
);
}
[Theory, BitAutoData, CollectionCustomization]
public async Task ValidateRequestAsync_NoCollectionsProvided_Failure(SutProvider<BulkAddCollectionAccessCommand> sutProvider)
{
var exception =
await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.AddAccessAsync(null, null, null));
Assert.Contains("No collections were provided.", exception.Message);
await sutProvider.GetDependency<ICollectionRepository>().DidNotReceiveWithAnyArgs().GetManyByManyIdsAsync(default);
await sutProvider.GetDependency<IOrganizationUserRepository>().DidNotReceiveWithAnyArgs().GetManyAsync(default);
await sutProvider.GetDependency<IGroupRepository>().DidNotReceiveWithAnyArgs().GetManyByManyIds(default);
}
[Theory, BitAutoData, CollectionCustomization]
public async Task ValidateRequestAsync_NoCollection_Failure(SutProvider<BulkAddCollectionAccessCommand> sutProvider,
IEnumerable<CollectionUser> collectionUsers,
IEnumerable<CollectionGroup> collectionGroups)
{
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.AddAccessAsync(Enumerable.Empty<Collection>().ToList(),
ToAccessSelection(collectionUsers),
ToAccessSelection(collectionGroups)
));
Assert.Contains("No collections were provided.", exception.Message);
await sutProvider.GetDependency<IOrganizationUserRepository>().DidNotReceiveWithAnyArgs().GetManyAsync(default);
await sutProvider.GetDependency<IGroupRepository>().DidNotReceiveWithAnyArgs().GetManyByManyIds(default);
}
[Theory, BitAutoData, CollectionCustomization]
public async Task ValidateRequestAsync_DifferentOrgs_Failure(SutProvider<BulkAddCollectionAccessCommand> sutProvider,
ICollection<Collection> collections,
IEnumerable<CollectionUser> collectionUsers,
IEnumerable<CollectionGroup> collectionGroups)
{
collections.First().OrganizationId = Guid.NewGuid();
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.AddAccessAsync(collections,
ToAccessSelection(collectionUsers),
ToAccessSelection(collectionGroups)
));
Assert.Contains("All collections must belong to the same organization.", exception.Message);
await sutProvider.GetDependency<IOrganizationUserRepository>().DidNotReceiveWithAnyArgs().GetManyAsync(default);
await sutProvider.GetDependency<IGroupRepository>().DidNotReceiveWithAnyArgs().GetManyByManyIds(default);
}
[Theory, BitAutoData, CollectionCustomization]
public async Task ValidateRequestAsync_MissingUser_Failure(SutProvider<BulkAddCollectionAccessCommand> sutProvider,
IList<Collection> collections,
IList<OrganizationUser> organizationUsers,
IEnumerable<CollectionUser> collectionUsers,
IEnumerable<CollectionGroup> collectionGroups)
{
organizationUsers.RemoveAt(0);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyAsync(
Arg.Is<IEnumerable<Guid>>(ids => ids.SequenceEqual(collectionUsers.Select(u => u.OrganizationUserId)))
)
.Returns(organizationUsers);
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.AddAccessAsync(collections,
ToAccessSelection(collectionUsers),
ToAccessSelection(collectionGroups)
));
Assert.Contains("One or more users do not exist.", exception.Message);
await sutProvider.GetDependency<IOrganizationUserRepository>().Received().GetManyAsync(
Arg.Is<IEnumerable<Guid>>(ids => ids.SequenceEqual(collectionUsers.Select(u => u.OrganizationUserId)))
);
await sutProvider.GetDependency<IGroupRepository>().DidNotReceiveWithAnyArgs().GetManyByManyIds(default);
}
[Theory, BitAutoData, CollectionCustomization]
public async Task ValidateRequestAsync_UserWrongOrg_Failure(SutProvider<BulkAddCollectionAccessCommand> sutProvider,
IList<Collection> collections,
IList<OrganizationUser> organizationUsers,
IEnumerable<CollectionUser> collectionUsers,
IEnumerable<CollectionGroup> collectionGroups)
{
organizationUsers.First().OrganizationId = Guid.NewGuid();
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyAsync(
Arg.Is<IEnumerable<Guid>>(ids => ids.SequenceEqual(collectionUsers.Select(u => u.OrganizationUserId)))
)
.Returns(organizationUsers);
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.AddAccessAsync(collections,
ToAccessSelection(collectionUsers),
ToAccessSelection(collectionGroups)
));
Assert.Contains("One or more users do not belong to the same organization as the collection being assigned.", exception.Message);
await sutProvider.GetDependency<IOrganizationUserRepository>().Received().GetManyAsync(
Arg.Is<IEnumerable<Guid>>(ids => ids.SequenceEqual(collectionUsers.Select(u => u.OrganizationUserId)))
);
await sutProvider.GetDependency<IGroupRepository>().DidNotReceiveWithAnyArgs().GetManyByManyIds(default);
}
[Theory, BitAutoData, CollectionCustomization]
public async Task ValidateRequestAsync_MissingGroup_Failure(SutProvider<BulkAddCollectionAccessCommand> sutProvider,
IList<Collection> collections,
IList<OrganizationUser> organizationUsers,
IList<Group> groups,
IEnumerable<CollectionUser> collectionUsers,
IEnumerable<CollectionGroup> collectionGroups)
{
groups.RemoveAt(0);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyAsync(
Arg.Is<IEnumerable<Guid>>(ids => ids.SequenceEqual(collectionUsers.Select(u => u.OrganizationUserId)))
)
.Returns(organizationUsers);
sutProvider.GetDependency<IGroupRepository>()
.GetManyByManyIds(
Arg.Is<IEnumerable<Guid>>(ids => ids.SequenceEqual(collectionGroups.Select(u => u.GroupId)))
)
.Returns(groups);
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.AddAccessAsync(collections,
ToAccessSelection(collectionUsers),
ToAccessSelection(collectionGroups)
));
Assert.Contains("One or more groups do not exist.", exception.Message);
await sutProvider.GetDependency<IOrganizationUserRepository>().Received().GetManyAsync(
Arg.Is<IEnumerable<Guid>>(ids => ids.SequenceEqual(collectionUsers.Select(u => u.OrganizationUserId)))
);
await sutProvider.GetDependency<IGroupRepository>().Received().GetManyByManyIds(
Arg.Is<IEnumerable<Guid>>(ids => ids.SequenceEqual(collectionGroups.Select(u => u.GroupId)))
);
}
[Theory, BitAutoData, CollectionCustomization]
public async Task ValidateRequestAsync_GroupWrongOrg_Failure(SutProvider<BulkAddCollectionAccessCommand> sutProvider,
IList<Collection> collections,
IList<OrganizationUser> organizationUsers,
IList<Group> groups,
IEnumerable<CollectionUser> collectionUsers,
IEnumerable<CollectionGroup> collectionGroups)
{
groups.First().OrganizationId = Guid.NewGuid();
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyAsync(
Arg.Is<IEnumerable<Guid>>(ids => ids.SequenceEqual(collectionUsers.Select(u => u.OrganizationUserId)))
)
.Returns(organizationUsers);
sutProvider.GetDependency<IGroupRepository>()
.GetManyByManyIds(
Arg.Is<IEnumerable<Guid>>(ids => ids.SequenceEqual(collectionGroups.Select(u => u.GroupId)))
)
.Returns(groups);
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.AddAccessAsync(collections,
ToAccessSelection(collectionUsers),
ToAccessSelection(collectionGroups)
));
Assert.Contains("One or more groups do not belong to the same organization as the collection being assigned.", exception.Message);
await sutProvider.GetDependency<IOrganizationUserRepository>().Received().GetManyAsync(
Arg.Is<IEnumerable<Guid>>(ids => ids.SequenceEqual(collectionUsers.Select(u => u.OrganizationUserId)))
);
await sutProvider.GetDependency<IGroupRepository>().Received().GetManyByManyIds(
Arg.Is<IEnumerable<Guid>>(ids => ids.SequenceEqual(collectionGroups.Select(u => u.GroupId)))
);
}
private static ICollection<CollectionAccessSelection> ToAccessSelection(IEnumerable<CollectionUser> collectionUsers)
{
return collectionUsers.Select(cu => new CollectionAccessSelection
{
Id = cu.OrganizationUserId,
Manage = cu.Manage,
HidePasswords = cu.HidePasswords,
ReadOnly = cu.ReadOnly
}).ToList();
}
private static ICollection<CollectionAccessSelection> ToAccessSelection(IEnumerable<CollectionGroup> collectionGroups)
{
return collectionGroups.Select(cg => new CollectionAccessSelection
{
Id = cg.GroupId,
Manage = cg.Manage,
HidePasswords = cg.HidePasswords,
ReadOnly = cg.ReadOnly
}).ToList();
}
}