using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Repositories; 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(); } }