mirror of
https://github.com/bitwarden/server
synced 2025-12-19 09:43:25 +00:00
[PM-15159] Create SelfHostedOrganizationSignUp command (#6089)
* Add SelfHostedOrganizationSignUpCommand for organization sign-up process Method extracted from OrganizationService * Register SelfHostedOrganizationSignUpCommand for dependency injection * Add unit tests for SelfHostedOrganizationSignUpCommand * Refactor SelfHostedOrganizationLicensesController to use ISelfHostedOrganizationSignUpCommand * Remove SignUpAsync method and related validation from IOrganizationService and OrganizationService * Move ISelfHostedOrganizationSignUpCommand into a separate file and update references * Enable null safety in SelfHostedOrganizationSignUpCommand and update ISelfHostedOrganizationSignUpCommand interface to reflect nullable types for organizationUser and collectionName.
This commit is contained in:
@@ -5,6 +5,7 @@ using Bit.Api.AdminConsole.Models.Response.Organizations;
|
|||||||
using Bit.Api.Models.Request;
|
using Bit.Api.Models.Request;
|
||||||
using Bit.Api.Models.Request.Organizations;
|
using Bit.Api.Models.Request.Organizations;
|
||||||
using Bit.Api.Utilities;
|
using Bit.Api.Utilities;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
|
||||||
using Bit.Core.Billing.Organizations.Commands;
|
using Bit.Core.Billing.Organizations.Commands;
|
||||||
using Bit.Core.Billing.Organizations.Models;
|
using Bit.Core.Billing.Organizations.Models;
|
||||||
using Bit.Core.Billing.Organizations.Queries;
|
using Bit.Core.Billing.Organizations.Queries;
|
||||||
@@ -28,7 +29,7 @@ public class SelfHostedOrganizationLicensesController : Controller
|
|||||||
private readonly ICurrentContext _currentContext;
|
private readonly ICurrentContext _currentContext;
|
||||||
private readonly IGetSelfHostedOrganizationLicenseQuery _getSelfHostedOrganizationLicenseQuery;
|
private readonly IGetSelfHostedOrganizationLicenseQuery _getSelfHostedOrganizationLicenseQuery;
|
||||||
private readonly IOrganizationConnectionRepository _organizationConnectionRepository;
|
private readonly IOrganizationConnectionRepository _organizationConnectionRepository;
|
||||||
private readonly IOrganizationService _organizationService;
|
private readonly ISelfHostedOrganizationSignUpCommand _selfHostedOrganizationSignUpCommand;
|
||||||
private readonly IOrganizationRepository _organizationRepository;
|
private readonly IOrganizationRepository _organizationRepository;
|
||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
private readonly IUpdateOrganizationLicenseCommand _updateOrganizationLicenseCommand;
|
private readonly IUpdateOrganizationLicenseCommand _updateOrganizationLicenseCommand;
|
||||||
@@ -37,7 +38,7 @@ public class SelfHostedOrganizationLicensesController : Controller
|
|||||||
ICurrentContext currentContext,
|
ICurrentContext currentContext,
|
||||||
IGetSelfHostedOrganizationLicenseQuery getSelfHostedOrganizationLicenseQuery,
|
IGetSelfHostedOrganizationLicenseQuery getSelfHostedOrganizationLicenseQuery,
|
||||||
IOrganizationConnectionRepository organizationConnectionRepository,
|
IOrganizationConnectionRepository organizationConnectionRepository,
|
||||||
IOrganizationService organizationService,
|
ISelfHostedOrganizationSignUpCommand selfHostedOrganizationSignUpCommand,
|
||||||
IOrganizationRepository organizationRepository,
|
IOrganizationRepository organizationRepository,
|
||||||
IUserService userService,
|
IUserService userService,
|
||||||
IUpdateOrganizationLicenseCommand updateOrganizationLicenseCommand)
|
IUpdateOrganizationLicenseCommand updateOrganizationLicenseCommand)
|
||||||
@@ -45,7 +46,7 @@ public class SelfHostedOrganizationLicensesController : Controller
|
|||||||
_currentContext = currentContext;
|
_currentContext = currentContext;
|
||||||
_getSelfHostedOrganizationLicenseQuery = getSelfHostedOrganizationLicenseQuery;
|
_getSelfHostedOrganizationLicenseQuery = getSelfHostedOrganizationLicenseQuery;
|
||||||
_organizationConnectionRepository = organizationConnectionRepository;
|
_organizationConnectionRepository = organizationConnectionRepository;
|
||||||
_organizationService = organizationService;
|
_selfHostedOrganizationSignUpCommand = selfHostedOrganizationSignUpCommand;
|
||||||
_organizationRepository = organizationRepository;
|
_organizationRepository = organizationRepository;
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
_updateOrganizationLicenseCommand = updateOrganizationLicenseCommand;
|
_updateOrganizationLicenseCommand = updateOrganizationLicenseCommand;
|
||||||
@@ -66,7 +67,7 @@ public class SelfHostedOrganizationLicensesController : Controller
|
|||||||
throw new BadRequestException("Invalid license");
|
throw new BadRequestException("Invalid license");
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await _organizationService.SignUpAsync(license, user, model.Key,
|
var result = await _selfHostedOrganizationSignUpCommand.SignUpAsync(license, user, model.Key,
|
||||||
model.CollectionName, model.Keys?.PublicKey, model.Keys?.EncryptedPrivateKey);
|
model.CollectionName, model.Keys?.PublicKey, model.Keys?.EncryptedPrivateKey);
|
||||||
|
|
||||||
return new OrganizationResponseModel(result.Item1, null);
|
return new OrganizationResponseModel(result.Item1, null);
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using Bit.Core.AdminConsole.Entities;
|
||||||
|
using Bit.Core.Billing.Organizations.Models;
|
||||||
|
using Bit.Core.Entities;
|
||||||
|
|
||||||
|
namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
|
||||||
|
|
||||||
|
public interface ISelfHostedOrganizationSignUpCommand
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new organization on a self-hosted instance
|
||||||
|
/// </summary>
|
||||||
|
Task<(Organization organization, OrganizationUser? organizationUser)> SignUpAsync(
|
||||||
|
OrganizationLicense license, User owner, string ownerKey,
|
||||||
|
string? collectionName, string publicKey, string privateKey);
|
||||||
|
}
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using Bit.Core.AdminConsole.Entities;
|
||||||
|
using Bit.Core.AdminConsole.Enums;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
|
||||||
|
using Bit.Core.AdminConsole.Services;
|
||||||
|
using Bit.Core.Billing.Organizations.Models;
|
||||||
|
using Bit.Core.Billing.Services;
|
||||||
|
using Bit.Core.Entities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Exceptions;
|
||||||
|
using Bit.Core.Models.Data;
|
||||||
|
using Bit.Core.Platform.Push;
|
||||||
|
using Bit.Core.Repositories;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
using Bit.Core.Settings;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
|
namespace Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||||
|
|
||||||
|
public class SelfHostedOrganizationSignUpCommand : ISelfHostedOrganizationSignUpCommand
|
||||||
|
{
|
||||||
|
private readonly IOrganizationRepository _organizationRepository;
|
||||||
|
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||||
|
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
|
||||||
|
private readonly IApplicationCacheService _applicationCacheService;
|
||||||
|
private readonly ICollectionRepository _collectionRepository;
|
||||||
|
private readonly IPushRegistrationService _pushRegistrationService;
|
||||||
|
private readonly IPushNotificationService _pushNotificationService;
|
||||||
|
private readonly IDeviceRepository _deviceRepository;
|
||||||
|
private readonly ILicensingService _licensingService;
|
||||||
|
private readonly IPolicyService _policyService;
|
||||||
|
private readonly IGlobalSettings _globalSettings;
|
||||||
|
private readonly IPaymentService _paymentService;
|
||||||
|
|
||||||
|
public SelfHostedOrganizationSignUpCommand(
|
||||||
|
IOrganizationRepository organizationRepository,
|
||||||
|
IOrganizationUserRepository organizationUserRepository,
|
||||||
|
IOrganizationApiKeyRepository organizationApiKeyRepository,
|
||||||
|
IApplicationCacheService applicationCacheService,
|
||||||
|
ICollectionRepository collectionRepository,
|
||||||
|
IPushRegistrationService pushRegistrationService,
|
||||||
|
IPushNotificationService pushNotificationService,
|
||||||
|
IDeviceRepository deviceRepository,
|
||||||
|
ILicensingService licensingService,
|
||||||
|
IPolicyService policyService,
|
||||||
|
IGlobalSettings globalSettings,
|
||||||
|
IPaymentService paymentService)
|
||||||
|
{
|
||||||
|
_organizationRepository = organizationRepository;
|
||||||
|
_organizationUserRepository = organizationUserRepository;
|
||||||
|
_organizationApiKeyRepository = organizationApiKeyRepository;
|
||||||
|
_applicationCacheService = applicationCacheService;
|
||||||
|
_collectionRepository = collectionRepository;
|
||||||
|
_pushRegistrationService = pushRegistrationService;
|
||||||
|
_pushNotificationService = pushNotificationService;
|
||||||
|
_deviceRepository = deviceRepository;
|
||||||
|
_licensingService = licensingService;
|
||||||
|
_policyService = policyService;
|
||||||
|
_globalSettings = globalSettings;
|
||||||
|
_paymentService = paymentService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<(Organization organization, OrganizationUser? organizationUser)> SignUpAsync(
|
||||||
|
OrganizationLicense license, User owner, string ownerKey, string? collectionName, string publicKey,
|
||||||
|
string privateKey)
|
||||||
|
{
|
||||||
|
if (license.LicenseType != LicenseType.Organization)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("Premium licenses cannot be applied to an organization. " +
|
||||||
|
"Upload this license from your personal account settings page.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var claimsPrincipal = _licensingService.GetClaimsPrincipalFromLicense(license);
|
||||||
|
var canUse = license.CanUse(_globalSettings, _licensingService, claimsPrincipal, out var exception);
|
||||||
|
|
||||||
|
if (!canUse)
|
||||||
|
{
|
||||||
|
throw new BadRequestException(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
var enabledOrgs = await _organizationRepository.GetManyByEnabledAsync();
|
||||||
|
if (enabledOrgs.Any(o => string.Equals(o.LicenseKey, license.LicenseKey)))
|
||||||
|
{
|
||||||
|
throw new BadRequestException("License is already in use by another organization.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await ValidateSignUpPoliciesAsync(owner.Id);
|
||||||
|
|
||||||
|
var organization = claimsPrincipal != null
|
||||||
|
// If the ClaimsPrincipal exists (there's a token on the license), use it to build the organization.
|
||||||
|
? OrganizationFactory.Create(owner, claimsPrincipal, publicKey, privateKey)
|
||||||
|
// If there's no ClaimsPrincipal (there's no token on the license), use the license to build the organization.
|
||||||
|
: OrganizationFactory.Create(owner, license, publicKey, privateKey);
|
||||||
|
|
||||||
|
var result = await SignUpAsync(organization, owner.Id, ownerKey, collectionName, false);
|
||||||
|
|
||||||
|
var dir = $"{_globalSettings.LicenseDirectory}/organization";
|
||||||
|
Directory.CreateDirectory(dir);
|
||||||
|
await using var fs = new FileStream(Path.Combine(dir, $"{organization.Id}.json"), FileMode.Create);
|
||||||
|
await JsonSerializer.SerializeAsync(fs, license, JsonHelpers.Indented);
|
||||||
|
return (result.organization, result.organizationUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Private helper method to create a new organization.
|
||||||
|
/// This is common code used by both the cloud and self-hosted methods.
|
||||||
|
/// </summary>
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@
|
|||||||
using Bit.Core.AdminConsole.Entities;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
using Bit.Core.AdminConsole.Models.Business;
|
using Bit.Core.AdminConsole.Models.Business;
|
||||||
using Bit.Core.Auth.Enums;
|
using Bit.Core.Auth.Enums;
|
||||||
using Bit.Core.Billing.Organizations.Models;
|
|
||||||
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;
|
||||||
@@ -21,11 +20,6 @@ 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 on a self-hosted instance
|
|
||||||
/// </summary>
|
|
||||||
Task<(Organization organization, OrganizationUser organizationUser)> SignUpAsync(OrganizationLicense license, User owner,
|
|
||||||
string ownerKey, string collectionName, string publicKey, string privateKey);
|
|
||||||
Task UpdateExpirationDateAsync(Guid organizationId, DateTime? expirationDate);
|
Task UpdateExpirationDateAsync(Guid organizationId, DateTime? expirationDate);
|
||||||
Task UpdateAsync(Organization organization, bool updateBilling = false, EventType eventType = EventType.Organization_Updated);
|
Task UpdateAsync(Organization organization, bool updateBilling = false, EventType eventType = EventType.Organization_Updated);
|
||||||
Task UpdateTwoFactorProviderAsync(Organization organization, TwoFactorProviderType type);
|
Task UpdateTwoFactorProviderAsync(Organization organization, TwoFactorProviderType type);
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ using Bit.Core.Auth.Repositories;
|
|||||||
using Bit.Core.Billing.Constants;
|
using Bit.Core.Billing.Constants;
|
||||||
using Bit.Core.Billing.Enums;
|
using Bit.Core.Billing.Enums;
|
||||||
using Bit.Core.Billing.Extensions;
|
using Bit.Core.Billing.Extensions;
|
||||||
using Bit.Core.Billing.Organizations.Models;
|
|
||||||
using Bit.Core.Billing.Pricing;
|
using Bit.Core.Billing.Pricing;
|
||||||
using Bit.Core.Billing.Services;
|
using Bit.Core.Billing.Services;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
@@ -396,155 +395,6 @@ public class OrganizationService : IOrganizationService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new organization on a self-hosted instance
|
|
||||||
/// </summary>
|
|
||||||
public async Task<(Organization organization, OrganizationUser organizationUser)> SignUpAsync(
|
|
||||||
OrganizationLicense license, User owner, string ownerKey, string collectionName, string publicKey,
|
|
||||||
string privateKey)
|
|
||||||
{
|
|
||||||
if (license.LicenseType != LicenseType.Organization)
|
|
||||||
{
|
|
||||||
throw new BadRequestException("Premium licenses cannot be applied to an organization. " +
|
|
||||||
"Upload this license from your personal account settings page.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var claimsPrincipal = _licensingService.GetClaimsPrincipalFromLicense(license);
|
|
||||||
var canUse = license.CanUse(_globalSettings, _licensingService, claimsPrincipal, out var exception);
|
|
||||||
|
|
||||||
if (!canUse)
|
|
||||||
{
|
|
||||||
throw new BadRequestException(exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
var enabledOrgs = await _organizationRepository.GetManyByEnabledAsync();
|
|
||||||
if (enabledOrgs.Any(o => string.Equals(o.LicenseKey, license.LicenseKey)))
|
|
||||||
{
|
|
||||||
throw new BadRequestException("License is already in use by another organization.");
|
|
||||||
}
|
|
||||||
|
|
||||||
await ValidateSignUpPoliciesAsync(owner.Id);
|
|
||||||
|
|
||||||
var organization = claimsPrincipal != null
|
|
||||||
// If the ClaimsPrincipal exists (there's a token on the license), use it to build the organization.
|
|
||||||
? OrganizationFactory.Create(owner, claimsPrincipal, publicKey, privateKey)
|
|
||||||
// If there's no ClaimsPrincipal (there's no token on the license), use the license to build the organization.
|
|
||||||
: OrganizationFactory.Create(owner, license, publicKey, privateKey);
|
|
||||||
|
|
||||||
var result = await SignUpAsync(organization, owner.Id, ownerKey, collectionName, false);
|
|
||||||
|
|
||||||
var dir = $"{_globalSettings.LicenseDirectory}/organization";
|
|
||||||
Directory.CreateDirectory(dir);
|
|
||||||
await using var fs = new FileStream(Path.Combine(dir, $"{organization.Id}.json"), FileMode.Create);
|
|
||||||
await JsonSerializer.SerializeAsync(fs, license, JsonHelpers.Indented);
|
|
||||||
return (result.organization, result.organizationUser);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Private helper method to create a new organization.
|
|
||||||
/// This is common code used by both the cloud and self-hosted methods.
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task UpdateExpirationDateAsync(Guid organizationId, DateTime? expirationDate)
|
public async Task UpdateExpirationDateAsync(Guid organizationId, DateTime? expirationDate)
|
||||||
{
|
{
|
||||||
var org = await GetOrgById(organizationId);
|
var org = await GetOrgById(organizationId);
|
||||||
@@ -1338,14 +1188,6 @@ public class OrganizationService : IOrganizationService
|
|||||||
await _groupRepository.UpdateUsersAsync(group.Id, users);
|
await _groupRepository.UpdateUsersAsync(group.Id, users);
|
||||||
}
|
}
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ReplaceAndUpdateCacheAsync(Organization org, EventType? orgEvent = null)
|
public async Task ReplaceAndUpdateCacheAsync(Organization org, EventType? orgEvent = null)
|
||||||
{
|
{
|
||||||
await _organizationRepository.ReplaceAsync(org);
|
await _organizationRepository.ReplaceAsync(org);
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ public static class OrganizationServiceCollectionExtensions
|
|||||||
services.AddScoped<ICloudOrganizationSignUpCommand, CloudOrganizationSignUpCommand>();
|
services.AddScoped<ICloudOrganizationSignUpCommand, CloudOrganizationSignUpCommand>();
|
||||||
services.AddScoped<IProviderClientOrganizationSignUpCommand, ProviderClientOrganizationSignUpCommand>();
|
services.AddScoped<IProviderClientOrganizationSignUpCommand, ProviderClientOrganizationSignUpCommand>();
|
||||||
services.AddScoped<IResellerClientOrganizationSignUpCommand, ResellerClientOrganizationSignUpCommand>();
|
services.AddScoped<IResellerClientOrganizationSignUpCommand, ResellerClientOrganizationSignUpCommand>();
|
||||||
|
services.AddScoped<ISelfHostedOrganizationSignUpCommand, SelfHostedOrganizationSignUpCommand>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddOrganizationDeleteCommands(this IServiceCollection services)
|
private static void AddOrganizationDeleteCommands(this IServiceCollection services)
|
||||||
|
|||||||
@@ -0,0 +1,351 @@
|
|||||||
|
using System.Security.Claims;
|
||||||
|
using Bit.Core.AdminConsole.Entities;
|
||||||
|
using Bit.Core.AdminConsole.Enums;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||||
|
using Bit.Core.AdminConsole.Services;
|
||||||
|
using Bit.Core.Billing.Organizations.Models;
|
||||||
|
using Bit.Core.Billing.Services;
|
||||||
|
using Bit.Core.Entities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Exceptions;
|
||||||
|
using Bit.Core.Models.Data;
|
||||||
|
using Bit.Core.Platform.Push;
|
||||||
|
using Bit.Core.Repositories;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
using Bit.Core.Settings;
|
||||||
|
using Bit.Test.Common.AutoFixture;
|
||||||
|
using Bit.Test.Common.AutoFixture.Attributes;
|
||||||
|
using NSubstitute;
|
||||||
|
using NSubstitute.ExceptionExtensions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Organizations;
|
||||||
|
|
||||||
|
[SutProviderCustomize]
|
||||||
|
public class SelfHostedOrganizationSignUpCommandTests
|
||||||
|
{
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task SignUpAsync_WithValidRequest_CreatesOrganizationSuccessfully(
|
||||||
|
User owner, string ownerKey, string collectionName, string publicKey,
|
||||||
|
string privateKey, List<Device> devices,
|
||||||
|
SutProvider<SelfHostedOrganizationSignUpCommand> sutProvider)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var globalSettings = sutProvider.GetDependency<IGlobalSettings>();
|
||||||
|
var license = CreateValidOrganizationLicense(globalSettings);
|
||||||
|
|
||||||
|
SetupCommonMocks(sutProvider, owner);
|
||||||
|
SetupLicenseValidation(sutProvider, license);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IDeviceRepository>()
|
||||||
|
.GetManyByUserIdAsync(owner.Id)
|
||||||
|
.Returns(devices);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await sutProvider.Sut.SignUpAsync(license, owner, ownerKey, collectionName, publicKey, privateKey);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result.organization);
|
||||||
|
Assert.NotNull(result.organizationUser);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IOrganizationRepository>()
|
||||||
|
.Received(1)
|
||||||
|
.CreateAsync(result.organization);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IOrganizationApiKeyRepository>()
|
||||||
|
.Received(1)
|
||||||
|
.CreateAsync(Arg.Is<OrganizationApiKey>(key =>
|
||||||
|
key.OrganizationId == result.organization.Id &&
|
||||||
|
key.Type == OrganizationApiKeyType.Default &&
|
||||||
|
!string.IsNullOrEmpty(key.ApiKey)));
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IApplicationCacheService>()
|
||||||
|
.Received(1)
|
||||||
|
.UpsertOrganizationAbilityAsync(result.organization);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.Received(1)
|
||||||
|
.CreateAsync(Arg.Is<OrganizationUser>(user =>
|
||||||
|
user.OrganizationId == result.organization.Id &&
|
||||||
|
user.UserId == owner.Id &&
|
||||||
|
user.Key == ownerKey &&
|
||||||
|
user.Type == OrganizationUserType.Owner &&
|
||||||
|
user.Status == OrganizationUserStatusType.Confirmed));
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<ICollectionRepository>()
|
||||||
|
.Received(1)
|
||||||
|
.CreateAsync(
|
||||||
|
Arg.Is<Collection>(c => c.Name == collectionName && c.OrganizationId == result.organization.Id),
|
||||||
|
Arg.Is<IEnumerable<CollectionAccessSelection>>(groups => groups == null),
|
||||||
|
Arg.Is<IEnumerable<CollectionAccessSelection>>(access =>
|
||||||
|
access.Any(a => a.Id == result.organizationUser.Id && a.Manage && !a.ReadOnly && !a.HidePasswords)));
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IPushNotificationService>()
|
||||||
|
.Received(1)
|
||||||
|
.PushSyncOrgKeysAsync(owner.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task SignUpAsync_WithPremiumLicense_ThrowsBadRequestException(
|
||||||
|
User owner, string ownerKey, string collectionName,
|
||||||
|
string publicKey, string privateKey,
|
||||||
|
SutProvider<SelfHostedOrganizationSignUpCommand> sutProvider)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var globalSettings = sutProvider.GetDependency<IGlobalSettings>();
|
||||||
|
var license = CreateValidOrganizationLicense(globalSettings, LicenseType.User);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
|
() => sutProvider.Sut.SignUpAsync(license, owner, ownerKey, collectionName, publicKey, privateKey));
|
||||||
|
|
||||||
|
Assert.Contains("Premium licenses cannot be applied to an organization", exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task SignUpAsync_WithInvalidLicense_ThrowsBadRequestException(
|
||||||
|
User owner, string ownerKey, string collectionName,
|
||||||
|
string publicKey, string privateKey,
|
||||||
|
SutProvider<SelfHostedOrganizationSignUpCommand> sutProvider)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var globalSettings = sutProvider.GetDependency<IGlobalSettings>();
|
||||||
|
var license = CreateValidOrganizationLicense(globalSettings);
|
||||||
|
license.CanUse(globalSettings, sutProvider.GetDependency<ILicensingService>(), null, out _)
|
||||||
|
.Returns(false);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
await Assert.ThrowsAsync<BadRequestException>(
|
||||||
|
() => sutProvider.Sut.SignUpAsync(license, owner, ownerKey, collectionName, publicKey, privateKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task SignUpAsync_WithLicenseAlreadyInUse_ThrowsBadRequestException(
|
||||||
|
User owner, string ownerKey, string collectionName,
|
||||||
|
string publicKey, string privateKey, Organization existingOrganization,
|
||||||
|
SutProvider<SelfHostedOrganizationSignUpCommand> sutProvider)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var globalSettings = sutProvider.GetDependency<IGlobalSettings>();
|
||||||
|
var license = CreateValidOrganizationLicense(globalSettings);
|
||||||
|
existingOrganization.LicenseKey = license.LicenseKey;
|
||||||
|
|
||||||
|
SetupCommonMocks(sutProvider, owner);
|
||||||
|
SetupLicenseValidation(sutProvider, license);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IOrganizationRepository>()
|
||||||
|
.GetManyByEnabledAsync()
|
||||||
|
.Returns(new List<Organization> { existingOrganization });
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
|
() => sutProvider.Sut.SignUpAsync(license, owner, ownerKey, collectionName, publicKey, privateKey));
|
||||||
|
|
||||||
|
Assert.Contains("License is already in use", exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task SignUpAsync_WithSingleOrgPolicy_ThrowsBadRequestException(
|
||||||
|
User owner, string ownerKey, string collectionName,
|
||||||
|
string publicKey, string privateKey,
|
||||||
|
SutProvider<SelfHostedOrganizationSignUpCommand> sutProvider)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var globalSettings = sutProvider.GetDependency<IGlobalSettings>();
|
||||||
|
var license = CreateValidOrganizationLicense(globalSettings);
|
||||||
|
|
||||||
|
SetupCommonMocks(sutProvider, owner);
|
||||||
|
SetupLicenseValidation(sutProvider, license);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IPolicyService>()
|
||||||
|
.AnyPoliciesApplicableToUserAsync(owner.Id, PolicyType.SingleOrg)
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
|
() => sutProvider.Sut.SignUpAsync(license, owner, ownerKey, collectionName, publicKey, privateKey));
|
||||||
|
|
||||||
|
Assert.Contains("You may not create an organization", exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task SignUpAsync_WithClaimsPrincipal_UsesClaimsPrincipalToCreateOrganization(
|
||||||
|
User owner, string ownerKey, string collectionName,
|
||||||
|
string publicKey, string privateKey, ClaimsPrincipal claimsPrincipal,
|
||||||
|
SutProvider<SelfHostedOrganizationSignUpCommand> sutProvider)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var globalSettings = sutProvider.GetDependency<IGlobalSettings>();
|
||||||
|
var license = CreateValidOrganizationLicense(globalSettings);
|
||||||
|
|
||||||
|
SetupCommonMocks(sutProvider, owner);
|
||||||
|
SetupLicenseValidation(sutProvider, license);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ILicensingService>()
|
||||||
|
.GetClaimsPrincipalFromLicense(license)
|
||||||
|
.Returns(claimsPrincipal);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IDeviceRepository>()
|
||||||
|
.GetManyByUserIdAsync(owner.Id)
|
||||||
|
.Returns(new List<Device>());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await sutProvider.Sut.SignUpAsync(license, owner, ownerKey, collectionName, publicKey, privateKey);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result.organization);
|
||||||
|
Assert.NotNull(result.organizationUser);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ILicensingService>()
|
||||||
|
.Received(1)
|
||||||
|
.GetClaimsPrincipalFromLicense(license);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task SignUpAsync_WithoutCollectionName_DoesNotCreateCollection(
|
||||||
|
User owner, string ownerKey, string publicKey, string privateKey,
|
||||||
|
SutProvider<SelfHostedOrganizationSignUpCommand> sutProvider)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var globalSettings = sutProvider.GetDependency<IGlobalSettings>();
|
||||||
|
var license = CreateValidOrganizationLicense(globalSettings);
|
||||||
|
|
||||||
|
SetupCommonMocks(sutProvider, owner);
|
||||||
|
SetupLicenseValidation(sutProvider, license);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IDeviceRepository>()
|
||||||
|
.GetManyByUserIdAsync(owner.Id)
|
||||||
|
.Returns(new List<Device>());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await sutProvider.Sut.SignUpAsync(license, owner, ownerKey, null, publicKey, privateKey);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result.organization);
|
||||||
|
Assert.NotNull(result.organizationUser);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<ICollectionRepository>()
|
||||||
|
.DidNotReceive()
|
||||||
|
.CreateAsync(Arg.Any<Collection>(), Arg.Is<IEnumerable<CollectionAccessSelection>>(x => true), Arg.Is<IEnumerable<CollectionAccessSelection>>(x => true));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task SignUpAsync_WithDevices_RegistersDevicesForPushNotifications(
|
||||||
|
User owner, string ownerKey, string collectionName,
|
||||||
|
string publicKey, string privateKey, List<Device> devices,
|
||||||
|
SutProvider<SelfHostedOrganizationSignUpCommand> sutProvider)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var globalSettings = sutProvider.GetDependency<IGlobalSettings>();
|
||||||
|
var license = CreateValidOrganizationLicense(globalSettings);
|
||||||
|
|
||||||
|
foreach (var device in devices)
|
||||||
|
{
|
||||||
|
device.PushToken = "push-token-" + device.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetupCommonMocks(sutProvider, owner);
|
||||||
|
SetupLicenseValidation(sutProvider, license);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IDeviceRepository>()
|
||||||
|
.GetManyByUserIdAsync(owner.Id)
|
||||||
|
.Returns(devices);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await sutProvider.Sut.SignUpAsync(license, owner, ownerKey, collectionName, publicKey, privateKey);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result.organization);
|
||||||
|
Assert.NotNull(result.organizationUser);
|
||||||
|
|
||||||
|
var expectedDeviceIds = devices.Select(d => d.Id.ToString());
|
||||||
|
await sutProvider.GetDependency<IPushRegistrationService>()
|
||||||
|
.Received(1)
|
||||||
|
.AddUserRegistrationOrganizationAsync(
|
||||||
|
Arg.Is<IEnumerable<string>>(ids => ids.SequenceEqual(expectedDeviceIds)),
|
||||||
|
result.organization.Id.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task SignUpAsync_OnException_CleansUpOrganization(
|
||||||
|
User owner, string ownerKey, string collectionName,
|
||||||
|
string publicKey, string privateKey,
|
||||||
|
SutProvider<SelfHostedOrganizationSignUpCommand> sutProvider)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var globalSettings = sutProvider.GetDependency<IGlobalSettings>();
|
||||||
|
var license = CreateValidOrganizationLicense(globalSettings);
|
||||||
|
|
||||||
|
SetupCommonMocks(sutProvider, owner);
|
||||||
|
SetupLicenseValidation(sutProvider, license);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IOrganizationApiKeyRepository>()
|
||||||
|
.CreateAsync(Arg.Any<OrganizationApiKey>())
|
||||||
|
.Throws(new Exception("Test exception"));
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
await Assert.ThrowsAsync<Exception>(
|
||||||
|
() => sutProvider.Sut.SignUpAsync(license, owner, ownerKey, collectionName, publicKey, privateKey));
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IOrganizationRepository>()
|
||||||
|
.Received(1)
|
||||||
|
.DeleteAsync(Arg.Any<Organization>());
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IApplicationCacheService>()
|
||||||
|
.Received(1)
|
||||||
|
.DeleteOrganizationAbilityAsync(Arg.Any<Guid>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupCommonMocks(
|
||||||
|
SutProvider<SelfHostedOrganizationSignUpCommand> sutProvider,
|
||||||
|
User owner)
|
||||||
|
{
|
||||||
|
var globalSettings = sutProvider.GetDependency<IGlobalSettings>();
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IOrganizationRepository>()
|
||||||
|
.CreateAsync(Arg.Any<Organization>())
|
||||||
|
.Returns(callInfo =>
|
||||||
|
{
|
||||||
|
var org = callInfo.Arg<Organization>();
|
||||||
|
org.Id = Guid.NewGuid();
|
||||||
|
return Task.FromResult(org);
|
||||||
|
});
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IPolicyService>()
|
||||||
|
.AnyPoliciesApplicableToUserAsync(owner.Id, PolicyType.SingleOrg)
|
||||||
|
.Returns(false);
|
||||||
|
|
||||||
|
globalSettings.LicenseDirectory.Returns("/tmp/licenses");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupLicenseValidation(
|
||||||
|
SutProvider<SelfHostedOrganizationSignUpCommand> sutProvider,
|
||||||
|
OrganizationLicense license)
|
||||||
|
{
|
||||||
|
var globalSettings = sutProvider.GetDependency<IGlobalSettings>();
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ILicensingService>()
|
||||||
|
.VerifyLicense(license)
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
license.CanUse(globalSettings, sutProvider.GetDependency<ILicensingService>(), null, out _)
|
||||||
|
.Returns(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OrganizationLicense CreateValidOrganizationLicense(
|
||||||
|
IGlobalSettings globalSettings,
|
||||||
|
LicenseType licenseType = LicenseType.Organization)
|
||||||
|
{
|
||||||
|
return new OrganizationLicense
|
||||||
|
{
|
||||||
|
LicenseType = licenseType,
|
||||||
|
Signature = Guid.NewGuid().ToString().Replace('-', '+'),
|
||||||
|
Issued = DateTime.UtcNow.AddDays(-1),
|
||||||
|
Expires = DateTime.UtcNow.AddDays(10),
|
||||||
|
Version = OrganizationLicense.CurrentLicenseFileVersion,
|
||||||
|
InstallationId = globalSettings.Installation.Id,
|
||||||
|
Enabled = true,
|
||||||
|
SelfHost = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user