Skip to content

Commit

Permalink
Merge pull request #3 from sztamas/handling-unknown-in-x-forwarded-for
Browse files Browse the repository at this point in the history
Handles "unknown" in the X-Forwarded-For header.
  • Loading branch information
sztamas authored Aug 30, 2020
2 parents 8bfb66e + 34b1389 commit 16fb8ee
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 4 deletions.
18 changes: 18 additions & 0 deletions docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,21 @@ Use this setting when using a managed proxy with a dynamic IP (like when
behind an AWS Load Balancer, or other cloud equivalent). When this
setting is ``True``, Django IP Restrict will always check the HTTP
``X-Forwarded-For`` header to determine the true client IP address.


IPRESTRICT_USE_PROXY_IF_UNKNOWN
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Default: ``False``

Some proxies can be configured to hide the client IP behind them by using
the special value ``unknown`` in the ``X-Forwarded-For`` header.
By default, these requests will be blocked.

Set this setting to ``True`` if you would like to use the proxy's IP address
instead when the proxy reports them as ``unknown``.
This will allow you to set up rules against the IP address of the proxy instead
of the client IP addresses it hides.

Please note that in this special case the proxy is also checked against
``IPRESTRICT_TRUSTED_PROXIES`` if you are using that setting.
13 changes: 13 additions & 0 deletions iprestrict/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
logger = logging.getLogger(__name__)


UNKNOWN = "unknown"


class IPRestrictMiddleware:
def __init__(self, get_response):
self.get_response = get_response
Expand All @@ -25,6 +28,9 @@ def __init__(self, get_response):
self.trust_all_proxies = bool(
get_setting("IPRESTRICT_TRUST_ALL_PROXIES", "TRUST_ALL_PROXIES", False)
)
self.use_proxy_if_unknown = bool(
getattr(settings, "IPRESTRICT_USE_PROXY_IF_UNKNOWN", False)
)

def __call__(self, request):
if self.reload_rules:
Expand Down Expand Up @@ -54,6 +60,13 @@ def extract_client_ip(self, request):
def extract_client_ip_proxied_request(self, client_ip, forwarded_for):
closest_proxy = client_ip
client_ip = forwarded_for.pop(0)

if client_ip == UNKNOWN:
if self.use_proxy_if_unknown:
client_ip = closest_proxy
else:
raise exceptions.PermissionDenied

if self.trust_all_proxies:
return client_ip

Expand Down
44 changes: 40 additions & 4 deletions tests/test_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def test_middleware_restricts_other_ip(self):
response = self.client.get("", REMOTE_ADDR="10.1.1.1")
self.assertEqual(response.status_code, 403)

@override_settings(IPRESTRICT_TRUSTED_PROXIES=(PROXY,), ALLOW_PROXIES=False)
@override_settings(IPRESTRICT_TRUSTED_PROXIES=(PROXY,))
def test_middleware_allows_if_proxy_is_trusted(self):
response = self.client.get("", REMOTE_ADDR=PROXY, HTTP_X_FORWARDED_FOR=LOCAL_IP)
self.assertEqual(response.status_code, 404)
Expand All @@ -78,6 +78,31 @@ def test_middleware_restricts_if_proxy_is_not_trusted(self):
response = self.client.get("", REMOTE_ADDR=PROXY, HTTP_X_FORWARDED_FOR=LOCAL_IP)
self.assertEqual(response.status_code, 403)

@override_settings(
IPRESTRICT_TRUSTED_PROXIES=(LOCAL_IP,), IPRESTRICT_USE_PROXY_IF_UNKNOWN=True
)
def test_middleware_allows_if_PROXY_is_used_and_allowed_when_IP_is_unknown(self):
response = self.client.get(
"", REMOTE_ADDR=LOCAL_IP, HTTP_X_FORWARDED_FOR="unknown"
)
self.assertEqual(response.status_code, 404)

@override_settings(IPRESTRICT_USE_PROXY_IF_UNKNOWN=True)
def test_middleware_restricts_if_PROXY_is_used_but_NOT_allowed_when_IP_is_unknown(
self,
):
response = self.client.get(
"", REMOTE_ADDR=LOCAL_IP, HTTP_X_FORWARDED_FOR="unknown"
)
self.assertEqual(response.status_code, 403)

@override_settings(IPRESTRICT_TRUSTED_PROXIES=(PROXY,))
def test_middleware_restricts_if_IP_is_unknown(self):
response = self.client.get(
"", REMOTE_ADDR=PROXY, HTTP_X_FORWARDED_FOR="unknown"
)
self.assertEqual(response.status_code, 403)


class ReloadRulesTest(TestCase):
def setUp(self):
Expand Down Expand Up @@ -149,9 +174,7 @@ def test_multiple_proxies_all_trusted(self):
client_ip = self.middleware.extract_client_ip(request)
self.assertEqual(client_ip, LOCAL_IP)

@override_settings(
IPRESTRICT_TRUSTED_PROXIES=(PROXY,), IPRESTRICT_TRUST_ALL_PROXIES=True
)
@override_settings(IPRESTRICT_TRUST_ALL_PROXIES=True)
def test_trust_all_proxies_on(self):
self.middleware = IPRestrictMiddleware(dummy_get_response)
proxies = ["1.1.1.1", "2.2.2.2", "3.3.3.3", "4.4.4.4"]
Expand All @@ -161,3 +184,16 @@ def test_trust_all_proxies_on(self):

client_ip = self.middleware.extract_client_ip(request)
self.assertEqual(client_ip, LOCAL_IP)

@override_settings(
IPRESTRICT_TRUST_ALL_PROXIES=True, IPRESTRICT_USE_PROXY_IF_UNKNOWN=True
)
def test_use_proxy_if_unknown(self):
self.middleware = IPRestrictMiddleware(dummy_get_response)
proxies = ["1.1.1.1", "2.2.2.2", "3.3.3.3", "4.4.4.4"]
request = self.factory.get(
"", REMOTE_ADDR=PROXY, HTTP_X_FORWARDED_FOR=", ".join(["unknown"] + proxies)
)

client_ip = self.middleware.extract_client_ip(request)
self.assertEqual(client_ip, PROXY)

0 comments on commit 16fb8ee

Please sign in to comment.