Files
Jens 7caea3a1ee
deploy / deploy (push) Canceled after 0s
demo mode
2026-07-27 23:38:37 +02:00

52 lines
1.6 KiB
Python

from __future__ import annotations
import ipaddress
from functools import lru_cache
from django.conf import settings
from django.http import HttpRequest
@lru_cache(maxsize=32)
def _trusted_networks(
values: tuple[str, ...],
) -> tuple[ipaddress.IPv4Network | ipaddress.IPv6Network, ...]:
return tuple(ipaddress.ip_network(value, strict=False) for value in values)
def _parse_ip(value: str) -> ipaddress.IPv4Address | ipaddress.IPv6Address | None:
candidate = value.strip()
if not candidate:
return None
try:
return ipaddress.ip_address(candidate)
except ValueError:
return None
def client_ip(request: HttpRequest) -> str:
"""Return a proxy-aware client IP without trusting arbitrary forwarded headers."""
remote = _parse_ip(request.META.get("REMOTE_ADDR", ""))
if remote is None:
return "unknown"
networks = _trusted_networks(tuple(getattr(settings, "TRUSTED_PROXY_CIDRS", ())))
if not networks or not any(remote in network for network in networks):
return str(remote)
forwarded = [
parsed
for item in request.META.get("HTTP_X_FORWARDED_FOR", "").split(",")
if (parsed := _parse_ip(item)) is not None
]
if not forwarded:
return str(remote)
# Walk from the nearest hop back to the client. The first address outside
# the explicitly trusted proxy ranges is the client address.
for candidate in reversed([*forwarded, remote]):
if not any(candidate in network for network in networks):
return str(candidate)
return str(forwarded[0])