From 53f5eee215c7c66dde29527c39d457bab819f78d Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Fri, 13 Oct 2023 00:56:50 +1000 Subject: [PATCH] [AC-1638] Disallow Secrets Manager for MSP-managed organizations (#3297) * Block MSPs from creating orgs with SM * Block MSPs from adding SM to a managed org * Prevent manually adding SM to an MSP-managed org * Revert "Prevent manually adding SM to an MSP-managed org" This change is no longer required This reverts commit 51b086243bf7ab63897a904b6b14fa1077a2bc6e. * Block provider from adding org with SM * Update error message when adding existing org with SM to provider * Update check to match client * Revert "Update check to match client" This reverts commit f195c1c1f6546757a5d591068a0a650ef0d8dceb. --- .../Services/ProviderService.cs | 6 +++++ .../Services/ProviderServiceTests.cs | 17 ++++++++++++++ .../AddSecretsManagerSubscriptionCommand.cs | 19 +++++++++++++--- .../Implementations/OrganizationService.cs | 5 +++++ ...dSecretsManagerSubscriptionCommandTests.cs | 22 +++++++++++++++++++ .../Services/OrganizationServiceTests.cs | 16 ++++++++++++++ 6 files changed, 82 insertions(+), 3 deletions(-) diff --git a/bitwarden_license/src/Commercial.Core/Services/ProviderService.cs b/bitwarden_license/src/Commercial.Core/Services/ProviderService.cs index 9a5f92424..d186eb6d4 100644 --- a/bitwarden_license/src/Commercial.Core/Services/ProviderService.cs +++ b/bitwarden_license/src/Commercial.Core/Services/ProviderService.cs @@ -354,6 +354,12 @@ public class ProviderService : IProviderService var organization = await _organizationRepository.GetByIdAsync(organizationId); ThrowOnInvalidPlanType(organization.PlanType); + if (organization.UseSecretsManager) + { + throw new BadRequestException( + "The organization is subscribed to Secrets Manager. Please contact Customer Support to manage the subscription."); + } + var providerOrganization = new ProviderOrganization { ProviderId = providerId, diff --git a/bitwarden_license/test/Commercial.Core.Test/Services/ProviderServiceTests.cs b/bitwarden_license/test/Commercial.Core.Test/Services/ProviderServiceTests.cs index babfa9c07..2a6e68bfb 100644 --- a/bitwarden_license/test/Commercial.Core.Test/Services/ProviderServiceTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/Services/ProviderServiceTests.cs @@ -431,6 +431,23 @@ public class ProviderServiceTests Assert.Equal("Organization already belongs to a provider.", exception.Message); } + [Theory, BitAutoData] + public async Task AddOrganization_OrganizationHasSecretsManager_Throws(Provider provider, Organization organization, string key, + SutProvider sutProvider) + { + organization.PlanType = PlanType.EnterpriseAnnually; + organization.UseSecretsManager = true; + + sutProvider.GetDependency().GetByIdAsync(provider.Id).Returns(provider); + var providerOrganizationRepository = sutProvider.GetDependency(); + providerOrganizationRepository.GetByOrganizationId(organization.Id).ReturnsNull(); + sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.AddOrganization(provider.Id, organization.Id, key)); + Assert.Equal("The organization is subscribed to Secrets Manager. Please contact Customer Support to manage the subscription.", exception.Message); + } + [Theory, BitAutoData] public async Task AddOrganization_Success(Provider provider, Organization organization, string key, SutProvider sutProvider) diff --git a/src/Core/OrganizationFeatures/OrganizationSubscriptions/AddSecretsManagerSubscriptionCommand.cs b/src/Core/OrganizationFeatures/OrganizationSubscriptions/AddSecretsManagerSubscriptionCommand.cs index 3741148af..52136bd1b 100644 --- a/src/Core/OrganizationFeatures/OrganizationSubscriptions/AddSecretsManagerSubscriptionCommand.cs +++ b/src/Core/OrganizationFeatures/OrganizationSubscriptions/AddSecretsManagerSubscriptionCommand.cs @@ -1,8 +1,10 @@ using Bit.Core.Entities; using Bit.Core.Enums; +using Bit.Core.Enums.Provider; using Bit.Core.Exceptions; using Bit.Core.Models.Business; using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface; +using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Utilities; @@ -12,17 +14,21 @@ public class AddSecretsManagerSubscriptionCommand : IAddSecretsManagerSubscripti { private readonly IPaymentService _paymentService; private readonly IOrganizationService _organizationService; + private readonly IProviderRepository _providerRepository; + public AddSecretsManagerSubscriptionCommand( IPaymentService paymentService, - IOrganizationService organizationService) + IOrganizationService organizationService, + IProviderRepository providerRepository) { _paymentService = paymentService; _organizationService = organizationService; + _providerRepository = providerRepository; } public async Task SignUpAsync(Organization organization, int additionalSmSeats, int additionalServiceAccounts) { - ValidateOrganization(organization); + await ValidateOrganization(organization); var plan = StaticStore.GetSecretsManagerPlan(organization.PlanType); var signup = SetOrganizationUpgrade(organization, additionalSmSeats, additionalServiceAccounts); @@ -55,7 +61,7 @@ public class AddSecretsManagerSubscriptionCommand : IAddSecretsManagerSubscripti return signup; } - private static void ValidateOrganization(Organization organization) + private async Task ValidateOrganization(Organization organization) { if (organization == null) { @@ -83,5 +89,12 @@ public class AddSecretsManagerSubscriptionCommand : IAddSecretsManagerSubscripti { throw new BadRequestException("No subscription found."); } + + var provider = await _providerRepository.GetByOrganizationIdAsync(organization.Id); + if (provider is { Type: ProviderType.Msp }) + { + throw new BadRequestException( + "Organizations with a Managed Service Provider do not support Secrets Manager."); + } } } diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index 10dc2a205..2388faded 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -410,6 +410,11 @@ public class OrganizationService : IOrganizationService var secretsManagerPlan = StaticStore.SecretManagerPlans.FirstOrDefault(p => p.Type == signup.Plan); if (signup.UseSecretsManager) { + if (provider) + { + throw new BadRequestException( + "Organizations with a Managed Service Provider do not support Secrets Manager."); + } ValidateSecretsManagerPlan(secretsManagerPlan, signup); } diff --git a/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/AddSecretsManagerSubscriptionCommandTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/AddSecretsManagerSubscriptionCommandTests.cs index a09500cf6..ec83fa102 100644 --- a/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/AddSecretsManagerSubscriptionCommandTests.cs +++ b/test/Core.Test/OrganizationFeatures/OrganizationSubscriptionUpdate/AddSecretsManagerSubscriptionCommandTests.cs @@ -1,9 +1,12 @@ using Bit.Core.Entities; +using Bit.Core.Entities.Provider; using Bit.Core.Enums; +using Bit.Core.Enums.Provider; using Bit.Core.Exceptions; using Bit.Core.Models.Business; using Bit.Core.Models.StaticStore; using Bit.Core.OrganizationFeatures.OrganizationSubscriptions; +using Bit.Core.Repositories; using Bit.Core.Services; using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; @@ -127,6 +130,25 @@ public class AddSecretsManagerSubscriptionCommandTests await VerifyDependencyNotCalledAsync(sutProvider); } + [Theory] + [BitAutoData] + public async Task SignUpAsync_ThrowsException_WhenOrganizationIsManagedByMSP( + SutProvider sutProvider, + Organization organization, + Provider provider) + { + organization.UseSecretsManager = false; + organization.SecretsManagerBeta = false; + provider.Type = ProviderType.Msp; + sutProvider.GetDependency().GetByOrganizationIdAsync(organization.Id).Returns(provider); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.SignUpAsync(organization, 10, 10)); + + Assert.Contains("Organizations with a Managed Service Provider do not support Secrets Manager.", exception.Message); + await VerifyDependencyNotCalledAsync(sutProvider); + } + private static async Task VerifyDependencyNotCalledAsync(SutProvider sutProvider) { await sutProvider.GetDependency().DidNotReceive() diff --git a/test/Core.Test/Services/OrganizationServiceTests.cs b/test/Core.Test/Services/OrganizationServiceTests.cs index 4907efafc..a4974d39a 100644 --- a/test/Core.Test/Services/OrganizationServiceTests.cs +++ b/test/Core.Test/Services/OrganizationServiceTests.cs @@ -263,6 +263,22 @@ public class OrganizationServiceTests ); } + [Theory] + [BitAutoData(PlanType.EnterpriseAnnually)] + public async Task SignUp_SM_Throws_WhenManagedByMSP(PlanType planType, OrganizationSignup signup, SutProvider sutProvider) + { + signup.Plan = planType; + signup.UseSecretsManager = true; + signup.AdditionalSeats = 15; + signup.AdditionalSmSeats = 10; + signup.AdditionalServiceAccounts = 20; + signup.PaymentMethodType = PaymentMethodType.Card; + signup.PremiumAccessAddon = false; + + var exception = await Assert.ThrowsAsync(() => sutProvider.Sut.SignUpAsync(signup, true)); + Assert.Contains("Organizations with a Managed Service Provider do not support Secrets Manager.", exception.Message); + } + [Theory] [BitAutoData] public async Task SignUpAsync_SecretManager_AdditionalServiceAccounts_NotAllowedByPlan_ShouldThrowException(OrganizationSignup signup, SutProvider sutProvider)