Set IP public/private classifier to be compliant with RFC 1918.

This commit is contained in:
Silas Barta 2016-04-27 11:19:40 -07:00
parent 339f5def76
commit 70af6ea206
2 changed files with 45 additions and 8 deletions

View file

@ -97,20 +97,32 @@ def is_valid_ip(ip_address):
return valid
def is_valid_public_ip(ip_address):
"""Returns whether IP address is both valid AND, per RFC 1918, not reserved as
private"""
if not is_valid_ip(ip_address):
return False
PRIVATE_IPS_PREFIX = (
'10.',
'172.16.', '172.17.', '172.18.', '172.19.', '172.20.', '172.21.', '172.22.',
'172.23.', '172.24.', '172.25.', '172.26.', '172.27.', '172.28.', '172.29.',
'172.30.', '172.31.',
'192.168.',
'127.',
)
return not ip_address.startswith(PRIVATE_IPS_PREFIX)
def get_ip_address_from_request(request):
""" Makes the best attempt to get the client's real IP or return the loopback """
PRIVATE_IPS_PREFIX = ('10.', '172.', '192.', '127.')
ip_address = ''
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', '')
if x_forwarded_for and ',' not in x_forwarded_for:
if not x_forwarded_for.startswith(PRIVATE_IPS_PREFIX) and is_valid_ip(x_forwarded_for):
if is_valid_public_ip(x_forwarded_for):
ip_address = x_forwarded_for.strip()
else:
for ip_raw in x_forwarded_for.split(','):
ip = ip_raw.strip()
if ip.startswith(PRIVATE_IPS_PREFIX):
continue
elif not is_valid_ip(ip):
if not is_valid_public_ip(ip):
continue
else:
ip_address = ip
@ -118,14 +130,14 @@ def get_ip_address_from_request(request):
if not ip_address:
x_real_ip = request.META.get('HTTP_X_REAL_IP', '')
if x_real_ip:
if not x_real_ip.startswith(PRIVATE_IPS_PREFIX) and is_valid_ip(x_real_ip):
if is_valid_public_ip(x_real_ip):
ip_address = x_real_ip.strip()
if not ip_address:
remote_addr = request.META.get('REMOTE_ADDR', '')
if remote_addr:
if not remote_addr.startswith(PRIVATE_IPS_PREFIX) and is_valid_ip(remote_addr):
if is_valid_public_ip(remote_addr):
ip_address = remote_addr.strip()
if remote_addr.startswith(PRIVATE_IPS_PREFIX) and is_valid_ip(remote_addr):
if not is_valid_public_ip(remote_addr) and is_valid_ip(remote_addr):
ip_address = remote_addr.strip()
if not ip_address:
ip_address = '127.0.0.1'

View file

@ -7,9 +7,11 @@ 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 axes.decorators import COOLOFF_TIME
from axes.decorators import FAILURE_LIMIT
from axes.decorators import is_valid_public_ip
from axes.models import AccessAttempt, AccessLog
from axes.signals import user_locked_out
from axes.utils import reset
@ -214,3 +216,26 @@ class AccessAttemptTest(TestCase):
extra_data = {string.ascii_letters * x: x for x in range(0, 1000)} # An impossibly large post dict
self._login(**extra_data)
self.assertEquals(len(AccessAttempt.objects.latest('id').post_data), 1024)
class IPClassifierTest(TestCase):
def test_classify_private_ips(self):
"""Tests whether is_valid_public_ip correctly classifies IPs as being
bot public and valid
"""
EXPECTED = {
'foobar': False, # invalid - not octects
'192.168.0': False, # invalid - only 3 octets
'192.168.0.0': False, # private
'192.168.165.1': False, # private
'192.249.19.1': True, # public but 192 prefix
'10.0.201.13': False, # private
'172.15.12.1': True, # public but 172 prefix
'172.16.12.1': False, # private
'172.31.12.1': False, # private
'172.32.0.1': True, # public but 127 prefix
'200.150.23.5': True, # normal public
}
for ip_address, is_valid_public in six.iteritems(EXPECTED):
self.assertEqual(is_valid_public_ip(ip_address), is_valid_public)