diff --git a/bitwarden_license/src/Commercial.Core/SecretsManager/Queries/Projects/MaxProjectsQuery.cs b/bitwarden_license/src/Commercial.Core/SecretsManager/Queries/Projects/MaxProjectsQuery.cs index 394e8aa9bc..7e8857e5d7 100644 --- a/bitwarden_license/src/Commercial.Core/SecretsManager/Queries/Projects/MaxProjectsQuery.cs +++ b/bitwarden_license/src/Commercial.Core/SecretsManager/Queries/Projects/MaxProjectsQuery.cs @@ -1,13 +1,9 @@ -using Bit.Core.AdminConsole.Entities; -using Bit.Core.Billing.Enums; -using Bit.Core.Billing.Licenses; -using Bit.Core.Billing.Licenses.Extensions; +using Bit.Core.Billing.Enums; using Bit.Core.Billing.Pricing; using Bit.Core.Exceptions; using Bit.Core.Repositories; using Bit.Core.SecretsManager.Queries.Projects.Interfaces; using Bit.Core.SecretsManager.Repositories; -using Bit.Core.Services; using Bit.Core.Settings; namespace Bit.Commercial.Core.SecretsManager.Queries.Projects; @@ -17,72 +13,42 @@ public class MaxProjectsQuery : IMaxProjectsQuery private readonly IOrganizationRepository _organizationRepository; private readonly IProjectRepository _projectRepository; private readonly IGlobalSettings _globalSettings; - private readonly ILicensingService _licensingService; private readonly IPricingClient _pricingClient; public MaxProjectsQuery( IOrganizationRepository organizationRepository, IProjectRepository projectRepository, IGlobalSettings globalSettings, - ILicensingService licensingService, IPricingClient pricingClient) { _organizationRepository = organizationRepository; _projectRepository = projectRepository; _globalSettings = globalSettings; - _licensingService = licensingService; _pricingClient = pricingClient; } public async Task<(short? max, bool? overMax)> GetByOrgIdAsync(Guid organizationId, int projectsToAdd) { + // "MaxProjects" only applies to free 2-person organizations, which can't be self-hosted. + if (_globalSettings.SelfHosted) + { + return (null, null); + } + var org = await _organizationRepository.GetByIdAsync(organizationId); if (org == null) { throw new NotFoundException(); } - var (planType, maxProjects) = await GetPlanTypeAndMaxProjectsAsync(org); + var plan = await _pricingClient.GetPlan(org.PlanType); - if (planType != PlanType.Free) + if (plan is not { SecretsManager: not null, Type: PlanType.Free }) { return (null, null); } var projects = await _projectRepository.GetProjectCountByOrganizationIdAsync(organizationId); - return ((short? max, bool? overMax))(projects + projectsToAdd > maxProjects ? (maxProjects, true) : (maxProjects, false)); - } - - private async Task<(PlanType planType, int maxProjects)> GetPlanTypeAndMaxProjectsAsync(Organization organization) - { - if (_globalSettings.SelfHosted) - { - var license = await _licensingService.ReadOrganizationLicenseAsync(organization); - - if (license == null) - { - throw new BadRequestException("License not found."); - } - - var claimsPrincipal = _licensingService.GetClaimsPrincipalFromLicense(license); - var maxProjects = claimsPrincipal.GetValue(OrganizationLicenseConstants.SmMaxProjects); - - if (!maxProjects.HasValue) - { - throw new BadRequestException("License does not contain a value for max Secrets Manager projects"); - } - - var planType = claimsPrincipal.GetValue(OrganizationLicenseConstants.PlanType); - return (planType, maxProjects.Value); - } - - var plan = await _pricingClient.GetPlan(organization.PlanType); - - if (plan is { SupportsSecretsManager: true }) - { - return (plan.Type, plan.SecretsManager.MaxProjects); - } - - throw new BadRequestException("Existing plan not found."); + return ((short? max, bool? overMax))(projects + projectsToAdd > plan.SecretsManager.MaxProjects ? (plan.SecretsManager.MaxProjects, true) : (plan.SecretsManager.MaxProjects, false)); } } diff --git a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Queries/Projects/MaxProjectsQueryTests.cs b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Queries/Projects/MaxProjectsQueryTests.cs index 158463fcfa..16ae8f7f2c 100644 --- a/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Queries/Projects/MaxProjectsQueryTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/SecretsManager/Queries/Projects/MaxProjectsQueryTests.cs @@ -1,14 +1,10 @@ -using System.Security.Claims; -using Bit.Commercial.Core.SecretsManager.Queries.Projects; +using Bit.Commercial.Core.SecretsManager.Queries.Projects; using Bit.Core.AdminConsole.Entities; using Bit.Core.Billing.Enums; -using Bit.Core.Billing.Licenses; using Bit.Core.Billing.Pricing; using Bit.Core.Exceptions; -using Bit.Core.Models.Business; using Bit.Core.Repositories; using Bit.Core.SecretsManager.Repositories; -using Bit.Core.Services; using Bit.Core.Settings; using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; @@ -22,11 +18,26 @@ namespace Bit.Commercial.Core.Test.SecretsManager.Queries.Projects; [SutProviderCustomize] public class MaxProjectsQueryTests { + [Theory] + [BitAutoData] + public async Task GetByOrgIdAsync_SelfHosted_ReturnsNulls(SutProvider sutProvider, + Guid organizationId) + { + sutProvider.GetDependency().SelfHosted.Returns(true); + + var (max, overMax) = await sutProvider.Sut.GetByOrgIdAsync(organizationId, 1); + + Assert.Null(max); + Assert.Null(overMax); + } + [Theory] [BitAutoData] public async Task GetByOrgIdAsync_OrganizationIsNull_ThrowsNotFound(SutProvider sutProvider, Guid organizationId) { + sutProvider.GetDependency().SelfHosted.Returns(false); + sutProvider.GetDependency().GetByIdAsync(default).ReturnsNull(); await Assert.ThrowsAsync(async () => await sutProvider.Sut.GetByOrgIdAsync(organizationId, 1)); @@ -35,54 +46,6 @@ public class MaxProjectsQueryTests .GetProjectCountByOrganizationIdAsync(organizationId); } - [Theory] - [BitAutoData(PlanType.FamiliesAnnually2019)] - [BitAutoData(PlanType.Custom)] - [BitAutoData(PlanType.FamiliesAnnually)] - public async Task GetByOrgIdAsync_Cloud_SmPlanIsNull_ThrowsBadRequest(PlanType planType, - SutProvider sutProvider, Organization organization) - { - organization.PlanType = planType; - sutProvider.GetDependency() - .GetByIdAsync(organization.Id) - .Returns(organization); - - sutProvider.GetDependency().SelfHosted.Returns(false); - var plan = StaticStore.GetPlan(planType); - sutProvider.GetDependency().GetPlan(organization.PlanType).Returns(plan); - - await Assert.ThrowsAsync( - async () => await sutProvider.Sut.GetByOrgIdAsync(organization.Id, 1)); - - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs() - .GetProjectCountByOrganizationIdAsync(organization.Id); - } - - [Theory] - [BitAutoData] - public async Task GetByOrgIdAsync_SelfHosted_NoMaxProjectsClaim_ThrowsBadRequest( - SutProvider sutProvider, Organization organization) - { - sutProvider.GetDependency() - .GetByIdAsync(organization.Id) - .Returns(organization); - - sutProvider.GetDependency().SelfHosted.Returns(true); - - var license = new OrganizationLicense(); - var claimsPrincipal = new ClaimsPrincipal(); - sutProvider.GetDependency().ReadOrganizationLicenseAsync(organization).Returns(license); - sutProvider.GetDependency().GetClaimsPrincipalFromLicense(license).Returns(claimsPrincipal); - - await Assert.ThrowsAsync( - async () => await sutProvider.Sut.GetByOrgIdAsync(organization.Id, 1)); - - await sutProvider.GetDependency() - .DidNotReceiveWithAnyArgs() - .GetProjectCountByOrganizationIdAsync(organization.Id); - } - [Theory] [BitAutoData(PlanType.TeamsMonthly2019)] [BitAutoData(PlanType.TeamsMonthly2020)] @@ -97,57 +60,16 @@ public class MaxProjectsQueryTests [BitAutoData(PlanType.EnterpriseAnnually2019)] [BitAutoData(PlanType.EnterpriseAnnually2020)] [BitAutoData(PlanType.EnterpriseAnnually)] - public async Task GetByOrgIdAsync_Cloud_SmNoneFreePlans_ReturnsNull(PlanType planType, + public async Task GetByOrgIdAsync_SmNoneFreePlans_ReturnsNull(PlanType planType, SutProvider sutProvider, Organization organization) { - organization.PlanType = planType; - sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); - sutProvider.GetDependency().SelfHosted.Returns(false); - var plan = StaticStore.GetPlan(planType); - sutProvider.GetDependency().GetPlan(organization.PlanType).Returns(plan); - var (limit, overLimit) = await sutProvider.Sut.GetByOrgIdAsync(organization.Id, 1); - - Assert.Null(limit); - Assert.Null(overLimit); - - await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() - .GetProjectCountByOrganizationIdAsync(organization.Id); - } - - [Theory] - [BitAutoData(PlanType.TeamsMonthly2019)] - [BitAutoData(PlanType.TeamsMonthly2020)] - [BitAutoData(PlanType.TeamsMonthly)] - [BitAutoData(PlanType.TeamsAnnually2019)] - [BitAutoData(PlanType.TeamsAnnually2020)] - [BitAutoData(PlanType.TeamsAnnually)] - [BitAutoData(PlanType.TeamsStarter)] - [BitAutoData(PlanType.EnterpriseMonthly2019)] - [BitAutoData(PlanType.EnterpriseMonthly2020)] - [BitAutoData(PlanType.EnterpriseMonthly)] - [BitAutoData(PlanType.EnterpriseAnnually2019)] - [BitAutoData(PlanType.EnterpriseAnnually2020)] - [BitAutoData(PlanType.EnterpriseAnnually)] - public async Task GetByOrgIdAsync_SelfHosted_SmNoneFreePlans_ReturnsNull(PlanType planType, - SutProvider sutProvider, Organization organization) - { organization.PlanType = planType; sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); - sutProvider.GetDependency().SelfHosted.Returns(true); - var license = new OrganizationLicense(); - var plan = StaticStore.GetPlan(planType); - var claims = new List - { - new (nameof(OrganizationLicenseConstants.PlanType), organization.PlanType.ToString()), - new (nameof(OrganizationLicenseConstants.SmMaxProjects), plan.SecretsManager.MaxProjects.ToString()) - }; - var identity = new ClaimsIdentity(claims, "TestAuthenticationType"); - var claimsPrincipal = new ClaimsPrincipal(identity); - sutProvider.GetDependency().ReadOrganizationLicenseAsync(organization).Returns(license); - sutProvider.GetDependency().GetClaimsPrincipalFromLicense(license).Returns(claimsPrincipal); + sutProvider.GetDependency().GetPlan(organization.PlanType) + .Returns(StaticStore.GetPlan(organization.PlanType)); var (limit, overLimit) = await sutProvider.Sut.GetByOrgIdAsync(organization.Id, 1); @@ -183,7 +105,7 @@ public class MaxProjectsQueryTests [BitAutoData(PlanType.Free, 3, 4, true)] [BitAutoData(PlanType.Free, 4, 4, true)] [BitAutoData(PlanType.Free, 40, 4, true)] - public async Task GetByOrgIdAsync_Cloud_SmFreePlan__Success(PlanType planType, int projects, int projectsToAdd, bool expectedOverMax, + public async Task GetByOrgIdAsync_SmFreePlan__Success(PlanType planType, int projects, int projectsToAdd, bool expectedOverMax, SutProvider sutProvider, Organization organization) { organization.PlanType = planType; @@ -191,66 +113,8 @@ public class MaxProjectsQueryTests sutProvider.GetDependency().GetProjectCountByOrganizationIdAsync(organization.Id) .Returns(projects); - sutProvider.GetDependency().SelfHosted.Returns(false); - var plan = StaticStore.GetPlan(planType); - sutProvider.GetDependency().GetPlan(organization.PlanType).Returns(plan); - - var (max, overMax) = await sutProvider.Sut.GetByOrgIdAsync(organization.Id, projectsToAdd); - - Assert.NotNull(max); - Assert.NotNull(overMax); - Assert.Equal(3, max.Value); - Assert.Equal(expectedOverMax, overMax); - - await sutProvider.GetDependency().Received(1) - .GetProjectCountByOrganizationIdAsync(organization.Id); - } - - [Theory] - [BitAutoData(PlanType.Free, 0, 1, false)] - [BitAutoData(PlanType.Free, 1, 1, false)] - [BitAutoData(PlanType.Free, 2, 1, false)] - [BitAutoData(PlanType.Free, 3, 1, true)] - [BitAutoData(PlanType.Free, 4, 1, true)] - [BitAutoData(PlanType.Free, 40, 1, true)] - [BitAutoData(PlanType.Free, 0, 2, false)] - [BitAutoData(PlanType.Free, 1, 2, false)] - [BitAutoData(PlanType.Free, 2, 2, true)] - [BitAutoData(PlanType.Free, 3, 2, true)] - [BitAutoData(PlanType.Free, 4, 2, true)] - [BitAutoData(PlanType.Free, 40, 2, true)] - [BitAutoData(PlanType.Free, 0, 3, false)] - [BitAutoData(PlanType.Free, 1, 3, true)] - [BitAutoData(PlanType.Free, 2, 3, true)] - [BitAutoData(PlanType.Free, 3, 3, true)] - [BitAutoData(PlanType.Free, 4, 3, true)] - [BitAutoData(PlanType.Free, 40, 3, true)] - [BitAutoData(PlanType.Free, 0, 4, true)] - [BitAutoData(PlanType.Free, 1, 4, true)] - [BitAutoData(PlanType.Free, 2, 4, true)] - [BitAutoData(PlanType.Free, 3, 4, true)] - [BitAutoData(PlanType.Free, 4, 4, true)] - [BitAutoData(PlanType.Free, 40, 4, true)] - public async Task GetByOrgIdAsync_SelfHosted_SmFreePlan__Success(PlanType planType, int projects, int projectsToAdd, bool expectedOverMax, - SutProvider sutProvider, Organization organization) - { - organization.PlanType = planType; - sutProvider.GetDependency().GetByIdAsync(organization.Id).Returns(organization); - sutProvider.GetDependency().GetProjectCountByOrganizationIdAsync(organization.Id) - .Returns(projects); - sutProvider.GetDependency().SelfHosted.Returns(true); - - var license = new OrganizationLicense(); - var plan = StaticStore.GetPlan(planType); - var claims = new List - { - new (nameof(OrganizationLicenseConstants.PlanType), organization.PlanType.ToString()), - new (nameof(OrganizationLicenseConstants.SmMaxProjects), plan.SecretsManager.MaxProjects.ToString()) - }; - var identity = new ClaimsIdentity(claims, "TestAuthenticationType"); - var claimsPrincipal = new ClaimsPrincipal(identity); - sutProvider.GetDependency().ReadOrganizationLicenseAsync(organization).Returns(license); - sutProvider.GetDependency().GetClaimsPrincipalFromLicense(license).Returns(claimsPrincipal); + sutProvider.GetDependency().GetPlan(organization.PlanType) + .Returns(StaticStore.GetPlan(organization.PlanType)); var (max, overMax) = await sutProvider.Sut.GetByOrgIdAsync(organization.Id, projectsToAdd); diff --git a/src/Core/Billing/Licenses/LicenseConstants.cs b/src/Core/Billing/Licenses/LicenseConstants.cs index 8ef896d6f9..513578f43e 100644 --- a/src/Core/Billing/Licenses/LicenseConstants.cs +++ b/src/Core/Billing/Licenses/LicenseConstants.cs @@ -34,7 +34,6 @@ public static class OrganizationLicenseConstants public const string UseSecretsManager = nameof(UseSecretsManager); public const string SmSeats = nameof(SmSeats); public const string SmServiceAccounts = nameof(SmServiceAccounts); - public const string SmMaxProjects = nameof(SmMaxProjects); public const string LimitCollectionCreationDeletion = nameof(LimitCollectionCreationDeletion); public const string AllowAdminAccessToAllCollectionItems = nameof(AllowAdminAccessToAllCollectionItems); public const string UseRiskInsights = nameof(UseRiskInsights); diff --git a/src/Core/Billing/Licenses/Models/LicenseContext.cs b/src/Core/Billing/Licenses/Models/LicenseContext.cs index 01eb3ac80c..8dcc24e939 100644 --- a/src/Core/Billing/Licenses/Models/LicenseContext.cs +++ b/src/Core/Billing/Licenses/Models/LicenseContext.cs @@ -7,5 +7,4 @@ public class LicenseContext { public Guid? InstallationId { get; init; } public required SubscriptionInfo SubscriptionInfo { get; init; } - public int? SmMaxProjects { get; set; } } diff --git a/src/Core/Billing/Licenses/Services/Implementations/OrganizationLicenseClaimsFactory.cs b/src/Core/Billing/Licenses/Services/Implementations/OrganizationLicenseClaimsFactory.cs index 7406ac16d9..6819d3cc0b 100644 --- a/src/Core/Billing/Licenses/Services/Implementations/OrganizationLicenseClaimsFactory.cs +++ b/src/Core/Billing/Licenses/Services/Implementations/OrganizationLicenseClaimsFactory.cs @@ -112,11 +112,6 @@ public class OrganizationLicenseClaimsFactory : ILicenseClaimsFactory GetLicenseAsync(Organization organization, Guid installationId, @@ -46,11 +42,7 @@ public class CloudGetOrganizationLicenseQuery : ICloudGetOrganizationLicenseQuer var subscriptionInfo = await GetSubscriptionAsync(organization); var license = new OrganizationLicense(organization, subscriptionInfo, installationId, _licensingService, version); - var plan = await _pricingClient.GetPlan(organization.PlanType); - int? smMaxProjects = plan?.SupportsSecretsManager ?? false - ? plan.SecretsManager.MaxProjects - : null; - license.Token = await _licensingService.CreateOrganizationTokenAsync(organization, installationId, subscriptionInfo, smMaxProjects); + license.Token = await _licensingService.CreateOrganizationTokenAsync(organization, installationId, subscriptionInfo); return license; } diff --git a/src/Core/Services/ILicensingService.cs b/src/Core/Services/ILicensingService.cs index 9c497ed538..2115e43085 100644 --- a/src/Core/Services/ILicensingService.cs +++ b/src/Core/Services/ILicensingService.cs @@ -21,8 +21,7 @@ public interface ILicensingService Task CreateOrganizationTokenAsync( Organization organization, Guid installationId, - SubscriptionInfo subscriptionInfo, - int? smMaxProjects); + SubscriptionInfo subscriptionInfo); Task CreateUserTokenAsync(User user, SubscriptionInfo subscriptionInfo); } diff --git a/src/Core/Services/Implementations/LicensingService.cs b/src/Core/Services/Implementations/LicensingService.cs index e3509bc964..dd603b4b63 100644 --- a/src/Core/Services/Implementations/LicensingService.cs +++ b/src/Core/Services/Implementations/LicensingService.cs @@ -339,13 +339,12 @@ public class LicensingService : ILicensingService } } - public async Task CreateOrganizationTokenAsync(Organization organization, Guid installationId, SubscriptionInfo subscriptionInfo, int? smMaxProjects) + public async Task CreateOrganizationTokenAsync(Organization organization, Guid installationId, SubscriptionInfo subscriptionInfo) { var licenseContext = new LicenseContext { InstallationId = installationId, SubscriptionInfo = subscriptionInfo, - SmMaxProjects = smMaxProjects }; var claims = await _organizationLicenseClaimsFactory.GenerateClaims(organization, licenseContext); diff --git a/src/Core/Services/NoopImplementations/NoopLicensingService.cs b/src/Core/Services/NoopImplementations/NoopLicensingService.cs index de5e954d44..b181e61138 100644 --- a/src/Core/Services/NoopImplementations/NoopLicensingService.cs +++ b/src/Core/Services/NoopImplementations/NoopLicensingService.cs @@ -62,7 +62,7 @@ public class NoopLicensingService : ILicensingService return null; } - public Task CreateOrganizationTokenAsync(Organization organization, Guid installationId, SubscriptionInfo subscriptionInfo, int? smMaxProjects) + public Task CreateOrganizationTokenAsync(Organization organization, Guid installationId, SubscriptionInfo subscriptionInfo) { return Task.FromResult(null); } diff --git a/test/Core.Test/OrganizationFeatures/OrganizationLicenses/CloudGetOrganizationLicenseQueryTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationLicenses/CloudGetOrganizationLicenseQueryTests.cs index 7af9044c80..cc8ab956ca 100644 --- a/test/Core.Test/OrganizationFeatures/OrganizationLicenses/CloudGetOrganizationLicenseQueryTests.cs +++ b/test/Core.Test/OrganizationFeatures/OrganizationLicenses/CloudGetOrganizationLicenseQueryTests.cs @@ -1,7 +1,6 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities.Provider; using Bit.Core.AdminConsole.Repositories; -using Bit.Core.Billing.Pricing; using Bit.Core.Enums; using Bit.Core.Exceptions; using Bit.Core.Models.Business; @@ -9,7 +8,6 @@ using Bit.Core.OrganizationFeatures.OrganizationLicenses; using Bit.Core.Platform.Installations; using Bit.Core.Services; using Bit.Core.Test.AutoFixture; -using Bit.Core.Utilities; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using NSubstitute; @@ -78,10 +76,8 @@ public class CloudGetOrganizationLicenseQueryTests sutProvider.GetDependency().GetByIdAsync(installationId).Returns(installation); sutProvider.GetDependency().GetSubscriptionAsync(organization).Returns(subInfo); sutProvider.GetDependency().SignLicense(Arg.Any()).Returns(licenseSignature); - var plan = StaticStore.GetPlan(organization.PlanType); - sutProvider.GetDependency().GetPlan(organization.PlanType).Returns(plan); sutProvider.GetDependency() - .CreateOrganizationTokenAsync(organization, installationId, subInfo, plan.SecretsManager.MaxProjects) + .CreateOrganizationTokenAsync(organization, installationId, subInfo) .Returns(token); var result = await sutProvider.Sut.GetLicenseAsync(organization, installationId);