using System; using System.Collections.Generic; using System.Threading.Tasks; using Bit.Core.Models.Data; using Bit.Core.Models.Table; using Bit.Core.Models.Business; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; using Microsoft.AspNetCore.DataProtection; using NSubstitute; using Xunit; using Bit.Core.Test.AutoFixture; using Bit.Core.Exceptions; using Bit.Core.Enums; using Bit.Core.Test.AutoFixture.Attributes; using Bit.Core.Test.AutoFixture.OrganizationFixtures; using System.Text.Json; namespace Bit.Core.Test.Services { public class OrganizationServiceTests { [Fact] public async Task OrgImportCreateNewUsers() { var orgRepo = Substitute.For(); var orgUserRepo = Substitute.For(); var collectionRepo = Substitute.For(); var userRepo = Substitute.For(); var groupRepo = Substitute.For(); var dataProtector = Substitute.For(); var mailService = Substitute.For(); var pushNotService = Substitute.For(); var pushRegService = Substitute.For(); var deviceRepo = Substitute.For(); var licenseService = Substitute.For(); var eventService = Substitute.For(); var installationRepo = Substitute.For(); var appCacheService = Substitute.For(); var paymentService = Substitute.For(); var policyRepo = Substitute.For(); var ssoConfigRepo = Substitute.For(); var ssoUserRepo = Substitute.For(); var referenceEventService = Substitute.For(); var globalSettings = Substitute.For(); var taxRateRepository = Substitute.For(); var orgService = new OrganizationService(orgRepo, orgUserRepo, collectionRepo, userRepo, groupRepo, dataProtector, mailService, pushNotService, pushRegService, deviceRepo, licenseService, eventService, installationRepo, appCacheService, paymentService, policyRepo, ssoConfigRepo, ssoUserRepo, referenceEventService, globalSettings, taxRateRepository); var id = Guid.NewGuid(); var userId = Guid.NewGuid(); var org = new Organization { Id = id, Name = "Test Org", UseDirectory = true, UseGroups = true, Seats = 3 }; orgRepo.GetByIdAsync(id).Returns(org); var existingUsers = new List(); existingUsers.Add(new OrganizationUserUserDetails { Id = Guid.NewGuid(), ExternalId = "a", Email = "a@test.com" }); orgUserRepo.GetManyDetailsByOrganizationAsync(id).Returns(existingUsers); orgUserRepo.GetCountByOrganizationIdAsync(id).Returns(1); var newUsers = new List(); newUsers.Add(new Models.Business.ImportedOrganizationUser { Email = "a@test.com", ExternalId = "a" }); newUsers.Add(new Models.Business.ImportedOrganizationUser { Email = "b@test.com", ExternalId = "b" }); newUsers.Add(new Models.Business.ImportedOrganizationUser { Email = "c@test.com", ExternalId = "c" }); await orgService.ImportAsync(id, userId, null, newUsers, null, false); await orgUserRepo.DidNotReceive().UpsertAsync(Arg.Any()); await orgUserRepo.Received(2).CreateAsync(Arg.Any()); } [Fact] public async Task OrgImportCreateNewUsersAndMarryExistingUser() { var orgRepo = Substitute.For(); var orgUserRepo = Substitute.For(); var collectionRepo = Substitute.For(); var userRepo = Substitute.For(); var groupRepo = Substitute.For(); var dataProtector = Substitute.For(); var mailService = Substitute.For(); var pushNotService = Substitute.For(); var pushRegService = Substitute.For(); var deviceRepo = Substitute.For(); var licenseService = Substitute.For(); var eventService = Substitute.For(); var installationRepo = Substitute.For(); var appCacheService = Substitute.For(); var paymentService = Substitute.For(); var policyRepo = Substitute.For(); var ssoConfigRepo = Substitute.For(); var ssoUserRepo = Substitute.For(); var referenceEventService = Substitute.For(); var globalSettings = Substitute.For(); var taxRateRepo = Substitute.For(); var orgService = new OrganizationService(orgRepo, orgUserRepo, collectionRepo, userRepo, groupRepo, dataProtector, mailService, pushNotService, pushRegService, deviceRepo, licenseService, eventService, installationRepo, appCacheService, paymentService, policyRepo, ssoConfigRepo, ssoUserRepo, referenceEventService, globalSettings, taxRateRepo); var id = Guid.NewGuid(); var userId = Guid.NewGuid(); var org = new Organization { Id = id, Name = "Test Org", UseDirectory = true, UseGroups = true, Seats = 3 }; orgRepo.GetByIdAsync(id).Returns(org); var existingUserAId = Guid.NewGuid(); var existingUsers = new List(); existingUsers.Add(new OrganizationUserUserDetails { Id = existingUserAId, // No external id here Email = "a@test.com" }); orgUserRepo.GetManyDetailsByOrganizationAsync(id).Returns(existingUsers); orgUserRepo.GetCountByOrganizationIdAsync(id).Returns(1); orgUserRepo.GetByIdAsync(existingUserAId).Returns(new OrganizationUser { Id = existingUserAId }); var newUsers = new List(); newUsers.Add(new Models.Business.ImportedOrganizationUser { Email = "a@test.com", ExternalId = "a" }); newUsers.Add(new Models.Business.ImportedOrganizationUser { Email = "b@test.com", ExternalId = "b" }); newUsers.Add(new Models.Business.ImportedOrganizationUser { Email = "c@test.com", ExternalId = "c" }); await orgService.ImportAsync(id, userId, null, newUsers, null, false); await orgUserRepo.Received(1).UpsertAsync(Arg.Any()); await orgUserRepo.Received(2).CreateAsync(Arg.Any()); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task UpgradePlan_OrganizationIsNull_Throws(Guid organizationId, OrganizationUpgrade upgrade, SutProvider sutProvider) { sutProvider.GetDependency().GetByIdAsync(organizationId).Returns(Task.FromResult(null)); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.UpgradePlanAsync(organizationId, upgrade)); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task UpgradePlan_GatewayCustomIdIsNull_Throws(Organization organization, OrganizationUpgrade upgrade, SutProvider sutProvider) { organization.GatewayCustomerId = string.Empty; sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade)); Assert.Contains("no payment method", exception.Message); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task UpgradePlan_AlreadyInPlan_Throws(Organization organization, OrganizationUpgrade upgrade, SutProvider sutProvider) { upgrade.Plan = organization.PlanType; sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade)); Assert.Contains("already on this plan", exception.Message); } [Theory, PaidOrganizationAutoData] public async Task UpgradePlan_UpgradeFromPaidPlan_Throws(Organization organization, OrganizationUpgrade upgrade, SutProvider sutProvider) { sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade)); Assert.Contains("can only upgrade", exception.Message); } [Theory] [FreeOrganizationUpgradeAutoData] public async Task UpgradePlan_Passes(Organization organization, OrganizationUpgrade upgrade, SutProvider sutProvider) { sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); await sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade); await sutProvider.GetDependency().Received(1).ReplaceAsync(organization); } [Theory] [OrganizationInviteAutoData] public async Task InviteUser_NoEmails_Throws(Organization organization, OrganizationUser invitor, OrganizationUserInvite invite, SutProvider sutProvider) { invite.Emails = null; sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); await Assert.ThrowsAsync( () => sutProvider.Sut.InviteUserAsync(organization.Id, invitor.UserId, null, invite)); } [Theory] [OrganizationInviteAutoData( inviteeUserType: (int)OrganizationUserType.Owner, invitorUserType: (int)OrganizationUserType.Admin )] public async Task InviteUser_NonOwnerConfiguringOwner_Throws(Organization organization, OrganizationUserInvite invite, OrganizationUser invitor, SutProvider sutProvider) { var organizationRepository = sutProvider.GetDependency(); var organizationUserRepository = sutProvider.GetDependency(); organizationRepository.GetByIdAsync(organization.Id).Returns(organization); organizationUserRepository.GetManyByUserAsync(invitor.Id).Returns(new List { invitor }); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.InviteUserAsync(organization.Id, invitor.UserId, null, invite)); Assert.Contains("only an owner", exception.Message.ToLowerInvariant()); } [Theory] [OrganizationInviteAutoData( inviteeUserType: (int)OrganizationUserType.Custom, invitorUserType: (int)OrganizationUserType.Admin )] public async Task InviteUser_NonAdminConfiguringAdmin_Throws(Organization organization, OrganizationUserInvite invite, OrganizationUser invitor, SutProvider sutProvider) { var organizationRepository = sutProvider.GetDependency(); var organizationUserRepository = sutProvider.GetDependency(); organizationRepository.GetByIdAsync(organization.Id).Returns(organization); organizationUserRepository.GetManyByUserAsync(invitor.Id).Returns(new List { invitor }); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.InviteUserAsync(organization.Id, invitor.UserId, null, invite)); Assert.Contains("only owners and admins", exception.Message.ToLowerInvariant()); } [Theory] [OrganizationInviteAutoData( inviteeUserType: (int)OrganizationUserType.Manager, invitorUserType: (int)OrganizationUserType.Custom )] public async Task InviteUser_CustomUserWithoutManageUsersConfiguringUser_Throws(Organization organization, OrganizationUserInvite invite, OrganizationUser invitor, SutProvider sutProvider) { invitor.Permissions = JsonSerializer.Serialize(new Permissions() { ManageUsers = false }, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, }); var organizationRepository = sutProvider.GetDependency(); var organizationUserRepository = sutProvider.GetDependency(); organizationRepository.GetByIdAsync(organization.Id).Returns(organization); organizationUserRepository.GetManyByUserAsync(invitor.UserId.Value).Returns(new List { invitor }); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.InviteUserAsync(organization.Id, invitor.UserId, null, invite)); Assert.Contains("account does not have permission", exception.Message.ToLowerInvariant()); } [Theory] [OrganizationInviteAutoData( inviteeUserType: (int)OrganizationUserType.Admin, invitorUserType: (int)OrganizationUserType.Custom )] public async Task InviteUser_CustomUserConfiguringAdmin_Throws(Organization organization, OrganizationUserInvite invite, OrganizationUser invitor, SutProvider sutProvider) { invitor.Permissions = JsonSerializer.Serialize(new Permissions() { ManageUsers = true }, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, }); var organizationRepository = sutProvider.GetDependency(); var organizationUserRepository = sutProvider.GetDependency(); organizationRepository.GetByIdAsync(organization.Id).Returns(organization); organizationUserRepository.GetManyByUserAsync(invitor.UserId.Value).Returns(new List { invitor }); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.InviteUserAsync(organization.Id, invitor.UserId, null, invite)); Assert.Contains("can not manage admins", exception.Message.ToLowerInvariant()); } [Theory] [OrganizationInviteAutoData( inviteeUserType: (int)OrganizationUserType.User, invitorUserType: (int)OrganizationUserType.Owner )] public async Task InviteUser_NoPermissionsObject_Passes(Organization organization, OrganizationUserInvite invite, OrganizationUser invitor, SutProvider sutProvider) { invite.Permissions = null; var organizationRepository = sutProvider.GetDependency(); var organizationUserRepository = sutProvider.GetDependency(); var eventService = sutProvider.GetDependency(); organizationRepository.GetByIdAsync(organization.Id).Returns(organization); organizationUserRepository.GetManyByUserAsync(invitor.UserId.Value).Returns(new List { invitor }); await sutProvider.Sut.InviteUserAsync(organization.Id, invitor.UserId, null, invite); } [Theory] [OrganizationInviteAutoData( inviteeUserType: (int)OrganizationUserType.User, invitorUserType: (int)OrganizationUserType.Custom )] public async Task InviteUser_Passes(Organization organization, OrganizationUserInvite invite, OrganizationUser invitor, SutProvider sutProvider) { invitor.Permissions = JsonSerializer.Serialize(new Permissions() { ManageUsers = true }, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, }); var organizationRepository = sutProvider.GetDependency(); var organizationUserRepository = sutProvider.GetDependency(); var eventService = sutProvider.GetDependency(); organizationRepository.GetByIdAsync(organization.Id).Returns(organization); organizationUserRepository.GetManyByUserAsync(invitor.UserId.Value).Returns(new List { invitor }); await sutProvider.Sut.InviteUserAsync(organization.Id, invitor.UserId, null, invite); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task SaveUser_NoUserId_Throws(OrganizationUser user, Guid? savingUserId, IEnumerable collections, SutProvider sutProvider) { user.Id = default(Guid); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.SaveUserAsync(user, savingUserId, collections)); Assert.Contains("invite the user first", exception.Message.ToLowerInvariant()); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task SaveUser_NoChangeToData_Throws(OrganizationUser user, Guid? savingUserId, IEnumerable collections, SutProvider sutProvider) { var organizationUserRepository = sutProvider.GetDependency(); organizationUserRepository.GetByIdAsync(user.Id).Returns(user); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.SaveUserAsync(user, savingUserId, collections)); Assert.Contains("make changes before saving", exception.Message.ToLowerInvariant()); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task SaveUser_Passes(OrganizationUser oldUserData, OrganizationUser newUserData, IEnumerable collections, OrganizationUser savingUser, SutProvider sutProvider) { var organizationUserRepository = sutProvider.GetDependency(); newUserData.Id = oldUserData.Id; newUserData.UserId = oldUserData.UserId; newUserData.OrganizationId = savingUser.OrganizationId = oldUserData.OrganizationId; savingUser.Type = OrganizationUserType.Owner; organizationUserRepository.GetByIdAsync(oldUserData.Id).Returns(oldUserData); organizationUserRepository.GetManyByUserAsync(savingUser.UserId.Value).Returns(new List { savingUser }); await sutProvider.Sut.SaveUserAsync(newUserData, savingUser.UserId, collections); } } }