Merge pull request #125 from AFioca/master

Fix #_get_user_attempts to include username when filtering AccessAtte…
This commit is contained in:
Camilo Nova 2015-09-01 11:13:35 -05:00
commit 697147b46f
2 changed files with 36 additions and 11 deletions

View file

@ -56,7 +56,8 @@ BEHIND_REVERSE_PROXY_WITH_DIRECT_ACCESS = getattr(settings, 'AXES_BEHIND_REVERSE
REVERSE_PROXY_HEADER = getattr(settings, 'AXES_REVERSE_PROXY_HEADER', 'HTTP_X_FORWARDED_FOR')
# lock out user from particular IP based on combination USER+IP
LOCK_OUT_BY_COMBINATION_USER_AND_IP = getattr(settings, 'AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP', False)
def should_lock_out_by_combination_user_and_ip():
return getattr(settings, 'AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP', False)
COOLOFF_TIME = getattr(settings, 'AXES_COOLOFF_TIME', None)
if (isinstance(COOLOFF_TIME, int) or isinstance(COOLOFF_TIME, float) ):
@ -238,10 +239,12 @@ def _get_user_attempts(request):
ip_address=ip, username=username, trusted=True
)
if not attempts and not LOCK_OUT_BY_COMBINATION_USER_AND_IP:
if not attempts:
params = {'ip_address': ip, 'trusted': False}
if USE_USER_AGENT:
params['user_agent'] = ua
if should_lock_out_by_combination_user_and_ip():
params['username'] = username
attempts = AccessAttempt.objects.filter(**params)

View file

@ -3,6 +3,7 @@ import string
import time
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
@ -17,12 +18,13 @@ from axes.utils import reset
class AccessAttemptTest(TestCase):
"""Test case using custom settings for testing
"""
VALID_USERNAME = 'valid-username'
VALID_PASSWORD = 'valid-password'
LOCKED_MESSAGE = 'Account locked: too many login attempts.'
LOGIN_FORM_KEY = '<input type="submit" value="Log in" />'
def _login(self, is_valid=False, user_agent='test-browser'):
"""Login a user. A valid credential is used when is_valid is True,
def _login(self, is_valid_username=False, is_valid_password=False, user_agent='test-browser'):
"""Login a user. A valid credential is used when is_valid_username is True,
otherwise it will use a random string to make a failed login.
"""
try:
@ -30,17 +32,22 @@ class AccessAttemptTest(TestCase):
except NoReverseMatch:
admin_login = reverse('admin:index')
if is_valid:
if is_valid_username:
# Use a valid username
username = self.user.username
username = self.VALID_USERNAME
else:
# Generate a wrong random username
chars = string.ascii_uppercase + string.digits
username = ''.join(random.choice(chars) for x in range(10))
if is_valid_password:
password = self.VALID_PASSWORD
else:
password = 'invalid-password'
response = self.client.post(admin_login, {
'username': username,
'password': self.VALID_PASSWORD,
'password': password,
'this_is_the_login_form': 1,
}, HTTP_USER_AGENT=user_agent)
@ -50,7 +57,7 @@ class AccessAttemptTest(TestCase):
"""Create a valid user for login
"""
self.user = User.objects.create_superuser(
username='valid-username',
username=self.VALID_USERNAME,
email='test@example.com',
password=self.VALID_PASSWORD,
)
@ -87,13 +94,13 @@ 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(is_valid_username=True, is_valid_password=True)
self.assertNotContains(response, self.LOGIN_FORM_KEY, status_code=302)
def test_valid_logout(self):
"""Tests a valid logout and make sure the logout_time is updated
"""
response = self._login(is_valid=True)
response = self._login(is_valid_username=True, is_valid_password=True)
self.assertEquals(AccessLog.objects.latest('id').logout_time, None)
response = self.client.get(reverse('admin:logout'))
@ -124,7 +131,7 @@ 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(is_valid_username=True, is_valid_password=True, user_agent=long_user_agent)
self.assertNotContains(response, self.LOGIN_FORM_KEY, status_code=302)
def test_long_user_agent_not_valid(self):
@ -183,3 +190,18 @@ class AccessAttemptTest(TestCase):
# Make another lockout
self.test_failure_limit_once()
self.assertEquals(scope.signal_received, 2)
@override_settings(AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP=True)
def test_lockout_by_combination_user_and_ip(self):
"""Tests the login lock with a valid username and invalid password
when AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP is True
"""
for i in range(1, FAILURE_LIMIT): # test until one try before the limit
response = self._login(is_valid_username=True, is_valid_password=False)
# Check if we are in the same login page
self.assertContains(response, self.LOGIN_FORM_KEY)
# So, we shouldn't have gotten a lock-out yet.
# But we should get one now
response = self._login(is_valid_username=True, is_valid_password=False)
self.assertContains(response, self.LOCKED_MESSAGE)