Fix and add tests for IPv4 and IPv6 parsing

This patch does not fix IPv6 parsing with ports
This commit is contained in:
Aleksi Häkli 2016-11-21 21:33:53 +02:00
parent ef3d527bee
commit 41877cdecd
8 changed files with 133 additions and 15 deletions

View file

@ -9,7 +9,9 @@ env:
install:
- pip install -q $DJANGO coveralls
script:
- coverage run --source=axes runtests.py
- coverage run -a --source=axes runtests.py
- coverage run -a --source=axes runtests_proxy.py
- coverage run -a --source=axes runtests_proxy_custom_header.py
- coverage report
after_success:
- coveralls

View file

@ -37,15 +37,18 @@ def is_ipv6(ip):
def get_ip(request):
ip = request.META.get('REMOTE_ADDR', '')
"""Parse IP address from REMOTE_ADDR or
AXES_REVERSE_PROXY_HEADER if AXES_BEHIND_REVERSE_PROXY is set."""
if BEHIND_REVERSE_PROXY:
ip = request.META.get(REVERSE_PROXY_HEADER, '').split(',', 1)[0]
ip = ip.strip()
# For requests originating from behind a reverse proxy,
# resolve the IP address from the given AXES_REVERSE_PROXY_HEADER.
# AXES_REVERSE_PROXY_HEADER defaults to HTTP_X_FORWARDED_FOR if not given,
# which is the Django calling format for the HTTP X-Forwarder-For header.
# Please see RFC7239 for additional information:
# https://tools.ietf.org/html/rfc7239#section-5
# IIS seems to add the port number to HTTP_X_FORWARDED_FOR
if ':' in ip:
ip = ip.split(':')[0]
ip = request.META.get(REVERSE_PROXY_HEADER, '')
if not ip:
raise Warning(
@ -54,11 +57,21 @@ def get_ip(request):
'server settings to make sure this header value is being '
'passed. Header value {0}'.format(REVERSE_PROXY_HEADER)
)
if not is_ipv6(ip):
# Fix for IIS adding client port number to 'HTTP_X_FORWARDED_FOR' header (removes port number).
ip = ''.join(ip.split(':')[:-1])
return ip
# X-Forwarded-For IPs can have multiple IPs of which the first one is the
# originating reverse and the rest are proxies that are between the client
ip = ip.split(',', 1)[0]
# As spaces are permitted between given X-Forwarded-For IP addresses, strip them as well
ip = ip.strip()
# Fix IIS adding client port number to 'X-Forwarded-For' header (strip port)
if not is_ipv6(ip):
ip = ip.split(':', 1)[0]
return ip
return request.META.get('REMOTE_ADDR', '')
def query2str(items, max_length=1024):

View file

@ -0,0 +1,3 @@
from .test_settings import *
AXES_BEHIND_REVERSE_PROXY = True

View file

@ -0,0 +1,3 @@
from .test_settings_proxy import *
AXES_REVERSE_PROXY_HEADER = 'HTTP_X_AXES_CUSTOM_HEADER'

View file

@ -4,14 +4,17 @@ import time
import json
import datetime
from mock import patch
from django.conf import settings
from django.test import TestCase
from django.test.utils import override_settings
from django.contrib.auth.models import User
from django.core.urlresolvers import NoReverseMatch
from django.core.urlresolvers import reverse
from django.utils import six
from mock import patch
from axes.decorators import get_ip
from axes.settings import COOLOFF_TIME
from axes.settings import FAILURE_LIMIT
from axes.models import AccessAttempt, AccessLog
@ -19,6 +22,11 @@ from axes.signals import user_locked_out
from axes.utils import reset, iso8601
class MockRequest:
def __init__(self):
self.META = dict()
class AccessAttemptTest(TestCase):
"""Test case using custom settings for testing
"""
@ -392,3 +400,67 @@ class UtilsTest(TestCase):
self.assertTrue(is_ipv6('ff80::220:16ff:fec9:1'))
self.assertFalse(is_ipv6('67.255.125.204'))
self.assertFalse(is_ipv6('foo'))
class GetIPProxyTest(TestCase):
"""Test get_ip returns correct addresses with proxy
"""
def setUp(self):
self.request = MockRequest()
def test_iis_ipv4_port_stripping(self):
self.ip = '192.168.1.1'
valid_headers = [
'192.168.1.1:6112',
'192.168.1.1:6033, 192.168.1.2:9001',
]
for header in valid_headers:
self.request.META['HTTP_X_FORWARDED_FOR'] = header
self.assertEqual(self.ip, get_ip(self.request))
def test_valid_ipv4_parsing(self):
self.ip = '192.168.1.1'
valid_headers = [
'192.168.1.1',
'192.168.1.1, 192.168.1.2',
' 192.168.1.1 , 192.168.1.2 ',
' 192.168.1.1 , 2001:db8:cafe::17 ',
]
for header in valid_headers:
self.request.META['HTTP_X_FORWARDED_FOR'] = header
self.assertEqual(self.ip, get_ip(self.request))
def test_valid_ipv6_parsing(self):
self.ip = '2001:db8:cafe::17'
valid_headers = [
'2001:db8:cafe::17',
'2001:db8:cafe::17 , 2001:db8:cafe::18',
'2001:db8:cafe::17, 2001:db8:cafe::18, 192.168.1.1',
]
for header in valid_headers:
self.request.META['HTTP_X_FORWARDED_FOR'] = header
self.assertEqual(self.ip, get_ip(self.request))
class GetIPProxyCustomHeaderTest(TestCase):
"""Test that get_ip returns correct addresses with a custom proxy header
"""
def setUp(self):
self.request = MockRequest()
def test_custom_header_parsing(self):
self.ip = '2001:db8:cafe::17'
valid_headers = [
' 2001:db8:cafe::17 , 2001:db8:cafe::18',
]
for header in valid_headers:
self.request.META[settings.AXES_REVERSE_PROXY_HEADER] = header
self.assertEqual(self.ip, get_ip(self.request))

View file

@ -1,4 +1,5 @@
#!/usr/bin/env python
import os
import sys
@ -6,10 +7,18 @@ import django
from django.conf import settings
from django.test.utils import get_runner
if __name__ == "__main__":
os.environ['DJANGO_SETTINGS_MODULE'] = 'axes.test_settings'
def run_tests(settings_module, *modules):
os.environ['DJANGO_SETTINGS_MODULE'] = settings_module
django.setup()
TestRunner = get_runner(settings)
test_runner = TestRunner()
failures = test_runner.run_tests(["axes"])
failures = test_runner.run_tests(*modules)
sys.exit(bool(failures))
if __name__ == '__main__':
run_tests('axes.test_settings', [
'axes.tests.AccessAttemptTest',
'axes.tests.UtilsTest',
])

8
runtests_proxy.py Normal file
View file

@ -0,0 +1,8 @@
#!/usr/bin/env python
from runtests import run_tests
if __name__ == '__main__':
run_tests('axes.test_settings_proxy', [
'axes.tests.GetIPProxyTest',
])

View file

@ -0,0 +1,8 @@
#!/usr/bin/env python
from runtests import run_tests
if __name__ == '__main__':
run_tests('axes.test_settings_proxy_custom_header', [
'axes.tests.GetIPProxyCustomHeaderTest',
])