0 follower

Class Yiisoft\ProxyMiddleware\TrustedHostsNetworkResolver

InheritanceYiisoft\ProxyMiddleware\TrustedHostsNetworkResolver
ImplementsPsr\Http\Server\MiddlewareInterface

Scans the entire connection chain and resolves the data from forwarded headers taking into account trusted IPs.

Additionally, all items' structure is thoroughly validated because headers' data can't be trusted. The following data is resolved:

  • IP.
  • Protocol.
  • Host.
  • Port.
  • IP identifier - unknown or obfuscated. Used with Forwarded RFC header.

The typical use case is having an application behind a load balancer.

Psalm Types

Name Value
ProtocolResolvingMapping array<non-empty-string, \Yiisoft\ProxyMiddleware\TrustedHostsNetworkResolver::PROTOCOL_*>
ProtocolResolvingCallable callable
ProtocolConfig lowercase-string|array{0: lowercase-string, 1: \Yiisoft\ProxyMiddleware\ProtocolResolvingMapping|\Yiisoft\ProxyMiddleware\ProtocolResolvingCallable}
SeparateForwardedHeaderGroup array{ip: lowercase-string, protocol: \Yiisoft\ProxyMiddleware\ProtocolConfig, host: lowercase-string, port: lowercase-string}
ForwardedHeaderGroup \Yiisoft\ProxyMiddleware\TrustedHostsNetworkResolver::FORWARDED_HEADER_GROUP_RFC|\Yiisoft\ProxyMiddleware\SeparateForwardedHeaderGroup
ForwardedHeaderGroups non-empty-list<\Yiisoft\ProxyMiddleware\ForwardedHeaderGroup>
RawConnectionChainItem array{ip: non-empty-string|null, protocol: \Yiisoft\ProxyMiddleware\TrustedHostsNetworkResolver::PROTOCOL_*|null, host: non-empty-string|null, port: integer|null, ipIdentifier: non-empty-string|null}
ConnectionChainItem array{ip: non-empty-string, protocol: \Yiisoft\ProxyMiddleware\TrustedHostsNetworkResolver::PROTOCOL_*|null, host: non-empty-string|null, port: integer|null, ipIdentifier: non-empty-string|null}

Public Methods

Hide inherited methods

Method Description Defined By
__construct() Yiisoft\ProxyMiddleware\TrustedHostsNetworkResolver
process() Yiisoft\ProxyMiddleware\TrustedHostsNetworkResolver
withConnectionChainItemsAttribute() Returns a new instance with changed name of request's attribute for storing validated and trusted connection chain items. Yiisoft\ProxyMiddleware\TrustedHostsNetworkResolver
withForwardedHeaderGroups() Returns a new instance with changed list of forwarded header groups to parse the data from. By including headers in this list, they are trusted automatically. Yiisoft\ProxyMiddleware\TrustedHostsNetworkResolver
withTrustedIps() Returns a new instance with changed list of connection chain trusted IPs Yiisoft\ProxyMiddleware\TrustedHostsNetworkResolver
withTypicalForwardedHeaders() Returns a new instance with changed list of headers that are considered related to forwarding. Yiisoft\ProxyMiddleware\TrustedHostsNetworkResolver

Protected Methods

Hide inherited methods

Method Description Defined By
reverseObfuscateIpIdentifier() A method intended to be overridden in user class for resolving obfuscated IP identifier obtained from {@see FORWARDED_HEADER_RFC}. Yiisoft\ProxyMiddleware\TrustedHostsNetworkResolver

Constants

Hide inherited constants

Constant Value Description Defined By
ALLOWED_PROTOCOLS [ self::PROTOCOL_HTTP, self::PROTOCOL_HTTPS, ] Yiisoft\ProxyMiddleware\TrustedHostsNetworkResolver
ALLOWED_RFC_HEADER_DIRECTIVES [ 'by', 'for', 'proto', 'host', ] Yiisoft\ProxyMiddleware\TrustedHostsNetworkResolver
ATTRIBUTE_REQUEST_CLIENT_IP 'requestClientIp' The name of request's attribute for storing resolved connection chain item's IP. Yiisoft\ProxyMiddleware\TrustedHostsNetworkResolver
DEFAULT_FORWARDED_HEADER_GROUPS [ self::FORWARDED_HEADER_GROUP_RFC, self::FORWARDED_HEADER_GROUP_X_PREFIX, ] Default value for {@see $forwardedHeaderGroups}. Yiisoft\ProxyMiddleware\TrustedHostsNetworkResolver
FORWARDED_HEADER_GROUP_RFC self::FORWARDED_HEADER_RFC The forwarded header group containing forwarded header according to RFC 7239 for including in {@see $forwardedHeaderGroups}. In this case the group contains only 1 header with all the data - {@see FORWARDED_HEADER_RFC}. Yiisoft\ProxyMiddleware\TrustedHostsNetworkResolver
FORWARDED_HEADER_GROUP_X_PREFIX [ 'ip' => 'x-forwarded-for', 'protocol' => 'x-forwarded-proto', 'host' => 'x-forwarded-host', 'port' => 'x-forwarded-port', ] The forwarded header group containing headers with "X" prefix for including in {@see $forwardedHeaderGroups}. In this case, the group contains multiple headers and the data is passed separately among them. Yiisoft\ProxyMiddleware\TrustedHostsNetworkResolver
FORWARDED_HEADER_RFC 'forwarded' The name of forwarded header according to RFC 7239. Yiisoft\ProxyMiddleware\TrustedHostsNetworkResolver
PORT_MAX 65535 Yiisoft\ProxyMiddleware\TrustedHostsNetworkResolver
PORT_MIN 1 Yiisoft\ProxyMiddleware\TrustedHostsNetworkResolver
PROTOCOL_HTTP 'http' Yiisoft\ProxyMiddleware\TrustedHostsNetworkResolver
PROTOCOL_HTTPS 'https' Yiisoft\ProxyMiddleware\TrustedHostsNetworkResolver
TYPICAL_FORWARDED_HEADERS [ 'forwarded', 'x-forwarded-for', 'x-forwarded-host', 'x-forwarded-proto', 'x-forwarded-port', 'front-end-https', ] Default value for {@see $typicalForwardedHeaders}. Yiisoft\ProxyMiddleware\TrustedHostsNetworkResolver

Method Details

Hide inherited methods

__construct() public method

public mixed __construct ( )

                public function __construct()
{
}

            
process() public method

public process ( \Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Server\RequestHandlerInterface $handler )
$request \Psr\Http\Message\ServerRequestInterface
$handler \Psr\Http\Server\RequestHandlerInterface
throws RuntimeException

When value returned from protocol resolving callable in {@see $forwardedHeaderGroups} or overridden {@see \Yiisoft\ProxyMiddleware\reverseObfuscateIpIdentifier} method is incorrect.

throws Yiisoft\ProxyMiddleware\Exception\RfcProxyParseException

When parsing of {@see \Yiisoft\ProxyMiddleware\FORWARDED_HEADER_RFC} failed.

throws Yiisoft\ProxyMiddleware\Exception\InvalidConnectionChainItemException

When resolved data of connection chain item (IP, protocol, host or port) is invalid.

                public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
    /** @var string|null $remoteAddr */
    $remoteAddr = $request->getServerParams()['REMOTE_ADDR'] ?? null;
    if (empty($remoteAddr)) {
        return $this->handleNotTrusted($request, $handler);
    }
    [$forwardedHeaderGroup, $connectionChainItems] = $this->getConnectionChainItems($remoteAddr, $request);
    $request = $this->filterTypicalForwardedHeaders($forwardedHeaderGroup, $request);
    $validatedConnectionChainItems = [];
    $connectionChainItem = $this->iterateConnectionChainItems(
        $connectionChainItems,
        $validatedConnectionChainItems,
        $request,
    );
    if ($this->connectionChainItemsAttribute !== null) {
        $request = $request->withAttribute($this->connectionChainItemsAttribute, $validatedConnectionChainItems);
    }
    $uri = $request->getUri();
    $protocol = $connectionChainItem['protocol'];
    if ($protocol !== null) {
        $uri = $uri->withScheme($protocol);
    }
    $host = $connectionChainItem['host'];
    if ($host !== null) {
        $uri = $uri->withHost($host);
    }
    $port = $connectionChainItem['port'];
    if ($port !== null) {
        $uri = $uri->withPort($port);
    }
    $request = $request
        ->withUri($uri)
        ->withAttribute(self::ATTRIBUTE_REQUEST_CLIENT_IP, $connectionChainItem['ip']);
    return $handler->handle($request);
}

            
reverseObfuscateIpIdentifier() protected method

A method intended to be overridden in user class for resolving obfuscated IP identifier obtained from {@see FORWARDED_HEADER_RFC}.

See also https://github.com/yiisoft/proxy-middleware#reverse-obfuscating-ip-identifier For example.

protected array|null reverseObfuscateIpIdentifier ( string $ipIdentifier, array $validatedConnectionChainItems, array $remainingConnectionChainItems, \Psr\Http\Message\RequestInterface $request )
$ipIdentifier string

Obfuscated IP identifier.

$validatedConnectionChainItems array

Connection chain items that are already trusted and passed the validation.

$remainingConnectionChainItems array

Connection chain items that are left to check whether they are valid and can be trusted.

$request \Psr\Http\Message\RequestInterface
return array|null

A list with 2 items where:

  • 1st item is expected to be IP;
  • 2nd item is expected to be port (or null when there is no port).

When unable to resolve IP identifier, null must be returned.

                protected function reverseObfuscateIpIdentifier(
    string $ipIdentifier,
    array $validatedConnectionChainItems,
    array $remainingConnectionChainItems,
    RequestInterface $request,
): ?array {
    return null;
}

            
withConnectionChainItemsAttribute() public method

Returns a new instance with changed name of request's attribute for storing validated and trusted connection chain items.

See also https://github.com/yiisoft/proxy-middleware#accessing-resolved-data For example.

public self withConnectionChainItemsAttribute ( string|null $attribute )
$attribute string|null

The name of request's attribute. Can be set to null to disable saving completely.

return self

New instance.

throws InvalidArgumentException

When attribute name is empty.

                public function withConnectionChainItemsAttribute(?string $attribute): self
{
    if (is_string($attribute)) {
        $this->assertNonEmpty($attribute, 'Attribute');
    }
    /** @var ?non-empty-string $attribute */
    $new = clone $this;
    $new->connectionChainItemsAttribute = $attribute;
    return $new;
}

            
withForwardedHeaderGroups() public method

Returns a new instance with changed list of forwarded header groups to parse the data from. By including headers in this list, they are trusted automatically.

The header groups are processed in the order they are defined. If the header containing IP is present and is non-empty, this group will be selected and further ones will be ignored.

See also https://github.com/yiisoft/proxy-middleware#forwarded-header-groups For detailed explanation and examples.

public self withForwardedHeaderGroups ( array $headerGroups )
$headerGroups array

List of forwarded header groups.

return self

New instance.

throws InvalidArgumentException

When the structure/contents of header groups is incorrect.

                public function withForwardedHeaderGroups(array $headerGroups): self
{
    $this->assertNonEmpty($headerGroups, 'Forwarded header groups');
    /** @psalm-var non-empty-array $headerGroups */
    $allowedHeaderGroupKeys = ['ip', 'protocol', 'host', 'port'];
    $validatedHeaderGroups = [];
    foreach ($headerGroups as $headerGroup) {
        if ($headerGroup === self::FORWARDED_HEADER_GROUP_RFC) {
            $validatedHeaderGroups[] = $headerGroup;
        } elseif (is_array($headerGroup)) {
            $this->assertNonEmpty($headerGroup, 'Forwarded header group array');
            $this->assertExactKeysForArray($allowedHeaderGroupKeys, $headerGroup, 'forwarded header group');
            $validatedHeaderGroup = [];
            foreach (['ip', 'host', 'port'] as $key) {
                $this->assertIsNonEmptyString($headerGroup[$key], "Header name for \"$key\"");
                $validatedHeaderGroup[$key] = $this->normalizeHeaderName($headerGroup[$key]);
            }
            if (is_string($headerGroup['protocol'])) {
                $this->assertNonEmpty($headerGroup['protocol'], 'Header name for "protocol"');
                $validatedHeaderGroup['protocol'] = $this->normalizeHeaderName($headerGroup['protocol']);
            } elseif (is_array($headerGroup['protocol'])) {
                $this->assertNonEmpty($headerGroup['protocol'], 'Protocol header config array');
                $this->assertExactKeysForArray([0, 1], $headerGroup['protocol'], 'protocol header config');
                $this->assertIsNonEmptyString($headerGroup['protocol'][0], 'Header name for "protocol"');
                $headerName = $this->normalizeHeaderName($headerGroup['protocol'][0]);
                if (is_array($headerGroup['protocol'][1])) {
                    $this->assertNonEmpty($headerGroup['protocol'][1], 'Values in mapping for protocol header');
                    foreach ($headerGroup['protocol'][1] as $key => $value) {
                        $this->assertIsNonEmptyString($key, 'Key in mapping for protocol header');
                        $this->assertIsAllowedProtocol($value, 'Value in mapping for protocol header');
                    }
                } elseif (!is_callable($headerGroup['protocol'][1])) {
                    $message = 'Protocol header resolving must be specified either via an associative array or a ' .
                        'callable.';
                    throw new InvalidArgumentException($message);
                }
                $validatedHeaderGroup['protocol'] = [$headerName, $headerGroup['protocol'][1]];
            } else {
                throw new InvalidArgumentException('Protocol header config must be either a string or an array.');
            }
            /** @psalm-var SeparateForwardedHeaderGroup $validatedHeaderGroup */
            $validatedHeaderGroups[] = $validatedHeaderGroup;
        } else {
            $message = 'Forwarded header group must be either an associative array or ' .
                'TrustedHostsNetworkResolver::FORWARDED_HEADER_GROUP_RFC constant.';
            throw new InvalidArgumentException($message);
        }
    }
    $new = clone $this;
    $new->forwardedHeaderGroups = $validatedHeaderGroups;
    return $new;
}

            
withTrustedIps() public method

Returns a new instance with changed list of connection chain trusted IPs

See also https://github.com/yiisoft/proxy-middleware#trusted-ips For detailed explanation and example.

public self withTrustedIps ( array $trustedIps )
$trustedIps array

List of connection chain trusted IPs.

return self

New instance.

throws InvalidArgumentException

When list contains invalid IPs.

                public function withTrustedIps(array $trustedIps): self
{
    $validatedIps = [];
    foreach ($trustedIps as $ip) {
        $this->assertIsNonEmptyString($ip, 'Trusted IP');
        if (!IpValidator::isIp($ip)) {
            throw new InvalidArgumentException("\"$ip\" is not a valid IP.");
        }
        $validatedIps[] = $ip;
    }
    $new = clone $this;
    $new->trustedIps = $validatedIps;
    return $new;
}

            
withTypicalForwardedHeaders() public method

Returns a new instance with changed list of headers that are considered related to forwarding.

The headers that are present in this list but missing in a matching forwarded header group will be deleted from request because they are potentially not secure and likely were not passed by a proxy server.

See also https://github.com/yiisoft/proxy-middleware#typical-forwarded-headers For example.

public self withTypicalForwardedHeaders ( array $headerNames )
$headerNames array

List of headers that are considered related to forwarding. Header names are case-insensitive.

return self

New instance.

throws InvalidArgumentException

When list is empty or header within it is empty.

                public function withTypicalForwardedHeaders(array $headerNames): self
{
    $this->assertNonEmpty($headerNames, 'Typical forwarded headers');
    $normalizedHeaderNames = [];
    foreach ($headerNames as $headerName) {
        $this->assertIsNonEmptyString($headerName, 'Typical forwarded header');
        $normalizedHeaderNames[] = $this->normalizeHeaderName($headerName);
    }
    $new = clone $this;
    $new->typicalForwardedHeaders = $normalizedHeaderNames;
    return $new;
}