using Bit.Core.AdminConsole.AbilitiesCache;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Entities.Provider;
using Bit.Core.AdminConsole.Models.Data.Provider;
using Bit.Core.Models.Data.Organizations;
namespace Bit.Core.Services.Implementations;
///
/// A feature-flagged routing service for application caching that bridges the gap between
/// scoped dependency injection (IFeatureService) and singleton services (cache implementations).
/// This service allows dynamic routing between IVCurrentInMemoryApplicationCacheService and
/// IVNextInMemoryApplicationCacheService based on the PM23845_VNextApplicationCache feature flag.
///
///
/// This service is necessary because:
/// - IFeatureService is registered as Scoped in the DI container
/// - IVNextInMemoryApplicationCacheService and IVCurrentInMemoryApplicationCacheService are registered as Singleton
/// - We need to evaluate feature flags at request time while maintaining singleton cache behavior
///
/// The service acts as a scoped proxy that can access the scoped IFeatureService while
/// delegating actual cache operations to the appropriate singleton implementation.
///
public class FeatureRoutedCacheService(
IFeatureService featureService,
IVNextInMemoryApplicationCacheService vNextInMemoryApplicationCacheService,
IVCurrentInMemoryApplicationCacheService inMemoryApplicationCacheService,
IApplicationCacheServiceBusMessaging serviceBusMessaging)
: IApplicationCacheService
{
public async Task> GetOrganizationAbilitiesAsync()
{
if (featureService.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache))
{
return await vNextInMemoryApplicationCacheService.GetOrganizationAbilitiesAsync();
}
return await inMemoryApplicationCacheService.GetOrganizationAbilitiesAsync();
}
public async Task GetOrganizationAbilityAsync(Guid orgId)
{
if (featureService.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache))
{
return await vNextInMemoryApplicationCacheService.GetOrganizationAbilityAsync(orgId);
}
return await inMemoryApplicationCacheService.GetOrganizationAbilityAsync(orgId);
}
public async Task> GetProviderAbilitiesAsync()
{
if (featureService.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache))
{
return await vNextInMemoryApplicationCacheService.GetProviderAbilitiesAsync();
}
return await inMemoryApplicationCacheService.GetProviderAbilitiesAsync();
}
public async Task UpsertOrganizationAbilityAsync(Organization organization)
{
if (featureService.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache))
{
await vNextInMemoryApplicationCacheService.UpsertOrganizationAbilityAsync(organization);
await serviceBusMessaging.NotifyOrganizationAbilityUpsertedAsync(organization);
}
else
{
await inMemoryApplicationCacheService.UpsertOrganizationAbilityAsync(organization);
}
}
public async Task UpsertProviderAbilityAsync(Provider provider)
{
if (featureService.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache))
{
await vNextInMemoryApplicationCacheService.UpsertProviderAbilityAsync(provider);
}
else
{
await inMemoryApplicationCacheService.UpsertProviderAbilityAsync(provider);
}
}
public async Task DeleteOrganizationAbilityAsync(Guid organizationId)
{
if (featureService.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache))
{
await vNextInMemoryApplicationCacheService.DeleteOrganizationAbilityAsync(organizationId);
await serviceBusMessaging.NotifyOrganizationAbilityDeletedAsync(organizationId);
}
else
{
await inMemoryApplicationCacheService.DeleteOrganizationAbilityAsync(organizationId);
}
}
public async Task DeleteProviderAbilityAsync(Guid providerId)
{
if (featureService.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache))
{
await vNextInMemoryApplicationCacheService.DeleteProviderAbilityAsync(providerId);
await serviceBusMessaging.NotifyProviderAbilityDeletedAsync(providerId);
}
else
{
await inMemoryApplicationCacheService.DeleteProviderAbilityAsync(providerId);
}
}
public async Task BaseUpsertOrganizationAbilityAsync(Organization organization)
{
if (featureService.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache))
{
await vNextInMemoryApplicationCacheService.UpsertOrganizationAbilityAsync(organization);
}
else
{
// NOTE: This is a temporary workaround InMemoryServiceBusApplicationCacheService legacy implementation.
// Avoid using this approach in new code.
if (inMemoryApplicationCacheService is InMemoryServiceBusApplicationCacheService serviceBusCache)
{
await serviceBusCache.BaseUpsertOrganizationAbilityAsync(organization);
}
else
{
throw new InvalidOperationException($"Expected {nameof(inMemoryApplicationCacheService)} to be of type {nameof(InMemoryServiceBusApplicationCacheService)}");
}
}
}
public async Task BaseDeleteOrganizationAbilityAsync(Guid organizationId)
{
if (featureService.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache))
{
await vNextInMemoryApplicationCacheService.DeleteOrganizationAbilityAsync(organizationId);
}
else
{
// NOTE: This is a temporary workaround InMemoryServiceBusApplicationCacheService legacy implementation.
// Avoid using this approach in new code.
if (inMemoryApplicationCacheService is InMemoryServiceBusApplicationCacheService serviceBusCache)
{
await serviceBusCache.BaseDeleteOrganizationAbilityAsync(organizationId);
}
else
{
throw new InvalidOperationException($"Expected {nameof(inMemoryApplicationCacheService)} to be of type {nameof(InMemoryServiceBusApplicationCacheService)}");
}
}
}
}