From 82908b1fb74c29a757319fa72b01b1a02a9b3a5d Mon Sep 17 00:00:00 2001
From: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
Date: Tue, 31 Jan 2023 07:42:10 +1000
Subject: [PATCH] [EC-826] Merge license sync feature branch to master (#2587)
* [EC-634] Extract GenerateLicenseAsync to a query (#2373)
* [EC-637] Add license sync to server (#2453)
* [EC-1036] Show correct license sync date (#2626)
* Update method name per new pattern
---
src/Admin/Controllers/ToolsController.cs | 9 +-
src/Api/Controllers/LicensesController.cs | 36 ++++--
.../OrganizationConnectionsController.cs | 2 +-
.../OrganizationSponsorshipsController.cs | 1 +
.../Controllers/OrganizationsController.cs | 9 +-
...elfHostedOrganizationLicensesController.cs | 117 ++++++++++++++++++
.../OrganizationConnectionRequestModel.cs | 3 +-
src/Core/Entities/OrganizationConnection.cs | 33 ++++-
...lfHostedOrganizationLicenseRequestModel.cs | 7 ++
.../OrganizationConnectionData.cs | 3 +-
.../BillingSyncConfig.cs | 15 ++-
.../IConnectionConfig.cs | 6 +
.../ScimConfig.cs | 14 ++-
.../CreateOrganizationConnectionCommand.cs | 3 +-
.../ICreateOrganizationConnectionCommand.cs | 3 +-
.../IUpdateOrganizationConnectionCommand.cs | 3 +-
.../IValidateBillingSyncKeyCommand.cs | 2 +-
.../UpdateOrganizationConnectionCommand.cs | 3 +-
.../ValidateBillingSyncKeyCommand.cs | 7 +-
.../Cloud/CloudGetOrganizationLicenseQuery.cs | 38 ++++++
.../IGetOrganizationLicenseQuery.cs | 15 +++
.../SelfHostedGetOrganizationLicenseQuery.cs | 66 ++++++++++
...OrganizationServiceCollectionExtensions.cs | 9 ++
.../SelfHostedSyncSponsorshipsCommand.cs | 15 +--
src/Core/Services/IOrganizationService.cs | 3 -
.../Implementations/OrganizationService.cs | 24 ----
.../OrganizationConnectionsControllerTests.cs | 2 +-
.../OrganizationsControllerTests.cs | 5 +-
.../AutoFixture/SutProviderExtensions.cs | 50 ++++++++
.../SubscriptionInfoCustomization.cs | 18 +++
.../Entities/OrganizationConnectionTests.cs | 78 ++++++++++++
.../BillingSyncConfigTests.cs | 27 ++++
.../ScimConfigTests.cs | 23 ++++
.../ValidateBillingSyncKeyCommandTests.cs | 4 +-
.../CloudGetOrganizationLicenseQueryTests.cs | 64 ++++++++++
...fHostedGetOrganizationLicenseQueryTests.cs | 94 ++++++++++++++
.../SelfHostedSyncSponsorshipsCommandTests.cs | 58 ++-------
37 files changed, 746 insertions(+), 123 deletions(-)
create mode 100644 src/Api/Controllers/SelfHosted/SelfHostedOrganizationLicensesController.cs
create mode 100644 src/Core/Models/Api/Request/OrganizationLicenses/SelfHostedOrganizationLicenseRequestModel.cs
create mode 100644 src/Core/Models/OrganizationConnectionConfigs/IConnectionConfig.cs
rename src/Core/OrganizationFeatures/{OrganizationSponsorships/FamiliesForEnterprise => OrganizationConnections}/Interfaces/IValidateBillingSyncKeyCommand.cs (64%)
rename src/Core/OrganizationFeatures/{OrganizationSponsorships/FamiliesForEnterprise/Cloud => OrganizationConnections}/ValidateBillingSyncKeyCommand.cs (70%)
create mode 100644 src/Core/OrganizationFeatures/OrganizationLicenses/Cloud/CloudGetOrganizationLicenseQuery.cs
create mode 100644 src/Core/OrganizationFeatures/OrganizationLicenses/Interfaces/IGetOrganizationLicenseQuery.cs
create mode 100644 src/Core/OrganizationFeatures/OrganizationLicenses/SelfHosted/SelfHostedGetOrganizationLicenseQuery.cs
create mode 100644 test/Common/AutoFixture/SutProviderExtensions.cs
create mode 100644 test/Core.Test/AutoFixture/SubscriptionInfoCustomization.cs
create mode 100644 test/Core.Test/Entities/OrganizationConnectionTests.cs
create mode 100644 test/Core.Test/Models/OrganizationConnectionConfigs/BillingSyncConfigTests.cs
create mode 100644 test/Core.Test/Models/OrganizationConnectionConfigs/ScimConfigTests.cs
rename test/Core.Test/OrganizationFeatures/{OrganizationSponsorships/FamiliesForEnterprise/Cloud => OrganizationConnections}/ValidateBillingSyncKeyCommandTests.cs (91%)
create mode 100644 test/Core.Test/OrganizationFeatures/OrganizationLicenses/CloudGetOrganizationLicenseQueryTests.cs
create mode 100644 test/Core.Test/OrganizationFeatures/OrganizationLicenses/SelfHostedGetOrganizationLicenseQueryTests.cs
diff --git a/src/Admin/Controllers/ToolsController.cs b/src/Admin/Controllers/ToolsController.cs
index 9bd6189b3e..b18864d371 100644
--- a/src/Admin/Controllers/ToolsController.cs
+++ b/src/Admin/Controllers/ToolsController.cs
@@ -3,6 +3,7 @@ using System.Text.Json;
using Bit.Admin.Models;
using Bit.Core.Entities;
using Bit.Core.Models.BitStripe;
+using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
@@ -18,7 +19,7 @@ public class ToolsController : Controller
{
private readonly GlobalSettings _globalSettings;
private readonly IOrganizationRepository _organizationRepository;
- private readonly IOrganizationService _organizationService;
+ private readonly ICloudGetOrganizationLicenseQuery _cloudGetOrganizationLicenseQuery;
private readonly IUserService _userService;
private readonly ITransactionRepository _transactionRepository;
private readonly IInstallationRepository _installationRepository;
@@ -30,7 +31,7 @@ public class ToolsController : Controller
public ToolsController(
GlobalSettings globalSettings,
IOrganizationRepository organizationRepository,
- IOrganizationService organizationService,
+ ICloudGetOrganizationLicenseQuery cloudGetOrganizationLicenseQuery,
IUserService userService,
ITransactionRepository transactionRepository,
IInstallationRepository installationRepository,
@@ -41,7 +42,7 @@ public class ToolsController : Controller
{
_globalSettings = globalSettings;
_organizationRepository = organizationRepository;
- _organizationService = organizationService;
+ _cloudGetOrganizationLicenseQuery = cloudGetOrganizationLicenseQuery;
_userService = userService;
_transactionRepository = transactionRepository;
_installationRepository = installationRepository;
@@ -259,7 +260,7 @@ public class ToolsController : Controller
if (organization != null)
{
- var license = await _organizationService.GenerateLicenseAsync(organization,
+ var license = await _cloudGetOrganizationLicenseQuery.GetLicenseAsync(organization,
model.InstallationId.Value, model.Version);
var ms = new MemoryStream();
await JsonSerializer.SerializeAsync(ms, license, JsonHelpers.Indented);
diff --git a/src/Api/Controllers/LicensesController.cs b/src/Api/Controllers/LicensesController.cs
index 63ed824795..f5391fddc2 100644
--- a/src/Api/Controllers/LicensesController.cs
+++ b/src/Api/Controllers/LicensesController.cs
@@ -1,6 +1,9 @@
using Bit.Core.Context;
using Bit.Core.Exceptions;
+using Bit.Core.Models.Api.OrganizationLicenses;
using Bit.Core.Models.Business;
+using Bit.Core.OrganizationFeatures.OrganizationConnections.Interfaces;
+using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Utilities;
@@ -14,26 +17,26 @@ namespace Bit.Api.Controllers;
[SelfHosted(NotSelfHostedOnly = true)]
public class LicensesController : Controller
{
- private readonly ILicensingService _licensingService;
private readonly IUserRepository _userRepository;
private readonly IUserService _userService;
private readonly IOrganizationRepository _organizationRepository;
- private readonly IOrganizationService _organizationService;
+ private readonly ICloudGetOrganizationLicenseQuery _cloudGetOrganizationLicenseQuery;
+ private readonly IValidateBillingSyncKeyCommand _validateBillingSyncKeyCommand;
private readonly ICurrentContext _currentContext;
public LicensesController(
- ILicensingService licensingService,
IUserRepository userRepository,
IUserService userService,
IOrganizationRepository organizationRepository,
- IOrganizationService organizationService,
+ ICloudGetOrganizationLicenseQuery cloudGetOrganizationLicenseQuery,
+ IValidateBillingSyncKeyCommand validateBillingSyncKeyCommand,
ICurrentContext currentContext)
{
- _licensingService = licensingService;
_userRepository = userRepository;
_userService = userService;
_organizationRepository = organizationRepository;
- _organizationService = organizationService;
+ _cloudGetOrganizationLicenseQuery = cloudGetOrganizationLicenseQuery;
+ _validateBillingSyncKeyCommand = validateBillingSyncKeyCommand;
_currentContext = currentContext;
}
@@ -55,21 +58,30 @@ public class LicensesController : Controller
return license;
}
+ ///
+ /// Used by self-hosted installations to get an updated license file
+ ///
[HttpGet("organization/{id}")]
- public async Task GetOrganization(string id, [FromQuery] string key)
+ public async Task OrganizationSync(string id, [FromBody] SelfHostedOrganizationLicenseRequestModel model)
{
- var org = await _organizationRepository.GetByIdAsync(new Guid(id));
- if (org == null)
+ var organization = await _organizationRepository.GetByIdAsync(new Guid(id));
+ if (organization == null)
{
- return null;
+ throw new NotFoundException("Organization not found.");
}
- else if (!org.LicenseKey.Equals(key))
+
+ if (!organization.LicenseKey.Equals(model.LicenseKey))
{
await Task.Delay(2000);
throw new BadRequestException("Invalid license key.");
}
- var license = await _organizationService.GenerateLicenseAsync(org, _currentContext.InstallationId.Value);
+ if (!await _validateBillingSyncKeyCommand.ValidateBillingSyncKeyAsync(organization, model.BillingSyncKey))
+ {
+ throw new BadRequestException("Invalid Billing Sync Key");
+ }
+
+ var license = await _cloudGetOrganizationLicenseQuery.GetLicenseAsync(organization, _currentContext.InstallationId.Value);
return license;
}
}
diff --git a/src/Api/Controllers/OrganizationConnectionsController.cs b/src/Api/Controllers/OrganizationConnectionsController.cs
index 13260b7cca..b7329a14fa 100644
--- a/src/Api/Controllers/OrganizationConnectionsController.cs
+++ b/src/Api/Controllers/OrganizationConnectionsController.cs
@@ -191,7 +191,7 @@ public class OrganizationConnectionsController : Controller
Guid? organizationConnectionId,
OrganizationConnectionRequestModel model,
Func, Task> validateAction = null)
- where T : new()
+ where T : IConnectionConfig
{
var typedModel = new OrganizationConnectionRequestModel(model);
if (validateAction != null)
diff --git a/src/Api/Controllers/OrganizationSponsorshipsController.cs b/src/Api/Controllers/OrganizationSponsorshipsController.cs
index fc5d38db1c..d9715d07c3 100644
--- a/src/Api/Controllers/OrganizationSponsorshipsController.cs
+++ b/src/Api/Controllers/OrganizationSponsorshipsController.cs
@@ -5,6 +5,7 @@ using Bit.Core.Entities;
using Bit.Core.Exceptions;
using Bit.Core.Models.Api.Request.OrganizationSponsorships;
using Bit.Core.Models.Api.Response.OrganizationSponsorships;
+using Bit.Core.OrganizationFeatures.OrganizationConnections.Interfaces;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.Services;
diff --git a/src/Api/Controllers/OrganizationsController.cs b/src/Api/Controllers/OrganizationsController.cs
index bb7c3a63a2..baf0dbfaaf 100644
--- a/src/Api/Controllers/OrganizationsController.cs
+++ b/src/Api/Controllers/OrganizationsController.cs
@@ -11,6 +11,7 @@ using Bit.Core.Exceptions;
using Bit.Core.Models.Business;
using Bit.Core.Models.Data.Organizations.Policies;
using Bit.Core.OrganizationFeatures.OrganizationApiKeys.Interfaces;
+using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
@@ -37,6 +38,7 @@ public class OrganizationsController : Controller
private readonly IRotateOrganizationApiKeyCommand _rotateOrganizationApiKeyCommand;
private readonly ICreateOrganizationApiKeyCommand _createOrganizationApiKeyCommand;
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
+ private readonly ICloudGetOrganizationLicenseQuery _cloudGetOrganizationLicenseQuery;
private readonly GlobalSettings _globalSettings;
public OrganizationsController(
@@ -53,6 +55,7 @@ public class OrganizationsController : Controller
IRotateOrganizationApiKeyCommand rotateOrganizationApiKeyCommand,
ICreateOrganizationApiKeyCommand createOrganizationApiKeyCommand,
IOrganizationApiKeyRepository organizationApiKeyRepository,
+ ICloudGetOrganizationLicenseQuery cloudGetOrganizationLicenseQuery,
GlobalSettings globalSettings)
{
_organizationRepository = organizationRepository;
@@ -68,6 +71,7 @@ public class OrganizationsController : Controller
_rotateOrganizationApiKeyCommand = rotateOrganizationApiKeyCommand;
_createOrganizationApiKeyCommand = createOrganizationApiKeyCommand;
_organizationApiKeyRepository = organizationApiKeyRepository;
+ _cloudGetOrganizationLicenseQuery = cloudGetOrganizationLicenseQuery;
_globalSettings = globalSettings;
}
@@ -149,7 +153,8 @@ public class OrganizationsController : Controller
throw new NotFoundException();
}
- var license = await _organizationService.GenerateLicenseAsync(orgIdGuid, installationId);
+ var org = await _organizationRepository.GetByIdAsync(new Guid(id));
+ var license = await _cloudGetOrganizationLicenseQuery.GetLicenseAsync(org, installationId);
if (license == null)
{
throw new NotFoundException();
@@ -215,6 +220,7 @@ public class OrganizationsController : Controller
return new OrganizationResponseModel(result.Item1);
}
+ [Obsolete("2022-12-7 Moved to SelfHostedOrganizationLicensesController, to be removed in EC-815")]
[HttpPost("license")]
[SelfHosted(SelfHostedOnly = true)]
public async Task PostLicense(OrganizationCreateLicenseRequestModel model)
@@ -448,6 +454,7 @@ public class OrganizationsController : Controller
}
}
+ [Obsolete("2022-12-7 Moved to SelfHostedOrganizationLicensesController, to be removed in EC-815")]
[HttpPost("{id}/license")]
[SelfHosted(SelfHostedOnly = true)]
public async Task PostLicense(string id, LicenseRequestModel model)
diff --git a/src/Api/Controllers/SelfHosted/SelfHostedOrganizationLicensesController.cs b/src/Api/Controllers/SelfHosted/SelfHostedOrganizationLicensesController.cs
new file mode 100644
index 0000000000..92cf50ae41
--- /dev/null
+++ b/src/Api/Controllers/SelfHosted/SelfHostedOrganizationLicensesController.cs
@@ -0,0 +1,117 @@
+using Bit.Api.Models.Request;
+using Bit.Api.Models.Request.Organizations;
+using Bit.Api.Models.Response.Organizations;
+using Bit.Api.Utilities;
+using Bit.Core.Context;
+using Bit.Core.Enums;
+using Bit.Core.Exceptions;
+using Bit.Core.Models.Business;
+using Bit.Core.Models.OrganizationConnectionConfigs;
+using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
+using Bit.Core.Repositories;
+using Bit.Core.Services;
+using Bit.Core.Utilities;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Bit.Api.Controllers.SelfHosted;
+
+[Route("organizations/licenses/self-hosted")]
+[Authorize("Application")]
+[SelfHosted(SelfHostedOnly = true)]
+public class SelfHostedOrganizationLicensesController : Controller
+{
+ private readonly ICurrentContext _currentContext;
+ private readonly ISelfHostedGetOrganizationLicenseQuery _selfHostedGetOrganizationLicenseQuery;
+ private readonly IOrganizationConnectionRepository _organizationConnectionRepository;
+ private readonly IOrganizationService _organizationService;
+ private readonly IOrganizationRepository _organizationRepository;
+ private readonly IUserService _userService;
+
+ public SelfHostedOrganizationLicensesController(
+ ICurrentContext currentContext,
+ ISelfHostedGetOrganizationLicenseQuery selfHostedGetOrganizationLicenseQuery,
+ IOrganizationConnectionRepository organizationConnectionRepository,
+ IOrganizationService organizationService,
+ IOrganizationRepository organizationRepository,
+ IUserService userService)
+ {
+ _currentContext = currentContext;
+ _selfHostedGetOrganizationLicenseQuery = selfHostedGetOrganizationLicenseQuery;
+ _organizationConnectionRepository = organizationConnectionRepository;
+ _organizationService = organizationService;
+ _organizationRepository = organizationRepository;
+ _userService = userService;
+ }
+
+ [HttpPost("")]
+ public async Task PostLicenseAsync(OrganizationCreateLicenseRequestModel model)
+ {
+ var user = await _userService.GetUserByPrincipalAsync(User);
+ if (user == null)
+ {
+ throw new UnauthorizedAccessException();
+ }
+
+ var license = await ApiHelpers.ReadJsonFileFromBody(HttpContext, model.License);
+ if (license == null)
+ {
+ throw new BadRequestException("Invalid license");
+ }
+
+ var result = await _organizationService.SignUpAsync(license, user, model.Key,
+ model.CollectionName, model.Keys?.PublicKey, model.Keys?.EncryptedPrivateKey);
+ return new OrganizationResponseModel(result.Item1);
+ }
+
+ [HttpPost("{id}")]
+ public async Task PostLicenseAsync(string id, LicenseRequestModel model)
+ {
+ var orgIdGuid = new Guid(id);
+ if (!await _currentContext.OrganizationOwner(orgIdGuid))
+ {
+ throw new NotFoundException();
+ }
+
+ var license = await ApiHelpers.ReadJsonFileFromBody(HttpContext, model.License);
+ if (license == null)
+ {
+ throw new BadRequestException("Invalid license");
+ }
+
+ await _organizationService.UpdateLicenseAsync(new Guid(id), license);
+ }
+
+ [HttpPost("{id}/sync")]
+ public async Task SyncLicenseAsync(string id)
+ {
+ var organization = await _organizationRepository.GetByIdAsync(new Guid(id));
+ if (organization == null)
+ {
+ throw new NotFoundException();
+ }
+
+ if (!await _currentContext.OrganizationOwner(organization.Id))
+ {
+ throw new NotFoundException();
+ }
+
+ var billingSyncConnection =
+ (await _organizationConnectionRepository.GetByOrganizationIdTypeAsync(organization.Id,
+ OrganizationConnectionType.CloudBillingSync)).FirstOrDefault();
+ if (billingSyncConnection == null)
+ {
+ throw new NotFoundException("Unable to get Cloud Billing Sync connection");
+ }
+
+ var license =
+ await _selfHostedGetOrganizationLicenseQuery.GetLicenseAsync(organization, billingSyncConnection);
+
+ await _organizationService.UpdateLicenseAsync(organization.Id, license);
+
+ var config = billingSyncConnection.GetConfig();
+ config.LastLicenseSync = DateTime.Now;
+ billingSyncConnection.SetConfig(config);
+ await _organizationConnectionRepository.ReplaceAsync(billingSyncConnection);
+ }
+}
diff --git a/src/Api/Models/Request/Organizations/OrganizationConnectionRequestModel.cs b/src/Api/Models/Request/Organizations/OrganizationConnectionRequestModel.cs
index 9dbc9ca0a0..96a444c27a 100644
--- a/src/Api/Models/Request/Organizations/OrganizationConnectionRequestModel.cs
+++ b/src/Api/Models/Request/Organizations/OrganizationConnectionRequestModel.cs
@@ -2,6 +2,7 @@
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Data.Organizations.OrganizationConnections;
+using Bit.Core.Models.OrganizationConnectionConfigs;
using Bit.Core.Utilities;
namespace Bit.Api.Models.Request.Organizations;
@@ -17,7 +18,7 @@ public class OrganizationConnectionRequestModel
}
-public class OrganizationConnectionRequestModel : OrganizationConnectionRequestModel where T : new()
+public class OrganizationConnectionRequestModel : OrganizationConnectionRequestModel where T : IConnectionConfig
{
public T ParsedConfig { get; private set; }
diff --git a/src/Core/Entities/OrganizationConnection.cs b/src/Core/Entities/OrganizationConnection.cs
index cc07177384..5b466fb4a6 100644
--- a/src/Core/Entities/OrganizationConnection.cs
+++ b/src/Core/Entities/OrganizationConnection.cs
@@ -1,15 +1,16 @@
using System.Text.Json;
using Bit.Core.Enums;
+using Bit.Core.Models.OrganizationConnectionConfigs;
using Bit.Core.Utilities;
namespace Bit.Core.Entities;
-public class OrganizationConnection : OrganizationConnection where T : new()
+public class OrganizationConnection : OrganizationConnection where T : IConnectionConfig
{
public new T Config
{
get => base.GetConfig();
- set => base.SetConfig(value);
+ set => base.SetConfig(value);
}
}
@@ -26,7 +27,7 @@ public class OrganizationConnection : ITableObject
Id = CoreHelpers.GenerateComb();
}
- public T GetConfig() where T : new()
+ public T GetConfig() where T : IConnectionConfig
{
try
{
@@ -38,8 +39,32 @@ public class OrganizationConnection : ITableObject
}
}
- public void SetConfig(T config) where T : new()
+ public void SetConfig(T config) where T : IConnectionConfig
{
Config = JsonSerializer.Serialize(config);
}
+
+ public bool Validate(out string exception) where T : IConnectionConfig
+ {
+ if (!Enabled)
+ {
+ exception = $"Connection disabled for organization {OrganizationId}";
+ return false;
+ }
+
+ if (string.IsNullOrWhiteSpace(Config))
+ {
+ exception = $"No saved Connection config for organization {OrganizationId}";
+ return false;
+ }
+
+ var config = GetConfig();
+ if (config == null)
+ {
+ exception = $"Error parsing Connection config for organization {OrganizationId}";
+ return false;
+ }
+
+ return config.Validate(out exception);
+ }
}
diff --git a/src/Core/Models/Api/Request/OrganizationLicenses/SelfHostedOrganizationLicenseRequestModel.cs b/src/Core/Models/Api/Request/OrganizationLicenses/SelfHostedOrganizationLicenseRequestModel.cs
new file mode 100644
index 0000000000..365d88877e
--- /dev/null
+++ b/src/Core/Models/Api/Request/OrganizationLicenses/SelfHostedOrganizationLicenseRequestModel.cs
@@ -0,0 +1,7 @@
+namespace Bit.Core.Models.Api.OrganizationLicenses;
+
+public class SelfHostedOrganizationLicenseRequestModel
+{
+ public string LicenseKey { get; set; }
+ public string BillingSyncKey { get; set; }
+}
diff --git a/src/Core/Models/Data/Organizations/OrganizationConnections/OrganizationConnectionData.cs b/src/Core/Models/Data/Organizations/OrganizationConnections/OrganizationConnectionData.cs
index 3a3edaed45..7a9aa77110 100644
--- a/src/Core/Models/Data/Organizations/OrganizationConnections/OrganizationConnectionData.cs
+++ b/src/Core/Models/Data/Organizations/OrganizationConnections/OrganizationConnectionData.cs
@@ -1,9 +1,10 @@
using Bit.Core.Entities;
using Bit.Core.Enums;
+using Bit.Core.Models.OrganizationConnectionConfigs;
namespace Bit.Core.Models.Data.Organizations.OrganizationConnections;
-public class OrganizationConnectionData where T : new()
+public class OrganizationConnectionData where T : IConnectionConfig
{
public Guid? Id { get; set; }
public OrganizationConnectionType Type { get; set; }
diff --git a/src/Core/Models/OrganizationConnectionConfigs/BillingSyncConfig.cs b/src/Core/Models/OrganizationConnectionConfigs/BillingSyncConfig.cs
index 204e165d05..07f07093d2 100644
--- a/src/Core/Models/OrganizationConnectionConfigs/BillingSyncConfig.cs
+++ b/src/Core/Models/OrganizationConnectionConfigs/BillingSyncConfig.cs
@@ -1,7 +1,20 @@
namespace Bit.Core.Models.OrganizationConnectionConfigs;
-public class BillingSyncConfig
+public class BillingSyncConfig : IConnectionConfig
{
public string BillingSyncKey { get; set; }
public Guid CloudOrganizationId { get; set; }
+ public DateTime? LastLicenseSync { get; set; }
+
+ public bool Validate(out string exception)
+ {
+ if (string.IsNullOrWhiteSpace(BillingSyncKey))
+ {
+ exception = "Failed to get Billing Sync Key";
+ return false;
+ }
+
+ exception = "";
+ return true;
+ }
}
diff --git a/src/Core/Models/OrganizationConnectionConfigs/IConnectionConfig.cs b/src/Core/Models/OrganizationConnectionConfigs/IConnectionConfig.cs
new file mode 100644
index 0000000000..9b02c359e4
--- /dev/null
+++ b/src/Core/Models/OrganizationConnectionConfigs/IConnectionConfig.cs
@@ -0,0 +1,6 @@
+namespace Bit.Core.Models.OrganizationConnectionConfigs;
+
+public interface IConnectionConfig
+{
+ bool Validate(out string exception);
+}
diff --git a/src/Core/Models/OrganizationConnectionConfigs/ScimConfig.cs b/src/Core/Models/OrganizationConnectionConfigs/ScimConfig.cs
index 63a1606cb2..8a4fcb4e8c 100644
--- a/src/Core/Models/OrganizationConnectionConfigs/ScimConfig.cs
+++ b/src/Core/Models/OrganizationConnectionConfigs/ScimConfig.cs
@@ -3,9 +3,21 @@ using Bit.Core.Enums;
namespace Bit.Core.Models.OrganizationConnectionConfigs;
-public class ScimConfig
+public class ScimConfig : IConnectionConfig
{
public bool Enabled { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public ScimProviderType? ScimProvider { get; set; }
+
+ public bool Validate(out string exception)
+ {
+ if (!Enabled)
+ {
+ exception = "Scim Config is disabled";
+ return false;
+ }
+
+ exception = "";
+ return true;
+ }
}
diff --git a/src/Core/OrganizationFeatures/OrganizationConnections/CreateOrganizationConnectionCommand.cs b/src/Core/OrganizationFeatures/OrganizationConnections/CreateOrganizationConnectionCommand.cs
index e3f308bc57..6c019001c0 100644
--- a/src/Core/OrganizationFeatures/OrganizationConnections/CreateOrganizationConnectionCommand.cs
+++ b/src/Core/OrganizationFeatures/OrganizationConnections/CreateOrganizationConnectionCommand.cs
@@ -1,5 +1,6 @@
using Bit.Core.Entities;
using Bit.Core.Models.Data.Organizations.OrganizationConnections;
+using Bit.Core.Models.OrganizationConnectionConfigs;
using Bit.Core.OrganizationFeatures.OrganizationConnections.Interfaces;
using Bit.Core.Repositories;
@@ -14,7 +15,7 @@ public class CreateOrganizationConnectionCommand : ICreateOrganizationConnection
_organizationConnectionRepository = organizationConnectionRepository;
}
- public async Task CreateAsync(OrganizationConnectionData connectionData) where T : new()
+ public async Task CreateAsync(OrganizationConnectionData connectionData) where T : IConnectionConfig
{
return await _organizationConnectionRepository.CreateAsync(connectionData.ToEntity());
}
diff --git a/src/Core/OrganizationFeatures/OrganizationConnections/Interfaces/ICreateOrganizationConnectionCommand.cs b/src/Core/OrganizationFeatures/OrganizationConnections/Interfaces/ICreateOrganizationConnectionCommand.cs
index b31920b10a..a97be09c3b 100644
--- a/src/Core/OrganizationFeatures/OrganizationConnections/Interfaces/ICreateOrganizationConnectionCommand.cs
+++ b/src/Core/OrganizationFeatures/OrganizationConnections/Interfaces/ICreateOrganizationConnectionCommand.cs
@@ -1,9 +1,10 @@
using Bit.Core.Entities;
using Bit.Core.Models.Data.Organizations.OrganizationConnections;
+using Bit.Core.Models.OrganizationConnectionConfigs;
namespace Bit.Core.OrganizationFeatures.OrganizationConnections.Interfaces;
public interface ICreateOrganizationConnectionCommand
{
- Task CreateAsync(OrganizationConnectionData connectionData) where T : new();
+ Task CreateAsync(OrganizationConnectionData connectionData) where T : IConnectionConfig;
}
diff --git a/src/Core/OrganizationFeatures/OrganizationConnections/Interfaces/IUpdateOrganizationConnectionCommand.cs b/src/Core/OrganizationFeatures/OrganizationConnections/Interfaces/IUpdateOrganizationConnectionCommand.cs
index 742e89c970..b245b89693 100644
--- a/src/Core/OrganizationFeatures/OrganizationConnections/Interfaces/IUpdateOrganizationConnectionCommand.cs
+++ b/src/Core/OrganizationFeatures/OrganizationConnections/Interfaces/IUpdateOrganizationConnectionCommand.cs
@@ -1,9 +1,10 @@
using Bit.Core.Entities;
using Bit.Core.Models.Data.Organizations.OrganizationConnections;
+using Bit.Core.Models.OrganizationConnectionConfigs;
namespace Bit.Core.OrganizationFeatures.OrganizationConnections.Interfaces;
public interface IUpdateOrganizationConnectionCommand
{
- Task UpdateAsync(OrganizationConnectionData connectionData) where T : new();
+ Task UpdateAsync(OrganizationConnectionData connectionData) where T : IConnectionConfig;
}
diff --git a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Interfaces/IValidateBillingSyncKeyCommand.cs b/src/Core/OrganizationFeatures/OrganizationConnections/Interfaces/IValidateBillingSyncKeyCommand.cs
similarity index 64%
rename from src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Interfaces/IValidateBillingSyncKeyCommand.cs
rename to src/Core/OrganizationFeatures/OrganizationConnections/Interfaces/IValidateBillingSyncKeyCommand.cs
index 53e926903f..9fb979548a 100644
--- a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Interfaces/IValidateBillingSyncKeyCommand.cs
+++ b/src/Core/OrganizationFeatures/OrganizationConnections/Interfaces/IValidateBillingSyncKeyCommand.cs
@@ -1,6 +1,6 @@
using Bit.Core.Entities;
-namespace Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
+namespace Bit.Core.OrganizationFeatures.OrganizationConnections.Interfaces;
public interface IValidateBillingSyncKeyCommand
{
diff --git a/src/Core/OrganizationFeatures/OrganizationConnections/UpdateOrganizationConnectionCommand.cs b/src/Core/OrganizationFeatures/OrganizationConnections/UpdateOrganizationConnectionCommand.cs
index 0d872b6f1f..3e64fd47ab 100644
--- a/src/Core/OrganizationFeatures/OrganizationConnections/UpdateOrganizationConnectionCommand.cs
+++ b/src/Core/OrganizationFeatures/OrganizationConnections/UpdateOrganizationConnectionCommand.cs
@@ -1,6 +1,7 @@
using Bit.Core.Entities;
using Bit.Core.Exceptions;
using Bit.Core.Models.Data.Organizations.OrganizationConnections;
+using Bit.Core.Models.OrganizationConnectionConfigs;
using Bit.Core.OrganizationFeatures.OrganizationConnections.Interfaces;
using Bit.Core.Repositories;
@@ -15,7 +16,7 @@ public class UpdateOrganizationConnectionCommand : IUpdateOrganizationConnection
_organizationConnectionRepository = organizationConnectionRepository;
}
- public async Task UpdateAsync(OrganizationConnectionData connectionData) where T : new()
+ public async Task UpdateAsync(OrganizationConnectionData connectionData) where T : IConnectionConfig
{
if (!connectionData.Id.HasValue)
{
diff --git a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/ValidateBillingSyncKeyCommand.cs b/src/Core/OrganizationFeatures/OrganizationConnections/ValidateBillingSyncKeyCommand.cs
similarity index 70%
rename from src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/ValidateBillingSyncKeyCommand.cs
rename to src/Core/OrganizationFeatures/OrganizationConnections/ValidateBillingSyncKeyCommand.cs
index 19c4398a70..a764bbcf23 100644
--- a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/ValidateBillingSyncKeyCommand.cs
+++ b/src/Core/OrganizationFeatures/OrganizationConnections/ValidateBillingSyncKeyCommand.cs
@@ -1,20 +1,17 @@
using Bit.Core.Entities;
using Bit.Core.Exceptions;
-using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
+using Bit.Core.OrganizationFeatures.OrganizationConnections.Interfaces;
using Bit.Core.Repositories;
-namespace Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud;
+namespace Bit.Core.OrganizationFeatures.OrganizationConnections;
public class ValidateBillingSyncKeyCommand : IValidateBillingSyncKeyCommand
{
- private readonly IOrganizationSponsorshipRepository _organizationSponsorshipRepository;
private readonly IOrganizationApiKeyRepository _apiKeyRepository;
public ValidateBillingSyncKeyCommand(
- IOrganizationSponsorshipRepository organizationSponsorshipRepository,
IOrganizationApiKeyRepository organizationApiKeyRepository)
{
- _organizationSponsorshipRepository = organizationSponsorshipRepository;
_apiKeyRepository = organizationApiKeyRepository;
}
diff --git a/src/Core/OrganizationFeatures/OrganizationLicenses/Cloud/CloudGetOrganizationLicenseQuery.cs b/src/Core/OrganizationFeatures/OrganizationLicenses/Cloud/CloudGetOrganizationLicenseQuery.cs
new file mode 100644
index 0000000000..ff8a6d34fb
--- /dev/null
+++ b/src/Core/OrganizationFeatures/OrganizationLicenses/Cloud/CloudGetOrganizationLicenseQuery.cs
@@ -0,0 +1,38 @@
+using Bit.Core.Entities;
+using Bit.Core.Exceptions;
+using Bit.Core.Models.Business;
+using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
+using Bit.Core.Repositories;
+using Bit.Core.Services;
+
+namespace Bit.Core.OrganizationFeatures.OrganizationLicenses;
+
+public class CloudGetOrganizationLicenseQuery : ICloudGetOrganizationLicenseQuery
+{
+ private readonly IInstallationRepository _installationRepository;
+ private readonly IPaymentService _paymentService;
+ private readonly ILicensingService _licensingService;
+
+ public CloudGetOrganizationLicenseQuery(
+ IInstallationRepository installationRepository,
+ IPaymentService paymentService,
+ ILicensingService licensingService)
+ {
+ _installationRepository = installationRepository;
+ _paymentService = paymentService;
+ _licensingService = licensingService;
+ }
+
+ public async Task GetLicenseAsync(Organization organization, Guid installationId,
+ int? version = null)
+ {
+ var installation = await _installationRepository.GetByIdAsync(installationId);
+ if (installation is not { Enabled: true })
+ {
+ throw new BadRequestException("Invalid installation id");
+ }
+
+ var subInfo = await _paymentService.GetSubscriptionAsync(organization);
+ return new OrganizationLicense(organization, subInfo, installationId, _licensingService, version);
+ }
+}
diff --git a/src/Core/OrganizationFeatures/OrganizationLicenses/Interfaces/IGetOrganizationLicenseQuery.cs b/src/Core/OrganizationFeatures/OrganizationLicenses/Interfaces/IGetOrganizationLicenseQuery.cs
new file mode 100644
index 0000000000..2c66833e63
--- /dev/null
+++ b/src/Core/OrganizationFeatures/OrganizationLicenses/Interfaces/IGetOrganizationLicenseQuery.cs
@@ -0,0 +1,15 @@
+using Bit.Core.Entities;
+using Bit.Core.Models.Business;
+
+namespace Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
+
+public interface ICloudGetOrganizationLicenseQuery
+{
+ Task GetLicenseAsync(Organization organization, Guid installationId,
+ int? version = null);
+}
+
+public interface ISelfHostedGetOrganizationLicenseQuery
+{
+ Task GetLicenseAsync(Organization organization, OrganizationConnection billingSyncConnection);
+}
diff --git a/src/Core/OrganizationFeatures/OrganizationLicenses/SelfHosted/SelfHostedGetOrganizationLicenseQuery.cs b/src/Core/OrganizationFeatures/OrganizationLicenses/SelfHosted/SelfHostedGetOrganizationLicenseQuery.cs
new file mode 100644
index 0000000000..84ae0a27db
--- /dev/null
+++ b/src/Core/OrganizationFeatures/OrganizationLicenses/SelfHosted/SelfHostedGetOrganizationLicenseQuery.cs
@@ -0,0 +1,66 @@
+using Bit.Core.Context;
+using Bit.Core.Entities;
+using Bit.Core.Exceptions;
+using Bit.Core.Models.Api.OrganizationLicenses;
+using Bit.Core.Models.Business;
+using Bit.Core.Models.OrganizationConnectionConfigs;
+using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
+using Bit.Core.Services;
+using Bit.Core.Settings;
+using Microsoft.Extensions.Logging;
+
+namespace Bit.Core.OrganizationFeatures.OrganizationLicenses;
+
+public class SelfHostedGetOrganizationLicenseQuery : BaseIdentityClientService, ISelfHostedGetOrganizationLicenseQuery
+{
+ private readonly IGlobalSettings _globalSettings;
+
+ public SelfHostedGetOrganizationLicenseQuery(IHttpClientFactory httpFactory, IGlobalSettings globalSettings, ILogger logger, ICurrentContext currentContext)
+ : base(
+ httpFactory,
+ globalSettings.Installation.ApiUri,
+ globalSettings.Installation.IdentityUri,
+ "api.licensing",
+ $"installation.{globalSettings.Installation.Id}",
+ globalSettings.Installation.Key,
+ logger)
+ {
+ _globalSettings = globalSettings;
+ }
+
+ public async Task GetLicenseAsync(Organization organization, OrganizationConnection billingSyncConnection)
+ {
+ if (!_globalSettings.SelfHosted)
+ {
+ throw new BadRequestException("This action is only available for self-hosted.");
+ }
+
+ if (!_globalSettings.EnableCloudCommunication)
+ {
+ throw new BadRequestException("Cloud communication is disabled in global settings");
+ }
+
+ if (!billingSyncConnection.Validate(out var exception))
+ {
+ throw new BadRequestException(exception);
+ }
+
+ var billingSyncConfig = billingSyncConnection.GetConfig();
+ var cloudOrganizationId = billingSyncConfig.CloudOrganizationId;
+
+ var response = await SendAsync(
+ HttpMethod.Get, $"licenses/organization/{cloudOrganizationId}", new SelfHostedOrganizationLicenseRequestModel()
+ {
+ BillingSyncKey = billingSyncConfig.BillingSyncKey,
+ LicenseKey = organization.LicenseKey,
+ }, true);
+
+ if (response == null)
+ {
+ _logger.LogDebug("Organization License sync failed for '{OrgId}'", organization.Id);
+ throw new BadRequestException("An error has occurred. Check your internet connection and ensure the billing token is correct.");
+ }
+
+ return response;
+ }
+}
diff --git a/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs b/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs
index 18c2f44dc9..b8e2a775e2 100644
--- a/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs
+++ b/src/Core/OrganizationFeatures/OrganizationServiceCollectionExtensions.cs
@@ -7,6 +7,8 @@ using Bit.Core.OrganizationFeatures.OrganizationCollections;
using Bit.Core.OrganizationFeatures.OrganizationCollections.Interfaces;
using Bit.Core.OrganizationFeatures.OrganizationConnections;
using Bit.Core.OrganizationFeatures.OrganizationConnections.Interfaces;
+using Bit.Core.OrganizationFeatures.OrganizationLicenses;
+using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
@@ -32,6 +34,7 @@ public static class OrganizationServiceCollectionExtensions
services.AddOrganizationApiKeyCommandsQueries();
services.AddOrganizationCollectionCommands();
services.AddOrganizationGroupCommands();
+ services.AddOrganizationLicenseCommandQueries();
}
private static void AddOrganizationConnectionCommands(this IServiceCollection services)
@@ -85,6 +88,12 @@ public static class OrganizationServiceCollectionExtensions
services.AddScoped();
}
+ private static void AddOrganizationLicenseCommandQueries(this IServiceCollection services)
+ {
+ services.AddScoped();
+ services.AddScoped();
+ }
+
private static void AddTokenizers(this IServiceCollection services)
{
services.AddSingleton>(serviceProvider =>
diff --git a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/SelfHosted/SelfHostedSyncSponsorshipsCommand.cs b/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/SelfHosted/SelfHostedSyncSponsorshipsCommand.cs
index eed143838e..0d22b53bad 100644
--- a/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/SelfHosted/SelfHostedSyncSponsorshipsCommand.cs
+++ b/src/Core/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/SelfHosted/SelfHostedSyncSponsorshipsCommand.cs
@@ -48,20 +48,13 @@ public class SelfHostedSyncSponsorshipsCommand : BaseIdentityClientService, ISel
{
throw new BadRequestException("Failed to sync instance with cloud - Cloud communication is disabled in global settings");
}
- if (!billingSyncConnection.Enabled)
+
+ if (!billingSyncConnection.Validate(out var exception))
{
- throw new BadRequestException($"Billing Sync Key disabled for organization {organizationId}");
- }
- if (string.IsNullOrWhiteSpace(billingSyncConnection.Config))
- {
- throw new BadRequestException($"No Billing Sync Key known for organization {organizationId}");
- }
- var billingSyncConfig = billingSyncConnection.GetConfig();
- if (billingSyncConfig == null || string.IsNullOrWhiteSpace(billingSyncConfig.BillingSyncKey))
- {
- throw new BadRequestException($"Failed to get Billing Sync Key for organization {organizationId}");
+ throw new BadRequestException(exception);
}
+ var billingSyncConfig = billingSyncConnection.GetConfig();
var organizationSponsorshipsDict = (await _organizationSponsorshipRepository.GetManyBySponsoringOrganizationAsync(organizationId))
.ToDictionary(i => i.SponsoringOrganizationUserId);
if (!organizationSponsorshipsDict.Any())
diff --git a/src/Core/Services/IOrganizationService.cs b/src/Core/Services/IOrganizationService.cs
index f3f3f16139..5383c1a3e3 100644
--- a/src/Core/Services/IOrganizationService.cs
+++ b/src/Core/Services/IOrganizationService.cs
@@ -56,9 +56,6 @@ public interface IOrganizationService
IEnumerable organizationUserIds, Guid? deletingUserId);
Task UpdateUserGroupsAsync(OrganizationUser organizationUser, IEnumerable groupIds, Guid? loggedInUserId);
Task UpdateUserResetPasswordEnrollmentAsync(Guid organizationId, Guid userId, string resetPasswordKey, Guid? callingUserId);
- Task GenerateLicenseAsync(Guid organizationId, Guid installationId);
- Task GenerateLicenseAsync(Organization organization, Guid installationId,
- int? version = null);
Task ImportAsync(Guid organizationId, Guid? importingUserId, IEnumerable groups,
IEnumerable newUsers, IEnumerable removeUserExternalIds,
bool overwriteExisting);
diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs
index bff3b9fa4a..8f89e7ed6e 100644
--- a/src/Core/Services/Implementations/OrganizationService.cs
+++ b/src/Core/Services/Implementations/OrganizationService.cs
@@ -1926,30 +1926,6 @@ public class OrganizationService : IOrganizationService
EventType.OrganizationUser_ResetPassword_Enroll : EventType.OrganizationUser_ResetPassword_Withdraw);
}
- public async Task GenerateLicenseAsync(Guid organizationId, Guid installationId)
- {
- var organization = await GetOrgById(organizationId);
- return await GenerateLicenseAsync(organization, installationId);
- }
-
- public async Task GenerateLicenseAsync(Organization organization, Guid installationId,
- int? version = null)
- {
- if (organization == null)
- {
- throw new NotFoundException();
- }
-
- var installation = await _installationRepository.GetByIdAsync(installationId);
- if (installation == null || !installation.Enabled)
- {
- throw new BadRequestException("Invalid installation id");
- }
-
- var subInfo = await _paymentService.GetSubscriptionAsync(organization);
- return new OrganizationLicense(organization, subInfo, installationId, _licensingService, version);
- }
-
public async Task InviteUserAsync(Guid organizationId, Guid? invitingUserId, string email,
OrganizationUserType type, bool accessAll, string externalId, IEnumerable collections,
IEnumerable groups)
diff --git a/test/Api.Test/Controllers/OrganizationConnectionsControllerTests.cs b/test/Api.Test/Controllers/OrganizationConnectionsControllerTests.cs
index 594e708bdf..0f243b9efb 100644
--- a/test/Api.Test/Controllers/OrganizationConnectionsControllerTests.cs
+++ b/test/Api.Test/Controllers/OrganizationConnectionsControllerTests.cs
@@ -366,7 +366,7 @@ public class OrganizationConnectionsControllerTests
}
private static OrganizationConnectionRequestModel RequestModelFromEntity(OrganizationConnection entity)
- where T : new()
+ where T : IConnectionConfig
{
return new(new OrganizationConnectionRequestModel()
{
diff --git a/test/Api.Test/Controllers/OrganizationsControllerTests.cs b/test/Api.Test/Controllers/OrganizationsControllerTests.cs
index fa1855c70d..f056beea80 100644
--- a/test/Api.Test/Controllers/OrganizationsControllerTests.cs
+++ b/test/Api.Test/Controllers/OrganizationsControllerTests.cs
@@ -6,6 +6,7 @@ using Bit.Core.Entities;
using Bit.Core.Exceptions;
using Bit.Core.Models.Data;
using Bit.Core.OrganizationFeatures.OrganizationApiKeys.Interfaces;
+using Bit.Core.OrganizationFeatures.OrganizationLicenses.Interfaces;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
@@ -29,6 +30,7 @@ public class OrganizationsControllerTests : IDisposable
private readonly IGetOrganizationApiKeyQuery _getOrganizationApiKeyQuery;
private readonly IRotateOrganizationApiKeyCommand _rotateOrganizationApiKeyCommand;
private readonly IOrganizationApiKeyRepository _organizationApiKeyRepository;
+ private readonly ICloudGetOrganizationLicenseQuery _cloudGetOrganizationLicenseQuery;
private readonly ICreateOrganizationApiKeyCommand _createOrganizationApiKeyCommand;
private readonly OrganizationsController _sut;
@@ -48,12 +50,13 @@ public class OrganizationsControllerTests : IDisposable
_rotateOrganizationApiKeyCommand = Substitute.For();
_organizationApiKeyRepository = Substitute.For();
_userService = Substitute.For();
+ _cloudGetOrganizationLicenseQuery = Substitute.For();
_createOrganizationApiKeyCommand = Substitute.For();
_sut = new OrganizationsController(_organizationRepository, _organizationUserRepository,
_policyRepository, _organizationService, _userService, _paymentService, _currentContext,
_ssoConfigRepository, _ssoConfigService, _getOrganizationApiKeyQuery, _rotateOrganizationApiKeyCommand,
- _createOrganizationApiKeyCommand, _organizationApiKeyRepository, _globalSettings);
+ _createOrganizationApiKeyCommand, _organizationApiKeyRepository, _cloudGetOrganizationLicenseQuery, _globalSettings);
}
public void Dispose()
diff --git a/test/Common/AutoFixture/SutProviderExtensions.cs b/test/Common/AutoFixture/SutProviderExtensions.cs
new file mode 100644
index 0000000000..1fdf226539
--- /dev/null
+++ b/test/Common/AutoFixture/SutProviderExtensions.cs
@@ -0,0 +1,50 @@
+using AutoFixture;
+using Bit.Core.Services;
+using Bit.Core.Settings;
+using NSubstitute;
+using RichardSzalay.MockHttp;
+
+namespace Bit.Test.Common.AutoFixture;
+
+public static class SutProviderExtensions
+{
+ public static SutProvider ConfigureBaseIdentityClientService(this SutProvider sutProvider,
+ string requestUrlFragment, HttpMethod requestHttpMethod, string identityResponse = null, string apiResponse = null)
+ where T : BaseIdentityClientService
+ {
+ var fixture = new Fixture().WithAutoNSubstitutionsAutoPopulatedProperties();
+ fixture.AddMockHttp();
+
+ var settings = fixture.Create();
+ settings.SelfHosted = true;
+ settings.EnableCloudCommunication = true;
+
+ var apiUri = fixture.Create();
+ var identityUri = fixture.Create();
+ settings.Installation.ApiUri.Returns(apiUri.ToString());
+ settings.Installation.IdentityUri.Returns(identityUri.ToString());
+
+ var apiHandler = new MockHttpMessageHandler();
+ var identityHandler = new MockHttpMessageHandler();
+ var syncUri = string.Concat(apiUri, requestUrlFragment);
+ var tokenUri = string.Concat(identityUri, "connect/token");
+
+ apiHandler.When(requestHttpMethod, syncUri)
+ .Respond("application/json", apiResponse);
+ identityHandler.When(HttpMethod.Post, tokenUri)
+ .Respond("application/json", identityResponse ?? "{\"access_token\":\"string\",\"expires_in\":3600,\"token_type\":\"Bearer\",\"scope\":\"string\"}");
+
+
+ var apiHttp = apiHandler.ToHttpClient();
+ var identityHttp = identityHandler.ToHttpClient();
+
+ var mockHttpClientFactory = Substitute.For();
+ mockHttpClientFactory.CreateClient(Arg.Is("client")).Returns(apiHttp);
+ mockHttpClientFactory.CreateClient(Arg.Is("identity")).Returns(identityHttp);
+
+ return sutProvider
+ .SetDependency(settings)
+ .SetDependency(mockHttpClientFactory)
+ .Create();
+ }
+}
diff --git a/test/Core.Test/AutoFixture/SubscriptionInfoCustomization.cs b/test/Core.Test/AutoFixture/SubscriptionInfoCustomization.cs
new file mode 100644
index 0000000000..d16d13b578
--- /dev/null
+++ b/test/Core.Test/AutoFixture/SubscriptionInfoCustomization.cs
@@ -0,0 +1,18 @@
+using AutoFixture;
+using Bit.Core.Models.Business;
+using Bit.Test.Common.AutoFixture.Attributes;
+
+namespace Bit.Core.Test.AutoFixture;
+
+public class SubscriptionInfoCustomizeAttribute : BitCustomizeAttribute
+{
+ public override ICustomization GetCustomization() => new SubscriptionInfoCustomization();
+}
+public class SubscriptionInfoCustomization : ICustomization
+{
+ public void Customize(IFixture fixture)
+ {
+ // The Subscription property uses the external Stripe library, which Autofixture doesn't handle
+ fixture.Customize(c => c.Without(s => s.Subscription));
+ }
+}
diff --git a/test/Core.Test/Entities/OrganizationConnectionTests.cs b/test/Core.Test/Entities/OrganizationConnectionTests.cs
new file mode 100644
index 0000000000..32690416c2
--- /dev/null
+++ b/test/Core.Test/Entities/OrganizationConnectionTests.cs
@@ -0,0 +1,78 @@
+using Bit.Core.Entities;
+using Bit.Core.Enums;
+using Bit.Core.Models.OrganizationConnectionConfigs;
+using Bit.Test.Common.AutoFixture.Attributes;
+using Xunit;
+
+namespace Bit.Core.Test.Entities;
+
+public class OrganizationConnectionTests
+{
+ [Theory]
+ [BitAutoData]
+ public void OrganizationConnection_CanUse_Success(Guid connectionId, Guid organizationId)
+ {
+ var connection = new OrganizationConnection()
+ {
+ Id = connectionId,
+ OrganizationId = organizationId,
+ Enabled = true,
+ Type = OrganizationConnectionType.Scim,
+ Config = new ScimConfig() { Enabled = true }
+ };
+
+ Assert.True(connection.Validate(out var exception));
+ Assert.True(string.IsNullOrEmpty(exception));
+ }
+
+ [Theory]
+ [BitAutoData]
+ public void OrganizationConnection_CanUse_WhenDisabled_ReturnsFalse(Guid connectionId, Guid organizationId)
+ {
+
+ var connection = new OrganizationConnection()
+ {
+ Id = connectionId,
+ OrganizationId = organizationId,
+ Enabled = false,
+ Type = OrganizationConnectionType.Scim,
+ Config = new ScimConfig() { Enabled = true }
+ };
+
+ Assert.False(connection.Validate(out var exception));
+ Assert.Contains("Connection disabled", exception);
+ }
+
+ [Theory]
+ [BitAutoData]
+ public void OrganizationConnection_CanUse_WhenNoConfig_ReturnsFalse(Guid connectionId, Guid organizationId)
+ {
+ var connection = new OrganizationConnection()
+ {
+ Id = connectionId,
+ OrganizationId = organizationId,
+ Enabled = true,
+ Type = OrganizationConnectionType.Scim,
+ };
+
+ Assert.False(connection.Validate(out var exception));
+ Assert.Contains("No saved Connection config", exception);
+ }
+
+ [Theory]
+ [BitAutoData]
+ public void OrganizationConnection_CanUse_WhenConfigInvalid_ReturnsFalse(Guid connectionId, Guid organizationId)
+ {
+ var connection = new OrganizationConnection()
+ {
+ Id = connectionId,
+ OrganizationId = organizationId,
+ Enabled = true,
+ Type = OrganizationConnectionType.Scim,
+ Config = new ScimConfig() { Enabled = false }
+ };
+
+ Assert.False(connection.Validate(out var exception));
+ Assert.Contains("Scim Config is disabled", exception);
+ }
+}
diff --git a/test/Core.Test/Models/OrganizationConnectionConfigs/BillingSyncConfigTests.cs b/test/Core.Test/Models/OrganizationConnectionConfigs/BillingSyncConfigTests.cs
new file mode 100644
index 0000000000..05e717ebde
--- /dev/null
+++ b/test/Core.Test/Models/OrganizationConnectionConfigs/BillingSyncConfigTests.cs
@@ -0,0 +1,27 @@
+using Bit.Core.Models.OrganizationConnectionConfigs;
+using Bit.Test.Common.AutoFixture.Attributes;
+using Xunit;
+
+namespace Bit.Core.Test.Models.OrganizationConnectionConfigs;
+
+public class BillingSyncConfigTests
+{
+ [Theory]
+ [BitAutoData]
+ public void BillingSyncConfig_CanUse_Success(string billingSyncKey)
+ {
+ var config = new BillingSyncConfig() { BillingSyncKey = billingSyncKey };
+
+ Assert.True(config.Validate(out var exception));
+ Assert.True(string.IsNullOrEmpty(exception));
+ }
+
+ [Fact]
+ public void BillingSyncConfig_CanUse_WhenNoKey_ReturnsFalse()
+ {
+ var config = new BillingSyncConfig();
+
+ Assert.False(config.Validate(out var exception));
+ Assert.Contains("Failed to get Billing Sync Key", exception);
+ }
+}
diff --git a/test/Core.Test/Models/OrganizationConnectionConfigs/ScimConfigTests.cs b/test/Core.Test/Models/OrganizationConnectionConfigs/ScimConfigTests.cs
new file mode 100644
index 0000000000..fa476eea26
--- /dev/null
+++ b/test/Core.Test/Models/OrganizationConnectionConfigs/ScimConfigTests.cs
@@ -0,0 +1,23 @@
+using Bit.Core.Models.OrganizationConnectionConfigs;
+using Xunit;
+
+namespace Bit.Core.Test.Models.OrganizationConnectionConfigs;
+
+public class ScimConfigTests
+{
+ [Fact]
+ public void ScimConfig_CanUse_Success()
+ {
+ var config = new ScimConfig() { Enabled = true };
+ Assert.True(config.Validate(out var exception));
+ Assert.True(string.IsNullOrEmpty(exception));
+ }
+
+ [Fact]
+ public void ScimConfig_CanUse_WhenDisabled_ReturnsFalse()
+ {
+ var config = new ScimConfig() { Enabled = false };
+ Assert.False(config.Validate(out var exception));
+ Assert.Contains("Config is disabled", exception);
+ }
+}
diff --git a/test/Core.Test/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/ValidateBillingSyncKeyCommandTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationConnections/ValidateBillingSyncKeyCommandTests.cs
similarity index 91%
rename from test/Core.Test/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/ValidateBillingSyncKeyCommandTests.cs
rename to test/Core.Test/OrganizationFeatures/OrganizationConnections/ValidateBillingSyncKeyCommandTests.cs
index 9b01e3035f..349f0316d7 100644
--- a/test/Core.Test/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/Cloud/ValidateBillingSyncKeyCommandTests.cs
+++ b/test/Core.Test/OrganizationFeatures/OrganizationConnections/ValidateBillingSyncKeyCommandTests.cs
@@ -1,14 +1,14 @@
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
-using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud;
+using Bit.Core.OrganizationFeatures.OrganizationConnections;
using Bit.Core.Repositories;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
-namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Cloud;
+namespace Bit.Core.Test.OrganizationFeatures.OrganizationConnections;
[SutProviderCustomize]
public class ValidateBillingSyncKeyCommandTests
diff --git a/test/Core.Test/OrganizationFeatures/OrganizationLicenses/CloudGetOrganizationLicenseQueryTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationLicenses/CloudGetOrganizationLicenseQueryTests.cs
new file mode 100644
index 0000000000..cb896ed717
--- /dev/null
+++ b/test/Core.Test/OrganizationFeatures/OrganizationLicenses/CloudGetOrganizationLicenseQueryTests.cs
@@ -0,0 +1,64 @@
+using Bit.Core.Entities;
+using Bit.Core.Enums;
+using Bit.Core.Exceptions;
+using Bit.Core.Models.Business;
+using Bit.Core.OrganizationFeatures.OrganizationLicenses;
+using Bit.Core.Repositories;
+using Bit.Core.Services;
+using Bit.Core.Test.AutoFixture;
+using Bit.Test.Common.AutoFixture;
+using Bit.Test.Common.AutoFixture.Attributes;
+using NSubstitute;
+using NSubstitute.ReturnsExtensions;
+using Xunit;
+
+namespace Bit.Core.Test.OrganizationFeatures.OrganizationLicenses;
+
+[SubscriptionInfoCustomize]
+[OrganizationLicenseCustomize]
+[SutProviderCustomize]
+public class CloudGetOrganizationLicenseQueryTests
+{
+ [Theory]
+ [BitAutoData]
+ public async Task GetLicenseAsync_InvalidInstallationId_Throws(SutProvider sutProvider,
+ Organization organization, Guid installationId, int version)
+ {
+ sutProvider.GetDependency().GetByIdAsync(installationId).ReturnsNull();
+ var exception = await Assert.ThrowsAsync(
+ async () => await sutProvider.Sut.GetLicenseAsync(organization, installationId, version));
+ Assert.Contains("Invalid installation id", exception.Message);
+ }
+
+ [Theory]
+ [BitAutoData]
+ public async Task GetLicenseAsync_DisabledOrganization_Throws(SutProvider sutProvider,
+ Organization organization, Guid installationId, Installation installation)
+ {
+ installation.Enabled = false;
+ sutProvider.GetDependency().GetByIdAsync(installationId).Returns(installation);
+
+ var exception = await Assert.ThrowsAsync(
+ async () => await sutProvider.Sut.GetLicenseAsync(organization, installationId));
+ Assert.Contains("Invalid installation id", exception.Message);
+ }
+
+ [Theory]
+ [BitAutoData]
+ public async Task GetLicenseAsync_CreatesAndReturns(SutProvider sutProvider,
+ Organization organization, Guid installationId, Installation installation, SubscriptionInfo subInfo,
+ byte[] licenseSignature)
+ {
+ installation.Enabled = true;
+ sutProvider.GetDependency().GetByIdAsync(installationId).Returns(installation);
+ sutProvider.GetDependency().GetSubscriptionAsync(organization).Returns(subInfo);
+ sutProvider.GetDependency().SignLicense(Arg.Any()).Returns(licenseSignature);
+
+ var result = await sutProvider.Sut.GetLicenseAsync(organization, installationId);
+
+ Assert.Equal(LicenseType.Organization, result.LicenseType);
+ Assert.Equal(organization.Id, result.Id);
+ Assert.Equal(installationId, result.InstallationId);
+ Assert.Equal(licenseSignature, result.SignatureBytes);
+ }
+}
diff --git a/test/Core.Test/OrganizationFeatures/OrganizationLicenses/SelfHostedGetOrganizationLicenseQueryTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationLicenses/SelfHostedGetOrganizationLicenseQueryTests.cs
new file mode 100644
index 0000000000..df4a93305c
--- /dev/null
+++ b/test/Core.Test/OrganizationFeatures/OrganizationLicenses/SelfHostedGetOrganizationLicenseQueryTests.cs
@@ -0,0 +1,94 @@
+using System.Text.Json;
+using Bit.Core.Entities;
+using Bit.Core.Exceptions;
+using Bit.Core.Models.Business;
+using Bit.Core.Models.OrganizationConnectionConfigs;
+using Bit.Core.OrganizationFeatures.OrganizationLicenses;
+using Bit.Core.Settings;
+using Bit.Core.Test.AutoFixture;
+using Bit.Test.Common.AutoFixture;
+using Bit.Test.Common.AutoFixture.Attributes;
+using Bit.Test.Common.Helpers;
+using Xunit;
+
+namespace Bit.Core.Test.OrganizationFeatures.OrganizationLicenses;
+
+[SutProviderCustomize]
+public class SelfHostedGetOrganizationLicenseQueryTests
+{
+ private static SutProvider GetSutProvider(BillingSyncConfig config,
+ string apiResponse = null)
+ {
+ return new SutProvider()
+ .ConfigureBaseIdentityClientService($"licenses/organization/{config.CloudOrganizationId}",
+ HttpMethod.Get, apiResponse: apiResponse);
+ }
+
+ [Theory]
+ [BitAutoData]
+ [OrganizationLicenseCustomize]
+ public async void GetLicenseAsync_Success(Organization organization,
+ OrganizationConnection billingSyncConnection, BillingSyncConfig config, OrganizationLicense license)
+ {
+ var sutProvider = GetSutProvider(config, JsonSerializer.Serialize(license));
+ billingSyncConnection.Enabled = true;
+ billingSyncConnection.Config = config;
+
+ var result = await sutProvider.Sut.GetLicenseAsync(organization, billingSyncConnection);
+ AssertHelper.AssertPropertyEqual(result, license);
+ }
+
+ [Theory]
+ [BitAutoData]
+ public async void GetLicenseAsync_WhenNotSelfHosted_Throws(Organization organization,
+ OrganizationConnection billingSyncConnection, BillingSyncConfig config)
+ {
+ var sutProvider = GetSutProvider(config);
+ sutProvider.GetDependency().SelfHosted = false;
+
+ var exception = await Assert.ThrowsAsync(() =>
+ sutProvider.Sut.GetLicenseAsync(organization, billingSyncConnection));
+ Assert.Contains("only available for self-hosted", exception.Message);
+ }
+
+ [Theory]
+ [BitAutoData]
+ public async void GetLicenseAsync_WhenCloudCommunicationDisabled_Throws(Organization organization,
+ OrganizationConnection billingSyncConnection, BillingSyncConfig config)
+ {
+ var sutProvider = GetSutProvider(config);
+ sutProvider.GetDependency().EnableCloudCommunication = false;
+
+ var exception = await Assert.ThrowsAsync(() =>
+ sutProvider.Sut.GetLicenseAsync(organization, billingSyncConnection));
+ Assert.Contains("Cloud communication is disabled", exception.Message);
+ }
+
+ [Theory]
+ [BitAutoData]
+ public async void GetLicenseAsync_WhenCantUseConnection_Throws(Organization organization,
+ OrganizationConnection billingSyncConnection, BillingSyncConfig config)
+ {
+ var sutProvider = GetSutProvider(config);
+ billingSyncConnection.Enabled = false;
+
+ var exception = await Assert.ThrowsAsync(() =>
+ sutProvider.Sut.GetLicenseAsync(organization, billingSyncConnection));
+ Assert.Contains("Connection disabled", exception.Message);
+ }
+
+ [Theory]
+ [BitAutoData]
+ public async void GetLicenseAsync_WhenNullResponse_Throws(Organization organization,
+ OrganizationConnection billingSyncConnection, BillingSyncConfig config)
+ {
+ var sutProvider = GetSutProvider(config);
+ billingSyncConnection.Enabled = true;
+ billingSyncConnection.Config = config;
+
+ var exception = await Assert.ThrowsAsync(() =>
+ sutProvider.Sut.GetLicenseAsync(organization, billingSyncConnection));
+ Assert.Contains("An error has occurred. Check your internet connection and ensure the billing token is correct.",
+ exception.Message);
+ }
+}
diff --git a/test/Core.Test/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/SelfHosted/SelfHostedSyncSponsorshipsCommandTests.cs b/test/Core.Test/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/SelfHosted/SelfHostedSyncSponsorshipsCommandTests.cs
index 5ec93a976b..1cfe38bf1d 100644
--- a/test/Core.Test/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/SelfHosted/SelfHostedSyncSponsorshipsCommandTests.cs
+++ b/test/Core.Test/OrganizationFeatures/OrganizationSponsorships/FamiliesForEnterprise/SelfHosted/SelfHostedSyncSponsorshipsCommandTests.cs
@@ -1,5 +1,4 @@
using System.Text.Json;
-using AutoFixture;
using Bit.Core.Entities;
using Bit.Core.Exceptions;
using Bit.Core.Models.Api.Response.OrganizationSponsorships;
@@ -12,55 +11,22 @@ using Bit.Core.Test.AutoFixture.OrganizationSponsorshipFixtures;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
-using RichardSzalay.MockHttp;
using Xunit;
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.SelfHosted;
public class SelfHostedSyncSponsorshipsCommandTests : FamiliesForEnterpriseTestsBase
{
-
- public static SutProvider GetSutProvider(bool enableCloudCommunication = true, string identityResponse = null, string apiResponse = null)
+ private static SutProvider GetSutProvider(string apiResponse = null)
{
- var fixture = new Fixture().WithAutoNSubstitutionsAutoPopulatedProperties();
- fixture.AddMockHttp();
-
- var settings = fixture.Create();
- settings.SelfHosted = true;
- settings.EnableCloudCommunication = enableCloudCommunication;
-
- var apiUri = fixture.Create();
- var identityUri = fixture.Create();
- settings.Installation.ApiUri.Returns(apiUri.ToString());
- settings.Installation.IdentityUri.Returns(identityUri.ToString());
-
- var apiHandler = new MockHttpMessageHandler();
- var identityHandler = new MockHttpMessageHandler();
- var syncUri = string.Concat(apiUri, "organization/sponsorship/sync");
- var tokenUri = string.Concat(identityUri, "connect/token");
-
- apiHandler.When(HttpMethod.Post, syncUri)
- .Respond("application/json", apiResponse);
- identityHandler.When(HttpMethod.Post, tokenUri)
- .Respond("application/json", identityResponse ?? "{\"access_token\":\"string\",\"expires_in\":3600,\"token_type\":\"Bearer\",\"scope\":\"string\"}");
-
-
- var apiHttp = apiHandler.ToHttpClient();
- var identityHttp = identityHandler.ToHttpClient();
-
- var mockHttpClientFactory = Substitute.For();
- mockHttpClientFactory.CreateClient(Arg.Is("client")).Returns(apiHttp);
- mockHttpClientFactory.CreateClient(Arg.Is("identity")).Returns(identityHttp);
-
- return new SutProvider(fixture)
- .SetDependency(settings)
- .SetDependency(mockHttpClientFactory)
- .Create();
+ return new SutProvider()
+ .ConfigureBaseIdentityClientService("organization/sponsorship/sync",
+ HttpMethod.Post, apiResponse: apiResponse);
}
[Theory]
[BitAutoData]
- public async Task SyncOrganization_BillingSyncKeyDisabled_ThrowsBadRequest(
+ public async Task SyncOrganization_BillingSyncConnectionDisabled_ThrowsBadRequest(
Guid cloudOrganizationId, OrganizationConnection billingSyncConnection)
{
var sutProvider = GetSutProvider();
@@ -73,7 +39,7 @@ public class SelfHostedSyncSponsorshipsCommandTests : FamiliesForEnterpriseTests
var exception = await Assert.ThrowsAsync(() =>
sutProvider.Sut.SyncOrganization(billingSyncConnection.OrganizationId, cloudOrganizationId, billingSyncConnection));
- Assert.Contains($"Billing Sync Key disabled", exception.Message);
+ Assert.Contains($"Connection disabled", exception.Message);
await sutProvider.GetDependency()
.DidNotReceiveWithAnyArgs()
@@ -85,7 +51,7 @@ public class SelfHostedSyncSponsorshipsCommandTests : FamiliesForEnterpriseTests
[Theory]
[BitAutoData]
- public async Task SyncOrganization_BillingSyncKeyEmpty_ThrowsBadRequest(
+ public async Task SyncOrganization_BillingSyncConfigEmpty_ThrowsBadRequest(
Guid cloudOrganizationId, OrganizationConnection billingSyncConnection)
{
var sutProvider = GetSutProvider();
@@ -94,7 +60,7 @@ public class SelfHostedSyncSponsorshipsCommandTests : FamiliesForEnterpriseTests
var exception = await Assert.ThrowsAsync(() =>
sutProvider.Sut.SyncOrganization(billingSyncConnection.OrganizationId, cloudOrganizationId, billingSyncConnection));
- Assert.Contains($"No Billing Sync Key known", exception.Message);
+ Assert.Contains($"No saved Connection config", exception.Message);
await sutProvider.GetDependency()
.DidNotReceiveWithAnyArgs()
@@ -109,7 +75,8 @@ public class SelfHostedSyncSponsorshipsCommandTests : FamiliesForEnterpriseTests
public async Task SyncOrganization_CloudCommunicationDisabled_EarlyReturn(
Guid cloudOrganizationId, OrganizationConnection billingSyncConnection)
{
- var sutProvider = GetSutProvider(false);
+ var sutProvider = GetSutProvider();
+ sutProvider.GetDependency().EnableCloudCommunication = false;
var exception = await Assert.ThrowsAsync(() =>
sutProvider.Sut.SyncOrganization(billingSyncConnection.OrganizationId, cloudOrganizationId, billingSyncConnection));
@@ -136,7 +103,8 @@ public class SelfHostedSyncSponsorshipsCommandTests : FamiliesForEnterpriseTests
SponsorshipsBatch = sponsorships.Select(o => new OrganizationSponsorshipData(o))
}));
- var sutProvider = GetSutProvider(apiResponse: syncJsonResponse);
+ var sutProvider = GetSutProvider(syncJsonResponse);
+
billingSyncConnection.SetConfig(new BillingSyncConfig
{
BillingSyncKey = "okslkcslkjf"
@@ -166,7 +134,7 @@ public class SelfHostedSyncSponsorshipsCommandTests : FamiliesForEnterpriseTests
SponsorshipsBatch = sponsorships.Select(o => new OrganizationSponsorshipData(o) { CloudSponsorshipRemoved = true })
}));
- var sutProvider = GetSutProvider(apiResponse: syncJsonResponse);
+ var sutProvider = GetSutProvider(syncJsonResponse);
billingSyncConnection.SetConfig(new BillingSyncConfig
{
BillingSyncKey = "okslkcslkjf"