mirror of
https://github.com/bitwarden/server.git
synced 2025-05-20 19:14:32 -05:00
[AC-1650] [AC-1578] (#3320)
* Upgraded old 2019 plans to have the same features as 2020 and beyond * Removed redundant test and moved additional test cases to GetByOrgIdAsync_SmNoneFreePlans_ReturnsNull * Fixed issue where feature flag wasn't returning correct plans * Resolved issue where getting plans would return a value that LINQ previously cached when feature flag was in a different state --------- Co-authored-by: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com>
This commit is contained in:
parent
da4a86c643
commit
f9fc43dbb1
@ -30,29 +30,36 @@ public class MaxProjectsQueryTests
|
|||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData(PlanType.FamiliesAnnually2019)]
|
[BitAutoData(PlanType.FamiliesAnnually2019)]
|
||||||
[BitAutoData(PlanType.TeamsMonthly2019)]
|
|
||||||
[BitAutoData(PlanType.TeamsAnnually2019)]
|
|
||||||
[BitAutoData(PlanType.EnterpriseMonthly2019)]
|
|
||||||
[BitAutoData(PlanType.EnterpriseAnnually2019)]
|
|
||||||
[BitAutoData(PlanType.Custom)]
|
[BitAutoData(PlanType.Custom)]
|
||||||
[BitAutoData(PlanType.FamiliesAnnually)]
|
[BitAutoData(PlanType.FamiliesAnnually)]
|
||||||
public async Task GetByOrgIdAsync_SmPlanIsNull_ThrowsBadRequest(PlanType planType,
|
public async Task GetByOrgIdAsync_SmPlanIsNull_ThrowsBadRequest(PlanType planType,
|
||||||
SutProvider<MaxProjectsQuery> sutProvider, Organization organization)
|
SutProvider<MaxProjectsQuery> sutProvider, Organization organization)
|
||||||
{
|
{
|
||||||
organization.PlanType = planType;
|
organization.PlanType = planType;
|
||||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
sutProvider.GetDependency<IOrganizationRepository>()
|
||||||
|
.GetByIdAsync(organization.Id)
|
||||||
|
.Returns(organization);
|
||||||
|
|
||||||
await Assert.ThrowsAsync<BadRequestException>(
|
await Assert.ThrowsAsync<BadRequestException>(
|
||||||
async () => await sutProvider.Sut.GetByOrgIdAsync(organization.Id, 1));
|
async () => await sutProvider.Sut.GetByOrgIdAsync(organization.Id, 1));
|
||||||
|
|
||||||
await sutProvider.GetDependency<IProjectRepository>().DidNotReceiveWithAnyArgs()
|
await sutProvider.GetDependency<IProjectRepository>()
|
||||||
|
.DidNotReceiveWithAnyArgs()
|
||||||
.GetProjectCountByOrganizationIdAsync(organization.Id);
|
.GetProjectCountByOrganizationIdAsync(organization.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
[BitAutoData(PlanType.TeamsMonthly2019)]
|
||||||
|
[BitAutoData(PlanType.TeamsMonthly2020)]
|
||||||
[BitAutoData(PlanType.TeamsMonthly)]
|
[BitAutoData(PlanType.TeamsMonthly)]
|
||||||
|
[BitAutoData(PlanType.TeamsAnnually2019)]
|
||||||
|
[BitAutoData(PlanType.TeamsAnnually2020)]
|
||||||
[BitAutoData(PlanType.TeamsAnnually)]
|
[BitAutoData(PlanType.TeamsAnnually)]
|
||||||
|
[BitAutoData(PlanType.EnterpriseMonthly2019)]
|
||||||
|
[BitAutoData(PlanType.EnterpriseMonthly2020)]
|
||||||
[BitAutoData(PlanType.EnterpriseMonthly)]
|
[BitAutoData(PlanType.EnterpriseMonthly)]
|
||||||
|
[BitAutoData(PlanType.EnterpriseAnnually2019)]
|
||||||
|
[BitAutoData(PlanType.EnterpriseAnnually2020)]
|
||||||
[BitAutoData(PlanType.EnterpriseAnnually)]
|
[BitAutoData(PlanType.EnterpriseAnnually)]
|
||||||
public async Task GetByOrgIdAsync_SmNoneFreePlans_ReturnsNull(PlanType planType,
|
public async Task GetByOrgIdAsync_SmNoneFreePlans_ReturnsNull(PlanType planType,
|
||||||
SutProvider<MaxProjectsQuery> sutProvider, Organization organization)
|
SutProvider<MaxProjectsQuery> sutProvider, Organization organization)
|
||||||
|
@ -68,6 +68,10 @@
|
|||||||
|
|
||||||
function togglePlanFeatures(planType) {
|
function togglePlanFeatures(planType) {
|
||||||
switch(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.TeamsMonthly)':
|
||||||
case '@((byte)PlanType.TeamsAnnually)':
|
case '@((byte)PlanType.TeamsAnnually)':
|
||||||
document.getElementById('@(nameof(Model.UsePolicies))').checked = false;
|
document.getElementById('@(nameof(Model.UsePolicies))').checked = false;
|
||||||
@ -85,6 +89,10 @@
|
|||||||
document.getElementById('@(nameof(Model.UseScim))').checked = false;
|
document.getElementById('@(nameof(Model.UseScim))').checked = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case '@((byte)PlanType.EnterpriseMonthly2019)':
|
||||||
|
case '@((byte)PlanType.EnterpriseAnnually2019)':
|
||||||
|
case '@((byte)PlanType.EnterpriseMonthly2020)':
|
||||||
|
case '@((byte)PlanType.EnterpriseAnnually2020)':
|
||||||
case '@((byte)PlanType.EnterpriseMonthly)':
|
case '@((byte)PlanType.EnterpriseMonthly)':
|
||||||
case '@((byte)PlanType.EnterpriseAnnually)':
|
case '@((byte)PlanType.EnterpriseAnnually)':
|
||||||
document.getElementById('@(nameof(Model.UsePolicies))').checked = true;
|
document.getElementById('@(nameof(Model.UsePolicies))').checked = true;
|
||||||
|
@ -32,8 +32,7 @@ public class ProfileOrganizationResponseModel : ResponseModel
|
|||||||
UsePasswordManager = organization.UsePasswordManager;
|
UsePasswordManager = organization.UsePasswordManager;
|
||||||
UsersGetPremium = organization.UsersGetPremium;
|
UsersGetPremium = organization.UsersGetPremium;
|
||||||
UseCustomPermissions = organization.UseCustomPermissions;
|
UseCustomPermissions = organization.UseCustomPermissions;
|
||||||
UseActivateAutofillPolicy = organization.PlanType == PlanType.EnterpriseAnnually ||
|
UseActivateAutofillPolicy = StaticStore.GetPlan(organization.PlanType).Product == ProductType.Enterprise;
|
||||||
organization.PlanType == PlanType.EnterpriseMonthly;
|
|
||||||
SelfHost = organization.SelfHost;
|
SelfHost = organization.SelfHost;
|
||||||
Seats = organization.Seats;
|
Seats = organization.Seats;
|
||||||
MaxCollections = organization.MaxCollections;
|
MaxCollections = organization.MaxCollections;
|
||||||
|
@ -25,8 +25,7 @@ public class ProfileProviderOrganizationResponseModel : ProfileOrganizationRespo
|
|||||||
UseResetPassword = organization.UseResetPassword;
|
UseResetPassword = organization.UseResetPassword;
|
||||||
UsersGetPremium = organization.UsersGetPremium;
|
UsersGetPremium = organization.UsersGetPremium;
|
||||||
UseCustomPermissions = organization.UseCustomPermissions;
|
UseCustomPermissions = organization.UseCustomPermissions;
|
||||||
UseActivateAutofillPolicy = organization.PlanType == PlanType.EnterpriseAnnually ||
|
UseActivateAutofillPolicy = StaticStore.GetPlan(organization.PlanType).Product == ProductType.Enterprise;
|
||||||
organization.PlanType == PlanType.EnterpriseMonthly;
|
|
||||||
SelfHost = organization.SelfHost;
|
SelfHost = organization.SelfHost;
|
||||||
Seats = organization.Seats;
|
Seats = organization.Seats;
|
||||||
MaxCollections = organization.MaxCollections;
|
MaxCollections = organization.MaxCollections;
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
using Bit.Api.Models.Response;
|
using Bit.Api.Models.Response;
|
||||||
|
using Bit.Core;
|
||||||
|
using Bit.Core.Context;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
@ -11,17 +15,39 @@ namespace Bit.Api.Controllers;
|
|||||||
public class PlansController : Controller
|
public class PlansController : Controller
|
||||||
{
|
{
|
||||||
private readonly ITaxRateRepository _taxRateRepository;
|
private readonly ITaxRateRepository _taxRateRepository;
|
||||||
public PlansController(ITaxRateRepository taxRateRepository)
|
private readonly IFeatureService _featureService;
|
||||||
|
private readonly ICurrentContext _currentContext;
|
||||||
|
|
||||||
|
public PlansController(
|
||||||
|
ITaxRateRepository taxRateRepository,
|
||||||
|
IFeatureService featureService,
|
||||||
|
ICurrentContext currentContext)
|
||||||
{
|
{
|
||||||
_taxRateRepository = taxRateRepository;
|
_taxRateRepository = taxRateRepository;
|
||||||
|
_featureService = featureService;
|
||||||
|
_currentContext = currentContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("")]
|
[HttpGet("")]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
public ListResponseModel<PlanResponseModel> Get()
|
public ListResponseModel<PlanResponseModel> Get()
|
||||||
{
|
{
|
||||||
|
var plansUpgradeIsEnabled = _featureService.IsEnabled(FeatureFlagKeys.BillingPlansUpgrade, _currentContext);
|
||||||
var data = StaticStore.Plans;
|
var data = StaticStore.Plans;
|
||||||
var responses = data.Select(plan => new PlanResponseModel(plan));
|
var responses = data
|
||||||
|
.Where(plan => plansUpgradeIsEnabled || plan.Type <= PlanType.EnterpriseAnnually2020)
|
||||||
|
.Select(plan =>
|
||||||
|
{
|
||||||
|
if (!plansUpgradeIsEnabled && plan.Type is <= PlanType.EnterpriseAnnually2020 and >= PlanType.TeamsMonthly2020)
|
||||||
|
{
|
||||||
|
plan.LegacyYear = null;
|
||||||
|
}
|
||||||
|
else if (plansUpgradeIsEnabled && plan.Type is <= PlanType.EnterpriseAnnually2020 and >= PlanType.TeamsMonthly2020)
|
||||||
|
{
|
||||||
|
plan.LegacyYear = 2023;
|
||||||
|
}
|
||||||
|
return new PlanResponseModel(plan);
|
||||||
|
});
|
||||||
return new ListResponseModel<PlanResponseModel>(responses);
|
return new ListResponseModel<PlanResponseModel>(responses);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,14 +159,18 @@ public class FreshsalesController : Controller
|
|||||||
planName = "Families";
|
planName = "Families";
|
||||||
return true;
|
return true;
|
||||||
case PlanType.TeamsAnnually:
|
case PlanType.TeamsAnnually:
|
||||||
|
case PlanType.TeamsAnnually2020:
|
||||||
case PlanType.TeamsAnnually2019:
|
case PlanType.TeamsAnnually2019:
|
||||||
case PlanType.TeamsMonthly:
|
case PlanType.TeamsMonthly:
|
||||||
|
case PlanType.TeamsMonthly2020:
|
||||||
case PlanType.TeamsMonthly2019:
|
case PlanType.TeamsMonthly2019:
|
||||||
planName = "Teams";
|
planName = "Teams";
|
||||||
return true;
|
return true;
|
||||||
case PlanType.EnterpriseAnnually:
|
case PlanType.EnterpriseAnnually:
|
||||||
|
case PlanType.EnterpriseAnnually2020:
|
||||||
case PlanType.EnterpriseAnnually2019:
|
case PlanType.EnterpriseAnnually2019:
|
||||||
case PlanType.EnterpriseMonthly:
|
case PlanType.EnterpriseMonthly:
|
||||||
|
case PlanType.EnterpriseMonthly2020:
|
||||||
case PlanType.EnterpriseMonthly2019:
|
case PlanType.EnterpriseMonthly2019:
|
||||||
planName = "Enterprise";
|
planName = "Enterprise";
|
||||||
return true;
|
return true;
|
||||||
|
@ -668,18 +668,7 @@ public class StripeController : Controller
|
|||||||
return new Tuple<Guid?, Guid?>(orgId, userId);
|
return new Tuple<Guid?, Guid?>(orgId, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool OrgPlanForInvoiceNotifications(Organization org)
|
private static bool OrgPlanForInvoiceNotifications(Organization org) => StaticStore.GetPlan(org.PlanType).IsAnnual;
|
||||||
{
|
|
||||||
switch (org.PlanType)
|
|
||||||
{
|
|
||||||
case PlanType.FamiliesAnnually:
|
|
||||||
case PlanType.TeamsAnnually:
|
|
||||||
case PlanType.EnterpriseAnnually:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<bool> AttemptToPayInvoiceAsync(Invoice invoice, bool attemptToPayWithStripe = false)
|
private async Task<bool> AttemptToPayInvoiceAsync(Invoice invoice, bool attemptToPayWithStripe = false)
|
||||||
{
|
{
|
||||||
|
@ -49,6 +49,7 @@ public static class FeatureFlagKeys
|
|||||||
public const string BulkCollectionAccess = "bulk-collection-access";
|
public const string BulkCollectionAccess = "bulk-collection-access";
|
||||||
public const string AutofillOverlay = "autofill-overlay";
|
public const string AutofillOverlay = "autofill-overlay";
|
||||||
public const string ItemShare = "item-share";
|
public const string ItemShare = "item-share";
|
||||||
|
public const string BillingPlansUpgrade = "billing-plans-upgrade";
|
||||||
|
|
||||||
public static List<string> GetAllKeys()
|
public static List<string> GetAllKeys()
|
||||||
{
|
{
|
||||||
|
@ -20,12 +20,20 @@ public enum PlanType : byte
|
|||||||
Custom = 6,
|
Custom = 6,
|
||||||
[Display(Name = "Families")]
|
[Display(Name = "Families")]
|
||||||
FamiliesAnnually = 7,
|
FamiliesAnnually = 7,
|
||||||
|
[Display(Name = "Teams (Monthly) 2020")]
|
||||||
|
TeamsMonthly2020 = 8,
|
||||||
|
[Display(Name = "Teams (Annually) 2020")]
|
||||||
|
TeamsAnnually2020 = 9,
|
||||||
|
[Display(Name = "Enterprise (Monthly) 2020")]
|
||||||
|
EnterpriseMonthly2020 = 10,
|
||||||
|
[Display(Name = "Enterprise (Annually) 2020")]
|
||||||
|
EnterpriseAnnually2020 = 11,
|
||||||
[Display(Name = "Teams (Monthly)")]
|
[Display(Name = "Teams (Monthly)")]
|
||||||
TeamsMonthly = 8,
|
TeamsMonthly = 12,
|
||||||
[Display(Name = "Teams (Annually)")]
|
[Display(Name = "Teams (Annually)")]
|
||||||
TeamsAnnually = 9,
|
TeamsAnnually = 13,
|
||||||
[Display(Name = "Enterprise (Monthly)")]
|
[Display(Name = "Enterprise (Monthly)")]
|
||||||
EnterpriseMonthly = 10,
|
EnterpriseMonthly = 14,
|
||||||
[Display(Name = "Enterprise (Annually)")]
|
[Display(Name = "Enterprise (Annually)")]
|
||||||
EnterpriseAnnually = 11,
|
EnterpriseAnnually = 15,
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ public abstract record Plan
|
|||||||
public bool HasCustomPermissions { get; protected init; }
|
public bool HasCustomPermissions { get; protected init; }
|
||||||
public int UpgradeSortOrder { get; protected init; }
|
public int UpgradeSortOrder { get; protected init; }
|
||||||
public int DisplaySortOrder { get; protected init; }
|
public int DisplaySortOrder { get; protected init; }
|
||||||
public int? LegacyYear { get; protected init; }
|
public int? LegacyYear { get; set; }
|
||||||
public bool Disabled { get; protected init; }
|
public bool Disabled { get; protected init; }
|
||||||
public PasswordManagerPlanFeatures PasswordManager { get; protected init; }
|
public PasswordManagerPlanFeatures PasswordManager { get; protected init; }
|
||||||
public SecretsManagerPlanFeatures SecretsManager { get; protected init; }
|
public SecretsManagerPlanFeatures SecretsManager { get; protected init; }
|
||||||
|
@ -24,6 +24,10 @@ public record Enterprise2019Plan : Models.StaticStore.Plan
|
|||||||
HasTotp = true;
|
HasTotp = true;
|
||||||
Has2fa = true;
|
Has2fa = true;
|
||||||
HasApi = true;
|
HasApi = true;
|
||||||
|
HasSso = true;
|
||||||
|
HasKeyConnector = true;
|
||||||
|
HasScim = true;
|
||||||
|
HasResetPassword = true;
|
||||||
UsersGetPremium = true;
|
UsersGetPremium = true;
|
||||||
HasCustomPermissions = true;
|
HasCustomPermissions = true;
|
||||||
|
|
||||||
@ -31,9 +35,41 @@ public record Enterprise2019Plan : Models.StaticStore.Plan
|
|||||||
DisplaySortOrder = 3;
|
DisplaySortOrder = 3;
|
||||||
LegacyYear = 2020;
|
LegacyYear = 2020;
|
||||||
|
|
||||||
|
SecretsManager = new Enterprise2019SecretsManagerFeatures(isAnnual);
|
||||||
PasswordManager = new Enterprise2019PasswordManagerFeatures(isAnnual);
|
PasswordManager = new Enterprise2019PasswordManagerFeatures(isAnnual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record Enterprise2019SecretsManagerFeatures : SecretsManagerPlanFeatures
|
||||||
|
{
|
||||||
|
public Enterprise2019SecretsManagerFeatures(bool isAnnual)
|
||||||
|
{
|
||||||
|
BaseSeats = 0;
|
||||||
|
BasePrice = 0;
|
||||||
|
BaseServiceAccount = 200;
|
||||||
|
|
||||||
|
HasAdditionalSeatsOption = true;
|
||||||
|
HasAdditionalServiceAccountOption = true;
|
||||||
|
|
||||||
|
AllowSeatAutoscale = true;
|
||||||
|
AllowServiceAccountsAutoscale = true;
|
||||||
|
|
||||||
|
if (isAnnual)
|
||||||
|
{
|
||||||
|
StripeSeatPlanId = "secrets-manager-enterprise-seat-annually";
|
||||||
|
StripeServiceAccountPlanId = "secrets-manager-service-account-annually";
|
||||||
|
SeatPrice = 144;
|
||||||
|
AdditionalPricePerServiceAccount = 6;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StripeSeatPlanId = "secrets-manager-enterprise-seat-monthly";
|
||||||
|
StripeServiceAccountPlanId = "secrets-manager-service-account-monthly";
|
||||||
|
SeatPrice = 13;
|
||||||
|
AdditionalPricePerServiceAccount = 0.5M;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private record Enterprise2019PasswordManagerFeatures : PasswordManagerPlanFeatures
|
private record Enterprise2019PasswordManagerFeatures : PasswordManagerPlanFeatures
|
||||||
{
|
{
|
||||||
public Enterprise2019PasswordManagerFeatures(bool isAnnual)
|
public Enterprise2019PasswordManagerFeatures(bool isAnnual)
|
||||||
|
101
src/Core/Models/StaticStore/Plans/Enterprise2020Plan.cs
Normal file
101
src/Core/Models/StaticStore/Plans/Enterprise2020Plan.cs
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
|
namespace Bit.Core.Models.StaticStore.Plans;
|
||||||
|
|
||||||
|
public record Enterprise2020Plan : Models.StaticStore.Plan
|
||||||
|
{
|
||||||
|
public Enterprise2020Plan(bool isAnnual)
|
||||||
|
{
|
||||||
|
Type = isAnnual ? PlanType.EnterpriseAnnually2020 : PlanType.EnterpriseMonthly2020;
|
||||||
|
Product = ProductType.Enterprise;
|
||||||
|
Name = isAnnual ? "Enterprise (Annually) 2020" : "Enterprise (Monthly) 2020";
|
||||||
|
IsAnnual = isAnnual;
|
||||||
|
NameLocalizationKey = "planNameEnterprise";
|
||||||
|
DescriptionLocalizationKey = "planDescEnterprise";
|
||||||
|
CanBeUsedByBusiness = 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;
|
||||||
|
LegacyYear = 2023;
|
||||||
|
|
||||||
|
PasswordManager = new Enterprise2020PasswordManagerFeatures(isAnnual);
|
||||||
|
SecretsManager = new Enterprise2020SecretsManagerFeatures(isAnnual);
|
||||||
|
}
|
||||||
|
|
||||||
|
private record Enterprise2020SecretsManagerFeatures : SecretsManagerPlanFeatures
|
||||||
|
{
|
||||||
|
public Enterprise2020SecretsManagerFeatures(bool isAnnual)
|
||||||
|
{
|
||||||
|
BaseSeats = 0;
|
||||||
|
BasePrice = 0;
|
||||||
|
BaseServiceAccount = 200;
|
||||||
|
|
||||||
|
HasAdditionalSeatsOption = true;
|
||||||
|
HasAdditionalServiceAccountOption = true;
|
||||||
|
|
||||||
|
AllowSeatAutoscale = true;
|
||||||
|
AllowServiceAccountsAutoscale = true;
|
||||||
|
|
||||||
|
if (isAnnual)
|
||||||
|
{
|
||||||
|
StripeSeatPlanId = "secrets-manager-enterprise-seat-annually";
|
||||||
|
StripeServiceAccountPlanId = "secrets-manager-service-account-annually";
|
||||||
|
SeatPrice = 144;
|
||||||
|
AdditionalPricePerServiceAccount = 6;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StripeSeatPlanId = "secrets-manager-enterprise-seat-monthly";
|
||||||
|
StripeServiceAccountPlanId = "secrets-manager-service-account-monthly";
|
||||||
|
SeatPrice = 13;
|
||||||
|
AdditionalPricePerServiceAccount = 0.5M;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record Enterprise2020PasswordManagerFeatures : PasswordManagerPlanFeatures
|
||||||
|
{
|
||||||
|
public Enterprise2020PasswordManagerFeatures(bool isAnnual)
|
||||||
|
{
|
||||||
|
BaseSeats = 0;
|
||||||
|
BaseStorageGb = 1;
|
||||||
|
|
||||||
|
HasAdditionalStorageOption = true;
|
||||||
|
HasAdditionalSeatsOption = true;
|
||||||
|
|
||||||
|
AllowSeatAutoscale = true;
|
||||||
|
|
||||||
|
if (isAnnual)
|
||||||
|
{
|
||||||
|
AdditionalStoragePricePerGb = 4;
|
||||||
|
StripeStoragePlanId = "storage-gb-annually";
|
||||||
|
StripeSeatPlanId = "2020-enterprise-org-seat-annually";
|
||||||
|
SeatPrice = 60;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StripeSeatPlanId = "2020-enterprise-seat-monthly";
|
||||||
|
StripeStoragePlanId = "storage-gb-monthly";
|
||||||
|
SeatPrice = 6;
|
||||||
|
AdditionalStoragePricePerGb = 0.5M;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -85,14 +85,14 @@ public record EnterprisePlan : Models.StaticStore.Plan
|
|||||||
{
|
{
|
||||||
AdditionalStoragePricePerGb = 4;
|
AdditionalStoragePricePerGb = 4;
|
||||||
StripeStoragePlanId = "storage-gb-annually";
|
StripeStoragePlanId = "storage-gb-annually";
|
||||||
StripeSeatPlanId = "2020-enterprise-org-seat-annually";
|
StripeSeatPlanId = "2023-enterprise-org-seat-annually";
|
||||||
SeatPrice = 60;
|
SeatPrice = 72;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
StripeSeatPlanId = "2020-enterprise-seat-monthly";
|
StripeSeatPlanId = "2023-enterprise-seat-monthly";
|
||||||
StripeStoragePlanId = "storage-gb-monthly";
|
StripeStoragePlanId = "storage-gb-monthly";
|
||||||
SeatPrice = 6;
|
SeatPrice = 7;
|
||||||
AdditionalStoragePricePerGb = 0.5M;
|
AdditionalStoragePricePerGb = 0.5M;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ public record Families2019Plan : Models.StaticStore.Plan
|
|||||||
|
|
||||||
HasSelfHost = true;
|
HasSelfHost = true;
|
||||||
HasTotp = true;
|
HasTotp = true;
|
||||||
|
UsersGetPremium = true;
|
||||||
|
|
||||||
UpgradeSortOrder = 1;
|
UpgradeSortOrder = 1;
|
||||||
DisplaySortOrder = 1;
|
DisplaySortOrder = 1;
|
||||||
|
@ -16,15 +16,53 @@ public record Teams2019Plan : Models.StaticStore.Plan
|
|||||||
|
|
||||||
TrialPeriodDays = 7;
|
TrialPeriodDays = 7;
|
||||||
|
|
||||||
|
HasGroups = true;
|
||||||
|
HasDirectory = true;
|
||||||
|
HasEvents = true;
|
||||||
HasTotp = true;
|
HasTotp = true;
|
||||||
|
Has2fa = true;
|
||||||
|
HasApi = true;
|
||||||
|
UsersGetPremium = true;
|
||||||
|
|
||||||
UpgradeSortOrder = 2;
|
UpgradeSortOrder = 2;
|
||||||
DisplaySortOrder = 2;
|
DisplaySortOrder = 2;
|
||||||
LegacyYear = 2020;
|
LegacyYear = 2020;
|
||||||
|
|
||||||
|
SecretsManager = new Teams2019SecretsManagerFeatures(isAnnual);
|
||||||
PasswordManager = new Teams2019PasswordManagerFeatures(isAnnual);
|
PasswordManager = new Teams2019PasswordManagerFeatures(isAnnual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record Teams2019SecretsManagerFeatures : SecretsManagerPlanFeatures
|
||||||
|
{
|
||||||
|
public Teams2019SecretsManagerFeatures(bool isAnnual)
|
||||||
|
{
|
||||||
|
BaseSeats = 0;
|
||||||
|
BasePrice = 0;
|
||||||
|
BaseServiceAccount = 50;
|
||||||
|
|
||||||
|
HasAdditionalSeatsOption = true;
|
||||||
|
HasAdditionalServiceAccountOption = true;
|
||||||
|
|
||||||
|
AllowSeatAutoscale = true;
|
||||||
|
AllowServiceAccountsAutoscale = true;
|
||||||
|
|
||||||
|
if (isAnnual)
|
||||||
|
{
|
||||||
|
StripeSeatPlanId = "secrets-manager-teams-seat-annually";
|
||||||
|
StripeServiceAccountPlanId = "secrets-manager-service-account-annually";
|
||||||
|
SeatPrice = 72;
|
||||||
|
AdditionalPricePerServiceAccount = 6;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StripeSeatPlanId = "secrets-manager-teams-seat-monthly";
|
||||||
|
StripeServiceAccountPlanId = "secrets-manager-service-account-monthly";
|
||||||
|
SeatPrice = 7;
|
||||||
|
AdditionalPricePerServiceAccount = 0.5M;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private record Teams2019PasswordManagerFeatures : PasswordManagerPlanFeatures
|
private record Teams2019PasswordManagerFeatures : PasswordManagerPlanFeatures
|
||||||
{
|
{
|
||||||
public Teams2019PasswordManagerFeatures(bool isAnnual)
|
public Teams2019PasswordManagerFeatures(bool isAnnual)
|
||||||
|
95
src/Core/Models/StaticStore/Plans/Teams2020Plan.cs
Normal file
95
src/Core/Models/StaticStore/Plans/Teams2020Plan.cs
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
|
namespace Bit.Core.Models.StaticStore.Plans;
|
||||||
|
|
||||||
|
public record Teams2020Plan : Models.StaticStore.Plan
|
||||||
|
{
|
||||||
|
public Teams2020Plan(bool isAnnual)
|
||||||
|
{
|
||||||
|
Type = isAnnual ? PlanType.TeamsAnnually2020 : PlanType.TeamsMonthly2020;
|
||||||
|
Product = ProductType.Teams;
|
||||||
|
Name = isAnnual ? "Teams (Annually) 2020" : "Teams (Monthly) 2020";
|
||||||
|
IsAnnual = isAnnual;
|
||||||
|
NameLocalizationKey = "planNameTeams";
|
||||||
|
DescriptionLocalizationKey = "planDescTeams";
|
||||||
|
CanBeUsedByBusiness = true;
|
||||||
|
|
||||||
|
TrialPeriodDays = 7;
|
||||||
|
|
||||||
|
HasGroups = true;
|
||||||
|
HasDirectory = true;
|
||||||
|
HasEvents = true;
|
||||||
|
HasTotp = true;
|
||||||
|
Has2fa = true;
|
||||||
|
HasApi = true;
|
||||||
|
UsersGetPremium = true;
|
||||||
|
|
||||||
|
UpgradeSortOrder = 2;
|
||||||
|
DisplaySortOrder = 2;
|
||||||
|
LegacyYear = 2023;
|
||||||
|
|
||||||
|
PasswordManager = new Teams2020PasswordManagerFeatures(isAnnual);
|
||||||
|
SecretsManager = new Teams2020SecretsManagerFeatures(isAnnual);
|
||||||
|
}
|
||||||
|
|
||||||
|
private record Teams2020SecretsManagerFeatures : SecretsManagerPlanFeatures
|
||||||
|
{
|
||||||
|
public Teams2020SecretsManagerFeatures(bool isAnnual)
|
||||||
|
{
|
||||||
|
BaseSeats = 0;
|
||||||
|
BasePrice = 0;
|
||||||
|
BaseServiceAccount = 50;
|
||||||
|
|
||||||
|
HasAdditionalSeatsOption = true;
|
||||||
|
HasAdditionalServiceAccountOption = true;
|
||||||
|
|
||||||
|
AllowSeatAutoscale = true;
|
||||||
|
AllowServiceAccountsAutoscale = true;
|
||||||
|
|
||||||
|
if (isAnnual)
|
||||||
|
{
|
||||||
|
StripeSeatPlanId = "secrets-manager-teams-seat-annually";
|
||||||
|
StripeServiceAccountPlanId = "secrets-manager-service-account-annually";
|
||||||
|
SeatPrice = 72;
|
||||||
|
AdditionalPricePerServiceAccount = 6;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StripeSeatPlanId = "secrets-manager-teams-seat-monthly";
|
||||||
|
StripeServiceAccountPlanId = "secrets-manager-service-account-monthly";
|
||||||
|
SeatPrice = 7;
|
||||||
|
AdditionalPricePerServiceAccount = 0.5M;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record Teams2020PasswordManagerFeatures : PasswordManagerPlanFeatures
|
||||||
|
{
|
||||||
|
public Teams2020PasswordManagerFeatures(bool isAnnual)
|
||||||
|
{
|
||||||
|
BaseSeats = 0;
|
||||||
|
BaseStorageGb = 1;
|
||||||
|
BasePrice = 0;
|
||||||
|
|
||||||
|
HasAdditionalStorageOption = true;
|
||||||
|
HasAdditionalSeatsOption = true;
|
||||||
|
|
||||||
|
AllowSeatAutoscale = true;
|
||||||
|
|
||||||
|
if (isAnnual)
|
||||||
|
{
|
||||||
|
StripeStoragePlanId = "storage-gb-annually";
|
||||||
|
StripeSeatPlanId = "2020-teams-org-seat-annually";
|
||||||
|
SeatPrice = 36;
|
||||||
|
AdditionalStoragePricePerGb = 4;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StripeSeatPlanId = "2020-teams-org-seat-monthly";
|
||||||
|
StripeStoragePlanId = "storage-gb-monthly";
|
||||||
|
SeatPrice = 4;
|
||||||
|
AdditionalStoragePricePerGb = 0.5M;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -78,15 +78,15 @@ public record TeamsPlan : Models.StaticStore.Plan
|
|||||||
if (isAnnual)
|
if (isAnnual)
|
||||||
{
|
{
|
||||||
StripeStoragePlanId = "storage-gb-annually";
|
StripeStoragePlanId = "storage-gb-annually";
|
||||||
StripeSeatPlanId = "2020-teams-org-seat-annually";
|
StripeSeatPlanId = "2023-teams-org-seat-annually";
|
||||||
SeatPrice = 36;
|
SeatPrice = 48;
|
||||||
AdditionalStoragePricePerGb = 4;
|
AdditionalStoragePricePerGb = 4;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
StripeSeatPlanId = "2020-teams-org-seat-monthly";
|
StripeSeatPlanId = "2023-teams-org-seat-monthly";
|
||||||
StripeStoragePlanId = "storage-gb-monthly";
|
StripeStoragePlanId = "storage-gb-monthly";
|
||||||
SeatPrice = 4;
|
SeatPrice = 5;
|
||||||
AdditionalStoragePricePerGb = 0.5M;
|
AdditionalStoragePricePerGb = 0.5M;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,14 +98,6 @@ public class PolicyService : IPolicyService
|
|||||||
await DependsOnSingleOrgAsync(org);
|
await DependsOnSingleOrgAsync(org);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Activate Autofill is only available to Enterprise 2020-current plans
|
|
||||||
case PolicyType.ActivateAutofill:
|
|
||||||
if (policy.Enabled)
|
|
||||||
{
|
|
||||||
LockedTo2020Plan(org);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
@ -274,14 +266,6 @@ public class PolicyService : IPolicyService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
private async Task RequiredBySsoTrustedDeviceEncryptionAsync(Organization org)
|
||||||
{
|
{
|
||||||
var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(org.Id);
|
var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(org.Id);
|
||||||
|
@ -6,7 +6,7 @@ using Bit.Core.Models.StaticStore.Plans;
|
|||||||
|
|
||||||
namespace Bit.Core.Utilities;
|
namespace Bit.Core.Utilities;
|
||||||
|
|
||||||
public class StaticStore
|
public static class StaticStore
|
||||||
{
|
{
|
||||||
static StaticStore()
|
static StaticStore()
|
||||||
{
|
{
|
||||||
@ -112,6 +112,11 @@ public class StaticStore
|
|||||||
new EnterprisePlan(false),
|
new EnterprisePlan(false),
|
||||||
new TeamsPlan(true),
|
new TeamsPlan(true),
|
||||||
new TeamsPlan(false),
|
new TeamsPlan(false),
|
||||||
|
|
||||||
|
new Enterprise2020Plan(true),
|
||||||
|
new Enterprise2020Plan(false),
|
||||||
|
new Teams2020Plan(true),
|
||||||
|
new Teams2020Plan(false),
|
||||||
new FamiliesPlan(),
|
new FamiliesPlan(),
|
||||||
new FreePlan(),
|
new FreePlan(),
|
||||||
new CustomPlan(),
|
new CustomPlan(),
|
||||||
@ -139,8 +144,7 @@ public class StaticStore
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public static Models.StaticStore.Plan GetPlan(PlanType planType) =>
|
public static Plan GetPlan(PlanType planType) => Plans.SingleOrDefault(p => p.Type == planType);
|
||||||
Plans.SingleOrDefault(p => p.Type == planType);
|
|
||||||
|
|
||||||
|
|
||||||
public static SponsoredPlan GetSponsoredPlan(PlanSponsorshipType planSponsorshipType) =>
|
public static SponsoredPlan GetSponsoredPlan(PlanSponsorshipType planSponsorshipType) =>
|
||||||
|
@ -95,41 +95,44 @@ public class OrganizationRepository : Repository<Core.Entities.Organization, Org
|
|||||||
|
|
||||||
public async Task<ICollection<Core.Entities.Organization>> SearchUnassignedToProviderAsync(string name, string ownerEmail, int skip, int take)
|
public async Task<ICollection<Core.Entities.Organization>> SearchUnassignedToProviderAsync(string name, string ownerEmail, int skip, int take)
|
||||||
{
|
{
|
||||||
using (var scope = ServiceScopeFactory.CreateScope())
|
using var scope = ServiceScopeFactory.CreateScope();
|
||||||
|
var dbContext = GetDatabaseContext(scope);
|
||||||
|
var query = from o in dbContext.Organizations
|
||||||
|
where o.PlanType >= PlanType.TeamsMonthly2020 && o.PlanType <= PlanType.EnterpriseAnnually &&
|
||||||
|
!dbContext.ProviderOrganizations.Any(po => po.OrganizationId == o.Id) &&
|
||||||
|
(string.IsNullOrWhiteSpace(name) || EF.Functions.Like(o.Name, $"%{name}%"))
|
||||||
|
select o;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(ownerEmail))
|
||||||
{
|
{
|
||||||
var dbContext = GetDatabaseContext(scope);
|
return await query.OrderByDescending(o => o.CreationDate)
|
||||||
var query = from o in dbContext.Organizations
|
.Skip(skip)
|
||||||
where o.PlanType >= PlanType.TeamsMonthly && o.PlanType <= PlanType.EnterpriseAnnually &&
|
.Take(take)
|
||||||
!dbContext.ProviderOrganizations.Any(po => po.OrganizationId == o.Id) &&
|
.ToArrayAsync();
|
||||||
(string.IsNullOrWhiteSpace(name) || EF.Functions.Like(o.Name, $"%{name}%"))
|
|
||||||
select o;
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(ownerEmail))
|
|
||||||
{
|
|
||||||
if (dbContext.Database.IsNpgsql())
|
|
||||||
{
|
|
||||||
query = from o in query
|
|
||||||
join ou in dbContext.OrganizationUsers
|
|
||||||
on o.Id equals ou.OrganizationId
|
|
||||||
join u in dbContext.Users
|
|
||||||
on ou.UserId equals u.Id
|
|
||||||
where ou.Type == OrganizationUserType.Owner && EF.Functions.ILike(EF.Functions.Collate(u.Email, "default"), $"{ownerEmail}%")
|
|
||||||
select o;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
query = from o in query
|
|
||||||
join ou in dbContext.OrganizationUsers
|
|
||||||
on o.Id equals ou.OrganizationId
|
|
||||||
join u in dbContext.Users
|
|
||||||
on ou.UserId equals u.Id
|
|
||||||
where ou.Type == OrganizationUserType.Owner && EF.Functions.Like(u.Email, $"{ownerEmail}%")
|
|
||||||
select o;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return await query.OrderByDescending(o => o.CreationDate).Skip(skip).Take(take).ToArrayAsync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dbContext.Database.IsNpgsql())
|
||||||
|
{
|
||||||
|
query = from o in query
|
||||||
|
join ou in dbContext.OrganizationUsers
|
||||||
|
on o.Id equals ou.OrganizationId
|
||||||
|
join u in dbContext.Users
|
||||||
|
on ou.UserId equals u.Id
|
||||||
|
where ou.Type == OrganizationUserType.Owner && EF.Functions.ILike(EF.Functions.Collate(u.Email, "default"), $"{ownerEmail}%")
|
||||||
|
select o;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
query = from o in query
|
||||||
|
join ou in dbContext.OrganizationUsers
|
||||||
|
on o.Id equals ou.OrganizationId
|
||||||
|
join u in dbContext.Users
|
||||||
|
on ou.UserId equals u.Id
|
||||||
|
where ou.Type == OrganizationUserType.Owner && EF.Functions.Like(u.Email, $"{ownerEmail}%")
|
||||||
|
select o;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await query.OrderByDescending(o => o.CreationDate).Skip(skip).Take(take).ToArrayAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateStorageAsync(Guid id)
|
public async Task UpdateStorageAsync(Guid id)
|
||||||
|
@ -133,8 +133,8 @@ public class SecretsManagerOrganizationCustomization : ICustomization
|
|||||||
{
|
{
|
||||||
public void Customize(IFixture fixture)
|
public void Customize(IFixture fixture)
|
||||||
{
|
{
|
||||||
|
const PlanType planType = PlanType.EnterpriseAnnually;
|
||||||
var organizationId = Guid.NewGuid();
|
var organizationId = Guid.NewGuid();
|
||||||
var planType = PlanType.EnterpriseAnnually;
|
|
||||||
|
|
||||||
fixture.Customize<Organization>(composer => composer
|
fixture.Customize<Organization>(composer => composer
|
||||||
.With(o => o.Id, organizationId)
|
.With(o => o.Id, organizationId)
|
||||||
@ -143,8 +143,7 @@ public class SecretsManagerOrganizationCustomization : ICustomization
|
|||||||
.With(o => o.PlanType, planType)
|
.With(o => o.PlanType, planType)
|
||||||
.With(o => o.Plan, StaticStore.GetPlan(planType).Name)
|
.With(o => o.Plan, StaticStore.GetPlan(planType).Name)
|
||||||
.With(o => o.MaxAutoscaleSmSeats, (int?)null)
|
.With(o => o.MaxAutoscaleSmSeats, (int?)null)
|
||||||
.With(o => o.MaxAutoscaleSmServiceAccounts, (int?)null)
|
.With(o => o.MaxAutoscaleSmServiceAccounts, (int?)null));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,17 +15,45 @@ public class SecretsManagerSubscriptionUpdateTests
|
|||||||
[BitAutoData(PlanType.Custom)]
|
[BitAutoData(PlanType.Custom)]
|
||||||
[BitAutoData(PlanType.FamiliesAnnually)]
|
[BitAutoData(PlanType.FamiliesAnnually)]
|
||||||
[BitAutoData(PlanType.FamiliesAnnually2019)]
|
[BitAutoData(PlanType.FamiliesAnnually2019)]
|
||||||
[BitAutoData(PlanType.EnterpriseMonthly2019)]
|
public Task UpdateSubscriptionAsync_WithNonSecretsManagerPlanType_ThrowsBadRequestException(
|
||||||
[BitAutoData(PlanType.EnterpriseAnnually2019)]
|
|
||||||
[BitAutoData(PlanType.TeamsMonthly2019)]
|
|
||||||
[BitAutoData(PlanType.TeamsAnnually2019)]
|
|
||||||
public async Task UpdateSubscriptionAsync_WithNonSecretsManagerPlanType_ThrowsBadRequestException(
|
|
||||||
PlanType planType,
|
PlanType planType,
|
||||||
Organization organization)
|
Organization organization)
|
||||||
{
|
{
|
||||||
|
// Arrange
|
||||||
organization.PlanType = planType;
|
organization.PlanType = planType;
|
||||||
|
|
||||||
|
// Act
|
||||||
var exception = Assert.Throws<NotFoundException>(() => new SecretsManagerSubscriptionUpdate(organization, false));
|
var exception = Assert.Throws<NotFoundException>(() => new SecretsManagerSubscriptionUpdate(organization, false));
|
||||||
|
|
||||||
|
// Assert
|
||||||
Assert.Contains("Invalid Secrets Manager plan", exception.Message, StringComparison.InvariantCultureIgnoreCase);
|
Assert.Contains("Invalid Secrets Manager plan", exception.Message, StringComparison.InvariantCultureIgnoreCase);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[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 UpdateSubscription_WithNonSecretsManagerPlanType_DoesNotThrowException(
|
||||||
|
PlanType planType,
|
||||||
|
Organization organization)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
organization.PlanType = planType;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var ex = Record.Exception(() => new SecretsManagerSubscriptionUpdate(organization, false));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Null(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,10 @@ public class StaticStoreTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void StaticStore_Initialization_Success()
|
public void StaticStore_Initialization_Success()
|
||||||
{
|
{
|
||||||
var plans = StaticStore.Plans;
|
var plans = StaticStore.Plans.ToList();
|
||||||
Assert.NotNull(plans);
|
Assert.NotNull(plans);
|
||||||
Assert.NotEmpty(plans);
|
Assert.NotEmpty(plans);
|
||||||
Assert.Equal(12, plans.Count());
|
Assert.Equal(16, plans.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
@ -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
|
@ -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
|
@ -0,0 +1,16 @@
|
|||||||
|
BEGIN TRY
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
UPDATE
|
||||||
|
[dbo].[Organization]
|
||||||
|
SET
|
||||||
|
[UsersGetPremium] = 1
|
||||||
|
WHERE
|
||||||
|
[PlanType] = 1 -- Families 2019 Annual
|
||||||
|
|
||||||
|
COMMIT TRANSACTION;
|
||||||
|
END TRY
|
||||||
|
BEGIN CATCH
|
||||||
|
ROLLBACK TRANSACTION;
|
||||||
|
THROW;
|
||||||
|
END CATCH
|
Loading…
x
Reference in New Issue
Block a user