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 sutProvider, Organization org, ICollection collections, ICollection organizationUsers, ICollection groups, IEnumerable collectionUsers, IEnumerable collectionGroups) { sutProvider.GetDependency() .GetManyAsync( Arg.Is>(ids => ids.SequenceEqual(collectionUsers.Select(u => u.OrganizationUserId))) ) .Returns(organizationUsers); sutProvider.GetDependency() .GetManyByManyIds( Arg.Is>(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().Received().GetManyAsync( Arg.Is>(ids => ids.SequenceEqual(userAccessSelections.Select(u => u.Id))) ); await sutProvider.GetDependency().Received().GetManyByManyIds( Arg.Is>(ids => ids.SequenceEqual(groupAccessSelections.Select(g => g.Id))) ); await sutProvider.GetDependency().Received().CreateOrUpdateAccessForManyAsync( org.Id, Arg.Is>(ids => ids.SequenceEqual(collections.Select(c => c.Id))), userAccessSelections, groupAccessSelections); await sutProvider.GetDependency().Received().LogCollectionEventsAsync( Arg.Is>( 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 sutProvider) { var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.AddAccessAsync(null, null, null)); Assert.Contains("No collections were provided.", exception.Message); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyByManyIdsAsync(default); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyAsync(default); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyByManyIds(default); } [Theory, BitAutoData, CollectionCustomization] public async Task ValidateRequestAsync_NoCollection_Failure(SutProvider sutProvider, IEnumerable collectionUsers, IEnumerable collectionGroups) { var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.AddAccessAsync(Enumerable.Empty().ToList(), ToAccessSelection(collectionUsers), ToAccessSelection(collectionGroups) )); Assert.Contains("No collections were provided.", exception.Message); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyAsync(default); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyByManyIds(default); } [Theory, BitAutoData, CollectionCustomization] public async Task ValidateRequestAsync_DifferentOrgs_Failure(SutProvider sutProvider, ICollection collections, IEnumerable collectionUsers, IEnumerable collectionGroups) { collections.First().OrganizationId = Guid.NewGuid(); var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.AddAccessAsync(collections, ToAccessSelection(collectionUsers), ToAccessSelection(collectionGroups) )); Assert.Contains("All collections must belong to the same organization.", exception.Message); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyAsync(default); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyByManyIds(default); } [Theory, BitAutoData, CollectionCustomization] public async Task ValidateRequestAsync_MissingUser_Failure(SutProvider sutProvider, IList collections, IList organizationUsers, IEnumerable collectionUsers, IEnumerable collectionGroups) { organizationUsers.RemoveAt(0); sutProvider.GetDependency() .GetManyAsync( Arg.Is>(ids => ids.SequenceEqual(collectionUsers.Select(u => u.OrganizationUserId))) ) .Returns(organizationUsers); var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.AddAccessAsync(collections, ToAccessSelection(collectionUsers), ToAccessSelection(collectionGroups) )); Assert.Contains("One or more users do not exist.", exception.Message); await sutProvider.GetDependency().Received().GetManyAsync( Arg.Is>(ids => ids.SequenceEqual(collectionUsers.Select(u => u.OrganizationUserId))) ); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyByManyIds(default); } [Theory, BitAutoData, CollectionCustomization] public async Task ValidateRequestAsync_UserWrongOrg_Failure(SutProvider sutProvider, IList collections, IList organizationUsers, IEnumerable collectionUsers, IEnumerable collectionGroups) { organizationUsers.First().OrganizationId = Guid.NewGuid(); sutProvider.GetDependency() .GetManyAsync( Arg.Is>(ids => ids.SequenceEqual(collectionUsers.Select(u => u.OrganizationUserId))) ) .Returns(organizationUsers); var exception = await Assert.ThrowsAsync(() => 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().Received().GetManyAsync( Arg.Is>(ids => ids.SequenceEqual(collectionUsers.Select(u => u.OrganizationUserId))) ); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyByManyIds(default); } [Theory, BitAutoData, CollectionCustomization] public async Task ValidateRequestAsync_MissingGroup_Failure(SutProvider sutProvider, IList collections, IList organizationUsers, IList groups, IEnumerable collectionUsers, IEnumerable collectionGroups) { groups.RemoveAt(0); sutProvider.GetDependency() .GetManyAsync( Arg.Is>(ids => ids.SequenceEqual(collectionUsers.Select(u => u.OrganizationUserId))) ) .Returns(organizationUsers); sutProvider.GetDependency() .GetManyByManyIds( Arg.Is>(ids => ids.SequenceEqual(collectionGroups.Select(u => u.GroupId))) ) .Returns(groups); var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.AddAccessAsync(collections, ToAccessSelection(collectionUsers), ToAccessSelection(collectionGroups) )); Assert.Contains("One or more groups do not exist.", exception.Message); await sutProvider.GetDependency().Received().GetManyAsync( Arg.Is>(ids => ids.SequenceEqual(collectionUsers.Select(u => u.OrganizationUserId))) ); await sutProvider.GetDependency().Received().GetManyByManyIds( Arg.Is>(ids => ids.SequenceEqual(collectionGroups.Select(u => u.GroupId))) ); } [Theory, BitAutoData, CollectionCustomization] public async Task ValidateRequestAsync_GroupWrongOrg_Failure(SutProvider sutProvider, IList collections, IList organizationUsers, IList groups, IEnumerable collectionUsers, IEnumerable collectionGroups) { groups.First().OrganizationId = Guid.NewGuid(); sutProvider.GetDependency() .GetManyAsync( Arg.Is>(ids => ids.SequenceEqual(collectionUsers.Select(u => u.OrganizationUserId))) ) .Returns(organizationUsers); sutProvider.GetDependency() .GetManyByManyIds( Arg.Is>(ids => ids.SequenceEqual(collectionGroups.Select(u => u.GroupId))) ) .Returns(groups); var exception = await Assert.ThrowsAsync(() => 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().Received().GetManyAsync( Arg.Is>(ids => ids.SequenceEqual(collectionUsers.Select(u => u.OrganizationUserId))) ); await sutProvider.GetDependency().Received().GetManyByManyIds( Arg.Is>(ids => ids.SequenceEqual(collectionGroups.Select(u => u.GroupId))) ); } private static ICollection ToAccessSelection(IEnumerable collectionUsers) { return collectionUsers.Select(cu => new CollectionAccessSelection { Id = cu.OrganizationUserId, Manage = cu.Manage, HidePasswords = cu.HidePasswords, ReadOnly = cu.ReadOnly }).ToList(); } private static ICollection ToAccessSelection(IEnumerable collectionGroups) { return collectionGroups.Select(cg => new CollectionAccessSelection { Id = cg.GroupId, Manage = cg.Manage, HidePasswords = cg.HidePasswords, ReadOnly = cg.ReadOnly }).ToList(); } }