using System; using System.Collections.Generic; using System.Linq; 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 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; using Organization = Bit.Core.Models.Table.Organization; using System.Linq; namespace Bit.Core.Test.Services { public class OrganizationServiceTests { // [Fact] [Theory, PaidOrganizationAutoData] public async Task OrgImportCreateNewUsers(SutProvider sutProvider, Guid userId, Organization org, List existingUsers, List newUsers) { org.UseDirectory = true; newUsers.Add(new ImportedOrganizationUser { Email = existingUsers.First().Email, ExternalId = existingUsers.First().ExternalId }); var expectedNewUsersCount = newUsers.Count - 1; sutProvider.GetDependency().GetByIdAsync(org.Id).Returns(org); sutProvider.GetDependency().GetManyDetailsByOrganizationAsync(org.Id) .Returns(existingUsers); sutProvider.GetDependency().GetCountByOrganizationIdAsync(org.Id) .Returns(existingUsers.Count); await sutProvider.Sut.ImportAsync(org.Id, userId, null, newUsers, null, false); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() .UpsertAsync(default); await sutProvider.GetDependency().Received(1) .UpsertManyAsync(Arg.Is>(users => users.Count() == 0)); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() .CreateAsync(default); // Create new users await sutProvider.GetDependency().Received(1) .CreateManyAsync(Arg.Is>(users => users.Count() == expectedNewUsersCount)); await sutProvider.GetDependency().Received(1) .BulkSendOrganizationInviteEmailAsync(org.Name, Arg.Is>(messages => messages.Count() == expectedNewUsersCount)); // Send events await sutProvider.GetDependency().Received(1) .LogOrganizationUserEventsAsync(Arg.Is>(events => events.Count() == expectedNewUsersCount)); await sutProvider.GetDependency().Received(1) .RaiseEventAsync(Arg.Is(referenceEvent => referenceEvent.Type == ReferenceEventType.InvitedUsers && referenceEvent.Id == org.Id && referenceEvent.Users == expectedNewUsersCount)); } [Theory, PaidOrganizationAutoData] public async Task OrgImportCreateNewUsersAndMarryExistingUser(SutProvider sutProvider, Guid userId, Organization org, List existingUsers, List newUsers) { org.UseDirectory = true; var reInvitedUser = existingUsers.First(); reInvitedUser.ExternalId = null; newUsers.Add(new ImportedOrganizationUser { Email = reInvitedUser.Email, ExternalId = reInvitedUser.Email, }); var expectedNewUsersCount = newUsers.Count - 1; sutProvider.GetDependency().GetByIdAsync(org.Id).Returns(org); sutProvider.GetDependency().GetManyDetailsByOrganizationAsync(org.Id) .Returns(existingUsers); sutProvider.GetDependency().GetCountByOrganizationIdAsync(org.Id) .Returns(existingUsers.Count); sutProvider.GetDependency().GetByIdAsync(reInvitedUser.Id) .Returns(new OrganizationUser { Id = reInvitedUser.Id }); await sutProvider.Sut.ImportAsync(org.Id, userId, null, newUsers, null, false); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() .UpsertAsync(default); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() .CreateAsync(default); await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() .CreateAsync(default, default); // Upserted existing user await sutProvider.GetDependency().Received(1) .UpsertManyAsync(Arg.Is>(users => users.Count() == 1)); // Created and invited new users await sutProvider.GetDependency().Received(1) .CreateManyAsync(Arg.Is>(users => users.Count() == expectedNewUsersCount)); await sutProvider.GetDependency().Received(1) .BulkSendOrganizationInviteEmailAsync(org.Name, Arg.Is>(messages => messages.Count() == expectedNewUsersCount)); // Sent events await sutProvider.GetDependency().Received(1) .LogOrganizationUserEventsAsync(Arg.Is>(events => events.Where(e => e.Item2 == EventType.OrganizationUser_Invited).Count() == expectedNewUsersCount)); await sutProvider.GetDependency().Received(1) .RaiseEventAsync(Arg.Is(referenceEvent => referenceEvent.Type == ReferenceEventType.InvitedUsers && referenceEvent.Id == org.Id && referenceEvent.Users == expectedNewUsersCount)); } [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.GetManyByOrganizationAsync(savingUser.OrganizationId, OrganizationUserType.Owner) .Returns(new List { savingUser }); organizationUserRepository.GetManyByUserAsync(savingUser.UserId.Value).Returns(new List { savingUser }); await sutProvider.Sut.SaveUserAsync(newUserData, savingUser.UserId, collections); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task DeleteUser_InvalidUser(OrganizationUser organizationUser, OrganizationUser deletingUser, SutProvider sutProvider) { var organizationUserRepository = sutProvider.GetDependency(); organizationUserRepository.GetByIdAsync(organizationUser.Id).Returns(organizationUser); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.DeleteUserAsync(Guid.NewGuid(), organizationUser.Id, deletingUser.UserId)); Assert.Contains("User not valid.", exception.Message); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task DeleteUser_RemoveYourself(OrganizationUser deletingUser, SutProvider sutProvider) { var organizationUserRepository = sutProvider.GetDependency(); organizationUserRepository.GetByIdAsync(deletingUser.Id).Returns(deletingUser); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.DeleteUserAsync(deletingUser.OrganizationId, deletingUser.Id, deletingUser.UserId)); Assert.Contains("You cannot remove yourself.", exception.Message); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task DeleteUser_NonOwnerRemoveOwner(OrganizationUser organizationUser, OrganizationUser deletingUser, SutProvider sutProvider) { var organizationUserRepository = sutProvider.GetDependency(); organizationUser.OrganizationId = deletingUser.OrganizationId; organizationUser.Type = OrganizationUserType.Owner; organizationUserRepository.GetByIdAsync(organizationUser.Id).Returns(organizationUser); organizationUserRepository.GetManyByUserAsync(deletingUser.UserId.Value).Returns(new[] { deletingUser }); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.DeleteUserAsync(deletingUser.OrganizationId, organizationUser.Id, deletingUser.UserId)); Assert.Contains("Only owners can delete other owners.", exception.Message); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task DeleteUser_LastOwner(OrganizationUser organizationUser, OrganizationUser deletingUser, SutProvider sutProvider) { var organizationUserRepository = sutProvider.GetDependency(); organizationUser.OrganizationId = deletingUser.OrganizationId; organizationUser.Type = OrganizationUserType.Owner; organizationUserRepository.GetByIdAsync(organizationUser.Id).Returns(organizationUser); organizationUserRepository.GetManyByOrganizationAsync(deletingUser.OrganizationId, OrganizationUserType.Owner) .Returns(new[] { organizationUser }); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.DeleteUserAsync(deletingUser.OrganizationId, organizationUser.Id, null)); Assert.Contains("Organization must have at least one confirmed owner.", exception.Message); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task DeleteUser_Success(OrganizationUser organizationUser, OrganizationUser deletingUser, SutProvider sutProvider) { var organizationUserRepository = sutProvider.GetDependency(); deletingUser.Type = OrganizationUserType.Owner; deletingUser.Status = OrganizationUserStatusType.Confirmed; organizationUser.OrganizationId = deletingUser.OrganizationId; organizationUserRepository.GetByIdAsync(organizationUser.Id).Returns(organizationUser); organizationUserRepository.GetByIdAsync(deletingUser.Id).Returns(deletingUser); organizationUserRepository.GetManyByUserAsync(deletingUser.UserId.Value).Returns(new[] { deletingUser }); organizationUserRepository.GetManyByOrganizationAsync(deletingUser.OrganizationId, OrganizationUserType.Owner) .Returns(new[] {deletingUser, organizationUser}); await sutProvider.Sut.DeleteUserAsync(deletingUser.OrganizationId, organizationUser.Id, deletingUser.UserId); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task DeleteUsers_FilterInvalid(OrganizationUser organizationUser, OrganizationUser deletingUser, SutProvider sutProvider) { var organizationUserRepository = sutProvider.GetDependency(); var organizationUsers = new[] { organizationUser }; var organizationUserIds = organizationUsers.Select(u => u.Id); organizationUserRepository.GetManyAsync(organizationUserIds).Returns(organizationUsers); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.DeleteUsersAsync(deletingUser.OrganizationId, organizationUserIds, deletingUser.UserId)); Assert.Contains("Users invalid.", exception.Message); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task DeleteUsers_RemoveYourself(OrganizationUser deletingUser, SutProvider sutProvider) { var organizationUserRepository = sutProvider.GetDependency(); var organizationUsers = new[] { deletingUser }; var organizationUserIds = organizationUsers.Select(u => u.Id); organizationUserRepository.GetManyAsync(organizationUserIds).Returns(organizationUsers); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.DeleteUsersAsync(deletingUser.OrganizationId, organizationUserIds, deletingUser.UserId)); Assert.Contains("You cannot remove yourself.", exception.Message); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task DeleteUsers_NonOwnerRemoveOwner(OrganizationUser deletingUser, OrganizationUser orgUser1, OrganizationUser orgUser2, SutProvider sutProvider) { var organizationUserRepository = sutProvider.GetDependency(); deletingUser.Type = OrganizationUserType.Admin; orgUser1.OrganizationId = orgUser2.OrganizationId = deletingUser.OrganizationId; var organizationUsers = new[] { orgUser1, orgUser2 }; var organizationUserIds = organizationUsers.Select(u => u.Id); organizationUserRepository.GetManyAsync(organizationUserIds).Returns(organizationUsers); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.DeleteUsersAsync(deletingUser.OrganizationId, organizationUserIds, deletingUser.UserId)); Assert.Contains("Only owners can delete other owners.", exception.Message); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task DeleteUsers_LastOwner(OrganizationUser orgUser, SutProvider sutProvider) { var organizationUserRepository = sutProvider.GetDependency(); orgUser.Type = OrganizationUserType.Owner; orgUser.Status = OrganizationUserStatusType.Confirmed; var organizationUsers = new[] { orgUser }; var organizationUserIds = organizationUsers.Select(u => u.Id); organizationUserRepository.GetManyAsync(organizationUserIds).Returns(organizationUsers); organizationUserRepository.GetManyByOrganizationAsync(orgUser.OrganizationId, OrganizationUserType.Owner).Returns(organizationUsers); var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.DeleteUsersAsync(orgUser.OrganizationId, organizationUserIds, null)); Assert.Contains("Organization must have at least one confirmed owner.", exception.Message); } [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task DeleteUsers_Success(OrganizationUser deletingUser, OrganizationUser orgUser1, OrganizationUser orgUser2, SutProvider sutProvider) { var organizationUserRepository = sutProvider.GetDependency(); deletingUser.Type = OrganizationUserType.Owner; deletingUser.Status = OrganizationUserStatusType.Confirmed; orgUser1.OrganizationId = orgUser2.OrganizationId = deletingUser.OrganizationId; orgUser1.Type = OrganizationUserType.Owner; var organizationUsers = new[] { orgUser1, orgUser2 }; var organizationUserIds = organizationUsers.Select(u => u.Id); organizationUserRepository.GetManyAsync(organizationUserIds).Returns(organizationUsers); organizationUserRepository.GetByIdAsync(deletingUser.Id).Returns(deletingUser); organizationUserRepository.GetManyByUserAsync(deletingUser.UserId.Value).Returns(new[] { deletingUser }); organizationUserRepository.GetManyByOrganizationAsync(deletingUser.OrganizationId, OrganizationUserType.Owner) .Returns(new[] {deletingUser, orgUser1}); await sutProvider.Sut.DeleteUsersAsync(deletingUser.OrganizationId, organizationUserIds, deletingUser.UserId); } } }