Browse Source

HUE-3861 [core] Upgrade Django Axes to include fix for AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP (#368)

1.6.0
Jenny Kim 9 years ago
parent
commit
3dac1a2
43 changed files with 338 additions and 40 deletions
  1. 4 0
      desktop/core/ext-py/django-axes-1.6.0/.travis.yml
  2. 27 0
      desktop/core/ext-py/django-axes-1.6.0/CHANGES.txt
  3. 0 0
      desktop/core/ext-py/django-axes-1.6.0/LICENSE
  4. 0 0
      desktop/core/ext-py/django-axes-1.6.0/MANIFEST.in
  5. 1 1
      desktop/core/ext-py/django-axes-1.6.0/Makefile
  6. 76 1
      desktop/core/ext-py/django-axes-1.6.0/PKG-INFO
  7. 48 0
      desktop/core/ext-py/django-axes-1.6.0/README.rst
  8. 0 0
      desktop/core/ext-py/django-axes-1.6.0/axes/__init__.py
  9. 0 0
      desktop/core/ext-py/django-axes-1.6.0/axes/admin.py
  10. 29 25
      desktop/core/ext-py/django-axes-1.6.0/axes/decorators.py
  11. 0 0
      desktop/core/ext-py/django-axes-1.6.0/axes/management/__init__.py
  12. 0 0
      desktop/core/ext-py/django-axes-1.6.0/axes/management/commands/__init__.py
  13. 0 0
      desktop/core/ext-py/django-axes-1.6.0/axes/management/commands/axes_list_attempts.py
  14. 0 0
      desktop/core/ext-py/django-axes-1.6.0/axes/management/commands/axes_reset.py
  15. 0 0
      desktop/core/ext-py/django-axes-1.6.0/axes/middleware.py
  16. 51 0
      desktop/core/ext-py/django-axes-1.6.0/axes/migrations/0001_initial.py
  17. 54 0
      desktop/core/ext-py/django-axes-1.6.0/axes/migrations/0002_auto_20151217_2044.py
  18. 0 0
      desktop/core/ext-py/django-axes-1.6.0/axes/migrations/__init__.py
  19. 4 0
      desktop/core/ext-py/django-axes-1.6.0/axes/models.py
  20. 0 0
      desktop/core/ext-py/django-axes-1.6.0/axes/signals.py
  21. 0 0
      desktop/core/ext-py/django-axes-1.6.0/axes/test_settings.py
  22. 0 0
      desktop/core/ext-py/django-axes-1.6.0/axes/test_urls.py
  23. 43 12
      desktop/core/ext-py/django-axes-1.6.0/axes/tests.py
  24. 0 0
      desktop/core/ext-py/django-axes-1.6.0/axes/utils.py
  25. 0 0
      desktop/core/ext-py/django-axes-1.6.0/axes/views.py
  26. 0 0
      desktop/core/ext-py/django-axes-1.6.0/examples/README.rst
  27. 0 0
      desktop/core/ext-py/django-axes-1.6.0/examples/example/__init__.py
  28. 0 0
      desktop/core/ext-py/django-axes-1.6.0/examples/example/foo/__init__.py
  29. 0 0
      desktop/core/ext-py/django-axes-1.6.0/examples/example/foo/management/__init__.py
  30. 0 0
      desktop/core/ext-py/django-axes-1.6.0/examples/example/foo/management/commands/__init__.py
  31. 0 0
      desktop/core/ext-py/django-axes-1.6.0/examples/example/foo/management/commands/axes_create_test_data.py
  32. 0 0
      desktop/core/ext-py/django-axes-1.6.0/examples/example/foo/models.py
  33. 0 0
      desktop/core/ext-py/django-axes-1.6.0/examples/example/local_settings.example
  34. 0 0
      desktop/core/ext-py/django-axes-1.6.0/examples/example/manage.py
  35. 0 0
      desktop/core/ext-py/django-axes-1.6.0/examples/example/settings.py
  36. 0 0
      desktop/core/ext-py/django-axes-1.6.0/examples/example/urls.py
  37. 0 0
      desktop/core/ext-py/django-axes-1.6.0/examples/example/wsgi.py
  38. 0 0
      desktop/core/ext-py/django-axes-1.6.0/examples/install.sh
  39. 0 0
      desktop/core/ext-py/django-axes-1.6.0/examples/reinstall.sh
  40. 0 0
      desktop/core/ext-py/django-axes-1.6.0/examples/test.sh
  41. 0 0
      desktop/core/ext-py/django-axes-1.6.0/examples/uninstall.sh
  42. 0 0
      desktop/core/ext-py/django-axes-1.6.0/setup.cfg
  43. 1 1
      desktop/core/ext-py/django-axes-1.6.0/setup.py

+ 4 - 0
desktop/core/ext-py/django-axes-1.4.0/.travis.yml → desktop/core/ext-py/django-axes-1.6.0/.travis.yml

@@ -3,12 +3,14 @@ language: python
 python:
   - 2.7
   - 3.3
+  - 3.4
 
 env:
   - DJANGO=Django==1.4.15
   - DJANGO=Django==1.5.10
   - DJANGO=Django==1.6.7
   - DJANGO=Django==1.7
+  - DJANGO=Django==1.8.5
 
 install:
   - pip install -q $DJANGO
@@ -20,3 +22,5 @@ matrix:
     exclude:
         - python: 3.3
           env: DJANGO=Django==1.4.15
+        - python: 3.4
+          env: DJANGO=Django==1.4.15

+ 27 - 0
desktop/core/ext-py/django-axes-1.4.0/CHANGES.txt → desktop/core/ext-py/django-axes-1.6.0/CHANGES.txt

@@ -1,6 +1,33 @@
 Changes
 =======
 
+1.6.0 (2016-01-07)
+------------------
+
+- Stopped using render_to_response so that other template engines work
+  [tarkatronic]
+
+- Improved performance & DoS prevention on query2str
+  [tarkatronic]
+
+- Immediately return from is_already_locked if the user is not lockable
+  [jdunck]
+
+- Iterate over ip addresses only once
+  [annp89]
+
+- added initial migration files to support django 1.7 &up
+  [ibaguio]
+
+- Add db indexes to CommonAccess model
+  [Schweigi]
+
+1.5.0 (2015-09-11)
+------------------
+
+- Fix #_get_user_attempts to include username when filtering AccessAttempts if AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP is True
+  [afioca]
+
 1.4.0 (2015-08-09)
 ------------------
 

+ 0 - 0
desktop/core/ext-py/django-axes-1.4.0/LICENSE → desktop/core/ext-py/django-axes-1.6.0/LICENSE


+ 0 - 0
desktop/core/ext-py/django-axes-1.4.0/MANIFEST.in → desktop/core/ext-py/django-axes-1.6.0/MANIFEST.in


+ 1 - 1
desktop/core/ext-py/django-axes-1.4.0/Makefile → desktop/core/ext-py/django-axes-1.6.0/Makefile

@@ -7,7 +7,7 @@ viewdoc:
 	viewdoc
 pyroma:
 	pyroma .
-releasetest:
+testrelease:
 	python setup.py sdist --format=zip upload -r test
 release:
 	python setup.py sdist --format=zip upload

+ 76 - 1
desktop/core/ext-py/django-axes-1.4.0/PKG-INFO → desktop/core/ext-py/django-axes-1.6.0/PKG-INFO

@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: django-axes
-Version: 1.4.0
+Version: 1.6.0
 Summary: Keep track of failed login attempts in Django-powered sites.
 Home-page: https://github.com/django-pci/django-axes
 Author: Alex Clark
@@ -174,9 +174,84 @@ Description: Django Axes
         
             AXES_USERNAME_FORM_FIELD = "email"
         
+        
+        Using a captcha
+        ===============
+        
+        Using https://github.com/mbi/django-simple-captcha you do the following:
+        
+        1. Change axes lockout url in ``settings.py``::
+        
+            AXES_LOCKOUT_URL = '/locked'
+        
+        2. Add the url in ``urls.py``::
+        
+            url(r'^locked/$', locked_out, name='locked_out'),
+        
+        3. Create a captcha form::
+        
+            class AxesCaptchaForm(forms.Form):
+                captcha = CaptchaField()
+        
+        4. Create a captcha view for the above url that resets on captcha success and redirects::
+        
+            def locked_out(request):
+                if request.POST:
+                    form = AxesCaptchaForm(request.POST)
+                    if form.is_valid():
+                        ip = get_ip_address_from_request(request)
+                        reset(ip=ip)
+                        return HttpResponseRedirect(reverse_lazy('signin'))
+                else:
+                    form = AxesCaptchaForm()
+            
+                return render_to_response('locked_out.html', dict(form=form), context_instance=RequestContext(request))
+        
+        5. Add a captcha template::
+        
+            <form action="" method="post">
+                {% csrf_token %}
+            
+                {{ form.captcha.errors }}
+                {{ form.captcha }}
+            
+                <div class="form-actions">
+                    <input type="submit" value="Submit" />
+                </div>
+            </form>
+        
+        Done.
+        
         Changes
         =======
         
+        1.6.0 (2016-01-07)
+        ------------------
+        
+        - Stopped using render_to_response so that other template engines work
+          [tarkatronic]
+        
+        - Improved performance & DoS prevention on query2str
+          [tarkatronic]
+        
+        - Immediately return from is_already_locked if the user is not lockable
+          [jdunck]
+        
+        - Iterate over ip addresses only once
+          [annp89]
+        
+        - added initial migration files to support django 1.7 &up
+          [ibaguio]
+        
+        - Add db indexes to CommonAccess model
+          [Schweigi]
+        
+        1.5.0 (2015-09-11)
+        ------------------
+        
+        - Fix #_get_user_attempts to include username when filtering AccessAttempts if AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP is True
+          [afioca]
+        
         1.4.0 (2015-08-09)
         ------------------
         

+ 48 - 0
desktop/core/ext-py/django-axes-1.4.0/README.rst → desktop/core/ext-py/django-axes-1.6.0/README.rst

@@ -165,3 +165,51 @@ leads to all users attempts being lumped together. To fix this add the following
 to your settings:
 
     AXES_USERNAME_FORM_FIELD = "email"
+
+
+Using a captcha
+===============
+
+Using https://github.com/mbi/django-simple-captcha you do the following:
+
+1. Change axes lockout url in ``settings.py``::
+
+    AXES_LOCKOUT_URL = '/locked'
+
+2. Add the url in ``urls.py``::
+
+    url(r'^locked/$', locked_out, name='locked_out'),
+
+3. Create a captcha form::
+
+    class AxesCaptchaForm(forms.Form):
+        captcha = CaptchaField()
+
+4. Create a captcha view for the above url that resets on captcha success and redirects::
+
+    def locked_out(request):
+        if request.POST:
+            form = AxesCaptchaForm(request.POST)
+            if form.is_valid():
+                ip = get_ip_address_from_request(request)
+                reset(ip=ip)
+                return HttpResponseRedirect(reverse_lazy('signin'))
+        else:
+            form = AxesCaptchaForm()
+    
+        return render_to_response('locked_out.html', dict(form=form), context_instance=RequestContext(request))
+
+5. Add a captcha template::
+
+    <form action="" method="post">
+        {% csrf_token %}
+    
+        {{ form.captcha.errors }}
+        {{ form.captcha }}
+    
+        <div class="form-actions">
+            <input type="submit" value="Submit" />
+        </div>
+    </form>
+
+Done.

+ 0 - 0
desktop/core/ext-py/django-axes-1.4.0/axes/__init__.py → desktop/core/ext-py/django-axes-1.6.0/axes/__init__.py


+ 0 - 0
desktop/core/ext-py/django-axes-1.4.0/axes/admin.py → desktop/core/ext-py/django-axes-1.6.0/axes/admin.py


+ 29 - 25
desktop/core/ext-py/django-axes-1.4.0/axes/decorators.py → desktop/core/ext-py/django-axes-1.6.0/axes/decorators.py

@@ -8,8 +8,7 @@ from django.contrib.auth import logout
 from django.core.exceptions import ObjectDoesNotExist
 from django.http import HttpResponse
 from django.http import HttpResponseRedirect
-from django.shortcuts import render_to_response
-from django.template import RequestContext
+from django.template.loader import get_template
 from django.utils import timezone as datetime
 from django.utils.translation import ugettext_lazy
 
@@ -56,7 +55,8 @@ BEHIND_REVERSE_PROXY_WITH_DIRECT_ACCESS = getattr(settings, 'AXES_BEHIND_REVERSE
 REVERSE_PROXY_HEADER = getattr(settings, 'AXES_REVERSE_PROXY_HEADER', 'HTTP_X_FORWARDED_FOR')
 
 # lock out user from particular IP based on combination USER+IP
-LOCK_OUT_BY_COMBINATION_USER_AND_IP = getattr(settings, 'AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP', False)
+def should_lock_out_by_combination_user_and_ip():
+    return getattr(settings, 'AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP', False)
 
 COOLOFF_TIME = getattr(settings, 'AXES_COOLOFF_TIME', None)
 if (isinstance(COOLOFF_TIME, int) or isinstance(COOLOFF_TIME, float) ):
@@ -106,8 +106,8 @@ def get_ip_address_from_request(request):
         if not x_forwarded_for.startswith(PRIVATE_IPS_PREFIX) and is_valid_ip(x_forwarded_for):
             ip_address = x_forwarded_for.strip()
     else:
-        ips = [ip.strip() for ip in x_forwarded_for.split(',')]
-        for ip in ips:
+        for ip_raw in x_forwarded_for.split(','):
+            ip = ip_raw.strip()
             if ip.startswith(PRIVATE_IPS_PREFIX):
                 continue
             elif not is_valid_ip(ip):
@@ -160,15 +160,11 @@ def query2str(items, max_length=1024):
 
     If there's a field called "password" it will be excluded from the output.
 
-    The length of the output is limited to max_length to avoid a DoS attack.
+    The length of the output is limited to max_length to avoid a DoS attack via excessively large payloads.
     """
 
-    kvs = []
-    for k, v in items:
-        if k != PASSWORD_FORM_FIELD:
-            kvs.append(six.u('%s=%s') % (k, v))
-
-    return '\n'.join(kvs)[:max_length]
+    return '\n'.join(['%s=%s' % (k, v) for k, v in six.iteritems(items)
+                      if k != PASSWORD_FORM_FIELD][:int(max_length/2)])[:max_length]
 
 
 def ip_in_whitelist(ip):
@@ -201,7 +197,7 @@ def is_user_lockable(request):
         return True
 
     if hasattr(user, 'nolockout'):
-        # need to revert since we need to return
+        # need to invert since we need to return
         # false for users that can't be blocked
         return not user.nolockout
 
@@ -209,7 +205,7 @@ def is_user_lockable(request):
         try:
             profile = user.get_profile()
             if hasattr(profile, 'nolockout'):
-                # need to revert since we need to return
+                # need to invert since we need to return
                 # false for users that can't be blocked
                 return not profile.nolockout
 
@@ -238,10 +234,12 @@ def _get_user_attempts(request):
             ip_address=ip, username=username, trusted=True
         )
 
-    if not attempts and not LOCK_OUT_BY_COMBINATION_USER_AND_IP:
+    if not attempts:
         params = {'ip_address': ip, 'trusted': False}
         if USE_USER_AGENT:
             params['user_agent'] = ua
+        if should_lock_out_by_combination_user_and_ip():
+            params['username'] = username
 
         attempts = AccessAttempt.objects.filter(**params)
 
@@ -342,8 +340,9 @@ def lockout_response(request):
             'failure_limit': FAILURE_LIMIT,
             'username': request.POST.get(USERNAME_FORM_FIELD, '')
         }
-        return render_to_response(LOCKOUT_TEMPLATE, context,
-                                  context_instance=RequestContext(request))
+        template = get_template(LOCKOUT_TEMPLATE)
+        content = template.render(context, request)
+        return HttpResponse(content)
 
     LOCKOUT_URL = get_lockout_url()
     if LOCKOUT_URL:
@@ -367,10 +366,15 @@ def is_already_locked(request):
     if ip_in_blacklist(ip):
         return True
 
-    attempts = get_user_attempts(request)
     user_lockable = is_user_lockable(request)
+
+    if not user_lockable:
+        return False
+
+    attempts = get_user_attempts(request)
+
     for attempt in attempts:
-        if attempt.failures_since_start >= FAILURE_LIMIT and LOCK_OUT_AT_FAILURE and user_lockable:
+        if attempt.failures_since_start >= FAILURE_LIMIT and LOCK_OUT_AT_FAILURE:
             return True
 
     return False
@@ -395,11 +399,11 @@ def check_request(request, login_unsuccessful):
             for attempt in attempts:
                 attempt.get_data = '%s\n---------\n%s' % (
                     attempt.get_data,
-                    query2str(request.GET.items()),
+                    query2str(request.GET),
                 )
                 attempt.post_data = '%s\n---------\n%s' % (
                     attempt.post_data,
-                    query2str(request.POST.items())
+                    query2str(request.POST)
                 )
                 attempt.http_accept = request.META.get('HTTP_ACCEPT', '<unknown>')
                 attempt.path_info = request.META.get('PATH_INFO', '<unknown>')
@@ -458,8 +462,8 @@ def create_new_failure_records(request, failures):
         'user_agent': ua,
         'ip_address': ip,
         'username': username,
-        'get_data': query2str(request.GET.items()),
-        'post_data': query2str(request.POST.items()),
+        'get_data': query2str(request.GET),
+        'post_data': query2str(request.POST),
         'http_accept': request.META.get('HTTP_ACCEPT', '<unknown>'),
         'path_info': request.META.get('PATH_INFO', '<unknown>'),
         'failures_since_start': failures,
@@ -482,8 +486,8 @@ def create_new_trusted_record(request):
         user_agent=ua,
         ip_address=ip,
         username=username,
-        get_data=query2str(request.GET.items()),
-        post_data=query2str(request.POST.items()),
+        get_data=query2str(request.GET),
+        post_data=query2str(request.POST),
         http_accept=request.META.get('HTTP_ACCEPT', '<unknown>'),
         path_info=request.META.get('PATH_INFO', '<unknown>'),
         failures_since_start=0,

+ 0 - 0
desktop/core/ext-py/django-axes-1.4.0/axes/management/__init__.py → desktop/core/ext-py/django-axes-1.6.0/axes/management/__init__.py


+ 0 - 0
desktop/core/ext-py/django-axes-1.4.0/axes/management/commands/__init__.py → desktop/core/ext-py/django-axes-1.6.0/axes/management/commands/__init__.py


+ 0 - 0
desktop/core/ext-py/django-axes-1.4.0/axes/management/commands/axes_list_attempts.py → desktop/core/ext-py/django-axes-1.6.0/axes/management/commands/axes_list_attempts.py


+ 0 - 0
desktop/core/ext-py/django-axes-1.4.0/axes/management/commands/axes_reset.py → desktop/core/ext-py/django-axes-1.6.0/axes/management/commands/axes_reset.py


+ 0 - 0
desktop/core/ext-py/django-axes-1.4.0/axes/middleware.py → desktop/core/ext-py/django-axes-1.6.0/axes/middleware.py


+ 51 - 0
desktop/core/ext-py/django-axes-1.6.0/axes/migrations/0001_initial.py

@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='AccessAttempt',
+            fields=[
+                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+                ('user_agent', models.CharField(max_length=255)),
+                ('ip_address', models.GenericIPAddressField(null=True, verbose_name=b'IP Address')),
+                ('username', models.CharField(max_length=255, null=True)),
+                ('trusted', models.BooleanField(default=False)),
+                ('http_accept', models.CharField(max_length=1025, verbose_name=b'HTTP Accept')),
+                ('path_info', models.CharField(max_length=255, verbose_name=b'Path')),
+                ('attempt_time', models.DateTimeField(auto_now_add=True)),
+                ('get_data', models.TextField(verbose_name=b'GET Data')),
+                ('post_data', models.TextField(verbose_name=b'POST Data')),
+                ('failures_since_start', models.PositiveIntegerField(verbose_name=b'Failed Logins')),
+            ],
+            options={
+                'ordering': ['-attempt_time'],
+                'abstract': False,
+            },
+        ),
+        migrations.CreateModel(
+            name='AccessLog',
+            fields=[
+                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+                ('user_agent', models.CharField(max_length=255)),
+                ('ip_address', models.GenericIPAddressField(null=True, verbose_name=b'IP Address')),
+                ('username', models.CharField(max_length=255, null=True)),
+                ('trusted', models.BooleanField(default=False)),
+                ('http_accept', models.CharField(max_length=1025, verbose_name=b'HTTP Accept')),
+                ('path_info', models.CharField(max_length=255, verbose_name=b'Path')),
+                ('attempt_time', models.DateTimeField(auto_now_add=True)),
+                ('logout_time', models.DateTimeField(null=True, blank=True)),
+            ],
+            options={
+                'ordering': ['-attempt_time'],
+                'abstract': False,
+            },
+        ),
+    ]

+ 54 - 0
desktop/core/ext-py/django-axes-1.6.0/axes/migrations/0002_auto_20151217_2044.py

@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('axes', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='accessattempt',
+            name='ip_address',
+            field=models.GenericIPAddressField(db_index=True, null=True, verbose_name=b'IP Address'),
+        ),
+        migrations.AlterField(
+            model_name='accessattempt',
+            name='trusted',
+            field=models.BooleanField(db_index=True, default=False),
+        ),
+        migrations.AlterField(
+            model_name='accessattempt',
+            name='user_agent',
+            field=models.CharField(db_index=True, max_length=255),
+        ),
+        migrations.AlterField(
+            model_name='accessattempt',
+            name='username',
+            field=models.CharField(db_index=True, max_length=255, null=True),
+        ),
+        migrations.AlterField(
+            model_name='accesslog',
+            name='ip_address',
+            field=models.GenericIPAddressField(db_index=True, null=True, verbose_name=b'IP Address'),
+        ),
+        migrations.AlterField(
+            model_name='accesslog',
+            name='trusted',
+            field=models.BooleanField(db_index=True, default=False),
+        ),
+        migrations.AlterField(
+            model_name='accesslog',
+            name='user_agent',
+            field=models.CharField(db_index=True, max_length=255),
+        ),
+        migrations.AlterField(
+            model_name='accesslog',
+            name='username',
+            field=models.CharField(db_index=True, max_length=255, null=True),
+        ),
+    ]

+ 0 - 0
desktop/core/ext-py/django-axes-1.4.0/examples/example/__init__.py → desktop/core/ext-py/django-axes-1.6.0/axes/migrations/__init__.py


+ 4 - 0
desktop/core/ext-py/django-axes-1.4.0/axes/models.py → desktop/core/ext-py/django-axes-1.6.0/axes/models.py

@@ -4,22 +4,26 @@ from django.utils import six
 class CommonAccess(models.Model):
     user_agent = models.CharField(
         max_length=255,
+        db_index=True,
     )
 
     ip_address = models.GenericIPAddressField(
         verbose_name='IP Address',
         null=True,
+        db_index=True,
     )
 
     username = models.CharField(
         max_length=255,
         null=True,
+        db_index=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,
+        db_index=True,
     )
 
     http_accept = models.CharField(

+ 0 - 0
desktop/core/ext-py/django-axes-1.4.0/axes/signals.py → desktop/core/ext-py/django-axes-1.6.0/axes/signals.py


+ 0 - 0
desktop/core/ext-py/django-axes-1.4.0/axes/test_settings.py → desktop/core/ext-py/django-axes-1.6.0/axes/test_settings.py


+ 0 - 0
desktop/core/ext-py/django-axes-1.4.0/axes/test_urls.py → desktop/core/ext-py/django-axes-1.6.0/axes/test_urls.py


+ 43 - 12
desktop/core/ext-py/django-axes-1.4.0/axes/tests.py → desktop/core/ext-py/django-axes-1.6.0/axes/tests.py

@@ -3,13 +3,14 @@ import string
 import time
 
 from django.test import TestCase
+from django.test.utils import override_settings
 from django.contrib.auth.models import User
 from django.core.urlresolvers import NoReverseMatch
 from django.core.urlresolvers import reverse
 
 from axes.decorators import COOLOFF_TIME
 from axes.decorators import FAILURE_LIMIT
-from axes.models import AccessLog
+from axes.models import AccessAttempt, AccessLog
 from axes.signals import user_locked_out
 from axes.utils import reset
 
@@ -17,12 +18,13 @@ from axes.utils import reset
 class AccessAttemptTest(TestCase):
     """Test case using custom settings for testing
     """
+    VALID_USERNAME = 'valid-username'
     VALID_PASSWORD = 'valid-password'
     LOCKED_MESSAGE = 'Account locked: too many login attempts.'
     LOGIN_FORM_KEY = '<input type="submit" value="Log in" />'
 
-    def _login(self, is_valid=False, user_agent='test-browser'):
-        """Login a user. A valid credential is used when is_valid is True,
+    def _login(self, is_valid_username=False, is_valid_password=False, user_agent='test-browser', **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.
         """
         try:
@@ -30,19 +32,26 @@ class AccessAttemptTest(TestCase):
         except NoReverseMatch:
             admin_login = reverse('admin:index')
 
-        if is_valid:
+        if is_valid_username:
             # Use a valid username
-            username = self.user.username
+            username = self.VALID_USERNAME
         else:
             # Generate a wrong random username
             chars = string.ascii_uppercase + string.digits
             username = ''.join(random.choice(chars) for x in range(10))
 
-        response = self.client.post(admin_login, {
+        if is_valid_password:
+            password = self.VALID_PASSWORD
+        else:
+            password = 'invalid-password'
+
+        post_data = {
             'username': username,
-            'password': self.VALID_PASSWORD,
+            'password': password,
             'this_is_the_login_form': 1,
-        }, HTTP_USER_AGENT=user_agent)
+        }
+        post_data.update(kwargs)
+        response = self.client.post(admin_login, post_data, HTTP_USER_AGENT=user_agent)
 
         return response
 
@@ -50,7 +59,7 @@ class AccessAttemptTest(TestCase):
         """Create a valid user for login
         """
         self.user = User.objects.create_superuser(
-            username='valid-username',
+            username=self.VALID_USERNAME,
             email='test@example.com',
             password=self.VALID_PASSWORD,
         )
@@ -87,13 +96,13 @@ class AccessAttemptTest(TestCase):
     def test_valid_login(self):
         """Tests a valid login for a real username
         """
-        response = self._login(is_valid=True)
+        response = self._login(is_valid_username=True, is_valid_password=True)
         self.assertNotContains(response, self.LOGIN_FORM_KEY, status_code=302)
 
     def test_valid_logout(self):
         """Tests a valid logout and make sure the logout_time is updated
         """
-        response = self._login(is_valid=True)
+        response = self._login(is_valid_username=True, is_valid_password=True)
         self.assertEquals(AccessLog.objects.latest('id').logout_time, None)
 
         response = self.client.get(reverse('admin:logout'))
@@ -124,7 +133,7 @@ class AccessAttemptTest(TestCase):
         """Tests if can handle a long user agent
         """
         long_user_agent = 'ie6' * 1024
-        response = self._login(is_valid=True, user_agent=long_user_agent)
+        response = self._login(is_valid_username=True, is_valid_password=True, user_agent=long_user_agent)
         self.assertNotContains(response, self.LOGIN_FORM_KEY, status_code=302)
 
     def test_long_user_agent_not_valid(self):
@@ -183,3 +192,25 @@ class AccessAttemptTest(TestCase):
         # Make another lockout
         self.test_failure_limit_once()
         self.assertEquals(scope.signal_received, 2)
+
+    @override_settings(AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP=True)
+    def test_lockout_by_combination_user_and_ip(self):
+        """Tests the login lock with a valid username and invalid password
+        when AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP is True
+        """
+        for i in range(1, FAILURE_LIMIT):  # test until one try before the limit
+            response = self._login(is_valid_username=True, is_valid_password=False)
+            # Check if we are in the same login page
+            self.assertContains(response, self.LOGIN_FORM_KEY)
+
+        # So, we shouldn't have gotten a lock-out yet.
+        # But we should get one now
+        response = self._login(is_valid_username=True, is_valid_password=False)
+        self.assertContains(response, self.LOCKED_MESSAGE)
+
+    def test_log_data_truncated(self):
+        """Tests that query2str properly truncates data to the max_length (default 1024)
+        """
+        extra_data = {string.ascii_letters * x: x for x in range(0, 1000)}  # An impossibly large post dict
+        self._login(**extra_data)
+        self.assertEquals(len(AccessAttempt.objects.latest('id').post_data), 1024)

+ 0 - 0
desktop/core/ext-py/django-axes-1.4.0/axes/utils.py → desktop/core/ext-py/django-axes-1.6.0/axes/utils.py


+ 0 - 0
desktop/core/ext-py/django-axes-1.4.0/axes/views.py → desktop/core/ext-py/django-axes-1.6.0/axes/views.py


+ 0 - 0
desktop/core/ext-py/django-axes-1.4.0/examples/README.rst → desktop/core/ext-py/django-axes-1.6.0/examples/README.rst


+ 0 - 0
desktop/core/ext-py/django-axes-1.4.0/examples/example/foo/__init__.py → desktop/core/ext-py/django-axes-1.6.0/examples/example/__init__.py


+ 0 - 0
desktop/core/ext-py/django-axes-1.4.0/examples/example/foo/management/__init__.py → desktop/core/ext-py/django-axes-1.6.0/examples/example/foo/__init__.py


+ 0 - 0
desktop/core/ext-py/django-axes-1.4.0/examples/example/foo/management/commands/__init__.py → desktop/core/ext-py/django-axes-1.6.0/examples/example/foo/management/__init__.py


+ 0 - 0
desktop/core/ext-py/django-axes-1.4.0/examples/example/foo/models.py → desktop/core/ext-py/django-axes-1.6.0/examples/example/foo/management/commands/__init__.py


+ 0 - 0
desktop/core/ext-py/django-axes-1.4.0/examples/example/foo/management/commands/axes_create_test_data.py → desktop/core/ext-py/django-axes-1.6.0/examples/example/foo/management/commands/axes_create_test_data.py


+ 0 - 0
desktop/core/ext-py/django-axes-1.6.0/examples/example/foo/models.py


+ 0 - 0
desktop/core/ext-py/django-axes-1.4.0/examples/example/local_settings.example → desktop/core/ext-py/django-axes-1.6.0/examples/example/local_settings.example


+ 0 - 0
desktop/core/ext-py/django-axes-1.4.0/examples/example/manage.py → desktop/core/ext-py/django-axes-1.6.0/examples/example/manage.py


+ 0 - 0
desktop/core/ext-py/django-axes-1.4.0/examples/example/settings.py → desktop/core/ext-py/django-axes-1.6.0/examples/example/settings.py


+ 0 - 0
desktop/core/ext-py/django-axes-1.4.0/examples/example/urls.py → desktop/core/ext-py/django-axes-1.6.0/examples/example/urls.py


+ 0 - 0
desktop/core/ext-py/django-axes-1.4.0/examples/example/wsgi.py → desktop/core/ext-py/django-axes-1.6.0/examples/example/wsgi.py


+ 0 - 0
desktop/core/ext-py/django-axes-1.4.0/examples/install.sh → desktop/core/ext-py/django-axes-1.6.0/examples/install.sh


+ 0 - 0
desktop/core/ext-py/django-axes-1.4.0/examples/reinstall.sh → desktop/core/ext-py/django-axes-1.6.0/examples/reinstall.sh


+ 0 - 0
desktop/core/ext-py/django-axes-1.4.0/examples/test.sh → desktop/core/ext-py/django-axes-1.6.0/examples/test.sh


+ 0 - 0
desktop/core/ext-py/django-axes-1.4.0/examples/uninstall.sh → desktop/core/ext-py/django-axes-1.6.0/examples/uninstall.sh


+ 0 - 0
desktop/core/ext-py/django-axes-1.4.0/setup.cfg → desktop/core/ext-py/django-axes-1.6.0/setup.cfg


+ 1 - 1
desktop/core/ext-py/django-axes-1.4.0/setup.py → desktop/core/ext-py/django-axes-1.6.0/setup.py

@@ -4,7 +4,7 @@
 import os
 from setuptools import setup, find_packages
 
-VERSION = '1.4.0'
+VERSION = '1.6.0'
 
 setup(
     name='django-axes',