blender-id/bid_main/utils.py
Oleg Komarov b494446d6b Fix current_login_ip DataError
Ignore spaces in X-Forwarded-For header.
2023-11-26 00:11:28 +01:00

71 lines
2.3 KiB
Python

# noqa: D100
from typing import Optional
import re
from django.http import HttpRequest
from django.core.validators import validate_ipv46_address
IPV4_WITH_PORT = re.compile(r"([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+):[0-9]+")
"""Regexp matching an IPv4 address with a port number."""
IPV6_WITH_PORT = re.compile(r"\[([0-9:]+)\]:[0-9]+")
"""Regexp matching an IPv6 address with a port number."""
def clean_ip_address(request: HttpRequest) -> str:
"""Retrieve a valid IP address from the given request.
Raises a django.code.exceptions.ValidationError
if no valid IP address could be determined.
"""
ip_address = get_client_ip(request)
validate_ipv46_address(ip_address)
return ip_address
def get_client_ip(request: HttpRequest) -> str:
"""Returns the IP of the request, accounting for the possibility of being behind a proxy."""
x_forwarded_for: Optional[str] = request.META.get('HTTP_X_FORWARDED_FOR', '')
# nginx's proxy_add_x_forwarded_for adds ', '
# we want to simplify the split, so removing all spaces first
x_forwarded_for = x_forwarded_for.replace(' ', '')
if x_forwarded_for:
# X_FORWARDED_FOR returns client1,proxy1,proxy2,...
remote_addr = x_forwarded_for.split(',', 1)[0].strip()
return _remove_port_nr(remote_addr)
remote_addr = request.META.get('REMOTE_ADDR', '')
if not remote_addr:
return ''
# REMOTE_ADDR can also be 'ip1,ip2' if people mess around with HTTP
# headers (we've seen this happen). Don't trust anything in that case.
if ',' in remote_addr:
return ''
return _remove_port_nr(remote_addr)
def _remove_port_nr(remote_addr: str) -> str:
# Occasionally the port number is included in REMOTE_ADDR.
# This needs to be filtered out so that downstream requests
# can just interpret the value as actual IP address.
if len(remote_addr) > 128:
# Prevent DoS attacks by not allowing obvious nonsense.
return ''
if ':' not in remote_addr:
return remote_addr
ipv4_with_port_match = IPV4_WITH_PORT.match(remote_addr)
if ipv4_with_port_match:
return ipv4_with_port_match.group(1)
ipv6_with_port_match = IPV6_WITH_PORT.match(remote_addr)
if ipv6_with_port_match:
return ipv6_with_port_match.group(1)
return remote_addr