< Summary

Information
Class: Elsa.ExternalAuthentication.Validation.SystemOutboundDnsResolver
Assembly: Elsa.ExternalAuthentication
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.ExternalAuthentication/Validation/OutboundDestinationValidator.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 1
Coverable lines: 1
Total lines: 108
Line coverage: 0%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
ResolveAsync()100%210%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.ExternalAuthentication/Validation/OutboundDestinationValidator.cs

#LineLine coverage
 1using System.Net;
 2using System.Net.Sockets;
 3using Elsa.ExternalAuthentication.Options;
 4using Microsoft.Extensions.Options;
 5
 6namespace Elsa.ExternalAuthentication.Validation;
 7
 8/// <summary>
 9/// Resolves and validates provider-controlled destinations before any outbound request or connection is made.
 10/// </summary>
 11public sealed class OutboundDestinationValidator(IOptions<ExternalAuthenticationOptions> options, IOutboundDnsResolver d
 12{
 13    public async ValueTask<IReadOnlyCollection<IPAddress>> ValidateAsync(Uri destination, CancellationToken cancellation
 14    {
 15        ValidateUri(destination);
 16        var addresses = destination.HostNameType is UriHostNameType.IPv4 or UriHostNameType.IPv6
 17            ? [IPAddress.Parse(destination.Host)]
 18            : await dnsResolver.ResolveAsync(destination.DnsSafeHost, cancellationToken);
 19
 20        if (addresses.Count == 0 || !options.Value.ProviderEgress.AllowPrivateNetworkDestinations && addresses.Any(IsUns
 21            throw new OutboundDestinationException();
 22
 23        return addresses;
 24    }
 25
 26    public async ValueTask<IPAddress> ResolveApprovedAddressAsync(DnsEndPoint endpoint, CancellationToken cancellationTo
 27    {
 28        var destination = new UriBuilder(Uri.UriSchemeHttps, endpoint.Host, endpoint.Port).Uri;
 29        var addresses = await ValidateAsync(destination, cancellationToken);
 30        return addresses.First();
 31    }
 32
 33    public void ValidateApprovedProxy(Uri proxyUri)
 34    {
 35        if (!proxyUri.IsAbsoluteUri ||
 36            !string.Equals(proxyUri.Scheme, "http", StringComparison.OrdinalIgnoreCase) && !string.Equals(proxyUri.Schem
 37            !string.IsNullOrEmpty(proxyUri.UserInfo) ||
 38            !string.IsNullOrEmpty(proxyUri.Fragment) ||
 39            proxyUri.HostNameType == UriHostNameType.Unknown)
 40            throw new OutboundDestinationException();
 41    }
 42
 43    private void ValidateUri(Uri destination)
 44    {
 45        var policy = options.Value.ProviderEgress;
 46        if (!destination.IsAbsoluteUri ||
 47            policy.RequireHttps && !string.Equals(destination.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnore
 48            !string.IsNullOrEmpty(destination.UserInfo) ||
 49            !string.IsNullOrEmpty(destination.Fragment) ||
 50            destination.HostNameType == UriHostNameType.Unknown)
 51            throw new OutboundDestinationException();
 52
 53        var allowedHosts = policy.AllowedHosts ?? [];
 54        if (allowedHosts.Count > 0 && !allowedHosts.Any(host => string.Equals(host.TrimEnd('.'), destination.DnsSafeHost
 55            throw new OutboundDestinationException();
 56    }
 57
 58    private static bool IsUnsafeAddress(IPAddress address)
 59    {
 60        if (address.IsIPv4MappedToIPv6)
 61            address = address.MapToIPv4();
 62
 63        if (address.AddressFamily == AddressFamily.InterNetwork)
 64        {
 65            var bytes = address.GetAddressBytes();
 66            return bytes[0] == 0 ||
 67                bytes[0] == 10 ||
 68                bytes[0] == 100 && bytes[1] is >= 64 and <= 127 ||
 69                bytes[0] == 127 ||
 70                bytes[0] == 169 && bytes[1] == 254 ||
 71                bytes[0] == 172 && bytes[1] is >= 16 and <= 31 ||
 72                bytes[0] == 192 && (bytes[1] == 0 || bytes[1] == 168) ||
 73                bytes[0] == 192 && bytes[1] == 0 && bytes[2] == 2 ||
 74                bytes[0] == 198 && (bytes[1] is 18 or 19 || bytes[1] == 51 && bytes[2] == 100) ||
 75                bytes[0] == 203 && bytes[1] == 0 && bytes[2] == 113 ||
 76                bytes[0] >= 224;
 77        }
 78
 79        if (address.AddressFamily != AddressFamily.InterNetworkV6)
 80            return true;
 81
 82        var ipv6 = address.GetAddressBytes();
 83        return IPAddress.IsLoopback(address) ||
 84            address.Equals(IPAddress.IPv6Any) ||
 85            address.IsIPv6LinkLocal ||
 86            address.IsIPv6SiteLocal ||
 87            address.IsIPv6Multicast ||
 88            ipv6[0] is 0xfc or 0xfd ||
 89            ipv6[0] == 0x20 && ipv6[1] == 0x01 && ipv6[2] == 0x0d && ipv6[3] == 0xb8;
 90    }
 91}
 92
 93public interface IOutboundDnsResolver
 94{
 95    ValueTask<IReadOnlyCollection<IPAddress>> ResolveAsync(string host, CancellationToken cancellationToken = default);
 96}
 97
 98public sealed class SystemOutboundDnsResolver : IOutboundDnsResolver
 99{
 0100    public async ValueTask<IReadOnlyCollection<IPAddress>> ResolveAsync(string host, CancellationToken cancellationToken
 101}
 102
 103public sealed class OutboundDestinationException : InvalidOperationException
 104{
 105    public OutboundDestinationException() : base("The provider destination is not permitted.")
 106    {
 107    }
 108}

Methods/Properties

ResolveAsync()