1
0
mirror of https://github.com/bitwarden/server synced 2025-12-06 00:03:34 +00:00

[PM-17562] Add support for null/all event type (#6100)

* [PM-17562] Add support for null/all event type

* Address PR Feedback

* Adjusted SQL scripts per feedback
This commit is contained in:
Brant DeBow
2025-07-22 10:02:13 -04:00
committed by GitHub
parent bdadf2af01
commit f4e1e2f1f7
17 changed files with 10064 additions and 24 deletions

View File

@@ -1,10 +1,10 @@
using System.ComponentModel.DataAnnotations;
#nullable enable
using System.Text.Json;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Bit.Core.Enums;
#nullable enable
namespace Bit.Api.AdminConsole.Models.Request.Organizations;
@@ -12,8 +12,7 @@ public class OrganizationIntegrationConfigurationRequestModel
{
public string? Configuration { get; set; }
[Required]
public EventType EventType { get; set; }
public EventType? EventType { get; set; }
public string? Filters { get; set; }

View File

@@ -25,6 +25,6 @@ public class OrganizationIntegrationConfigurationResponseModel : ResponseModel
public string? Configuration { get; set; }
public string? Filters { get; set; }
public DateTime CreationDate { get; set; }
public EventType EventType { get; set; }
public EventType? EventType { get; set; }
public string? Template { get; set; }
}

View File

@@ -10,7 +10,7 @@ public class OrganizationIntegrationConfiguration : ITableObject<Guid>
{
public Guid Id { get; set; }
public Guid OrganizationIntegrationId { get; set; }
public EventType EventType { get; set; }
public EventType? EventType { get; set; }
public string? Configuration { get; set; }
public string? Template { get; set; }
public DateTime CreationDate { get; internal set; } = DateTime.UtcNow;

View File

@@ -11,7 +11,7 @@ public class OrganizationIntegrationConfigurationDetails
public Guid OrganizationId { get; set; }
public Guid OrganizationIntegrationId { get; set; }
public IntegrationType IntegrationType { get; set; }
public EventType EventType { get; set; }
public EventType? EventType { get; set; }
public string? Configuration { get; set; }
public string? Filters { get; set; }
public string? IntegrationConfiguration { get; set; }

View File

@@ -10,7 +10,7 @@ namespace Bit.Core.Services;
public class IntegrationConfigurationDetailsCacheService : BackgroundService, IIntegrationConfigurationDetailsCache
{
private readonly record struct IntegrationCacheKey(Guid OrganizationId, IntegrationType IntegrationType, EventType EventType);
private readonly record struct IntegrationCacheKey(Guid OrganizationId, IntegrationType IntegrationType, EventType? EventType);
private readonly IOrganizationIntegrationConfigurationRepository _repository;
private readonly ILogger<IntegrationConfigurationDetailsCacheService> _logger;
private readonly TimeSpan _refreshInterval;
@@ -19,8 +19,7 @@ public class IntegrationConfigurationDetailsCacheService : BackgroundService, II
public IntegrationConfigurationDetailsCacheService(
IOrganizationIntegrationConfigurationRepository repository,
GlobalSettings globalSettings,
ILogger<IntegrationConfigurationDetailsCacheService> logger
)
ILogger<IntegrationConfigurationDetailsCacheService> logger)
{
_repository = repository;
_logger = logger;
@@ -32,10 +31,21 @@ public class IntegrationConfigurationDetailsCacheService : BackgroundService, II
IntegrationType integrationType,
EventType eventType)
{
var key = new IntegrationCacheKey(organizationId, integrationType, eventType);
return _cache.TryGetValue(key, out var value)
? value
: new List<OrganizationIntegrationConfigurationDetails>();
var specificKey = new IntegrationCacheKey(organizationId, integrationType, eventType);
var allEventsKey = new IntegrationCacheKey(organizationId, integrationType, null);
var results = new List<OrganizationIntegrationConfigurationDetails>();
if (_cache.TryGetValue(specificKey, out var specificConfigs))
{
results.AddRange(specificConfigs);
}
if (_cache.TryGetValue(allEventsKey, out var fallbackConfigs))
{
results.AddRange(fallbackConfigs);
}
return results;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)

View File

@@ -2,7 +2,7 @@ CREATE TABLE [dbo].[OrganizationIntegrationConfiguration]
(
[Id] UNIQUEIDENTIFIER NOT NULL,
[OrganizationIntegrationId] UNIQUEIDENTIFIER NOT NULL,
[EventType] SMALLINT NOT NULL,
[EventType] SMALLINT NULL,
[Configuration] VARCHAR (MAX) NULL,
[Template] VARCHAR (MAX) NULL,
[CreationDate] DATETIME2 (7) NOT NULL,

View File

@@ -1,6 +1,7 @@
#nullable enable
using System.Text.Json;
using Bit.Core.Enums;
using Bit.Core.Models.Data.Organizations;
using Bit.Core.Repositories;
using Bit.Core.Services;
@@ -28,18 +29,55 @@ public class IntegrationConfigurationDetailsCacheServiceTests
}
[Theory, BitAutoData]
public async Task GetConfigurationDetails_KeyExists_ReturnsExpectedList(OrganizationIntegrationConfigurationDetails config)
public async Task GetConfigurationDetails_SpecificKeyExists_ReturnsExpectedList(OrganizationIntegrationConfigurationDetails config)
{
config.EventType = EventType.Cipher_Created;
var sutProvider = GetSutProvider([config]);
await sutProvider.Sut.RefreshAsync();
var result = sutProvider.Sut.GetConfigurationDetails(
config.OrganizationId,
config.IntegrationType,
config.EventType);
EventType.Cipher_Created);
Assert.Single(result);
Assert.Same(config, result[0]);
}
[Theory, BitAutoData]
public async Task GetConfigurationDetails_AllEventsKeyExists_ReturnsExpectedList(OrganizationIntegrationConfigurationDetails config)
{
config.EventType = null;
var sutProvider = GetSutProvider([config]);
await sutProvider.Sut.RefreshAsync();
var result = sutProvider.Sut.GetConfigurationDetails(
config.OrganizationId,
config.IntegrationType,
EventType.Cipher_Created);
Assert.Single(result);
Assert.Same(config, result[0]);
}
[Theory, BitAutoData]
public async Task GetConfigurationDetails_BothSpecificAndAllEventsKeyExists_ReturnsExpectedList(
OrganizationIntegrationConfigurationDetails specificConfig,
OrganizationIntegrationConfigurationDetails allKeysConfig
)
{
specificConfig.EventType = EventType.Cipher_Created;
allKeysConfig.EventType = null;
allKeysConfig.OrganizationId = specificConfig.OrganizationId;
allKeysConfig.IntegrationType = specificConfig.IntegrationType;
var sutProvider = GetSutProvider([specificConfig, allKeysConfig]);
await sutProvider.Sut.RefreshAsync();
var result = sutProvider.Sut.GetConfigurationDetails(
specificConfig.OrganizationId,
specificConfig.IntegrationType,
EventType.Cipher_Created);
Assert.Equal(2, result.Count);
Assert.Contains(result, r => r.Template == specificConfig.Template);
Assert.Contains(result, r => r.Template == allKeysConfig.Template);
}
[Theory, BitAutoData]
public async Task GetConfigurationDetails_KeyMissing_ReturnsEmptyList(OrganizationIntegrationConfigurationDetails config)
{
@@ -48,10 +86,12 @@ public class IntegrationConfigurationDetailsCacheServiceTests
var result = sutProvider.Sut.GetConfigurationDetails(
Guid.NewGuid(),
config.IntegrationType,
config.EventType);
config.EventType ?? EventType.Cipher_Created);
Assert.Empty(result);
}
[Theory, BitAutoData]
public async Task GetConfigurationDetails_ReturnsCachedValue_EvenIfRepositoryChanges(OrganizationIntegrationConfigurationDetails config)
{
@@ -67,7 +107,7 @@ public class IntegrationConfigurationDetailsCacheServiceTests
var result = sutProvider.Sut.GetConfigurationDetails(
config.OrganizationId,
config.IntegrationType,
config.EventType);
config.EventType ?? EventType.Cipher_Created);
Assert.Single(result);
Assert.NotEqual("Changed", result[0].Template); // should not yet pick up change from repository
@@ -76,7 +116,7 @@ public class IntegrationConfigurationDetailsCacheServiceTests
result = sutProvider.Sut.GetConfigurationDetails(
config.OrganizationId,
config.IntegrationType,
config.EventType);
config.EventType ?? EventType.Cipher_Created);
Assert.Single(result);
Assert.Equal("Changed", result[0].Template); // Should have the new value
}
@@ -94,7 +134,7 @@ public class IntegrationConfigurationDetailsCacheServiceTests
var results = sutProvider.Sut.GetConfigurationDetails(
config1.OrganizationId,
config1.IntegrationType,
config1.EventType);
config1.EventType ?? EventType.Cipher_Created);
Assert.Equal(2, results.Count);
Assert.Contains(results, r => r.Template == config1.Template);

View File

@@ -0,0 +1,102 @@
IF EXISTS (
SELECT 1
FROM sys.columns
WHERE object_id = OBJECT_ID(N'[dbo].[OrganizationIntegrationConfiguration]')
AND name = 'EventType'
AND is_nullable = 0 -- Currently NOT NULL
)
BEGIN
ALTER TABLE [dbo].[OrganizationIntegrationConfiguration]
ALTER COLUMN [EventType] SMALLINT NULL
END
GO
CREATE OR ALTER PROCEDURE [dbo].[OrganizationIntegrationConfiguration_Create]
@Id UNIQUEIDENTIFIER OUTPUT,
@OrganizationIntegrationId UNIQUEIDENTIFIER,
@EventType SMALLINT,
@Configuration VARCHAR(MAX),
@Template VARCHAR(MAX),
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7),
@Filters VARCHAR(MAX) = NULL
AS
BEGIN
SET NOCOUNT ON
INSERT INTO [dbo].[OrganizationIntegrationConfiguration]
(
[Id],
[OrganizationIntegrationId],
[EventType],
[Configuration],
[Template],
[CreationDate],
[RevisionDate],
[Filters]
)
VALUES
(
@Id,
@OrganizationIntegrationId,
@EventType,
@Configuration,
@Template,
@CreationDate,
@RevisionDate,
@Filters
)
END
GO
CREATE OR ALTER PROCEDURE [dbo].[OrganizationIntegrationConfiguration_Update]
@Id UNIQUEIDENTIFIER OUTPUT,
@OrganizationIntegrationId UNIQUEIDENTIFIER,
@EventType SMALLINT,
@Configuration VARCHAR(MAX),
@Template VARCHAR(MAX),
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7),
@Filters VARCHAR(MAX) = NULL
AS
BEGIN
SET NOCOUNT ON
UPDATE
[dbo].[OrganizationIntegrationConfiguration]
SET
[OrganizationIntegrationId] = @OrganizationIntegrationId,
[EventType] = @EventType,
[Configuration] = @Configuration,
[Template] = @Template,
[CreationDate] = @CreationDate,
[RevisionDate] = @RevisionDate,
[Filters] = @Filters
WHERE
[Id] = @Id
END
GO
CREATE OR ALTER VIEW [dbo].[OrganizationIntegrationConfigurationView]
AS
SELECT
*
FROM
[dbo].[OrganizationIntegrationConfiguration]
GO
CREATE OR ALTER VIEW [dbo].[OrganizationIntegrationConfigurationDetailsView]
AS
SELECT
oi.[OrganizationId],
oi.[Type] AS [IntegrationType],
oic.[EventType],
oic.[Configuration],
oi.[Configuration] AS [IntegrationConfiguration],
oic.[Template],
oic.[Filters]
FROM
[dbo].[OrganizationIntegrationConfiguration] oic
INNER JOIN
[dbo].[OrganizationIntegration] oi ON oi.[Id] = oic.[OrganizationIntegrationId]
GO

View File

@@ -0,0 +1,35 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.MySqlMigrations.Migrations;
/// <inheritdoc />
public partial class AllowNullEventTypeOrganizationIntegrationConfiguration : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<int>(
name: "EventType",
table: "OrganizationIntegrationConfiguration",
type: "int",
nullable: true,
oldClrType: typeof(int),
oldType: "int");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<int>(
name: "EventType",
table: "OrganizationIntegrationConfiguration",
type: "int",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "int",
oldNullable: true);
}
}

View File

@@ -313,7 +313,7 @@ namespace Bit.MySqlMigrations.Migrations
b.Property<DateTime>("CreationDate")
.HasColumnType("datetime(6)");
b.Property<int>("EventType")
b.Property<int?>("EventType")
.HasColumnType("int");
b.Property<string>("Filters")

View File

@@ -0,0 +1,35 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.PostgresMigrations.Migrations;
/// <inheritdoc />
public partial class AllowNullEventTypeOrganizationIntegrationConfiguration : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<int>(
name: "EventType",
table: "OrganizationIntegrationConfiguration",
type: "integer",
nullable: true,
oldClrType: typeof(int),
oldType: "integer");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<int>(
name: "EventType",
table: "OrganizationIntegrationConfiguration",
type: "integer",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "integer",
oldNullable: true);
}
}

View File

@@ -316,7 +316,7 @@ namespace Bit.PostgresMigrations.Migrations
b.Property<DateTime>("CreationDate")
.HasColumnType("timestamp with time zone");
b.Property<int>("EventType")
b.Property<int?>("EventType")
.HasColumnType("integer");
b.Property<string>("Filters")

View File

@@ -0,0 +1,35 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.SqliteMigrations.Migrations;
/// <inheritdoc />
public partial class AllowNullEventTypeOrganizationIntegrationConfiguration : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<int>(
name: "EventType",
table: "OrganizationIntegrationConfiguration",
type: "INTEGER",
nullable: true,
oldClrType: typeof(int),
oldType: "INTEGER");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<int>(
name: "EventType",
table: "OrganizationIntegrationConfiguration",
type: "INTEGER",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "INTEGER",
oldNullable: true);
}
}

View File

@@ -308,7 +308,7 @@ namespace Bit.SqliteMigrations.Migrations
b.Property<DateTime>("CreationDate")
.HasColumnType("TEXT");
b.Property<int>("EventType")
b.Property<int?>("EventType")
.HasColumnType("INTEGER");
b.Property<string>("Filters")