Make sure axes can handle weird user agents. Fixes #38

This commit is contained in:
Camilo Nova 2013-04-04 19:19:52 -05:00
parent f502093a7d
commit dacdb32d18
4 changed files with 82 additions and 25 deletions

View file

@ -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:

View file

@ -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', '<unknown>')
ua = request.META.get('HTTP_USER_AGENT', '<unknown>')[: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', '<unknown>'),
user_agent=request.META.get('HTTP_USER_AGENT', '<unknown>')[:255],
ip_address=get_ip(request),
username=request.POST.get('username', None),
http_accept=request.META.get('HTTP_ACCEPT', '<unknown>'),
@ -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', '<unknown>')
ua = request.META.get('HTTP_USER_AGENT', '<unknown>')[: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', '<unknown>')
ua = request.META.get('HTTP_USER_AGENT', '<unknown>')[:255]
username = request.POST.get('username', None)
if not username:

View file

@ -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)

View file

@ -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)