Class Yiisoft\ProxyMiddleware\TrustedHostsNetworkResolver
| Inheritance | Yiisoft\ProxyMiddleware\TrustedHostsNetworkResolver |
|---|---|
| Implements | Psr\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
ForwardedRFC 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
| 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
| 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
| 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
| 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);
}
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:
When unable to resolve IP identifier, |
|---|---|---|
protected function reverseObfuscateIpIdentifier(
string $ipIdentifier,
array $validatedConnectionChainItems,
array $remainingConnectionChainItems,
RequestInterface $request,
): ?array {
return null;
}
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 |
| 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;
}
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;
}
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;
}
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;
}
Signup or Login in order to comment.