1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-02 08:32:50 -05:00

[AC-1486] Feature: SM Billing (#3073)

* [AC-1423] Add AddonProduct and BitwardenProduct properties to BillingSubscriptionItem (#3037)

* [AC-1423] Add AddonProduct and BitwardenProduct properties to BillingSubscriptionItem

* [AC-1423] Add helper to StaticStore.cs to find a Plan by StripePlanId

* [AC-1423] Use the helper method to set SubscriptionInfo.BitwardenProduct

* Add SecretsManagerBilling feature flag to Constants

* [AC 1409] Secrets Manager Subscription Stripe Integration  (#3019)

* [AC-1418] Add missing SecretsManagerPlan property to OrganizationResponseModel (#3055)

* [AC 1460] Update Stripe Configuration (#3070)

* [AC 1410] Secrets Manager subscription adjustment back-end changes (#3036)

* Create UpgradeSecretsManagerSubscription command

* [AC-1495] Extract UpgradePlanAsync into a command (#3081)

* This is a pure lift & shift with no refactors

* [AC-1503] Fix Stripe integration on organization upgrade (#3084)

* Fix SM parameters not being passed to Stripe

* [AC-1504] Allow SM max autoscale limits to be disabled (#3085)

* [AC-1488] Changed SM Signup and Upgrade paths to set SmServiceAccounts to include the plan BaseServiceAccount (#3086)

* [AC-1510] Enable access to Secrets Manager to Organization owner for new Subscription (#3089)

* Revert changes to ReferenceEvent code (#3091)

This will be done in AC-1481

* Add UsePasswordManager to sync data (#3114)

* [AC-1522] Fix service account check on upgrading (#3111)

* [AC-1521] Address checkmarx security feedback (#3124)

* Reinstate target attribute but add noopener noreferrer

* Update date on migration script

---------

Co-authored-by: Shane Melton <smelton@bitwarden.com>
Co-authored-by: Thomas Rittson <trittson@bitwarden.com>
Co-authored-by: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com>
Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
Co-authored-by: cyprain-okeke <cokeke@bitwarden.com>
Co-authored-by: Rui Tomé <108268980+r-tome@users.noreply.github.com>
Co-authored-by: Conner Turnbull <cturnbull@bitwarden.com>
Co-authored-by: Rui Tome <rtome@bitwarden.com>
This commit is contained in:
Álison Fernandes
2023-07-24 23:05:05 +01:00
committed by GitHub
parent 4ec765ae19
commit 35111382e5
50 changed files with 2880 additions and 381 deletions

View File

@ -13,6 +13,7 @@ using Bit.Core.Exceptions;
using Bit.Core.Models.Business;
using Bit.Core.Models.Data;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Models.StaticStore;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
@ -22,6 +23,7 @@ using Bit.Core.Test.AutoFixture.PolicyFixtures;
using Bit.Core.Tools.Enums;
using Bit.Core.Tools.Models.Business;
using Bit.Core.Tools.Services;
using Bit.Core.Utilities;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
@ -143,55 +145,117 @@ public class OrganizationServiceTests
referenceEvent.Users == expectedNewUsersCount));
}
[Theory, BitAutoData]
public async Task UpgradePlan_OrganizationIsNull_Throws(Guid organizationId, OrganizationUpgrade upgrade,
SutProvider<OrganizationService> sutProvider)
{
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationId).Returns(Task.FromResult<Organization>(null));
var exception = await Assert.ThrowsAsync<NotFoundException>(
() => sutProvider.Sut.UpgradePlanAsync(organizationId, upgrade));
}
[Theory, BitAutoData]
public async Task UpgradePlan_GatewayCustomIdIsNull_Throws(Organization organization, OrganizationUpgrade upgrade,
SutProvider<OrganizationService> sutProvider)
[Theory]
[BitAutoData(PlanType.EnterpriseAnnually)]
[BitAutoData(PlanType.EnterpriseMonthly)]
[BitAutoData(PlanType.TeamsAnnually)]
[BitAutoData(PlanType.TeamsMonthly)]
public async Task SignUp_SM_Passes(PlanType planType, OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
{
organization.GatewayCustomerId = string.Empty;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade));
Assert.Contains("no payment method", exception.Message);
}
signup.Plan = planType;
[Theory, BitAutoData]
public async Task UpgradePlan_AlreadyInPlan_Throws(Organization organization, OrganizationUpgrade upgrade,
SutProvider<OrganizationService> sutProvider)
{
upgrade.Plan = organization.PlanType;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade));
Assert.Contains("already on this plan", exception.Message);
}
var passwordManagerPlan = StaticStore.GetPasswordManagerPlan(signup.Plan);
var secretsManagerPlan = StaticStore.GetSecretsManagerPlan(signup.Plan);
[Theory, PaidOrganizationCustomize(CheckedPlanType = PlanType.Free), BitAutoData]
public async Task UpgradePlan_UpgradeFromPaidPlan_Throws(Organization organization, OrganizationUpgrade upgrade,
SutProvider<OrganizationService> sutProvider)
{
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade));
Assert.Contains("can only upgrade", exception.Message);
signup.UseSecretsManager = true;
signup.AdditionalSeats = 15;
signup.AdditionalSmSeats = 10;
signup.AdditionalServiceAccounts = 20;
signup.PaymentMethodType = PaymentMethodType.Card;
signup.PremiumAccessAddon = false;
var purchaseOrganizationPlan = StaticStore.Plans.Where(x => x.Type == signup.Plan).ToList();
var result = await sutProvider.Sut.SignUpAsync(signup);
await sutProvider.GetDependency<IOrganizationRepository>().Received(1).CreateAsync(
Arg.Is<Organization>(o =>
o.Seats == passwordManagerPlan.BaseSeats + signup.AdditionalSeats
&& o.SmSeats == secretsManagerPlan.BaseSeats + signup.AdditionalSmSeats
&& o.SmServiceAccounts == secretsManagerPlan.BaseServiceAccount + signup.AdditionalServiceAccounts));
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).CreateAsync(
Arg.Is<OrganizationUser>(o => o.AccessSecretsManager == signup.UseSecretsManager));
await sutProvider.GetDependency<IReferenceEventService>().Received(1)
.RaiseEventAsync(Arg.Is<ReferenceEvent>(referenceEvent =>
referenceEvent.Type == ReferenceEventType.Signup &&
referenceEvent.PlanName == purchaseOrganizationPlan[0].Name &&
referenceEvent.PlanType == purchaseOrganizationPlan[0].Type &&
referenceEvent.Seats == result.Item1.Seats &&
referenceEvent.Storage == result.Item1.MaxStorageGb));
// TODO: add reference events for SmSeats and Service Accounts - see AC-1481
Assert.NotNull(result);
Assert.NotNull(result.Item1);
Assert.NotNull(result.Item2);
Assert.IsType<Tuple<Organization, OrganizationUser>>(result);
await sutProvider.GetDependency<IPaymentService>().Received(1).PurchaseOrganizationAsync(
Arg.Any<Organization>(),
signup.PaymentMethodType.Value,
signup.PaymentToken,
Arg.Is<List<Plan>>(plan => plan.All(p => purchaseOrganizationPlan.Contains(p))),
signup.AdditionalStorageGb,
signup.AdditionalSeats,
signup.PremiumAccessAddon,
signup.TaxInfo,
false,
signup.AdditionalSmSeats.GetValueOrDefault(),
signup.AdditionalServiceAccounts.GetValueOrDefault()
);
}
[Theory]
[FreeOrganizationUpgradeCustomize, BitAutoData]
public async Task UpgradePlan_Passes(Organization organization, OrganizationUpgrade upgrade,
SutProvider<OrganizationService> sutProvider)
[BitAutoData]
public async Task SignUpAsync_SecretManager_AdditionalServiceAccounts_NotAllowedByPlan_ShouldThrowException(OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
{
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
await sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade);
await sutProvider.GetDependency<IOrganizationRepository>().Received(1).ReplaceAsync(organization);
signup.AdditionalSmSeats = 0;
signup.AdditionalSeats = 0;
signup.Plan = PlanType.Free;
signup.UseSecretsManager = true;
signup.PaymentMethodType = PaymentMethodType.Card;
signup.PremiumAccessAddon = false;
signup.AdditionalServiceAccounts = 10;
signup.AdditionalStorageGb = 0;
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.SignUpAsync(signup));
Assert.Contains("Plan does not allow additional Service Accounts.", exception.Message);
}
[Theory]
[BitAutoData]
public async Task SignUpAsync_SMSeatsGreatThanPMSeat_ShouldThrowException(OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
{
signup.AdditionalSmSeats = 100;
signup.AdditionalSeats = 10;
signup.Plan = PlanType.EnterpriseAnnually;
signup.UseSecretsManager = true;
signup.PaymentMethodType = PaymentMethodType.Card;
signup.PremiumAccessAddon = false;
signup.AdditionalServiceAccounts = 10;
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.SignUpAsync(signup));
Assert.Contains("You cannot have more Secrets Manager seats than Password Manager seats", exception.Message);
}
[Theory]
[BitAutoData]
public async Task SignUpAsync_InvalidateServiceAccount_ShouldThrowException(OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
{
signup.AdditionalSmSeats = 10;
signup.AdditionalSeats = 10;
signup.Plan = PlanType.EnterpriseAnnually;
signup.UseSecretsManager = true;
signup.PaymentMethodType = PaymentMethodType.Card;
signup.PremiumAccessAddon = false;
signup.AdditionalServiceAccounts = -10;
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.SignUpAsync(signup));
Assert.Contains("You can't subtract Service Accounts!", exception.Message);
}
[Theory]
@ -1469,4 +1533,5 @@ public class OrganizationServiceTests
Assert.Equal(includeProvider, result);
}
}