From cbf020cdf955e6342b3c7fbf11c86f77639ce41e Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Thu, 7 Dec 2023 20:15:30 +0530 Subject: [PATCH] Fixes IPv6 detection from headers (#14456) * fixes client ip detection for v6 * adds test for get_client_ip * Employ urlparse() to strip port numbers from IPs --------- Co-authored-by: Jeremy Stretch --- netbox/utilities/request.py | 18 ++++++++++++----- netbox/utilities/tests/test_request.py | 28 ++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 netbox/utilities/tests/test_request.py diff --git a/netbox/utilities/request.py b/netbox/utilities/request.py index 0f8ee9cae..a5ca145e9 100644 --- a/netbox/utilities/request.py +++ b/netbox/utilities/request.py @@ -1,4 +1,5 @@ -from netaddr import IPAddress +from netaddr import AddrFormatError, IPAddress +from urllib.parse import urlparse __all__ = ( 'get_client_ip', @@ -17,11 +18,18 @@ def get_client_ip(request, additional_headers=()): ) for header in HTTP_HEADERS: if header in request.META: - client_ip = request.META[header].split(',')[0].partition(':')[0] + ip = request.META[header].split(',')[0].strip() try: - return IPAddress(client_ip) - except ValueError: - raise ValueError(f"Invalid IP address set for {header}: {client_ip}") + return IPAddress(ip) + except AddrFormatError: + # Parse the string with urlparse() to remove port number or any other cruft + ip = urlparse(f'//{ip}').hostname + + try: + return IPAddress(ip) + except AddrFormatError: + # We did our best + raise ValueError(f"Invalid IP address set for {header}: {ip}") # Could not determine the client IP address from request headers return None diff --git a/netbox/utilities/tests/test_request.py b/netbox/utilities/tests/test_request.py new file mode 100644 index 000000000..69f677323 --- /dev/null +++ b/netbox/utilities/tests/test_request.py @@ -0,0 +1,28 @@ +from django.test import TestCase, RequestFactory + +from netaddr import IPAddress +from utilities.request import get_client_ip + + +class GetClientIPTests(TestCase): + def setUp(self): + self.factory = RequestFactory() + + def test_ipv4_address(self): + request = self.factory.get('/', HTTP_X_FORWARDED_FOR='192.168.1.1') + self.assertEqual(get_client_ip(request), IPAddress('192.168.1.1')) + request = self.factory.get('/', HTTP_X_FORWARDED_FOR='192.168.1.1:8080') + self.assertEqual(get_client_ip(request), IPAddress('192.168.1.1')) + + def test_ipv6_address(self): + request = self.factory.get('/', HTTP_X_FORWARDED_FOR='2001:db8::8a2e:370:7334') + self.assertEqual(get_client_ip(request), IPAddress('2001:db8::8a2e:370:7334')) + request = self.factory.get('/', HTTP_X_FORWARDED_FOR='[2001:db8::8a2e:370:7334]') + self.assertEqual(get_client_ip(request), IPAddress('2001:db8::8a2e:370:7334')) + request = self.factory.get('/', HTTP_X_FORWARDED_FOR='[2001:db8::8a2e:370:7334]:8080') + self.assertEqual(get_client_ip(request), IPAddress('2001:db8::8a2e:370:7334')) + + def test_invalid_ip_address(self): + request = self.factory.get('/', HTTP_X_FORWARDED_FOR='invalid_ip') + with self.assertRaises(ValueError): + get_client_ip(request)