From dacdb32d1852d52fb8a36886d591ec876095d05f Mon Sep 17 00:00:00 2001 From: Camilo Nova Date: Thu, 4 Apr 2013 19:19:52 -0500 Subject: [PATCH] Make sure axes can handle weird user agents. Fixes #38 --- .travis.yml | 1 + axes/decorators.py | 8 +++--- axes/models.py | 69 +++++++++++++++++++++++++++++++++------------- axes/tests.py | 29 +++++++++++++++++-- 4 files changed, 82 insertions(+), 25 deletions(-) diff --git a/.travis.yml b/.travis.yml index eee3f05..1b14e17 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ python: env: - PYTHONPATH=$PYTHONPATH:$PWD DJANGO_VERSION=1.4.5 - PYTHONPATH=$PYTHONPATH:$PWD DJANGO_VERSION=1.5 + - PYTHONPATH=$PYTHONPATH:$PWD DJANGO_VERSION=1.5.1 install: - pip install --use-mirrors Django==$DJANGO_VERSION script: diff --git a/axes/decorators.py b/axes/decorators.py index 52ca507..995057f 100644 --- a/axes/decorators.py +++ b/axes/decorators.py @@ -160,7 +160,7 @@ def get_user_attempts(request): username = request.POST.get('username', None) if USE_USER_AGENT: - ua = request.META.get('HTTP_USER_AGENT', '') + ua = request.META.get('HTTP_USER_AGENT', '')[:255] attempts = AccessAttempt.objects.filter( user_agent=ua, ip_address=ip, username=username, trusted=True ) @@ -237,7 +237,7 @@ def watch_login(func): ) access_log = AccessLog.objects.create( - user_agent=request.META.get('HTTP_USER_AGENT', ''), + user_agent=request.META.get('HTTP_USER_AGENT', '')[:255], ip_address=get_ip(request), username=request.POST.get('username', None), http_accept=request.META.get('HTTP_ACCEPT', ''), @@ -368,7 +368,7 @@ def check_request(request, login_unsuccessful): def create_new_failure_records(request, failures): ip = get_ip(request) - ua = request.META.get('HTTP_USER_AGENT', '') + ua = request.META.get('HTTP_USER_AGENT', '')[:255] username = request.POST.get('username', None) params = { @@ -397,7 +397,7 @@ def create_new_failure_records(request, failures): def create_new_trusted_record(request): ip = get_ip(request) - ua = request.META.get('HTTP_USER_AGENT', '') + ua = request.META.get('HTTP_USER_AGENT', '')[:255] username = request.POST.get('username', None) if not username: diff --git a/axes/models.py b/axes/models.py index 2245986..f29a3a6 100644 --- a/axes/models.py +++ b/axes/models.py @@ -1,41 +1,72 @@ from django.db import models -#import signals -FAILURES_DESC = 'Failed Logins' -#XXX TODO -# set unique by user_agent, ip -# make user agent, ip indexed fields class CommonAccess(models.Model): - user_agent = models.CharField(max_length=255) - ip_address = models.IPAddressField('IP Address', null=True) - username = models.CharField(max_length=255, null=True) + user_agent = models.CharField( + max_length=255, + ) + + ip_address = models.IPAddressField( + verbose_name='IP Address', + null=True, + ) + + username = models.CharField( + max_length=255, + null=True, + ) # Once a user logs in from an ip, that combination is trusted and not # locked out in case of a distributed attack - trusted = models.BooleanField(default=False) - http_accept = models.CharField('HTTP Accept', max_length=1025) - path_info = models.CharField('Path', max_length=255) - attempt_time = models.DateTimeField(auto_now_add=True) + trusted = models.BooleanField( + default=False, + ) + + http_accept = models.CharField( + verbose_name='HTTP Accept', + max_length=1025, + ) + + path_info = models.CharField( + verbose_name='Path', + max_length=255, + ) + + attempt_time = models.DateTimeField( + auto_now_add=True, + ) class Meta: abstract = True ordering = ['-attempt_time'] -class AccessAttempt(CommonAccess): - get_data = models.TextField('GET Data') - post_data = models.TextField('POST Data') - failures_since_start = models.PositiveIntegerField(FAILURES_DESC) - def __unicode__(self): - return u'Attempted Access: %s' % self.attempt_time +class AccessAttempt(CommonAccess): + get_data = models.TextField( + verbose_name='GET Data', + ) + + post_data = models.TextField( + verbose_name='POST Data', + ) + + failures_since_start = models.PositiveIntegerField( + verbose_name='Failed Logins', + ) @property def failures(self): return self.failures_since_start + def __unicode__(self): + return u'Attempted Access: %s' % self.attempt_time + + class AccessLog(CommonAccess): - logout_time = models.DateTimeField(null=True, blank=True) + logout_time = models.DateTimeField( + null=True, + blank=True, + ) def __unicode__(self): return u'Access Log for %s @ %s' % (self.username, self.attempt_time) diff --git a/axes/tests.py b/axes/tests.py index bfc7d62..c7f1057 100644 --- a/axes/tests.py +++ b/axes/tests.py @@ -30,11 +30,11 @@ class AccessAttemptTest(TestCase): return self._generate_random_string() - def _login(self, existing_username=False): + def _login(self, existing_username=False, user_agent='test-browser'): response = self.client.post(reverse('admin:index'), { 'username': self._random_username(existing_username), 'password': self._generate_random_string(), - }) + }, HTTP_USER_AGENT=user_agent) return response @@ -101,3 +101,28 @@ class AccessAttemptTest(TestCase): 'password': valid_username }) self.assertNotIn(LOGIN_FORM_KEY, response.context) + + def test_long_user_agent_valid(self): + """Tests if can handle a long user agent + """ + long_user_agent = 'ie6' * 1024 + valid_username = self._random_username(existing_username=True) + response = self.client.post(reverse('admin:index'), { + 'username': valid_username, + 'password': valid_username + }, HTTP_USER_AGENT=long_user_agent) + self.assertNotIn(LOGIN_FORM_KEY, response.context) + + def test_long_user_agent_not_valid(self): + """Tests if can handle a long user agent with failure + """ + long_user_agent = 'ie6' * 1024 + for i in range(0, FAILURE_LIMIT): + response = self._login( + existing_username=False, + user_agent=long_user_agent, + ) + self.assertContains(response, LOGIN_FORM_KEY) + + response = self._login() + self.assertContains(response, self.LOCKED_MESSAGE)