using System.Security.Claims; using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.OrganizationFeatures.Organizations; using Bit.Core.AdminConsole.Services; using Bit.Core.Billing.Organizations.Models; using Bit.Core.Billing.Services; using Bit.Core.Entities; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Data; using Bit.Core.Platform.Push; using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Settings; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Organizations; [SutProviderCustomize] public class SelfHostedOrganizationSignUpCommandTests { [Theory, BitAutoData] public async Task SignUpAsync_WithValidRequest_CreatesOrganizationSuccessfully( User owner, string ownerKey, string collectionName, string publicKey, string privateKey, List devices, SutProvider sutProvider) { // Arrange var globalSettings = sutProvider.GetDependency(); var license = CreateValidOrganizationLicense(globalSettings); SetupCommonMocks(sutProvider, owner); SetupLicenseValidation(sutProvider, license); sutProvider.GetDependency() .GetManyByUserIdAsync(owner.Id) .Returns(devices); // Act var result = await sutProvider.Sut.SignUpAsync(license, owner, ownerKey, collectionName, publicKey, privateKey); // Assert Assert.NotNull(result.organization); Assert.NotNull(result.organizationUser); await sutProvider.GetDependency() .Received(1) .CreateAsync(result.organization); await sutProvider.GetDependency() .Received(1) .CreateAsync(Arg.Is(key => key.OrganizationId == result.organization.Id && key.Type == OrganizationApiKeyType.Default && !string.IsNullOrEmpty(key.ApiKey))); await sutProvider.GetDependency() .Received(1) .UpsertOrganizationAbilityAsync(result.organization); await sutProvider.GetDependency() .Received(1) .CreateAsync(Arg.Is(user => user.OrganizationId == result.organization.Id && user.UserId == owner.Id && user.Key == ownerKey && user.Type == OrganizationUserType.Owner && user.Status == OrganizationUserStatusType.Confirmed)); await sutProvider.GetDependency() .Received(1) .CreateAsync( Arg.Is(c => c.Name == collectionName && c.OrganizationId == result.organization.Id), Arg.Is>(groups => groups == null), Arg.Is>(access => access.Any(a => a.Id == result.organizationUser.Id && a.Manage && !a.ReadOnly && !a.HidePasswords))); await sutProvider.GetDependency() .Received(1) .PushSyncOrgKeysAsync(owner.Id); } [Theory, BitAutoData] public async Task SignUpAsync_WithPremiumLicense_ThrowsBadRequestException( User owner, string ownerKey, string collectionName, string publicKey, string privateKey, SutProvider sutProvider) { // Arrange var globalSettings = sutProvider.GetDependency(); var license = CreateValidOrganizationLicense(globalSettings, LicenseType.User); // Act & Assert var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.SignUpAsync(license, owner, ownerKey, collectionName, publicKey, privateKey)); Assert.Contains("Premium licenses cannot be applied to an organization", exception.Message); } [Theory, BitAutoData] public async Task SignUpAsync_WithInvalidLicense_ThrowsBadRequestException( User owner, string ownerKey, string collectionName, string publicKey, string privateKey, SutProvider sutProvider) { // Arrange var globalSettings = sutProvider.GetDependency(); var license = CreateValidOrganizationLicense(globalSettings); license.CanUse(globalSettings, sutProvider.GetDependency(), null, out _) .Returns(false); // Act & Assert await Assert.ThrowsAsync( () => sutProvider.Sut.SignUpAsync(license, owner, ownerKey, collectionName, publicKey, privateKey)); } [Theory, BitAutoData] public async Task SignUpAsync_WithLicenseAlreadyInUse_ThrowsBadRequestException( User owner, string ownerKey, string collectionName, string publicKey, string privateKey, Organization existingOrganization, SutProvider sutProvider) { // Arrange var globalSettings = sutProvider.GetDependency(); var license = CreateValidOrganizationLicense(globalSettings); existingOrganization.LicenseKey = license.LicenseKey; SetupCommonMocks(sutProvider, owner); SetupLicenseValidation(sutProvider, license); sutProvider.GetDependency() .GetManyByEnabledAsync() .Returns(new List { existingOrganization }); // Act & Assert var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.SignUpAsync(license, owner, ownerKey, collectionName, publicKey, privateKey)); Assert.Contains("License is already in use", exception.Message); } [Theory, BitAutoData] public async Task SignUpAsync_WithSingleOrgPolicy_ThrowsBadRequestException( User owner, string ownerKey, string collectionName, string publicKey, string privateKey, SutProvider sutProvider) { // Arrange var globalSettings = sutProvider.GetDependency(); var license = CreateValidOrganizationLicense(globalSettings); SetupCommonMocks(sutProvider, owner); SetupLicenseValidation(sutProvider, license); sutProvider.GetDependency() .AnyPoliciesApplicableToUserAsync(owner.Id, PolicyType.SingleOrg) .Returns(true); // Act & Assert var exception = await Assert.ThrowsAsync( () => sutProvider.Sut.SignUpAsync(license, owner, ownerKey, collectionName, publicKey, privateKey)); Assert.Contains("You may not create an organization", exception.Message); } [Theory, BitAutoData] public async Task SignUpAsync_WithClaimsPrincipal_UsesClaimsPrincipalToCreateOrganization( User owner, string ownerKey, string collectionName, string publicKey, string privateKey, ClaimsPrincipal claimsPrincipal, SutProvider sutProvider) { // Arrange var globalSettings = sutProvider.GetDependency(); var license = CreateValidOrganizationLicense(globalSettings); SetupCommonMocks(sutProvider, owner); SetupLicenseValidation(sutProvider, license); sutProvider.GetDependency() .GetClaimsPrincipalFromLicense(license) .Returns(claimsPrincipal); sutProvider.GetDependency() .GetManyByUserIdAsync(owner.Id) .Returns(new List()); // Act var result = await sutProvider.Sut.SignUpAsync(license, owner, ownerKey, collectionName, publicKey, privateKey); // Assert Assert.NotNull(result.organization); Assert.NotNull(result.organizationUser); sutProvider.GetDependency() .Received(1) .GetClaimsPrincipalFromLicense(license); } [Theory, BitAutoData] public async Task SignUpAsync_WithoutCollectionName_DoesNotCreateCollection( User owner, string ownerKey, string publicKey, string privateKey, SutProvider sutProvider) { // Arrange var globalSettings = sutProvider.GetDependency(); var license = CreateValidOrganizationLicense(globalSettings); SetupCommonMocks(sutProvider, owner); SetupLicenseValidation(sutProvider, license); sutProvider.GetDependency() .GetManyByUserIdAsync(owner.Id) .Returns(new List()); // Act var result = await sutProvider.Sut.SignUpAsync(license, owner, ownerKey, null, publicKey, privateKey); // Assert Assert.NotNull(result.organization); Assert.NotNull(result.organizationUser); await sutProvider.GetDependency() .DidNotReceive() .CreateAsync(Arg.Any(), Arg.Is>(x => true), Arg.Is>(x => true)); } [Theory, BitAutoData] public async Task SignUpAsync_WithDevices_RegistersDevicesForPushNotifications( User owner, string ownerKey, string collectionName, string publicKey, string privateKey, List devices, SutProvider sutProvider) { // Arrange var globalSettings = sutProvider.GetDependency(); var license = CreateValidOrganizationLicense(globalSettings); foreach (var device in devices) { device.PushToken = "push-token-" + device.Id; } SetupCommonMocks(sutProvider, owner); SetupLicenseValidation(sutProvider, license); sutProvider.GetDependency() .GetManyByUserIdAsync(owner.Id) .Returns(devices); // Act var result = await sutProvider.Sut.SignUpAsync(license, owner, ownerKey, collectionName, publicKey, privateKey); // Assert Assert.NotNull(result.organization); Assert.NotNull(result.organizationUser); var expectedDeviceIds = devices.Select(d => d.Id.ToString()); await sutProvider.GetDependency() .Received(1) .AddUserRegistrationOrganizationAsync( Arg.Is>(ids => ids.SequenceEqual(expectedDeviceIds)), result.organization.Id.ToString()); } [Theory, BitAutoData] public async Task SignUpAsync_OnException_CleansUpOrganization( User owner, string ownerKey, string collectionName, string publicKey, string privateKey, SutProvider sutProvider) { // Arrange var globalSettings = sutProvider.GetDependency(); var license = CreateValidOrganizationLicense(globalSettings); SetupCommonMocks(sutProvider, owner); SetupLicenseValidation(sutProvider, license); sutProvider.GetDependency() .CreateAsync(Arg.Any()) .Throws(new Exception("Test exception")); // Act & Assert await Assert.ThrowsAsync( () => sutProvider.Sut.SignUpAsync(license, owner, ownerKey, collectionName, publicKey, privateKey)); await sutProvider.GetDependency() .Received(1) .DeleteAsync(Arg.Any()); await sutProvider.GetDependency() .Received(1) .DeleteOrganizationAbilityAsync(Arg.Any()); } private void SetupCommonMocks( SutProvider sutProvider, User owner) { var globalSettings = sutProvider.GetDependency(); sutProvider.GetDependency() .CreateAsync(Arg.Any()) .Returns(callInfo => { var org = callInfo.Arg(); org.Id = Guid.NewGuid(); return Task.FromResult(org); }); sutProvider.GetDependency() .AnyPoliciesApplicableToUserAsync(owner.Id, PolicyType.SingleOrg) .Returns(false); globalSettings.LicenseDirectory.Returns("/tmp/licenses"); } private void SetupLicenseValidation( SutProvider sutProvider, OrganizationLicense license) { var globalSettings = sutProvider.GetDependency(); sutProvider.GetDependency() .VerifyLicense(license) .Returns(true); license.CanUse(globalSettings, sutProvider.GetDependency(), null, out _) .Returns(true); } private OrganizationLicense CreateValidOrganizationLicense( IGlobalSettings globalSettings, LicenseType licenseType = LicenseType.Organization) { return new OrganizationLicense { LicenseType = licenseType, Signature = Guid.NewGuid().ToString().Replace('-', '+'), Issued = DateTime.UtcNow.AddDays(-1), Expires = DateTime.UtcNow.AddDays(10), Version = OrganizationLicense.CurrentLicenseFileVersion, InstallationId = globalSettings.Installation.Id, Enabled = true, SelfHost = true }; } }