using Elsa.Extensions;
using Elsa.ExternalAuthentication.Contracts;
using Elsa.ExternalAuthentication.Options;
using Elsa.ExternalAuthentication.Permissions;
using Elsa.ExternalAuthentication.Policies;
using Elsa.ExternalAuthentication.Providers;
using Elsa.ExternalAuthentication.Services;
using Elsa.ExternalAuthentication.Stores.InMemory;
using Elsa.ExternalAuthentication.Validation;
using Elsa.Identity.Contracts;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
namespace Microsoft.Extensions.DependencyInjection;
public static class ServiceCollectionExtensions
{
/// Adds the explicit, non-readiness External Authentication health bridge.
public static IHealthChecksBuilder AddExternalAuthenticationHealthCheck(this IServiceCollection services, string name = "external-authentication", IEnumerable? tags = null) =>
services.AddHealthChecks().AddCheck(name, HealthStatus.Degraded, tags ?? ["external-authentication", "optional"]);
///
/// Adds the protocol-neutral External Authentication foundation and its single-node defaults.
/// Hosts requiring durable, multi-node state may replace the store registrations.
///
public static IServiceCollection AddExternalAuthenticationServices(this IServiceCollection services, Action? configureOptions = null)
{
var options = services.AddOptions().ValidateOnStart();
if (configureOptions != null)
options.Configure(configureOptions);
// The module evaluates permissions outside endpoint authorization -- delegation, the grant boundary,
// and the recovery override -- so it depends on the evaluator whether or not a host wired one up.
// The call is TryAdd-based and idempotent, so a host that already registered one keeps it.
services.AddElsaAuthorization();
// Contributed explicitly rather than left to the host's assembly scan, so the module's resources reach
// the catalog on any host that registers its services, the same reason AddElsaAuthorization is called
// here. Registration is TryAddEnumerable-backed, so a host that also scans this assembly gets one copy.
services.AddPermissionDescriptors();
services.AddExternalAuthenticationExtension(ExternalAuthenticationExtensionKind.UnlinkedIdentityPolicy, RejectUnlinkedIdentityPolicy.PolicyType);
services.AddExternalAuthenticationExtension(ExternalAuthenticationExtensionKind.UnlinkedIdentityPolicy, CreateUserUnlinkedIdentityPolicy.PolicyType);
services.AddExternalAuthenticationExtension(ExternalAuthenticationExtensionKind.UnlinkedIdentityPolicy, MatchExternalUserUnlinkedIdentityPolicy.PolicyType);
services.AddExternalAuthenticationExtension(ExternalAuthenticationExtensionKind.PermissionGrantSource, ElsaRolePermissionGrantSource.SourceType);
services.AddExternalAuthenticationExtension(ExternalAuthenticationExtensionKind.PermissionGrantSource, ClaimMappingPermissionGrantSource.SourceType);
services.AddExternalAuthenticationExtension(ExternalAuthenticationExtensionKind.PermissionGrantSource, GroupMappingPermissionGrantSource.SourceType);
services.AddExternalAuthenticationExtension(ExternalAuthenticationExtensionKind.PermissionGrantSource, ClaimPassThroughPermissionGrantSource.SourceType);
// The validator warns about grant-boundary configuration, and ValidateOnStart resolves it on any
// IOptions access, so a logger has to be resolvable even on a bare service collection. AddLogging is
// TryAdd-based, so a host that already configured logging keeps its own.
services.AddLogging();
services.TryAddEnumerable(ServiceDescriptor.Singleton, ExternalAuthenticationOptionsValidator>());
services.AddDataProtection();
services.AddRateLimiter(_ => { });
services.TryAddEnumerable(ServiceDescriptor.Singleton, ConfigureExternalAuthenticationRateLimiterOptions>());
services.TryAddSingleton();
services.TryAddSingleton();
services.TryAddSingleton();
services.TryAddScoped();
services.TryAddScoped();
services.TryAddSingleton();
services.TryAddSingleton();
services.TryAddSingleton();
services.TryAddSingleton();
services.TryAddSingleton();
services.TryAddSingleton();
services.TryAddEnumerable(ServiceDescriptor.Singleton());
services.TryAddSingleton();
services.TryAddEnumerable(ServiceDescriptor.Singleton());
services.TryAddSingleton();
services.TryAddSingleton();
services.TryAddSingleton();
services.TryAddSingleton();
services.TryAddSingleton();
services.TryAddSingleton();
services.TryAddScoped();
services.TryAddSingleton();
services.TryAddSingleton();
services.TryAddSingleton();
services.TryAddSingleton();
services.TryAddSingleton();
services.TryAddSingleton();
services.TryAddSingleton();
services.TryAddSingleton();
services.TryAddSingleton();
services.TryAddScoped();
services.TryAddScoped(serviceProvider => serviceProvider.GetRequiredService());
services.TryAddScoped(serviceProvider => serviceProvider.GetRequiredService());
services.TryAddScoped();
services.TryAddScoped();
services.TryAddScoped();
services.TryAddScoped();
services.TryAddEnumerable(ServiceDescriptor.Singleton());
services.TryAddEnumerable(ServiceDescriptor.Singleton());
services.TryAddEnumerable(ServiceDescriptor.Singleton());
services.TryAddEnumerable(ServiceDescriptor.Singleton());
services.TryAddEnumerable(ServiceDescriptor.Scoped());
services.TryAddEnumerable(ServiceDescriptor.Scoped());
services.TryAddEnumerable(ServiceDescriptor.Scoped());
services.TryAddEnumerable(ServiceDescriptor.Scoped());
services.TryAddScoped();
services.TryAddScoped();
services.TryAddScoped();
services.TryAddEnumerable(ServiceDescriptor.Scoped());
services.TryAddEnumerable(ServiceDescriptor.Scoped());
return services;
}
///
/// Registers the stable identifier of a trusted deployment-installed extension
/// for startup selection validation.
///
public static IServiceCollection AddExternalAuthenticationExtension(
this IServiceCollection services,
ExternalAuthenticationExtensionKind kind,
string type)
{
ArgumentException.ThrowIfNullOrWhiteSpace(type);
services.Configure(options =>
options.Registrations.Add(new(kind, type)));
return services;
}
}