Fixed conflict

This commit is contained in:
Camilo Nova 2013-11-07 18:45:56 -05:00
commit 20ffed0fc4
7 changed files with 111 additions and 21 deletions

View file

@ -1,6 +1,25 @@
Changes
=======
1.3.6 (unreleased)
------------------
- Nothing changed yet.
1.3.5 (2013-11-01)
------------------
- Fix an issue with __version__ loading the wrong version [graingert]
1.3.4 (2013-11-01)
------------------
- Update README.rst for PyPI [marty] [camilonova] [graingert]
- Add cooloff period [visualspace]
1.3.3 (2013-07-05)
------------------

View file

@ -1,9 +1,9 @@
Django Axes
===========
.. image:: https://secure.travis-ci.org/codekoala/django-axes.png?branch=master
.. image:: https://secure.travis-ci.org/django-security/django-axes.png?branch=master
:alt: Build Status
:target: http://travis-ci.org/codekoala/django-axes
:target: http://travis-ci.org/django-security/django-axes
``django-axes`` is a very simple way for you to keep track of failed login
attempts, both for the Django admin and for the rest of your site. The name is
@ -135,10 +135,7 @@ Usage
Using ``django-axes`` is extremely simple. Once you install the application
and the middleware, all you need to do is periodically check the Access
Attempts section of the admin. A log file is also created for you to keep
track of the events surrounding failed login attempts. This log file can be
found in your Django project directory, by the name of ``axes.log``. In the
future I plan on offering a way to customize options for logging a bit more.
Attempts section of the admin.
By default, django-axes will lock out repeated attempts from the same IP
address. You can allow this IP to attempt again by deleting the relevant

View file

@ -1,6 +1,10 @@
VERSION = (1, 3, 3)
try:
__version__ = __import__('pkg_resources').get_distribution(
'django-axes'
).version
except:
__version__ = ''
def get_version():
return '%s.%s.%s' % VERSION
return __version__

View file

@ -133,7 +133,7 @@ def is_user_lockable(request):
try:
profile = user.get_profile()
except (SiteProfileNotAvailable, ObjectDoesNotExist):
except (SiteProfileNotAvailable, ObjectDoesNotExist, AttributeError):
# no profile
return True
@ -144,13 +144,12 @@ def is_user_lockable(request):
else:
return True
def get_user_attempts(request):
def _get_user_attempts(request):
"""Returns access attempt record if it exists.
Otherwise return None.
"""
ip = get_ip(request)
username = request.POST.get('username', None)
if USE_USER_AGENT:
@ -174,11 +173,26 @@ def get_user_attempts(request):
params['username'] = username
attempts |= AccessAttempt.objects.filter(**params)
return attempts
def get_user_attempts(request):
objects_deleted = False
attempts = _get_user_attempts(request)
if COOLOFF_TIME:
for attempt in attempts:
if attempt.attempt_time + COOLOFF_TIME < datetime.now() \
and attempt.trusted is False:
attempt.delete()
if attempt.attempt_time + COOLOFF_TIME < datetime.now():
if attempt.trusted:
attempt.failures_since_start = 0
attempt.save()
else:
attempt.delete()
objects_deleted = True
# If objects were deleted, we need to update the queryset to reflect this,
# so force a reload.
if objects_deleted:
attempts = _get_user_attempts(request)
return attempts

View file

@ -38,3 +38,6 @@ SECRET_KEY = 'too-secret-for-test'
LOGIN_REDIRECT_URL = '/admin'
AXES_LOGIN_FAILURE_LIMIT = 10
from datetime import timedelta
AXES_COOLOFF_TIME=timedelta(seconds = 2)

View file

@ -1,10 +1,11 @@
import random
import string
import time
from django.test import TestCase
from django.test.client import Client
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.test.utils import override_settings
from axes.decorators import FAILURE_LIMIT
from axes.decorators import LOGIN_FORM_KEY
@ -108,6 +109,60 @@ class AccessAttemptTest(TestCase):
self.assertNotIn(LOGIN_FORM_KEY, response.content)
def _successful_login(self, username, password):
c = Client()
response = c.post('/admin/', {
'username': username,
'password': username,
'this_is_the_login_form': 1,
})
return response
def _unsuccessful_login(self, username):
c = Client()
response = c.post('/admin/', {
'username': username,
'password': 'wrong',
'this_is_the_login_form': 1,
})
return response
def test_cooling_off_for_trusted_user(self):
valid_username = self._random_username(existing_username=True)
# Test successful login, this makes the user trusted.
response = self._successful_login(valid_username, valid_username)
self.assertNotIn(LOGIN_FORM_KEY, response.content)
self.test_cooling_off(username=valid_username)
def test_cooling_off(self, username=None):
if username:
valid_username = username
else:
valid_username = self._random_username(existing_username=True)
# Test unsuccessful login and stop just before lockout happens
for i in range(0, FAILURE_LIMIT):
response = self._unsuccessful_login(valid_username)
# Check if we are in the same login page
self.assertIn(LOGIN_FORM_KEY, response.content)
# Lock out the user
response = self._unsuccessful_login(valid_username)
self.assertIn(self.LOCKED_MESSAGE, response.content)
# Wait for the cooling off period
time.sleep(COOLOFF_TIME.total_seconds())
# It should be possible to login again, make sure it is.
response = self._successful_login(valid_username, valid_username)
self.assertNotIn(self.LOCKED_MESSAGE, response.content)
def test_valid_logout(self):
"""Tests a valid logout and make sure the logout_time is updated
"""

View file

@ -3,11 +3,9 @@
from setuptools import setup, find_packages
version = __import__('axes').get_version()
setup(
name='django-axes',
version=version,
version='1.3.6.dev0',
description="Keep track of failed login attempts in Django-powered sites.",
long_description=(open('README.rst', 'r').read() + '\n' +
open('CHANGES.txt', 'r').read()),
@ -16,7 +14,7 @@ setup(
author_email='codekoala@gmail.com',
maintainer='Alex Clark',
maintainer_email='aclark@aclark.net',
url='https://github.com/codekoala/django-axes',
url='https://github.com/django-security/django-axes',
license='MIT',
package_dir={'axes': 'axes'},
include_package_data=True,