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; } }