Repository: jazzband/django-axes

Issue:
FailedLoginMiddleware Adds the decorator to admin login twice in django 1.4
I'm guessing that django 1.4 changed how this was handled, but using `FailedLoginMiddleware` causes the login decorator to be called twice, adding two failures to the access entry instead of one.

Using only `FailedAuthLoginMiddleware` solves the problem, and it still works for admin.



Discussion:
Can you send a pull request?

Not sure if this is the same issue, but login failures are causing double entries in the Access attempts database. One with the correct IP address but the other with a null IP address.

Django 1.4.2 and django-userena.

@bazaarsoft yea that's what was happening to me. Recording double entries in the db, and locking people out too soon.

Just to confirm, this issue was NOT fixed in [1.2.9](https://github.com/codekoala/django-axes/issues/28).

I wouldn't expect to have been fixed, the issue is still open. (I.e. we're waiting for someone to send a pull request.)

Sorry - the other pull request implied that it was fixed at the same time...


Resolving patch:
diff --git a/axes/admin.py b/axes/admin.py
--- a/axes/admin.py
+++ b/axes/admin.py
@@ -1,13 +1,32 @@
 from django.contrib import admin
-from axes.models import AccessAttempt, AccessLog
+
+from axes.models import AccessLog
+from axes.models import AccessAttempt
 
 
 class AccessAttemptAdmin(admin.ModelAdmin):
-    list_display = ('attempt_time', 'ip_address', 'user_agent', 'path_info',
-                    'failures_since_start')
-    list_filter = ['attempt_time', 'ip_address', 'path_info']
-    search_fields = ['ip_address', 'user_agent', 'path_info']
+    list_display = (
+        'attempt_time',
+        'ip_address',
+        'user_agent',
+        'path_info',
+        'failures_since_start',
+    )
+
+    list_filter = [
+        'attempt_time',
+        'ip_address',
+        'path_info',
+    ]
+
+    search_fields = [
+        'ip_address',
+        'user_agent',
+        'path_info',
+    ]
+
     date_hierarchy = 'attempt_time'
+
     fieldsets = (
         (None, {
             'fields': ('path_info', 'failures_since_start')
@@ -22,12 +41,31 @@ class AccessAttemptAdmin(admin.ModelAdmin):
 
 admin.site.register(AccessAttempt, AccessAttemptAdmin)
 
+
 class AccessLogAdmin(admin.ModelAdmin):
-    list_display = ('attempt_time','logout_time', 'ip_address', 
-        'user_agent', 'path_info')
-    list_filter = ['attempt_time', 'logout_time', 'ip_address', 'path_info']
-    search_fields = ['ip_address', 'user_agent', 'path_info']
+    list_display = (
+        'attempt_time',
+        'logout_time',
+        'ip_address',
+        'user_agent',
+        'path_info',
+    )
+
+    list_filter = [
+        'attempt_time',
+        'logout_time',
+        'ip_address',
+        'path_info',
+    ]
+
+    search_fields = [
+        'ip_address',
+        'user_agent',
+        'path_info',
+    ]
+
     date_hierarchy = 'attempt_time'
+
     fieldsets = (
         (None, {
             'fields': ('path_info',)
@@ -37,4 +75,4 @@ class AccessLogAdmin(admin.ModelAdmin):
         })
     )
 
-admin.site.register(AccessLog, AccessLogAdmin)
\ No newline at end of file
+admin.site.register(AccessLog, AccessLogAdmin)
diff --git a/axes/decorators.py b/axes/decorators.py
--- a/axes/decorators.py
+++ b/axes/decorators.py
@@ -94,15 +94,15 @@ def query2str(items):
 def ip_in_whitelist(ip):
     if IP_WHITELIST is not None:
         return ip in IP_WHITELIST
-    else:
-        return False
+
+    return False
 
 
 def ip_in_blacklist(ip):
     if IP_BLACKLIST is not None:
         return ip in IP_BLACKLIST
-    else:
-        return False
+
+    return False
 
 
 log = logging.getLogger(LOGGER)
@@ -110,6 +110,7 @@ def ip_in_blacklist(ip):
     log.info('AXES: BEGIN LOG')
     log.info('Using django-axes ' + axes.get_version())
 
+
 def is_user_lockable(request):
     """ Check if the user has a profile with nolockout
     If so, then return the value to see if this user is special
@@ -133,9 +134,9 @@ def is_user_lockable(request):
     else:
         return True
 
+
 def get_user_attempts(request):
-    """
-    Returns access attempt record if it exists.
+    """Returns access attempt record if it exists.
     Otherwise return None.
     """
     ip = get_ip(request)
@@ -152,7 +153,7 @@ def get_user_attempts(request):
             ip_address=ip, username=username, trusted=True
         )
 
-    if len(attempts) == 0:
+    if not attempts:
         params = {'ip_address': ip, 'trusted': False}
         if USE_USER_AGENT:
             params['user_agent'] = ua
@@ -218,11 +219,20 @@ def decorated_login(request, *args, **kwargs):
                 not response.has_header('location') and
                 response.status_code != 302
             )
-            log_access_request(request, login_unsuccessful)
+
+            access_log = AccessLog.objects.create(
+                user_agent=request.META.get('HTTP_USER_AGENT', '<unknown>'),
+                ip_address=get_ip(request),
+                username=request.POST.get('username', None),
+                http_accept=request.META.get('HTTP_ACCEPT', '<unknown>'),
+                path_info=request.META.get('PATH_INFO', '<unknown>'),
+                trusted=not login_unsuccessful,
+            )
             if check_request(request, login_unsuccessful):
                 return response
 
             return lockout_response(request)
+
         return response
 
     return decorated_login
@@ -268,18 +278,6 @@ def is_already_locked(request):
     return False
 
 
-def log_access_request(request, login_unsuccessful):
-    """ Log the access attempt """
-    access_log = AccessLog()
-    access_log.user_agent = request.META.get('HTTP_USER_AGENT', '<unknown>')
-    access_log.ip_address = get_ip(request)
-    access_log.username = request.POST.get('username', None)
-    access_log.http_accept = request.META.get('HTTP_ACCEPT', '<unknown>')
-    access_log.path_info = request.META.get('PATH_INFO', '<unknown>')
-    access_log.trusted = not login_unsuccessful
-    access_log.save()
-
-
 def check_request(request, login_unsuccessful):
     ip_address = get_ip(request)
     username = request.POST.get('username', None)
diff --git a/axes/middleware.py b/axes/middleware.py
--- a/axes/middleware.py
+++ b/axes/middleware.py
@@ -1,31 +1,11 @@
-from django.contrib import admin
 from django.contrib.auth import views as auth_views
+
 from axes.decorators import watch_login
 
 
 class FailedLoginMiddleware(object):
-
     def __init__(self, *args, **kwargs):
         super(FailedLoginMiddleware, self).__init__(*args, **kwargs)
 
-        # watch the admin login page
-        admin.site.login = watch_login(admin.site.login)
-
-        # and the regular auth login page
-        auth_views.login = watch_login(auth_views.login)
-
-
-class FailedAdminLoginMiddleware(object):
-    def __init__(self, *args, **kwargs):
-        super(FailedAdminLoginMiddleware, self).__init__(*args, **kwargs)
-
-        # watch the admin login page
-        admin.site.login = watch_login(admin.site.login)
-
-
-class FailedAuthLoginMiddleware(object):
-    def __init__(self, *args, **kwargs):
-        super(FailedAuthLoginMiddleware, self).__init__(*args, **kwargs)
-
-        # watch the admin login page
+        # watch the auth login
         auth_views.login = watch_login(auth_views.login)


Test patch: