using System.Security.Claims; using Bit.Api.Controllers; using Bit.Api.Models.Request; using Bit.Api.Vault.AuthorizationHandlers.Collections; using Bit.Core; using Bit.Core.AdminConsole.Entities; using Bit.Core.Context; using Bit.Core.Entities; using Bit.Core.Exceptions; using Bit.Core.Models.Data; using Bit.Core.OrganizationFeatures.OrganizationCollections.Interfaces; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Test.AutoFixture; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Microsoft.AspNetCore.Authorization; using NSubstitute; using Xunit; namespace Bit.Api.Test.Controllers; [ControllerCustomize(typeof(CollectionsController))] [SutProviderCustomize] [FeatureServiceCustomize(FeatureFlagKeys.FlexibleCollections)] public class CollectionsControllerTests { [Theory, BitAutoData] public async Task Post_Success(Guid orgId, CollectionRequestModel collectionRequest, SutProvider sutProvider) { Collection ExpectedCollection() => Arg.Is(c => c.Name == collectionRequest.Name && c.ExternalId == collectionRequest.ExternalId && c.OrganizationId == orgId); sutProvider.GetDependency() .AuthorizeAsync(Arg.Any(), ExpectedCollection(), Arg.Is>(r => r.Contains(CollectionOperations.Create))) .Returns(AuthorizationResult.Success()); _ = await sutProvider.Sut.Post(orgId, collectionRequest); await sutProvider.GetDependency() .Received(1) .SaveAsync(Arg.Any(), Arg.Any>(), Arg.Any>()); } [Theory, BitAutoData] public async Task Put_Success(Guid orgId, Guid collectionId, Guid userId, CollectionRequestModel collectionRequest, SutProvider sutProvider) { sutProvider.GetDependency() .ViewAssignedCollections(orgId) .Returns(true); sutProvider.GetDependency() .EditAssignedCollections(orgId) .Returns(true); sutProvider.GetDependency() .UserId .Returns(userId); sutProvider.GetDependency() .GetByIdAsync(collectionId, userId) .Returns(new CollectionDetails { OrganizationId = orgId, }); _ = await sutProvider.Sut.Put(orgId, collectionId, collectionRequest); } [Theory, BitAutoData] public async Task Put_CanNotEditAssignedCollection_ThrowsNotFound(Guid orgId, Guid collectionId, Guid userId, CollectionRequestModel collectionRequest, SutProvider sutProvider) { sutProvider.GetDependency() .EditAssignedCollections(orgId) .Returns(true); sutProvider.GetDependency() .UserId .Returns(userId); sutProvider.GetDependency() .GetByIdAsync(collectionId, userId) .Returns(Task.FromResult(null)); _ = await Assert.ThrowsAsync(async () => await sutProvider.Sut.Put(orgId, collectionId, collectionRequest)); } [Theory, BitAutoData] public async Task GetOrganizationCollectionsWithGroups_NoManagerPermissions_ThrowsNotFound(Organization organization, SutProvider sutProvider) { sutProvider.GetDependency().ViewAssignedCollections(organization.Id).Returns(false); await Assert.ThrowsAsync(() => sutProvider.Sut.GetManyWithDetails(organization.Id)); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyByOrganizationIdWithAccessAsync(default); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyByUserIdWithAccessAsync(default, default); } [Theory, BitAutoData] public async Task GetOrganizationCollectionsWithGroups_AdminPermissions_GetsAllCollections(Organization organization, User user, SutProvider sutProvider) { sutProvider.GetDependency().UserId.Returns(user.Id); sutProvider.GetDependency().ViewAllCollections(organization.Id).Returns(true); sutProvider.GetDependency().OrganizationAdmin(organization.Id).Returns(true); await sutProvider.Sut.GetManyWithDetails(organization.Id); await sutProvider.GetDependency().Received().GetManyByOrganizationIdWithAccessAsync(organization.Id); await sutProvider.GetDependency().Received().GetManyByUserIdWithAccessAsync(user.Id, organization.Id); } [Theory, BitAutoData] public async Task GetOrganizationCollectionsWithGroups_MissingViewAllPermissions_GetsAssignedCollections(Organization organization, User user, SutProvider sutProvider) { sutProvider.GetDependency().UserId.Returns(user.Id); sutProvider.GetDependency().ViewAssignedCollections(organization.Id).Returns(true); sutProvider.GetDependency().OrganizationManager(organization.Id).Returns(true); await sutProvider.Sut.GetManyWithDetails(organization.Id); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyByOrganizationIdWithAccessAsync(default); await sutProvider.GetDependency().Received().GetManyByUserIdWithAccessAsync(user.Id, organization.Id); } [Theory, BitAutoData] public async Task GetOrganizationCollectionsWithGroups_CustomUserWithManagerPermissions_GetsAssignedCollections(Organization organization, User user, SutProvider sutProvider) { sutProvider.GetDependency().UserId.Returns(user.Id); sutProvider.GetDependency().ViewAssignedCollections(organization.Id).Returns(true); sutProvider.GetDependency().EditAssignedCollections(organization.Id).Returns(true); await sutProvider.Sut.GetManyWithDetails(organization.Id); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().GetManyByOrganizationIdWithAccessAsync(default); await sutProvider.GetDependency().Received().GetManyByUserIdWithAccessAsync(user.Id, organization.Id); } [Theory, BitAutoData] public async Task DeleteMany_Success(Guid orgId, Collection collection1, Collection collection2, SutProvider sutProvider) { // Arrange var model = new CollectionBulkDeleteRequestModel { Ids = new[] { collection1.Id, collection2.Id } }; var collections = new List { new CollectionDetails { Id = collection1.Id, OrganizationId = orgId, }, new CollectionDetails { Id = collection2.Id, OrganizationId = orgId, }, }; sutProvider.GetDependency().GetManyByManyIdsAsync(Arg.Any>()) .Returns(collections); sutProvider.GetDependency() .AuthorizeAsync(Arg.Any(), collections, Arg.Is>(r => r.Contains(CollectionOperations.Delete))) .Returns(AuthorizationResult.Success()); // Act await sutProvider.Sut.DeleteMany(orgId, model); // Assert await sutProvider.GetDependency() .Received(1) .DeleteManyAsync(Arg.Is>(coll => coll.Select(c => c.Id).SequenceEqual(collections.Select(c => c.Id)))); } [Theory, BitAutoData] public async Task DeleteMany_PermissionDenied_ThrowsNotFound(Guid orgId, Collection collection1, Collection collection2, SutProvider sutProvider) { // Arrange var model = new CollectionBulkDeleteRequestModel { Ids = new[] { collection1.Id, collection2.Id } }; var collections = new List { new CollectionDetails { Id = collection1.Id, OrganizationId = orgId, }, new CollectionDetails { Id = collection2.Id, OrganizationId = orgId, }, }; sutProvider.GetDependency().GetManyByManyIdsAsync(Arg.Any>()) .Returns(collections); sutProvider.GetDependency() .AuthorizeAsync(Arg.Any(), collections, Arg.Is>(r => r.Contains(CollectionOperations.Delete))) .Returns(AuthorizationResult.Failed()); // Assert await Assert.ThrowsAsync(() => sutProvider.Sut.DeleteMany(orgId, model)); await sutProvider.GetDependency() .DidNotReceiveWithAnyArgs() .DeleteManyAsync((IEnumerable)default); } [Theory, BitAutoData] public async Task PostBulkCollectionAccess_Success(User actingUser, ICollection collections, SutProvider 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() .GetManyByManyIdsAsync(model.CollectionIds) .Returns(collections); sutProvider.GetDependency() .UserId.Returns(actingUser.Id); sutProvider.GetDependency().AuthorizeAsync( Arg.Any(), ExpectedCollectionAccess(), Arg.Is>( r => r.Contains(CollectionOperations.ModifyAccess) )) .Returns(AuthorizationResult.Success()); IEnumerable ExpectedCollectionAccess() => Arg.Is>(cols => cols.SequenceEqual(collections)); // Act await sutProvider.Sut.PostBulkCollectionAccess(model); // Assert await sutProvider.GetDependency().Received().AuthorizeAsync( Arg.Any(), ExpectedCollectionAccess(), Arg.Is>( r => r.Contains(CollectionOperations.ModifyAccess)) ); await sutProvider.GetDependency().Received() .AddAccessAsync( Arg.Is>(g => g.SequenceEqual(collections)), Arg.Is>(u => u.All(c => c.Id == userId && c.Manage)), Arg.Is>(g => g.All(c => c.Id == groupId && c.ReadOnly))); } [Theory, BitAutoData] public async Task PostBulkCollectionAccess_CollectionsNotFound_Throws(User actingUser, ICollection collections, SutProvider 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() .UserId.Returns(actingUser.Id); sutProvider.GetDependency() .GetManyByManyIdsAsync(model.CollectionIds) .Returns(collections.Skip(1).ToList()); var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.PostBulkCollectionAccess(model)); Assert.Equal("One or more collections not found.", exception.Message); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs().AuthorizeAsync( Arg.Any(), Arg.Any>(), Arg.Any>() ); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() .AddAccessAsync(default, default, default); } [Theory, BitAutoData] public async Task PostBulkCollectionAccess_AccessDenied_Throws(User actingUser, ICollection collections, SutProvider 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() .UserId.Returns(actingUser.Id); sutProvider.GetDependency() .GetManyByManyIdsAsync(model.CollectionIds) .Returns(collections); sutProvider.GetDependency().AuthorizeAsync( Arg.Any(), ExpectedCollectionAccess(), Arg.Is>( r => r.Contains(CollectionOperations.ModifyAccess) )) .Returns(AuthorizationResult.Failed()); IEnumerable ExpectedCollectionAccess() => Arg.Is>(cols => cols.SequenceEqual(collections)); await Assert.ThrowsAsync(() => sutProvider.Sut.PostBulkCollectionAccess(model)); await sutProvider.GetDependency().Received().AuthorizeAsync( Arg.Any(), ExpectedCollectionAccess(), Arg.Is>( r => r.Contains(CollectionOperations.ModifyAccess)) ); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() .AddAccessAsync(default, default, default); } }