mirror of
https://github.com/bitwarden/server.git
synced 2025-04-05 21:18:13 -05:00
[PM-12488] Migrating Cloud Org Sign Up to Command (#5078)
This commit is contained in:
parent
6a9b7ece2b
commit
2333a934a9
@ -13,6 +13,7 @@ using Bit.Core.AdminConsole.Enums;
|
|||||||
using Bit.Core.AdminConsole.Models.Business.Tokenables;
|
using Bit.Core.AdminConsole.Models.Business.Tokenables;
|
||||||
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.Auth.Enums;
|
using Bit.Core.Auth.Enums;
|
||||||
@ -52,11 +53,11 @@ public class OrganizationsController : Controller
|
|||||||
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
|
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
|
||||||
private readonly IFeatureService _featureService;
|
private readonly IFeatureService _featureService;
|
||||||
private readonly GlobalSettings _globalSettings;
|
private readonly GlobalSettings _globalSettings;
|
||||||
private readonly IPushNotificationService _pushNotificationService;
|
|
||||||
private readonly IProviderRepository _providerRepository;
|
private readonly IProviderRepository _providerRepository;
|
||||||
private readonly IProviderBillingService _providerBillingService;
|
private readonly IProviderBillingService _providerBillingService;
|
||||||
private readonly IDataProtectorTokenFactory<OrgDeleteTokenable> _orgDeleteTokenDataFactory;
|
private readonly IDataProtectorTokenFactory<OrgDeleteTokenable> _orgDeleteTokenDataFactory;
|
||||||
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
|
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
|
||||||
|
private readonly ICloudOrganizationSignUpCommand _cloudOrganizationSignUpCommand;
|
||||||
|
|
||||||
public OrganizationsController(
|
public OrganizationsController(
|
||||||
IOrganizationRepository organizationRepository,
|
IOrganizationRepository organizationRepository,
|
||||||
@ -73,11 +74,11 @@ public class OrganizationsController : Controller
|
|||||||
IOrganizationApiKeyRepository organizationApiKeyRepository,
|
IOrganizationApiKeyRepository organizationApiKeyRepository,
|
||||||
IFeatureService featureService,
|
IFeatureService featureService,
|
||||||
GlobalSettings globalSettings,
|
GlobalSettings globalSettings,
|
||||||
IPushNotificationService pushNotificationService,
|
|
||||||
IProviderRepository providerRepository,
|
IProviderRepository providerRepository,
|
||||||
IProviderBillingService providerBillingService,
|
IProviderBillingService providerBillingService,
|
||||||
IDataProtectorTokenFactory<OrgDeleteTokenable> orgDeleteTokenDataFactory,
|
IDataProtectorTokenFactory<OrgDeleteTokenable> orgDeleteTokenDataFactory,
|
||||||
IRemoveOrganizationUserCommand removeOrganizationUserCommand)
|
IRemoveOrganizationUserCommand removeOrganizationUserCommand,
|
||||||
|
ICloudOrganizationSignUpCommand cloudOrganizationSignUpCommand)
|
||||||
{
|
{
|
||||||
_organizationRepository = organizationRepository;
|
_organizationRepository = organizationRepository;
|
||||||
_organizationUserRepository = organizationUserRepository;
|
_organizationUserRepository = organizationUserRepository;
|
||||||
@ -93,11 +94,11 @@ public class OrganizationsController : Controller
|
|||||||
_organizationApiKeyRepository = organizationApiKeyRepository;
|
_organizationApiKeyRepository = organizationApiKeyRepository;
|
||||||
_featureService = featureService;
|
_featureService = featureService;
|
||||||
_globalSettings = globalSettings;
|
_globalSettings = globalSettings;
|
||||||
_pushNotificationService = pushNotificationService;
|
|
||||||
_providerRepository = providerRepository;
|
_providerRepository = providerRepository;
|
||||||
_providerBillingService = providerBillingService;
|
_providerBillingService = providerBillingService;
|
||||||
_orgDeleteTokenDataFactory = orgDeleteTokenDataFactory;
|
_orgDeleteTokenDataFactory = orgDeleteTokenDataFactory;
|
||||||
_removeOrganizationUserCommand = removeOrganizationUserCommand;
|
_removeOrganizationUserCommand = removeOrganizationUserCommand;
|
||||||
|
_cloudOrganizationSignUpCommand = cloudOrganizationSignUpCommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{id}")]
|
[HttpGet("{id}")]
|
||||||
@ -175,8 +176,8 @@ public class OrganizationsController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
var organizationSignup = model.ToOrganizationSignup(user);
|
var organizationSignup = model.ToOrganizationSignup(user);
|
||||||
var result = await _organizationService.SignUpAsync(organizationSignup);
|
var result = await _cloudOrganizationSignUpCommand.SignUpOrganizationAsync(organizationSignup);
|
||||||
return new OrganizationResponseModel(result.Item1);
|
return new OrganizationResponseModel(result.Organization);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("create-without-payment")]
|
[HttpPost("create-without-payment")]
|
||||||
@ -190,8 +191,8 @@ public class OrganizationsController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
var organizationSignup = model.ToOrganizationSignup(user);
|
var organizationSignup = model.ToOrganizationSignup(user);
|
||||||
var result = await _organizationService.SignUpAsync(organizationSignup);
|
var result = await _cloudOrganizationSignUpCommand.SignUpOrganizationAsync(organizationSignup);
|
||||||
return new OrganizationResponseModel(result.Item1);
|
return new OrganizationResponseModel(result.Organization);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPut("{id}")]
|
[HttpPut("{id}")]
|
||||||
|
@ -0,0 +1,368 @@
|
|||||||
|
using Bit.Core.AdminConsole.Entities;
|
||||||
|
using Bit.Core.AdminConsole.Enums;
|
||||||
|
using Bit.Core.AdminConsole.Services;
|
||||||
|
using Bit.Core.Billing.Enums;
|
||||||
|
using Bit.Core.Billing.Models.Sales;
|
||||||
|
using Bit.Core.Billing.Services;
|
||||||
|
using Bit.Core.Context;
|
||||||
|
using Bit.Core.Entities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Exceptions;
|
||||||
|
using Bit.Core.Models.Business;
|
||||||
|
using Bit.Core.Models.Data;
|
||||||
|
using Bit.Core.Models.StaticStore;
|
||||||
|
using Bit.Core.Repositories;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
using Bit.Core.Tools.Enums;
|
||||||
|
using Bit.Core.Tools.Models.Business;
|
||||||
|
using Bit.Core.Tools.Services;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
|
namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||||
|
|
||||||
|
public record SignUpOrganizationResponse(
|
||||||
|
Organization Organization,
|
||||||
|
OrganizationUser OrganizationUser,
|
||||||
|
Collection DefaultCollection);
|
||||||
|
|
||||||
|
public interface ICloudOrganizationSignUpCommand
|
||||||
|
{
|
||||||
|
Task<SignUpOrganizationResponse> SignUpOrganizationAsync(OrganizationSignup signup);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CloudOrganizationSignUpCommand(
|
||||||
|
IOrganizationUserRepository organizationUserRepository,
|
||||||
|
IFeatureService featureService,
|
||||||
|
IOrganizationBillingService organizationBillingService,
|
||||||
|
IPaymentService paymentService,
|
||||||
|
IPolicyService policyService,
|
||||||
|
IReferenceEventService referenceEventService,
|
||||||
|
ICurrentContext currentContext,
|
||||||
|
IOrganizationRepository organizationRepository,
|
||||||
|
IOrganizationApiKeyRepository organizationApiKeyRepository,
|
||||||
|
IApplicationCacheService applicationCacheService,
|
||||||
|
IPushRegistrationService pushRegistrationService,
|
||||||
|
IPushNotificationService pushNotificationService,
|
||||||
|
ICollectionRepository collectionRepository,
|
||||||
|
IDeviceRepository deviceRepository) : ICloudOrganizationSignUpCommand
|
||||||
|
{
|
||||||
|
public async Task<SignUpOrganizationResponse> SignUpOrganizationAsync(OrganizationSignup signup)
|
||||||
|
{
|
||||||
|
var plan = StaticStore.GetPlan(signup.Plan);
|
||||||
|
|
||||||
|
ValidatePasswordManagerPlan(plan, signup);
|
||||||
|
|
||||||
|
if (signup.UseSecretsManager)
|
||||||
|
{
|
||||||
|
if (signup.IsFromProvider)
|
||||||
|
{
|
||||||
|
throw new BadRequestException(
|
||||||
|
"Organizations with a Managed Service Provider do not support Secrets Manager.");
|
||||||
|
}
|
||||||
|
ValidateSecretsManagerPlan(plan, signup);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!signup.IsFromProvider)
|
||||||
|
{
|
||||||
|
await ValidateSignUpPoliciesAsync(signup.Owner.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
var organization = new Organization
|
||||||
|
{
|
||||||
|
// Pre-generate the org id so that we can save it with the Stripe subscription
|
||||||
|
Id = CoreHelpers.GenerateComb(),
|
||||||
|
Name = signup.Name,
|
||||||
|
BillingEmail = signup.BillingEmail,
|
||||||
|
BusinessName = signup.BusinessName,
|
||||||
|
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,
|
||||||
|
LicenseKey = CoreHelpers.SecureRandomString(20),
|
||||||
|
PublicKey = signup.PublicKey,
|
||||||
|
PrivateKey = signup.PrivateKey,
|
||||||
|
CreationDate = DateTime.UtcNow,
|
||||||
|
RevisionDate = DateTime.UtcNow,
|
||||||
|
Status = OrganizationStatusType.Created,
|
||||||
|
UsePasswordManager = true,
|
||||||
|
UseSecretsManager = signup.UseSecretsManager
|
||||||
|
};
|
||||||
|
|
||||||
|
if (signup.UseSecretsManager)
|
||||||
|
{
|
||||||
|
organization.SmSeats = plan.SecretsManager.BaseSeats + signup.AdditionalSmSeats.GetValueOrDefault();
|
||||||
|
organization.SmServiceAccounts = plan.SecretsManager.BaseServiceAccount +
|
||||||
|
signup.AdditionalServiceAccounts.GetValueOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plan.Type == PlanType.Free && !signup.IsFromProvider)
|
||||||
|
{
|
||||||
|
var adminCount =
|
||||||
|
await organizationUserRepository.GetCountByFreeOrganizationAdminUserAsync(signup.Owner.Id);
|
||||||
|
if (adminCount > 0)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("You can only be an admin of one free organization.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (plan.Type != PlanType.Free)
|
||||||
|
{
|
||||||
|
if (featureService.IsEnabled(FeatureFlagKeys.AC2476_DeprecateStripeSourcesAPI))
|
||||||
|
{
|
||||||
|
var sale = OrganizationSale.From(organization, signup);
|
||||||
|
await organizationBillingService.Finalize(sale);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (signup.PaymentMethodType != null)
|
||||||
|
{
|
||||||
|
await paymentService.PurchaseOrganizationAsync(organization, signup.PaymentMethodType.Value,
|
||||||
|
signup.PaymentToken, plan, signup.AdditionalStorageGb, signup.AdditionalSeats,
|
||||||
|
signup.PremiumAccessAddon, signup.TaxInfo, signup.IsFromProvider, signup.AdditionalSmSeats.GetValueOrDefault(),
|
||||||
|
signup.AdditionalServiceAccounts.GetValueOrDefault(), signup.IsFromSecretsManagerTrial);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await paymentService.PurchaseOrganizationNoPaymentMethod(organization, plan, signup.AdditionalSeats,
|
||||||
|
signup.PremiumAccessAddon, signup.AdditionalSmSeats.GetValueOrDefault(),
|
||||||
|
signup.AdditionalServiceAccounts.GetValueOrDefault(), signup.IsFromSecretsManagerTrial);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ownerId = signup.IsFromProvider ? default : signup.Owner.Id;
|
||||||
|
var returnValue = await SignUpAsync(organization, ownerId, signup.OwnerKey, signup.CollectionName, true);
|
||||||
|
await referenceEventService.RaiseEventAsync(
|
||||||
|
new ReferenceEvent(ReferenceEventType.Signup, organization, currentContext)
|
||||||
|
{
|
||||||
|
PlanName = plan.Name,
|
||||||
|
PlanType = plan.Type,
|
||||||
|
Seats = returnValue.Item1.Seats,
|
||||||
|
SignupInitiationPath = signup.InitiationPath,
|
||||||
|
Storage = returnValue.Item1.MaxStorageGb,
|
||||||
|
// TODO: add reference events for SmSeats and Service Accounts - see AC-1481
|
||||||
|
});
|
||||||
|
|
||||||
|
return new SignUpOrganizationResponse(returnValue.organization, returnValue.organizationUser, returnValue.defaultCollection);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ValidatePasswordManagerPlan(Plan plan, OrganizationUpgrade upgrade)
|
||||||
|
{
|
||||||
|
ValidatePlan(plan, upgrade.AdditionalSeats, "Password Manager");
|
||||||
|
|
||||||
|
if (plan.PasswordManager.BaseSeats + upgrade.AdditionalSeats <= 0)
|
||||||
|
{
|
||||||
|
throw new BadRequestException($"You do not have any Password Manager seats!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (upgrade.AdditionalSeats < 0)
|
||||||
|
{
|
||||||
|
throw new BadRequestException($"You can't subtract Password Manager seats!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!plan.PasswordManager.HasAdditionalStorageOption && upgrade.AdditionalStorageGb > 0)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("Plan does not allow additional storage.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (upgrade.AdditionalStorageGb < 0)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("You can't subtract storage!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!plan.PasswordManager.HasPremiumAccessOption && upgrade.PremiumAccessAddon)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("This plan does not allow you to buy the premium access addon.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!plan.PasswordManager.HasAdditionalSeatsOption && upgrade.AdditionalSeats > 0)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("Plan does not allow additional users.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plan.PasswordManager.HasAdditionalSeatsOption && plan.PasswordManager.MaxAdditionalSeats.HasValue &&
|
||||||
|
upgrade.AdditionalSeats > plan.PasswordManager.MaxAdditionalSeats.Value)
|
||||||
|
{
|
||||||
|
throw new BadRequestException($"Selected plan allows a maximum of " +
|
||||||
|
$"{plan.PasswordManager.MaxAdditionalSeats.GetValueOrDefault(0)} additional users.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ValidateSecretsManagerPlan(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.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 Machine Accounts.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((plan.ProductTier == ProductTierType.TeamsStarter &&
|
||||||
|
upgrade.AdditionalSmSeats.GetValueOrDefault() > plan.PasswordManager.BaseSeats) ||
|
||||||
|
(plan.ProductTier != ProductTierType.TeamsStarter &&
|
||||||
|
upgrade.AdditionalSmSeats.GetValueOrDefault() > upgrade.AdditionalSeats))
|
||||||
|
{
|
||||||
|
throw new BadRequestException("You cannot have more Secrets Manager seats than Password Manager seats.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (upgrade.AdditionalServiceAccounts.GetValueOrDefault() < 0)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("You can't subtract Machine Accounts!");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (plan.SecretsManager.HasAdditionalSeatsOption)
|
||||||
|
{
|
||||||
|
case false when upgrade.AdditionalSmSeats > 0:
|
||||||
|
throw new BadRequestException("Plan does not allow additional users.");
|
||||||
|
case true when plan.SecretsManager.MaxAdditionalSeats.HasValue &&
|
||||||
|
upgrade.AdditionalSmSeats > plan.SecretsManager.MaxAdditionalSeats.Value:
|
||||||
|
throw new BadRequestException($"Selected plan allows a maximum of " +
|
||||||
|
$"{plan.SecretsManager.MaxAdditionalSeats.GetValueOrDefault(0)} additional users.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ValidatePlan(Plan plan, int additionalSeats, string productType)
|
||||||
|
{
|
||||||
|
if (plan is null)
|
||||||
|
{
|
||||||
|
throw new BadRequestException($"{productType} Plan was null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plan.Disabled)
|
||||||
|
{
|
||||||
|
throw new BadRequestException($"{productType} Plan not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (additionalSeats < 0)
|
||||||
|
{
|
||||||
|
throw new BadRequestException($"You can't subtract {productType} seats!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ValidateSignUpPoliciesAsync(Guid ownerId)
|
||||||
|
{
|
||||||
|
var anySingleOrgPolicies = await policyService.AnyPoliciesApplicableToUserAsync(ownerId, PolicyType.SingleOrg);
|
||||||
|
if (anySingleOrgPolicies)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("You may not create an organization. You belong to an organization " +
|
||||||
|
"which has a policy that prohibits you from being a member of any other organization.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<(Organization organization, OrganizationUser organizationUser, Collection defaultCollection)> SignUpAsync(Organization organization,
|
||||||
|
Guid ownerId, string ownerKey, string collectionName, bool withPayment)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await organizationRepository.CreateAsync(organization);
|
||||||
|
await organizationApiKeyRepository.CreateAsync(new OrganizationApiKey
|
||||||
|
{
|
||||||
|
OrganizationId = organization.Id,
|
||||||
|
ApiKey = CoreHelpers.SecureRandomString(30),
|
||||||
|
Type = OrganizationApiKeyType.Default,
|
||||||
|
RevisionDate = DateTime.UtcNow,
|
||||||
|
});
|
||||||
|
await applicationCacheService.UpsertOrganizationAbilityAsync(organization);
|
||||||
|
|
||||||
|
// ownerId == default if the org is created by a provider - in this case it's created without an
|
||||||
|
// owner and the first owner is immediately invited afterwards
|
||||||
|
OrganizationUser orgUser = null;
|
||||||
|
if (ownerId != default)
|
||||||
|
{
|
||||||
|
orgUser = new OrganizationUser
|
||||||
|
{
|
||||||
|
OrganizationId = organization.Id,
|
||||||
|
UserId = ownerId,
|
||||||
|
Key = ownerKey,
|
||||||
|
AccessSecretsManager = organization.UseSecretsManager,
|
||||||
|
Type = OrganizationUserType.Owner,
|
||||||
|
Status = OrganizationUserStatusType.Confirmed,
|
||||||
|
CreationDate = organization.CreationDate,
|
||||||
|
RevisionDate = organization.CreationDate
|
||||||
|
};
|
||||||
|
orgUser.SetNewId();
|
||||||
|
|
||||||
|
await organizationUserRepository.CreateAsync(orgUser);
|
||||||
|
|
||||||
|
var devices = await GetUserDeviceIdsAsync(orgUser.UserId.Value);
|
||||||
|
await pushRegistrationService.AddUserRegistrationOrganizationAsync(devices, organization.Id.ToString());
|
||||||
|
await pushNotificationService.PushSyncOrgKeysAsync(ownerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Collection defaultCollection = null;
|
||||||
|
if (!string.IsNullOrWhiteSpace(collectionName))
|
||||||
|
{
|
||||||
|
defaultCollection = new Collection
|
||||||
|
{
|
||||||
|
Name = collectionName,
|
||||||
|
OrganizationId = organization.Id,
|
||||||
|
CreationDate = organization.CreationDate,
|
||||||
|
RevisionDate = organization.CreationDate
|
||||||
|
};
|
||||||
|
|
||||||
|
// Give the owner Can Manage access over the default collection
|
||||||
|
List<CollectionAccessSelection> defaultOwnerAccess = null;
|
||||||
|
if (orgUser != null)
|
||||||
|
{
|
||||||
|
defaultOwnerAccess =
|
||||||
|
[new CollectionAccessSelection { Id = orgUser.Id, HidePasswords = false, ReadOnly = false, Manage = true }];
|
||||||
|
}
|
||||||
|
|
||||||
|
await collectionRepository.CreateAsync(defaultCollection, null, defaultOwnerAccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (organization, orgUser, defaultCollection);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
if (withPayment)
|
||||||
|
{
|
||||||
|
await paymentService.CancelAndRecoverChargesAsync(organization);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (organization.Id != default(Guid))
|
||||||
|
{
|
||||||
|
await organizationRepository.DeleteAsync(organization);
|
||||||
|
await applicationCacheService.DeleteOrganizationAbilityAsync(organization.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IEnumerable<string>> GetUserDeviceIdsAsync(Guid userId)
|
||||||
|
{
|
||||||
|
var devices = await deviceRepository.GetManyByUserIdAsync(userId);
|
||||||
|
return devices
|
||||||
|
.Where(d => !string.IsNullOrWhiteSpace(d.PushToken))
|
||||||
|
.Select(d => d.Id.ToString());
|
||||||
|
}
|
||||||
|
}
|
@ -20,13 +20,7 @@ public interface IOrganizationService
|
|||||||
Task AutoAddSeatsAsync(Organization organization, int seatsToAdd);
|
Task AutoAddSeatsAsync(Organization organization, int seatsToAdd);
|
||||||
Task<string> AdjustSeatsAsync(Guid organizationId, int seatAdjustment);
|
Task<string> AdjustSeatsAsync(Guid organizationId, int seatAdjustment);
|
||||||
Task VerifyBankAsync(Guid organizationId, int amount1, int amount2);
|
Task VerifyBankAsync(Guid organizationId, int amount1, int amount2);
|
||||||
/// <summary>
|
|
||||||
/// Create a new organization in a cloud environment
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A tuple containing the new organization, the initial organizationUser (if any) and the default collection (if any)</returns>
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
Task<(Organization organization, OrganizationUser? organizationUser, Collection? defaultCollection)> SignUpAsync(OrganizationSignup organizationSignup);
|
|
||||||
|
|
||||||
Task<(Organization organization, OrganizationUser organizationUser, Collection defaultCollection)> SignupClientAsync(OrganizationSignup signup);
|
Task<(Organization organization, OrganizationUser organizationUser, Collection defaultCollection)> SignupClientAsync(OrganizationSignup signup);
|
||||||
#nullable disable
|
#nullable disable
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -16,7 +16,6 @@ using Bit.Core.Auth.Repositories;
|
|||||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||||
using Bit.Core.Billing.Enums;
|
using Bit.Core.Billing.Enums;
|
||||||
using Bit.Core.Billing.Extensions;
|
using Bit.Core.Billing.Extensions;
|
||||||
using Bit.Core.Billing.Models.Sales;
|
|
||||||
using Bit.Core.Billing.Services;
|
using Bit.Core.Billing.Services;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
@ -502,129 +501,6 @@ public class OrganizationService : IOrganizationService
|
|||||||
return returnValue;
|
return returnValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new organization in a cloud environment
|
|
||||||
/// </summary>
|
|
||||||
public async Task<(Organization organization, OrganizationUser organizationUser, Collection defaultCollection)> SignUpAsync(OrganizationSignup signup)
|
|
||||||
{
|
|
||||||
var plan = StaticStore.GetPlan(signup.Plan);
|
|
||||||
|
|
||||||
ValidatePasswordManagerPlan(plan, signup);
|
|
||||||
|
|
||||||
if (signup.UseSecretsManager)
|
|
||||||
{
|
|
||||||
if (signup.IsFromProvider)
|
|
||||||
{
|
|
||||||
throw new BadRequestException(
|
|
||||||
"Organizations with a Managed Service Provider do not support Secrets Manager.");
|
|
||||||
}
|
|
||||||
ValidateSecretsManagerPlan(plan, signup);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!signup.IsFromProvider)
|
|
||||||
{
|
|
||||||
await ValidateSignUpPoliciesAsync(signup.Owner.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
var organization = new Organization
|
|
||||||
{
|
|
||||||
// Pre-generate the org id so that we can save it with the Stripe subscription..
|
|
||||||
Id = CoreHelpers.GenerateComb(),
|
|
||||||
Name = signup.Name,
|
|
||||||
BillingEmail = signup.BillingEmail,
|
|
||||||
BusinessName = signup.BusinessName,
|
|
||||||
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,
|
|
||||||
LicenseKey = CoreHelpers.SecureRandomString(20),
|
|
||||||
PublicKey = signup.PublicKey,
|
|
||||||
PrivateKey = signup.PrivateKey,
|
|
||||||
CreationDate = DateTime.UtcNow,
|
|
||||||
RevisionDate = DateTime.UtcNow,
|
|
||||||
Status = OrganizationStatusType.Created,
|
|
||||||
UsePasswordManager = true,
|
|
||||||
UseSecretsManager = signup.UseSecretsManager
|
|
||||||
};
|
|
||||||
|
|
||||||
if (signup.UseSecretsManager)
|
|
||||||
{
|
|
||||||
organization.SmSeats = plan.SecretsManager.BaseSeats + signup.AdditionalSmSeats.GetValueOrDefault();
|
|
||||||
organization.SmServiceAccounts = plan.SecretsManager.BaseServiceAccount +
|
|
||||||
signup.AdditionalServiceAccounts.GetValueOrDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (plan.Type == PlanType.Free && !signup.IsFromProvider)
|
|
||||||
{
|
|
||||||
var adminCount =
|
|
||||||
await _organizationUserRepository.GetCountByFreeOrganizationAdminUserAsync(signup.Owner.Id);
|
|
||||||
if (adminCount > 0)
|
|
||||||
{
|
|
||||||
throw new BadRequestException("You can only be an admin of one free organization.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (plan.Type != PlanType.Free)
|
|
||||||
{
|
|
||||||
var deprecateStripeSourcesAPI = _featureService.IsEnabled(FeatureFlagKeys.AC2476_DeprecateStripeSourcesAPI);
|
|
||||||
|
|
||||||
if (deprecateStripeSourcesAPI)
|
|
||||||
{
|
|
||||||
var sale = OrganizationSale.From(organization, signup);
|
|
||||||
await _organizationBillingService.Finalize(sale);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (signup.PaymentMethodType != null)
|
|
||||||
{
|
|
||||||
await _paymentService.PurchaseOrganizationAsync(organization, signup.PaymentMethodType.Value,
|
|
||||||
signup.PaymentToken, plan, signup.AdditionalStorageGb, signup.AdditionalSeats,
|
|
||||||
signup.PremiumAccessAddon, signup.TaxInfo, signup.IsFromProvider, signup.AdditionalSmSeats.GetValueOrDefault(),
|
|
||||||
signup.AdditionalServiceAccounts.GetValueOrDefault(), signup.IsFromSecretsManagerTrial);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await _paymentService.PurchaseOrganizationNoPaymentMethod(organization, plan, signup.AdditionalSeats,
|
|
||||||
signup.PremiumAccessAddon, signup.AdditionalSmSeats.GetValueOrDefault(),
|
|
||||||
signup.AdditionalServiceAccounts.GetValueOrDefault(), signup.IsFromSecretsManagerTrial);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var ownerId = signup.IsFromProvider ? default : signup.Owner.Id;
|
|
||||||
var returnValue = await SignUpAsync(organization, ownerId, signup.OwnerKey, signup.CollectionName, true);
|
|
||||||
await _referenceEventService.RaiseEventAsync(
|
|
||||||
new ReferenceEvent(ReferenceEventType.Signup, organization, _currentContext)
|
|
||||||
{
|
|
||||||
PlanName = plan.Name,
|
|
||||||
PlanType = plan.Type,
|
|
||||||
Seats = returnValue.Item1.Seats,
|
|
||||||
SignupInitiationPath = signup.InitiationPath,
|
|
||||||
Storage = returnValue.Item1.MaxStorageGb,
|
|
||||||
// TODO: add reference events for SmSeats and Service Accounts - see AC-1481
|
|
||||||
});
|
|
||||||
|
|
||||||
return returnValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ValidateSignUpPoliciesAsync(Guid ownerId)
|
private async Task ValidateSignUpPoliciesAsync(Guid ownerId)
|
||||||
{
|
{
|
||||||
var anySingleOrgPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(ownerId, PolicyType.SingleOrg);
|
var anySingleOrgPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(ownerId, PolicyType.SingleOrg);
|
||||||
|
@ -8,6 +8,7 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationConnections;
|
|||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationConnections.Interfaces;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationConnections.Interfaces;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||||
@ -50,12 +51,16 @@ public static class OrganizationServiceCollectionExtensions
|
|||||||
services.AddOrganizationGroupCommands();
|
services.AddOrganizationGroupCommands();
|
||||||
services.AddOrganizationLicenseCommandsQueries();
|
services.AddOrganizationLicenseCommandsQueries();
|
||||||
services.AddOrganizationDomainCommandsQueries();
|
services.AddOrganizationDomainCommandsQueries();
|
||||||
|
services.AddOrganizationSignUpCommands();
|
||||||
services.AddOrganizationAuthCommands();
|
services.AddOrganizationAuthCommands();
|
||||||
services.AddOrganizationUserCommands();
|
services.AddOrganizationUserCommands();
|
||||||
services.AddOrganizationUserCommandsQueries();
|
services.AddOrganizationUserCommandsQueries();
|
||||||
services.AddBaseOrganizationSubscriptionCommandsQueries();
|
services.AddBaseOrganizationSubscriptionCommandsQueries();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IServiceCollection AddOrganizationSignUpCommands(this IServiceCollection services) =>
|
||||||
|
services.AddScoped<ICloudOrganizationSignUpCommand, CloudOrganizationSignUpCommand>();
|
||||||
|
|
||||||
private static void AddOrganizationConnectionCommands(this IServiceCollection services)
|
private static void AddOrganizationConnectionCommands(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddScoped<ICreateOrganizationConnectionCommand, CreateOrganizationConnectionCommand>();
|
services.AddScoped<ICreateOrganizationConnectionCommand, CreateOrganizationConnectionCommand>();
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using Bit.Api.IntegrationTest.Factories;
|
using Bit.Api.IntegrationTest.Factories;
|
||||||
using Bit.Core.AdminConsole.Entities;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||||
using Bit.Core.Billing.Enums;
|
using Bit.Core.Billing.Enums;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Business;
|
using Bit.Core.Models.Business;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
|
||||||
using Bit.IntegrationTestCommon.Factories;
|
using Bit.IntegrationTestCommon.Factories;
|
||||||
|
|
||||||
namespace Bit.Api.IntegrationTest.Helpers;
|
namespace Bit.Api.IntegrationTest.Helpers;
|
||||||
@ -24,11 +24,11 @@ public static class OrganizationTestHelpers
|
|||||||
PaymentMethodType paymentMethod = PaymentMethodType.None) where T : class
|
PaymentMethodType paymentMethod = PaymentMethodType.None) where T : class
|
||||||
{
|
{
|
||||||
var userRepository = factory.GetService<IUserRepository>();
|
var userRepository = factory.GetService<IUserRepository>();
|
||||||
var organizationService = factory.GetService<IOrganizationService>();
|
var organizationSignUpCommand = factory.GetService<ICloudOrganizationSignUpCommand>();
|
||||||
|
|
||||||
var owner = await userRepository.GetByEmailAsync(ownerEmail);
|
var owner = await userRepository.GetByEmailAsync(ownerEmail);
|
||||||
|
|
||||||
var signUpResult = await organizationService.SignUpAsync(new OrganizationSignup
|
var signUpResult = await organizationSignUpCommand.SignUpOrganizationAsync(new OrganizationSignup
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
BillingEmail = billingEmail,
|
BillingEmail = billingEmail,
|
||||||
@ -39,9 +39,9 @@ public static class OrganizationTestHelpers
|
|||||||
PaymentMethodType = paymentMethod
|
PaymentMethodType = paymentMethod
|
||||||
});
|
});
|
||||||
|
|
||||||
Debug.Assert(signUpResult.organizationUser is not null);
|
Debug.Assert(signUpResult.OrganizationUser is not null);
|
||||||
|
|
||||||
return new Tuple<Organization, OrganizationUser>(signUpResult.organization, signUpResult.organizationUser);
|
return new Tuple<Organization, OrganizationUser>(signUpResult.Organization, signUpResult.OrganizationUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -7,6 +7,7 @@ using Bit.Core.AdminConsole.Entities;
|
|||||||
using Bit.Core.AdminConsole.Enums.Provider;
|
using Bit.Core.AdminConsole.Enums.Provider;
|
||||||
using Bit.Core.AdminConsole.Models.Business.Tokenables;
|
using Bit.Core.AdminConsole.Models.Business.Tokenables;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.Auth.Entities;
|
using Bit.Core.Auth.Entities;
|
||||||
@ -46,11 +47,11 @@ public class OrganizationsControllerTests : IDisposable
|
|||||||
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
|
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
|
||||||
private readonly ICreateOrganizationApiKeyCommand _createOrganizationApiKeyCommand;
|
private readonly ICreateOrganizationApiKeyCommand _createOrganizationApiKeyCommand;
|
||||||
private readonly IFeatureService _featureService;
|
private readonly IFeatureService _featureService;
|
||||||
private readonly IPushNotificationService _pushNotificationService;
|
|
||||||
private readonly IProviderRepository _providerRepository;
|
private readonly IProviderRepository _providerRepository;
|
||||||
private readonly IProviderBillingService _providerBillingService;
|
private readonly IProviderBillingService _providerBillingService;
|
||||||
private readonly IDataProtectorTokenFactory<OrgDeleteTokenable> _orgDeleteTokenDataFactory;
|
private readonly IDataProtectorTokenFactory<OrgDeleteTokenable> _orgDeleteTokenDataFactory;
|
||||||
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
|
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
|
||||||
|
private readonly ICloudOrganizationSignUpCommand _cloudOrganizationSignUpCommand;
|
||||||
private readonly OrganizationsController _sut;
|
private readonly OrganizationsController _sut;
|
||||||
|
|
||||||
public OrganizationsControllerTests()
|
public OrganizationsControllerTests()
|
||||||
@ -69,11 +70,11 @@ public class OrganizationsControllerTests : IDisposable
|
|||||||
_userService = Substitute.For<IUserService>();
|
_userService = Substitute.For<IUserService>();
|
||||||
_createOrganizationApiKeyCommand = Substitute.For<ICreateOrganizationApiKeyCommand>();
|
_createOrganizationApiKeyCommand = Substitute.For<ICreateOrganizationApiKeyCommand>();
|
||||||
_featureService = Substitute.For<IFeatureService>();
|
_featureService = Substitute.For<IFeatureService>();
|
||||||
_pushNotificationService = Substitute.For<IPushNotificationService>();
|
|
||||||
_providerRepository = Substitute.For<IProviderRepository>();
|
_providerRepository = Substitute.For<IProviderRepository>();
|
||||||
_providerBillingService = Substitute.For<IProviderBillingService>();
|
_providerBillingService = Substitute.For<IProviderBillingService>();
|
||||||
_orgDeleteTokenDataFactory = Substitute.For<IDataProtectorTokenFactory<OrgDeleteTokenable>>();
|
_orgDeleteTokenDataFactory = Substitute.For<IDataProtectorTokenFactory<OrgDeleteTokenable>>();
|
||||||
_removeOrganizationUserCommand = Substitute.For<IRemoveOrganizationUserCommand>();
|
_removeOrganizationUserCommand = Substitute.For<IRemoveOrganizationUserCommand>();
|
||||||
|
_cloudOrganizationSignUpCommand = Substitute.For<ICloudOrganizationSignUpCommand>();
|
||||||
|
|
||||||
_sut = new OrganizationsController(
|
_sut = new OrganizationsController(
|
||||||
_organizationRepository,
|
_organizationRepository,
|
||||||
@ -90,11 +91,11 @@ public class OrganizationsControllerTests : IDisposable
|
|||||||
_organizationApiKeyRepository,
|
_organizationApiKeyRepository,
|
||||||
_featureService,
|
_featureService,
|
||||||
_globalSettings,
|
_globalSettings,
|
||||||
_pushNotificationService,
|
|
||||||
_providerRepository,
|
_providerRepository,
|
||||||
_providerBillingService,
|
_providerBillingService,
|
||||||
_orgDeleteTokenDataFactory,
|
_orgDeleteTokenDataFactory,
|
||||||
_removeOrganizationUserCommand);
|
_removeOrganizationUserCommand,
|
||||||
|
_cloudOrganizationSignUpCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
@ -0,0 +1,238 @@
|
|||||||
|
using Bit.Core.AdminConsole.Entities;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||||
|
using Bit.Core.Billing.Enums;
|
||||||
|
using Bit.Core.Entities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Exceptions;
|
||||||
|
using Bit.Core.Models.Business;
|
||||||
|
using Bit.Core.Models.Data;
|
||||||
|
using Bit.Core.Models.StaticStore;
|
||||||
|
using Bit.Core.Repositories;
|
||||||
|
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 Bit.Test.Common.AutoFixture;
|
||||||
|
using Bit.Test.Common.AutoFixture.Attributes;
|
||||||
|
using NSubstitute;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Organizations.OrganizationSignUp;
|
||||||
|
|
||||||
|
[SutProviderCustomize]
|
||||||
|
public class CloudICloudOrganizationSignUpCommandTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData(PlanType.FamiliesAnnually)]
|
||||||
|
public async Task SignUp_PM_Family_Passes(PlanType planType, OrganizationSignup signup, SutProvider<CloudOrganizationSignUpCommand> sutProvider)
|
||||||
|
{
|
||||||
|
signup.Plan = planType;
|
||||||
|
|
||||||
|
var plan = StaticStore.GetPlan(signup.Plan);
|
||||||
|
|
||||||
|
signup.AdditionalSeats = 0;
|
||||||
|
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||||
|
signup.PremiumAccessAddon = false;
|
||||||
|
signup.UseSecretsManager = false;
|
||||||
|
signup.IsFromSecretsManagerTrial = false;
|
||||||
|
|
||||||
|
var result = await sutProvider.Sut.SignUpOrganizationAsync(signup);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IOrganizationRepository>().Received(1).CreateAsync(
|
||||||
|
Arg.Is<Organization>(o =>
|
||||||
|
o.Seats == plan.PasswordManager.BaseSeats + signup.AdditionalSeats
|
||||||
|
&& o.SmSeats == null
|
||||||
|
&& o.SmServiceAccounts == null));
|
||||||
|
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 == plan.Name &&
|
||||||
|
referenceEvent.PlanType == plan.Type &&
|
||||||
|
referenceEvent.Seats == result.Organization.Seats &&
|
||||||
|
referenceEvent.Storage == result.Organization.MaxStorageGb));
|
||||||
|
// TODO: add reference events for SmSeats and Service Accounts - see AC-1481
|
||||||
|
|
||||||
|
Assert.NotNull(result.Organization);
|
||||||
|
Assert.NotNull(result.OrganizationUser);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IPaymentService>().Received(1).PurchaseOrganizationAsync(
|
||||||
|
Arg.Any<Organization>(),
|
||||||
|
signup.PaymentMethodType.Value,
|
||||||
|
signup.PaymentToken,
|
||||||
|
plan,
|
||||||
|
signup.AdditionalStorageGb,
|
||||||
|
signup.AdditionalSeats,
|
||||||
|
signup.PremiumAccessAddon,
|
||||||
|
signup.TaxInfo,
|
||||||
|
false,
|
||||||
|
signup.AdditionalSmSeats.GetValueOrDefault(),
|
||||||
|
signup.AdditionalServiceAccounts.GetValueOrDefault(),
|
||||||
|
signup.UseSecretsManager
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData(PlanType.FamiliesAnnually)]
|
||||||
|
public async Task SignUp_AssignsOwnerToDefaultCollection
|
||||||
|
(PlanType planType, OrganizationSignup signup, SutProvider<CloudOrganizationSignUpCommand> sutProvider)
|
||||||
|
{
|
||||||
|
signup.Plan = planType;
|
||||||
|
signup.AdditionalSeats = 0;
|
||||||
|
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||||
|
signup.PremiumAccessAddon = false;
|
||||||
|
signup.UseSecretsManager = false;
|
||||||
|
|
||||||
|
// Extract orgUserId when created
|
||||||
|
Guid? orgUserId = null;
|
||||||
|
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.CreateAsync(Arg.Do<OrganizationUser>(ou => orgUserId = ou.Id));
|
||||||
|
|
||||||
|
var result = await sutProvider.Sut.SignUpOrganizationAsync(signup);
|
||||||
|
|
||||||
|
// Assert: created a Can Manage association for the default collection
|
||||||
|
Assert.NotNull(orgUserId);
|
||||||
|
await sutProvider.GetDependency<ICollectionRepository>().Received(1).CreateAsync(
|
||||||
|
Arg.Any<Collection>(),
|
||||||
|
Arg.Is<IEnumerable<CollectionAccessSelection>>(cas => cas == null),
|
||||||
|
Arg.Is<IEnumerable<CollectionAccessSelection>>(cas =>
|
||||||
|
cas.Count() == 1 &&
|
||||||
|
cas.All(c =>
|
||||||
|
c.Id == orgUserId &&
|
||||||
|
!c.ReadOnly &&
|
||||||
|
!c.HidePasswords &&
|
||||||
|
c.Manage)));
|
||||||
|
|
||||||
|
Assert.NotNull(result.Organization);
|
||||||
|
Assert.NotNull(result.OrganizationUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData(PlanType.EnterpriseAnnually)]
|
||||||
|
[BitAutoData(PlanType.EnterpriseMonthly)]
|
||||||
|
[BitAutoData(PlanType.TeamsAnnually)]
|
||||||
|
[BitAutoData(PlanType.TeamsMonthly)]
|
||||||
|
public async Task SignUp_SM_Passes(PlanType planType, OrganizationSignup signup, SutProvider<CloudOrganizationSignUpCommand> sutProvider)
|
||||||
|
{
|
||||||
|
signup.Plan = planType;
|
||||||
|
|
||||||
|
var plan = StaticStore.GetPlan(signup.Plan);
|
||||||
|
|
||||||
|
signup.UseSecretsManager = true;
|
||||||
|
signup.AdditionalSeats = 15;
|
||||||
|
signup.AdditionalSmSeats = 10;
|
||||||
|
signup.AdditionalServiceAccounts = 20;
|
||||||
|
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||||
|
signup.PremiumAccessAddon = false;
|
||||||
|
signup.IsFromSecretsManagerTrial = false;
|
||||||
|
|
||||||
|
var result = await sutProvider.Sut.SignUpOrganizationAsync(signup);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IOrganizationRepository>().Received(1).CreateAsync(
|
||||||
|
Arg.Is<Organization>(o =>
|
||||||
|
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 == plan.Name &&
|
||||||
|
referenceEvent.PlanType == plan.Type &&
|
||||||
|
referenceEvent.Seats == result.Organization.Seats &&
|
||||||
|
referenceEvent.Storage == result.Organization.MaxStorageGb));
|
||||||
|
// TODO: add reference events for SmSeats and Service Accounts - see AC-1481
|
||||||
|
|
||||||
|
Assert.NotNull(result.Organization);
|
||||||
|
Assert.NotNull(result.OrganizationUser);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IPaymentService>().Received(1).PurchaseOrganizationAsync(
|
||||||
|
Arg.Any<Organization>(),
|
||||||
|
signup.PaymentMethodType.Value,
|
||||||
|
signup.PaymentToken,
|
||||||
|
Arg.Is<Plan>(plan),
|
||||||
|
signup.AdditionalStorageGb,
|
||||||
|
signup.AdditionalSeats,
|
||||||
|
signup.PremiumAccessAddon,
|
||||||
|
signup.TaxInfo,
|
||||||
|
false,
|
||||||
|
signup.AdditionalSmSeats.GetValueOrDefault(),
|
||||||
|
signup.AdditionalServiceAccounts.GetValueOrDefault(),
|
||||||
|
signup.IsFromSecretsManagerTrial
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData(PlanType.EnterpriseAnnually)]
|
||||||
|
public async Task SignUp_SM_Throws_WhenManagedByMSP(PlanType planType, OrganizationSignup signup, SutProvider<CloudOrganizationSignUpCommand> sutProvider)
|
||||||
|
{
|
||||||
|
signup.Plan = planType;
|
||||||
|
signup.UseSecretsManager = true;
|
||||||
|
signup.AdditionalSeats = 15;
|
||||||
|
signup.AdditionalSmSeats = 10;
|
||||||
|
signup.AdditionalServiceAccounts = 20;
|
||||||
|
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||||
|
signup.PremiumAccessAddon = false;
|
||||||
|
signup.IsFromProvider = true;
|
||||||
|
|
||||||
|
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.SignUpOrganizationAsync(signup));
|
||||||
|
Assert.Contains("Organizations with a Managed Service Provider do not support Secrets Manager.", exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task SignUpAsync_SecretManager_AdditionalServiceAccounts_NotAllowedByPlan_ShouldThrowException(OrganizationSignup signup, SutProvider<CloudOrganizationSignUpCommand> sutProvider)
|
||||||
|
{
|
||||||
|
signup.AdditionalSmSeats = 0;
|
||||||
|
signup.AdditionalSeats = 0;
|
||||||
|
signup.Plan = PlanType.Free;
|
||||||
|
signup.UseSecretsManager = true;
|
||||||
|
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||||
|
signup.PremiumAccessAddon = false;
|
||||||
|
signup.AdditionalServiceAccounts = 10;
|
||||||
|
signup.AdditionalStorageGb = 0;
|
||||||
|
|
||||||
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
|
() => sutProvider.Sut.SignUpOrganizationAsync(signup));
|
||||||
|
Assert.Contains("Plan does not allow additional Machine Accounts.", exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task SignUpAsync_SMSeatsGreatThanPMSeat_ShouldThrowException(OrganizationSignup signup, SutProvider<CloudOrganizationSignUpCommand> sutProvider)
|
||||||
|
{
|
||||||
|
signup.AdditionalSmSeats = 100;
|
||||||
|
signup.AdditionalSeats = 10;
|
||||||
|
signup.Plan = PlanType.EnterpriseAnnually;
|
||||||
|
signup.UseSecretsManager = true;
|
||||||
|
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||||
|
signup.PremiumAccessAddon = false;
|
||||||
|
signup.AdditionalServiceAccounts = 10;
|
||||||
|
|
||||||
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
|
() => sutProvider.Sut.SignUpOrganizationAsync(signup));
|
||||||
|
Assert.Contains("You cannot have more Secrets Manager seats than Password Manager seats", exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task SignUpAsync_InvalidateServiceAccount_ShouldThrowException(OrganizationSignup signup, SutProvider<CloudOrganizationSignUpCommand> sutProvider)
|
||||||
|
{
|
||||||
|
signup.AdditionalSmSeats = 10;
|
||||||
|
signup.AdditionalSeats = 10;
|
||||||
|
signup.Plan = PlanType.EnterpriseAnnually;
|
||||||
|
signup.UseSecretsManager = true;
|
||||||
|
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||||
|
signup.PremiumAccessAddon = false;
|
||||||
|
signup.AdditionalServiceAccounts = -10;
|
||||||
|
|
||||||
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
|
() => sutProvider.Sut.SignUpOrganizationAsync(signup));
|
||||||
|
Assert.Contains("You can't subtract Machine Accounts!", exception.Message);
|
||||||
|
}
|
||||||
|
}
|
@ -20,7 +20,6 @@ using Bit.Core.Models.Business;
|
|||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||||
using Bit.Core.Models.Mail;
|
using Bit.Core.Models.Mail;
|
||||||
using Bit.Core.Models.StaticStore;
|
|
||||||
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
|
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
@ -200,221 +199,6 @@ public class OrganizationServiceTests
|
|||||||
referenceEvent.Users == expectedNewUsersCount));
|
referenceEvent.Users == expectedNewUsersCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[BitAutoData(PlanType.FamiliesAnnually)]
|
|
||||||
public async Task SignUp_PM_Family_Passes(PlanType planType, OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
|
|
||||||
{
|
|
||||||
signup.Plan = planType;
|
|
||||||
|
|
||||||
var plan = StaticStore.GetPlan(signup.Plan);
|
|
||||||
|
|
||||||
signup.AdditionalSeats = 0;
|
|
||||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
|
||||||
signup.PremiumAccessAddon = false;
|
|
||||||
signup.UseSecretsManager = false;
|
|
||||||
signup.IsFromSecretsManagerTrial = false;
|
|
||||||
|
|
||||||
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 == plan.PasswordManager.BaseSeats + signup.AdditionalSeats
|
|
||||||
&& o.SmSeats == null
|
|
||||||
&& o.SmServiceAccounts == null));
|
|
||||||
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 == 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
|
|
||||||
|
|
||||||
Assert.NotNull(result.Item1);
|
|
||||||
Assert.NotNull(result.Item2);
|
|
||||||
|
|
||||||
await sutProvider.GetDependency<IPaymentService>().Received(1).PurchaseOrganizationAsync(
|
|
||||||
Arg.Any<Organization>(),
|
|
||||||
signup.PaymentMethodType.Value,
|
|
||||||
signup.PaymentToken,
|
|
||||||
plan,
|
|
||||||
signup.AdditionalStorageGb,
|
|
||||||
signup.AdditionalSeats,
|
|
||||||
signup.PremiumAccessAddon,
|
|
||||||
signup.TaxInfo,
|
|
||||||
false,
|
|
||||||
signup.AdditionalSmSeats.GetValueOrDefault(),
|
|
||||||
signup.AdditionalServiceAccounts.GetValueOrDefault(),
|
|
||||||
signup.UseSecretsManager
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[BitAutoData(PlanType.FamiliesAnnually)]
|
|
||||||
public async Task SignUp_AssignsOwnerToDefaultCollection
|
|
||||||
(PlanType planType, OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
|
|
||||||
{
|
|
||||||
signup.Plan = planType;
|
|
||||||
signup.AdditionalSeats = 0;
|
|
||||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
|
||||||
signup.PremiumAccessAddon = false;
|
|
||||||
signup.UseSecretsManager = false;
|
|
||||||
|
|
||||||
// Extract orgUserId when created
|
|
||||||
Guid? orgUserId = null;
|
|
||||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
|
||||||
.CreateAsync(Arg.Do<OrganizationUser>(ou => orgUserId = ou.Id));
|
|
||||||
|
|
||||||
var result = await sutProvider.Sut.SignUpAsync(signup);
|
|
||||||
|
|
||||||
// Assert: created a Can Manage association for the default collection
|
|
||||||
Assert.NotNull(orgUserId);
|
|
||||||
await sutProvider.GetDependency<ICollectionRepository>().Received(1).CreateAsync(
|
|
||||||
Arg.Any<Collection>(),
|
|
||||||
Arg.Is<IEnumerable<CollectionAccessSelection>>(cas => cas == null),
|
|
||||||
Arg.Is<IEnumerable<CollectionAccessSelection>>(cas =>
|
|
||||||
cas.Count() == 1 &&
|
|
||||||
cas.All(c =>
|
|
||||||
c.Id == orgUserId &&
|
|
||||||
!c.ReadOnly &&
|
|
||||||
!c.HidePasswords &&
|
|
||||||
c.Manage)));
|
|
||||||
|
|
||||||
Assert.NotNull(result.Item1);
|
|
||||||
Assert.NotNull(result.Item2);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[BitAutoData(PlanType.EnterpriseAnnually)]
|
|
||||||
[BitAutoData(PlanType.EnterpriseMonthly)]
|
|
||||||
[BitAutoData(PlanType.TeamsAnnually)]
|
|
||||||
[BitAutoData(PlanType.TeamsMonthly)]
|
|
||||||
public async Task SignUp_SM_Passes(PlanType planType, OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
|
|
||||||
{
|
|
||||||
signup.Plan = planType;
|
|
||||||
|
|
||||||
var plan = StaticStore.GetPlan(signup.Plan);
|
|
||||||
|
|
||||||
signup.UseSecretsManager = true;
|
|
||||||
signup.AdditionalSeats = 15;
|
|
||||||
signup.AdditionalSmSeats = 10;
|
|
||||||
signup.AdditionalServiceAccounts = 20;
|
|
||||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
|
||||||
signup.PremiumAccessAddon = false;
|
|
||||||
signup.IsFromSecretsManagerTrial = false;
|
|
||||||
|
|
||||||
var result = await sutProvider.Sut.SignUpAsync(signup);
|
|
||||||
|
|
||||||
await sutProvider.GetDependency<IOrganizationRepository>().Received(1).CreateAsync(
|
|
||||||
Arg.Is<Organization>(o =>
|
|
||||||
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 == 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
|
|
||||||
|
|
||||||
Assert.NotNull(result.Item1);
|
|
||||||
Assert.NotNull(result.Item2);
|
|
||||||
|
|
||||||
await sutProvider.GetDependency<IPaymentService>().Received(1).PurchaseOrganizationAsync(
|
|
||||||
Arg.Any<Organization>(),
|
|
||||||
signup.PaymentMethodType.Value,
|
|
||||||
signup.PaymentToken,
|
|
||||||
Arg.Is<Plan>(plan),
|
|
||||||
signup.AdditionalStorageGb,
|
|
||||||
signup.AdditionalSeats,
|
|
||||||
signup.PremiumAccessAddon,
|
|
||||||
signup.TaxInfo,
|
|
||||||
false,
|
|
||||||
signup.AdditionalSmSeats.GetValueOrDefault(),
|
|
||||||
signup.AdditionalServiceAccounts.GetValueOrDefault(),
|
|
||||||
signup.IsFromSecretsManagerTrial
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[BitAutoData(PlanType.EnterpriseAnnually)]
|
|
||||||
public async Task SignUp_SM_Throws_WhenManagedByMSP(PlanType planType, OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
|
|
||||||
{
|
|
||||||
signup.Plan = planType;
|
|
||||||
signup.UseSecretsManager = true;
|
|
||||||
signup.AdditionalSeats = 15;
|
|
||||||
signup.AdditionalSmSeats = 10;
|
|
||||||
signup.AdditionalServiceAccounts = 20;
|
|
||||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
|
||||||
signup.PremiumAccessAddon = false;
|
|
||||||
signup.IsFromProvider = true;
|
|
||||||
|
|
||||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.SignUpAsync(signup));
|
|
||||||
Assert.Contains("Organizations with a Managed Service Provider do not support Secrets Manager.", exception.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[BitAutoData]
|
|
||||||
public async Task SignUpAsync_SecretManager_AdditionalServiceAccounts_NotAllowedByPlan_ShouldThrowException(OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
|
|
||||||
{
|
|
||||||
signup.AdditionalSmSeats = 0;
|
|
||||||
signup.AdditionalSeats = 0;
|
|
||||||
signup.Plan = PlanType.Free;
|
|
||||||
signup.UseSecretsManager = true;
|
|
||||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
|
||||||
signup.PremiumAccessAddon = false;
|
|
||||||
signup.AdditionalServiceAccounts = 10;
|
|
||||||
signup.AdditionalStorageGb = 0;
|
|
||||||
|
|
||||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
|
||||||
() => sutProvider.Sut.SignUpAsync(signup));
|
|
||||||
Assert.Contains("Plan does not allow additional Machine Accounts.", exception.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[BitAutoData]
|
|
||||||
public async Task SignUpAsync_SMSeatsGreatThanPMSeat_ShouldThrowException(OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
|
|
||||||
{
|
|
||||||
signup.AdditionalSmSeats = 100;
|
|
||||||
signup.AdditionalSeats = 10;
|
|
||||||
signup.Plan = PlanType.EnterpriseAnnually;
|
|
||||||
signup.UseSecretsManager = true;
|
|
||||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
|
||||||
signup.PremiumAccessAddon = false;
|
|
||||||
signup.AdditionalServiceAccounts = 10;
|
|
||||||
|
|
||||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
|
||||||
() => sutProvider.Sut.SignUpAsync(signup));
|
|
||||||
Assert.Contains("You cannot have more Secrets Manager seats than Password Manager seats", exception.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[BitAutoData]
|
|
||||||
public async Task SignUpAsync_InvalidateServiceAccount_ShouldThrowException(OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
|
|
||||||
{
|
|
||||||
signup.AdditionalSmSeats = 10;
|
|
||||||
signup.AdditionalSeats = 10;
|
|
||||||
signup.Plan = PlanType.EnterpriseAnnually;
|
|
||||||
signup.UseSecretsManager = true;
|
|
||||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
|
||||||
signup.PremiumAccessAddon = false;
|
|
||||||
signup.AdditionalServiceAccounts = -10;
|
|
||||||
|
|
||||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
|
||||||
() => sutProvider.Sut.SignUpAsync(signup));
|
|
||||||
Assert.Contains("You can't subtract Machine Accounts!", exception.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task SignupClientAsync_Succeeds(
|
public async Task SignupClientAsync_Succeeds(
|
||||||
OrganizationSignup signup,
|
OrganizationSignup signup,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user