Merge pull request #158 from mullakhmetov/json-response-type

Added JSON response type.
This commit is contained in:
Camilo Nova 2016-06-07 09:12:10 -05:00
commit a87ffa6874
3 changed files with 83 additions and 8 deletions

View file

@ -1,5 +1,6 @@
import logging
import socket
import json
from datetime import timedelta
@ -30,6 +31,7 @@ from axes.models import AccessAttempt
from axes.signals import user_locked_out
import axes
from django.utils import six
from axes.utils import iso8601
# see if the user has overridden the failure limit
@ -352,12 +354,19 @@ def watch_login(func):
def lockout_response(request):
context = {
'failure_limit': FAILURE_LIMIT,
'username': request.POST.get(USERNAME_FORM_FIELD, '')
}
if request.is_ajax():
context.update({'cooloff_time': iso8601(COOLOFF_TIME)})
return HttpResponse(json.dumps(context),
content_type='application/json',
status=403)
if LOCKOUT_TEMPLATE:
context = {
'cooloff_time': COOLOFF_TIME,
'failure_limit': FAILURE_LIMIT,
'username': request.POST.get(USERNAME_FORM_FIELD, '')
}
context.update({'cooloff_time': COOLOFF_TIME})
return render(request, LOCKOUT_TEMPLATE, context, status=403)
LOCKOUT_URL = get_lockout_url()

View file

@ -1,6 +1,8 @@
import random
import string
import time
import json
import datetime
from django.test import TestCase
from django.test.utils import override_settings
@ -14,7 +16,7 @@ from axes.decorators import FAILURE_LIMIT
from axes.decorators import is_valid_public_ip
from axes.models import AccessAttempt, AccessLog
from axes.signals import user_locked_out
from axes.utils import reset
from axes.utils import reset, iso8601
class AccessAttemptTest(TestCase):
@ -25,7 +27,8 @@ class AccessAttemptTest(TestCase):
LOCKED_MESSAGE = 'Account locked: too many login attempts.'
LOGIN_FORM_KEY = '<input type="submit" value="Log in" />'
def _login(self, is_valid_username=False, is_valid_password=False, user_agent='test-browser', **kwargs):
def _login(self, is_valid_username=False, is_valid_password=False,
is_json=False, **kwargs):
"""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.
"""
@ -47,13 +50,23 @@ class AccessAttemptTest(TestCase):
else:
password = 'invalid-password'
headers = {
'user_agent': 'test-browser'
}
post_data = {
'username': username,
'password': password,
'this_is_the_login_form': 1,
}
post_data.update(kwargs)
response = self.client.post(admin_login, post_data, HTTP_USER_AGENT=user_agent)
if is_json:
headers.update({
'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest',
'content_type': 'application/json',
})
post_data = json.dumps(post_data)
response = self.client.post(admin_login, post_data, **headers)
return response
@ -217,6 +230,14 @@ class AccessAttemptTest(TestCase):
self._login(**extra_data)
self.assertEquals(len(AccessAttempt.objects.latest('id').post_data), 1024)
def test_json_response(self):
"""Tests response content type and status code for the ajax request
"""
self.test_failure_limit_once()
response = self._login(is_json=True)
self.assertEquals(response.status_code, 403)
self.assertEquals(response.get('Content-Type'), 'application/json')
class IPClassifierTest(TestCase):
@ -239,3 +260,27 @@ class IPClassifierTest(TestCase):
}
for ip_address, is_valid_public in six.iteritems(EXPECTED):
self.assertEqual(is_valid_public_ip(ip_address), is_valid_public)
class UtilsTest(TestCase):
def test_iso8601(self):
"""Tests iso8601 correctly translates datetime.timdelta to ISO 8601
formatted duration."""
EXPECTED = {
datetime.timedelta(days=1, hours=25, minutes=42, seconds=8):
'P2D1H42M8S',
datetime.timedelta(days=7, seconds=342):
'P7D5M42S',
datetime.timedelta(days=0, hours=2, minutes=42):
'P2H42M',
datetime.timedelta(hours=20, seconds=42):
'P20H42S',
datetime.timedelta(seconds=300):
'P5M',
datetime.timedelta(seconds=9005):
'P2H30M5S',
datetime.timedelta(minutes=9005):
'P6D6H5M'
}
for timedelta, iso_duration in six.iteritems(EXPECTED):
self.assertEqual(iso8601(timedelta), iso_duration)

View file

@ -18,3 +18,24 @@ def reset(ip=None, username=None):
attempts.delete()
return count
def iso8601(value):
"""Returns datetime.timedelta translated to ISO 8601 formatted duration.
"""
seconds = value.total_seconds()
minutes, seconds = divmod(seconds, 60)
hours, minutes = divmod(minutes, 60)
days, hours = divmod(hours, 24)
date = '{:.0f}D'.format(days) if days else ''
time_values = hours, minutes, seconds
time_designators = 'H', 'M', 'S'
time = ''.join(
[('{:.0f}'.format(value) + designator)
for value, designator in zip(time_values, time_designators)
if value]
)
return u'P' + date + time