1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-03 00:52:49 -05:00

Merge branch 'feature/flexible-collections' into flexible-collections/deprecate-custom-collection-perm

This commit is contained in:
Rui Tome
2023-10-19 12:57:00 +01:00
79 changed files with 1656 additions and 1460 deletions

View File

@ -45,6 +45,9 @@ jobs:
version: ${{ github.event.inputs.version_number }}
file_path: "Directory.Build.props"
- name: Refresh lockfiles
run: dotnet restore -f --force-evaluate --no-cache
- name: Setup git
run: |
git config --local user.email "106330231+bitwarden-devops-bot@users.noreply.github.com"

View File

@ -28,8 +28,8 @@ public class MaxProjectsQuery : IMaxProjectsQuery
throw new NotFoundException();
}
var plan = StaticStore.GetSecretsManagerPlan(org.PlanType);
if (plan == null)
var plan = StaticStore.GetPlan(org.PlanType);
if (plan?.SecretsManager == null)
{
throw new BadRequestException("Existing plan not found.");
}
@ -37,7 +37,7 @@ public class MaxProjectsQuery : IMaxProjectsQuery
if (plan.Type == PlanType.Free)
{
var projects = await _projectRepository.GetProjectCountByOrganizationIdAsync(organizationId);
return projects + projectsToAdd > plan.MaxProjects ? (plan.MaxProjects, true) : (plan.MaxProjects, false);
return ((short? max, bool? overMax))(projects + projectsToAdd > plan.SecretsManager.MaxProjects ? (plan.SecretsManager.MaxProjects, true) : (plan.SecretsManager.MaxProjects, false));
}
return (null, null);

View File

@ -1,6 +1,6 @@
{
"sdk": {
"version": "6.0.413",
"version": "6.0.415",
"rollForward": "latestFeature"
}
}

View File

@ -205,9 +205,8 @@ public class OrganizationsController : Controller
var organization = await GetOrganization(id, model);
if (organization.UseSecretsManager &&
!organization.SecretsManagerBeta
&& StaticStore.GetSecretsManagerPlan(organization.PlanType) == null
)
!organization.SecretsManagerBeta &&
!StaticStore.GetPlan(organization.PlanType).SupportsSecretsManager)
{
throw new BadRequestException("Plan does not support Secrets Manager");
}

View File

@ -158,11 +158,12 @@ public class OrganizationEditModel : OrganizationViewModel
* Add mappings for individual properties as you need them
*/
public IEnumerable<Dictionary<string, object>> GetPlansHelper() =>
StaticStore.SecretManagerPlans.Select(p =>
new Dictionary<string, object>
StaticStore.Plans
.Where(p => p.SupportsSecretsManager)
.Select(p => new Dictionary<string, object>
{
{ "type", p.Type },
{ "baseServiceAccount", p.BaseServiceAccount }
{ "baseServiceAccount", p.SecretsManager.BaseServiceAccount }
});
public Organization CreateOrganization(Provider provider)

View File

@ -540,7 +540,7 @@ public class OrganizationsController : Controller
if (model.Type == OrganizationApiKeyType.BillingSync || model.Type == OrganizationApiKeyType.Scim)
{
// Non-enterprise orgs should not be able to create or view an apikey of billing sync/scim key types
var plan = StaticStore.GetPasswordManagerPlan(organization.PlanType);
var plan = StaticStore.GetPlan(organization.PlanType);
if (plan.Product != ProductType.Enterprise)
{
throw new NotFoundException();

View File

@ -19,30 +19,12 @@ public class PlansController : Controller
[HttpGet("")]
[AllowAnonymous]
public ListResponseModel<PlanResponseModel> Get()
{
var data = StaticStore.PasswordManagerPlans;
var responses = data.Select(plan => new PlanResponseModel(plan));
return new ListResponseModel<PlanResponseModel>(responses);
}
[HttpGet("all")]
[AllowAnonymous]
public ListResponseModel<PlanResponseModel> GetAllPlans()
{
var data = StaticStore.Plans;
var responses = data.Select(plan => new PlanResponseModel(plan));
return new ListResponseModel<PlanResponseModel>(responses);
}
[HttpGet("sm-plans")]
[AllowAnonymous]
public ListResponseModel<PlanResponseModel> GetSecretsManagerPlans()
{
var data = StaticStore.SecretManagerPlans;
var responses = data.Select(plan => new PlanResponseModel(plan));
return new ListResponseModel<PlanResponseModel>(responses);
}
[HttpGet("sales-tax-rates")]
public async Task<ListResponseModel<TaxRateResponseModel>> GetTaxRates()
{

View File

@ -26,12 +26,7 @@ public class OrganizationResponseModel : ResponseModel
BusinessCountry = organization.BusinessCountry;
BusinessTaxNumber = organization.BusinessTaxNumber;
BillingEmail = organization.BillingEmail;
Plan = new PlanResponseModel(StaticStore.PasswordManagerPlans.FirstOrDefault(plan => plan.Type == organization.PlanType));
var matchingPlan = StaticStore.GetSecretsManagerPlan(organization.PlanType);
if (matchingPlan != null)
{
SecretsManagerPlan = new PlanResponseModel(matchingPlan);
}
Plan = new PlanResponseModel(StaticStore.GetPlan(organization.PlanType));
PlanType = organization.PlanType;
Seats = organization.Seats;
MaxAutoscaleSeats = organization.MaxAutoscaleSeats;

View File

@ -21,15 +21,6 @@ public class PlanResponseModel : ResponseModel
NameLocalizationKey = plan.NameLocalizationKey;
DescriptionLocalizationKey = plan.DescriptionLocalizationKey;
CanBeUsedByBusiness = plan.CanBeUsedByBusiness;
BaseSeats = plan.BaseSeats;
BaseStorageGb = plan.BaseStorageGb;
MaxCollections = plan.MaxCollections;
MaxUsers = plan.MaxUsers;
HasAdditionalSeatsOption = plan.HasAdditionalSeatsOption;
HasAdditionalStorageOption = plan.HasAdditionalStorageOption;
MaxAdditionalSeats = plan.MaxAdditionalSeats;
MaxAdditionalStorage = plan.MaxAdditionalStorage;
HasPremiumAccessOption = plan.HasPremiumAccessOption;
TrialPeriodDays = plan.TrialPeriodDays;
HasSelfHost = plan.HasSelfHost;
HasPolicies = plan.HasPolicies;
@ -45,22 +36,12 @@ public class PlanResponseModel : ResponseModel
DisplaySortOrder = plan.DisplaySortOrder;
LegacyYear = plan.LegacyYear;
Disabled = plan.Disabled;
StripePlanId = plan.StripePlanId;
StripeSeatPlanId = plan.StripeSeatPlanId;
StripeStoragePlanId = plan.StripeStoragePlanId;
BasePrice = plan.BasePrice;
SeatPrice = plan.SeatPrice;
AdditionalStoragePricePerGb = plan.AdditionalStoragePricePerGb;
PremiumAccessOptionPrice = plan.PremiumAccessOptionPrice;
if (plan.SecretsManager != null)
{
SecretsManager = new SecretsManagerPlanFeaturesResponseModel(plan.SecretsManager);
}
AdditionalPricePerServiceAccount = plan.AdditionalPricePerServiceAccount;
BaseServiceAccount = plan.BaseServiceAccount;
MaxServiceAccounts = plan.MaxServiceAccounts;
MaxAdditionalServiceAccounts = plan.MaxAdditionalServiceAccount;
HasAdditionalServiceAccountOption = plan.HasAdditionalServiceAccountOption;
MaxProjects = plan.MaxProjects;
BitwardenProduct = plan.BitwardenProduct;
StripeServiceAccountPlanId = plan.StripeServiceAccountPlanId;
PasswordManager = new PasswordManagerPlanFeaturesResponseModel(plan.PasswordManager);
}
public PlanType Type { get; set; }
@ -70,16 +51,6 @@ public class PlanResponseModel : ResponseModel
public string NameLocalizationKey { get; set; }
public string DescriptionLocalizationKey { get; set; }
public bool CanBeUsedByBusiness { get; set; }
public int BaseSeats { get; set; }
public short? BaseStorageGb { get; set; }
public short? MaxCollections { get; set; }
public short? MaxUsers { get; set; }
public bool HasAdditionalSeatsOption { get; set; }
public int? MaxAdditionalSeats { get; set; }
public bool HasAdditionalStorageOption { get; set; }
public short? MaxAdditionalStorage { get; set; }
public bool HasPremiumAccessOption { get; set; }
public int? TrialPeriodDays { get; set; }
public bool HasSelfHost { get; set; }
@ -98,21 +69,95 @@ public class PlanResponseModel : ResponseModel
public int DisplaySortOrder { get; set; }
public int? LegacyYear { get; set; }
public bool Disabled { get; set; }
public SecretsManagerPlanFeaturesResponseModel SecretsManager { get; protected init; }
public PasswordManagerPlanFeaturesResponseModel PasswordManager { get; protected init; }
public string StripePlanId { get; set; }
public string StripeSeatPlanId { get; set; }
public string StripeStoragePlanId { get; set; }
public string StripePremiumAccessPlanId { get; set; }
public decimal BasePrice { get; set; }
public decimal SeatPrice { get; set; }
public decimal AdditionalStoragePricePerGb { get; set; }
public decimal PremiumAccessOptionPrice { get; set; }
public string StripeServiceAccountPlanId { get; set; }
public decimal? AdditionalPricePerServiceAccount { get; set; }
public short? BaseServiceAccount { get; set; }
public short? MaxServiceAccounts { get; set; }
public short? MaxAdditionalServiceAccounts { get; set; }
public bool HasAdditionalServiceAccountOption { get; set; }
public short? MaxProjects { get; set; }
public BitwardenProductType BitwardenProduct { get; set; }
public class SecretsManagerPlanFeaturesResponseModel
{
public SecretsManagerPlanFeaturesResponseModel(Plan.SecretsManagerPlanFeatures plan)
{
MaxServiceAccounts = plan.MaxServiceAccounts;
AllowServiceAccountsAutoscale = plan is { AllowServiceAccountsAutoscale: true };
StripeServiceAccountPlanId = plan.StripeServiceAccountPlanId;
AdditionalPricePerServiceAccount = plan.AdditionalPricePerServiceAccount;
BaseServiceAccount = plan.BaseServiceAccount;
MaxAdditionalServiceAccount = plan.MaxAdditionalServiceAccount;
HasAdditionalServiceAccountOption = plan is { HasAdditionalServiceAccountOption: true };
StripeSeatPlanId = plan.StripeSeatPlanId;
HasAdditionalSeatsOption = plan is { HasAdditionalSeatsOption: true };
BasePrice = plan.BasePrice;
SeatPrice = plan.SeatPrice;
BaseSeats = plan.BaseSeats;
MaxSeats = plan.MaxSeats;
MaxAdditionalSeats = plan.MaxAdditionalSeats;
AllowSeatAutoscale = plan.AllowSeatAutoscale;
MaxProjects = plan.MaxProjects;
}
// Service accounts
public short? MaxServiceAccounts { get; init; }
public bool AllowServiceAccountsAutoscale { get; init; }
public string StripeServiceAccountPlanId { get; init; }
public decimal? AdditionalPricePerServiceAccount { get; init; }
public short? BaseServiceAccount { get; init; }
public short? MaxAdditionalServiceAccount { get; init; }
public bool HasAdditionalServiceAccountOption { get; init; }
// Seats
public string StripeSeatPlanId { get; init; }
public bool HasAdditionalSeatsOption { get; init; }
public decimal BasePrice { get; init; }
public decimal SeatPrice { get; init; }
public int BaseSeats { get; init; }
public short? MaxSeats { get; init; }
public int? MaxAdditionalSeats { get; init; }
public bool AllowSeatAutoscale { get; init; }
// Features
public int MaxProjects { get; init; }
}
public record PasswordManagerPlanFeaturesResponseModel
{
public PasswordManagerPlanFeaturesResponseModel(Plan.PasswordManagerPlanFeatures plan)
{
StripePlanId = plan.StripePlanId;
StripeSeatPlanId = plan.StripeSeatPlanId;
BasePrice = plan.BasePrice;
SeatPrice = plan.SeatPrice;
AllowSeatAutoscale = plan.AllowSeatAutoscale;
HasAdditionalSeatsOption = plan.HasAdditionalSeatsOption;
MaxAdditionalSeats = plan.MaxAdditionalSeats;
BaseSeats = plan.BaseSeats;
HasPremiumAccessOption = plan.HasPremiumAccessOption;
StripePremiumAccessPlanId = plan.StripePremiumAccessPlanId;
PremiumAccessOptionPrice = plan.PremiumAccessOptionPrice;
MaxSeats = plan.MaxSeats;
BaseStorageGb = plan.BaseStorageGb;
HasAdditionalStorageOption = plan.HasAdditionalStorageOption;
AdditionalStoragePricePerGb = plan.AdditionalStoragePricePerGb;
StripeStoragePlanId = plan.StripeStoragePlanId;
MaxAdditionalStorage = plan.MaxAdditionalStorage;
MaxCollections = plan.MaxCollections;
}
// Seats
public string StripePlanId { get; init; }
public string StripeSeatPlanId { get; init; }
public decimal BasePrice { get; init; }
public decimal SeatPrice { get; init; }
public bool AllowSeatAutoscale { get; init; }
public bool HasAdditionalSeatsOption { get; init; }
public int? MaxAdditionalSeats { get; init; }
public int BaseSeats { get; init; }
public bool HasPremiumAccessOption { get; init; }
public string StripePremiumAccessPlanId { get; init; }
public decimal PremiumAccessOptionPrice { get; init; }
public short? MaxSeats { get; init; }
// Storage
public short? BaseStorageGb { get; init; }
public bool HasAdditionalStorageOption { get; init; }
public decimal AdditionalStoragePricePerGb { get; init; }
public string StripeStoragePlanId { get; init; }
public short? MaxAdditionalStorage { get; init; }
// Feature
public short? MaxCollections { get; init; }
}
}

View File

@ -55,7 +55,7 @@ public class ProfileOrganizationResponseModel : ResponseModel
FamilySponsorshipAvailable = FamilySponsorshipFriendlyName == null &&
StaticStore.GetSponsoredPlan(PlanSponsorshipType.FamiliesForEnterprise)
.UsersCanSponsor(organization);
PlanProductType = StaticStore.GetPasswordManagerPlan(organization.PlanType).Product;
PlanProductType = StaticStore.GetPlan(organization.PlanType).Product;
FamilySponsorshipLastSyncDate = organization.FamilySponsorshipLastSyncDate;
FamilySponsorshipToDelete = organization.FamilySponsorshipToDelete;
FamilySponsorshipValidUntil = organization.FamilySponsorshipValidUntil;

View File

@ -42,6 +42,6 @@ public class ProfileProviderOrganizationResponseModel : ProfileOrganizationRespo
UserId = organization.UserId;
ProviderId = organization.ProviderId;
ProviderName = organization.ProviderName;
PlanProductType = StaticStore.GetPasswordManagerPlan(organization.PlanType).Product;
PlanProductType = StaticStore.GetPlan(organization.PlanType).Product;
}
}

View File

@ -1,5 +1,4 @@
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Api;
using Bit.Core.Models.Business;
using Bit.Core.Utilities;
@ -98,7 +97,6 @@ public class BillingSubscription
Quantity = item.Quantity;
SponsoredSubscriptionItem = item.SponsoredSubscriptionItem;
AddonSubscriptionItem = item.AddonSubscriptionItem;
BitwardenProduct = item.BitwardenProduct;
}
public string Name { get; set; }
@ -107,7 +105,6 @@ public class BillingSubscription
public string Interval { get; set; }
public bool SponsoredSubscriptionItem { get; set; }
public bool AddonSubscriptionItem { get; set; }
public BitwardenProductType BitwardenProduct { get; set; }
}
}

View File

@ -10,14 +10,12 @@ using Bit.Core.SecretsManager.Commands.AccessPolicies.Interfaces;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.SecretsManager.Controllers;
[Authorize("secrets")]
[SelfHosted(NotSelfHostedOnly = true)]
[Route("access-policies")]
public class AccessPoliciesController : Controller
{

View File

@ -10,14 +10,12 @@ using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Queries.Projects.Interfaces;
using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.SecretsManager.Controllers;
[Authorize("secrets")]
[SelfHosted(NotSelfHostedOnly = true)]
public class ProjectsController : Controller
{
private readonly ICurrentContext _currentContext;

View File

@ -14,14 +14,12 @@ using Bit.Core.Services;
using Bit.Core.Tools.Enums;
using Bit.Core.Tools.Models.Business;
using Bit.Core.Tools.Services;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.SecretsManager.Controllers;
[Authorize("secrets")]
[SelfHosted(NotSelfHostedOnly = true)]
public class SecretsController : Controller
{
private readonly ICurrentContext _currentContext;

View File

@ -7,14 +7,12 @@ using Bit.Core.SecretsManager.Commands.Porting.Interfaces;
using Bit.Core.SecretsManager.Queries.Projects.Interfaces;
using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.SecretsManager.Controllers;
[Authorize("secrets")]
[SelfHosted(NotSelfHostedOnly = true)]
public class SecretsManagerPortingController : Controller
{
private readonly ISecretRepository _secretRepository;

View File

@ -3,14 +3,12 @@ using Bit.Core.Context;
using Bit.Core.Exceptions;
using Bit.Core.SecretsManager.Commands.Trash.Interfaces;
using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.SecretsManager.Controllers;
[Authorize("secrets")]
[SelfHosted(NotSelfHostedOnly = true)]
public class TrashController : Controller
{
private readonly ICurrentContext _currentContext;

View File

@ -14,14 +14,12 @@ using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Queries.ServiceAccounts.Interfaces;
using Bit.Core.SecretsManager.Repositories;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.SecretsManager.Controllers;
[Authorize("secrets")]
[SelfHosted(NotSelfHostedOnly = true)]
[Route("service-accounts")]
public class ServiceAccountsController : Controller
{

View File

@ -27,6 +27,8 @@ namespace Bit.Api.Vault.Controllers;
[Authorize("Application")]
public class CiphersController : Controller
{
private static readonly Version _fido2KeyCipherMinimumVersion = new Version(Constants.Fido2KeyCipherMinimumVersion);
private readonly ICipherRepository _cipherRepository;
private readonly ICollectionCipherRepository _collectionCipherRepository;
private readonly ICipherService _cipherService;
@ -178,7 +180,8 @@ public class CiphersController : Controller
throw new NotFoundException();
}
ValidateItemLevelEncryptionIsAvailable(cipher);
ValidateClientVersionForItemLevelEncryptionSupport(cipher);
ValidateClientVersionForFido2CredentialSupport(cipher);
var collectionIds = (await _collectionCipherRepository.GetManyByUserIdCipherIdAsync(userId, id)).Select(c => c.CollectionId).ToList();
var modelOrgId = string.IsNullOrWhiteSpace(model.OrganizationId) ?
@ -202,7 +205,8 @@ public class CiphersController : Controller
var userId = _userService.GetProperUserId(User).Value;
var cipher = await _cipherRepository.GetOrganizationDetailsByIdAsync(id);
ValidateItemLevelEncryptionIsAvailable(cipher);
ValidateClientVersionForItemLevelEncryptionSupport(cipher);
ValidateClientVersionForFido2CredentialSupport(cipher);
if (cipher == null || !cipher.OrganizationId.HasValue ||
!await _currentContext.EditAnyCollection(cipher.OrganizationId.Value))
@ -267,6 +271,9 @@ public class CiphersController : Controller
throw new NotFoundException();
}
ValidateClientVersionForItemLevelEncryptionSupport(cipher);
ValidateClientVersionForFido2CredentialSupport(cipher);
var original = cipher.Clone();
await _cipherService.ShareAsync(original, model.Cipher.ToCipher(cipher), new Guid(model.Cipher.OrganizationId),
model.CollectionIds.Select(c => new Guid(c)), userId, model.Cipher.LastKnownRevisionDate);
@ -529,7 +536,12 @@ public class CiphersController : Controller
throw new BadRequestException("Trying to move ciphers that you do not own.");
}
shareCiphers.Add((cipher.ToCipher(ciphersDict[cipher.Id.Value]), cipher.LastKnownRevisionDate));
var existingCipher = ciphersDict[cipher.Id.Value];
ValidateClientVersionForItemLevelEncryptionSupport(existingCipher);
ValidateClientVersionForFido2CredentialSupport(existingCipher);
shareCiphers.Add((cipher.ToCipher(existingCipher), cipher.LastKnownRevisionDate));
}
await _cipherService.ShareManyAsync(shareCiphers, organizationId,
@ -582,7 +594,7 @@ public class CiphersController : Controller
throw new NotFoundException();
}
ValidateItemLevelEncryptionIsAvailable(cipher);
ValidateClientVersionForItemLevelEncryptionSupport(cipher);
if (request.FileSize > CipherService.MAX_FILE_SIZE)
{
@ -804,11 +816,23 @@ public class CiphersController : Controller
}
}
private void ValidateItemLevelEncryptionIsAvailable(Cipher cipher)
private void ValidateClientVersionForItemLevelEncryptionSupport(Cipher cipher)
{
if (cipher.Key != null && _currentContext.ClientVersion < _cipherKeyEncryptionMinimumVersion)
{
throw new BadRequestException("Cannot edit item. Update to the latest version of Bitwarden and try again.");
}
}
private void ValidateClientVersionForFido2CredentialSupport(Cipher cipher)
{
if (cipher.Type == Core.Vault.Enums.CipherType.Login)
{
var loginData = JsonSerializer.Deserialize<CipherLoginData>(cipher.Data);
if (loginData?.Fido2Credentials != null && _currentContext.ClientVersion < _fido2KeyCipherMinimumVersion)
{
throw new BadRequestException("Cannot edit item. Update to the latest version of Bitwarden and try again.");
}
}
}
}

View File

@ -0,0 +1,89 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.Utilities;
using Bit.Core.Vault.Models.Data;
namespace Bit.Api.Vault.Models;
public class CipherFido2CredentialModel
{
public CipherFido2CredentialModel() { }
public CipherFido2CredentialModel(CipherLoginFido2CredentialData data)
{
CredentialId = data.CredentialId;
KeyType = data.KeyType;
KeyAlgorithm = data.KeyAlgorithm;
KeyCurve = data.KeyCurve;
KeyValue = data.KeyValue;
RpId = data.RpId;
RpName = data.RpName;
UserHandle = data.UserHandle;
UserDisplayName = data.UserDisplayName;
Counter = data.Counter;
Discoverable = data.Discoverable;
CreationDate = data.CreationDate;
}
[EncryptedString]
[EncryptedStringLength(1000)]
public string CredentialId { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string KeyType { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string KeyAlgorithm { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string KeyCurve { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string KeyValue { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string RpId { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string RpName { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string UserHandle { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string UserDisplayName { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string Counter { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string Discoverable { get; set; }
[Required]
public DateTime CreationDate { get; set; }
public CipherLoginFido2CredentialData ToCipherLoginFido2CredentialData()
{
return new CipherLoginFido2CredentialData
{
CredentialId = CredentialId,
KeyType = KeyType,
KeyAlgorithm = KeyAlgorithm,
KeyCurve = KeyCurve,
KeyValue = KeyValue,
RpId = RpId,
RpName = RpName,
UserHandle = UserHandle,
UserDisplayName = UserDisplayName,
Counter = Counter,
Discoverable = Discoverable,
CreationDate = CreationDate
};
}
}
static class CipherFido2CredentialModelExtensions
{
public static CipherLoginFido2CredentialData[] ToCipherLoginFido2CredentialData(this CipherFido2CredentialModel[] models)
{
return models.Select(m => m.ToCipherLoginFido2CredentialData()).ToArray();
}
}

View File

@ -16,6 +16,11 @@ public class CipherLoginModel
Uri = data.Uri;
}
if (data.Fido2Credentials != null)
{
Fido2Credentials = data.Fido2Credentials.Select(key => new CipherFido2CredentialModel(key)).ToArray();
}
Username = data.Username;
Password = data.Password;
PasswordRevisionDate = data.PasswordRevisionDate;
@ -55,6 +60,7 @@ public class CipherLoginModel
[EncryptedStringLength(1000)]
public string Totp { get; set; }
public bool? AutofillOnPageLoad { get; set; }
public CipherFido2CredentialModel[] Fido2Credentials { get; set; }
public class CipherLoginUriModel
{

View File

@ -166,6 +166,7 @@ public class CipherRequestModel
PasswordRevisionDate = Login.PasswordRevisionDate,
Totp = Login.Totp,
AutofillOnPageLoad = Login.AutofillOnPageLoad,
Fido2Credentials = Login.Fido2Credentials == null ? null : Login.Fido2Credentials.ToCipherLoginFido2CredentialData(),
};
}

View File

@ -447,7 +447,7 @@ public class StripeController : Controller
// org
if (ids.Item1.HasValue)
{
if (subscription.Items.Any(i => StaticStore.PasswordManagerPlans.Any(p => p.StripePlanId == i.Plan.Id)))
if (subscription.Items.Any(i => StaticStore.Plans.Any(p => p.PasswordManager.StripePlanId == i.Plan.Id)))
{
await _organizationService.EnableAsync(ids.Item1.Value, subscription.CurrentPeriodEnd);

View File

@ -20,6 +20,8 @@ public static class Constants
/// </summary>
public const int OrganizationSelfHostSubscriptionGracePeriodDays = 60;
public const string Fido2KeyCipherMinimumVersion = "2023.10.0";
public const string CipherKeyEncryptionMinimumVersion = "2023.9.2";
}
@ -38,6 +40,7 @@ public static class FeatureFlagKeys
public const string DisplayEuEnvironment = "display-eu-environment";
public const string DisplayLowKdfIterationWarning = "display-kdf-iteration-warning";
public const string TrustedDeviceEncryption = "trusted-device-encryption";
public const string Fido2VaultCredentials = "fido2-vault-credentials";
public const string AutofillV2 = "autofill-v2";
public const string BrowserFilelessImport = "browser-fileless-import";
public const string FlexibleCollections = "flexible-collections";
@ -56,7 +59,8 @@ public static class FeatureFlagKeys
// place overriding values when needed locally (offline), or return null
return new Dictionary<string, string>()
{
{ TrustedDeviceEncryption, "true" }
{ TrustedDeviceEncryption, "true" },
{ Fido2VaultCredentials, "true" }
};
}
}

View File

@ -14,7 +14,7 @@ public class CurrentContextOrganization
Id = orgUser.OrganizationId;
Type = orgUser.Type;
Permissions = CoreHelpers.LoadClassFromJsonData<Permissions>(orgUser.Permissions);
AccessSecretsManager = orgUser.AccessSecretsManager && orgUser.UseSecretsManager;
AccessSecretsManager = orgUser.AccessSecretsManager && orgUser.UseSecretsManager && orgUser.Enabled;
LimitCollectionCreationDeletion = orgUser.LimitCollectionCreationDeletion;
}

View File

@ -254,5 +254,9 @@ public class Organization : ITableObject<Guid>, ISubscriber, IStorable, IStorabl
ExpirationDate = license.Expires;
LicenseKey = license.LicenseKey;
RevisionDate = DateTime.UtcNow;
UsePasswordManager = license.UsePasswordManager;
UseSecretsManager = license.UseSecretsManager;
SmSeats = license.SmSeats;
SmServiceAccounts = license.SmServiceAccounts;
}
}

View File

@ -1,11 +0,0 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Core.Enums;
public enum BitwardenProductType : byte
{
[Display(Name = "Password Manager")]
PasswordManager = 0,
[Display(Name = "Secrets Manager")]
SecretsManager = 1,
}

View File

@ -47,6 +47,10 @@ public class OrganizationLicense : ILicense
UsersGetPremium = org.UsersGetPremium;
UseCustomPermissions = org.UseCustomPermissions;
Issued = DateTime.UtcNow;
UsePasswordManager = org.UsePasswordManager;
UseSecretsManager = org.UseSecretsManager;
SmSeats = org.SmSeats;
SmServiceAccounts = org.SmServiceAccounts;
if (subscriptionInfo?.Subscription == null)
{
@ -125,6 +129,10 @@ public class OrganizationLicense : ILicense
public DateTime? Refresh { get; set; }
public DateTime? Expires { get; set; }
public DateTime? ExpirationWithoutGracePeriod { get; set; }
public bool UsePasswordManager { get; set; }
public bool UseSecretsManager { get; set; }
public int? SmSeats { get; set; }
public int? SmServiceAccounts { get; set; }
public bool Trial { get; set; }
public LicenseType? LicenseType { get; set; }
public string Hash { get; set; }
@ -137,10 +145,10 @@ public class OrganizationLicense : ILicense
/// </summary>
/// <remarks>Intentionally set one version behind to allow self hosted users some time to update before
/// getting out of date license errors</remarks>
private const int CURRENT_LICENSE_FILE_VERSION = 11;
private const int CURRENT_LICENSE_FILE_VERSION = 12;
private bool ValidLicenseVersion
{
get => Version is >= 1 and <= 12;
get => Version is >= 1 and <= 13;
}
public byte[] GetDataBytes(bool forHash = false)
@ -176,6 +184,8 @@ public class OrganizationLicense : ILicense
(Version >= 11 || !p.Name.Equals(nameof(UseCustomPermissions))) &&
// ExpirationWithoutGracePeriod was added in Version 12
(Version >= 12 || !p.Name.Equals(nameof(ExpirationWithoutGracePeriod))) &&
// UseSecretsManager was added in Version 13
(Version >= 13 || !p.Name.Equals(nameof(UseSecretsManager))) &&
(
!forHash ||
(
@ -315,6 +325,14 @@ public class OrganizationLicense : ILicense
valid = organization.UseCustomPermissions == UseCustomPermissions;
}
if (valid && Version >= 13)
{
valid = organization.UseSecretsManager == UseSecretsManager &&
organization.UsePasswordManager == UsePasswordManager &&
organization.SmSeats == SmSeats &&
organization.SmServiceAccounts == SmServiceAccounts;
}
return valid;
}
else

View File

@ -43,18 +43,18 @@ public class SecretsManagerSubscriptionUpdate
/// The seats the organization will have after the update, excluding the base seats included in the plan
/// Usually this is what the organization is billed for
/// </summary>
public int SmSeatsExcludingBase => SmSeats.HasValue ? SmSeats.Value - Plan.BaseSeats : 0;
public int SmSeatsExcludingBase => SmSeats.HasValue ? SmSeats.Value - Plan.SecretsManager.BaseSeats : 0;
/// <summary>
/// The seats the organization will have after the update, excluding the base seats included in the plan
/// Usually this is what the organization is billed for
/// </summary>
public int SmServiceAccountsExcludingBase => SmServiceAccounts.HasValue ? SmServiceAccounts.Value - Plan.BaseServiceAccount.GetValueOrDefault() : 0;
public int SmServiceAccountsExcludingBase => SmServiceAccounts.HasValue ? SmServiceAccounts.Value - Plan.SecretsManager!.BaseServiceAccount : 0;
public bool SmSeatsChanged => SmSeats != Organization.SmSeats;
public bool SmServiceAccountsChanged => SmServiceAccounts != Organization.SmServiceAccounts;
public bool MaxAutoscaleSmSeatsChanged => MaxAutoscaleSmSeats != Organization.MaxAutoscaleSmSeats;
public bool MaxAutoscaleSmServiceAccountsChanged =>
MaxAutoscaleSmServiceAccounts != Organization.MaxAutoscaleSmServiceAccounts;
public Plan Plan => Utilities.StaticStore.GetSecretsManagerPlan(Organization.PlanType);
public Plan Plan => Utilities.StaticStore.GetPlan(Organization.PlanType);
public bool SmSeatAutoscaleLimitReached => SmSeats.HasValue && MaxAutoscaleSmSeats.HasValue && SmSeats == MaxAutoscaleSmSeats;
public bool SmServiceAccountAutoscaleLimitReached => SmServiceAccounts.HasValue &&
@ -70,7 +70,7 @@ public class SecretsManagerSubscriptionUpdate
Organization = organization;
if (Plan == null)
if (!Plan.SupportsSecretsManager)
{
throw new NotFoundException("Invalid Secrets Manager plan.");
}

View File

@ -1,12 +1,12 @@
using Bit.Core.Entities;
using Bit.Core.Enums;
using Stripe;
using Plan = Bit.Core.Models.StaticStore.Plan;
namespace Bit.Core.Models.Business;
public class OrganizationSubscriptionOptionsBase : Stripe.SubscriptionCreateOptions
{
public OrganizationSubscriptionOptionsBase(Organization org, List<StaticStore.Plan> plans, TaxInfo taxInfo, int additionalSeats,
public OrganizationSubscriptionOptionsBase(Organization org, StaticStore.Plan plan, TaxInfo taxInfo, int additionalSeats,
int additionalStorageGb, bool premiumAccessAddon, int additionalSmSeats, int additionalServiceAccounts)
{
Items = new List<SubscriptionItemOptions>();
@ -14,79 +14,80 @@ public class OrganizationSubscriptionOptionsBase : Stripe.SubscriptionCreateOpti
{
[org.GatewayIdField()] = org.Id.ToString()
};
foreach (var plan in plans)
{
AddPlanIdToSubscription(plan);
switch (plan.BitwardenProduct)
{
case BitwardenProductType.PasswordManager:
{
AddPremiumAccessAddon(premiumAccessAddon, plan);
AddAdditionalSeatToSubscription(additionalSeats, plan);
AddAdditionalStorage(additionalStorageGb, plan);
break;
}
case BitwardenProductType.SecretsManager:
{
AddAdditionalSeatToSubscription(additionalSmSeats, plan);
AddServiceAccount(additionalServiceAccounts, plan);
break;
}
}
AddPlanIdToSubscription(plan);
if (org.UseSecretsManager)
{
AddSecretsManagerSeat(plan, additionalSmSeats);
AddServiceAccount(plan, additionalServiceAccounts);
}
AddPremiumAccessAddon(plan, premiumAccessAddon);
AddPasswordManagerSeat(plan, additionalSeats);
AddAdditionalStorage(plan, additionalStorageGb);
if (!string.IsNullOrWhiteSpace(taxInfo?.StripeTaxRateId))
{
DefaultTaxRates = new List<string> { taxInfo.StripeTaxRateId };
}
}
private void AddServiceAccount(int additionalServiceAccounts, StaticStore.Plan plan)
private void AddSecretsManagerSeat(Plan plan, int additionalSmSeats)
{
if (additionalServiceAccounts > 0 && plan.StripeServiceAccountPlanId != null)
if (additionalSmSeats > 0 && plan.SecretsManager.StripeSeatPlanId != null)
{
Items.Add(new SubscriptionItemOptions
{ Plan = plan.SecretsManager.StripeSeatPlanId, Quantity = additionalSmSeats });
}
}
private void AddPasswordManagerSeat(Plan plan, int additionalSeats)
{
if (additionalSeats > 0 && plan.PasswordManager.StripeSeatPlanId != null)
{
Items.Add(new SubscriptionItemOptions
{ Plan = plan.PasswordManager.StripeSeatPlanId, Quantity = additionalSeats });
}
}
private void AddServiceAccount(StaticStore.Plan plan, int additionalServiceAccounts)
{
if (additionalServiceAccounts > 0 && plan.SecretsManager.StripeServiceAccountPlanId != null)
{
Items.Add(new SubscriptionItemOptions
{
Plan = plan.StripeServiceAccountPlanId,
Plan = plan.SecretsManager.StripeServiceAccountPlanId,
Quantity = additionalServiceAccounts
});
}
}
private void AddAdditionalStorage(int additionalStorageGb, StaticStore.Plan plan)
private void AddAdditionalStorage(StaticStore.Plan plan, int additionalStorageGb)
{
if (additionalStorageGb > 0)
{
Items.Add(new SubscriptionItemOptions
{
Plan = plan.StripeStoragePlanId,
Plan = plan.PasswordManager.StripeStoragePlanId,
Quantity = additionalStorageGb
});
}
}
private void AddPremiumAccessAddon(bool premiumAccessAddon, StaticStore.Plan plan)
private void AddPremiumAccessAddon(StaticStore.Plan plan, bool premiumAccessAddon)
{
if (premiumAccessAddon && plan.StripePremiumAccessPlanId != null)
if (premiumAccessAddon && plan.PasswordManager.StripePremiumAccessPlanId != null)
{
Items.Add(new SubscriptionItemOptions { Plan = plan.StripePremiumAccessPlanId, Quantity = 1 });
}
}
private void AddAdditionalSeatToSubscription(int additionalSeats, StaticStore.Plan plan)
{
if (additionalSeats > 0 && plan.StripeSeatPlanId != null)
{
Items.Add(new SubscriptionItemOptions { Plan = plan.StripeSeatPlanId, Quantity = additionalSeats });
Items.Add(new SubscriptionItemOptions { Plan = plan.PasswordManager.StripePremiumAccessPlanId, Quantity = 1 });
}
}
private void AddPlanIdToSubscription(StaticStore.Plan plan)
{
if (plan.StripePlanId != null)
if (plan.PasswordManager.StripePlanId != null)
{
Items.Add(new SubscriptionItemOptions { Plan = plan.StripePlanId, Quantity = 1 });
Items.Add(new SubscriptionItemOptions { Plan = plan.PasswordManager.StripePlanId, Quantity = 1 });
}
}
}
@ -94,14 +95,14 @@ public class OrganizationSubscriptionOptionsBase : Stripe.SubscriptionCreateOpti
public class OrganizationPurchaseSubscriptionOptions : OrganizationSubscriptionOptionsBase
{
public OrganizationPurchaseSubscriptionOptions(
Organization org, List<StaticStore.Plan> plans,
Organization org, StaticStore.Plan plan,
TaxInfo taxInfo, int additionalSeats,
int additionalStorageGb, bool premiumAccessAddon,
int additionalSmSeats, int additionalServiceAccounts) :
base(org, plans, taxInfo, additionalSeats, additionalStorageGb, premiumAccessAddon, additionalSmSeats, additionalServiceAccounts)
base(org, plan, taxInfo, additionalSeats, additionalStorageGb, premiumAccessAddon, additionalSmSeats, additionalServiceAccounts)
{
OffSession = true;
TrialPeriodDays = plans.FirstOrDefault(x => x.BitwardenProduct == BitwardenProductType.PasswordManager)!.TrialPeriodDays;
TrialPeriodDays = plan.TrialPeriodDays;
}
}
@ -109,8 +110,8 @@ public class OrganizationUpgradeSubscriptionOptions : OrganizationSubscriptionOp
{
public OrganizationUpgradeSubscriptionOptions(
string customerId, Organization org,
List<StaticStore.Plan> plans, OrganizationUpgrade upgrade) :
base(org, plans, upgrade.TaxInfo, upgrade.AdditionalSeats, upgrade.AdditionalStorageGb,
StaticStore.Plan plan, OrganizationUpgrade upgrade) :
base(org, plan, upgrade.TaxInfo, upgrade.AdditionalSeats, upgrade.AdditionalStorageGb,
upgrade.PremiumAccessAddon, upgrade.AdditionalSmSeats.GetValueOrDefault(),
upgrade.AdditionalServiceAccounts.GetValueOrDefault())
{

View File

@ -1,5 +1,4 @@
using Bit.Core.Enums;
using Stripe;
using Stripe;
namespace Bit.Core.Models.Business;
@ -64,16 +63,12 @@ public class SubscriptionInfo
Interval = item.Plan.Interval;
AddonSubscriptionItem =
Utilities.StaticStore.IsAddonSubscriptionItem(item.Plan.Id);
BitwardenProduct =
Utilities.StaticStore.GetPlanByStripeId(item.Plan.Id)?.BitwardenProduct ?? BitwardenProductType.PasswordManager;
}
Quantity = (int)item.Quantity;
SponsoredSubscriptionItem = Utilities.StaticStore.SponsoredPlans.Any(p => p.StripePlanId == item.Plan.Id);
}
public BitwardenProductType BitwardenProduct { get; set; }
public bool AddonSubscriptionItem { get; set; }
public string Name { get; set; }

View File

@ -1,5 +1,4 @@
using Bit.Core.Entities;
using Bit.Core.Enums;
using Stripe;
namespace Bit.Core.Models.Business;
@ -30,29 +29,23 @@ public abstract class SubscriptionUpdate
planId == null ? null : subscription.Items?.Data?.FirstOrDefault(i => i.Plan.Id == planId);
}
public class SeatSubscriptionUpdate : SubscriptionUpdate
public abstract class BaseSeatSubscriptionUpdate : SubscriptionUpdate
{
private readonly int _previousSeats;
private readonly StaticStore.Plan _plan;
protected readonly StaticStore.Plan Plan;
private readonly long? _additionalSeats;
protected override List<string> PlanIds => new() { _plan.StripeSeatPlanId };
public SeatSubscriptionUpdate(Organization organization, StaticStore.Plan plan, long? additionalSeats)
protected BaseSeatSubscriptionUpdate(Organization organization, StaticStore.Plan plan, long? additionalSeats, int previousSeats)
{
_plan = plan;
Plan = plan;
_additionalSeats = additionalSeats;
switch (plan.BitwardenProduct)
{
case BitwardenProductType.PasswordManager:
_previousSeats = organization.Seats.GetValueOrDefault();
break;
case BitwardenProductType.SecretsManager:
_previousSeats = organization.SmSeats.GetValueOrDefault();
break;
}
_previousSeats = previousSeats;
}
protected abstract string GetPlanId();
protected override List<string> PlanIds => new() { GetPlanId() };
public override List<SubscriptionItemOptions> UpgradeItemsOptions(Subscription subscription)
{
var item = SubscriptionItem(subscription, PlanIds.Single());
@ -85,12 +78,30 @@ public class SeatSubscriptionUpdate : SubscriptionUpdate
}
}
public class SeatSubscriptionUpdate : BaseSeatSubscriptionUpdate
{
public SeatSubscriptionUpdate(Organization organization, StaticStore.Plan plan, long? additionalSeats)
: base(organization, plan, additionalSeats, organization.Seats.GetValueOrDefault())
{ }
protected override string GetPlanId() => Plan.PasswordManager.StripeSeatPlanId;
}
public class SmSeatSubscriptionUpdate : BaseSeatSubscriptionUpdate
{
public SmSeatSubscriptionUpdate(Organization organization, StaticStore.Plan plan, long? additionalSeats)
: base(organization, plan, additionalSeats, organization.SmSeats.GetValueOrDefault())
{ }
protected override string GetPlanId() => Plan.SecretsManager.StripeSeatPlanId;
}
public class ServiceAccountSubscriptionUpdate : SubscriptionUpdate
{
private long? _prevServiceAccounts;
private readonly StaticStore.Plan _plan;
private readonly long? _additionalServiceAccounts;
protected override List<string> PlanIds => new() { _plan.StripeServiceAccountPlanId };
protected override List<string> PlanIds => new() { _plan.SecretsManager.StripeServiceAccountPlanId };
public ServiceAccountSubscriptionUpdate(Organization organization, StaticStore.Plan plan, long? additionalServiceAccounts)
{
@ -190,7 +201,7 @@ public class SponsorOrganizationSubscriptionUpdate : SubscriptionUpdate
public SponsorOrganizationSubscriptionUpdate(StaticStore.Plan existingPlan, StaticStore.SponsoredPlan sponsoredPlan, bool applySponsorship)
{
_existingPlanStripeId = existingPlan.StripePlanId;
_existingPlanStripeId = existingPlan.PasswordManager.StripePlanId;
_sponsoredPlanStripeId = sponsoredPlan?.StripePlanId;
_applySponsorship = applySponsorship;
}
@ -269,7 +280,7 @@ public class SecretsManagerSubscribeUpdate : SubscriptionUpdate
private readonly long? _additionalServiceAccounts;
private readonly int _previousSeats;
private readonly int _previousServiceAccounts;
protected override List<string> PlanIds => new() { _plan.StripeSeatPlanId, _plan.StripeServiceAccountPlanId };
protected override List<string> PlanIds => new() { _plan.SecretsManager.StripeSeatPlanId, _plan.SecretsManager.StripeServiceAccountPlanId };
public SecretsManagerSubscribeUpdate(Organization organization, StaticStore.Plan plan, long? additionalSeats, long? additionalServiceAccounts)
{
_plan = plan;
@ -303,7 +314,7 @@ public class SecretsManagerSubscribeUpdate : SubscriptionUpdate
{
updatedItems.Add(new SubscriptionItemOptions
{
Price = _plan.StripeSeatPlanId,
Price = _plan.SecretsManager.StripeSeatPlanId,
Quantity = _additionalSeats
});
}
@ -312,7 +323,7 @@ public class SecretsManagerSubscribeUpdate : SubscriptionUpdate
{
updatedItems.Add(new SubscriptionItemOptions
{
Price = _plan.StripeServiceAccountPlanId,
Price = _plan.SecretsManager.StripeServiceAccountPlanId,
Quantity = _additionalServiceAccounts
});
}
@ -322,14 +333,14 @@ public class SecretsManagerSubscribeUpdate : SubscriptionUpdate
{
updatedItems.Add(new SubscriptionItemOptions
{
Price = _plan.StripeSeatPlanId,
Price = _plan.SecretsManager.StripeSeatPlanId,
Quantity = _previousSeats,
Deleted = _previousSeats == 0 ? true : (bool?)null,
});
updatedItems.Add(new SubscriptionItemOptions
{
Price = _plan.StripeServiceAccountPlanId,
Price = _plan.SecretsManager.StripeServiceAccountPlanId,
Quantity = _previousServiceAccounts,
Deleted = _previousServiceAccounts == 0 ? true : (bool?)null,
});

View File

@ -2,64 +2,84 @@
namespace Bit.Core.Models.StaticStore;
public class Plan
public abstract record Plan
{
public PlanType Type { get; set; }
public ProductType Product { get; set; }
public string Name { get; set; }
public bool IsAnnual { get; set; }
public string NameLocalizationKey { get; set; }
public string DescriptionLocalizationKey { get; set; }
public bool CanBeUsedByBusiness { get; set; }
public int BaseSeats { get; set; }
public short? BaseStorageGb { get; set; }
public short? MaxCollections { get; set; }
public short? MaxUsers { get; set; }
public short? MaxServiceAccounts { get; set; }
public bool AllowSeatAutoscale { get; set; }
public PlanType Type { get; protected init; }
public ProductType Product { get; protected init; }
public string Name { get; protected init; }
public bool IsAnnual { get; protected init; }
public string NameLocalizationKey { get; protected init; }
public string DescriptionLocalizationKey { get; protected init; }
public bool CanBeUsedByBusiness { get; protected init; }
public int? TrialPeriodDays { get; protected init; }
public bool HasSelfHost { get; protected init; }
public bool HasPolicies { get; protected init; }
public bool HasGroups { get; protected init; }
public bool HasDirectory { get; protected init; }
public bool HasEvents { get; protected init; }
public bool HasTotp { get; protected init; }
public bool Has2fa { get; protected init; }
public bool HasApi { get; protected init; }
public bool HasSso { get; protected init; }
public bool HasKeyConnector { get; protected init; }
public bool HasScim { get; protected init; }
public bool HasResetPassword { get; protected init; }
public bool UsersGetPremium { get; protected init; }
public bool HasCustomPermissions { get; protected init; }
public int UpgradeSortOrder { get; protected init; }
public int DisplaySortOrder { get; protected init; }
public int? LegacyYear { get; protected init; }
public bool Disabled { get; protected init; }
public PasswordManagerPlanFeatures PasswordManager { get; protected init; }
public SecretsManagerPlanFeatures SecretsManager { get; protected init; }
public bool SupportsSecretsManager => SecretsManager != null;
public bool AllowServiceAccountsAutoscale { get; set; }
public record SecretsManagerPlanFeatures
{
// Service accounts
public short? MaxServiceAccounts { get; init; }
public bool AllowServiceAccountsAutoscale { get; init; }
public string StripeServiceAccountPlanId { get; init; }
public decimal? AdditionalPricePerServiceAccount { get; init; }
public short BaseServiceAccount { get; init; }
public short? MaxAdditionalServiceAccount { get; init; }
public bool HasAdditionalServiceAccountOption { get; init; }
// Seats
public string StripeSeatPlanId { get; init; }
public bool HasAdditionalSeatsOption { get; init; }
public decimal BasePrice { get; init; }
public decimal SeatPrice { get; init; }
public int BaseSeats { get; init; }
public short? MaxSeats { get; init; }
public int? MaxAdditionalSeats { get; init; }
public bool AllowSeatAutoscale { get; init; }
public bool HasAdditionalSeatsOption { get; set; }
public int? MaxAdditionalSeats { get; set; }
public bool HasAdditionalStorageOption { get; set; }
public short? MaxAdditionalStorage { get; set; }
public bool HasPremiumAccessOption { get; set; }
public int? TrialPeriodDays { get; set; }
// Features
public int MaxProjects { get; init; }
}
public bool HasSelfHost { get; set; }
public bool HasPolicies { get; set; }
public bool HasGroups { get; set; }
public bool HasDirectory { get; set; }
public bool HasEvents { get; set; }
public bool HasTotp { get; set; }
public bool Has2fa { get; set; }
public bool HasApi { get; set; }
public bool HasSso { get; set; }
public bool HasKeyConnector { get; set; }
public bool HasScim { get; set; }
public bool HasResetPassword { get; set; }
public bool UsersGetPremium { get; set; }
public bool HasCustomPermissions { get; set; }
public int UpgradeSortOrder { get; set; }
public int DisplaySortOrder { get; set; }
public int? LegacyYear { get; set; }
public bool Disabled { get; set; }
public string StripePlanId { get; set; }
public string StripeSeatPlanId { get; set; }
public string StripeStoragePlanId { get; set; }
public string StripeServiceAccountPlanId { get; set; }
public string StripePremiumAccessPlanId { get; set; }
public decimal BasePrice { get; set; }
public decimal SeatPrice { get; set; }
public decimal AdditionalStoragePricePerGb { get; set; }
public decimal PremiumAccessOptionPrice { get; set; }
public decimal? AdditionalPricePerServiceAccount { get; set; }
public short? BaseServiceAccount { get; set; }
public short? MaxAdditionalServiceAccount { get; set; }
public bool HasAdditionalServiceAccountOption { get; set; }
public short? MaxProjects { get; set; }
public BitwardenProductType BitwardenProduct { get; set; }
public record PasswordManagerPlanFeatures
{
// Seats
public string StripePlanId { get; init; }
public string StripeSeatPlanId { get; init; }
public decimal BasePrice { get; init; }
public decimal SeatPrice { get; init; }
public bool AllowSeatAutoscale { get; init; }
public bool HasAdditionalSeatsOption { get; init; }
public int? MaxAdditionalSeats { get; init; }
public int BaseSeats { get; init; }
public bool HasPremiumAccessOption { get; init; }
public string StripePremiumAccessPlanId { get; init; }
public decimal PremiumAccessOptionPrice { get; init; }
public short? MaxSeats { get; init; }
// Storage
public short? BaseStorageGb { get; init; }
public bool HasAdditionalStorageOption { get; init; }
public decimal AdditionalStoragePricePerGb { get; init; }
public string StripeStoragePlanId { get; init; }
public short? MaxAdditionalStorage { get; init; }
// Feature
public short? MaxCollections { get; init; }
}
}

View File

@ -0,0 +1,20 @@
using Bit.Core.Enums;
namespace Bit.Core.Models.StaticStore.Plans;
public record CustomPlan : Models.StaticStore.Plan
{
public CustomPlan()
{
Type = PlanType.Custom;
PasswordManager = new CustomPasswordManagerFeatures();
}
private record CustomPasswordManagerFeatures : PasswordManagerPlanFeatures
{
public CustomPasswordManagerFeatures()
{
AllowSeatAutoscale = true;
}
}
}

View File

@ -0,0 +1,65 @@
using Bit.Core.Enums;
namespace Bit.Core.Models.StaticStore.Plans;
public record Enterprise2019Plan : Models.StaticStore.Plan
{
public Enterprise2019Plan(bool isAnnual)
{
Type = isAnnual ? PlanType.EnterpriseAnnually2019 : PlanType.EnterpriseMonthly2019;
Product = ProductType.Enterprise;
Name = isAnnual ? "Enterprise (Annually) 2019" : "Enterprise (Monthly) 2019";
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;
UsersGetPremium = true;
HasCustomPermissions = true;
UpgradeSortOrder = 3;
DisplaySortOrder = 3;
LegacyYear = 2020;
PasswordManager = new Enterprise2019PasswordManagerFeatures(isAnnual);
}
private record Enterprise2019PasswordManagerFeatures : PasswordManagerPlanFeatures
{
public Enterprise2019PasswordManagerFeatures(bool isAnnual)
{
BaseSeats = 0;
BaseStorageGb = 1;
HasAdditionalStorageOption = true;
HasAdditionalSeatsOption = true;
AllowSeatAutoscale = true;
if (isAnnual)
{
StripeStoragePlanId = "storage-gb-annually";
StripeSeatPlanId = "enterprise-org-seat-annually";
SeatPrice = 36;
AdditionalStoragePricePerGb = 4;
}
else
{
StripeSeatPlanId = "enterprise-org-seat-monthly";
StripeStoragePlanId = "storage-gb-monthly";
SeatPrice = 4M;
AdditionalStoragePricePerGb = 0.5M;
}
}
}
}

View File

@ -0,0 +1,100 @@
using Bit.Core.Enums;
namespace Bit.Core.Models.StaticStore.Plans;
public record EnterprisePlan : Models.StaticStore.Plan
{
public EnterprisePlan(bool isAnnual)
{
Type = isAnnual ? PlanType.EnterpriseAnnually : PlanType.EnterpriseMonthly;
Product = ProductType.Enterprise;
Name = isAnnual ? "Enterprise (Annually)" : "Enterprise (Monthly)";
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;
PasswordManager = new EnterprisePasswordManagerFeatures(isAnnual);
SecretsManager = new EnterpriseSecretsManagerFeatures(isAnnual);
}
private record EnterpriseSecretsManagerFeatures : SecretsManagerPlanFeatures
{
public EnterpriseSecretsManagerFeatures(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 EnterprisePasswordManagerFeatures : PasswordManagerPlanFeatures
{
public EnterprisePasswordManagerFeatures(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;
}
}
}
}

View File

@ -0,0 +1,49 @@
using Bit.Core.Enums;
namespace Bit.Core.Models.StaticStore.Plans;
public record Families2019Plan : Models.StaticStore.Plan
{
public Families2019Plan()
{
Type = PlanType.FamiliesAnnually2019;
Product = ProductType.Families;
Name = "Families 2019";
IsAnnual = true;
NameLocalizationKey = "planNameFamilies";
DescriptionLocalizationKey = "planDescFamilies";
TrialPeriodDays = 7;
HasSelfHost = true;
HasTotp = true;
UpgradeSortOrder = 1;
DisplaySortOrder = 1;
LegacyYear = 2020;
PasswordManager = new Families2019PasswordManagerFeatures();
}
private record Families2019PasswordManagerFeatures : PasswordManagerPlanFeatures
{
public Families2019PasswordManagerFeatures()
{
BaseSeats = 5;
BaseStorageGb = 1;
MaxSeats = 5;
HasAdditionalStorageOption = true;
HasPremiumAccessOption = true;
StripePlanId = "personal-org-annually";
StripeStoragePlanId = "storage-gb-annually";
StripePremiumAccessPlanId = "personal-org-premium-access-annually";
BasePrice = 12;
AdditionalStoragePricePerGb = 4;
PremiumAccessOptionPrice = 40;
AllowSeatAutoscale = false;
}
}
}

View File

@ -0,0 +1,46 @@
using Bit.Core.Enums;
namespace Bit.Core.Models.StaticStore.Plans;
public record FamiliesPlan : Models.StaticStore.Plan
{
public FamiliesPlan()
{
Type = PlanType.FamiliesAnnually;
Product = ProductType.Families;
Name = "Families";
IsAnnual = true;
NameLocalizationKey = "planNameFamilies";
DescriptionLocalizationKey = "planDescFamilies";
TrialPeriodDays = 7;
HasSelfHost = true;
HasTotp = true;
UsersGetPremium = true;
UpgradeSortOrder = 1;
DisplaySortOrder = 1;
PasswordManager = new TeamsPasswordManagerFeatures();
}
private record TeamsPasswordManagerFeatures : PasswordManagerPlanFeatures
{
public TeamsPasswordManagerFeatures()
{
BaseSeats = 6;
BaseStorageGb = 1;
MaxSeats = 6;
HasAdditionalStorageOption = true;
StripePlanId = "2020-families-org-annually";
StripeStoragePlanId = "storage-gb-annually";
BasePrice = 40;
AdditionalStoragePricePerGb = 4;
AllowSeatAutoscale = false;
}
}
}

View File

@ -0,0 +1,47 @@
using Bit.Core.Enums;
namespace Bit.Core.Models.StaticStore.Plans;
public record FreePlan : Models.StaticStore.Plan
{
public FreePlan()
{
Type = PlanType.Free;
Product = ProductType.Free;
Name = "Free";
NameLocalizationKey = "planNameFree";
DescriptionLocalizationKey = "planDescFree";
UpgradeSortOrder = -1; // Always the lowest plan, cannot be upgraded to
DisplaySortOrder = -1;
PasswordManager = new FreePasswordManagerFeatures();
SecretsManager = new FreeSecretsManagerFeatures();
}
private record FreeSecretsManagerFeatures : SecretsManagerPlanFeatures
{
public FreeSecretsManagerFeatures()
{
BaseSeats = 2;
BaseServiceAccount = 3;
MaxProjects = 3;
MaxSeats = 2;
MaxServiceAccounts = 3;
AllowSeatAutoscale = false;
}
}
private record FreePasswordManagerFeatures : PasswordManagerPlanFeatures
{
public FreePasswordManagerFeatures()
{
BaseSeats = 2;
MaxCollections = 2;
MaxSeats = 2;
AllowSeatAutoscale = false;
}
}
}

View File

@ -0,0 +1,60 @@
using Bit.Core.Enums;
namespace Bit.Core.Models.StaticStore.Plans;
public record Teams2019Plan : Models.StaticStore.Plan
{
public Teams2019Plan(bool isAnnual)
{
Type = isAnnual ? PlanType.TeamsAnnually2019 : PlanType.TeamsMonthly2019;
Product = ProductType.Teams;
Name = isAnnual ? "Teams (Annually) 2019" : "Teams (Monthly) 2019";
IsAnnual = isAnnual;
NameLocalizationKey = "planNameTeams";
DescriptionLocalizationKey = "planDescTeams";
CanBeUsedByBusiness = true;
TrialPeriodDays = 7;
HasTotp = true;
UpgradeSortOrder = 2;
DisplaySortOrder = 2;
LegacyYear = 2020;
PasswordManager = new Teams2019PasswordManagerFeatures(isAnnual);
}
private record Teams2019PasswordManagerFeatures : PasswordManagerPlanFeatures
{
public Teams2019PasswordManagerFeatures(bool isAnnual)
{
BaseSeats = 5;
BaseStorageGb = 1;
HasAdditionalStorageOption = true;
HasAdditionalSeatsOption = true;
AllowSeatAutoscale = true;
if (isAnnual)
{
StripePlanId = "teams-org-annually";
StripeStoragePlanId = "storage-gb-annually";
StripeSeatPlanId = "teams-org-seat-annually";
SeatPrice = 24;
BasePrice = 60;
AdditionalStoragePricePerGb = 4;
}
else
{
StripePlanId = "teams-org-monthly";
StripeSeatPlanId = "teams-org-seat-monthly";
StripeStoragePlanId = "storage-gb-monthly";
BasePrice = 8;
SeatPrice = 2.5M;
AdditionalStoragePricePerGb = 0.5M;
}
}
}
}

View File

@ -0,0 +1,94 @@
using Bit.Core.Enums;
namespace Bit.Core.Models.StaticStore.Plans;
public record TeamsPlan : Models.StaticStore.Plan
{
public TeamsPlan(bool isAnnual)
{
Type = isAnnual ? PlanType.TeamsAnnually : PlanType.TeamsMonthly;
Product = ProductType.Teams;
Name = isAnnual ? "Teams (Annually)" : "Teams (Monthly)";
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;
PasswordManager = new TeamsPasswordManagerFeatures(isAnnual);
SecretsManager = new TeamsSecretsManagerFeatures(isAnnual);
}
private record TeamsSecretsManagerFeatures : SecretsManagerPlanFeatures
{
public TeamsSecretsManagerFeatures(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 TeamsPasswordManagerFeatures : PasswordManagerPlanFeatures
{
public TeamsPasswordManagerFeatures(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;
}
}
}
}

View File

@ -54,7 +54,7 @@ public class CloudSyncSponsorshipsCommand : ICloudSyncSponsorshipsCommand
{
var requiredSponsoringProductType = StaticStore.GetSponsoredPlan(selfHostedSponsorship.PlanSponsorshipType)?.SponsoringProductType;
if (requiredSponsoringProductType == null
|| StaticStore.GetPasswordManagerPlan(sponsoringOrg.PlanType).Product != requiredSponsoringProductType.Value)
|| StaticStore.GetPlan(sponsoringOrg.PlanType).Product != requiredSponsoringProductType.Value)
{
continue; // prevent unsupported sponsorships
}

View File

@ -51,7 +51,7 @@ public class SetUpSponsorshipCommand : ISetUpSponsorshipCommand
var requiredSponsoredProductType = StaticStore.GetSponsoredPlan(sponsorship.PlanSponsorshipType.Value)?.SponsoredProductType;
if (requiredSponsoredProductType == null ||
sponsoredOrganization == null ||
StaticStore.GetPasswordManagerPlan(sponsoredOrganization.PlanType).Product != requiredSponsoredProductType.Value)
StaticStore.GetPlan(sponsoredOrganization.PlanType).Product != requiredSponsoredProductType.Value)
{
throw new BadRequestException("Can only redeem sponsorship offer on families organizations.");
}

View File

@ -56,7 +56,7 @@ public class ValidateSponsorshipCommand : CancelSponsorshipCommand, IValidateSpo
return false;
}
var sponsoringOrgPlan = Utilities.StaticStore.GetPasswordManagerPlan(sponsoringOrganization.PlanType);
var sponsoringOrgPlan = Utilities.StaticStore.GetPlan(sponsoringOrganization.PlanType);
if (OrgDisabledForMoreThanGracePeriod(sponsoringOrganization) ||
sponsoredPlan.SponsoringProductType != sponsoringOrgPlan.Product ||
existingSponsorship.ToDelete ||

View File

@ -32,7 +32,7 @@ public class CreateSponsorshipCommand : ICreateSponsorshipCommand
var requiredSponsoringProductType = StaticStore.GetSponsoredPlan(sponsorshipType)?.SponsoringProductType;
if (requiredSponsoringProductType == null ||
sponsoringOrg == null ||
StaticStore.GetPasswordManagerPlan(sponsoringOrg.PlanType).Product != requiredSponsoringProductType.Value)
StaticStore.GetPlan(sponsoringOrg.PlanType).Product != requiredSponsoringProductType.Value)
{
throw new BadRequestException("Specified Organization cannot sponsor other organizations.");
}

View File

@ -30,7 +30,7 @@ public class AddSecretsManagerSubscriptionCommand : IAddSecretsManagerSubscripti
{
await ValidateOrganization(organization);
var plan = StaticStore.GetSecretsManagerPlan(organization.PlanType);
var plan = StaticStore.GetPlan(organization.PlanType);
var signup = SetOrganizationUpgrade(organization, additionalSmSeats, additionalServiceAccounts);
_organizationService.ValidateSecretsManagerPlan(plan, signup);
@ -39,8 +39,8 @@ public class AddSecretsManagerSubscriptionCommand : IAddSecretsManagerSubscripti
await _paymentService.AddSecretsManagerToSubscription(organization, plan, additionalSmSeats, additionalServiceAccounts);
}
organization.SmSeats = plan.BaseSeats + additionalSmSeats;
organization.SmServiceAccounts = plan.BaseServiceAccount.GetValueOrDefault() + additionalServiceAccounts;
organization.SmSeats = plan.SecretsManager.BaseSeats + additionalSmSeats;
organization.SmServiceAccounts = plan.SecretsManager.BaseServiceAccount + additionalServiceAccounts;
organization.UseSecretsManager = true;
await _organizationService.ReplaceAndUpdateCacheAsync(organization);
@ -79,7 +79,7 @@ public class AddSecretsManagerSubscriptionCommand : IAddSecretsManagerSubscripti
throw new BadRequestException("Organization already uses Secrets Manager.");
}
var plan = StaticStore.GetSecretsManagerPlan(organization.PlanType);
var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == organization.PlanType && p.SupportsSecretsManager);
if (string.IsNullOrWhiteSpace(organization.GatewayCustomerId) && plan.Product != ProductType.Free)
{
throw new BadRequestException("No payment method found.");

View File

@ -205,10 +205,10 @@ public class UpdateSecretsManagerSubscriptionCommand : IUpdateSecretsManagerSubs
}
// Check plan maximum seats
if (!plan.HasAdditionalSeatsOption ||
(plan.MaxAdditionalSeats.HasValue && update.SmSeatsExcludingBase > plan.MaxAdditionalSeats.Value))
if (!plan.SecretsManager.HasAdditionalSeatsOption ||
(plan.SecretsManager.MaxAdditionalSeats.HasValue && update.SmSeatsExcludingBase > plan.SecretsManager.MaxAdditionalSeats.Value))
{
var planMaxSeats = plan.BaseSeats + plan.MaxAdditionalSeats.GetValueOrDefault();
var planMaxSeats = plan.SecretsManager.BaseSeats + plan.SecretsManager.MaxAdditionalSeats.GetValueOrDefault();
throw new BadRequestException($"You have reached the maximum number of Secrets Manager seats ({planMaxSeats}) for this plan.");
}
@ -222,9 +222,9 @@ public class UpdateSecretsManagerSubscriptionCommand : IUpdateSecretsManagerSubs
}
// Check minimum seats included with plan
if (plan.BaseSeats > update.SmSeats.Value)
if (plan.SecretsManager.BaseSeats > update.SmSeats.Value)
{
throw new BadRequestException($"Plan has a minimum of {plan.BaseSeats} Secrets Manager seats.");
throw new BadRequestException($"Plan has a minimum of {plan.SecretsManager.BaseSeats} Secrets Manager seats.");
}
// Check minimum seats required by business logic
@ -262,11 +262,11 @@ public class UpdateSecretsManagerSubscriptionCommand : IUpdateSecretsManagerSubs
}
// Check plan maximum service accounts
if (!plan.HasAdditionalServiceAccountOption ||
(plan.MaxAdditionalServiceAccount.HasValue && update.SmServiceAccountsExcludingBase > plan.MaxAdditionalServiceAccount.Value))
if (!plan.SecretsManager.HasAdditionalServiceAccountOption ||
(plan.SecretsManager.MaxAdditionalServiceAccount.HasValue && update.SmServiceAccountsExcludingBase > plan.SecretsManager.MaxAdditionalServiceAccount.Value))
{
var planMaxServiceAccounts = plan.BaseServiceAccount.GetValueOrDefault() +
plan.MaxAdditionalServiceAccount.GetValueOrDefault();
var planMaxServiceAccounts = plan.SecretsManager.BaseServiceAccount +
plan.SecretsManager.MaxAdditionalServiceAccount.GetValueOrDefault();
throw new BadRequestException($"You have reached the maximum number of service accounts ({planMaxServiceAccounts}) for this plan.");
}
@ -281,9 +281,9 @@ public class UpdateSecretsManagerSubscriptionCommand : IUpdateSecretsManagerSubs
}
// Check minimum service accounts included with plan
if (plan.BaseServiceAccount.HasValue && plan.BaseServiceAccount.Value > update.SmServiceAccounts.Value)
if (plan.SecretsManager.BaseServiceAccount > update.SmServiceAccounts.Value)
{
throw new BadRequestException($"Plan has a minimum of {plan.BaseServiceAccount} service accounts.");
throw new BadRequestException($"Plan has a minimum of {plan.SecretsManager.BaseServiceAccount} service accounts.");
}
// Check minimum service accounts required by business logic
@ -319,15 +319,15 @@ public class UpdateSecretsManagerSubscriptionCommand : IUpdateSecretsManagerSubs
throw new BadRequestException($"Cannot set max Secrets Manager seat autoscaling below current Secrets Manager seat count.");
}
if (plan.MaxUsers.HasValue && update.MaxAutoscaleSmSeats.Value > plan.MaxUsers)
if (plan.SecretsManager.MaxSeats.HasValue && update.MaxAutoscaleSmSeats.Value > plan.SecretsManager.MaxSeats)
{
throw new BadRequestException(string.Concat(
$"Your plan has a Secrets Manager seat limit of {plan.MaxUsers}, ",
$"Your plan has a Secrets Manager seat limit of {plan.SecretsManager.MaxSeats}, ",
$"but you have specified a max autoscale count of {update.MaxAutoscaleSmSeats}.",
"Reduce your max autoscale count."));
}
if (!plan.AllowSeatAutoscale)
if (!plan.SecretsManager.AllowSeatAutoscale)
{
throw new BadRequestException("Your plan does not allow Secrets Manager seat autoscaling.");
}
@ -349,15 +349,15 @@ public class UpdateSecretsManagerSubscriptionCommand : IUpdateSecretsManagerSubs
$"Cannot set max service accounts autoscaling below current service accounts count.");
}
if (!plan.AllowServiceAccountsAutoscale)
if (!plan.SecretsManager.AllowServiceAccountsAutoscale)
{
throw new BadRequestException("Your plan does not allow service accounts autoscaling.");
}
if (plan.MaxServiceAccounts.HasValue && update.MaxAutoscaleSmServiceAccounts.Value > plan.MaxServiceAccounts)
if (plan.SecretsManager.MaxServiceAccounts.HasValue && update.MaxAutoscaleSmServiceAccounts.Value > plan.SecretsManager.MaxServiceAccounts)
{
throw new BadRequestException(string.Concat(
$"Your plan has a service account limit of {plan.MaxServiceAccounts}, ",
$"Your plan has a service account limit of {plan.SecretsManager.MaxServiceAccounts}, ",
$"but you have specified a max autoscale count of {update.MaxAutoscaleSmServiceAccounts}.",
"Reduce your max autoscale count."));
}

View File

@ -73,69 +73,67 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
throw new BadRequestException("Your account has no payment method available.");
}
var existingPasswordManagerPlan = StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == organization.PlanType);
if (existingPasswordManagerPlan == null)
var existingPlan = StaticStore.GetPlan(organization.PlanType);
if (existingPlan == null)
{
throw new BadRequestException("Existing plan not found.");
}
var newPasswordManagerPlan =
StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == upgrade.Plan && !p.Disabled);
if (newPasswordManagerPlan == null)
var newPlan = StaticStore.Plans.FirstOrDefault(p => p.Type == upgrade.Plan && !p.Disabled);
if (newPlan == null)
{
throw new BadRequestException("Plan not found.");
}
if (existingPasswordManagerPlan.Type == newPasswordManagerPlan.Type)
if (existingPlan.Type == newPlan.Type)
{
throw new BadRequestException("Organization is already on this plan.");
}
if (existingPasswordManagerPlan.UpgradeSortOrder >= newPasswordManagerPlan.UpgradeSortOrder)
if (existingPlan.UpgradeSortOrder >= newPlan.UpgradeSortOrder)
{
throw new BadRequestException("You cannot upgrade to this plan.");
}
if (existingPasswordManagerPlan.Type != PlanType.Free)
if (existingPlan.Type != PlanType.Free)
{
throw new BadRequestException("You can only upgrade from the free plan. Contact support.");
}
_organizationService.ValidatePasswordManagerPlan(newPasswordManagerPlan, upgrade);
var newSecretsManagerPlan =
StaticStore.SecretManagerPlans.FirstOrDefault(p => p.Type == upgrade.Plan && !p.Disabled);
_organizationService.ValidatePasswordManagerPlan(newPlan, upgrade);
if (upgrade.UseSecretsManager)
{
_organizationService.ValidateSecretsManagerPlan(newSecretsManagerPlan, upgrade);
_organizationService.ValidateSecretsManagerPlan(newPlan, upgrade);
}
var newPasswordManagerPlanSeats = (short)(newPasswordManagerPlan.BaseSeats +
(newPasswordManagerPlan.HasAdditionalSeatsOption ? upgrade.AdditionalSeats : 0));
if (!organization.Seats.HasValue || organization.Seats.Value > newPasswordManagerPlanSeats)
var updatedPasswordManagerSeats = (short)(newPlan.PasswordManager.BaseSeats +
(newPlan.PasswordManager.HasAdditionalSeatsOption ? upgrade.AdditionalSeats : 0));
if (!organization.Seats.HasValue || organization.Seats.Value > updatedPasswordManagerSeats)
{
var occupiedSeats =
await _organizationUserRepository.GetOccupiedSeatCountByOrganizationIdAsync(organization.Id);
if (occupiedSeats > newPasswordManagerPlanSeats)
if (occupiedSeats > updatedPasswordManagerSeats)
{
throw new BadRequestException($"Your organization currently has {occupiedSeats} seats filled. " +
$"Your new plan only has ({newPasswordManagerPlanSeats}) seats. Remove some users.");
$"Your new plan only has ({updatedPasswordManagerSeats}) seats. Remove some users.");
}
}
if (newPasswordManagerPlan.MaxCollections.HasValue && (!organization.MaxCollections.HasValue ||
if (newPlan.PasswordManager.MaxCollections.HasValue && (!organization.MaxCollections.HasValue ||
organization.MaxCollections.Value >
newPasswordManagerPlan.MaxCollections.Value))
newPlan.PasswordManager.MaxCollections.Value))
{
var collectionCount = await _collectionRepository.GetCountByOrganizationIdAsync(organization.Id);
if (collectionCount > newPasswordManagerPlan.MaxCollections.Value)
if (collectionCount > newPlan.PasswordManager.MaxCollections.Value)
{
throw new BadRequestException($"Your organization currently has {collectionCount} collections. " +
$"Your new plan allows for a maximum of ({newPasswordManagerPlan.MaxCollections.Value}) collections. " +
$"Your new plan allows for a maximum of ({newPlan.PasswordManager.MaxCollections.Value}) collections. " +
"Remove some collections.");
}
}
if (!newPasswordManagerPlan.HasGroups && organization.UseGroups)
if (!newPlan.HasGroups && organization.UseGroups)
{
var groups = await _groupRepository.GetManyByOrganizationIdAsync(organization.Id);
if (groups.Any())
@ -145,7 +143,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
}
}
if (!newPasswordManagerPlan.HasPolicies && organization.UsePolicies)
if (!newPlan.HasPolicies && organization.UsePolicies)
{
var policies = await _policyRepository.GetManyByOrganizationIdAsync(organization.Id);
if (policies.Any(p => p.Enabled))
@ -155,7 +153,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
}
}
if (!newPasswordManagerPlan.HasSso && organization.UseSso)
if (!newPlan.HasSso && organization.UseSso)
{
var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(organization.Id);
if (ssoConfig != null && ssoConfig.Enabled)
@ -165,7 +163,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
}
}
if (!newPasswordManagerPlan.HasKeyConnector && organization.UseKeyConnector)
if (!newPlan.HasKeyConnector && organization.UseKeyConnector)
{
var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(organization.Id);
if (ssoConfig != null && ssoConfig.GetData().MemberDecryptionType == MemberDecryptionType.KeyConnector)
@ -175,7 +173,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
}
}
if (!newPasswordManagerPlan.HasResetPassword && organization.UseResetPassword)
if (!newPlan.HasResetPassword && organization.UseResetPassword)
{
var resetPasswordPolicy =
await _policyRepository.GetByOrganizationIdTypeAsync(organization.Id, PolicyType.ResetPassword);
@ -186,7 +184,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
}
}
if (!newPasswordManagerPlan.HasScim && organization.UseScim)
if (!newPlan.HasScim && organization.UseScim)
{
var scimConnections = await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(organization.Id,
OrganizationConnectionType.Scim);
@ -197,7 +195,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
}
}
if (!newPasswordManagerPlan.HasCustomPermissions && organization.UseCustomPermissions)
if (!newPlan.HasCustomPermissions && organization.UseCustomPermissions)
{
var organizationCustomUsers =
await _organizationUserRepository.GetManyByOrganizationAsync(organization.Id,
@ -209,9 +207,9 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
}
}
if (upgrade.UseSecretsManager && newSecretsManagerPlan != null)
if (upgrade.UseSecretsManager)
{
await ValidateSecretsManagerSeatsAndServiceAccountAsync(upgrade, organization, newSecretsManagerPlan);
await ValidateSecretsManagerSeatsAndServiceAccountAsync(upgrade, organization, newPlan);
}
// TODO: Check storage?
@ -220,12 +218,8 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
if (string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId))
{
var organizationUpgradePlan = upgrade.UseSecretsManager
? StaticStore.Plans.Where(p => p.Type == upgrade.Plan).ToList()
: StaticStore.Plans.Where(p => p.Type == upgrade.Plan && p.BitwardenProduct == BitwardenProductType.PasswordManager).ToList();
paymentIntentClientSecret = await _paymentService.UpgradeFreeOrganizationAsync(organization,
organizationUpgradePlan, upgrade);
newPlan, upgrade);
success = string.IsNullOrWhiteSpace(paymentIntentClientSecret);
}
else
@ -235,34 +229,34 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
}
organization.BusinessName = upgrade.BusinessName;
organization.PlanType = newPasswordManagerPlan.Type;
organization.Seats = (short)(newPasswordManagerPlan.BaseSeats + upgrade.AdditionalSeats);
organization.MaxCollections = newPasswordManagerPlan.MaxCollections;
organization.UseGroups = newPasswordManagerPlan.HasGroups;
organization.UseDirectory = newPasswordManagerPlan.HasDirectory;
organization.UseEvents = newPasswordManagerPlan.HasEvents;
organization.UseTotp = newPasswordManagerPlan.HasTotp;
organization.Use2fa = newPasswordManagerPlan.Has2fa;
organization.UseApi = newPasswordManagerPlan.HasApi;
organization.SelfHost = newPasswordManagerPlan.HasSelfHost;
organization.UsePolicies = newPasswordManagerPlan.HasPolicies;
organization.MaxStorageGb = !newPasswordManagerPlan.BaseStorageGb.HasValue
organization.PlanType = newPlan.Type;
organization.Seats = (short)(newPlan.PasswordManager.BaseSeats + upgrade.AdditionalSeats);
organization.MaxCollections = newPlan.PasswordManager.MaxCollections;
organization.UseGroups = newPlan.HasGroups;
organization.UseDirectory = newPlan.HasDirectory;
organization.UseEvents = newPlan.HasEvents;
organization.UseTotp = newPlan.HasTotp;
organization.Use2fa = newPlan.Has2fa;
organization.UseApi = newPlan.HasApi;
organization.SelfHost = newPlan.HasSelfHost;
organization.UsePolicies = newPlan.HasPolicies;
organization.MaxStorageGb = !newPlan.PasswordManager.BaseStorageGb.HasValue
? (short?)null
: (short)(newPasswordManagerPlan.BaseStorageGb.Value + upgrade.AdditionalStorageGb);
organization.UseGroups = newPasswordManagerPlan.HasGroups;
organization.UseDirectory = newPasswordManagerPlan.HasDirectory;
organization.UseEvents = newPasswordManagerPlan.HasEvents;
organization.UseTotp = newPasswordManagerPlan.HasTotp;
organization.Use2fa = newPasswordManagerPlan.Has2fa;
organization.UseApi = newPasswordManagerPlan.HasApi;
organization.UseSso = newPasswordManagerPlan.HasSso;
organization.UseKeyConnector = newPasswordManagerPlan.HasKeyConnector;
organization.UseScim = newPasswordManagerPlan.HasScim;
organization.UseResetPassword = newPasswordManagerPlan.HasResetPassword;
organization.SelfHost = newPasswordManagerPlan.HasSelfHost;
organization.UsersGetPremium = newPasswordManagerPlan.UsersGetPremium || upgrade.PremiumAccessAddon;
organization.UseCustomPermissions = newPasswordManagerPlan.HasCustomPermissions;
organization.Plan = newPasswordManagerPlan.Name;
: (short)(newPlan.PasswordManager.BaseStorageGb.Value + upgrade.AdditionalStorageGb);
organization.UseGroups = newPlan.HasGroups;
organization.UseDirectory = newPlan.HasDirectory;
organization.UseEvents = newPlan.HasEvents;
organization.UseTotp = newPlan.HasTotp;
organization.Use2fa = newPlan.Has2fa;
organization.UseApi = newPlan.HasApi;
organization.UseSso = newPlan.HasSso;
organization.UseKeyConnector = newPlan.HasKeyConnector;
organization.UseScim = newPlan.HasScim;
organization.UseResetPassword = newPlan.HasResetPassword;
organization.SelfHost = newPlan.HasSelfHost;
organization.UsersGetPremium = newPlan.UsersGetPremium || upgrade.PremiumAccessAddon;
organization.UseCustomPermissions = newPlan.HasCustomPermissions;
organization.Plan = newPlan.Name;
organization.Enabled = success;
organization.PublicKey = upgrade.PublicKey;
organization.PrivateKey = upgrade.PrivateKey;
@ -271,8 +265,8 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
if (upgrade.UseSecretsManager)
{
organization.SmSeats = newSecretsManagerPlan.BaseSeats + upgrade.AdditionalSmSeats.GetValueOrDefault();
organization.SmServiceAccounts = newSecretsManagerPlan.BaseServiceAccount.GetValueOrDefault() +
organization.SmSeats = newPlan.SecretsManager.BaseSeats + upgrade.AdditionalSmSeats.GetValueOrDefault();
organization.SmServiceAccounts = newPlan.SecretsManager.BaseServiceAccount +
upgrade.AdditionalServiceAccounts.GetValueOrDefault();
}
@ -283,10 +277,10 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
await _referenceEventService.RaiseEventAsync(
new ReferenceEvent(ReferenceEventType.UpgradePlan, organization, _currentContext)
{
PlanName = newPasswordManagerPlan.Name,
PlanType = newPasswordManagerPlan.Type,
OldPlanName = existingPasswordManagerPlan.Name,
OldPlanType = existingPasswordManagerPlan.Type,
PlanName = newPlan.Name,
PlanType = newPlan.Type,
OldPlanName = existingPlan.Name,
OldPlanType = existingPlan.Type,
Seats = organization.Seats,
Storage = organization.MaxStorageGb,
// TODO: add reference events for SmSeats and Service Accounts - see AC-1481
@ -299,8 +293,8 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
private async Task ValidateSecretsManagerSeatsAndServiceAccountAsync(OrganizationUpgrade upgrade, Organization organization,
Models.StaticStore.Plan newSecretsManagerPlan)
{
var newPlanSmSeats = (short)(newSecretsManagerPlan.BaseSeats +
(newSecretsManagerPlan.HasAdditionalSeatsOption
var newPlanSmSeats = (short)(newSecretsManagerPlan.SecretsManager.BaseSeats +
(newSecretsManagerPlan.SecretsManager.HasAdditionalSeatsOption
? upgrade.AdditionalSmSeats
: 0));
var occupiedSmSeats =
@ -316,10 +310,10 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
}
}
var additionalServiceAccounts = newSecretsManagerPlan.HasAdditionalServiceAccountOption
var additionalServiceAccounts = newSecretsManagerPlan.SecretsManager.HasAdditionalServiceAccountOption
? upgrade.AdditionalServiceAccounts
: 0;
var newPlanServiceAccounts = newSecretsManagerPlan.BaseServiceAccount + additionalServiceAccounts;
var newPlanServiceAccounts = newSecretsManagerPlan.SecretsManager.BaseServiceAccount + additionalServiceAccounts;
if (!organization.SmServiceAccounts.HasValue || organization.SmServiceAccounts.Value > newPlanServiceAccounts)
{
@ -329,7 +323,7 @@ public class UpgradeOrganizationPlanCommand : IUpgradeOrganizationPlanCommand
{
throw new BadRequestException(
$"Your organization currently has {currentServiceAccounts} service accounts. " +
$"Your new plan only allows {newSecretsManagerPlan.MaxServiceAccounts} service accounts. " +
$"Your new plan only allows {newSecretsManagerPlan.SecretsManager.MaxServiceAccounts} service accounts. " +
"Remove some service accounts or increase your subscription.");
}
}

View File

@ -9,12 +9,12 @@ public interface IPaymentService
{
Task CancelAndRecoverChargesAsync(ISubscriber subscriber);
Task<string> PurchaseOrganizationAsync(Organization org, PaymentMethodType paymentMethodType,
string paymentToken, List<Plan> plans, short additionalStorageGb, int additionalSeats,
string paymentToken, Plan plan, short additionalStorageGb, int additionalSeats,
bool premiumAccessAddon, TaxInfo taxInfo, bool provider = false, int additionalSmSeats = 0,
int additionalServiceAccount = 0);
Task SponsorOrganizationAsync(Organization org, OrganizationSponsorship sponsorship);
Task RemoveOrganizationSponsorshipAsync(Organization org, OrganizationSponsorship sponsorship);
Task<string> UpgradeFreeOrganizationAsync(Organization org, List<Plan> plans, OrganizationUpgrade upgrade);
Task<string> UpgradeFreeOrganizationAsync(Organization org, Plan plan, OrganizationUpgrade upgrade);
Task<string> PurchasePremiumAsync(User user, PaymentMethodType paymentMethodType, string paymentToken,
short additionalStorageGb, TaxInfo taxInfo);
Task<string> AdjustSeatsAsync(Organization organization, Plan plan, int additionalSeats, DateTime? prorationDate = null);

View File

@ -175,19 +175,19 @@ public class OrganizationService : IOrganizationService
throw new NotFoundException();
}
var plan = StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == organization.PlanType);
var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == organization.PlanType);
if (plan == null)
{
throw new BadRequestException("Existing plan not found.");
}
if (!plan.HasAdditionalStorageOption)
if (!plan.PasswordManager.HasAdditionalStorageOption)
{
throw new BadRequestException("Plan does not allow additional storage.");
}
var secret = await BillingHelpers.AdjustStorageAsync(_paymentService, organization, storageAdjustmentGb,
plan.StripeStoragePlanId);
plan.PasswordManager.StripeStoragePlanId);
await _referenceEventService.RaiseEventAsync(
new ReferenceEvent(ReferenceEventType.AdjustStorage, organization, _currentContext)
{
@ -233,21 +233,21 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException($"Cannot set max seat autoscaling below current seat count.");
}
var plan = StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == organization.PlanType);
var plan = StaticStore.GetPlan(organization.PlanType);
if (plan == null)
{
throw new BadRequestException("Existing plan not found.");
}
if (!plan.AllowSeatAutoscale)
if (!plan.PasswordManager.AllowSeatAutoscale)
{
throw new BadRequestException("Your plan does not allow seat autoscaling.");
}
if (plan.MaxUsers.HasValue && maxAutoscaleSeats.HasValue &&
maxAutoscaleSeats > plan.MaxUsers)
if (plan.PasswordManager.MaxSeats.HasValue && maxAutoscaleSeats.HasValue &&
maxAutoscaleSeats > plan.PasswordManager.MaxSeats)
{
throw new BadRequestException(string.Concat($"Your plan has a seat limit of {plan.MaxUsers}, ",
throw new BadRequestException(string.Concat($"Your plan has a seat limit of {plan.PasswordManager.MaxSeats}, ",
$"but you have specified a max autoscale count of {maxAutoscaleSeats}.",
"Reduce your max autoscale seat count."));
}
@ -285,21 +285,21 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException("No subscription found.");
}
var plan = StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == organization.PlanType);
var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == organization.PlanType);
if (plan == null)
{
throw new BadRequestException("Existing plan not found.");
}
if (!plan.HasAdditionalSeatsOption)
if (!plan.PasswordManager.HasAdditionalSeatsOption)
{
throw new BadRequestException("Plan does not allow additional seats.");
}
var newSeatTotal = organization.Seats.Value + seatAdjustment;
if (plan.BaseSeats > newSeatTotal)
if (plan.PasswordManager.BaseSeats > newSeatTotal)
{
throw new BadRequestException($"Plan has a minimum of {plan.BaseSeats} seats.");
throw new BadRequestException($"Plan has a minimum of {plan.PasswordManager.BaseSeats} seats.");
}
if (newSeatTotal <= 0)
@ -307,11 +307,11 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException("You must have at least 1 seat.");
}
var additionalSeats = newSeatTotal - plan.BaseSeats;
if (plan.MaxAdditionalSeats.HasValue && additionalSeats > plan.MaxAdditionalSeats.Value)
var additionalSeats = newSeatTotal - plan.PasswordManager.BaseSeats;
if (plan.PasswordManager.MaxAdditionalSeats.HasValue && additionalSeats > plan.PasswordManager.MaxAdditionalSeats.Value)
{
throw new BadRequestException($"Organization plan allows a maximum of " +
$"{plan.MaxAdditionalSeats.Value} additional seats.");
$"{plan.PasswordManager.MaxAdditionalSeats.Value} additional seats.");
}
if (!organization.Seats.HasValue || organization.Seats.Value > newSeatTotal)
@ -403,11 +403,10 @@ public class OrganizationService : IOrganizationService
public async Task<Tuple<Organization, OrganizationUser>> SignUpAsync(OrganizationSignup signup,
bool provider = false)
{
var passwordManagerPlan = StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == signup.Plan);
var plan = StaticStore.GetPlan(signup.Plan);
ValidatePasswordManagerPlan(passwordManagerPlan, signup);
ValidatePasswordManagerPlan(plan, signup);
var secretsManagerPlan = StaticStore.SecretManagerPlans.FirstOrDefault(p => p.Type == signup.Plan);
if (signup.UseSecretsManager)
{
if (provider)
@ -415,7 +414,7 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException(
"Organizations with a Managed Service Provider do not support Secrets Manager.");
}
ValidateSecretsManagerPlan(secretsManagerPlan, signup);
ValidateSecretsManagerPlan(plan, signup);
}
if (!provider)
@ -430,25 +429,25 @@ public class OrganizationService : IOrganizationService
Name = signup.Name,
BillingEmail = signup.BillingEmail,
BusinessName = signup.BusinessName,
PlanType = passwordManagerPlan.Type,
Seats = (short)(passwordManagerPlan.BaseSeats + signup.AdditionalSeats),
MaxCollections = passwordManagerPlan.MaxCollections,
MaxStorageGb = !passwordManagerPlan.BaseStorageGb.HasValue ?
(short?)null : (short)(passwordManagerPlan.BaseStorageGb.Value + signup.AdditionalStorageGb),
UsePolicies = passwordManagerPlan.HasPolicies,
UseSso = passwordManagerPlan.HasSso,
UseGroups = passwordManagerPlan.HasGroups,
UseEvents = passwordManagerPlan.HasEvents,
UseDirectory = passwordManagerPlan.HasDirectory,
UseTotp = passwordManagerPlan.HasTotp,
Use2fa = passwordManagerPlan.Has2fa,
UseApi = passwordManagerPlan.HasApi,
UseResetPassword = passwordManagerPlan.HasResetPassword,
SelfHost = passwordManagerPlan.HasSelfHost,
UsersGetPremium = passwordManagerPlan.UsersGetPremium || signup.PremiumAccessAddon,
UseCustomPermissions = passwordManagerPlan.HasCustomPermissions,
UseScim = passwordManagerPlan.HasScim,
Plan = passwordManagerPlan.Name,
PlanType = plan!.Type,
Seats = (short)(plan.PasswordManager.BaseSeats + signup.AdditionalSeats),
MaxCollections = plan.PasswordManager.MaxCollections,
MaxStorageGb = !plan.PasswordManager.BaseStorageGb.HasValue ?
(short?)null : (short)(plan.PasswordManager.BaseStorageGb.Value + signup.AdditionalStorageGb),
UsePolicies = plan.HasPolicies,
UseSso = plan.HasSso,
UseGroups = plan.HasGroups,
UseEvents = plan.HasEvents,
UseDirectory = plan.HasDirectory,
UseTotp = plan.HasTotp,
Use2fa = plan.Has2fa,
UseApi = plan.HasApi,
UseResetPassword = plan.HasResetPassword,
SelfHost = plan.HasSelfHost,
UsersGetPremium = plan.UsersGetPremium || signup.PremiumAccessAddon,
UseCustomPermissions = plan.HasCustomPermissions,
UseScim = plan.HasScim,
Plan = plan.Name,
Gateway = null,
ReferenceData = signup.Owner.ReferenceData,
Enabled = true,
@ -464,12 +463,12 @@ public class OrganizationService : IOrganizationService
if (signup.UseSecretsManager)
{
organization.SmSeats = secretsManagerPlan.BaseSeats + signup.AdditionalSmSeats.GetValueOrDefault();
organization.SmServiceAccounts = secretsManagerPlan.BaseServiceAccount.GetValueOrDefault() +
organization.SmSeats = plan.SecretsManager.BaseSeats + signup.AdditionalSmSeats.GetValueOrDefault();
organization.SmServiceAccounts = plan.SecretsManager.BaseServiceAccount +
signup.AdditionalServiceAccounts.GetValueOrDefault();
}
if (passwordManagerPlan.Type == PlanType.Free && !provider)
if (plan.Type == PlanType.Free && !provider)
{
var adminCount =
await _organizationUserRepository.GetCountByFreeOrganizationAdminUserAsync(signup.Owner.Id);
@ -478,14 +477,10 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException("You can only be an admin of one free organization.");
}
}
else if (passwordManagerPlan.Type != PlanType.Free)
else if (plan.Type != PlanType.Free)
{
var purchaseOrganizationPlan = signup.UseSecretsManager
? StaticStore.Plans.Where(p => p.Type == signup.Plan).ToList()
: StaticStore.PasswordManagerPlans.Where(p => p.Type == signup.Plan).Take(1).ToList();
await _paymentService.PurchaseOrganizationAsync(organization, signup.PaymentMethodType.Value,
signup.PaymentToken, purchaseOrganizationPlan, signup.AdditionalStorageGb, signup.AdditionalSeats,
signup.PaymentToken, plan, signup.AdditionalStorageGb, signup.AdditionalSeats,
signup.PremiumAccessAddon, signup.TaxInfo, provider, signup.AdditionalSmSeats.GetValueOrDefault(),
signup.AdditionalServiceAccounts.GetValueOrDefault());
}
@ -495,8 +490,8 @@ public class OrganizationService : IOrganizationService
await _referenceEventService.RaiseEventAsync(
new ReferenceEvent(ReferenceEventType.Signup, organization, _currentContext)
{
PlanName = passwordManagerPlan.Name,
PlanType = passwordManagerPlan.Type,
PlanName = plan.Name,
PlanType = plan.Type,
Seats = returnValue.Item1.Seats,
Storage = returnValue.Item1.MaxStorageGb,
// TODO: add reference events for SmSeats and Service Accounts - see AC-1481
@ -525,7 +520,7 @@ public class OrganizationService : IOrganizationService
}
if (license.PlanType != PlanType.Custom &&
StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == license.PlanType && !p.Disabled) == null)
StaticStore.Plans.FirstOrDefault(p => p.Type == license.PlanType && !p.Disabled) == null)
{
throw new BadRequestException("Plan not found.");
}
@ -573,7 +568,11 @@ public class OrganizationService : IOrganizationService
PrivateKey = privateKey,
CreationDate = DateTime.UtcNow,
RevisionDate = DateTime.UtcNow,
Status = OrganizationStatusType.Created
Status = OrganizationStatusType.Created,
UsePasswordManager = license.UsePasswordManager,
UseSecretsManager = license.UseSecretsManager,
SmSeats = license.SmSeats,
SmServiceAccounts = license.SmServiceAccounts
};
var result = await SignUpAsync(organization, owner.Id, ownerKey, collectionName, false);
@ -1951,11 +1950,6 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException($"{productType} Plan not found.");
}
if (plan.BaseSeats + additionalSeats <= 0)
{
throw new BadRequestException($"You do not have any {productType} seats!");
}
if (additionalSeats < 0)
{
throw new BadRequestException($"You can't subtract {productType} seats!");
@ -1966,7 +1960,7 @@ public class OrganizationService : IOrganizationService
{
ValidatePlan(plan, upgrade.AdditionalSeats, "Password Manager");
if (plan.BaseSeats + upgrade.AdditionalSeats <= 0)
if (plan.PasswordManager.BaseSeats + upgrade.AdditionalSeats <= 0)
{
throw new BadRequestException($"You do not have any Password Manager seats!");
}
@ -1976,7 +1970,7 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException($"You can't subtract Password Manager seats!");
}
if (!plan.HasAdditionalStorageOption && upgrade.AdditionalStorageGb > 0)
if (!plan.PasswordManager.HasAdditionalStorageOption && upgrade.AdditionalStorageGb > 0)
{
throw new BadRequestException("Plan does not allow additional storage.");
}
@ -1986,29 +1980,39 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException("You can't subtract storage!");
}
if (!plan.HasPremiumAccessOption && upgrade.PremiumAccessAddon)
if (!plan.PasswordManager.HasPremiumAccessOption && upgrade.PremiumAccessAddon)
{
throw new BadRequestException("This plan does not allow you to buy the premium access addon.");
}
if (!plan.HasAdditionalSeatsOption && upgrade.AdditionalSeats > 0)
if (!plan.PasswordManager.HasAdditionalSeatsOption && upgrade.AdditionalSeats > 0)
{
throw new BadRequestException("Plan does not allow additional users.");
}
if (plan.HasAdditionalSeatsOption && plan.MaxAdditionalSeats.HasValue &&
upgrade.AdditionalSeats > plan.MaxAdditionalSeats.Value)
if (plan.PasswordManager.HasAdditionalSeatsOption && plan.PasswordManager.MaxAdditionalSeats.HasValue &&
upgrade.AdditionalSeats > plan.PasswordManager.MaxAdditionalSeats.Value)
{
throw new BadRequestException($"Selected plan allows a maximum of " +
$"{plan.MaxAdditionalSeats.GetValueOrDefault(0)} additional users.");
$"{plan.PasswordManager.MaxAdditionalSeats.GetValueOrDefault(0)} additional users.");
}
}
public void ValidateSecretsManagerPlan(Models.StaticStore.Plan plan, OrganizationUpgrade upgrade)
{
if (plan.SupportsSecretsManager == false)
{
throw new BadRequestException("Invalid Secrets Manager plan selected.");
}
ValidatePlan(plan, upgrade.AdditionalSmSeats.GetValueOrDefault(), "Secrets Manager");
if (!plan.HasAdditionalServiceAccountOption && upgrade.AdditionalServiceAccounts > 0)
if (plan.SecretsManager.BaseSeats + upgrade.AdditionalSmSeats <= 0)
{
throw new BadRequestException($"You do not have any Secrets Manager seats!");
}
if (!plan.SecretsManager.HasAdditionalServiceAccountOption && upgrade.AdditionalServiceAccounts > 0)
{
throw new BadRequestException("Plan does not allow additional Service Accounts.");
}
@ -2023,14 +2027,14 @@ public class OrganizationService : IOrganizationService
throw new BadRequestException("You can't subtract Service Accounts!");
}
switch (plan.HasAdditionalSeatsOption)
switch (plan.SecretsManager.HasAdditionalSeatsOption)
{
case false when upgrade.AdditionalSmSeats > 0:
throw new BadRequestException("Plan does not allow additional users.");
case true when plan.MaxAdditionalSeats.HasValue &&
upgrade.AdditionalSmSeats > plan.MaxAdditionalSeats.Value:
case true when plan.SecretsManager.MaxAdditionalSeats.HasValue &&
upgrade.AdditionalSmSeats > plan.SecretsManager.MaxAdditionalSeats.Value:
throw new BadRequestException($"Selected plan allows a maximum of " +
$"{plan.MaxAdditionalSeats.GetValueOrDefault(0)} additional users.");
$"{plan.SecretsManager.MaxAdditionalSeats.GetValueOrDefault(0)} additional users.");
}
}
@ -2459,7 +2463,7 @@ public class OrganizationService : IOrganizationService
public async Task CreatePendingOrganization(Organization organization, string ownerEmail, ClaimsPrincipal user, IUserService userService, bool salesAssistedTrialStarted)
{
var plan = StaticStore.PasswordManagerPlans.FirstOrDefault(p => p.Type == organization.PlanType);
var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == organization.PlanType);
if (plan is not { LegacyYear: null })
{
throw new BadRequestException("Invalid plan selected.");

View File

@ -49,7 +49,7 @@ public class StripePaymentService : IPaymentService
}
public async Task<string> PurchaseOrganizationAsync(Organization org, PaymentMethodType paymentMethodType,
string paymentToken, List<StaticStore.Plan> plans, short additionalStorageGb,
string paymentToken, StaticStore.Plan plan, short additionalStorageGb,
int additionalSeats, bool premiumAccessAddon, TaxInfo taxInfo, bool provider = false,
int additionalSmSeats = 0, int additionalServiceAccount = 0)
{
@ -119,7 +119,7 @@ public class StripePaymentService : IPaymentService
}
}
var subCreateOptions = new OrganizationPurchaseSubscriptionOptions(org, plans, taxInfo, additionalSeats, additionalStorageGb, premiumAccessAddon
var subCreateOptions = new OrganizationPurchaseSubscriptionOptions(org, plan, taxInfo, additionalSeats, additionalStorageGb, premiumAccessAddon
, additionalSmSeats, additionalServiceAccount);
Stripe.Customer customer = null;
@ -211,7 +211,7 @@ public class StripePaymentService : IPaymentService
private async Task ChangeOrganizationSponsorship(Organization org, OrganizationSponsorship sponsorship, bool applySponsorship)
{
var existingPlan = Utilities.StaticStore.GetPasswordManagerPlan(org.PlanType);
var existingPlan = Utilities.StaticStore.GetPlan(org.PlanType);
var sponsoredPlan = sponsorship != null ?
Utilities.StaticStore.GetSponsoredPlan(sponsorship.PlanSponsorshipType.Value) :
null;
@ -231,7 +231,7 @@ public class StripePaymentService : IPaymentService
public Task RemoveOrganizationSponsorshipAsync(Organization org, OrganizationSponsorship sponsorship) =>
ChangeOrganizationSponsorship(org, sponsorship, false);
public async Task<string> UpgradeFreeOrganizationAsync(Organization org, List<StaticStore.Plan> plans,
public async Task<string> UpgradeFreeOrganizationAsync(Organization org, StaticStore.Plan plan,
OrganizationUpgrade upgrade)
{
if (!string.IsNullOrWhiteSpace(org.GatewaySubscriptionId))
@ -266,7 +266,7 @@ public class StripePaymentService : IPaymentService
}
}
var subCreateOptions = new OrganizationUpgradeSubscriptionOptions(customer.Id, org, plans, upgrade);
var subCreateOptions = new OrganizationUpgradeSubscriptionOptions(customer.Id, org, plan, upgrade);
var (stripePaymentMethod, paymentMethodType) = IdentifyPaymentMethod(customer, subCreateOptions);
var subscription = await ChargeForNewSubscriptionAsync(org, customer, false,

View File

@ -1,398 +0,0 @@
using Bit.Core.Enums;
using Bit.Core.Models.StaticStore;
namespace Bit.Core.Utilities;
public static class PasswordManagerPlanStore
{
public static IEnumerable<Plan> CreatePlan()
{
return new List<Plan>
{
new Plan
{
Type = PlanType.Free,
Product = ProductType.Free,
BitwardenProduct = BitwardenProductType.PasswordManager,
Name = "Free",
NameLocalizationKey = "planNameFree",
DescriptionLocalizationKey = "planDescFree",
BaseSeats = 2,
MaxCollections = 2,
MaxUsers = 2,
UpgradeSortOrder = -1, // Always the lowest plan, cannot be upgraded to
DisplaySortOrder = -1,
AllowSeatAutoscale = false,
},
new Plan
{
Type = PlanType.FamiliesAnnually2019,
Product = ProductType.Families,
BitwardenProduct = BitwardenProductType.PasswordManager,
Name = "Families 2019",
IsAnnual = true,
NameLocalizationKey = "planNameFamilies",
DescriptionLocalizationKey = "planDescFamilies",
BaseSeats = 5,
BaseStorageGb = 1,
MaxUsers = 5,
HasAdditionalStorageOption = true,
HasPremiumAccessOption = true,
TrialPeriodDays = 7,
HasSelfHost = true,
HasTotp = true,
UpgradeSortOrder = 1,
DisplaySortOrder = 1,
LegacyYear = 2020,
StripePlanId = "personal-org-annually",
StripeStoragePlanId = "storage-gb-annually",
StripePremiumAccessPlanId = "personal-org-premium-access-annually",
BasePrice = 12,
AdditionalStoragePricePerGb = 4,
PremiumAccessOptionPrice = 40,
AllowSeatAutoscale = false,
},
new Plan
{
Type = PlanType.TeamsAnnually2019,
Product = ProductType.Teams,
BitwardenProduct = BitwardenProductType.PasswordManager,
Name = "Teams (Annually) 2019",
IsAnnual = true,
NameLocalizationKey = "planNameTeams",
DescriptionLocalizationKey = "planDescTeams",
CanBeUsedByBusiness = true,
BaseSeats = 5,
BaseStorageGb = 1,
HasAdditionalSeatsOption = true,
HasAdditionalStorageOption = true,
TrialPeriodDays = 7,
HasTotp = true,
UpgradeSortOrder = 2,
DisplaySortOrder = 2,
LegacyYear = 2020,
StripePlanId = "teams-org-annually",
StripeSeatPlanId = "teams-org-seat-annually",
StripeStoragePlanId = "storage-gb-annually",
BasePrice = 60,
SeatPrice = 24,
AdditionalStoragePricePerGb = 4,
AllowSeatAutoscale = true,
},
new Plan
{
Type = PlanType.TeamsMonthly2019,
Product = ProductType.Teams,
BitwardenProduct = BitwardenProductType.PasswordManager,
Name = "Teams (Monthly) 2019",
NameLocalizationKey = "planNameTeams",
DescriptionLocalizationKey = "planDescTeams",
CanBeUsedByBusiness = true,
BaseSeats = 5,
BaseStorageGb = 1,
HasAdditionalSeatsOption = true,
HasAdditionalStorageOption = true,
TrialPeriodDays = 7,
HasTotp = true,
UpgradeSortOrder = 2,
DisplaySortOrder = 2,
LegacyYear = 2020,
StripePlanId = "teams-org-monthly",
StripeSeatPlanId = "teams-org-seat-monthly",
StripeStoragePlanId = "storage-gb-monthly",
BasePrice = 8,
SeatPrice = 2.5M,
AdditionalStoragePricePerGb = 0.5M,
AllowSeatAutoscale = true,
},
new Plan
{
Type = PlanType.EnterpriseAnnually2019,
Name = "Enterprise (Annually) 2019",
IsAnnual = true,
Product = ProductType.Enterprise,
BitwardenProduct = BitwardenProductType.PasswordManager,
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,
UsersGetPremium = true,
HasCustomPermissions = true,
UpgradeSortOrder = 3,
DisplaySortOrder = 3,
LegacyYear = 2020,
StripePlanId = null,
StripeSeatPlanId = "enterprise-org-seat-annually",
StripeStoragePlanId = "storage-gb-annually",
BasePrice = 0,
SeatPrice = 36,
AdditionalStoragePricePerGb = 4,
AllowSeatAutoscale = true,
},
new Plan
{
Type = PlanType.EnterpriseMonthly2019,
Product = ProductType.Enterprise,
BitwardenProduct = BitwardenProductType.PasswordManager,
Name = "Enterprise (Monthly) 2019",
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,
UsersGetPremium = true,
HasCustomPermissions = true,
UpgradeSortOrder = 3,
DisplaySortOrder = 3,
LegacyYear = 2020,
StripePlanId = null,
StripeSeatPlanId = "enterprise-org-seat-monthly",
StripeStoragePlanId = "storage-gb-monthly",
BasePrice = 0,
SeatPrice = 4M,
AdditionalStoragePricePerGb = 0.5M,
AllowSeatAutoscale = true,
},
new Plan
{
Type = PlanType.FamiliesAnnually,
Product = ProductType.Families,
BitwardenProduct = BitwardenProductType.PasswordManager,
Name = "Families",
IsAnnual = true,
NameLocalizationKey = "planNameFamilies",
DescriptionLocalizationKey = "planDescFamilies",
BaseSeats = 6,
BaseStorageGb = 1,
MaxUsers = 6,
HasAdditionalStorageOption = true,
TrialPeriodDays = 7,
HasSelfHost = true,
HasTotp = true,
UsersGetPremium = true,
UpgradeSortOrder = 1,
DisplaySortOrder = 1,
StripePlanId = "2020-families-org-annually",
StripeStoragePlanId = "storage-gb-annually",
BasePrice = 40,
AdditionalStoragePricePerGb = 4,
AllowSeatAutoscale = false,
},
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 = "2020-teams-org-seat-annually",
StripeStoragePlanId = "storage-gb-annually",
SeatPrice = 36,
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 = "2020-teams-org-seat-monthly",
StripeStoragePlanId = "storage-gb-monthly",
SeatPrice = 4,
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 = "2020-enterprise-org-seat-annually",
StripeStoragePlanId = "storage-gb-annually",
BasePrice = 0,
SeatPrice = 60,
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 = "2020-enterprise-seat-monthly",
StripeStoragePlanId = "storage-gb-monthly",
BasePrice = 0,
SeatPrice = 6,
AdditionalStoragePricePerGb = 0.5M,
AllowSeatAutoscale = true,
},
new Plan
{
Type = PlanType.Custom,
AllowSeatAutoscale = true,
},
};
}
}

View File

@ -1,172 +0,0 @@
using Bit.Core.Enums;
using Bit.Core.Models.StaticStore;
namespace Bit.Core.Utilities;
public static class SecretsManagerPlanStore
{
public static IEnumerable<Plan> CreatePlan()
{
return new List<Plan>
{
new Plan
{
Type = PlanType.EnterpriseMonthly,
Product = ProductType.Enterprise,
BitwardenProduct = BitwardenProductType.SecretsManager,
Name = "Enterprise (Monthly)",
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
},
new Plan
{
Type = PlanType.EnterpriseAnnually,
Name = "Enterprise (Annually)",
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
},
new Plan
{
Type = PlanType.TeamsMonthly,
Name = "Teams (Monthly)",
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
},
new Plan
{
Type = PlanType.TeamsAnnually,
Name = "Teams (Annually)",
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
},
new Plan
{
Type = PlanType.Free,
Product = ProductType.Free,
BitwardenProduct = BitwardenProductType.SecretsManager,
Name = "Free",
NameLocalizationKey = "planNameFree",
DescriptionLocalizationKey = "planDescFree",
BaseSeats = 2,
BaseServiceAccount = 3,
MaxProjects = 3,
MaxUsers = 2,
MaxServiceAccounts = 3,
UpgradeSortOrder = -1, // Always the lowest plan, cannot be upgraded to
DisplaySortOrder = -1,
AllowSeatAutoscale = false,
}
};
}
}

View File

@ -1,6 +1,8 @@
using Bit.Core.Enums;
using System.Collections.Immutable;
using Bit.Core.Enums;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Models.StaticStore;
using Bit.Core.Models.StaticStore.Plans;
namespace Bit.Core.Utilities;
@ -104,21 +106,26 @@ public class StaticStore
GlobalDomains.Add(GlobalEquivalentDomainsType.Pinterest, new List<string> { "pinterest.com", "pinterest.com.au", "pinterest.cl", "pinterest.de", "pinterest.dk", "pinterest.es", "pinterest.fr", "pinterest.co.uk", "pinterest.jp", "pinterest.co.kr", "pinterest.nz", "pinterest.pt", "pinterest.se" });
#endregion
#region Plans
Plans = new List<Models.StaticStore.Plan>
{
new EnterprisePlan(true),
new EnterprisePlan(false),
new TeamsPlan(true),
new TeamsPlan(false),
new FamiliesPlan(),
new FreePlan(),
new CustomPlan(),
PasswordManagerPlans = PasswordManagerPlanStore.CreatePlan();
SecretManagerPlans = SecretsManagerPlanStore.CreatePlan();
Plans = PasswordManagerPlans.Concat(SecretManagerPlans);
#endregion
new Enterprise2019Plan(true),
new Enterprise2019Plan(false),
new Teams2019Plan(true),
new Teams2019Plan(false),
new Families2019Plan(),
}.ToImmutableList();
}
public static IDictionary<GlobalEquivalentDomainsType, IEnumerable<string>> GlobalDomains { get; set; }
public static IEnumerable<Plan> Plans { get; set; }
public static IEnumerable<Plan> SecretManagerPlans { get; set; }
public static IEnumerable<Plan> PasswordManagerPlans { get; set; }
public static IEnumerable<Models.StaticStore.Plan> Plans { get; }
public static IEnumerable<SponsoredPlan> SponsoredPlans { get; set; } = new[]
{
new SponsoredPlan
@ -128,21 +135,20 @@ public class StaticStore
SponsoringProductType = ProductType.Enterprise,
StripePlanId = "2021-family-for-enterprise-annually",
UsersCanSponsor = (OrganizationUserOrganizationDetails org) =>
GetPasswordManagerPlan(org.PlanType).Product == ProductType.Enterprise,
GetPlan(org.PlanType).Product == ProductType.Enterprise,
}
};
public static Plan GetPasswordManagerPlan(PlanType planType) =>
PasswordManagerPlans.SingleOrDefault(p => p.Type == planType);
public static Plan GetSecretsManagerPlan(PlanType planType) =>
SecretManagerPlans.SingleOrDefault(p => p.Type == planType);
public static Models.StaticStore.Plan GetPlan(PlanType planType) =>
Plans.SingleOrDefault(p => p.Type == planType);
public static SponsoredPlan GetSponsoredPlan(PlanSponsorshipType planSponsorshipType) =>
SponsoredPlans.FirstOrDefault(p => p.PlanSponsorshipType == planSponsorshipType);
/// <summary>
/// Determines if the stripe plan id is an addon item by checking if the provided stripe plan id
/// matches either the <see cref="Plan.StripeStoragePlanId"/> or <see cref="Plan.StripeServiceAccountPlanId"/>
/// matches either the <see cref="Plan.PasswordManagerPlanFeatures.StripeStoragePlanId"/> or <see cref="Plan.SecretsManagerPlanFeatures.StripeServiceAccountPlanId"/>
/// in any <see cref="Plans"/>.
/// </summary>
/// <param name="stripePlanId"></param>
@ -151,41 +157,8 @@ public class StaticStore
/// </returns>
public static bool IsAddonSubscriptionItem(string stripePlanId)
{
if (PasswordManagerPlans.Select(p => p.StripeStoragePlanId).Contains(stripePlanId))
{
return true;
}
if (SecretManagerPlans.Select(p => p.StripeServiceAccountPlanId).Contains(stripePlanId))
{
return true;
}
return false;
}
/// <summary>
/// Get a <see cref="Plan"/> by comparing the provided stripeId to the various
/// Stripe plan ids within a <see cref="Plan"/>.
/// The following <see cref="Plan"/> properties are checked:
/// <list type="bullet">
/// <item><see cref="Plan.StripePlanId"/></item>
/// <item><see cref="Plan.StripeSeatPlanId"/></item>
/// <item><see cref="Plan.StripeStoragePlanId"/></item>
/// <item><see cref="Plan.StripeServiceAccountPlanId"/></item>
/// <item><see cref="Plan.StripePremiumAccessPlanId"/></item>
/// </list>
/// </summary>
/// <param name="stripeId"></param>
/// <returns>The plan if a matching stripeId was found, null otherwise</returns>
public static Plan GetPlanByStripeId(string stripeId)
{
return Plans.FirstOrDefault(p =>
p.StripePlanId == stripeId ||
p.StripeSeatPlanId == stripeId ||
p.StripeStoragePlanId == stripeId ||
p.StripeServiceAccountPlanId == stripeId ||
p.StripePremiumAccessPlanId == stripeId
);
return Plans.Any(p =>
p.PasswordManager.StripeStoragePlanId == stripePlanId ||
(p.SecretsManager?.StripeServiceAccountPlanId == stripePlanId));
}
}

View File

@ -7,5 +7,5 @@ public enum CipherType : byte
Login = 1,
SecureNote = 2,
Card = 3,
Identity = 4
Identity = 4,
}

View File

@ -19,6 +19,7 @@ public class CipherLoginData : CipherData
public DateTime? PasswordRevisionDate { get; set; }
public string Totp { get; set; }
public bool? AutofillOnPageLoad { get; set; }
public CipherLoginFido2CredentialData[] Fido2Credentials { get; set; }
public class CipherLoginUriData
{

View File

@ -0,0 +1,19 @@
namespace Bit.Core.Vault.Models.Data;
public class CipherLoginFido2CredentialData
{
public CipherLoginFido2CredentialData() { }
public string CredentialId { get; set; }
public string KeyType { get; set; }
public string KeyAlgorithm { get; set; }
public string KeyCurve { get; set; }
public string KeyValue { get; set; }
public string RpId { get; set; }
public string RpName { get; set; }
public string UserHandle { get; set; }
public string UserDisplayName { get; set; }
public string Counter { get; set; }
public string Discoverable { get; set; }
public DateTime CreationDate { get; set; }
}

View File

@ -100,7 +100,7 @@ public class ClientStore : IClientStore
{
case ServiceAccountApiKeyDetails key:
var org = await _organizationRepository.GetByIdAsync(key.ServiceAccountOrganizationId);
if (!org.UseSecretsManager)
if (!org.UseSecretsManager || !org.Enabled)
{
return null;
}

View File

@ -56,12 +56,16 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task CreateProjectAccessPolicies_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task CreateProjectAccessPolicies_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var (projectId, serviceAccountId) = await CreateProjectAndServiceAccountAsync(org.Id);
@ -82,7 +86,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
public async Task CreateProjectAccessPolicies_NoPermission()
{
// Create a new account as a user
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
@ -105,7 +109,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task CreateProjectAccessPolicies_MismatchedOrgIds_NotFound(PermissionType permissionType)
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var (projectId, serviceAccountId) = await CreateProjectAndServiceAccountAsync(org.Id, true);
@ -130,7 +134,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task CreateProjectAccessPolicies_Success(PermissionType permissionType)
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var (projectId, serviceAccountId) = await CreateProjectAndServiceAccountAsync(org.Id);
@ -167,12 +171,16 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task UpdateAccessPolicy_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task UpdateAccessPolicy_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var initData = await SetupAccessPolicyRequest(org.Id);
@ -188,7 +196,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
public async Task UpdateAccessPolicy_NoPermission()
{
// Create a new account as a user
await _organizationHelper.Initialize(true, true);
await _organizationHelper.Initialize(true, true, true);
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
@ -208,7 +216,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task UpdateAccessPolicy_Success(PermissionType permissionType)
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var initData = await SetupAccessPolicyRequest(org.Id);
@ -248,12 +256,16 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task DeleteAccessPolicy_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task DeleteAccessPolicy_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var initData = await SetupAccessPolicyRequest(org.Id);
@ -265,7 +277,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
public async Task DeleteAccessPolicy_NoPermission()
{
// Create a new account as a user
await _organizationHelper.Initialize(true, true);
await _organizationHelper.Initialize(true, true, true);
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
@ -281,7 +293,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task DeleteAccessPolicy_Success(PermissionType permissionType)
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var initData = await SetupAccessPolicyRequest(org.Id);
@ -309,7 +321,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
[Fact]
public async Task GetProjectAccessPolicies_ReturnsEmpty()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var project = await _projectRepository.CreateAsync(new Project
@ -330,12 +342,16 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task GetProjectAccessPolicies_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task GetProjectAccessPolicies_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var initData = await SetupAccessPolicyRequest(org.Id);
@ -348,7 +364,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
public async Task GetProjectAccessPolicies_NoPermission()
{
// Create a new account as a user
await _organizationHelper.Initialize(true, true);
await _organizationHelper.Initialize(true, true, true);
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
@ -364,7 +380,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task GetProjectAccessPolicies(PermissionType permissionType)
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var initData = await SetupAccessPolicyRequest(org.Id);
@ -392,12 +408,16 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task GetPeoplePotentialGrantees_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task GetPeoplePotentialGrantees_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var response =
@ -411,7 +431,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task GetPeoplePotentialGrantees_Success(PermissionType permissionType)
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
if (permissionType == PermissionType.RunAsUserWithPermission)
@ -432,12 +452,16 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task GetServiceAccountPotentialGrantees_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task GetServiceAccountPotentialGrantees_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var response =
@ -450,7 +474,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
public async Task GetServiceAccountPotentialGrantees_OnlyReturnsServiceAccountsWithWriteAccess()
{
// Create a new account as a user
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
@ -477,7 +501,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task GetServiceAccountsPotentialGrantees_Success(PermissionType permissionType)
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
@ -517,12 +541,16 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task GetProjectPotentialGrantees_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task GetProjectPotentialGrantees_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var response =
@ -535,7 +563,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
public async Task GetProjectPotentialGrantees_OnlyReturnsProjectsWithWriteAccess()
{
// Create a new account as a user
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
@ -558,7 +586,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task GetProjectPotentialGrantees_Success(PermissionType permissionType)
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var project = await _projectRepository.CreateAsync(new Project
@ -595,12 +623,16 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task CreateServiceAccountAccessPolicies_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task CreateServiceAccountAccessPolicies_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, orgUser) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, orgUser) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
@ -627,7 +659,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task CreateServiceAccountAccessPolicies_MismatchOrgId_NotFound(PermissionType permissionType)
{
var (org, orgUser) = await _organizationHelper.Initialize(true, true);
var (org, orgUser) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var ownerOrgUserId = orgUser.Id;
@ -650,7 +682,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task CreateServiceAccountAccessPolicies_Success(PermissionType permissionType)
{
var (org, orgUser) = await _organizationHelper.Initialize(true, true);
var (org, orgUser) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var ownerOrgUserId = orgUser.Id;
@ -687,7 +719,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
public async Task CreateServiceAccountAccessPolicies_NoPermission()
{
// Create a new account as a user
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
@ -711,12 +743,16 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task GetServiceAccountAccessPolicies_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task GetServiceAccountAccessPolicies_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var initData = await SetupAccessPolicyRequest(org.Id);
@ -727,7 +763,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
[Fact]
public async Task GetServiceAccountAccessPolicies_ReturnsEmpty()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
@ -750,7 +786,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
public async Task GetServiceAccountAccessPolicies_NoPermission()
{
// Create a new account as a user
await _organizationHelper.Initialize(true, true);
await _organizationHelper.Initialize(true, true, true);
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
@ -766,7 +802,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task GetServiceAccountAccessPolicies(PermissionType permissionType)
{
var (org, ownerOrgUser) = await _organizationHelper.Initialize(true, true);
var (org, ownerOrgUser) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var initData = await SetupAccessPolicyRequest(org.Id);
@ -811,12 +847,16 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task CreateServiceAccountGrantedPolicies_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task CreateServiceAccountGrantedPolicies_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
@ -836,7 +876,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
public async Task CreateServiceAccountGrantedPolicies_NoPermission()
{
// Create a new account as a user
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
@ -865,7 +905,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task CreateServiceAccountGrantedPolicies_MismatchedOrgId_NotFound(PermissionType permissionType)
{
var (org, orgUser) = await _organizationHelper.Initialize(true, true);
var (org, orgUser) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var ownerOrgUserId = orgUser.Id;
@ -886,7 +926,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task CreateServiceAccountGrantedPolicies_Success(PermissionType permissionType)
{
var (org, orgUser) = await _organizationHelper.Initialize(true, true);
var (org, orgUser) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var ownerOrgUserId = orgUser.Id;
@ -918,12 +958,16 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task GetServiceAccountGrantedPolicies_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task GetServiceAccountGrantedPolicies_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var initData = await SetupAccessPolicyRequest(org.Id);
@ -934,7 +978,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
[Fact]
public async Task GetServiceAccountGrantedPolicies_ReturnsEmpty()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
@ -957,7 +1001,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
public async Task GetServiceAccountGrantedPolicies_NoPermission_ReturnsEmpty()
{
// Create a new account as a user
await _organizationHelper.Initialize(true, true);
await _organizationHelper.Initialize(true, true, true);
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
@ -977,7 +1021,7 @@ public class AccessPoliciesControllerTests : IClassFixture<ApiApplicationFactory
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task GetServiceAccountGrantedPolicies(PermissionType permissionType)
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var initData = await SetupAccessPolicyRequest(org.Id);

View File

@ -56,12 +56,16 @@ public class ProjectsControllerTests : IClassFixture<ApiApplicationFactory>, IAs
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task ListByOrganization_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task ListByOrganization_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var response = await _client.GetAsync($"/organizations/{org.Id}/projects");
@ -71,7 +75,7 @@ public class ProjectsControllerTests : IClassFixture<ApiApplicationFactory>, IAs
[Fact]
public async Task ListByOrganization_UserWithoutPermission_EmptyList()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
@ -102,12 +106,16 @@ public class ProjectsControllerTests : IClassFixture<ApiApplicationFactory>, IAs
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task Create_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task Create_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var request = new ProjectCreateRequestModel { Name = _mockEncryptedString };
@ -134,7 +142,7 @@ public class ProjectsControllerTests : IClassFixture<ApiApplicationFactory>, IAs
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task Create_Success(PermissionType permissionType)
{
var (org, adminOrgUser) = await _organizationHelper.Initialize(true, true);
var (org, adminOrgUser) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var orgUserId = adminOrgUser.Id;
var currentUserId = adminOrgUser.UserId!.Value;
@ -178,12 +186,16 @@ public class ProjectsControllerTests : IClassFixture<ApiApplicationFactory>, IAs
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task Update_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task Update_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var initialProject = await _projectRepository.CreateAsync(new Project
@ -231,7 +243,7 @@ public class ProjectsControllerTests : IClassFixture<ApiApplicationFactory>, IAs
[Fact]
public async Task Update_NonExistingProject_NotFound()
{
await _organizationHelper.Initialize(true, true);
await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var request = new ProjectUpdateRequestModel
@ -248,7 +260,7 @@ public class ProjectsControllerTests : IClassFixture<ApiApplicationFactory>, IAs
[Fact]
public async Task Update_MissingAccessPolicy_NotFound()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
@ -270,12 +282,16 @@ public class ProjectsControllerTests : IClassFixture<ApiApplicationFactory>, IAs
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task Get_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task Get_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var project = await _projectRepository.CreateAsync(new Project
@ -295,7 +311,7 @@ public class ProjectsControllerTests : IClassFixture<ApiApplicationFactory>, IAs
[Fact]
public async Task Get_MissingAccessPolicy_NotFound()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
@ -312,7 +328,7 @@ public class ProjectsControllerTests : IClassFixture<ApiApplicationFactory>, IAs
[Fact]
public async Task Get_NonExistingProject_NotFound()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
@ -346,12 +362,16 @@ public class ProjectsControllerTests : IClassFixture<ApiApplicationFactory>, IAs
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task Delete_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task Delete_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var projectIds = await CreateProjectsAsync(org.Id);
@ -363,7 +383,7 @@ public class ProjectsControllerTests : IClassFixture<ApiApplicationFactory>, IAs
[Fact]
public async Task Delete_MissingAccessPolicy_AccessDenied()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
@ -417,7 +437,7 @@ public class ProjectsControllerTests : IClassFixture<ApiApplicationFactory>, IAs
private async Task<(List<Guid>, Organization)> SetupProjectsWithAccessAsync(PermissionType permissionType,
int projectsToCreate = 3)
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var projectIds = await CreateProjectsAsync(org.Id, projectsToCreate);
@ -446,7 +466,7 @@ public class ProjectsControllerTests : IClassFixture<ApiApplicationFactory>, IAs
private async Task<Project> SetupProjectWithAccessAsync(PermissionType permissionType)
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var initialProject = await _projectRepository.CreateAsync(new Project

View File

@ -56,12 +56,16 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task ListByOrganization_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task ListByOrganization_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var response = await _client.GetAsync($"/organizations/{org.Id}/secrets");
@ -73,7 +77,7 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task ListByOrganization_Success(PermissionType permissionType)
{
var (org, orgUserOwner) = await _organizationHelper.Initialize(true, true);
var (org, orgUserOwner) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var project = await _projectRepository.CreateAsync(new Project
@ -123,12 +127,16 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task Create_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task Create_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var request = new SecretCreateRequestModel
@ -145,7 +153,7 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
[Fact]
public async Task CreateWithoutProject_RunAsAdmin_Success()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var request = new SecretCreateRequestModel
@ -179,7 +187,7 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
[Fact]
public async Task CreateWithDifferentProjectOrgId_RunAsAdmin_NotFound()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var project = await _projectRepository.CreateAsync(new Project { Name = "123" });
@ -199,7 +207,7 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
[Fact]
public async Task CreateWithMultipleProjects_RunAsAdmin_BadRequest()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var projectA = await _projectRepository.CreateAsync(new Project { OrganizationId = org.Id, Name = "123A" });
@ -220,7 +228,7 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
[Fact]
public async Task CreateWithoutProject_RunAsUser_NotFound()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
@ -240,7 +248,7 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task CreateWithProject_Success(PermissionType permissionType)
{
var (org, orgAdminUser) = await _organizationHelper.Initialize(true, true);
var (org, orgAdminUser) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
AccessClientType accessType = AccessClientType.NoAccessCheck;
@ -296,12 +304,16 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task Get_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task Get_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var secret = await _secretRepository.CreateAsync(new Secret
@ -321,7 +333,7 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task Get_Success(PermissionType permissionType)
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var project = await _projectRepository.CreateAsync(new Project()
@ -371,12 +383,16 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task GetSecretsByProject_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task GetSecretsByProject_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var project = await _projectRepository.CreateAsync(new Project
@ -392,7 +408,7 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
[Fact]
public async Task GetSecretsByProject_UserWithNoPermission_EmptyList()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
@ -425,7 +441,7 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task GetSecretsByProject_Success(PermissionType permissionType)
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var project = await _projectRepository.CreateAsync(new Project()
@ -473,12 +489,16 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task Update_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task Update_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var secret = await _secretRepository.CreateAsync(new Secret
@ -505,7 +525,7 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task Update_Success(PermissionType permissionType)
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var project = await _projectRepository.CreateAsync(new Project()
@ -572,7 +592,7 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
[Fact]
public async Task UpdateWithDifferentProjectOrgId_RunAsAdmin_NotFound()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var project = await _projectRepository.CreateAsync(new Project { Name = "123" });
@ -600,7 +620,7 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
[Fact]
public async Task UpdateWithMultipleProjects_BadRequest()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var projectA = await _projectRepository.CreateAsync(new Project { OrganizationId = org.Id, Name = "123A" });
@ -627,12 +647,16 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task Delete_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task Delete_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var secret = await _secretRepository.CreateAsync(new Secret
@ -651,7 +675,7 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
[Fact]
public async Task Delete_MissingAccessPolicy_AccessDenied()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
@ -671,7 +695,7 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task Delete_Success(PermissionType permissionType)
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var (project, secretIds) = await CreateSecretsAsync(org.Id, 3);
@ -710,12 +734,16 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task GetSecretsByIds_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task GetSecretsByIds_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var secret = await _secretRepository.CreateAsync(new Secret
@ -737,7 +765,7 @@ public class SecretsControllerTests : IClassFixture<ApiApplicationFactory>, IAsy
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task GetSecretsByIds_Success(PermissionType permissionType)
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var (project, secretIds) = await CreateSecretsAsync(org.Id);

View File

@ -45,12 +45,16 @@ public class SecretsManagerPortingControllerTests : IClassFixture<ApiApplication
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task Import_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task Import_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var projectsList = new List<SMImportRequestModel.InnerProjectImportRequestModel>();
@ -62,12 +66,16 @@ public class SecretsManagerPortingControllerTests : IClassFixture<ApiApplication
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task Export_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task Export_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var response = await _client.GetAsync($"sm/{org.Id}/export");

View File

@ -48,12 +48,16 @@ public class SecretsTrashControllerTests : IClassFixture<ApiApplicationFactory>,
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task ListByOrganization_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task ListByOrganization_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var response = await _client.GetAsync($"/secrets/{org.Id}/trash");
@ -63,7 +67,7 @@ public class SecretsTrashControllerTests : IClassFixture<ApiApplicationFactory>,
[Fact]
public async Task ListByOrganization_NotAdmin_Unauthorized()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
@ -74,7 +78,7 @@ public class SecretsTrashControllerTests : IClassFixture<ApiApplicationFactory>,
[Fact]
public async Task ListByOrganization_Success()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
await _secretRepository.CreateAsync(new Secret
@ -100,12 +104,16 @@ public class SecretsTrashControllerTests : IClassFixture<ApiApplicationFactory>,
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task Empty_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task Empty_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var ids = new List<Guid> { Guid.NewGuid() };
@ -116,7 +124,7 @@ public class SecretsTrashControllerTests : IClassFixture<ApiApplicationFactory>,
[Fact]
public async Task Empty_NotAdmin_Unauthorized()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
@ -128,7 +136,7 @@ public class SecretsTrashControllerTests : IClassFixture<ApiApplicationFactory>,
[Fact]
public async Task Empty_Invalid_NotFound()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var secret = await _secretRepository.CreateAsync(new Secret
@ -146,7 +154,7 @@ public class SecretsTrashControllerTests : IClassFixture<ApiApplicationFactory>,
[Fact]
public async Task Empty_Success()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var secret = await _secretRepository.CreateAsync(new Secret
@ -163,12 +171,16 @@ public class SecretsTrashControllerTests : IClassFixture<ApiApplicationFactory>,
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task Restore_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task Restore_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var ids = new List<Guid> { Guid.NewGuid() };
@ -179,7 +191,7 @@ public class SecretsTrashControllerTests : IClassFixture<ApiApplicationFactory>,
[Fact]
public async Task Restore_NotAdmin_Unauthorized()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
@ -191,7 +203,7 @@ public class SecretsTrashControllerTests : IClassFixture<ApiApplicationFactory>,
[Fact]
public async Task Restore_Invalid_NotFound()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var secret = await _secretRepository.CreateAsync(new Secret
@ -209,7 +221,7 @@ public class SecretsTrashControllerTests : IClassFixture<ApiApplicationFactory>,
[Fact]
public async Task Restore_Success()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var secret = await _secretRepository.CreateAsync(new Secret

View File

@ -61,12 +61,16 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task ListByOrganization_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task ListByOrganization_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var response = await _client.GetAsync($"/organizations/{org.Id}/service-accounts");
@ -76,7 +80,7 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
[Fact]
public async Task ListByOrganization_Admin_Success()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var serviceAccountIds = await SetupGetServiceAccountsByOrganizationAsync(org);
@ -93,7 +97,7 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
[Fact]
public async Task ListByOrganization_User_Success()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
@ -121,12 +125,16 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task GetByServiceAccountId_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task GetByServiceAccountId_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
@ -142,7 +150,7 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
[Fact]
public async Task GetByServiceAccountId_ServiceAccountDoesNotExist_NotFound()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var response = await _client.GetAsync($"/service-accounts/{new Guid()}");
@ -152,7 +160,7 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
[Fact]
public async Task GetByServiceAccountId_UserWithoutPermission_NotFound()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
@ -185,12 +193,16 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task Create_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task Create_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var request = new ServiceAccountCreateRequestModel { Name = _mockEncryptedString };
@ -204,7 +216,7 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task Create_Success(PermissionType permissionType)
{
var (org, adminOrgUser) = await _organizationHelper.Initialize(true, true);
var (org, adminOrgUser) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var orgUserId = adminOrgUser.Id;
@ -248,12 +260,16 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task Update_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task Update_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var initialServiceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
@ -271,7 +287,7 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
[Fact]
public async Task Update_User_NoPermissions()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
@ -290,7 +306,7 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
[Fact]
public async Task Update_NonExistingServiceAccount_NotFound()
{
await _organizationHelper.Initialize(true, true);
await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var request = new ServiceAccountUpdateRequestModel { Name = _mockNewName };
@ -327,12 +343,16 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task Delete_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task Delete_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var initialServiceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
@ -350,7 +370,7 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
[Fact]
public async Task Delete_MissingAccessPolicy_AccessDenied()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
@ -373,7 +393,7 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task Delete_Success(PermissionType permissionType)
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
{
@ -413,12 +433,16 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task GetAccessTokens_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task GetAccessTokens_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
@ -434,7 +458,7 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
[Fact]
public async Task GetAccessTokens_UserNoPermission_NotFound()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
@ -460,7 +484,7 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task GetAccessTokens_Success(PermissionType permissionType)
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
@ -506,12 +530,16 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task CreateAccessToken_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task CreateAccessToken_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
@ -536,7 +564,7 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
[Fact]
public async Task CreateAccessToken_Admin()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
@ -569,7 +597,7 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
[Fact]
public async Task CreateAccessToken_User_WithPermission()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
@ -605,7 +633,7 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
[Fact]
public async Task CreateAccessToken_User_NoPermission()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
var (email, _) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
@ -631,7 +659,7 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
[Fact]
public async Task CreateAccessToken_ExpireAtNull_Admin()
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
@ -661,12 +689,16 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
}
[Theory]
[InlineData(false, false)]
[InlineData(true, false)]
[InlineData(false, true)]
public async Task RevokeAccessToken_SmNotEnabled_NotFound(bool useSecrets, bool accessSecrets)
[InlineData(false, false, false)]
[InlineData(false, false, true)]
[InlineData(false, true, false)]
[InlineData(false, true, true)]
[InlineData(true, false, false)]
[InlineData(true, false, true)]
[InlineData(true, true, false)]
public async Task RevokeAccessToken_SmAccessDenied_NotFound(bool useSecrets, bool accessSecrets, bool organizationEnabled)
{
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets);
var (org, _) = await _organizationHelper.Initialize(useSecrets, accessSecrets, organizationEnabled);
await LoginAsync(_email);
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
@ -696,7 +728,7 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
[InlineData(true)]
public async Task RevokeAccessToken_User_NoPermission(bool hasReadAccess)
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
var (email, orgUser) = await _organizationHelper.CreateNewUser(OrganizationUserType.User, true);
await LoginAsync(email);
@ -740,7 +772,7 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
[InlineData(PermissionType.RunAsUserWithPermission)]
public async Task RevokeAccessToken_Success(PermissionType permissionType)
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
var serviceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount
{
@ -814,7 +846,7 @@ public class ServiceAccountsControllerTests : IClassFixture<ApiApplicationFactor
private async Task<ServiceAccount> SetupServiceAccountWithAccessAsync(PermissionType permissionType)
{
var (org, _) = await _organizationHelper.Initialize(true, true);
var (org, _) = await _organizationHelper.Initialize(true, true, true);
await LoginAsync(_email);
var initialServiceAccount = await _serviceAccountRepository.CreateAsync(new ServiceAccount

View File

@ -25,13 +25,22 @@ public class SecretsManagerOrganizationHelper
_ownerEmail = ownerEmail;
}
public async Task<(Organization organization, OrganizationUser owner)> Initialize(bool useSecrets, bool ownerAccessSecrets)
public async Task<(Organization organization, OrganizationUser owner)> Initialize(bool useSecrets, bool ownerAccessSecrets, bool organizationEnabled)
{
(_organization, _owner) = await OrganizationTestHelpers.SignUpAsync(_factory, ownerEmail: _ownerEmail, billingEmail: _ownerEmail);
if (useSecrets)
if (useSecrets || !organizationEnabled)
{
_organization.UseSecretsManager = true;
if (useSecrets)
{
_organization.UseSecretsManager = true;
}
if (!organizationEnabled)
{
_organization.Enabled = false;
}
await _organizationRepository.ReplaceAsync(_organization);
}

View File

@ -20,11 +20,11 @@ namespace Bit.Api.Test.Controllers;
public class OrganizationSponsorshipsControllerTests
{
public static IEnumerable<object[]> EnterprisePlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPasswordManagerPlan(p).Product == ProductType.Enterprise).Select(p => new object[] { p });
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product == ProductType.Enterprise).Select(p => new object[] { p });
public static IEnumerable<object[]> NonEnterprisePlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPasswordManagerPlan(p).Product != ProductType.Enterprise).Select(p => new object[] { p });
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product != ProductType.Enterprise).Select(p => new object[] { p });
public static IEnumerable<object[]> NonFamiliesPlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPasswordManagerPlan(p).Product != ProductType.Families).Select(p => new object[] { p });
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product != ProductType.Families).Select(p => new object[] { p });
public static IEnumerable<object[]> NonConfirmedOrganizationUsersStatuses =>
Enum.GetValues<OrganizationUserStatusType>()

View File

@ -44,7 +44,7 @@ public class ProjectsControllerTests
[Theory]
[BitAutoData]
public async void ListByOrganization_SmNotEnabled_Throws(SutProvider<ProjectsController> sutProvider, Guid data)
public async void ListByOrganization_SmAccessDenied_Throws(SutProvider<ProjectsController> sutProvider, Guid data)
{
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(data).Returns(false);
@ -205,7 +205,7 @@ public class ProjectsControllerTests
[Theory]
[BitAutoData]
public async void Get_SmNotEnabled_Throws(SutProvider<ProjectsController> sutProvider, Guid data, Guid orgId)
public async void Get_SmAccessDenied_Throws(SutProvider<ProjectsController> sutProvider, Guid data, Guid orgId)
{
SetupAdmin(sutProvider, orgId);
sutProvider.GetDependency<ICurrentContext>().AccessSecretsManager(orgId).Returns(false);

View File

@ -73,7 +73,7 @@ public class SyncControllerTests
user.EquivalentDomains = JsonSerializer.Serialize(userEquivalentDomains);
user.ExcludedGlobalEquivalentDomains = JsonSerializer.Serialize(userExcludedGlobalEquivalentDomains);
// At least 1 org needs to be enabled to fully test
// At least 1 org needs to be enabled to fully test
if (!organizationUserDetails.Any(o => o.Enabled))
{
// We need at least 1 enabled org
@ -165,7 +165,7 @@ public class SyncControllerTests
user.EquivalentDomains = JsonSerializer.Serialize(userEquivalentDomains);
user.ExcludedGlobalEquivalentDomains = JsonSerializer.Serialize(userExcludedGlobalEquivalentDomains);
// All orgs disabled
// All orgs disabled
if (organizationUserDetails.Count > 0)
{
foreach (var orgUserDetails in organizationUserDetails)
@ -218,7 +218,7 @@ public class SyncControllerTests
Assert.IsType<SyncResponseModel>(result);
// Collections should be empty when all standard orgs are disabled.
// Collections should be empty when all standard orgs are disabled.
Assert.Empty(result.Collections);
}
@ -297,7 +297,7 @@ public class SyncControllerTests
Assert.IsType<SyncResponseModel>(result);
// Look up ProviderOrg output and compare to ProviderOrg method inputs to ensure
// product type is set correctly.
// product type is set correctly.
foreach (var profProviderOrg in result.Profile.ProviderOrganizations)
{
var matchedProviderUserOrgDetails =
@ -305,7 +305,7 @@ public class SyncControllerTests
if (matchedProviderUserOrgDetails != null)
{
var providerOrgProductType = StaticStore.GetPasswordManagerPlan(matchedProviderUserOrgDetails.PlanType).Product;
var providerOrgProductType = StaticStore.GetPlan(matchedProviderUserOrgDetails.PlanType).Product;
Assert.Equal(providerOrgProductType, profProviderOrg.PlanProductType);
}
}
@ -337,7 +337,7 @@ public class SyncControllerTests
await sendRepository.ReceivedWithAnyArgs(1)
.GetManyByUserIdAsync(default);
// These two are only called when at least 1 enabled org.
// These two are only called when at least 1 enabled org.
if (hasEnabledOrgs)
{
await collectionRepository.ReceivedWithAnyArgs(1)
@ -347,7 +347,7 @@ public class SyncControllerTests
}
else
{
// all disabled orgs
// all disabled orgs
await collectionRepository.ReceivedWithAnyArgs(0)
.GetManyByUserIdAsync(default);
await collectionCipherRepository.ReceivedWithAnyArgs(0)

View File

@ -66,7 +66,7 @@ internal class PaidOrganization : ICustomization
public PlanType CheckedPlanType { get; set; }
public void Customize(IFixture fixture)
{
var validUpgradePlans = StaticStore.PasswordManagerPlans.Where(p => p.Type != PlanType.Free && p.LegacyYear == null).OrderBy(p => p.UpgradeSortOrder).Select(p => p.Type).ToList();
var validUpgradePlans = StaticStore.Plans.Where(p => p.Type != PlanType.Free && p.LegacyYear == null).OrderBy(p => p.UpgradeSortOrder).Select(p => p.Type).ToList();
var lowestActivePaidPlan = validUpgradePlans.First();
CheckedPlanType = CheckedPlanType.Equals(PlanType.Free) ? lowestActivePaidPlan : CheckedPlanType;
validUpgradePlans.Remove(lowestActivePaidPlan);
@ -94,11 +94,11 @@ internal class FreeOrganizationUpgrade : ICustomization
.With(o => o.PlanType, PlanType.Free));
var plansToIgnore = new List<PlanType> { PlanType.Free, PlanType.Custom };
var selectedPlan = StaticStore.PasswordManagerPlans.Last(p => !plansToIgnore.Contains(p.Type) && !p.Disabled);
var selectedPlan = StaticStore.Plans.Last(p => !plansToIgnore.Contains(p.Type) && !p.Disabled);
fixture.Customize<OrganizationUpgrade>(composer => composer
.With(ou => ou.Plan, selectedPlan.Type)
.With(ou => ou.PremiumAccessAddon, selectedPlan.HasPremiumAccessOption));
.With(ou => ou.PremiumAccessAddon, selectedPlan.PasswordManager.HasPremiumAccessOption));
fixture.Customize<Organization>(composer => composer
.Without(o => o.GatewaySubscriptionId));
}
@ -140,7 +140,7 @@ public class SecretsManagerOrganizationCustomization : ICustomization
.With(o => o.UseSecretsManager, true)
.With(o => o.SecretsManagerBeta, false)
.With(o => o.PlanType, planType)
.With(o => o.Plan, StaticStore.GetPasswordManagerPlan(planType).Name)
.With(o => o.Plan, StaticStore.GetPlan(planType).Name)
.With(o => o.MaxAutoscaleSmSeats, (int?)null)
.With(o => o.MaxAutoscaleSmServiceAccounts, (int?)null)
);

View File

@ -6,16 +6,16 @@ namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesFo
public abstract class FamiliesForEnterpriseTestsBase
{
public static IEnumerable<object[]> EnterprisePlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPasswordManagerPlan(p).Product == ProductType.Enterprise).Select(p => new object[] { p });
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product == ProductType.Enterprise).Select(p => new object[] { p });
public static IEnumerable<object[]> NonEnterprisePlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPasswordManagerPlan(p).Product != ProductType.Enterprise).Select(p => new object[] { p });
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product != ProductType.Enterprise).Select(p => new object[] { p });
public static IEnumerable<object[]> FamiliesPlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPasswordManagerPlan(p).Product == ProductType.Families).Select(p => new object[] { p });
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product == ProductType.Families).Select(p => new object[] { p });
public static IEnumerable<object[]> NonFamiliesPlanTypes =>
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPasswordManagerPlan(p).Product != ProductType.Families).Select(p => new object[] { p });
Enum.GetValues<PlanType>().Where(p => StaticStore.GetPlan(p).Product != ProductType.Families).Select(p => new object[] { p });
public static IEnumerable<object[]> NonConfirmedOrganizationUsersStatuses =>
Enum.GetValues<OrganizationUserStatusType>()

View File

@ -32,7 +32,7 @@ public class AddSecretsManagerSubscriptionCommandTests
{
organization.PlanType = planType;
var plan = StaticStore.SecretManagerPlans.FirstOrDefault(p => p.Type == organization.PlanType);
var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == organization.PlanType);
await sutProvider.Sut.SignUpAsync(organization, additionalSmSeats, additionalServiceAccounts);
@ -49,8 +49,8 @@ public class AddSecretsManagerSubscriptionCommandTests
// TODO: call ReferenceEventService - see AC-1481
sutProvider.GetDependency<IOrganizationService>().Received(1).ReplaceAndUpdateCacheAsync(Arg.Is<Organization>(c =>
c.SmSeats == plan.BaseSeats + additionalSmSeats &&
c.SmServiceAccounts == plan.BaseServiceAccount.GetValueOrDefault() + additionalServiceAccounts &&
c.SmSeats == plan.SecretsManager.BaseSeats + additionalSmSeats &&
c.SmServiceAccounts == plan.SecretsManager.BaseServiceAccount + additionalServiceAccounts &&
c.UseSecretsManager == true));
}

View File

@ -52,7 +52,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
await sutProvider.Sut.UpdateSubscriptionAsync(update);
var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == organization.PlanType);
var plan = StaticStore.GetPlan(organization.PlanType);
await sutProvider.GetDependency<IPaymentService>().Received(1)
.AdjustSeatsAsync(organization, plan, update.SmSeatsExcludingBase);
await sutProvider.GetDependency<IPaymentService>().Received(1)
@ -96,7 +96,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
await sutProvider.Sut.UpdateSubscriptionAsync(update);
var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == organization.PlanType);
var plan = StaticStore.GetPlan(organization.PlanType);
await sutProvider.GetDependency<IPaymentService>().Received(1)
.AdjustSeatsAsync(organization, plan, update.SmSeatsExcludingBase);
await sutProvider.GetDependency<IPaymentService>().Received(1)
@ -213,11 +213,11 @@ public class UpdateSecretsManagerSubscriptionCommandTests
public async Task AdjustServiceAccountsAsync_WithEnterpriseOrTeamsPlans_Success(PlanType planType, Guid organizationId,
SutProvider<UpdateSecretsManagerSubscriptionCommand> sutProvider)
{
var plan = StaticStore.SecretManagerPlans.FirstOrDefault(p => p.Type == planType);
var plan = StaticStore.GetPlan(planType);
var organizationSeats = plan.BaseSeats + 10;
var organizationSeats = plan.SecretsManager.BaseSeats + 10;
var organizationMaxAutoscaleSeats = 20;
var organizationServiceAccounts = plan.BaseServiceAccount.GetValueOrDefault() + 10;
var organizationServiceAccounts = plan.SecretsManager.BaseServiceAccount + 10;
var organizationMaxAutoscaleServiceAccounts = 300;
var organization = new Organization
@ -235,7 +235,7 @@ public class UpdateSecretsManagerSubscriptionCommandTests
var smServiceAccountsAdjustment = 10;
var expectedSmServiceAccounts = organizationServiceAccounts + smServiceAccountsAdjustment;
var expectedSmServiceAccountsExcludingBase = expectedSmServiceAccounts - plan.BaseServiceAccount.GetValueOrDefault();
var expectedSmServiceAccountsExcludingBase = expectedSmServiceAccounts - plan.SecretsManager.BaseServiceAccount;
var update = new SecretsManagerSubscriptionUpdate(organization, false).AdjustServiceAccounts(10);

View File

@ -94,6 +94,7 @@ public class UpgradeOrganizationPlanCommandTests
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
upgrade.AdditionalSmSeats = 10;
upgrade.AdditionalSeats = 10;
upgrade.Plan = PlanType.TeamsAnnually;
await sutProvider.Sut.UpgradePlanAsync(organization.Id, upgrade);
await sutProvider.GetDependency<IOrganizationService>().Received(1).ReplaceAndUpdateCacheAsync(organization);
}
@ -108,8 +109,7 @@ public class UpgradeOrganizationPlanCommandTests
{
upgrade.Plan = planType;
var passwordManagerPlan = StaticStore.GetPasswordManagerPlan(upgrade.Plan);
var secretsManagerPlan = StaticStore.GetSecretsManagerPlan(upgrade.Plan);
var plan = StaticStore.GetPlan(upgrade.Plan);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
@ -121,9 +121,9 @@ public class UpgradeOrganizationPlanCommandTests
await sutProvider.GetDependency<IOrganizationService>().Received(1).ReplaceAndUpdateCacheAsync(
Arg.Is<Organization>(o =>
o.Seats == passwordManagerPlan.BaseSeats + upgrade.AdditionalSeats
&& o.SmSeats == secretsManagerPlan.BaseSeats + upgrade.AdditionalSmSeats
&& o.SmServiceAccounts == secretsManagerPlan.BaseServiceAccount + upgrade.AdditionalServiceAccounts));
o.Seats == plan.PasswordManager.BaseSeats + upgrade.AdditionalSeats
&& o.SmSeats == plan.SecretsManager.BaseSeats + upgrade.AdditionalSmSeats
&& o.SmServiceAccounts == plan.SecretsManager.BaseServiceAccount + upgrade.AdditionalServiceAccounts));
Assert.True(result.Item1);
Assert.NotNull(result.Item2);

View File

@ -155,20 +155,20 @@ public class OrganizationServiceTests
{
signup.Plan = planType;
var passwordManagerPlan = StaticStore.GetPasswordManagerPlan(signup.Plan);
var plan = StaticStore.GetPlan(signup.Plan);
signup.AdditionalSeats = 0;
signup.PaymentMethodType = PaymentMethodType.Card;
signup.PremiumAccessAddon = false;
signup.UseSecretsManager = false;
var purchaseOrganizationPlan = StaticStore.Plans.Where(x => x.Type == signup.Plan).ToList();
var purchaseOrganizationPlan = StaticStore.GetPlan(signup.Plan);
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.Seats == plan.PasswordManager.BaseSeats + signup.AdditionalSeats
&& o.SmSeats == null
&& o.SmServiceAccounts == null));
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).CreateAsync(
@ -177,8 +177,8 @@ public class OrganizationServiceTests
await sutProvider.GetDependency<IReferenceEventService>().Received(1)
.RaiseEventAsync(Arg.Is<ReferenceEvent>(referenceEvent =>
referenceEvent.Type == ReferenceEventType.Signup &&
referenceEvent.PlanName == passwordManagerPlan.Name &&
referenceEvent.PlanType == passwordManagerPlan.Type &&
referenceEvent.PlanName == plan.Name &&
referenceEvent.PlanType == plan.Type &&
referenceEvent.Seats == result.Item1.Seats &&
referenceEvent.Storage == result.Item1.MaxStorageGb));
// TODO: add reference events for SmSeats and Service Accounts - see AC-1481
@ -192,7 +192,7 @@ public class OrganizationServiceTests
Arg.Any<Organization>(),
signup.PaymentMethodType.Value,
signup.PaymentToken,
Arg.Is<List<Plan>>(plan => plan.Single() == passwordManagerPlan),
plan,
signup.AdditionalStorageGb,
signup.AdditionalSeats,
signup.PremiumAccessAddon,
@ -212,8 +212,7 @@ public class OrganizationServiceTests
{
signup.Plan = planType;
var passwordManagerPlan = StaticStore.GetPasswordManagerPlan(signup.Plan);
var secretsManagerPlan = StaticStore.GetSecretsManagerPlan(signup.Plan);
var plan = StaticStore.GetPlan(signup.Plan);
signup.UseSecretsManager = true;
signup.AdditionalSeats = 15;
@ -222,23 +221,21 @@ public class OrganizationServiceTests
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));
o.Seats == plan.PasswordManager.BaseSeats + signup.AdditionalSeats
&& o.SmSeats == plan.SecretsManager.BaseSeats + signup.AdditionalSmSeats
&& o.SmServiceAccounts == plan.SecretsManager.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.PlanName == plan.Name &&
referenceEvent.PlanType == plan.Type &&
referenceEvent.Seats == result.Item1.Seats &&
referenceEvent.Storage == result.Item1.MaxStorageGb));
// TODO: add reference events for SmSeats and Service Accounts - see AC-1481
@ -252,7 +249,7 @@ public class OrganizationServiceTests
Arg.Any<Organization>(),
signup.PaymentMethodType.Value,
signup.PaymentToken,
Arg.Is<List<Plan>>(plan => plan.All(p => purchaseOrganizationPlan.Contains(p))),
Arg.Is<Plan>(plan),
signup.AdditionalStorageGb,
signup.AdditionalSeats,
signup.PremiumAccessAddon,
@ -1721,7 +1718,7 @@ public class OrganizationServiceTests
public void ValidateSecretsManagerPlan_ThrowsException_WhenInvalidPlanSelected(
PlanType planType, SutProvider<OrganizationService> sutProvider)
{
var plan = StaticStore.Plans.FirstOrDefault(x => x.Type == planType);
var plan = StaticStore.GetPlan(planType);
var signup = new OrganizationUpgrade
{
@ -1742,7 +1739,7 @@ public class OrganizationServiceTests
[BitAutoData(PlanType.EnterpriseMonthly)]
public void ValidateSecretsManagerPlan_ThrowsException_WhenNoSecretsManagerSeats(PlanType planType, SutProvider<OrganizationService> sutProvider)
{
var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == planType);
var plan = StaticStore.GetPlan(planType);
var signup = new OrganizationUpgrade
{
UseSecretsManager = true,
@ -1759,7 +1756,7 @@ public class OrganizationServiceTests
[BitAutoData(PlanType.Free)]
public void ValidateSecretsManagerPlan_ThrowsException_WhenSubtractingSeats(PlanType planType, SutProvider<OrganizationService> sutProvider)
{
var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == planType);
var plan = StaticStore.GetPlan(planType);
var signup = new OrganizationUpgrade
{
UseSecretsManager = true,
@ -1776,7 +1773,7 @@ public class OrganizationServiceTests
PlanType planType,
SutProvider<OrganizationService> sutProvider)
{
var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == planType);
var plan = StaticStore.GetPlan(planType);
var signup = new OrganizationUpgrade
{
UseSecretsManager = true,
@ -1795,7 +1792,7 @@ public class OrganizationServiceTests
[BitAutoData(PlanType.EnterpriseMonthly)]
public void ValidateSecretsManagerPlan_ThrowsException_WhenMoreSeatsThanPasswordManagerSeats(PlanType planType, SutProvider<OrganizationService> sutProvider)
{
var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == planType);
var plan = StaticStore.GetPlan(planType);
var signup = new OrganizationUpgrade
{
UseSecretsManager = true,
@ -1816,7 +1813,7 @@ public class OrganizationServiceTests
PlanType planType,
SutProvider<OrganizationService> sutProvider)
{
var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == planType);
var plan = StaticStore.GetPlan(planType);
var signup = new OrganizationUpgrade
{
UseSecretsManager = true,
@ -1834,7 +1831,7 @@ public class OrganizationServiceTests
PlanType planType,
SutProvider<OrganizationService> sutProvider)
{
var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == planType);
var plan = StaticStore.GetPlan(planType);
var signup = new OrganizationUpgrade
{
UseSecretsManager = true,
@ -1855,7 +1852,7 @@ public class OrganizationServiceTests
PlanType planType,
SutProvider<OrganizationService> sutProvider)
{
var plan = StaticStore.SecretManagerPlans.FirstOrDefault(x => x.Type == planType);
var plan = StaticStore.GetPlan(planType);
var signup = new OrganizationUpgrade
{
UseSecretsManager = true,

View File

@ -40,7 +40,7 @@ public class StripePaymentServiceTests
[Theory, BitAutoData]
public async void PurchaseOrganizationAsync_Stripe_ProviderOrg_Coupon_Add(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo, bool provider = true)
{
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer
@ -56,7 +56,7 @@ public class StripePaymentServiceTests
.BaseServiceUri.CloudRegion
.Returns("US");
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plans, 0, 0, false, taxInfo, provider);
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 0, 0, false, taxInfo, provider);
Assert.Null(result);
Assert.Equal(GatewayType.Stripe, organization.Gateway);
@ -95,8 +95,8 @@ public class StripePaymentServiceTests
public async void PurchaseOrganizationAsync_SM_Stripe_ProviderOrg_Coupon_Add(SutProvider<StripePaymentService> sutProvider, Organization organization,
string paymentToken, TaxInfo taxInfo, bool provider = true)
{
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
organization.UseSecretsManager = true;
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer
{
@ -112,7 +112,7 @@ public class StripePaymentServiceTests
.BaseServiceUri.CloudRegion
.Returns("US");
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plans, 1, 1,
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 1, 1,
false, taxInfo, provider, 1, 1);
Assert.Null(result);
@ -151,8 +151,8 @@ public class StripePaymentServiceTests
[Theory, BitAutoData]
public async void PurchaseOrganizationAsync_Stripe(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
{
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
organization.UseSecretsManager = true;
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer
{
@ -167,7 +167,7 @@ public class StripePaymentServiceTests
.BaseServiceUri.CloudRegion
.Returns("US");
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plans, 0, 0
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 0, 0
, false, taxInfo, false, 8, 10);
Assert.Null(result);
@ -207,7 +207,7 @@ public class StripePaymentServiceTests
[Theory, BitAutoData]
public async void PurchaseOrganizationAsync_Stripe_PM(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
{
var plans = StaticStore.PasswordManagerPlans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
paymentToken = "pm_" + paymentToken;
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
@ -224,7 +224,7 @@ public class StripePaymentServiceTests
.BaseServiceUri.CloudRegion
.Returns("US");
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plans, 0, 0, false, taxInfo);
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 0, 0, false, taxInfo);
Assert.Null(result);
Assert.Equal(GatewayType.Stripe, organization.Gateway);
@ -264,7 +264,7 @@ public class StripePaymentServiceTests
[Theory, BitAutoData]
public async void PurchaseOrganizationAsync_Stripe_TaxRate(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
{
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer
@ -280,7 +280,7 @@ public class StripePaymentServiceTests
t.Country == taxInfo.BillingAddressCountry && t.PostalCode == taxInfo.BillingAddressPostalCode))
.Returns(new List<TaxRate> { new() { Id = "T-1" } });
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plans, 0, 0, false, taxInfo);
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 0, 0, false, taxInfo);
Assert.Null(result);
@ -293,7 +293,7 @@ public class StripePaymentServiceTests
[Theory, BitAutoData]
public async void PurchaseOrganizationAsync_Stripe_TaxRate_SM(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
{
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer
@ -309,7 +309,7 @@ public class StripePaymentServiceTests
t.Country == taxInfo.BillingAddressCountry && t.PostalCode == taxInfo.BillingAddressPostalCode))
.Returns(new List<TaxRate> { new() { Id = "T-1" } });
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plans, 2, 2,
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 2, 2,
false, taxInfo, false, 2, 2);
Assert.Null(result);
@ -323,7 +323,7 @@ public class StripePaymentServiceTests
[Theory, BitAutoData]
public async void PurchaseOrganizationAsync_Stripe_Declined(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
{
var plan = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
paymentToken = "pm_" + paymentToken;
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
@ -356,7 +356,7 @@ public class StripePaymentServiceTests
[Theory, BitAutoData]
public async void PurchaseOrganizationAsync_SM_Stripe_Declined(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
{
var plan = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
paymentToken = "pm_" + paymentToken;
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
@ -390,7 +390,7 @@ public class StripePaymentServiceTests
[Theory, BitAutoData]
public async void PurchaseOrganizationAsync_Stripe_RequiresAction(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
{
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer
@ -412,7 +412,7 @@ public class StripePaymentServiceTests
},
});
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plans, 0, 0, false, taxInfo);
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan, 0, 0, false, taxInfo);
Assert.Equal("clientSecret", result);
Assert.False(organization.Enabled);
@ -421,7 +421,7 @@ public class StripePaymentServiceTests
[Theory, BitAutoData]
public async void PurchaseOrganizationAsync_SM_Stripe_RequiresAction(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
{
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer
@ -443,7 +443,7 @@ public class StripePaymentServiceTests
},
});
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plans,
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.Card, paymentToken, plan,
10, 10, false, taxInfo, false, 10, 10);
Assert.Equal("clientSecret", result);
@ -453,7 +453,7 @@ public class StripePaymentServiceTests
[Theory, BitAutoData]
public async void PurchaseOrganizationAsync_Paypal(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
{
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer
@ -480,7 +480,7 @@ public class StripePaymentServiceTests
var braintreeGateway = sutProvider.GetDependency<IBraintreeGateway>();
braintreeGateway.Customer.CreateAsync(default).ReturnsForAnyArgs(customerResult);
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plans, 0, 0, false, taxInfo);
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plan, 0, 0, false, taxInfo);
Assert.Null(result);
Assert.Equal(GatewayType.Stripe, organization.Gateway);
@ -517,10 +517,8 @@ public class StripePaymentServiceTests
[Theory, BitAutoData]
public async void PurchaseOrganizationAsync_SM_Paypal(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
{
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var passwordManagerPlan = plans.Single(p => p.BitwardenProduct == BitwardenProductType.PasswordManager);
var secretsManagerPlan = plans.Single(p => p.BitwardenProduct == BitwardenProductType.SecretsManager);
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
organization.UseSecretsManager = true;
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
stripeAdapter.CustomerCreateAsync(default).ReturnsForAnyArgs(new Stripe.Customer
{
@ -550,7 +548,7 @@ public class StripePaymentServiceTests
var additionalSeats = 10;
var additionalSmSeats = 5;
var additionalServiceAccounts = 20;
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plans,
var result = await sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plan,
additionalStorage, additionalSeats, false, taxInfo, false, additionalSmSeats, additionalServiceAccounts);
Assert.Null(result);
@ -582,17 +580,17 @@ public class StripePaymentServiceTests
s.Expand[0] == "latest_invoice.payment_intent" &&
s.Metadata[organization.GatewayIdField()] == organization.Id.ToString() &&
s.Items.Count == 4 &&
s.Items.Count(i => i.Plan == passwordManagerPlan.StripeSeatPlanId && i.Quantity == additionalSeats) == 1 &&
s.Items.Count(i => i.Plan == passwordManagerPlan.StripeStoragePlanId && i.Quantity == additionalStorage) == 1 &&
s.Items.Count(i => i.Plan == secretsManagerPlan.StripeSeatPlanId && i.Quantity == additionalSmSeats) == 1 &&
s.Items.Count(i => i.Plan == secretsManagerPlan.StripeServiceAccountPlanId && i.Quantity == additionalServiceAccounts) == 1
s.Items.Count(i => i.Plan == plan.PasswordManager.StripeSeatPlanId && i.Quantity == additionalSeats) == 1 &&
s.Items.Count(i => i.Plan == plan.PasswordManager.StripeStoragePlanId && i.Quantity == additionalStorage) == 1 &&
s.Items.Count(i => i.Plan == plan.SecretsManager.StripeSeatPlanId && i.Quantity == additionalSmSeats) == 1 &&
s.Items.Count(i => i.Plan == plan.SecretsManager.StripeServiceAccountPlanId && i.Quantity == additionalServiceAccounts) == 1
));
}
[Theory, BitAutoData]
public async void PurchaseOrganizationAsync_Paypal_FailedCreate(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
{
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
var customerResult = Substitute.For<Result<Customer>>();
customerResult.IsSuccess().Returns(false);
@ -601,7 +599,7 @@ public class StripePaymentServiceTests
braintreeGateway.Customer.CreateAsync(default).ReturnsForAnyArgs(customerResult);
var exception = await Assert.ThrowsAsync<GatewayException>(
() => sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plans, 0, 0, false, taxInfo));
() => sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plan, 0, 0, false, taxInfo));
Assert.Equal("Failed to create PayPal customer record.", exception.Message);
}
@ -609,7 +607,7 @@ public class StripePaymentServiceTests
[Theory, BitAutoData]
public async void PurchaseOrganizationAsync_SM_Paypal_FailedCreate(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
{
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
var customerResult = Substitute.For<Result<Customer>>();
customerResult.IsSuccess().Returns(false);
@ -618,7 +616,7 @@ public class StripePaymentServiceTests
braintreeGateway.Customer.CreateAsync(default).ReturnsForAnyArgs(customerResult);
var exception = await Assert.ThrowsAsync<GatewayException>(
() => sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plans,
() => sutProvider.Sut.PurchaseOrganizationAsync(organization, PaymentMethodType.PayPal, paymentToken, plan,
1, 1, false, taxInfo, false, 8, 8));
Assert.Equal("Failed to create PayPal customer record.", exception.Message);
@ -627,7 +625,7 @@ public class StripePaymentServiceTests
[Theory, BitAutoData]
public async void PurchaseOrganizationAsync_PayPal_Declined(SutProvider<StripePaymentService> sutProvider, Organization organization, string paymentToken, TaxInfo taxInfo)
{
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var plans = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
paymentToken = "pm_" + paymentToken;
var stripeAdapter = sutProvider.GetDependency<IStripeAdapter>();
@ -689,7 +687,7 @@ public class StripePaymentServiceTests
});
stripeAdapter.SubscriptionCreateAsync(default).ReturnsForAnyArgs(new Stripe.Subscription { });
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
var upgrade = new OrganizationUpgrade()
{
@ -700,7 +698,7 @@ public class StripePaymentServiceTests
AdditionalSmSeats = 0,
AdditionalServiceAccounts = 0
};
var result = await sutProvider.Sut.UpgradeFreeOrganizationAsync(organization, plans, upgrade);
var result = await sutProvider.Sut.UpgradeFreeOrganizationAsync(organization, plan, upgrade);
Assert.Null(result);
}
@ -736,8 +734,8 @@ public class StripePaymentServiceTests
AdditionalServiceAccounts = 50
};
var plans = StaticStore.Plans.Where(p => p.Type == PlanType.EnterpriseAnnually).ToList();
var result = await sutProvider.Sut.UpgradeFreeOrganizationAsync(organization, plans, upgrade);
var plan = StaticStore.GetPlan(PlanType.EnterpriseAnnually);
var result = await sutProvider.Sut.UpgradeFreeOrganizationAsync(organization, plan, upgrade);
Assert.Null(result);
}

View File

@ -1,5 +1,4 @@
using Bit.Core.Enums;
using Bit.Core.Models.StaticStore;
using Bit.Core.Utilities;
using Xunit;
@ -14,57 +13,18 @@ public class StaticStoreTests
var plans = StaticStore.Plans;
Assert.NotNull(plans);
Assert.NotEmpty(plans);
Assert.Equal(17, plans.Count());
Assert.Equal(12, plans.Count());
}
[Theory]
[InlineData(PlanType.EnterpriseAnnually)]
public void StaticStore_GetPasswordManagerPlanByPlanType_Success(PlanType planType)
[InlineData(PlanType.EnterpriseMonthly)]
[InlineData(PlanType.TeamsMonthly)]
[InlineData(PlanType.TeamsAnnually)]
public void StaticStore_GetPlan_Success(PlanType planType)
{
var plan = StaticStore.GetPasswordManagerPlan(planType);
var plan = StaticStore.GetPlan(planType);
Assert.NotNull(plan);
Assert.Equal(planType, plan.Type);
}
[Theory]
[InlineData(PlanType.EnterpriseAnnually)]
public void StaticStore_GetSecretsManagerPlanByPlanType_Success(PlanType planType)
{
var plan = StaticStore.GetSecretsManagerPlan(planType);
Assert.NotNull(plan);
Assert.Equal(planType, plan.Type);
}
[Theory]
[InlineData(PlanType.EnterpriseAnnually)]
public void StaticStore_GetPasswordManagerPlan_ReturnsPasswordManagerPlans(PlanType planType)
{
var plan = StaticStore.GetPasswordManagerPlan(planType);
Assert.NotNull(plan);
Assert.Equal(BitwardenProductType.PasswordManager, plan.BitwardenProduct);
}
[Theory]
[InlineData(PlanType.EnterpriseAnnually)]
public void StaticStore_GetSecretsManagerPlan_ReturnsSecretManagerPlans(PlanType planType)
{
var plan = StaticStore.GetSecretsManagerPlan(planType);
Assert.NotNull(plan);
Assert.Equal(BitwardenProductType.SecretsManager, plan.BitwardenProduct);
}
[Theory]
[InlineData(PlanType.EnterpriseAnnually, BitwardenProductType.PasswordManager)]
public void StaticStore_AddDuplicatePlans_SingleOrDefaultThrowsException(PlanType planType, BitwardenProductType bitwardenProductType)
{
var plansStore = new List<Plan>
{
new Plan { Type = PlanType.EnterpriseAnnually, BitwardenProduct = BitwardenProductType.PasswordManager },
new Plan { Type = PlanType.EnterpriseAnnually, BitwardenProduct = BitwardenProductType.PasswordManager }
};
Assert.Throws<InvalidOperationException>(() => plansStore.SingleOrDefault(p => p.Type == planType && p.BitwardenProduct == bitwardenProductType));
}
}

View File

@ -23,6 +23,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Api.IntegrationTest", "Api.
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure.IntegrationTest", "Infrastructure.IntegrationTest\Infrastructure.IntegrationTest.csproj", "{5827E256-D1C5-4BBE-BB74-ED28A83578FA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Identity.Test", "Identity.Test\Identity.Test.csproj", "{CE6A0F24-4193-4CCC-9BE1-6D1D85782CA9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -156,5 +158,17 @@ Global
{5827E256-D1C5-4BBE-BB74-ED28A83578FA}.Release|x64.Build.0 = Release|Any CPU
{5827E256-D1C5-4BBE-BB74-ED28A83578FA}.Release|x86.ActiveCfg = Release|Any CPU
{5827E256-D1C5-4BBE-BB74-ED28A83578FA}.Release|x86.Build.0 = Release|Any CPU
{CE6A0F24-4193-4CCC-9BE1-6D1D85782CA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CE6A0F24-4193-4CCC-9BE1-6D1D85782CA9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CE6A0F24-4193-4CCC-9BE1-6D1D85782CA9}.Debug|x64.ActiveCfg = Debug|Any CPU
{CE6A0F24-4193-4CCC-9BE1-6D1D85782CA9}.Debug|x64.Build.0 = Debug|Any CPU
{CE6A0F24-4193-4CCC-9BE1-6D1D85782CA9}.Debug|x86.ActiveCfg = Debug|Any CPU
{CE6A0F24-4193-4CCC-9BE1-6D1D85782CA9}.Debug|x86.Build.0 = Debug|Any CPU
{CE6A0F24-4193-4CCC-9BE1-6D1D85782CA9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CE6A0F24-4193-4CCC-9BE1-6D1D85782CA9}.Release|Any CPU.Build.0 = Release|Any CPU
{CE6A0F24-4193-4CCC-9BE1-6D1D85782CA9}.Release|x64.ActiveCfg = Release|Any CPU
{CE6A0F24-4193-4CCC-9BE1-6D1D85782CA9}.Release|x64.Build.0 = Release|Any CPU
{CE6A0F24-4193-4CCC-9BE1-6D1D85782CA9}.Release|x86.ActiveCfg = Release|Any CPU
{CE6A0F24-4193-4CCC-9BE1-6D1D85782CA9}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal