Browse Source
* move UpdateLicenseAsync from service to command * create new SelfHostedOrganizationDetails view model and move license validation logic there * move occupied seat count logic to database levelpull/2742/head
30 changed files with 967 additions and 239 deletions
@ -0,0 +1,145 @@
@@ -0,0 +1,145 @@
|
||||
using Bit.Core.Entities; |
||||
using Bit.Core.Enums; |
||||
using Bit.Core.Models.Business; |
||||
using Bit.Core.Models.OrganizationConnectionConfigs; |
||||
|
||||
namespace Bit.Core.Models.Data.Organizations; |
||||
|
||||
public class SelfHostedOrganizationDetails : Organization |
||||
{ |
||||
public int OccupiedSeatCount { get; set; } |
||||
public int CollectionCount { get; set; } |
||||
public int GroupCount { get; set; } |
||||
public IEnumerable<OrganizationUser> OrganizationUsers { get; set; } |
||||
public IEnumerable<Policy> Policies { get; set; } |
||||
public SsoConfig SsoConfig { get; set; } |
||||
public IEnumerable<OrganizationConnection> ScimConnections { get; set; } |
||||
|
||||
public bool CanUseLicense(OrganizationLicense license, out string exception) |
||||
{ |
||||
if (license.Seats.HasValue && OccupiedSeatCount > license.Seats.Value) |
||||
{ |
||||
exception = $"Your organization currently has {OccupiedSeatCount} seats filled. " + |
||||
$"Your new license only has ({license.Seats.Value}) seats. Remove some users."; |
||||
return false; |
||||
} |
||||
|
||||
if (license.MaxCollections.HasValue && CollectionCount > license.MaxCollections.Value) |
||||
{ |
||||
exception = $"Your organization currently has {CollectionCount} collections. " + |
||||
$"Your new license allows for a maximum of ({license.MaxCollections.Value}) collections. " + |
||||
"Remove some collections."; |
||||
return false; |
||||
} |
||||
|
||||
if (!license.UseGroups && UseGroups && GroupCount > 1) |
||||
{ |
||||
exception = $"Your organization currently has {GroupCount} groups. " + |
||||
$"Your new license does not allow for the use of groups. Remove all groups."; |
||||
return false; |
||||
} |
||||
|
||||
var enabledPolicyCount = Policies.Count(p => p.Enabled); |
||||
if (!license.UsePolicies && UsePolicies && enabledPolicyCount > 0) |
||||
{ |
||||
exception = $"Your organization currently has {enabledPolicyCount} enabled " + |
||||
$"policies. Your new license does not allow for the use of policies. Disable all policies."; |
||||
return false; |
||||
} |
||||
|
||||
if (!license.UseSso && UseSso && SsoConfig is { Enabled: true }) |
||||
{ |
||||
exception = $"Your organization currently has a SSO configuration. " + |
||||
$"Your new license does not allow for the use of SSO. Disable your SSO configuration."; |
||||
return false; |
||||
} |
||||
|
||||
if (!license.UseKeyConnector && UseKeyConnector && SsoConfig?.Data != null && |
||||
SsoConfig.GetData().KeyConnectorEnabled) |
||||
{ |
||||
exception = $"Your organization currently has Key Connector enabled. " + |
||||
$"Your new license does not allow for the use of Key Connector. Disable your Key Connector."; |
||||
return false; |
||||
} |
||||
|
||||
if (!license.UseScim && UseScim && ScimConnections != null && |
||||
ScimConnections.Any(c => c.GetConfig<ScimConfig>() is { Enabled: true })) |
||||
{ |
||||
exception = "Your new plan does not allow the SCIM feature. " + |
||||
"Disable your SCIM configuration."; |
||||
return false; |
||||
} |
||||
|
||||
if (!license.UseCustomPermissions && UseCustomPermissions && |
||||
OrganizationUsers.Any(ou => ou.Type == OrganizationUserType.Custom)) |
||||
{ |
||||
exception = "Your new plan does not allow the Custom Permissions feature. " + |
||||
"Disable your Custom Permissions configuration."; |
||||
return false; |
||||
} |
||||
|
||||
if (!license.UseResetPassword && UseResetPassword && |
||||
Policies.Any(p => p.Type == PolicyType.ResetPassword && p.Enabled)) |
||||
{ |
||||
exception = "Your new license does not allow the Password Reset feature. " |
||||
+ "Disable your Password Reset policy."; |
||||
return false; |
||||
} |
||||
|
||||
exception = ""; |
||||
return true; |
||||
} |
||||
|
||||
public Organization ToOrganization() |
||||
{ |
||||
// Any new Organization properties must be added here for them to flow through to self-hosted organizations |
||||
return new Organization |
||||
{ |
||||
Id = Id, |
||||
Identifier = Identifier, |
||||
Name = Name, |
||||
BusinessName = BusinessName, |
||||
BusinessAddress1 = BusinessAddress1, |
||||
BusinessAddress2 = BusinessAddress2, |
||||
BusinessAddress3 = BusinessAddress3, |
||||
BusinessCountry = BusinessCountry, |
||||
BusinessTaxNumber = BusinessTaxNumber, |
||||
BillingEmail = BillingEmail, |
||||
Plan = Plan, |
||||
PlanType = PlanType, |
||||
Seats = Seats, |
||||
MaxCollections = MaxCollections, |
||||
UsePolicies = UsePolicies, |
||||
UseSso = UseSso, |
||||
UseKeyConnector = UseKeyConnector, |
||||
UseScim = UseScim, |
||||
UseGroups = UseGroups, |
||||
UseDirectory = UseDirectory, |
||||
UseEvents = UseEvents, |
||||
UseTotp = UseTotp, |
||||
Use2fa = Use2fa, |
||||
UseApi = UseApi, |
||||
UseResetPassword = UseResetPassword, |
||||
UseSecretsManager = UseSecretsManager, |
||||
SelfHost = SelfHost, |
||||
UsersGetPremium = UsersGetPremium, |
||||
UseCustomPermissions = UseCustomPermissions, |
||||
Storage = Storage, |
||||
MaxStorageGb = MaxStorageGb, |
||||
Gateway = Gateway, |
||||
GatewayCustomerId = GatewayCustomerId, |
||||
GatewaySubscriptionId = GatewaySubscriptionId, |
||||
ReferenceData = ReferenceData, |
||||
Enabled = Enabled, |
||||
LicenseKey = LicenseKey, |
||||
PublicKey = PublicKey, |
||||
PrivateKey = PrivateKey, |
||||
TwoFactorProviders = TwoFactorProviders, |
||||
ExpirationDate = ExpirationDate, |
||||
CreationDate = CreationDate, |
||||
RevisionDate = RevisionDate, |
||||
MaxAutoscaleSeats = MaxAutoscaleSeats, |
||||
OwnersNotifiedOfAutoscaling = OwnersNotifiedOfAutoscaling, |
||||
}; |
||||
} |
||||
} |
||||
@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
|
||||
#nullable enable |
||||
|
||||
using Bit.Core.Entities; |
||||
using Bit.Core.Models.Business; |
||||
using Bit.Core.Models.Data.Organizations; |
||||
|
||||
namespace Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces; |
||||
|
||||
public interface IUpdateOrganizationLicenseCommand |
||||
{ |
||||
Task UpdateLicenseAsync(SelfHostedOrganizationDetails selfHostedOrganization, |
||||
OrganizationLicense license, Organization? currentOrganizationUsingLicenseKey); |
||||
} |
||||
@ -0,0 +1,66 @@
@@ -0,0 +1,66 @@
|
||||
#nullable enable |
||||
|
||||
using System.Text.Json; |
||||
using Bit.Core.Entities; |
||||
using Bit.Core.Exceptions; |
||||
using Bit.Core.Models.Business; |
||||
using Bit.Core.Models.Data.Organizations; |
||||
using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces; |
||||
using Bit.Core.Services; |
||||
using Bit.Core.Settings; |
||||
using Bit.Core.Utilities; |
||||
|
||||
namespace Bit.Core.OrganizationFeatures.OrganizationLicenses; |
||||
|
||||
public class UpdateOrganizationLicenseCommand : IUpdateOrganizationLicenseCommand |
||||
{ |
||||
private readonly ILicensingService _licensingService; |
||||
private readonly IGlobalSettings _globalSettings; |
||||
private readonly IOrganizationService _organizationService; |
||||
|
||||
public UpdateOrganizationLicenseCommand( |
||||
ILicensingService licensingService, |
||||
IGlobalSettings globalSettings, |
||||
IOrganizationService organizationService) |
||||
{ |
||||
_licensingService = licensingService; |
||||
_globalSettings = globalSettings; |
||||
_organizationService = organizationService; |
||||
} |
||||
|
||||
public async Task UpdateLicenseAsync(SelfHostedOrganizationDetails selfHostedOrganization, |
||||
OrganizationLicense license, Organization? currentOrganizationUsingLicenseKey) |
||||
{ |
||||
if (currentOrganizationUsingLicenseKey != null && currentOrganizationUsingLicenseKey.Id != selfHostedOrganization.Id) |
||||
{ |
||||
throw new BadRequestException("License is already in use by another organization."); |
||||
} |
||||
|
||||
var canUse = license.CanUse(_globalSettings, _licensingService, out var exception) && |
||||
selfHostedOrganization.CanUseLicense(license, out exception); |
||||
|
||||
if (!canUse) |
||||
{ |
||||
throw new BadRequestException(exception); |
||||
} |
||||
|
||||
await WriteLicenseFileAsync(selfHostedOrganization, license); |
||||
await UpdateOrganizationAsync(selfHostedOrganization, license); |
||||
} |
||||
|
||||
private async Task WriteLicenseFileAsync(Organization organization, OrganizationLicense license) |
||||
{ |
||||
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); |
||||
} |
||||
|
||||
private async Task UpdateOrganizationAsync(SelfHostedOrganizationDetails selfHostedOrganizationDetails, OrganizationLicense license) |
||||
{ |
||||
var organization = selfHostedOrganizationDetails.ToOrganization(); |
||||
organization.UpdateFromLicense(license); |
||||
|
||||
await _organizationService.ReplaceAndUpdateCacheAsync(organization); |
||||
} |
||||
} |
||||
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
using Bit.Core.Enums; |
||||
using Bit.Infrastructure.EntityFramework.Models; |
||||
|
||||
namespace Bit.Infrastructure.EntityFramework.Repositories.Queries; |
||||
|
||||
public class OrganizationUserReadOccupiedSeatCountByOrganizationIdQuery : IQuery<OrganizationUser> |
||||
{ |
||||
private readonly Guid _organizationId; |
||||
|
||||
public OrganizationUserReadOccupiedSeatCountByOrganizationIdQuery(Guid organizationId) |
||||
{ |
||||
_organizationId = organizationId; |
||||
} |
||||
|
||||
public IQueryable<OrganizationUser> Run(DatabaseContext dbContext) |
||||
{ |
||||
var query = from ou in dbContext.OrganizationUsers |
||||
where ou.OrganizationId == _organizationId && ou.Status >= OrganizationUserStatusType.Invited |
||||
select ou; |
||||
return query; |
||||
} |
||||
} |
||||
@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
|
||||
CREATE PROCEDURE [dbo].[Group_ReadCountByOrganizationId] |
||||
@OrganizationId UNIQUEIDENTIFIER |
||||
AS |
||||
BEGIN |
||||
SET NOCOUNT ON |
||||
|
||||
SELECT |
||||
COUNT(1) |
||||
FROM |
||||
[dbo].[Group] |
||||
WHERE |
||||
[OrganizationId] = @OrganizationId |
||||
END |
||||
@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
|
||||
CREATE PROCEDURE [dbo].[OrganizationUser_ReadOccupiedSeatCountByOrganizationId] |
||||
@OrganizationId UNIQUEIDENTIFIER |
||||
AS |
||||
BEGIN |
||||
SET NOCOUNT ON |
||||
|
||||
SELECT |
||||
COUNT(1) |
||||
FROM |
||||
[dbo].[OrganizationUserView] |
||||
WHERE |
||||
OrganizationId = @OrganizationId |
||||
AND Status >= 0 --Invited |
||||
END |
||||
@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
|
||||
CREATE PROCEDURE [dbo].[Organization_ReadByLicenseKey] |
||||
@LicenseKey VARCHAR (100) |
||||
AS |
||||
BEGIN |
||||
SET NOCOUNT ON |
||||
|
||||
SELECT |
||||
* |
||||
FROM |
||||
[dbo].[OrganizationView] |
||||
WHERE |
||||
[LicenseKey] = @LicenseKey |
||||
END |
||||
@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
|
||||
CREATE PROCEDURE [dbo].[Organization_ReadSelfHostedDetailsById] |
||||
@Id UNIQUEIDENTIFIER |
||||
AS |
||||
BEGIN |
||||
SET NOCOUNT ON |
||||
|
||||
EXEC [dbo].[Organization_ReadById] @Id |
||||
EXEC [dbo].[OrganizationUser_ReadOccupiedSeatCountByOrganizationId] @Id |
||||
EXEC [dbo].[Collection_ReadCountByOrganizationId] @Id |
||||
EXEC [dbo].[Group_ReadCountByOrganizationId] @Id |
||||
EXEC [dbo].[OrganizationUser_ReadByOrganizationId] @Id, NULL |
||||
EXEC [dbo].[Policy_ReadByOrganizationId] @Id |
||||
EXEC [dbo].[SsoConfig_ReadByOrganizationId] @Id |
||||
EXEC [dbo].[OrganizationConnection_ReadByOrganizationIdType] @Id, 2 --Scim connection type |
||||
END |
||||
@ -0,0 +1,379 @@
@@ -0,0 +1,379 @@
|
||||
using Bit.Core.Entities; |
||||
using Bit.Core.Enums; |
||||
using Bit.Core.Models.Business; |
||||
using Bit.Core.Models.Data; |
||||
using Bit.Core.Models.Data.Organizations; |
||||
using Bit.Core.Models.OrganizationConnectionConfigs; |
||||
using Bit.Core.Test.AutoFixture; |
||||
using Bit.Test.Common.AutoFixture.Attributes; |
||||
using Xunit; |
||||
|
||||
namespace Bit.Core.Test.Models.Data; |
||||
|
||||
public class SelfHostedOrganizationDetailsTests |
||||
{ |
||||
[Theory] |
||||
[BitAutoData] |
||||
[OrganizationLicenseCustomize] |
||||
public async Task ValidateForOrganization_Success(List<OrganizationUser> orgUsers, |
||||
List<Policy> policies, SsoConfig ssoConfig, List<OrganizationConnection<ScimConfig>> scimConnections, OrganizationLicense license) |
||||
{ |
||||
var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); |
||||
|
||||
var result = orgDetails.CanUseLicense(license, out var exception); |
||||
|
||||
Assert.True(result); |
||||
Assert.True(string.IsNullOrEmpty(exception)); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData] |
||||
[OrganizationLicenseCustomize] |
||||
public async Task ValidateForOrganization_OccupiedSeatCount_ExceedsLicense_Fail(List<OrganizationUser> orgUsers, |
||||
List<Policy> policies, SsoConfig ssoConfig, List<OrganizationConnection<ScimConfig>> scimConnections, OrganizationLicense license) |
||||
{ |
||||
var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); |
||||
orgLicense.Seats = 1; |
||||
|
||||
var result = orgDetails.CanUseLicense(license, out var exception); |
||||
|
||||
Assert.False(result); |
||||
Assert.Contains("Remove some users", exception); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData] |
||||
[OrganizationLicenseCustomize] |
||||
public async Task ValidateForOrganization_MaxCollections_ExceedsLicense_Fail(List<OrganizationUser> orgUsers, |
||||
List<Policy> policies, SsoConfig ssoConfig, List<OrganizationConnection<ScimConfig>> scimConnections, OrganizationLicense license) |
||||
{ |
||||
var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); |
||||
orgLicense.MaxCollections = 1; |
||||
|
||||
var result = orgDetails.CanUseLicense(license, out var exception); |
||||
|
||||
Assert.False(result); |
||||
Assert.Contains("Remove some collections", exception); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData] |
||||
[OrganizationLicenseCustomize] |
||||
public async Task ValidateForOrganization_Groups_NotAllowedByLicense_Fail(List<OrganizationUser> orgUsers, |
||||
List<Policy> policies, SsoConfig ssoConfig, List<OrganizationConnection<ScimConfig>> scimConnections, OrganizationLicense license) |
||||
{ |
||||
var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); |
||||
orgLicense.UseGroups = false; |
||||
|
||||
var result = orgDetails.CanUseLicense(license, out var exception); |
||||
|
||||
Assert.False(result); |
||||
Assert.Contains("Your new license does not allow for the use of groups", exception); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData] |
||||
[OrganizationLicenseCustomize] |
||||
public async Task ValidateForOrganization_Policies_NotAllowedByLicense_Fail(List<OrganizationUser> orgUsers, |
||||
List<Policy> policies, SsoConfig ssoConfig, List<OrganizationConnection<ScimConfig>> scimConnections, OrganizationLicense license) |
||||
{ |
||||
var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); |
||||
orgLicense.UsePolicies = false; |
||||
|
||||
var result = orgDetails.CanUseLicense(license, out var exception); |
||||
|
||||
Assert.False(result); |
||||
Assert.Contains("Your new license does not allow for the use of policies", exception); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData] |
||||
[OrganizationLicenseCustomize] |
||||
public async Task ValidateForOrganization_DisabledPolicies_NotAllowedByLicense_Success(List<OrganizationUser> orgUsers, |
||||
List<Policy> policies, SsoConfig ssoConfig, List<OrganizationConnection<ScimConfig>> scimConnections, OrganizationLicense license) |
||||
{ |
||||
var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); |
||||
orgLicense.UsePolicies = false; |
||||
((List<Policy>)orgDetails.Policies).ForEach(p => p.Enabled = false); |
||||
|
||||
var result = orgDetails.CanUseLicense(license, out var exception); |
||||
|
||||
Assert.True(result); |
||||
Assert.True(string.IsNullOrEmpty(exception)); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData] |
||||
[OrganizationLicenseCustomize] |
||||
public async Task ValidateForOrganization_Sso_NotAllowedByLicense_Fail(List<OrganizationUser> orgUsers, |
||||
List<Policy> policies, SsoConfig ssoConfig, List<OrganizationConnection<ScimConfig>> scimConnections, OrganizationLicense license) |
||||
{ |
||||
var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); |
||||
orgLicense.UseSso = false; |
||||
|
||||
var result = orgDetails.CanUseLicense(license, out var exception); |
||||
|
||||
Assert.False(result); |
||||
Assert.Contains("Your new license does not allow for the use of SSO", exception); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData] |
||||
[OrganizationLicenseCustomize] |
||||
public async Task ValidateForOrganization_DisabledSso_NotAllowedByLicense_Success(List<OrganizationUser> orgUsers, |
||||
List<Policy> policies, SsoConfig ssoConfig, List<OrganizationConnection<ScimConfig>> scimConnections, OrganizationLicense license) |
||||
{ |
||||
var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); |
||||
orgLicense.UseSso = false; |
||||
orgDetails.SsoConfig.Enabled = false; |
||||
|
||||
var result = orgDetails.CanUseLicense(license, out var exception); |
||||
|
||||
Assert.True(result); |
||||
Assert.True(string.IsNullOrEmpty(exception)); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData] |
||||
[OrganizationLicenseCustomize] |
||||
public async Task ValidateForOrganization_NoSso_NotAllowedByLicense_Success(List<OrganizationUser> orgUsers, |
||||
List<Policy> policies, SsoConfig ssoConfig, List<OrganizationConnection<ScimConfig>> scimConnections, OrganizationLicense license) |
||||
{ |
||||
var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); |
||||
orgLicense.UseSso = false; |
||||
orgDetails.SsoConfig = null; |
||||
|
||||
var result = orgDetails.CanUseLicense(license, out var exception); |
||||
|
||||
Assert.True(result); |
||||
Assert.True(string.IsNullOrEmpty(exception)); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData] |
||||
[OrganizationLicenseCustomize] |
||||
public async Task ValidateForOrganization_KeyConnector_NotAllowedByLicense_Fail(List<OrganizationUser> orgUsers, |
||||
List<Policy> policies, SsoConfig ssoConfig, List<OrganizationConnection<ScimConfig>> scimConnections, OrganizationLicense license) |
||||
{ |
||||
var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); |
||||
orgLicense.UseKeyConnector = false; |
||||
|
||||
var result = orgDetails.CanUseLicense(license, out var exception); |
||||
|
||||
Assert.False(result); |
||||
Assert.Contains("Your new license does not allow for the use of Key Connector", exception); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData] |
||||
[OrganizationLicenseCustomize] |
||||
public async Task ValidateForOrganization_DisabledKeyConnector_NotAllowedByLicense_Success(List<OrganizationUser> orgUsers, |
||||
List<Policy> policies, SsoConfig ssoConfig, List<OrganizationConnection<ScimConfig>> scimConnections, OrganizationLicense license) |
||||
{ |
||||
var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); |
||||
orgLicense.UseKeyConnector = false; |
||||
orgDetails.SsoConfig.SetData(new SsoConfigurationData() { KeyConnectorEnabled = false }); |
||||
|
||||
var result = orgDetails.CanUseLicense(license, out var exception); |
||||
|
||||
Assert.True(result); |
||||
Assert.True(string.IsNullOrEmpty(exception)); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData] |
||||
[OrganizationLicenseCustomize] |
||||
public async Task ValidateForOrganization_NoSsoKeyConnector_NotAllowedByLicense_Success(List<OrganizationUser> orgUsers, |
||||
List<Policy> policies, SsoConfig ssoConfig, List<OrganizationConnection<ScimConfig>> scimConnections, OrganizationLicense license) |
||||
{ |
||||
var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); |
||||
orgLicense.UseKeyConnector = false; |
||||
orgDetails.SsoConfig = null; |
||||
|
||||
var result = orgDetails.CanUseLicense(license, out var exception); |
||||
|
||||
Assert.True(result); |
||||
Assert.True(string.IsNullOrEmpty(exception)); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData] |
||||
[OrganizationLicenseCustomize] |
||||
public async Task ValidateForOrganization_Scim_NotAllowedByLicense_Fail(List<OrganizationUser> orgUsers, |
||||
List<Policy> policies, SsoConfig ssoConfig, List<OrganizationConnection<ScimConfig>> scimConnections, OrganizationLicense license) |
||||
{ |
||||
var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); |
||||
orgLicense.UseScim = false; |
||||
|
||||
var result = orgDetails.CanUseLicense(license, out var exception); |
||||
|
||||
Assert.False(result); |
||||
Assert.Contains("Your new plan does not allow the SCIM feature", exception); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData] |
||||
[OrganizationLicenseCustomize] |
||||
public async Task ValidateForOrganization_DisabledScim_NotAllowedByLicense_Success(List<OrganizationUser> orgUsers, |
||||
List<Policy> policies, SsoConfig ssoConfig, List<OrganizationConnection<ScimConfig>> scimConnections, OrganizationLicense license) |
||||
{ |
||||
var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); |
||||
orgLicense.UseScim = false; |
||||
((List<OrganizationConnection<ScimConfig>>)orgDetails.ScimConnections) |
||||
.ForEach(c => c.SetConfig(new ScimConfig() { Enabled = false })); |
||||
|
||||
var result = orgDetails.CanUseLicense(license, out var exception); |
||||
|
||||
Assert.True(result); |
||||
Assert.True(string.IsNullOrEmpty(exception)); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData] |
||||
[OrganizationLicenseCustomize] |
||||
public async Task ValidateForOrganization_NoScimConfig_NotAllowedByLicense_Success(List<OrganizationUser> orgUsers, |
||||
List<Policy> policies, SsoConfig ssoConfig, List<OrganizationConnection<ScimConfig>> scimConnections, OrganizationLicense license) |
||||
{ |
||||
var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); |
||||
orgLicense.UseScim = false; |
||||
orgDetails.ScimConnections = null; |
||||
|
||||
var result = orgDetails.CanUseLicense(license, out var exception); |
||||
|
||||
Assert.True(result); |
||||
Assert.True(string.IsNullOrEmpty(exception)); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData] |
||||
[OrganizationLicenseCustomize] |
||||
public async Task ValidateForOrganization_CustomPermissions_NotAllowedByLicense_Fail(List<OrganizationUser> orgUsers, |
||||
List<Policy> policies, SsoConfig ssoConfig, List<OrganizationConnection<ScimConfig>> scimConnections, OrganizationLicense license) |
||||
{ |
||||
var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); |
||||
orgLicense.UseCustomPermissions = false; |
||||
|
||||
var result = orgDetails.CanUseLicense(license, out var exception); |
||||
|
||||
Assert.False(result); |
||||
Assert.Contains("Your new plan does not allow the Custom Permissions feature", exception); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData] |
||||
[OrganizationLicenseCustomize] |
||||
public async Task ValidateForOrganization_NoCustomPermissions_NotAllowedByLicense_Success(List<OrganizationUser> orgUsers, |
||||
List<Policy> policies, SsoConfig ssoConfig, List<OrganizationConnection<ScimConfig>> scimConnections, OrganizationLicense license) |
||||
{ |
||||
var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); |
||||
orgLicense.UseCustomPermissions = false; |
||||
((List<OrganizationUser>)orgDetails.OrganizationUsers).ForEach(ou => ou.Type = OrganizationUserType.User); |
||||
|
||||
var result = orgDetails.CanUseLicense(license, out var exception); |
||||
|
||||
Assert.True(result); |
||||
Assert.True(string.IsNullOrEmpty(exception)); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData] |
||||
[OrganizationLicenseCustomize] |
||||
public async Task ValidateForOrganization_ResetPassword_NotAllowedByLicense_Fail(List<OrganizationUser> orgUsers, |
||||
List<Policy> policies, SsoConfig ssoConfig, List<OrganizationConnection<ScimConfig>> scimConnections, OrganizationLicense license) |
||||
{ |
||||
var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); |
||||
orgLicense.UseResetPassword = false; |
||||
|
||||
var result = orgDetails.CanUseLicense(license, out var exception); |
||||
|
||||
Assert.False(result); |
||||
Assert.Contains("Your new license does not allow the Password Reset feature", exception); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData] |
||||
[OrganizationLicenseCustomize] |
||||
public async Task ValidateForOrganization_DisabledResetPassword_NotAllowedByLicense_Success(List<OrganizationUser> orgUsers, |
||||
List<Policy> policies, SsoConfig ssoConfig, List<OrganizationConnection<ScimConfig>> scimConnections, OrganizationLicense license) |
||||
{ |
||||
var (orgDetails, orgLicense) = GetOrganizationAndLicense(orgUsers, policies, ssoConfig, scimConnections, license); |
||||
orgLicense.UseResetPassword = false; |
||||
((List<Policy>)orgDetails.Policies).ForEach(p => p.Enabled = false); |
||||
|
||||
var result = orgDetails.CanUseLicense(license, out var exception); |
||||
|
||||
Assert.True(result); |
||||
Assert.True(string.IsNullOrEmpty(exception)); |
||||
} |
||||
|
||||
private (SelfHostedOrganizationDetails organization, OrganizationLicense license) GetOrganizationAndLicense(List<OrganizationUser> orgUsers, |
||||
List<Policy> policies, SsoConfig ssoConfig, List<OrganizationConnection<ScimConfig>> scimConnections, OrganizationLicense license) |
||||
{ |
||||
// The default state is that all features are used by Org and allowed by License |
||||
// Each test then toggles on/off as necessary |
||||
policies.ForEach(p => p.Enabled = true); |
||||
policies.First().Type = PolicyType.ResetPassword; |
||||
|
||||
ssoConfig.Enabled = true; |
||||
ssoConfig.SetData(new SsoConfigurationData() |
||||
{ |
||||
KeyConnectorEnabled = true |
||||
}); |
||||
|
||||
var enabledScimConfig = new ScimConfig() { Enabled = true }; |
||||
scimConnections.ForEach(c => c.Config = enabledScimConfig); |
||||
|
||||
orgUsers.First().Type = OrganizationUserType.Custom; |
||||
|
||||
var organization = new SelfHostedOrganizationDetails() |
||||
{ |
||||
OccupiedSeatCount = 10, |
||||
CollectionCount = 5, |
||||
GroupCount = 5, |
||||
OrganizationUsers = orgUsers, |
||||
Policies = policies, |
||||
SsoConfig = ssoConfig, |
||||
ScimConnections = scimConnections, |
||||
|
||||
UsePolicies = true, |
||||
UseSso = true, |
||||
UseKeyConnector = true, |
||||
UseScim = true, |
||||
UseGroups = true, |
||||
UseDirectory = true, |
||||
UseEvents = true, |
||||
UseTotp = true, |
||||
Use2fa = true, |
||||
UseApi = true, |
||||
UseResetPassword = true, |
||||
SelfHost = true, |
||||
UsersGetPremium = true, |
||||
UseCustomPermissions = true, |
||||
}; |
||||
|
||||
license.Enabled = true; |
||||
license.PlanType = PlanType.EnterpriseAnnually; |
||||
license.Seats = 10; |
||||
license.MaxCollections = 5; |
||||
license.UsePolicies = true; |
||||
license.UseSso = true; |
||||
license.UseKeyConnector = true; |
||||
license.UseScim = true; |
||||
license.UseGroups = true; |
||||
license.UseEvents = true; |
||||
license.UseDirectory = true; |
||||
license.UseTotp = true; |
||||
license.Use2fa = true; |
||||
license.UseApi = true; |
||||
license.UseResetPassword = true; |
||||
license.MaxStorageGb = 1; |
||||
license.SelfHost = true; |
||||
license.UsersGetPremium = true; |
||||
license.UseCustomPermissions = true; |
||||
license.Version = 11; |
||||
license.Issued = DateTime.Now; |
||||
license.Expires = DateTime.Now.AddYears(1); |
||||
|
||||
return (organization, license); |
||||
} |
||||
} |
||||
@ -0,0 +1,62 @@
@@ -0,0 +1,62 @@
|
||||
CREATE OR ALTER PROCEDURE [dbo].[Group_ReadCountByOrganizationId] |
||||
@OrganizationId UNIQUEIDENTIFIER |
||||
AS |
||||
BEGIN |
||||
SET NOCOUNT ON |
||||
|
||||
SELECT |
||||
COUNT(1) |
||||
FROM |
||||
[dbo].[Group] |
||||
WHERE |
||||
[OrganizationId] = @OrganizationId |
||||
END |
||||
GO |
||||
|
||||
CREATE OR ALTER PROCEDURE [dbo].[OrganizationUser_ReadOccupiedSeatCountByOrganizationId] |
||||
@OrganizationId UNIQUEIDENTIFIER |
||||
AS |
||||
BEGIN |
||||
SET NOCOUNT ON |
||||
|
||||
SELECT |
||||
COUNT(1) |
||||
FROM |
||||
[dbo].[OrganizationUserView] |
||||
WHERE |
||||
OrganizationId = @OrganizationId |
||||
AND Status >= 0 --Invited |
||||
END |
||||
GO |
||||
|
||||
CREATE OR ALTER PROCEDURE [dbo].[Organization_ReadByLicenseKey] |
||||
@LicenseKey VARCHAR (100) |
||||
AS |
||||
BEGIN |
||||
SET NOCOUNT ON |
||||
|
||||
SELECT |
||||
* |
||||
FROM |
||||
[dbo].[OrganizationView] |
||||
WHERE |
||||
[LicenseKey] = @LicenseKey |
||||
END |
||||
GO |
||||
|
||||
CREATE OR ALTER PROCEDURE [dbo].[Organization_ReadSelfHostedDetailsById] |
||||
@Id UNIQUEIDENTIFIER |
||||
AS |
||||
BEGIN |
||||
SET NOCOUNT ON |
||||
|
||||
EXEC [dbo].[Organization_ReadById] @Id |
||||
EXEC [dbo].[OrganizationUser_ReadOccupiedSeatCountByOrganizationId] @Id |
||||
EXEC [dbo].[Collection_ReadCountByOrganizationId] @Id |
||||
EXEC [dbo].[Group_ReadCountByOrganizationId] @Id |
||||
EXEC [dbo].[OrganizationUser_ReadByOrganizationId] @Id, NULL |
||||
EXEC [dbo].[Policy_ReadByOrganizationId] @Id |
||||
EXEC [dbo].[SsoConfig_ReadByOrganizationId] @Id |
||||
EXEC [dbo].[OrganizationConnection_ReadByOrganizationIdType] @Id, 2 --Scim connection type |
||||
END |
||||
GO |
||||
Loading…
Reference in new issue