Merge branch 'master' into logging-conf

Conflicts:
	axes/tests.py
This commit is contained in:
Jack Sullivan 2017-05-13 11:55:53 -07:00
commit 25e5757aff
8 changed files with 576 additions and 30 deletions

View file

@ -11,6 +11,7 @@ install:
script:
- coverage run -a --source=axes runtests.py
- coverage run -a --source=axes runtests_proxy.py
- coverage run -a --source=axes runtests_num_proxies.py
- coverage run -a --source=axes runtests_proxy_custom_header.py
- coverage report
after_success:

View file

@ -113,15 +113,15 @@ def get_ip(request):
if BEHIND_REVERSE_PROXY:
# 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.
# AXES_REVERSE_PROXY_HEADER defaults to HTTP_X_FORWARDED_FOR,
# which is the Django name for the HTTP X-Forwarder-For header.
# Please see RFC7239 for additional information:
# https://tools.ietf.org/html/rfc7239#section-5
# The REVERSE_PROXY_HEADER HTTP header is a list
# of potentionally unsecure IPs, for example:
# X-Forwarded-For: 1.1.1.1, 11.11.11.11:8080, 111.111.111.111
ip = request.META.get(REVERSE_PROXY_HEADER, '')
ip_str = request.META.get(REVERSE_PROXY_HEADER, '')
# We need to know the number of proxies present in the request chain
# in order to securely calculate the one IP that is the real client IP.
@ -130,23 +130,45 @@ def get_ip(request):
# configurations, with e.g. the X-Forwarded-For header containing
# the originating client IP, proxies and possibly spoofed values.
#
# If you are using a special header for client calculation such as
# the X-Real-IP or the like with nginx, please check this configuration.
# If you are using a special header for client calculation such as the
# X-Real-IP or the like with nginx, please check this configuration.
#
# Please see discussion for more information:
# https://github.com/jazzband/django-axes/issues/224
ip = [ip.strip() for ip in ip.split(',')][-NUM_PROXIES]
ip_list = [ip.strip() for ip in ip_str.split(',')]
# Fix IIS adding client port number to 'X-Forwarded-For' header (strip port)
if not is_ipv6(ip):
ip = ip.split(':', 1)[0]
# Pick the nth last IP in the given list of addresses after parsing
if len(ip_list) >= NUM_PROXIES:
ip = ip_list[-NUM_PROXIES]
# Fix IIS adding client port number to the
# 'X-Forwarded-For' header (strip port)
if not is_ipv6(ip):
ip = ip.split(':', 1)[0]
# If nth last is not found, default to no IP and raise a warning
else:
ip = ''
raise Warning(
'AXES: Axes is configured for operation behind a '
'reverse proxy but received too few IPs in the HTTP '
'AXES_REVERSE_PROXY_HEADER. Check your '
'AXES_NUM_PROXIES configuration. '
'Header name: {0}, value: {1}'.format(
REVERSE_PROXY_HEADER, ip_str
)
)
if not ip:
raise Warning(
'AXES: Axes is configured for operation behind a reverse proxy '
'but could not find an HTTP header value. Check your proxy '
'server settings to make sure this header value is being '
'passed. Header name {0}'.format(REVERSE_PROXY_HEADER)
'AXES: Axes is configured for operation behind a reverse '
'proxy but could not find a suitable IP in the specified '
'HTTP header. Check your proxy server settings to make '
'sure correct headers are being passed to Django in '
'AXES_REVERSE_PROXY_HEADER. '
'Header name: {0}, value: {1}'.format(
REVERSE_PROXY_HEADER, ip_str
)
)
return ip
@ -233,11 +255,18 @@ def _get_user_attempts(request):
)
if not attempts:
params = {'ip_address': ip, 'trusted': False}
params = {'trusted': False}
if AXES_ONLY_USER_FAILURES:
params['username'] = username
elif LOCK_OUT_BY_COMBINATION_USER_AND_IP:
params['username'] = username
params['ip_address'] = ip
else:
params['ip_address'] = ip
if USE_USER_AGENT:
params['user_agent'] = ua
if LOCK_OUT_BY_COMBINATION_USER_AND_IP:
params['username'] = username
attempts = AccessAttempt.objects.filter(**params)
@ -577,23 +606,30 @@ def get_cache_key(request_or_object):
:param request_or_object: Request or AccessAttempt object
:return cache-key: String, key to be used in cache system
"""
ua = None
ip = None
if isinstance(request_or_object, AccessAttempt):
ip = request_or_object.ip_address
un = request_or_object.username
ua = request_or_object.user_agent
else:
ip = get_ip(request_or_object)
un = request_or_object.POST.get(USERNAME_FORM_FIELD, None)
ua = request_or_object.META.get('HTTP_USER_AGENT', '<unknown>')[:255]
ip = ip.encode('utf-8')
ip = ip.encode('utf-8') if ip else ''.encode('utf-8')
un = un.encode('utf-8') if un else ''.encode('utf-8')
ua = ua.encode('utf-8') if ua else ''.encode('utf-8')
if ua:
ua = ua.encode('utf-8')
cache_hash_key = 'axes-{}'.format(md5(ip+ua).hexdigest())
if AXES_ONLY_USER_FAILURES:
attributes = un
elif LOCK_OUT_BY_COMBINATION_USER_AND_IP:
attributes = ip+un
else:
cache_hash_key = 'axes-{}'.format(md5(ip).hexdigest())
attributes = ip
if USE_USER_AGENT:
attributes += ua
cache_hash_key = 'axes-{}'.format(md5(attributes).hexdigest())
return cache_hash_key

View file

@ -1,5 +1,3 @@
from datetime import timedelta
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
@ -55,4 +53,3 @@ USE_TZ = False
LOGIN_REDIRECT_URL = '/admin/'
AXES_LOGIN_FAILURE_LIMIT = 10
AXES_COOLOFF_TIME = timedelta(seconds=2)

View file

@ -0,0 +1,5 @@
from .test_settings import *
AXES_BEHIND_REVERSE_PROXY = True
AXES_REVERSE_PROXY_HEADER = 'HTTP_X_FORWARDED_FOR'
AXES_NUM_PROXIES = 2

View file

@ -16,13 +16,15 @@ from django.utils import six
from django.test.client import RequestFactory
from axes.decorators import get_ip, get_cache_key, get_client_str
from axes.settings import COOLOFF_TIME
from axes.settings import FAILURE_LIMIT
from axes.models import AccessAttempt, AccessLog
from axes.signals import user_locked_out
from axes.utils import reset, iso8601
TEST_COOLOFF_TIME = datetime.timedelta(seconds=2)
class MockRequest:
def __init__(self):
self.META = dict()
@ -140,17 +142,19 @@ class AccessAttemptTest(TestCase):
self.assertNotEquals(AccessLog.objects.latest('id').logout_time, None)
self.assertContains(response, 'Logged out')
@patch('axes.decorators.COOLOFF_TIME', TEST_COOLOFF_TIME)
def test_cooling_off(self):
"""Tests if the cooling time allows a user to login
"""
self.test_failure_limit_once()
# Wait for the cooling off period
time.sleep(COOLOFF_TIME.total_seconds())
time.sleep(TEST_COOLOFF_TIME.total_seconds())
# It should be possible to login again, make sure it is.
self.test_valid_login()
@patch('axes.decorators.COOLOFF_TIME', TEST_COOLOFF_TIME)
def test_cooling_off_for_trusted_user(self):
"""Test the cooling time for a trusted user
"""
@ -213,7 +217,7 @@ class AccessAttemptTest(TestCase):
ip = '127.0.0.1'.encode('utf-8')
ua = '<unknown>'.encode('utf-8')
cache_hash_key_checker = 'axes-{}'.format(md5((ip+ua)).hexdigest())
cache_hash_key_checker = 'axes-{}'.format(md5((ip)).hexdigest())
request_factory = RequestFactory()
request = request_factory.post('/admin/login/',
@ -443,6 +447,469 @@ class AccessAttemptTest(TestCase):
self.assertEqual(response.status_code, 200)
class AccessAttemptConfigTest(TestCase):
""" This set of tests checks for lockouts under different configurations
and circumstances to prevent false positives and false negatives.
Always block attempted logins for the same user from the same IP.
Always allow attempted logins for a different user from a different IP.
"""
IP_1 = '10.1.1.1'
IP_2 = '10.2.2.2'
USER_1 = 'valid-user-1'
USER_2 = 'valid-user-2'
VALID_PASSWORD = 'valid-password'
WRONG_PASSWORD = 'wrong-password'
LOCKED_MESSAGE = 'Account locked: too many login attempts.'
LOGIN_FORM_KEY = '<input type="submit" value="Log in" />'
ALLOWED = 302
BLOCKED = 403
def _login(self, username, password, ip_addr='127.0.0.1',
is_json=False, **kwargs):
"""Login a user and get the response.
IP address can be configured to test IP blocking functionality.
"""
try:
admin_login = reverse('admin:login')
except NoReverseMatch:
admin_login = reverse('admin:index')
headers = {
'user_agent': 'test-browser'
}
post_data = {
'username': username,
'password': password,
'this_is_the_login_form': 1,
}
post_data.update(kwargs)
if is_json:
headers.update({
'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest',
'content_type': 'application/json',
})
post_data = json.dumps(post_data)
response = self.client.post(
admin_login, post_data, REMOTE_ADDR=ip_addr, **headers
)
return response
def _lockout_user1_from_ip1(self):
for i in range(1, FAILURE_LIMIT+1):
response = self._login(
username=self.USER_1,
password=self.WRONG_PASSWORD,
ip_addr=self.IP_1
)
return response
def setUp(self):
"""Create two valid users for authentication.
"""
self.user = User.objects.create_superuser(
username=self.USER_1,
email='test_1@example.com',
password=self.VALID_PASSWORD,
)
self.user = User.objects.create_superuser(
username=self.USER_2,
email='test_2@example.com',
password=self.VALID_PASSWORD,
)
# Test for true and false positives when blocking by IP *OR* user (default).
# Cache disabled. Default settings.
@patch('axes.decorators.cache.set', return_value=None)
@patch('axes.decorators.cache.get', return_value=None)
def test_lockout_by_ip_blocks_when_same_user_same_ip_without_cache(
self, cache_get_mock=None, cache_set_mock=None
):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
# User 1 is still blocked from IP 1.
response = self._login(
self.USER_1,
self.VALID_PASSWORD,
ip_addr=self.IP_1
)
self.assertEqual(response.status_code, self.BLOCKED)
@patch('axes.decorators.cache.set', return_value=None)
@patch('axes.decorators.cache.get', return_value=None)
def test_lockout_by_ip_allows_when_same_user_diff_ip_without_cache(
self, cache_get_mock=None, cache_set_mock=None
):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
# User 1 can still login from IP 2.
response = self._login(
self.USER_1,
self.VALID_PASSWORD,
ip_addr=self.IP_2
)
self.assertEqual(response.status_code, self.ALLOWED)
@patch('axes.decorators.cache.set', return_value=None)
@patch('axes.decorators.cache.get', return_value=None)
def test_lockout_by_ip_blocks_when_diff_user_same_ip_without_cache(
self, cache_get_mock=None, cache_set_mock=None
):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
# User 2 is also locked out from IP 1.
response = self._login(
self.USER_2,
self.VALID_PASSWORD,
ip_addr=self.IP_1
)
self.assertEqual(response.status_code, self.BLOCKED)
@patch('axes.decorators.cache.set', return_value=None)
@patch('axes.decorators.cache.get', return_value=None)
def test_lockout_by_ip_allows_when_diff_user_diff_ip_without_cache(
self, cache_get_mock=None, cache_set_mock=None
):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
# User 2 can still login from IP 2.
response = self._login(
self.USER_2,
self.VALID_PASSWORD,
ip_addr=self.IP_2
)
self.assertEqual(response.status_code, self.ALLOWED)
# Test for true and false positives when blocking by user only.
# Cache disabled. When AXES_ONLY_USER_FAILURES = True
@patch('axes.decorators.AXES_ONLY_USER_FAILURES', True)
@patch('axes.decorators.cache.set', return_value=None)
@patch('axes.decorators.cache.get', return_value=None)
def test_lockout_by_user_blocks_when_same_user_same_ip_without_cache(
self, cache_get_mock=None, cache_set_mock=None
):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
# User 1 is still blocked from IP 1.
response = self._login(
self.USER_1,
self.VALID_PASSWORD,
ip_addr=self.IP_1
)
self.assertEqual(response.status_code, self.BLOCKED)
@patch('axes.decorators.AXES_ONLY_USER_FAILURES', True)
@patch('axes.decorators.cache.set', return_value=None)
@patch('axes.decorators.cache.get', return_value=None)
def test_lockout_by_user_blocks_when_same_user_diff_ip_without_cache(
self, cache_get_mock=None, cache_set_mock=None
):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
# User 1 is also locked out from IP 2.
response = self._login(
self.USER_1,
self.VALID_PASSWORD,
ip_addr=self.IP_2
)
self.assertEqual(response.status_code, self.BLOCKED)
@patch('axes.decorators.AXES_ONLY_USER_FAILURES', True)
@patch('axes.decorators.cache.set', return_value=None)
@patch('axes.decorators.cache.get', return_value=None)
def test_lockout_by_user_allows_when_diff_user_same_ip_without_cache(
self, cache_get_mock=None, cache_set_mock=None
):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
# User 2 can still login from IP 1.
response = self._login(
self.USER_2,
self.VALID_PASSWORD,
ip_addr=self.IP_1
)
self.assertEqual(response.status_code, self.ALLOWED)
@patch('axes.decorators.AXES_ONLY_USER_FAILURES', True)
@patch('axes.decorators.cache.set', return_value=None)
@patch('axes.decorators.cache.get', return_value=None)
def test_lockout_by_user_allows_when_diff_user_diff_ip_without_cache(
self, cache_get_mock=None, cache_set_mock=None
):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
# User 2 can still login from IP 2.
response = self._login(
self.USER_2,
self.VALID_PASSWORD,
ip_addr=self.IP_2
)
self.assertEqual(response.status_code, self.ALLOWED)
# Test for true and false positives when blocking by user and IP together.
# Cache disabled. When LOCK_OUT_BY_COMBINATION_USER_AND_IP = True
@patch('axes.decorators.LOCK_OUT_BY_COMBINATION_USER_AND_IP', True)
@patch('axes.decorators.cache.set', return_value=None)
@patch('axes.decorators.cache.get', return_value=None)
def test_lockout_by_user_and_ip_blocks_when_same_user_same_ip_without_cache(
self, cache_get_mock=None, cache_set_mock=None
):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
# User 1 is still blocked from IP 1.
response = self._login(
self.USER_1,
self.VALID_PASSWORD,
ip_addr=self.IP_1
)
self.assertEqual(response.status_code, self.BLOCKED)
@patch('axes.decorators.LOCK_OUT_BY_COMBINATION_USER_AND_IP', True)
@patch('axes.decorators.cache.set', return_value=None)
@patch('axes.decorators.cache.get', return_value=None)
def test_lockout_by_user_and_ip_allows_when_same_user_diff_ip_without_cache(
self, cache_get_mock=None, cache_set_mock=None
):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
# User 1 can still login from IP 2.
response = self._login(
self.USER_1,
self.VALID_PASSWORD,
ip_addr=self.IP_2
)
self.assertEqual(response.status_code, self.ALLOWED)
@patch('axes.decorators.LOCK_OUT_BY_COMBINATION_USER_AND_IP', True)
@patch('axes.decorators.cache.set', return_value=None)
@patch('axes.decorators.cache.get', return_value=None)
def test_lockout_by_user_and_ip_allows_when_diff_user_same_ip_without_cache(
self, cache_get_mock=None, cache_set_mock=None
):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
# User 2 can still login from IP 1.
response = self._login(
self.USER_2,
self.VALID_PASSWORD,
ip_addr=self.IP_1
)
self.assertEqual(response.status_code, self.ALLOWED)
@patch('axes.decorators.LOCK_OUT_BY_COMBINATION_USER_AND_IP', True)
@patch('axes.decorators.cache.set', return_value=None)
@patch('axes.decorators.cache.get', return_value=None)
def test_lockout_by_user_and_ip_allows_when_diff_user_diff_ip_without_cache(
self, cache_get_mock=None, cache_set_mock=None
):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
# User 2 can still login from IP 2.
response = self._login(
self.USER_2,
self.VALID_PASSWORD,
ip_addr=self.IP_2
)
self.assertEqual(response.status_code, self.ALLOWED)
# Test for true and false positives when blocking by IP *OR* user (default).
# With cache enabled. Default criteria.
def test_lockout_by_ip_blocks_when_same_user_same_ip_using_cache(
self, cache_get_mock=None, cache_set_mock=None
):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
# User 1 is still blocked from IP 1.
response = self._login(
self.USER_1,
self.VALID_PASSWORD,
ip_addr=self.IP_1
)
self.assertEqual(response.status_code, self.BLOCKED)
def test_lockout_by_ip_allows_when_same_user_diff_ip_using_cache(
self, cache_get_mock=None, cache_set_mock=None
):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
# User 1 can still login from IP 2.
response = self._login(
self.USER_1,
self.VALID_PASSWORD,
ip_addr=self.IP_2
)
self.assertEqual(response.status_code, self.ALLOWED)
def test_lockout_by_ip_blocks_when_diff_user_same_ip_using_cache(
self, cache_get_mock=None, cache_set_mock=None
):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
# User 2 is also locked out from IP 1.
response = self._login(
self.USER_2,
self.VALID_PASSWORD,
ip_addr=self.IP_1
)
self.assertEqual(response.status_code, self.BLOCKED)
def test_lockout_by_ip_allows_when_diff_user_diff_ip_using_cache(
self, cache_get_mock=None, cache_set_mock=None
):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
# User 2 can still login from IP 2.
response = self._login(
self.USER_2,
self.VALID_PASSWORD,
ip_addr=self.IP_2
)
self.assertEqual(response.status_code, self.ALLOWED)
# Test for true and false positives when blocking by user only.
# With cache enabled. When AXES_ONLY_USER_FAILURES = True
@patch('axes.decorators.AXES_ONLY_USER_FAILURES', True)
def test_lockout_by_user_blocks_when_same_user_same_ip_using_cache(
self, cache_get_mock=None, cache_set_mock=None
):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
# User 1 is still blocked from IP 1.
response = self._login(
self.USER_1,
self.VALID_PASSWORD,
ip_addr=self.IP_1
)
self.assertEqual(response.status_code, self.BLOCKED)
@patch('axes.decorators.AXES_ONLY_USER_FAILURES', True)
def test_lockout_by_user_blocks_when_same_user_diff_ip_using_cache(
self, cache_get_mock=None, cache_set_mock=None
):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
# User 1 is also locked out from IP 2.
response = self._login(
self.USER_1,
self.VALID_PASSWORD,
ip_addr=self.IP_2
)
self.assertEqual(response.status_code, self.BLOCKED)
@patch('axes.decorators.AXES_ONLY_USER_FAILURES', True)
def test_lockout_by_user_allows_when_diff_user_same_ip_using_cache(
self, cache_get_mock=None, cache_set_mock=None
):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
# User 2 can still login from IP 1.
response = self._login(
self.USER_2,
self.VALID_PASSWORD,
ip_addr=self.IP_1
)
self.assertEqual(response.status_code, self.ALLOWED)
@patch('axes.decorators.AXES_ONLY_USER_FAILURES', True)
def test_lockout_by_user_allows_when_diff_user_diff_ip_using_cache(
self, cache_get_mock=None, cache_set_mock=None
):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
# User 2 can still login from IP 2.
response = self._login(
self.USER_2,
self.VALID_PASSWORD,
ip_addr=self.IP_2
)
self.assertEqual(response.status_code, self.ALLOWED)
# Test for true and false positives when blocking by user and IP together.
# With cache enabled. When LOCK_OUT_BY_COMBINATION_USER_AND_IP = True
@patch('axes.decorators.LOCK_OUT_BY_COMBINATION_USER_AND_IP', True)
def test_lockout_by_user_and_ip_blocks_when_same_user_same_ip_using_cache(
self, cache_get_mock=None, cache_set_mock=None
):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
# User 1 is still blocked from IP 1.
response = self._login(
self.USER_1,
self.VALID_PASSWORD,
ip_addr=self.IP_1
)
self.assertEqual(response.status_code, self.BLOCKED)
@patch('axes.decorators.LOCK_OUT_BY_COMBINATION_USER_AND_IP', True)
def test_lockout_by_user_and_ip_allows_when_same_user_diff_ip_using_cache(
self, cache_get_mock=None, cache_set_mock=None
):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
# User 1 can still login from IP 2.
response = self._login(
self.USER_1,
self.VALID_PASSWORD,
ip_addr=self.IP_2
)
self.assertEqual(response.status_code, self.ALLOWED)
@patch('axes.decorators.LOCK_OUT_BY_COMBINATION_USER_AND_IP', True)
def test_lockout_by_user_and_ip_allows_when_diff_user_same_ip_using_cache(
self, cache_get_mock=None, cache_set_mock=None
):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
# User 2 can still login from IP 1.
response = self._login(
self.USER_2,
self.VALID_PASSWORD,
ip_addr=self.IP_1
)
self.assertEqual(response.status_code, self.ALLOWED)
@patch('axes.decorators.LOCK_OUT_BY_COMBINATION_USER_AND_IP', True)
def test_lockout_by_user_and_ip_allows_when_diff_user_diff_ip_using_cache(
self, cache_get_mock=None, cache_set_mock=None
):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
# User 2 can still login from IP 2.
response = self._login(
self.USER_2,
self.VALID_PASSWORD,
ip_addr=self.IP_2
)
self.assertEqual(response.status_code, self.ALLOWED)
class UtilsTest(TestCase):
def test_iso8601(self):
"""Tests iso8601 correctly translates datetime.timdelta to ISO 8601
@ -562,3 +1029,33 @@ class GetIPProxyCustomHeaderTest(TestCase):
for header in valid_headers:
self.request.META[settings.AXES_REVERSE_PROXY_HEADER] = header
self.assertEqual(self.ip, get_ip(self.request))
class GetIPNumProxiesTest(TestCase):
"""Test that get_ip returns the correct last IP when NUM_PROXIES is configured
"""
def setUp(self):
self.request = MockRequest()
def test_header_ordering(self):
self.ip = '2.2.2.2'
valid_headers = [
'4.4.4.4, 3.3.3.3, 2.2.2.2, 1.1.1.1',
' 3.3.3.3, 2.2.2.2, 1.1.1.1',
' 2.2.2.2, 1.1.1.1',
]
for header in valid_headers:
self.request.META[settings.AXES_REVERSE_PROXY_HEADER] = header
self.assertEqual(self.ip, get_ip(self.request))
def test_invalid_headers_too_few(self):
self.request.META[settings.AXES_REVERSE_PROXY_HEADER] = '1.1.1.1'
with self.assertRaises(Warning):
get_ip(self.request)
def test_invalid_headers_no_ip(self):
self.request.META[settings.AXES_REVERSE_PROXY_HEADER] = ''
with self.assertRaises(Warning):
get_ip(self.request)

View file

@ -20,5 +20,6 @@ def run_tests(settings_module, *modules):
if __name__ == '__main__':
run_tests('axes.test_settings', [
'axes.tests.AccessAttemptTest',
'axes.tests.AccessAttemptConfigTest',
'axes.tests.UtilsTest',
])

8
runtests_num_proxies.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_num_proxies', [
'axes.tests.GetIPNumProxiesTest',
])

View file

@ -20,6 +20,7 @@ setup(
url='https://github.com/django-pci/django-axes',
license='MIT',
package_dir={'axes': 'axes'},
install_requires=['pytz'],
include_package_data=True,
packages=find_packages(),
classifiers=[