< Summary

Information
Class: AsiBackbone.AspNetCore.Endpoints.DefaultAsiBackboneEndpointGovernanceService
Assembly: AsiBackbone.AspNetCore
File(s): /home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.AspNetCore/Endpoints/DefaultAsiBackboneEndpointGovernanceService.cs
Line coverage
63%
Covered lines: 104
Uncovered lines: 61
Coverable lines: 165
Total lines: 265
Line coverage: 63%
Branch coverage
55%
Covered branches: 29
Total branches: 52
Branch coverage: 55.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)50%1616100%
EvaluateAsync()57.14%1132852.38%
CreateBlockedDecisionResult(...)50%44100%
CreateConfigurationFailure(...)0%620%
CreateCapabilityFailure(...)100%22100%

File(s)

/home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.AspNetCore/Endpoints/DefaultAsiBackboneEndpointGovernanceService.cs

#LineLine coverage
 1using AsiBackbone.AspNetCore.Actors;
 2using AsiBackbone.AspNetCore.Correlation;
 3using AsiBackbone.AspNetCore.Handshakes;
 4using AsiBackbone.AspNetCore.Results;
 5using AsiBackbone.Core.Actors;
 6using AsiBackbone.Core.Audit;
 7using AsiBackbone.Core.Constraints;
 8using AsiBackbone.Core.Decisions;
 9using AsiBackbone.Core.Evaluation;
 10using Microsoft.AspNetCore.Http;
 11using Microsoft.Extensions.DependencyInjection;
 12using Microsoft.Extensions.Options;
 13
 14namespace AsiBackbone.AspNetCore.Endpoints;
 15
 16/// <summary>
 17/// Default host-adapter implementation for ergonomic ASP.NET Core endpoint governance.
 18/// </summary>
 19public sealed class DefaultAsiBackboneEndpointGovernanceService : IAsiBackboneEndpointGovernanceService
 20{
 21    private readonly IServiceProvider serviceProvider;
 22    private readonly IAsiBackboneHttpActorContextResolver actorContextResolver;
 23    private readonly IAsiBackboneHttpRequestCorrelationResolver requestCorrelationResolver;
 24    private readonly IAsiBackboneAcknowledgmentChallengeService acknowledgmentChallengeService;
 25    private readonly AsiBackboneEndpointGovernanceOptions endpointOptions;
 26    private readonly AsiBackboneHttpResultMappingOptions resultOptions;
 27
 28    /// <summary>
 29    /// Initializes a new instance of the <see cref="DefaultAsiBackboneEndpointGovernanceService" /> class.
 30    /// </summary>
 931    public DefaultAsiBackboneEndpointGovernanceService(
 932        IServiceProvider serviceProvider,
 933        IAsiBackboneHttpActorContextResolver actorContextResolver,
 934        IAsiBackboneHttpRequestCorrelationResolver requestCorrelationResolver,
 935        IAsiBackboneAcknowledgmentChallengeService acknowledgmentChallengeService,
 936        IOptions<AsiBackboneEndpointGovernanceOptions> endpointOptions,
 937        IOptions<AsiBackboneHttpResultMappingOptions> resultOptions)
 38    {
 939        this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
 940        this.actorContextResolver = actorContextResolver ?? throw new ArgumentNullException(nameof(actorContextResolver)
 941        this.requestCorrelationResolver = requestCorrelationResolver ?? throw new ArgumentNullException(nameof(requestCo
 942        this.acknowledgmentChallengeService = acknowledgmentChallengeService ?? throw new ArgumentNullException(nameof(a
 943        this.endpointOptions = endpointOptions?.Value ?? throw new ArgumentNullException(nameof(endpointOptions));
 944        this.resultOptions = resultOptions?.Value ?? throw new ArgumentNullException(nameof(resultOptions));
 945        this.endpointOptions.Validate();
 946        this.resultOptions.Validate();
 947    }
 48
 49    /// <inheritdoc />
 50    public async ValueTask<AsiBackboneEndpointGovernanceResult> EvaluateAsync(
 51        HttpContext httpContext,
 52        AsiBackboneEndpointGovernanceDescriptor descriptor,
 53        CancellationToken cancellationToken = default)
 54    {
 955        ArgumentNullException.ThrowIfNull(httpContext);
 956        ArgumentNullException.ThrowIfNull(descriptor);
 957        cancellationToken.ThrowIfCancellationRequested();
 58
 959        if (!descriptor.HasGovernanceMetadata)
 60        {
 061            return AsiBackboneEndpointGovernanceResult.Allow();
 62        }
 63
 964        AsiBackboneHttpRequestCorrelation correlation = requestCorrelationResolver.ResolveRequestCorrelation();
 965        IReadOnlyDictionary<string, string> endpointMetadata = descriptor.ToMetadata();
 966        AsiBackboneConstraintEvaluationContext evaluationContext = correlation.ToEvaluationContext(
 967            endpointOptions.PolicyVersion,
 968            endpointOptions.PolicyHash,
 969            endpointMetadata);
 70
 971        var decision = GovernanceDecision.Allow(
 972            correlationId: evaluationContext.CorrelationId,
 973            traceId: correlation.TraceId,
 974            policyVersion: evaluationContext.PolicyVersion,
 975            policyHash: evaluationContext.PolicyHash);
 76
 977        if (descriptor.PolicyTypes.Count > 0)
 78        {
 579            IAsiBackbonePolicyEvaluator<AsiBackboneConstraintEvaluationContext>? evaluator =
 580                httpContext.RequestServices.GetService<IAsiBackbonePolicyEvaluator<AsiBackboneConstraintEvaluationContex
 81
 582            if (evaluator is null)
 83            {
 084                return endpointOptions.FailClosedWhenPolicyEvaluatorMissing
 085                    ? CreateConfigurationFailure(
 086                        httpContext,
 087                        descriptor,
 088                        endpointMetadata,
 089                        "endpoint.policy_evaluator.missing",
 090                        "Endpoint governance policy metadata was present, but no AsiBackbone policy evaluator was regist
 091                        decision,
 092                        "aspnetcore.endpoint.governance.configuration.policy_evaluator")
 093                    : AsiBackboneEndpointGovernanceResult.Allow(decision);
 94            }
 95
 596            decision = await evaluator
 597                .EvaluateAsync(evaluationContext, cancellationToken)
 598                .ConfigureAwait(false);
 99        }
 100
 9101        if (decision.CanProceed && descriptor.CapabilityScopes.Count > 0)
 102        {
 6103            IAsiBackboneEndpointCapabilityGrantValidator? capabilityValidator = httpContext.RequestServices.GetService<I
 104
 6105            if (capabilityValidator is null)
 106            {
 4107                return endpointOptions.FailClosedWhenCapabilityValidatorMissing
 4108                    ? CreateCapabilityFailure(
 4109                        httpContext,
 4110                        descriptor,
 4111                        endpointMetadata,
 4112                        "endpoint.capability_validator.missing",
 4113                        "Endpoint capability metadata was present, but no host-owned endpoint capability validator was r
 4114                        decision,
 4115                        "aspnetcore.endpoint.governance.capability.configuration")
 4116                    : AsiBackboneEndpointGovernanceResult.Allow(decision);
 117            }
 118
 2119            decision = await capabilityValidator
 2120                .ValidateAsync(httpContext, descriptor, decision, cancellationToken)
 2121                .ConfigureAwait(false);
 122        }
 123
 5124        IAsiBackboneActorContext actor = actorContextResolver.ResolveActorContext();
 125
 5126        if (descriptor.EmitGovernanceAudit)
 127        {
 1128            IAsiBackboneAuditSink? auditSink = serviceProvider.GetService<IAsiBackboneAuditSink>();
 1129            if (auditSink is null)
 130            {
 0131                return endpointOptions.FailClosedWhenAuditSinkMissing
 0132                    ? CreateConfigurationFailure(
 0133                        httpContext,
 0134                        descriptor,
 0135                        endpointMetadata,
 0136                        "endpoint.audit_sink.missing",
 0137                        "Endpoint governance audit emission was requested, but no host-owned audit sink was registered."
 0138                        decision,
 0139                        "aspnetcore.endpoint.governance.configuration.audit_sink")
 0140                    : AsiBackboneEndpointGovernanceResult.Allow(decision);
 141            }
 142
 1143            var residue = AuditResidue.FromDecision(
 1144                actor,
 1145                descriptor.OperationName,
 1146                decision,
 1147                metadata: endpointMetadata,
 1148                decisionStage: "aspnetcore.endpoint.governance");
 149
 1150            await auditSink.WriteAsync(residue, cancellationToken).ConfigureAwait(false);
 151        }
 152
 5153        if (decision.RequiresAcknowledgment && descriptor.RequiresLiabilityHandshake)
 154        {
 0155            AsiBackboneAcknowledgmentChallenge challenge = acknowledgmentChallengeService.CreateChallenge(
 0156                actor,
 0157                descriptor.OperationName,
 0158                decision,
 0159                endpointMetadata);
 160
 0161            IResult challengeResult = Microsoft.AspNetCore.Http.Results.Json(
 0162                challenge,
 0163                statusCode: endpointOptions.AcknowledgmentChallengeStatusCode);
 164
 0165            return AsiBackboneEndpointGovernanceResult.Challenge(challenge, challengeResult, decision);
 166        }
 167
 5168        return decision.CanProceed
 5169            ? AsiBackboneEndpointGovernanceResult.Allow(decision)
 5170            : CreateBlockedDecisionResult(decision);
 9171    }
 172
 173    private AsiBackboneEndpointGovernanceResult CreateBlockedDecisionResult(GovernanceDecision decision)
 174    {
 4175        return decision.IsDenied && resultOptions.DeniedStatusCode == StatusCodes.Status403Forbidden
 4176            ? AsiBackboneEndpointGovernanceResult.BlockWithDefaultFailure(decision)
 4177            : AsiBackboneEndpointGovernanceResult.Block(decision.ToHttpResult(resultOptions), decision);
 178    }
 179
 180    private AsiBackboneEndpointGovernanceResult CreateConfigurationFailure(
 181        HttpContext httpContext,
 182        AsiBackboneEndpointGovernanceDescriptor descriptor,
 183        IReadOnlyDictionary<string, string> metadata,
 184        string code,
 185        string message,
 186        GovernanceDecision currentDecision,
 187        string decisionStage)
 188    {
 0189        var decision = GovernanceDecision.Deny(
 0190            code,
 0191            message,
 0192            correlationId: currentDecision.CorrelationId,
 0193            traceId: currentDecision.TraceId,
 0194            policyVersion: currentDecision.PolicyVersion,
 0195            policyHash: currentDecision.PolicyHash);
 196
 0197        return AsiBackboneEndpointGovernanceDevelopmentDiagnostics.IsEnabled(httpContext, endpointOptions)
 0198            ? AsiBackboneEndpointGovernanceResult.Block(
 0199                AsiBackboneEndpointGovernanceDevelopmentDiagnostics.CreateProblem(
 0200                    httpContext,
 0201                    endpointOptions,
 0202                    descriptor,
 0203                    decision,
 0204                    decisionStage,
 0205                    title: "Endpoint governance configuration is incomplete.",
 0206                    detail: message,
 0207                    statusCode: endpointOptions.ConfigurationFailureStatusCode,
 0208                    metadata: metadata),
 0209                decision)
 0210            : AsiBackboneEndpointGovernanceResult.Block(
 0211            Microsoft.AspNetCore.Http.Results.Problem(
 0212                title: "Endpoint governance configuration is incomplete.",
 0213                detail: message,
 0214                statusCode: endpointOptions.ConfigurationFailureStatusCode,
 0215                extensions: new Dictionary<string, object?>(StringComparer.Ordinal)
 0216                {
 0217                    ["reasonCodes"] = decision.ReasonCodes,
 0218                    ["outcome"] = decision.Outcome.ToString()
 0219                }),
 0220            decision);
 221    }
 222
 223    private AsiBackboneEndpointGovernanceResult CreateCapabilityFailure(
 224        HttpContext httpContext,
 225        AsiBackboneEndpointGovernanceDescriptor descriptor,
 226        IReadOnlyDictionary<string, string> metadata,
 227        string code,
 228        string message,
 229        GovernanceDecision currentDecision,
 230        string decisionStage)
 231    {
 4232        var decision = GovernanceDecision.Deny(
 4233            code,
 4234            message,
 4235            correlationId: currentDecision.CorrelationId,
 4236            traceId: currentDecision.TraceId,
 4237            policyVersion: currentDecision.PolicyVersion,
 4238            policyHash: currentDecision.PolicyHash);
 239
 4240        return AsiBackboneEndpointGovernanceDevelopmentDiagnostics.IsEnabled(httpContext, endpointOptions)
 4241            ? AsiBackboneEndpointGovernanceResult.Block(
 4242                AsiBackboneEndpointGovernanceDevelopmentDiagnostics.CreateProblem(
 4243                    httpContext,
 4244                    endpointOptions,
 4245                    descriptor,
 4246                    decision,
 4247                    decisionStage,
 4248                    title: "Endpoint capability grant validation failed.",
 4249                    detail: message,
 4250                    statusCode: endpointOptions.CapabilityFailureStatusCode,
 4251                    metadata: metadata),
 4252                decision)
 4253            : AsiBackboneEndpointGovernanceResult.Block(
 4254            Microsoft.AspNetCore.Http.Results.Problem(
 4255                title: "Endpoint capability grant validation failed.",
 4256                detail: message,
 4257                statusCode: endpointOptions.CapabilityFailureStatusCode,
 4258                extensions: new Dictionary<string, object?>(StringComparer.Ordinal)
 4259                {
 4260                    ["reasonCodes"] = decision.ReasonCodes,
 4261                    ["outcome"] = decision.Outcome.ToString()
 4262                }),
 4263            decision);
 264    }
 265}