mirror of
https://github.com/jazzband/django-defender.git
synced 2026-05-22 14:21:57 +00:00
Match Django Axes behavior on locked users
Match how Django Axes locks out users based on IP and username.
Also adds additional test to cover the functionality as the previous
tests were not verifying usernames.
Reference:
6bd5a50cb8/axes/decorators.py (L219)
This commit is contained in:
parent
a0d94d5220
commit
270b1154b7
2 changed files with 43 additions and 12 deletions
|
|
@ -28,10 +28,12 @@ except NoReverseMatch:
|
|||
LOGIN_FORM_KEY = 'this_is_the_login_form'
|
||||
|
||||
|
||||
VALID_USERNAME = VALID_PASSWORD = 'valid'
|
||||
|
||||
|
||||
class AccessAttemptTest(TestCase):
|
||||
""" Test case using custom settings for testing
|
||||
"""
|
||||
VALID_USERNAME = 'valid'
|
||||
LOCKED_MESSAGE = 'Account locked: too many login attempts.'
|
||||
PERMANENT_LOCKED_MESSAGE = (
|
||||
LOCKED_MESSAGE + ' Contact an admin to unlock your account.'
|
||||
|
|
@ -43,17 +45,23 @@ class AccessAttemptTest(TestCase):
|
|||
|
||||
return ''.join(random.choice(chars) for x in range(20))
|
||||
|
||||
def _login(self, is_valid=False, user_agent='test-browser'):
|
||||
""" Login a user. A valid credential is used when is_valid is True,
|
||||
otherwise it will use a random string to make a failed login.
|
||||
def _login(self, username=None, password=None, user_agent='test-browser',
|
||||
remote_addr='127.0.0.1'):
|
||||
""" Login a user. If the username or password is not provided
|
||||
it will use a random string instead. Use the VALID_USERNAME and
|
||||
VALID_PASSWORD to make a valid login.
|
||||
"""
|
||||
username = self.VALID_USERNAME if is_valid else self._get_random_str()
|
||||
if username is None:
|
||||
username = self._get_random_str()
|
||||
|
||||
if password is None:
|
||||
password = self._get_random_str()
|
||||
|
||||
response = self.client.post(ADMIN_LOGIN_URL, {
|
||||
'username': username,
|
||||
'password': username,
|
||||
'password': password,
|
||||
LOGIN_FORM_KEY: 1,
|
||||
}, HTTP_USER_AGENT=user_agent)
|
||||
}, HTTP_USER_AGENT=user_agent, REMOTE_ADDR=remote_addr)
|
||||
|
||||
return response
|
||||
|
||||
|
|
@ -61,9 +69,9 @@ class AccessAttemptTest(TestCase):
|
|||
""" Create a valid user for login
|
||||
"""
|
||||
self.user = User.objects.create_superuser(
|
||||
username=self.VALID_USERNAME,
|
||||
username=VALID_USERNAME,
|
||||
email='test@example.com',
|
||||
password=self.VALID_USERNAME,
|
||||
password=VALID_PASSWORD,
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
|
|
@ -116,7 +124,29 @@ class AccessAttemptTest(TestCase):
|
|||
def test_valid_login(self):
|
||||
""" Tests a valid login for a real username
|
||||
"""
|
||||
response = self._login(is_valid=True)
|
||||
response = self._login(username=VALID_USERNAME, password=VALID_PASSWORD)
|
||||
self.assertNotContains(response, LOGIN_FORM_KEY, status_code=302)
|
||||
|
||||
def test_blocked_ip_cannot_login(self):
|
||||
""" Test an user with blocked ip cannot login with another username
|
||||
"""
|
||||
for i in range(0, config.FAILURE_LIMIT + 1):
|
||||
response = self._login(username=VALID_USERNAME)
|
||||
|
||||
# try to login with a different user
|
||||
response = self._login(username='myuser')
|
||||
self.assertContains(response, self.LOCKED_MESSAGE)
|
||||
|
||||
def test_trusted_user_can_login(self):
|
||||
""" Test an user with a non-blocked ip can login after an attacker has
|
||||
triggered a block with a different ip.
|
||||
"""
|
||||
for i in range(0, config.FAILURE_LIMIT + 1):
|
||||
response = self._login(username=VALID_USERNAME)
|
||||
|
||||
# change the client ip so that can we login again
|
||||
response = self._login(username=VALID_USERNAME, password=VALID_PASSWORD,
|
||||
remote_addr='8.8.8.8')
|
||||
self.assertNotContains(response, LOGIN_FORM_KEY, status_code=302)
|
||||
|
||||
def test_cooling_off(self):
|
||||
|
|
@ -142,7 +172,8 @@ class AccessAttemptTest(TestCase):
|
|||
""" Tests if can handle a long user agent
|
||||
"""
|
||||
long_user_agent = 'ie6' * 1024
|
||||
response = self._login(is_valid=True, user_agent=long_user_agent)
|
||||
response = self._login(username=VALID_USERNAME, password=VALID_PASSWORD,
|
||||
user_agent=long_user_agent)
|
||||
self.assertNotContains(response, LOGIN_FORM_KEY, status_code=302)
|
||||
|
||||
@patch('defender.config.BEHIND_REVERSE_PROXY', True)
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@ def is_already_locked(request):
|
|||
if not user_blocked:
|
||||
user_blocked = False
|
||||
|
||||
return ip_blocked or user_blocked
|
||||
return ip_blocked and user_blocked
|
||||
|
||||
|
||||
def check_request(request, login_unsuccessful):
|
||||
|
|
|
|||
Loading…
Reference in a new issue