mirror of
https://github.com/jazzband/django-defender.git
synced 2026-05-14 10:33:14 +00:00
add 2 new setting variables for more granular failure limit control (#113)
This commit is contained in:
parent
b546224372
commit
250c4d5388
4 changed files with 52 additions and 5 deletions
|
|
@ -323,6 +323,10 @@ These should be defined in your ``settings.py`` file.
|
|||
|
||||
* ``DEFENDER_LOGIN_FAILURE_LIMIT``: Int: The number of login attempts allowed before a
|
||||
record is created for the failed logins. [Default: ``3``]
|
||||
* ``DEFENDER_LOGIN_FAILURE_LIMIT_USERNAME``: Int: The number of login attempts allowed
|
||||
on a username before a record is created for the failed logins. [Default: ``DEFENDER_LOGIN_FAILURE_LIMIT``]
|
||||
* ``DEFENDER_LOGIN_FAILURE_LIMIT_IP``: Int: The number of login attempts allowed
|
||||
from an IP before a record is created for the failed logins. [Default: ``DEFENDER_LOGIN_FAILURE_LIMIT``]
|
||||
* ``DEFENDER_BEHIND_REVERSE_PROXY``: Boolean: Is defender behind a reverse proxy?
|
||||
[Default: ``False``]
|
||||
* ``DEFENDER_REVERSE_PROXY_HEADER``: String: the name of the http header with your
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ MOCK_REDIS = get_setting('DEFENDER_MOCK_REDIS', False)
|
|||
|
||||
# see if the user has overridden the failure limit
|
||||
FAILURE_LIMIT = get_setting('DEFENDER_LOGIN_FAILURE_LIMIT', 3)
|
||||
USERNAME_FAILURE_LIMIT = get_setting('DEFENDER_LOGIN_FAILURE_LIMIT_USERNAME', FAILURE_LIMIT)
|
||||
IP_FAILURE_LIMIT = get_setting('DEFENDER_LOGIN_FAILURE_LIMIT_IP', FAILURE_LIMIT)
|
||||
|
||||
# If this is True, the lockout checks to evaluate if the IP failure limit and
|
||||
# the username failure limit has been reached before issuing the lockout.
|
||||
|
|
|
|||
|
|
@ -173,6 +173,47 @@ class AccessAttemptTest(DefenderTestCase):
|
|||
response = self.client.get(ADMIN_LOGIN_URL)
|
||||
self.assertContains(response, self.LOCKED_MESSAGE)
|
||||
|
||||
@patch('defender.config.USERNAME_FAILURE_LIMIT', 3)
|
||||
def test_username_failure_limit(self):
|
||||
""" Tests that the username failure limit setting is
|
||||
respected when trying to login one more time than failure limit
|
||||
"""
|
||||
for i in range(0, config.USERNAME_FAILURE_LIMIT):
|
||||
ip = '74.125.239.{0}.'.format(i)
|
||||
response = self._login(username=VALID_USERNAME, remote_addr=ip)
|
||||
# Check if we are in the same login page
|
||||
self.assertContains(response, LOGIN_FORM_KEY)
|
||||
|
||||
# So, we shouldn't have gotten a lock-out yet.
|
||||
# But we should get one now
|
||||
response = self._login(username=VALID_USERNAME, remote_addr=ip)
|
||||
self.assertContains(response, self.LOCKED_MESSAGE)
|
||||
|
||||
# doing a get should not get locked out message
|
||||
response = self.client.get(ADMIN_LOGIN_URL)
|
||||
self.assertContains(response, LOGIN_FORM_KEY)
|
||||
|
||||
@patch('defender.config.IP_FAILURE_LIMIT', 3)
|
||||
def test_ip_failure_limit(self):
|
||||
""" Tests that the IP failure limit setting is
|
||||
respected when trying to login one more time than failure limit
|
||||
"""
|
||||
for i in range(0, config.IP_FAILURE_LIMIT):
|
||||
username = 'john-doe__%d' % i
|
||||
response = self._login(username=username)
|
||||
# Check if we are in the same login page
|
||||
self.assertContains(response, LOGIN_FORM_KEY)
|
||||
|
||||
# So, we shouldn't have gotten a lock-out yet.
|
||||
# But we should get one now
|
||||
response = self._login(username=VALID_USERNAME)
|
||||
self.assertContains(response, self.LOCKED_MESSAGE)
|
||||
|
||||
# doing a get should also get locked out message
|
||||
response = self.client.get(ADMIN_LOGIN_URL)
|
||||
self.assertContains(response, self.LOCKED_MESSAGE)
|
||||
|
||||
|
||||
def test_valid_login(self):
|
||||
""" Tests a valid login for a real username
|
||||
"""
|
||||
|
|
@ -787,7 +828,7 @@ class AccessAttemptTest(DefenderTestCase):
|
|||
self.assertEqual(data_out, [])
|
||||
|
||||
@patch('defender.config.BEHIND_REVERSE_PROXY', True)
|
||||
@patch('defender.config.FAILURE_LIMIT', 3)
|
||||
@patch('defender.config.IP_FAILURE_LIMIT', 3)
|
||||
def test_login_blocked_for_non_standard_login_views_without_msg(self):
|
||||
"""
|
||||
Check that a view wich returns the expected status code is causing
|
||||
|
|
@ -819,7 +860,7 @@ class AccessAttemptTest(DefenderTestCase):
|
|||
self.assertEqual(data_out, ['192.168.24.24'])
|
||||
|
||||
@patch('defender.config.BEHIND_REVERSE_PROXY', True)
|
||||
@patch('defender.config.FAILURE_LIMIT', 3)
|
||||
@patch('defender.config.IP_FAILURE_LIMIT', 3)
|
||||
def test_login_blocked_for_non_standard_login_views_with_msg(self):
|
||||
"""
|
||||
Check that a view wich returns the expected status code and the
|
||||
|
|
@ -850,7 +891,7 @@ class AccessAttemptTest(DefenderTestCase):
|
|||
self.assertEqual(data_out, ['192.168.24.24'])
|
||||
|
||||
@patch('defender.config.BEHIND_REVERSE_PROXY', True)
|
||||
@patch('defender.config.FAILURE_LIMIT', 3)
|
||||
@patch('defender.config.IP_FAILURE_LIMIT', 3)
|
||||
def test_login_non_blocked_for_non_standard_login_views_different_msg(self):
|
||||
"""
|
||||
Check that a view wich returns the expected status code but not the
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ def record_failed_attempt(ip_address, username):
|
|||
# we only want to increment the IP if this is disabled.
|
||||
ip_count = increment_key(get_ip_attempt_cache_key(ip_address))
|
||||
# if over the limit, add to block
|
||||
if ip_count > config.FAILURE_LIMIT:
|
||||
if ip_count > config.IP_FAILURE_LIMIT:
|
||||
block_ip(ip_address)
|
||||
ip_block = True
|
||||
|
||||
|
|
@ -208,7 +208,7 @@ def record_failed_attempt(ip_address, username):
|
|||
if username and not config.DISABLE_USERNAME_LOCKOUT:
|
||||
user_count = increment_key(get_username_attempt_cache_key(username))
|
||||
# if over the limit, add to block
|
||||
if user_count > config.FAILURE_LIMIT:
|
||||
if user_count > config.USERNAME_FAILURE_LIMIT:
|
||||
block_username(username)
|
||||
user_block = True
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue