using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Bit.Core.Enums; using Bit.Core.Enums.Provider; using Bit.Core.Exceptions; using Bit.Core.Models.Business.Provider; using Bit.Core.Models.Table; using Bit.Core.Models.Table.Provider; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Test.AutoFixture; using Bit.Core.Test.AutoFixture.Attributes; using Bit.Core.Test.AutoFixture.ProviderUserFixtures; using Bit.Core.Utilities; using Microsoft.AspNetCore.DataProtection; using NSubstitute; using Xunit; using ProviderUser = Bit.Core.Models.Table.Provider.ProviderUser; namespace Bit.Core.Test.Services { public class ProviderServiceTests { [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task CreateAsync_UserIdIsInvalid_Throws(SutProvider sutProvider) { var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.CreateAsync(default)); Assert.Contains("Invalid owner.", exception.Message); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task CreateAsync_Success(User user, SutProvider sutProvider) { var userRepository = sutProvider.GetDependency(); userRepository.GetByEmailAsync(user.Email).Returns(user); await sutProvider.Sut.CreateAsync(user.Email); await sutProvider.GetDependency().ReceivedWithAnyArgs().CreateAsync(default); await sutProvider.GetDependency().ReceivedWithAnyArgs().SendProviderSetupInviteEmailAsync(default, default, default); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task CompleteSetupAsync_UserIdIsInvalid_Throws(SutProvider sutProvider) { var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.CompleteSetupAsync(default, default, default, default)); Assert.Contains("Invalid owner.", exception.Message); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task CompleteSetupAsync_TokenIsInvalid_Throws(User user, Provider provider, SutProvider sutProvider) { var userService = sutProvider.GetDependency(); userService.GetUserByIdAsync(user.Id).Returns(user); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.CompleteSetupAsync(provider, user.Id, default, default)); Assert.Contains("Invalid token.", exception.Message); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task CompleteSetupAsync_Success(User user, Provider provider, SutProvider sutProvider) { var userService = sutProvider.GetDependency(); userService.GetUserByIdAsync(user.Id).Returns(user); var dataProtectionProvider = DataProtectionProvider.Create("ApplicationName"); var protector = dataProtectionProvider.CreateProtector("ProviderServiceDataProtector"); sutProvider.GetDependency().CreateProtector("ProviderServiceDataProtector") .Returns(protector); sutProvider.Create(); var token = protector.Protect($"ProviderSetupInvite {provider.Id} {user.Email} {CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow)}"); await sutProvider.Sut.CompleteSetupAsync(provider, user.Id, token, default); await sutProvider.GetDependency().Received().UpsertAsync(provider); await sutProvider.GetDependency().Received() .CreateAsync(Arg.Is(pu => pu.UserId == user.Id && pu.ProviderId == provider.Id)); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task UpdateAsync_ProviderIdIsInvalid_Throws(Provider provider, SutProvider sutProvider) { provider.Id = default; var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.UpdateAsync(provider)); Assert.Contains("Cannot create provider this way.", exception.Message); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task UpdateAsync_Success(Provider provider, SutProvider sutProvider) { await sutProvider.Sut.UpdateAsync(provider); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task InviteUserAsync_ProviderIdIsInvalid_Throws(Provider provider, SutProvider sutProvider) { provider.Id = default; await Assert.ThrowsAsync(() => sutProvider.Sut.InviteUserAsync(provider.Id, default, default)); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task InviteUserAsync_EmailsInvalid_Throws(Provider provider, ProviderUserInvite providerUserInvite, SutProvider sutProvider) { var providerRepository = sutProvider.GetDependency(); providerRepository.GetByIdAsync(provider.Id).Returns(provider); providerUserInvite.Emails = null; await Assert.ThrowsAsync(() => sutProvider.Sut.InviteUserAsync(provider.Id, default, providerUserInvite)); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task InviteUserAsync_AlreadyInvited(Provider provider, ProviderUserInvite providerUserInvite, SutProvider sutProvider) { var providerRepository = sutProvider.GetDependency(); providerRepository.GetByIdAsync(provider.Id).Returns(provider); var providerUserRepository = sutProvider.GetDependency(); providerUserRepository.GetCountByProviderAsync(default, default, default).ReturnsForAnyArgs(1); var result = await sutProvider.Sut.InviteUserAsync(provider.Id, default, providerUserInvite); Assert.Empty(result); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task InviteUserAsync_Success(Provider provider, ProviderUserInvite providerUserInvite, SutProvider sutProvider) { var providerRepository = sutProvider.GetDependency(); providerRepository.GetByIdAsync(provider.Id).Returns(provider); var providerUserRepository = sutProvider.GetDependency(); providerUserRepository.GetCountByProviderAsync(default, default, default).ReturnsForAnyArgs(0); var result = await sutProvider.Sut.InviteUserAsync(provider.Id, default, providerUserInvite); Assert.Equal(providerUserInvite.Emails.Count(), result.Count); Assert.True(result.TrueForAll(pu => pu.Status == ProviderUserStatusType.Invited), "Status must be invited"); Assert.True(result.TrueForAll(pu => pu.ProviderId == provider.Id), "Provider Id must be correct"); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task ResendInvitesAsync_Errors(Provider provider, [ProviderUser(ProviderUserStatusType.Invited)]ProviderUser pu1, [ProviderUser(ProviderUserStatusType.Accepted)]ProviderUser pu2, [ProviderUser(ProviderUserStatusType.Confirmed)]ProviderUser pu3, [ProviderUser(ProviderUserStatusType.Invited)]ProviderUser pu4, SutProvider sutProvider) { var providerUsers = new[] {pu1, pu2, pu3, pu4}; pu1.ProviderId = pu2.ProviderId = pu3.ProviderId = provider.Id; var providerRepository = sutProvider.GetDependency(); providerRepository.GetByIdAsync(provider.Id).Returns(provider); var providerUserRepository = sutProvider.GetDependency(); providerUserRepository.GetManyAsync(default).ReturnsForAnyArgs(providerUsers.ToList()); var result = await sutProvider.Sut.ResendInvitesAsync(provider.Id, default, providerUsers.Select(pu => pu.Id)); Assert.Equal("", result[0].Item2); Assert.Equal("User invalid.", result[1].Item2); Assert.Equal("User invalid.", result[2].Item2); Assert.Equal("User invalid.", result[3].Item2); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task ResendInvitesAsync_Success(Provider provider, IEnumerable providerUsers, SutProvider sutProvider) { foreach (var providerUser in providerUsers) { providerUser.ProviderId = provider.Id; providerUser.Status = ProviderUserStatusType.Invited; } var providerRepository = sutProvider.GetDependency(); providerRepository.GetByIdAsync(provider.Id).Returns(provider); var providerUserRepository = sutProvider.GetDependency(); providerUserRepository.GetManyAsync(default).ReturnsForAnyArgs(providerUsers.ToList()); var result = await sutProvider.Sut.ResendInvitesAsync(provider.Id, default, providerUsers.Select(pu => pu.Id)); Assert.True(result.All(r => r.Item2 == "")); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task AcceptUserAsync_UserIsInvalid_Throws(ProviderUser providerUser, User user, SutProvider sutProvider) { var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.AcceptUserAsync(default, default, default)); Assert.Equal("User invalid.", exception.Message); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task AcceptUserAsync_AlreadyAccepted_Throws( [ProviderUser(ProviderUserStatusType.Accepted)]ProviderUser providerUser, User user, SutProvider sutProvider) { var providerUserRepository = sutProvider.GetDependency(); providerUserRepository.GetByIdAsync(providerUser.Id).Returns(providerUser); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.AcceptUserAsync(providerUser.Id, user, default)); Assert.Equal("Already accepted.", exception.Message); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task AcceptUserAsync_TokenIsInvalid_Throws( [ProviderUser(ProviderUserStatusType.Invited)]ProviderUser providerUser, User user, SutProvider sutProvider) { var providerUserRepository = sutProvider.GetDependency(); providerUserRepository.GetByIdAsync(providerUser.Id).Returns(providerUser); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.AcceptUserAsync(providerUser.Id, user, default)); Assert.Equal("Invalid token.", exception.Message); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task AcceptUserAsync_WrongEmail_Throws( [ProviderUser(ProviderUserStatusType.Invited)]ProviderUser providerUser, User user, SutProvider sutProvider) { var providerUserRepository = sutProvider.GetDependency(); providerUserRepository.GetByIdAsync(providerUser.Id).Returns(providerUser); var dataProtectionProvider = DataProtectionProvider.Create("ApplicationName"); var protector = dataProtectionProvider.CreateProtector("ProviderServiceDataProtector"); sutProvider.GetDependency().CreateProtector("ProviderServiceDataProtector") .Returns(protector); sutProvider.Create(); var token = protector.Protect($"ProviderUserInvite {providerUser.Id} {user.Email} {CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow)}"); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.AcceptUserAsync(providerUser.Id, user, token)); Assert.Equal("User email does not match invite.", exception.Message); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task AcceptUserAsync_Success( [ProviderUser(ProviderUserStatusType.Invited)]ProviderUser providerUser, User user, SutProvider sutProvider) { var providerUserRepository = sutProvider.GetDependency(); providerUserRepository.GetByIdAsync(providerUser.Id).Returns(providerUser); var dataProtectionProvider = DataProtectionProvider.Create("ApplicationName"); var protector = dataProtectionProvider.CreateProtector("ProviderServiceDataProtector"); sutProvider.GetDependency().CreateProtector("ProviderServiceDataProtector") .Returns(protector); sutProvider.Create(); providerUser.Email = user.Email; var token = protector.Protect($"ProviderUserInvite {providerUser.Id} {user.Email} {CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow)}"); var pu = await sutProvider.Sut.AcceptUserAsync(providerUser.Id, user, token); Assert.Null(pu.Email); Assert.Equal(ProviderUserStatusType.Accepted, pu.Status); Assert.Equal(user.Id, pu.UserId); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task ConfirmUsersAsync_NoValid( [ProviderUser(ProviderUserStatusType.Invited)]ProviderUser pu1, [ProviderUser(ProviderUserStatusType.Accepted)]ProviderUser pu2, [ProviderUser(ProviderUserStatusType.Confirmed)]ProviderUser pu3, SutProvider sutProvider) { pu1.ProviderId = pu3.ProviderId; var providerUsers = new[] {pu1, pu2, pu3}; var providerUserRepository = sutProvider.GetDependency(); providerUserRepository.GetManyAsync(default).ReturnsForAnyArgs(providerUsers); var dict = providerUsers.ToDictionary(pu => pu.Id, _ => "key"); var result = await sutProvider.Sut.ConfirmUsersAsync(pu1.ProviderId, dict, default); Assert.Empty(result); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task ConfirmUsersAsync_Success( [ProviderUser(ProviderUserStatusType.Invited)]ProviderUser pu1, User u1, [ProviderUser(ProviderUserStatusType.Accepted)]ProviderUser pu2, User u2, [ProviderUser(ProviderUserStatusType.Confirmed)]ProviderUser pu3, User u3, Provider provider, User user, SutProvider sutProvider) { pu1.ProviderId = pu2.ProviderId = pu3.ProviderId = provider.Id; pu1.UserId = u1.Id; pu2.UserId = u2.Id; pu3.UserId = u3.Id; var providerUsers = new[] {pu1, pu2, pu3}; var providerUserRepository = sutProvider.GetDependency(); providerUserRepository.GetManyAsync(default).ReturnsForAnyArgs(providerUsers); var providerRepository = sutProvider.GetDependency(); providerRepository.GetByIdAsync(provider.Id).Returns(provider); var userRepository = sutProvider.GetDependency(); userRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] {u1, u2, u3}); var dict = providerUsers.ToDictionary(pu => pu.Id, _ => "key"); var result = await sutProvider.Sut.ConfirmUsersAsync(pu1.ProviderId, dict, user.Id); Assert.Equal("Invalid user.", result[0].Item2); Assert.Equal("", result[1].Item2); Assert.Equal("Invalid user.", result[2].Item2); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task SaveUserAsync_UserIdIsInvalid_Throws(ProviderUser providerUser, SutProvider sutProvider) { providerUser.Id = default; var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.SaveUserAsync(providerUser, default)); Assert.Equal("Invite the user first.", exception.Message); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task SaveUserAsync_Success( [ProviderUser(type: ProviderUserType.ProviderAdmin)]ProviderUser providerUser, User savingUser, SutProvider sutProvider) { var providerUserRepository = sutProvider.GetDependency(); providerUserRepository.GetByIdAsync(providerUser.Id).Returns(providerUser); await sutProvider.Sut.SaveUserAsync(providerUser, savingUser.Id); await providerUserRepository.Received().ReplaceAsync(providerUser); await sutProvider.GetDependency().Received() .LogProviderUserEventAsync(providerUser, EventType.ProviderUser_Updated, null); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task DeleteUsersAsync_NoRemainingOwner_Throws(Provider provider, User deletingUser, ICollection providerUsers, SutProvider sutProvider) { var userIds = providerUsers.Select(pu => pu.Id); providerUsers.First().UserId = deletingUser.Id; foreach (var providerUser in providerUsers) { providerUser.ProviderId = provider.Id; } providerUsers.Last().ProviderId = default; var providerUserRepository = sutProvider.GetDependency(); providerUserRepository.GetManyAsync(default).ReturnsForAnyArgs(providerUsers); providerUserRepository.GetManyByProviderAsync(default, default).ReturnsForAnyArgs(new ProviderUser[] {}); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.DeleteUsersAsync(provider.Id, userIds, deletingUser.Id)); Assert.Equal("Provider must have at least one confirmed ProviderAdmin.", exception.Message); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task DeleteUsersAsync_Success(Provider provider, User deletingUser, ICollection providerUsers, [ProviderUser(ProviderUserStatusType.Confirmed, ProviderUserType.ProviderAdmin)]ProviderUser remainingOwner, SutProvider sutProvider) { var userIds = providerUsers.Select(pu => pu.Id); providerUsers.First().UserId = deletingUser.Id; foreach (var providerUser in providerUsers) { providerUser.ProviderId = provider.Id; } providerUsers.Last().ProviderId = default; var providerUserRepository = sutProvider.GetDependency(); providerUserRepository.GetManyAsync(default).ReturnsForAnyArgs(providerUsers); providerUserRepository.GetManyByProviderAsync(default, default).ReturnsForAnyArgs(new[] {remainingOwner}); var result = await sutProvider.Sut.DeleteUsersAsync(provider.Id, userIds, deletingUser.Id); Assert.NotEmpty(result); Assert.Equal("You cannot remove yourself.", result[0].Item2); Assert.Equal("", result[1].Item2); Assert.Equal("Invalid user.", result[2].Item2); } } }