< Summary - AsyncResponse (Release / net8.0+net10.0 / unit+integration)

Information
Class: Microsoft.Extensions.DependencyInjection.AsyncResponseCallbackAllowList.AllowListAuthorizer
Assembly: AsyncResponse.Core
File(s): /home/runner/work/AsyncResponse/AsyncResponse/src/AsyncResponse.Core/CallbackAuthorization.cs
Line coverage
100%
Covered lines: 8
Uncovered lines: 0
Coverable lines: 8
Total lines: 120
Line coverage: 100%
Branch coverage
100%
Covered branches: 6
Total branches: 6
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
IsAllowed(...)100%66100%

File(s)

/home/runner/work/AsyncResponse/AsyncResponse/src/AsyncResponse.Core/CallbackAuthorization.cs

#LineLine coverage
 1using AsyncResponse;
 2
 3namespace Microsoft.Extensions.DependencyInjection;
 4
 5/// <summary>
 6/// Builds an allowlist of callback targets for <see cref="IAsyncResponseCallbackAuthorizer"/>.
 7/// Configure it once, at the type level — no per-method attributes. A target is allowed when its
 8/// service type is allowed, or when any registered predicate accepts the <c>(type, method)</c> pair.
 9/// </summary>
 10public sealed class AsyncResponseCallbackAllowList
 11{
 12    private readonly HashSet<string> _allowedTypes = new(StringComparer.Ordinal);
 13    private readonly List<Func<string, string, bool>> _predicates = [];
 14
 15    /// <summary>
 16    /// Whether the built-in <see cref="IDurableFlowExecutor"/> is allowed as a callback target.
 17    /// Defaults to <c>true</c>: durable flows persist its methods as their resume/recover/fail
 18    /// targets, and rejecting them would break flow recovery. Note the security trade-off — an
 19    /// attacker with write access to the recovery store or worker transport can then invoke
 20    /// flow-executor methods (bounded to flow ids, terminal failure, and checkpointing a payload
 21    /// into a flow's ledger; see docs/security.md). Hosts that do not use durable flows, or that
 22    /// accept re-registering the executor themselves, can set this to <c>false</c>.
 23    /// </summary>
 24    public bool AllowDurableFlowExecutor { get; set; } = true;
 25
 26    /// <summary>Allows every callback method on the given service type.</summary>
 27    public AsyncResponseCallbackAllowList Allow(Type serviceType)
 28    {
 29        ArgumentNullException.ThrowIfNull(serviceType);
 30        if (serviceType.FullName is { } name)
 31            _allowedTypes.Add(name);
 32        return this;
 33    }
 34
 35    /// <summary>Allows every callback method on the given service type.</summary>
 36    public AsyncResponseCallbackAllowList Allow<TService>() => Allow(typeof(TService));
 37
 38    /// <summary>Allows a callback by its persisted service full name (for types not referenceable at config time).</sum
 39    public AsyncResponseCallbackAllowList Allow(string serviceInterfaceFullName)
 40    {
 41        ArgumentException.ThrowIfNullOrWhiteSpace(serviceInterfaceFullName);
 42        _allowedTypes.Add(serviceInterfaceFullName);
 43        return this;
 44    }
 45
 46    /// <summary>Allows callbacks matching a custom predicate over the <c>(serviceFullName, methodName)</c> pair.</summa
 47    public AsyncResponseCallbackAllowList Allow(Func<string, string, bool> predicate)
 48    {
 49        ArgumentNullException.ThrowIfNull(predicate);
 50        _predicates.Add(predicate);
 51        return this;
 52    }
 53
 54    internal IAsyncResponseCallbackAuthorizer Build()
 55    {
 56        // Materialized at build time (not special-cased in IsAllowed) so the executor shows up in
 57        // the same allowlist mechanism as every other target and stays overridable by config.
 58        if (AllowDurableFlowExecutor && typeof(IDurableFlowExecutor).FullName is { } executorName)
 59            _allowedTypes.Add(executorName);
 60
 61        return new AllowListAuthorizer(_allowedTypes, _predicates);
 62    }
 63
 264    private sealed class AllowListAuthorizer(HashSet<string> allowedTypes, List<Func<string, string, bool>> predicates)
 65        : IAsyncResponseCallbackAuthorizer
 66    {
 67        /// <summary>Runs the IsAllowed operation.</summary>
 68        public bool IsAllowed(string serviceInterfaceFullName, string methodName)
 69        {
 270            if (allowedTypes.Contains(serviceInterfaceFullName))
 271                return true;
 72
 273            foreach (var predicate in predicates)
 74            {
 275                if (predicate(serviceInterfaceFullName, methodName))
 276                    return true;
 77            }
 78
 279            return false;
 280        }
 81    }
 82}
 83
 84/// <summary>
 85/// Opt-in registration for callback authorization (review item 1). By default no authorizer is
 86/// registered and any DI-registered service method may be a callback target — calling these methods
 87/// is the only thing that turns on the allowlist.
 88/// </summary>
 89public static class AsyncResponseCallbackAuthorizationExtensions
 90{
 91    /// <summary>Registers an allowlist authorizer configured by <paramref name="configure"/>.</summary>
 92    public static AsyncResponseRegistrationBuilder AuthorizeCallbacks(
 93        this AsyncResponseRegistrationBuilder builder,
 94        Action<AsyncResponseCallbackAllowList> configure)
 95    {
 96        ArgumentNullException.ThrowIfNull(builder);
 97        ArgumentNullException.ThrowIfNull(configure);
 98
 99        var allowList = new AsyncResponseCallbackAllowList();
 100        configure(allowList);
 101        builder.Services.AddSingleton(allowList.Build());
 102        return builder;
 103    }
 104
 105    /// <summary>
 106    /// Registers a custom <see cref="IAsyncResponseCallbackAuthorizer"/>. Unlike the allowlist
 107    /// overload, nothing is allowed implicitly: when durable flows are enabled the authorizer must
 108    /// allow <see cref="IDurableFlowExecutor"/>, whose methods are persisted as every flow's
 109    /// resume/recover/fail targets — rejecting them breaks flow recovery.
 110    /// </summary>
 111    public static AsyncResponseRegistrationBuilder AuthorizeCallbacks(
 112        this AsyncResponseRegistrationBuilder builder,
 113        IAsyncResponseCallbackAuthorizer authorizer)
 114    {
 115        ArgumentNullException.ThrowIfNull(builder);
 116        ArgumentNullException.ThrowIfNull(authorizer);
 117        builder.Services.AddSingleton(authorizer);
 118        return builder;
 119    }
 120}