mirror of
https://github.com/bitwarden/server
synced 2026-02-25 00:52:57 +00:00
Add CQRS and caching support for OrganizationIntegrationConfigurations (#6690)
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
|
||||
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
|
||||
/// <summary>
|
||||
/// Command implementation for creating organization integration configurations with validation and cache invalidation support.
|
||||
/// </summary>
|
||||
public class CreateOrganizationIntegrationConfigurationCommand(
|
||||
IOrganizationIntegrationRepository integrationRepository,
|
||||
IOrganizationIntegrationConfigurationRepository configurationRepository,
|
||||
[FromKeyedServices(EventIntegrationsCacheConstants.CacheName)] IFusionCache cache,
|
||||
IOrganizationIntegrationConfigurationValidator validator)
|
||||
: ICreateOrganizationIntegrationConfigurationCommand
|
||||
{
|
||||
public async Task<OrganizationIntegrationConfiguration> CreateAsync(
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegrationConfiguration configuration)
|
||||
{
|
||||
var integration = await integrationRepository.GetByIdAsync(integrationId);
|
||||
if (integration == null || integration.OrganizationId != organizationId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
if (!validator.ValidateConfiguration(integration.Type, configuration))
|
||||
{
|
||||
throw new BadRequestException(
|
||||
$"Invalid Configuration and/or Filters for integration type {integration.Type}");
|
||||
}
|
||||
|
||||
var created = await configurationRepository.CreateAsync(configuration);
|
||||
|
||||
// Invalidate the cached configuration details
|
||||
// Even though this is a new record, the cache could hold a stale empty list for this
|
||||
if (created.EventType == null)
|
||||
{
|
||||
// Wildcard configuration - invalidate all cached results for this org/integration
|
||||
await cache.RemoveByTagAsync(
|
||||
EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
organizationId: organizationId,
|
||||
integrationType: integration.Type
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Specific event type - only invalidate that specific cache entry
|
||||
await cache.RemoveAsync(
|
||||
EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
organizationId: organizationId,
|
||||
integrationType: integration.Type,
|
||||
eventType: created.EventType.Value
|
||||
));
|
||||
}
|
||||
|
||||
return created;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
|
||||
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
|
||||
/// <summary>
|
||||
/// Command implementation for deleting organization integration configurations with cache invalidation support.
|
||||
/// </summary>
|
||||
public class DeleteOrganizationIntegrationConfigurationCommand(
|
||||
IOrganizationIntegrationRepository integrationRepository,
|
||||
IOrganizationIntegrationConfigurationRepository configurationRepository,
|
||||
[FromKeyedServices(EventIntegrationsCacheConstants.CacheName)] IFusionCache cache)
|
||||
: IDeleteOrganizationIntegrationConfigurationCommand
|
||||
{
|
||||
public async Task DeleteAsync(Guid organizationId, Guid integrationId, Guid configurationId)
|
||||
{
|
||||
var integration = await integrationRepository.GetByIdAsync(integrationId);
|
||||
if (integration == null || integration.OrganizationId != organizationId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
var configuration = await configurationRepository.GetByIdAsync(configurationId);
|
||||
if (configuration is null || configuration.OrganizationIntegrationId != integrationId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
await configurationRepository.DeleteAsync(configuration);
|
||||
|
||||
if (configuration.EventType == null)
|
||||
{
|
||||
// Wildcard configuration - invalidate all cached results for this org/integration
|
||||
await cache.RemoveByTagAsync(
|
||||
EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
organizationId: organizationId,
|
||||
integrationType: integration.Type
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Specific event type - only invalidate that specific cache entry
|
||||
await cache.RemoveAsync(
|
||||
EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
organizationId: organizationId,
|
||||
integrationType: integration.Type,
|
||||
eventType: configuration.EventType.Value
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
|
||||
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
|
||||
/// <summary>
|
||||
/// Query implementation for retrieving organization integration configurations.
|
||||
/// </summary>
|
||||
public class GetOrganizationIntegrationConfigurationsQuery(
|
||||
IOrganizationIntegrationRepository integrationRepository,
|
||||
IOrganizationIntegrationConfigurationRepository configurationRepository)
|
||||
: IGetOrganizationIntegrationConfigurationsQuery
|
||||
{
|
||||
public async Task<List<OrganizationIntegrationConfiguration>> GetManyByIntegrationAsync(
|
||||
Guid organizationId,
|
||||
Guid integrationId)
|
||||
{
|
||||
var integration = await integrationRepository.GetByIdAsync(integrationId);
|
||||
if (integration == null || integration.OrganizationId != organizationId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var configurations = await configurationRepository.GetManyByIntegrationAsync(integrationId);
|
||||
return configurations.ToList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
|
||||
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Command interface for creating organization integration configurations.
|
||||
/// </summary>
|
||||
public interface ICreateOrganizationIntegrationConfigurationCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new configuration for an organization integration.
|
||||
/// </summary>
|
||||
/// <param name="organizationId">The unique identifier of the organization.</param>
|
||||
/// <param name="integrationId">The unique identifier of the integration.</param>
|
||||
/// <param name="configuration">The configuration to create.</param>
|
||||
/// <returns>The created configuration.</returns>
|
||||
/// <exception cref="Exceptions.NotFoundException">Thrown when the integration does not exist
|
||||
/// or does not belong to the specified organization.</exception>
|
||||
/// <exception cref="Exceptions.BadRequestException">Thrown when the configuration or filters
|
||||
/// are invalid for the integration type.</exception>
|
||||
Task<OrganizationIntegrationConfiguration> CreateAsync(Guid organizationId, Guid integrationId, OrganizationIntegrationConfiguration configuration);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Command interface for deleting organization integration configurations.
|
||||
/// </summary>
|
||||
public interface IDeleteOrganizationIntegrationConfigurationCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Deletes a configuration from an organization integration.
|
||||
/// </summary>
|
||||
/// <param name="organizationId">The unique identifier of the organization.</param>
|
||||
/// <param name="integrationId">The unique identifier of the integration.</param>
|
||||
/// <param name="configurationId">The unique identifier of the configuration to delete.</param>
|
||||
/// <exception cref="Exceptions.NotFoundException">
|
||||
/// Thrown when the integration or configuration does not exist,
|
||||
/// or the integration does not belong to the specified organization,
|
||||
/// or the configuration does not belong to the specified integration.</exception>
|
||||
Task DeleteAsync(Guid organizationId, Guid integrationId, Guid configurationId);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
|
||||
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Query interface for retrieving organization integration configurations.
|
||||
/// </summary>
|
||||
public interface IGetOrganizationIntegrationConfigurationsQuery
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves all configurations for a specific organization integration.
|
||||
/// </summary>
|
||||
/// <param name="organizationId">The unique identifier of the organization.</param>
|
||||
/// <param name="integrationId">The unique identifier of the integration.</param>
|
||||
/// <returns>A list of configurations associated with the integration.</returns>
|
||||
/// <exception cref="Exceptions.NotFoundException">Thrown when the integration does not exist
|
||||
/// or does not belong to the specified organization.</exception>
|
||||
Task<List<OrganizationIntegrationConfiguration>> GetManyByIntegrationAsync(Guid organizationId, Guid integrationId);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
|
||||
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Command interface for updating organization integration configurations.
|
||||
/// </summary>
|
||||
public interface IUpdateOrganizationIntegrationConfigurationCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Updates an existing configuration for an organization integration.
|
||||
/// </summary>
|
||||
/// <param name="organizationId">The unique identifier of the organization.</param>
|
||||
/// <param name="integrationId">The unique identifier of the integration.</param>
|
||||
/// <param name="configurationId">The unique identifier of the configuration to update.</param>
|
||||
/// <param name="updatedConfiguration">The updated configuration data.</param>
|
||||
/// <returns>The updated configuration.</returns>
|
||||
/// <exception cref="Exceptions.NotFoundException">
|
||||
/// Thrown when the integration or the configuration does not exist,
|
||||
/// or the integration does not belong to the specified organization,
|
||||
/// or the configuration does not belong to the specified integration.</exception>
|
||||
/// <exception cref="Exceptions.BadRequestException">Thrown when the configuration or filters
|
||||
/// are invalid for the integration type.</exception>
|
||||
Task<OrganizationIntegrationConfiguration> UpdateAsync(Guid organizationId, Guid integrationId, Guid configurationId, OrganizationIntegrationConfiguration updatedConfiguration);
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
|
||||
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
|
||||
/// <summary>
|
||||
/// Command implementation for updating organization integration configurations with validation and cache invalidation support.
|
||||
/// </summary>
|
||||
public class UpdateOrganizationIntegrationConfigurationCommand(
|
||||
IOrganizationIntegrationRepository integrationRepository,
|
||||
IOrganizationIntegrationConfigurationRepository configurationRepository,
|
||||
[FromKeyedServices(EventIntegrationsCacheConstants.CacheName)] IFusionCache cache,
|
||||
IOrganizationIntegrationConfigurationValidator validator)
|
||||
: IUpdateOrganizationIntegrationConfigurationCommand
|
||||
{
|
||||
public async Task<OrganizationIntegrationConfiguration> UpdateAsync(
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration)
|
||||
{
|
||||
var integration = await integrationRepository.GetByIdAsync(integrationId);
|
||||
if (integration == null || integration.OrganizationId != organizationId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
var configuration = await configurationRepository.GetByIdAsync(configurationId);
|
||||
if (configuration is null || configuration.OrganizationIntegrationId != integrationId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
if (!validator.ValidateConfiguration(integration.Type, updatedConfiguration))
|
||||
{
|
||||
throw new BadRequestException($"Invalid Configuration and/or Filters for integration type {integration.Type}");
|
||||
}
|
||||
|
||||
updatedConfiguration.Id = configuration.Id;
|
||||
updatedConfiguration.CreationDate = configuration.CreationDate;
|
||||
await configurationRepository.ReplaceAsync(updatedConfiguration);
|
||||
|
||||
// If either old or new EventType is null (wildcard), invalidate all cached results
|
||||
// for the specific integration
|
||||
if (configuration.EventType == null || updatedConfiguration.EventType == null)
|
||||
{
|
||||
// Wildcard involved - invalidate all cached results for this org/integration
|
||||
await cache.RemoveByTagAsync(
|
||||
EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
organizationId: organizationId,
|
||||
integrationType: integration.Type
|
||||
));
|
||||
|
||||
return updatedConfiguration;
|
||||
}
|
||||
|
||||
// Both are specific event types - invalidate specific cache entries
|
||||
await cache.RemoveAsync(
|
||||
EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
organizationId: organizationId,
|
||||
integrationType: integration.Type,
|
||||
eventType: configuration.EventType.Value
|
||||
));
|
||||
|
||||
// If event type changed, also clear the new event type's cache
|
||||
if (configuration.EventType != updatedConfiguration.EventType)
|
||||
{
|
||||
await cache.RemoveAsync(
|
||||
EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
organizationId: organizationId,
|
||||
integrationType: integration.Type,
|
||||
eventType: updatedConfiguration.EventType.Value
|
||||
));
|
||||
}
|
||||
|
||||
return updatedConfiguration;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user