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 79ffb421e1b3..ab24d28271dc 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 @@ -30,12 +30,8 @@ await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() [Theory] [BitAutoData(PlanType.FamiliesAnnually2019)] - [BitAutoData(PlanType.TeamsMonthly2019)] - [BitAutoData(PlanType.TeamsAnnually2019)] - [BitAutoData(PlanType.EnterpriseMonthly2019)] - [BitAutoData(PlanType.EnterpriseAnnually2019)] - [BitAutoData(PlanType.Custom)] [BitAutoData(PlanType.FamiliesAnnually)] + [BitAutoData(PlanType.Custom)] public async Task GetByOrgIdAsync_SmPlanIsNull_ThrowsBadRequest(PlanType planType, SutProvider sutProvider, Organization organization) { diff --git a/src/Admin/Views/Shared/_OrganizationFormScripts.cshtml b/src/Admin/Views/Shared/_OrganizationFormScripts.cshtml index 88dc8a78c1f7..816239d42b4e 100644 --- a/src/Admin/Views/Shared/_OrganizationFormScripts.cshtml +++ b/src/Admin/Views/Shared/_OrganizationFormScripts.cshtml @@ -64,6 +64,10 @@ function togglePlanFeatures(planType) { switch(planType) { + case '@((byte)PlanType.TeamsMonthly2019)': + case '@((byte)PlanType.TeamsAnnually2019)': + case '@((byte)PlanType.TeamsMonthly2020)': + case '@((byte)PlanType.TeamsAnnually2020)': case '@((byte)PlanType.TeamsMonthly)': case '@((byte)PlanType.TeamsAnnually)': document.getElementById('@(nameof(Model.UsePolicies))').checked = false; @@ -81,6 +85,10 @@ document.getElementById('@(nameof(Model.UseScim))').checked = false; break; + case '@((byte)PlanType.EnterpriseMonthly2019)': + case '@((byte)PlanType.EnterpriseAnnually2019)': + case '@((byte)PlanType.EnterpriseMonthly2020)': + case '@((byte)PlanType.EnterpriseAnnually2020)': case '@((byte)PlanType.EnterpriseMonthly)': case '@((byte)PlanType.EnterpriseAnnually)': document.getElementById('@(nameof(Model.UsePolicies))').checked = true; diff --git a/src/Api/Models/Response/ProfileOrganizationResponseModel.cs b/src/Api/Models/Response/ProfileOrganizationResponseModel.cs index a0ededa0b6b1..29127fd684ce 100644 --- a/src/Api/Models/Response/ProfileOrganizationResponseModel.cs +++ b/src/Api/Models/Response/ProfileOrganizationResponseModel.cs @@ -32,8 +32,8 @@ public ProfileOrganizationResponseModel(OrganizationUserOrganizationDetails orga UsePasswordManager = organization.UsePasswordManager; UsersGetPremium = organization.UsersGetPremium; UseCustomPermissions = organization.UseCustomPermissions; - UseActivateAutofillPolicy = organization.PlanType == PlanType.EnterpriseAnnually || - organization.PlanType == PlanType.EnterpriseMonthly; + UseActivateAutofillPolicy = + StaticStore.GetPasswordManagerPlan(organization.PlanType).Product == ProductType.Enterprise; SelfHost = organization.SelfHost; Seats = organization.Seats; MaxCollections = organization.MaxCollections; diff --git a/src/Api/Models/Response/ProfileProviderOrganizationResponseModel.cs b/src/Api/Models/Response/ProfileProviderOrganizationResponseModel.cs index 32bfd81db268..aad437f5f8d0 100644 --- a/src/Api/Models/Response/ProfileProviderOrganizationResponseModel.cs +++ b/src/Api/Models/Response/ProfileProviderOrganizationResponseModel.cs @@ -24,8 +24,8 @@ public ProfileProviderOrganizationResponseModel(ProviderUserOrganizationDetails UseResetPassword = organization.UseResetPassword; UsersGetPremium = organization.UsersGetPremium; UseCustomPermissions = organization.UseCustomPermissions; - UseActivateAutofillPolicy = organization.PlanType == PlanType.EnterpriseAnnually || - organization.PlanType == PlanType.EnterpriseMonthly; + UseActivateAutofillPolicy = + StaticStore.GetPasswordManagerPlan(organization.PlanType).Product == ProductType.Enterprise; SelfHost = organization.SelfHost; Seats = organization.Seats; MaxCollections = organization.MaxCollections; diff --git a/src/Billing/Controllers/FreshsalesController.cs b/src/Billing/Controllers/FreshsalesController.cs index 95b9e2506511..a66edd6ca6bd 100644 --- a/src/Billing/Controllers/FreshsalesController.cs +++ b/src/Billing/Controllers/FreshsalesController.cs @@ -159,14 +159,18 @@ private static bool TryGetPlanName(PlanType planType, out string planName) planName = "Families"; return true; case PlanType.TeamsAnnually: + case PlanType.TeamsAnnually2020: case PlanType.TeamsAnnually2019: case PlanType.TeamsMonthly: + case PlanType.TeamsMonthly2020: case PlanType.TeamsMonthly2019: planName = "Teams"; return true; case PlanType.EnterpriseAnnually: + case PlanType.EnterpriseAnnually2020: case PlanType.EnterpriseAnnually2019: case PlanType.EnterpriseMonthly: + case PlanType.EnterpriseMonthly2020: case PlanType.EnterpriseMonthly2019: planName = "Enterprise"; return true; diff --git a/src/Billing/Controllers/StripeController.cs b/src/Billing/Controllers/StripeController.cs index 00a8fa5ac6cd..32eaf9ac749c 100644 --- a/src/Billing/Controllers/StripeController.cs +++ b/src/Billing/Controllers/StripeController.cs @@ -226,7 +226,7 @@ await _userService.UpdatePremiumExpirationAsync(userId, } var org = await _organizationRepository.GetByIdAsync(ids.Item1.Value); - if (org != null && OrgPlanForInvoiceNotifications(org)) + if (org != null && OrgPlanShouldSendUpcomingInvoiceNotificationEmail(org)) { email = org.BillingEmail; } @@ -710,18 +710,9 @@ private async Task AttemptToPayOpenSubscriptionAsync(Subscription unpaidSubscrip return new Tuple(orgId, userId); } - private bool OrgPlanForInvoiceNotifications(Organization org) - { - switch (org.PlanType) - { - case PlanType.FamiliesAnnually: - case PlanType.TeamsAnnually: - case PlanType.EnterpriseAnnually: - return true; - default: - return false; - } - } + private static bool OrgPlanShouldSendUpcomingInvoiceNotificationEmail(Organization org) => + StaticStore.GetPasswordManagerPlan(org.PlanType) + .IsAnnual; private async Task AttemptToPayInvoiceAsync(Invoice invoice, bool attemptToPayWithStripe = false) { diff --git a/src/Core/Enums/PlanType.cs b/src/Core/Enums/PlanType.cs index ac32f217e40d..de5c8bc05a4d 100644 --- a/src/Core/Enums/PlanType.cs +++ b/src/Core/Enums/PlanType.cs @@ -20,12 +20,20 @@ public enum PlanType : byte Custom = 6, [Display(Name = "Families")] FamiliesAnnually = 7, + [Display(Name = "Teams (Monthly) 2023")] + TeamsMonthly2020 = 8, + [Display(Name = "Teams (Annually) 2023")] + TeamsAnnually2020 = 9, + [Display(Name = "Enterprise (Monthly) 2023")] + EnterpriseMonthly2020 = 10, + [Display(Name = "Enterprise (Annually) 2023")] + EnterpriseAnnually2020 = 11, [Display(Name = "Teams (Monthly)")] - TeamsMonthly = 8, + TeamsMonthly = 12, [Display(Name = "Teams (Annually)")] - TeamsAnnually = 9, + TeamsAnnually = 13, [Display(Name = "Enterprise (Monthly)")] - EnterpriseMonthly = 10, + EnterpriseMonthly = 14, [Display(Name = "Enterprise (Annually)")] - EnterpriseAnnually = 11, + EnterpriseAnnually = 15, } diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index 10dc2a205608..0612da458ef6 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -1936,11 +1936,6 @@ private async Task GetOrgById(Guid id) private static void ValidatePlan(Models.StaticStore.Plan plan, int additionalSeats, string productType) { - if (plan is not { LegacyYear: null }) - { - throw new BadRequestException($"Invalid {productType} plan selected."); - } - if (plan.Disabled) { throw new BadRequestException($"{productType} Plan not found."); diff --git a/src/Core/Services/Implementations/PolicyService.cs b/src/Core/Services/Implementations/PolicyService.cs index 5f4e680df74d..2c9b6c73e1bb 100644 --- a/src/Core/Services/Implementations/PolicyService.cs +++ b/src/Core/Services/Implementations/PolicyService.cs @@ -98,14 +98,6 @@ public async Task SaveAsync(Policy policy, IUserService userService, IOrganizati await DependsOnSingleOrgAsync(org); } break; - - // Activate Autofill is only available to Enterprise 2020-current plans - case PolicyType.ActivateAutofill: - if (policy.Enabled) - { - LockedTo2020Plan(org); - } - break; } var now = DateTime.UtcNow; @@ -274,14 +266,6 @@ private async Task RequiredByVaultTimeoutAsync(Organization org) } } - private void LockedTo2020Plan(Organization org) - { - if (org.PlanType != PlanType.EnterpriseAnnually && org.PlanType != PlanType.EnterpriseMonthly) - { - throw new BadRequestException("This policy is only available to 2020 Enterprise plans."); - } - } - private async Task RequiredBySsoTrustedDeviceEncryptionAsync(Organization org) { var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(org.Id); diff --git a/src/Core/Utilities/PasswordManagerPlanStore.cs b/src/Core/Utilities/PasswordManagerPlanStore.cs index 1f9400eba61b..99a69b0d93e8 100644 --- a/src/Core/Utilities/PasswordManagerPlanStore.cs +++ b/src/Core/Utilities/PasswordManagerPlanStore.cs @@ -76,7 +76,13 @@ public static IEnumerable CreatePlan() HasAdditionalStorageOption = true, TrialPeriodDays = 7, + Has2fa = true, + HasApi = true, + HasDirectory = true, + HasEvents = true, + HasGroups = true, HasTotp = true, + UsersGetPremium = true, UpgradeSortOrder = 2, DisplaySortOrder = 2, @@ -107,7 +113,13 @@ public static IEnumerable CreatePlan() HasAdditionalStorageOption = true, TrialPeriodDays = 7, + Has2fa = true, + HasApi = true, + HasDirectory = true, + HasEvents = true, + HasGroups = true, HasTotp = true, + UsersGetPremium = true, UpgradeSortOrder = 2, DisplaySortOrder = 2, @@ -147,6 +159,10 @@ public static IEnumerable CreatePlan() HasTotp = true, Has2fa = true, HasApi = true, + HasSso = true, + HasKeyConnector = true, + HasScim = true, + HasResetPassword = true, UsersGetPremium = true, HasCustomPermissions = true, @@ -187,6 +203,10 @@ public static IEnumerable CreatePlan() Has2fa = true, HasApi = true, HasSelfHost = true, + HasSso = true, + HasKeyConnector = true, + HasScim = true, + HasResetPassword = true, UsersGetPremium = true, HasCustomPermissions = true, @@ -235,10 +255,10 @@ public static IEnumerable CreatePlan() }, new Plan { - Type = PlanType.TeamsAnnually, + Type = PlanType.TeamsAnnually2020, Product = ProductType.Teams, BitwardenProduct = BitwardenProductType.PasswordManager, - Name = "Teams (Annually)", + Name = "Teams (Annually) 2020", IsAnnual = true, NameLocalizationKey = "planNameTeams", DescriptionLocalizationKey = "planDescTeams", @@ -267,13 +287,14 @@ public static IEnumerable CreatePlan() AdditionalStoragePricePerGb = 4, AllowSeatAutoscale = true, + LegacyYear = 2023 }, new Plan { - Type = PlanType.TeamsMonthly, + Type = PlanType.TeamsMonthly2020, Product = ProductType.Teams, BitwardenProduct = BitwardenProductType.PasswordManager, - Name = "Teams (Monthly)", + Name = "Teams (Monthly) 2020", NameLocalizationKey = "planNameTeams", DescriptionLocalizationKey = "planDescTeams", CanBeUsedByBusiness = true, @@ -301,11 +322,12 @@ public static IEnumerable CreatePlan() AdditionalStoragePricePerGb = 0.5M, AllowSeatAutoscale = true, + LegacyYear = 2023 }, new Plan { - Type = PlanType.EnterpriseAnnually, - Name = "Enterprise (Annually)", + Type = PlanType.EnterpriseAnnually2020, + Name = "Enterprise (Annually) 2020", Product = ProductType.Enterprise, BitwardenProduct = BitwardenProductType.PasswordManager, IsAnnual = true, @@ -344,13 +366,14 @@ public static IEnumerable CreatePlan() AdditionalStoragePricePerGb = 4, AllowSeatAutoscale = true, + LegacyYear = 2023 }, new Plan { - Type = PlanType.EnterpriseMonthly, + Type = PlanType.EnterpriseMonthly2020, Product = ProductType.Enterprise, BitwardenProduct = BitwardenProductType.PasswordManager, - Name = "Enterprise (Monthly)", + Name = "Enterprise (Monthly) 2020", NameLocalizationKey = "planNameEnterprise", DescriptionLocalizationKey = "planDescEnterprise", CanBeUsedByBusiness = true, @@ -385,6 +408,161 @@ public static IEnumerable CreatePlan() SeatPrice = 6, AdditionalStoragePricePerGb = 0.5M, + AllowSeatAutoscale = true, + LegacyYear = 2023 + }, + new Plan + { + Type = PlanType.TeamsAnnually, + Product = ProductType.Teams, + BitwardenProduct = BitwardenProductType.PasswordManager, + Name = "Teams (Annually)", + IsAnnual = true, + NameLocalizationKey = "planNameTeams", + DescriptionLocalizationKey = "planDescTeams", + CanBeUsedByBusiness = true, + BaseStorageGb = 1, + BaseSeats = 0, + + HasAdditionalSeatsOption = true, + HasAdditionalStorageOption = true, + TrialPeriodDays = 7, + + Has2fa = true, + HasApi = true, + HasDirectory = true, + HasEvents = true, + HasGroups = true, + HasTotp = true, + UsersGetPremium = true, + + UpgradeSortOrder = 2, + DisplaySortOrder = 2, + + StripeSeatPlanId = "2023-teams-org-seat-annually", + StripeStoragePlanId = "storage-gb-annually", + SeatPrice = 48, + AdditionalStoragePricePerGb = 4, + + AllowSeatAutoscale = true, + }, + new Plan + { + Type = PlanType.TeamsMonthly, + Product = ProductType.Teams, + BitwardenProduct = BitwardenProductType.PasswordManager, + Name = "Teams (Monthly)", + NameLocalizationKey = "planNameTeams", + DescriptionLocalizationKey = "planDescTeams", + CanBeUsedByBusiness = true, + BaseStorageGb = 1, + BaseSeats = 0, + + HasAdditionalSeatsOption = true, + HasAdditionalStorageOption = true, + TrialPeriodDays = 7, + + Has2fa = true, + HasApi = true, + HasDirectory = true, + HasEvents = true, + HasGroups = true, + HasTotp = true, + UsersGetPremium = true, + + UpgradeSortOrder = 2, + DisplaySortOrder = 2, + + StripeSeatPlanId = "2023-teams-org-seat-monthly", + StripeStoragePlanId = "storage-gb-monthly", + SeatPrice = 5, + AdditionalStoragePricePerGb = 0.5M, + + AllowSeatAutoscale = true, + }, + new Plan + { + Type = PlanType.EnterpriseAnnually, + Name = "Enterprise (Annually)", + Product = ProductType.Enterprise, + BitwardenProduct = BitwardenProductType.PasswordManager, + IsAnnual = true, + NameLocalizationKey = "planNameEnterprise", + DescriptionLocalizationKey = "planDescEnterprise", + CanBeUsedByBusiness = true, + BaseSeats = 0, + BaseStorageGb = 1, + + HasAdditionalSeatsOption = true, + HasAdditionalStorageOption = true, + TrialPeriodDays = 7, + + HasPolicies = true, + HasSelfHost = true, + HasGroups = true, + HasDirectory = true, + HasEvents = true, + HasTotp = true, + Has2fa = true, + HasApi = true, + HasSso = true, + HasKeyConnector = true, + HasScim = true, + HasResetPassword = true, + UsersGetPremium = true, + HasCustomPermissions = true, + + UpgradeSortOrder = 3, + DisplaySortOrder = 3, + + StripeSeatPlanId = "2023-enterprise-org-seat-annually", + StripeStoragePlanId = "storage-gb-annually", + BasePrice = 0, + SeatPrice = 72, + AdditionalStoragePricePerGb = 4, + + AllowSeatAutoscale = true, + }, + new Plan + { + Type = PlanType.EnterpriseMonthly, + Product = ProductType.Enterprise, + BitwardenProduct = BitwardenProductType.PasswordManager, + Name = "Enterprise (Monthly)", + NameLocalizationKey = "planNameEnterprise", + DescriptionLocalizationKey = "planDescEnterprise", + CanBeUsedByBusiness = true, + BaseSeats = 0, + BaseStorageGb = 1, + + HasAdditionalSeatsOption = true, + HasAdditionalStorageOption = true, + TrialPeriodDays = 7, + + HasPolicies = true, + HasGroups = true, + HasDirectory = true, + HasEvents = true, + HasTotp = true, + Has2fa = true, + HasApi = true, + HasSelfHost = true, + HasSso = true, + HasKeyConnector = true, + HasScim = true, + HasResetPassword = true, + UsersGetPremium = true, + HasCustomPermissions = true, + + UpgradeSortOrder = 3, + DisplaySortOrder = 3, + + StripeSeatPlanId = "2023-enterprise-seat-monthly", + StripeStoragePlanId = "storage-gb-monthly", + BasePrice = 0, + SeatPrice = 7, + AdditionalStoragePricePerGb = 0.5M, + AllowSeatAutoscale = true, }, new Plan diff --git a/src/Core/Utilities/SecretsManagerPlanStore.cs b/src/Core/Utilities/SecretsManagerPlanStore.cs index c37c4244623d..4effc14f53c6 100644 --- a/src/Core/Utilities/SecretsManagerPlanStore.cs +++ b/src/Core/Utilities/SecretsManagerPlanStore.cs @@ -9,6 +9,84 @@ public static IEnumerable CreatePlan() { return new List { + new Plan + { + Type = PlanType.EnterpriseMonthly2019, + Product = ProductType.Enterprise, + BitwardenProduct = BitwardenProductType.SecretsManager, + Name = "Enterprise (Monthly) 2019", + NameLocalizationKey = "planNameEnterprise", + DescriptionLocalizationKey = "planDescEnterprise", + CanBeUsedByBusiness = true, + BaseSeats = 0, + BaseServiceAccount = 200, + HasAdditionalSeatsOption = true, + HasAdditionalServiceAccountOption = true, + TrialPeriodDays = 7, + HasPolicies = true, + HasGroups = true, + HasDirectory = true, + HasEvents = true, + HasTotp = true, + Has2fa = true, + HasApi = true, + HasSelfHost = true, + HasSso = true, + HasKeyConnector = true, + HasScim = true, + HasResetPassword = true, + UsersGetPremium = true, + HasCustomPermissions = true, + UpgradeSortOrder = 3, + DisplaySortOrder = 3, + StripeSeatPlanId = "secrets-manager-enterprise-seat-monthly", + StripeServiceAccountPlanId = "secrets-manager-service-account-monthly", + BasePrice = 0, + SeatPrice = 13, + AdditionalPricePerServiceAccount = 0.5M, + AllowSeatAutoscale = true, + AllowServiceAccountsAutoscale = true, + LegacyYear = 2019 + }, + new Plan + { + Type = PlanType.EnterpriseMonthly2020, + Product = ProductType.Enterprise, + BitwardenProduct = BitwardenProductType.SecretsManager, + Name = "Enterprise (Monthly) 2020", + NameLocalizationKey = "planNameEnterprise", + DescriptionLocalizationKey = "planDescEnterprise", + CanBeUsedByBusiness = true, + BaseSeats = 0, + BaseServiceAccount = 200, + HasAdditionalSeatsOption = true, + HasAdditionalServiceAccountOption = true, + TrialPeriodDays = 7, + HasPolicies = true, + HasGroups = true, + HasDirectory = true, + HasEvents = true, + HasTotp = true, + Has2fa = true, + HasApi = true, + HasSelfHost = true, + HasSso = true, + HasKeyConnector = true, + HasScim = true, + HasResetPassword = true, + UsersGetPremium = true, + HasCustomPermissions = true, + UpgradeSortOrder = 3, + DisplaySortOrder = 3, + StripeSeatPlanId = "secrets-manager-enterprise-seat-monthly", + StripeServiceAccountPlanId = "secrets-manager-service-account-monthly", + BasePrice = 0, + SeatPrice = 13, + AdditionalPricePerServiceAccount = 0.5M, + AllowSeatAutoscale = true, + AllowServiceAccountsAutoscale = true, + LegacyYear = 2023 + }, new Plan { Type = PlanType.EnterpriseMonthly, @@ -48,6 +126,86 @@ public static IEnumerable CreatePlan() AllowServiceAccountsAutoscale = true }, new Plan + { + Type = PlanType.EnterpriseAnnually2019, + Name = "Enterprise (Annually) 2019", + Product = ProductType.Enterprise, + BitwardenProduct = BitwardenProductType.SecretsManager, + IsAnnual = true, + NameLocalizationKey = "planNameEnterprise", + DescriptionLocalizationKey = "planDescEnterprise", + CanBeUsedByBusiness = true, + BaseSeats = 0, + BaseServiceAccount = 200, + HasAdditionalSeatsOption = true, + HasAdditionalServiceAccountOption = true, + TrialPeriodDays = 7, + HasPolicies = true, + HasSelfHost = true, + HasGroups = true, + HasDirectory = true, + HasEvents = true, + HasTotp = true, + Has2fa = true, + HasApi = true, + HasSso = true, + HasKeyConnector = true, + HasScim = true, + HasResetPassword = true, + UsersGetPremium = true, + HasCustomPermissions = true, + UpgradeSortOrder = 3, + DisplaySortOrder = 3, + StripeSeatPlanId = "secrets-manager-enterprise-seat-annually", + StripeServiceAccountPlanId = "secrets-manager-service-account-annually", + BasePrice = 0, + SeatPrice = 144, + AdditionalPricePerServiceAccount = 6, + AllowSeatAutoscale = true, + AllowServiceAccountsAutoscale = true, + LegacyYear = 2019 + }, + new Plan + { + Type = PlanType.EnterpriseAnnually2020, + Name = "Enterprise (Annually) 2020", + Product = ProductType.Enterprise, + BitwardenProduct = BitwardenProductType.SecretsManager, + IsAnnual = true, + NameLocalizationKey = "planNameEnterprise", + DescriptionLocalizationKey = "planDescEnterprise", + CanBeUsedByBusiness = true, + BaseSeats = 0, + BaseServiceAccount = 200, + HasAdditionalSeatsOption = true, + HasAdditionalServiceAccountOption = true, + TrialPeriodDays = 7, + HasPolicies = true, + HasSelfHost = true, + HasGroups = true, + HasDirectory = true, + HasEvents = true, + HasTotp = true, + Has2fa = true, + HasApi = true, + HasSso = true, + HasKeyConnector = true, + HasScim = true, + HasResetPassword = true, + UsersGetPremium = true, + HasCustomPermissions = true, + UpgradeSortOrder = 3, + DisplaySortOrder = 3, + StripeSeatPlanId = "secrets-manager-enterprise-seat-annually", + StripeServiceAccountPlanId = "secrets-manager-service-account-annually", + BasePrice = 0, + SeatPrice = 144, + AdditionalPricePerServiceAccount = 6, + AllowSeatAutoscale = true, + AllowServiceAccountsAutoscale = true, + LegacyYear = 2023 + }, + new Plan { Type = PlanType.EnterpriseAnnually, Name = "Enterprise (Annually)", @@ -87,6 +245,70 @@ public static IEnumerable CreatePlan() AllowServiceAccountsAutoscale = true }, new Plan + { + Type = PlanType.TeamsMonthly2019, + Name = "Teams (Monthly) 2019", + Product = ProductType.Teams, + BitwardenProduct = BitwardenProductType.SecretsManager, + NameLocalizationKey = "planNameTeams", + DescriptionLocalizationKey = "planDescTeams", + CanBeUsedByBusiness = true, + BaseSeats = 0, + BaseServiceAccount = 50, + HasAdditionalSeatsOption = true, + HasAdditionalServiceAccountOption = true, + TrialPeriodDays = 7, + Has2fa = true, + HasApi = true, + HasDirectory = true, + HasEvents = true, + HasGroups = true, + HasTotp = true, + UsersGetPremium = true, + UpgradeSortOrder = 2, + DisplaySortOrder = 2, + StripeSeatPlanId = "secrets-manager-teams-seat-monthly", + StripeServiceAccountPlanId = "secrets-manager-service-account-monthly", + BasePrice = 0, + SeatPrice = 7, + AdditionalPricePerServiceAccount = 0.5M, + AllowSeatAutoscale = true, + AllowServiceAccountsAutoscale = true, + LegacyYear = 2019 + }, + new Plan + { + Type = PlanType.TeamsMonthly2020, + Name = "Teams (Monthly) 2020", + Product = ProductType.Teams, + BitwardenProduct = BitwardenProductType.SecretsManager, + NameLocalizationKey = "planNameTeams", + DescriptionLocalizationKey = "planDescTeams", + CanBeUsedByBusiness = true, + BaseSeats = 0, + BaseServiceAccount = 50, + HasAdditionalSeatsOption = true, + HasAdditionalServiceAccountOption = true, + TrialPeriodDays = 7, + Has2fa = true, + HasApi = true, + HasDirectory = true, + HasEvents = true, + HasGroups = true, + HasTotp = true, + UsersGetPremium = true, + UpgradeSortOrder = 2, + DisplaySortOrder = 2, + StripeSeatPlanId = "secrets-manager-teams-seat-monthly", + StripeServiceAccountPlanId = "secrets-manager-service-account-monthly", + BasePrice = 0, + SeatPrice = 7, + AdditionalPricePerServiceAccount = 0.5M, + AllowSeatAutoscale = true, + AllowServiceAccountsAutoscale = true, + LegacyYear = 2023 + }, + new Plan { Type = PlanType.TeamsMonthly, Name = "Teams (Monthly)", @@ -118,6 +340,74 @@ public static IEnumerable CreatePlan() AllowServiceAccountsAutoscale = true }, new Plan + { + Type = PlanType.TeamsAnnually2019, + Name = "Teams (Annually) 2019", + Product = ProductType.Teams, + BitwardenProduct = BitwardenProductType.SecretsManager, + IsAnnual = true, + NameLocalizationKey = "planNameTeams", + DescriptionLocalizationKey = "planDescTeams", + CanBeUsedByBusiness = true, + BaseSeats = 0, + BaseServiceAccount = 50, + HasAdditionalSeatsOption = true, + HasAdditionalServiceAccountOption = true, + TrialPeriodDays = 7, + Has2fa = true, + HasApi = true, + HasDirectory = true, + HasEvents = true, + HasGroups = true, + HasTotp = true, + UsersGetPremium = true, + + UpgradeSortOrder = 2, + DisplaySortOrder = 2, + StripeSeatPlanId = "secrets-manager-teams-seat-annually", + StripeServiceAccountPlanId = "secrets-manager-service-account-annually", + BasePrice = 0, + SeatPrice = 72, + AdditionalPricePerServiceAccount = 6, + AllowSeatAutoscale = true, + AllowServiceAccountsAutoscale = true, + LegacyYear = 2019 + }, + new Plan + { + Type = PlanType.TeamsAnnually2020, + Name = "Teams (Annually) 2020", + Product = ProductType.Teams, + BitwardenProduct = BitwardenProductType.SecretsManager, + IsAnnual = true, + NameLocalizationKey = "planNameTeams", + DescriptionLocalizationKey = "planDescTeams", + CanBeUsedByBusiness = true, + BaseSeats = 0, + BaseServiceAccount = 50, + HasAdditionalSeatsOption = true, + HasAdditionalServiceAccountOption = true, + TrialPeriodDays = 7, + Has2fa = true, + HasApi = true, + HasDirectory = true, + HasEvents = true, + HasGroups = true, + HasTotp = true, + UsersGetPremium = true, + + UpgradeSortOrder = 2, + DisplaySortOrder = 2, + StripeSeatPlanId = "secrets-manager-teams-seat-annually", + StripeServiceAccountPlanId = "secrets-manager-service-account-annually", + BasePrice = 0, + SeatPrice = 72, + AdditionalPricePerServiceAccount = 6, + AllowSeatAutoscale = true, + AllowServiceAccountsAutoscale = true, + LegacyYear = 2023 + }, + new Plan { Type = PlanType.TeamsAnnually, Name = "Teams (Annually)", diff --git a/test/Core.Test/Models/Business/SecretsManagerSubscriptionUpdateTests.cs b/test/Core.Test/Models/Business/SecretsManagerSubscriptionUpdateTests.cs index 0adef6b473ca..0b105f171100 100644 --- a/test/Core.Test/Models/Business/SecretsManagerSubscriptionUpdateTests.cs +++ b/test/Core.Test/Models/Business/SecretsManagerSubscriptionUpdateTests.cs @@ -15,10 +15,6 @@ public class SecretsManagerSubscriptionUpdateTests [BitAutoData(PlanType.Custom)] [BitAutoData(PlanType.FamiliesAnnually)] [BitAutoData(PlanType.FamiliesAnnually2019)] - [BitAutoData(PlanType.EnterpriseMonthly2019)] - [BitAutoData(PlanType.EnterpriseAnnually2019)] - [BitAutoData(PlanType.TeamsMonthly2019)] - [BitAutoData(PlanType.TeamsAnnually2019)] public async Task UpdateSubscriptionAsync_WithNonSecretsManagerPlanType_ThrowsBadRequestException( PlanType planType, Organization organization) diff --git a/test/Core.Test/Services/OrganizationServiceTests.cs b/test/Core.Test/Services/OrganizationServiceTests.cs index 4907efafcb75..9cab570ca3e8 100644 --- a/test/Core.Test/Services/OrganizationServiceTests.cs +++ b/test/Core.Test/Services/OrganizationServiceTests.cs @@ -1685,25 +1685,6 @@ public async Task HasConfirmedOwnersExcept_WithConfirmedProviderUser_IncludeProv Assert.Equal(includeProvider, result); } - [Theory] - [BitAutoData(PlanType.EnterpriseAnnually2019)] - public void ValidateSecretsManagerPlan_ThrowsException_WhenInvalidPlanSelected( - PlanType planType, SutProvider sutProvider) - { - var plan = StaticStore.Plans.FirstOrDefault(x => x.Type == planType); - - var signup = new OrganizationUpgrade - { - UseSecretsManager = true, - AdditionalSmSeats = 1, - AdditionalServiceAccounts = 10, - AdditionalSeats = 1 - }; - - var exception = Assert.Throws(() => sutProvider.Sut.ValidateSecretsManagerPlan(plan, signup)); - Assert.Contains("Invalid Secrets Manager plan selected.", exception.Message); - } - [Theory] [BitAutoData(PlanType.TeamsAnnually)] [BitAutoData(PlanType.TeamsMonthly)] @@ -1816,14 +1797,23 @@ public void ValidateSecretsManagerPlan_ThrowsException_WhenPlanDoesNotAllowAddit } [Theory] - [BitAutoData(PlanType.TeamsAnnually)] - [BitAutoData(PlanType.TeamsMonthly)] - [BitAutoData(PlanType.EnterpriseAnnually)] + [BitAutoData(PlanType.EnterpriseMonthly2019)] + [BitAutoData(PlanType.EnterpriseMonthly2020)] [BitAutoData(PlanType.EnterpriseMonthly)] + [BitAutoData(PlanType.EnterpriseAnnually2019)] + [BitAutoData(PlanType.EnterpriseAnnually2020)] + [BitAutoData(PlanType.EnterpriseAnnually)] + [BitAutoData(PlanType.TeamsMonthly2019)] + [BitAutoData(PlanType.TeamsMonthly2020)] + [BitAutoData(PlanType.TeamsMonthly)] + [BitAutoData(PlanType.TeamsAnnually2019)] + [BitAutoData(PlanType.TeamsAnnually2020)] + [BitAutoData(PlanType.TeamsAnnually)] public void ValidateSecretsManagerPlan_ValidPlan_NoExceptionThrown( PlanType planType, SutProvider sutProvider) { + // Arrange var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == planType); var signup = new OrganizationUpgrade { @@ -1833,7 +1823,11 @@ public void ValidateSecretsManagerPlan_ValidPlan_NoExceptionThrown( AdditionalSeats = 4 }; - sutProvider.Sut.ValidateSecretsManagerPlan(plan, signup); + // Act + var ex = Record.Exception(() => sutProvider.Sut.ValidateSecretsManagerPlan(plan, signup)); + + // Assert + Assert.Null(ex); } [Theory] diff --git a/test/Core.Test/Utilities/StaticStoreTests.cs b/test/Core.Test/Utilities/StaticStoreTests.cs index d30a0e6c733b..99b32969c647 100644 --- a/test/Core.Test/Utilities/StaticStoreTests.cs +++ b/test/Core.Test/Utilities/StaticStoreTests.cs @@ -14,7 +14,7 @@ public void StaticStore_Initialization_Success() var plans = StaticStore.Plans; Assert.NotNull(plans); Assert.NotEmpty(plans); - Assert.Equal(17, plans.Count()); + Assert.Equal(29, plans.Count()); } [Theory] diff --git a/util/Migrator/DbScripts/2023-09-29_00_2019TeamsPlanFeatureUpgrade.sql b/util/Migrator/DbScripts/2023-09-29_00_2019TeamsPlanFeatureUpgrade.sql new file mode 100644 index 000000000000..683e7be531e3 --- /dev/null +++ b/util/Migrator/DbScripts/2023-09-29_00_2019TeamsPlanFeatureUpgrade.sql @@ -0,0 +1,21 @@ +BEGIN TRY + BEGIN TRANSACTION; + + UPDATE + [dbo].[Organization] + SET + [Use2fa] = 1, + [UseApi] = 1, + [UseDirectory] = 1, + [UseEvents] = 1, + [UseGroups] = 1, + [UsersGetPremium] = 1 + WHERE + [PlanType] IN (2, 3); -- Teams 2019 + + COMMIT TRANSACTION; +END TRY +BEGIN CATCH + ROLLBACK TRANSACTION; + THROW; +END CATCH diff --git a/util/Migrator/DbScripts/2023-09-29_01_2019EnterprisePlanFeatureUpgrade.sql b/util/Migrator/DbScripts/2023-09-29_01_2019EnterprisePlanFeatureUpgrade.sql new file mode 100644 index 000000000000..94e58e89fa33 --- /dev/null +++ b/util/Migrator/DbScripts/2023-09-29_01_2019EnterprisePlanFeatureUpgrade.sql @@ -0,0 +1,19 @@ +BEGIN TRY + BEGIN TRANSACTION; + + UPDATE + [dbo].[Organization] + SET + [UseSso] = 1, + [UseKeyConnector] = 1, + [UseScim] = 1, + [UseResetPassword] = 1 + WHERE + [PlanType] IN (4, 5) -- Enterprise 2019 + + COMMIT TRANSACTION; +END TRY +BEGIN CATCH + ROLLBACK TRANSACTION; + THROW; +END CATCH