< Summary - Heimdall API

Information
Class: ArturRios.Heimdall.WebApi.WebApiServiceCollectionExtensions
Assembly: ArturRios.Heimdall.WebApi
File(s): /home/ci/agent/workspace/heimdall-api-docs_main/app/src/Presentation/ArturRios.Heimdall.WebApi/Registration/WebApiServiceCollectionExtensions.cs
Line coverage
100%
Covered lines: 62
Uncovered lines: 0
Coverable lines: 62
Total lines: 131
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage
100%
Covered methods: 4
Fully covered methods: 4
Total methods: 4
Method coverage: 100%
Full method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
AddRequestContext(...)100%11100%
AddHealthProbes(...)100%11100%
AddTokenIssuing(...)100%11100%
AddHeimdallControllers(...)100%11100%

File(s)

/home/ci/agent/workspace/heimdall-api-docs_main/app/src/Presentation/ArturRios.Heimdall.WebApi/Registration/WebApiServiceCollectionExtensions.cs

#LineLine coverage
 1using ArturRios.Data.Relational.Core.DependencyInjection;
 2using ArturRios.Heimdall.Command.Services;
 3using ArturRios.Heimdall.Query.Handlers;
 4using ArturRios.Heimdall.Query.HealthChecks;
 5using ArturRios.Heimdall.Query.Input;
 6using ArturRios.Heimdall.Query.Output;
 7using ArturRios.Heimdall.Shared.Security;
 8using ArturRios.Heimdall.Shared.Services;
 9using ArturRios.Heimdall.WebApi.Binding;
 10using ArturRios.Heimdall.WebApi.Monitoring;
 11using ArturRios.Heimdall.WebApi.Security;
 12using ArturRios.Mediator.Query.Interfaces;
 13using ArturRios.Util.WebApi.Configuration;
 14using ArturRios.Util.WebApi.Security.Extensions;
 15
 16namespace ArturRios.Heimdall.WebApi;
 17
 18public static class WebApiServiceCollectionExtensions
 19{
 20    /// <summary>
 21    ///     Registers the accessors that read the acting person and the retry hint from the current HTTP
 22    ///     request.
 23    /// </summary>
 24    public static IServiceCollection AddRequestContext(this IServiceCollection services)
 25    {
 97326        services.AddHttpContextAccessor();
 97327        services.AddScoped<IActorAccessor, HttpContextActorAccessor>();
 97328        services.AddScoped<IRetryAfterAdvisor, HttpResponseRetryAfterAdvisor>();
 29
 97330        return services;
 31    }
 32
 33    /// <summary>
 34    ///     Registers the health checks (UC-30) and the readiness probe's cache (#215).
 35    /// </summary>
 36    public static IServiceCollection AddHealthProbes(this IServiceCollection services)
 37    {
 38        // Health checks (UC-30). Each IServiceHealthCheck is one verified dependency; the detailed
 39        // handler resolves them all as IEnumerable, so new checks are added by registering another.
 40        // The readiness probe (#215) asks the database check alone, so it is also resolvable as itself.
 97341        services.AddSingleton(TimeProvider.System);
 97342        services.AddSingleton<ReadinessCache>();
 97343        services.AddScoped<DatabaseHealthCheck>();
 97544        services.AddScoped<IServiceHealthCheck>(provider => provider.GetRequiredService<DatabaseHealthCheck>());
 97345        services
 97346            .AddScoped<IQueryHandlerAsync<DetailedHealthQuery, HealthCheckOutput>, GetDetailedHealthQueryHandler>();
 47
 97348        return services;
 49    }
 50
 51    /// <summary>
 52    ///     Registers the person, challenge and ownership services that issue and check tokens.
 53    /// </summary>
 54    public static IServiceCollection AddTokenIssuing(this IServiceCollection services)
 55    {
 97356        services.AddScoped<IScopeOwnershipChecker, ScopeOwnershipChecker>();
 57
 58        // UC-11 issues tokens through the same claims mapper the middleware validates them with,
 59        // registered by AddTokenAuthentication in TokenSecurityRegistrationExtensions.AddTokenSecurity.
 97360        services.AddScoped<IAuthTokenIssuer, JwtAuthTokenIssuer>();
 61        // Shared by LoginCommandHandler (UC-11) and VerifyTwoFactorAuthCommandHandler (UC-38) so the
 62        // scope-eligibility rules and the final token-issuing call live in one place.
 97363        services.AddScoped<PersonAuthTokenService>();
 64        // UC-38's challenge token (FR-2F-07…FR-2F-10, NFR-17): one class both issues and validates
 65        // it, registered once and exposed through both of its interfaces.
 97366        services.AddScoped<JwtTwoFactorChallengeTokenIssuer>();
 97367        services.AddScoped<ITwoFactorChallengeTokenIssuer>(
 108268            provider => provider.GetRequiredService<JwtTwoFactorChallengeTokenIssuer>());
 97369        services.AddScoped<ITwoFactorChallengeTokenValidator>(
 106370            provider => provider.GetRequiredService<JwtTwoFactorChallengeTokenIssuer>());
 71
 97372        return services;
 73    }
 74
 75    /// <summary>
 76    ///     Adds the MVC configuration Heimdall applies on top of the standard sequence's controllers.
 77    /// </summary>
 78    public static IServiceCollection AddHeimdallControllers(this IServiceCollection services)
 79    {
 80        // The standard sequence has already called AddControllers(); calling it again adds this
 81        // configuration to the same MVC registration.
 82        //
 83        // MfaPendingGuardFilter (FR-2F-10, NFR-17) runs as a global MVC authorization filter, on
 84        // every controller action — added here rather than per-action, since a UC-38 challenge token
 85        // must be rejected everywhere except POST /api/auth/2fa/verify, and that endpoint needs no
 86        // opt-out: it never reads the challenge token as a bearer credential to begin with.
 97387        services.AddControllers(options =>
 97388        {
 96689            options.Filters.Add<MfaPendingGuardFilter>();
 97390
 97391            // NFR-24: a subject-rights-only token, issued to a restricted identity at sign-in, is
 97392            // confined to the data-subject-rights actions — on its claims alone, so before the
 97393            // liveness check's database read, like the guard above.
 96694            options.Filters.Add<SubjectRightsOnlyGuardFilter>();
 97395
 97396            // Runs after the guard above, which is the cheap check: a challenge token misused as a
 97397            // bearer credential is rejected on its claims alone, without spending the database read
 97398            // this one makes. Global for the same reason — a token naming a deleted identity must be
 97399            // refused everywhere, and leaving that to each handler is what let a logically deleted
 973100            // System Admin keep acting until their token expired.
 966101            options.Filters.Add<ActorLivenessFilter>();
 973102
 973103            // Turns a saturated password-hash gate into 503 rather than 500 (TH-03). An exception
 973104            // filter because the condition is cross-cutting: every endpoint that derives a password
 973105            // can meet it, and each has its own message map.
 966106            options.Filters.Add<PasswordHashSaturationFilter>();
 973107
 973108            // A server-side line for every 5xx the pipeline produces as a result, which nothing else
 973109            // writes (#227); an unhandled exception is already logged by ExceptionMiddleware.
 966110            options.Filters.Add<ServerErrorLoggingFilter>();
 973111
 973112            // NFR-10 (#186): a request MVC could not bind answers 400 instead of running the action with
 973113            // a null or default argument. An action filter, so after the authorization filters above —
 973114            // an unauthenticated caller still gets 401 — and after model binding has filled ModelState.
 966115            options.Filters.Add<MalformedRequestFilter>();
 973116
 973117            // Also applied by ArturRios.Heimdall.OpenApiGen/Program.cs, which builds its own
 973118            // AddControllers() rather than running this Startup, so that call site cannot catch a
 973119            // removal here. Without it, [FromQuery] list-query properties marked server-populated
 973120            // would become bindable from the query string again on the running API — the controllers
 973121            // still overwrite them before use, so the query-string forgery tests would keep passing,
 973122            // and the published document (generated from the other call site) would look unchanged.
 973123            // The guarantee that these properties are non-bindable, not merely undocumented, would
 973124            // silently revert with no test catching it.
 966125            ModelBindingConfiguration.Configure(options);
 1939126        });
 973127        services.AddEndpointsApiExplorer();
 128
 973129        return services;
 130    }
 131}