From e7ea050e680735a3fbb4693a452417c01dedd91c Mon Sep 17 00:00:00 2001 From: Brant DeBow Date: Wed, 31 Dec 2025 18:34:42 -0500 Subject: [PATCH] Add data models for scim and cloud billing sync to event integrations --- .../OrganizationIntegrationResponseModel.cs | 18 ++++- .../CloudBillingSyncIntegration.cs | 28 ++++++++ .../Data/EventIntegrations/ScimIntegration.cs | 22 ++++++ ...ganizationIntegrationResponseModelTests.cs | 72 ++++++++++++++++--- 4 files changed, 129 insertions(+), 11 deletions(-) create mode 100644 src/Core/Dirt/Models/Data/EventIntegrations/CloudBillingSyncIntegration.cs create mode 100644 src/Core/Dirt/Models/Data/EventIntegrations/ScimIntegration.cs diff --git a/src/Api/Dirt/Models/Response/OrganizationIntegrationResponseModel.cs b/src/Api/Dirt/Models/Response/OrganizationIntegrationResponseModel.cs index 60e885fe82..699c095bcf 100644 --- a/src/Api/Dirt/Models/Response/OrganizationIntegrationResponseModel.cs +++ b/src/Api/Dirt/Models/Response/OrganizationIntegrationResponseModel.cs @@ -24,9 +24,21 @@ public class OrganizationIntegrationResponseModel : ResponseModel public OrganizationIntegrationStatus Status => Type switch { - // Not yet implemented, shouldn't be present, NotApplicable - IntegrationType.CloudBillingSync => OrganizationIntegrationStatus.NotApplicable, - IntegrationType.Scim => OrganizationIntegrationStatus.NotApplicable, + // CloudBillingSync: Check if configuration exists and Enabled property is true + // If null, invalid config, or Enabled is false, status is Invalid + IntegrationType.CloudBillingSync => string.IsNullOrWhiteSpace(Configuration) + ? OrganizationIntegrationStatus.Invalid + : (JsonSerializer.Deserialize(Configuration)?.Enabled ?? false) + ? OrganizationIntegrationStatus.Completed + : OrganizationIntegrationStatus.Invalid, + + // Scim: Check if configuration exists and Enabled property is true + // If null, invalid config, or Enabled is false, status is Invalid + IntegrationType.Scim => string.IsNullOrWhiteSpace(Configuration) + ? OrganizationIntegrationStatus.Invalid + : (JsonSerializer.Deserialize(Configuration)?.Enabled ?? false) + ? OrganizationIntegrationStatus.Completed + : OrganizationIntegrationStatus.Invalid, // Webhook is allowed to be null. If it's present, it's Completed IntegrationType.Webhook => OrganizationIntegrationStatus.Completed, diff --git a/src/Core/Dirt/Models/Data/EventIntegrations/CloudBillingSyncIntegration.cs b/src/Core/Dirt/Models/Data/EventIntegrations/CloudBillingSyncIntegration.cs new file mode 100644 index 0000000000..85c904f022 --- /dev/null +++ b/src/Core/Dirt/Models/Data/EventIntegrations/CloudBillingSyncIntegration.cs @@ -0,0 +1,28 @@ +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; + +/// +/// Configuration for CloudBillingSync integration type. +/// +public class CloudBillingSyncIntegration +{ + /// + /// Whether the billing sync integration is enabled. + /// Replaces the separate Enabled column from OrganizationConnection. + /// + public bool Enabled { get; set; } + + /// + /// API key for cloud billing synchronization. + /// + public string BillingSyncKey { get; set; } = string.Empty; + + /// + /// Reference to the cloud organization ID. + /// + public Guid CloudOrganizationId { get; set; } + + /// + /// Timestamp of the last successful license sync. + /// + public DateTime? LastLicenseSync { get; set; } +} diff --git a/src/Core/Dirt/Models/Data/EventIntegrations/ScimIntegration.cs b/src/Core/Dirt/Models/Data/EventIntegrations/ScimIntegration.cs new file mode 100644 index 0000000000..3d505b9712 --- /dev/null +++ b/src/Core/Dirt/Models/Data/EventIntegrations/ScimIntegration.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; +using Bit.Core.AdminConsole.Enums; + +namespace Bit.Core.Dirt.Models.Data.EventIntegrations; + +/// +/// Configuration for SCIM integration type. +/// +public class ScimIntegration +{ + /// + /// Whether the SCIM integration is enabled. + /// Replaces the separate Enabled column from OrganizationConnection. + /// + public bool Enabled { get; set; } + + /// + /// The SCIM provider type (e.g., Okta, Azure AD, OneLogin). + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public ScimProviderType? ScimProvider { get; set; } +} diff --git a/test/Api.Test/Dirt/Models/Response/OrganizationIntegrationResponseModelTests.cs b/test/Api.Test/Dirt/Models/Response/OrganizationIntegrationResponseModelTests.cs index e6f8d5d756..bea3d137e6 100644 --- a/test/Api.Test/Dirt/Models/Response/OrganizationIntegrationResponseModelTests.cs +++ b/test/Api.Test/Dirt/Models/Response/OrganizationIntegrationResponseModelTests.cs @@ -14,29 +14,85 @@ namespace Bit.Api.Test.Dirt.Models.Response; public class OrganizationIntegrationResponseModelTests { [Theory, BitAutoData] - public void Status_CloudBillingSync_AlwaysNotApplicable(OrganizationIntegration oi) + public void Status_CloudBillingSync_NullConfig_ReturnsInvalid(OrganizationIntegration oi) { oi.Type = IntegrationType.CloudBillingSync; oi.Configuration = null; var model = new OrganizationIntegrationResponseModel(oi); - Assert.Equal(OrganizationIntegrationStatus.NotApplicable, model.Status); - model.Configuration = "{}"; - Assert.Equal(OrganizationIntegrationStatus.NotApplicable, model.Status); + Assert.Equal(OrganizationIntegrationStatus.Invalid, model.Status); } [Theory, BitAutoData] - public void Status_Scim_AlwaysNotApplicable(OrganizationIntegration oi) + public void Status_CloudBillingSync_EnabledFalse_ReturnsInvalid(OrganizationIntegration oi) + { + oi.Type = IntegrationType.CloudBillingSync; + oi.Configuration = JsonSerializer.Serialize(new CloudBillingSyncIntegration + { + Enabled = false, + BillingSyncKey = "test-key", + CloudOrganizationId = Guid.NewGuid() + }); + + var model = new OrganizationIntegrationResponseModel(oi); + + Assert.Equal(OrganizationIntegrationStatus.Invalid, model.Status); + } + + [Theory, BitAutoData] + public void Status_CloudBillingSync_EnabledTrue_ReturnsCompleted(OrganizationIntegration oi) + { + oi.Type = IntegrationType.CloudBillingSync; + oi.Configuration = JsonSerializer.Serialize(new CloudBillingSyncIntegration + { + Enabled = true, + BillingSyncKey = "test-key", + CloudOrganizationId = Guid.NewGuid() + }); + + var model = new OrganizationIntegrationResponseModel(oi); + + Assert.Equal(OrganizationIntegrationStatus.Completed, model.Status); + } + + [Theory, BitAutoData] + public void Status_Scim_NullConfig_ReturnsInvalid(OrganizationIntegration oi) { oi.Type = IntegrationType.Scim; oi.Configuration = null; var model = new OrganizationIntegrationResponseModel(oi); - Assert.Equal(OrganizationIntegrationStatus.NotApplicable, model.Status); - model.Configuration = "{}"; - Assert.Equal(OrganizationIntegrationStatus.NotApplicable, model.Status); + Assert.Equal(OrganizationIntegrationStatus.Invalid, model.Status); + } + + [Theory, BitAutoData] + public void Status_Scim_EnabledFalse_ReturnsInvalid(OrganizationIntegration oi) + { + oi.Type = IntegrationType.Scim; + oi.Configuration = JsonSerializer.Serialize(new ScimIntegration + { + Enabled = false + }); + + var model = new OrganizationIntegrationResponseModel(oi); + + Assert.Equal(OrganizationIntegrationStatus.Invalid, model.Status); + } + + [Theory, BitAutoData] + public void Status_Scim_EnabledTrue_ReturnsCompleted(OrganizationIntegration oi) + { + oi.Type = IntegrationType.Scim; + oi.Configuration = JsonSerializer.Serialize(new ScimIntegration + { + Enabled = true + }); + + var model = new OrganizationIntegrationResponseModel(oi); + + Assert.Equal(OrganizationIntegrationStatus.Completed, model.Status); } [Theory, BitAutoData]