Repository: jazzband/django-axes

Issue:
axes-reset doesn't help "trusted" users
Hi,

There is this comment in the reset code: 

&#35; no need to reset trusted records. If they fail, they go to untrusted

And you then add 'trusted': false to params.

However I have access attempts in the db that are marked as trusted, are over the failure limit, and therefore there are users getting locked out permanently due to the is_already_locked() short circuit in the decorated_login function.

Any idea what might be going wrong here?

Thanks in advance,

Ben



Discussion:
No but maybe @camilonova can help

@benpbenp @aclark4life I see two options here, one is to reset the trusted records as well (i like this one) and the other one is to check is_already_locked() how is working with trusted records, what you guys think is better?

@camilonova Resetting trusted users as part of "axes-reset" seems reasonable to me


Resolving patch:
diff --git a/axes/management/commands/axes_reset.py b/axes/management/commands/axes_reset.py
--- a/axes/management/commands/axes_reset.py
+++ b/axes/management/commands/axes_reset.py
@@ -1,4 +1,6 @@
-from django.core.management.base import BaseCommand, CommandError
+from django.core.management.base import BaseCommand
+from django.core.management.base import CommandError
+
 from axes.utils import reset
 
 
@@ -8,8 +10,14 @@ class Command(BaseCommand):
             "IP, resets only for that IP")
 
     def handle(self, *args, **kwargs):
+        count = 0
         if args:
             for ip in args:
-                reset(ip)
+                count += reset(ip=ip)
+        else:
+            count = reset()
+
+        if count:
+            print '{0} attempts removed.'.format(count)
         else:
-            reset()
+            print 'No attempts found.'
diff --git a/axes/utils.py b/axes/utils.py
--- a/axes/utils.py
+++ b/axes/utils.py
@@ -1,25 +1,20 @@
 from axes.models import AccessAttempt
 
 
-def reset(ip=None, username=None, silent=False):
-    # no need to reset trusted records. If they fail, they go to untrusted
-    params = {
-        'trusted': False,
-    }
+def reset(ip=None, username=None):
+    """Reset records that match ip or username, and
+    return the count of removed attempts.
+    """
+    count = 0
 
+    attempts = AccessAttempt.objects.all()
     if ip:
-        params['ip_address'] = ip
-
-    attempts = AccessAttempt.objects.filter(**params)
+        attempts = attempts.filter(ip_address=ip)
     if username:
-        if 'ip_address' in params:
-            del params['ip_address']
-
-        params['username'] = username
-        attempts |= AccessAttempt.objects.filter(**params)
+        attempts = attempts.filter(username=username)
 
     if attempts:
+        count = attempts.count()
         attempts.delete()
-    else:
-        if not silent:
-            print 'No attempts found.'
+
+    return count


Test patch:
diff --git a/axes/tests.py b/axes/tests.py
--- a/axes/tests.py
+++ b/axes/tests.py
@@ -6,10 +6,11 @@
 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, COOLOFF_TIME, LOGIN_FORM_KEY
-from axes.models import AccessLog, AccessAttempt
+from axes.decorators import FAILURE_LIMIT
+from axes.decorators import LOGIN_FORM_KEY
+from axes.models import AccessLog
+from axes.utils import reset
 
 
 class AccessAttemptTest(TestCase):
@@ -206,3 +207,27 @@ def test_long_user_agent_not_valid(self):
 
         response = self._login()
         self.assertIn(self.LOCKED_MESSAGE, response.content)
+
+    def test_reset_ip(self):
+        """Tests if can reset an ip address
+        """
+        # Make a lockout
+        self.test_with_real_username_max()
+
+        # Reset the ip so we can try again
+        reset(ip='127.0.0.1')
+
+        # Make a login attempt again
+        self.test_with_real_username_max()
+
+    def test_reset_all(self):
+        """Tests if can reset all attempts
+        """
+        # Make a lockout
+        self.test_with_real_username_max()
+
+        # Reset all attempts so we can try again
+        reset()
+
+        # Make a login attempt again
+        self.test_with_real_username_max()