Skip to content
Browse files

Initial import

--HG--
extra : convert_revision : svn%3A6515f4ec-ab5a-11dd-8fd9-859366ca643a/trunk%402
  • Loading branch information...
1 parent d564fcc commit 404f02b85cdca1afe5cff9427cb799a9f418313f wheaties.box committed
View
21 LICENSE
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2008 Josh VanderLinden
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
View
8 MANIFEST
@@ -0,0 +1,8 @@
+README
+setup.py
+axes/__init__.py
+axes/admin.py
+axes/decorators.py
+axes/middleware.py
+axes/models.py
+axes/views.py
View
1 MANIFEST.in
@@ -0,0 +1 @@
+recursive-include axes *.py
View
82 README
@@ -0,0 +1,82 @@
+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.
+
+==Requirements==
+
+`django-axes` requires Django 1.0 or later. The application is intended to work around the Django admin and the regular `django.contrib.auth` login-powered pages.
+
+==Installation==
+
+Download `django-axes` using *one* of the following methods:
+
+===easy_install===
+
+You can download the package from the [http://pypi.python.org/pypi/django-axes/ CheeseShop] or use
+
+{{{
+easy_install django-axes
+}}}
+
+to download and install `django-axes`.
+
+===Package Download===
+
+Download the latest `.tar.gz` file from the downloads section and extract it somewhere you'll remember. Use `python setup.py install` to install it.
+
+===Checkout from Subversion===
+
+Execute the following command (or use the equivalent function in a GUI such as TortoiseSVN), and make sure you're checking `django-axes` out somewhere on the `PYTHONPATH`.
+
+{{{
+svn co http://django-axes.googlecode.com/svn/trunk/axes axes
+}}}
+
+===Verifying Installation===
+
+The easiest way to ensure that you have successfully installed `django-axes` is to execute a command such as:
+
+{{{
+python -c "import axes; print axes.get_version()"
+}}}
+
+If that command completes with some sort of version number, you're probably good to go. If you see error outout, you need to check your installation (I'd start with your `PYTHONPATH`).
+
+==Configuration==
+
+First of all, you must add this project to your list of `INSTALLED_APPS` in `settings.py`:
+
+{{{
+INSTALLED_APPS = (
+ 'django.contrib.admin',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ ...
+ 'axes',
+ ...
+)
+}}}
+
+Next, install the `FailedLoginMiddleware` middleware:
+
+{{{
+MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'axes.middleware.FailedLoginMiddleware'
+)
+}}}
+
+Run `manage.py syncdb`. This creates the appropriate tables in your database that are necessary for operation.
+
+===Customizing Axes===
+
+You have a couple options available to you to customize `django-axes` a bit. These should be defined in your `settings.py` file.
+
+ * `LOGIN_FAILURE_LIMIT`: The number of login attempts allowed before a record is created for the failed logins. Default: `3`
+ * `LOGIN_FAILURE_RESET`: Determines whether or not the number of failed attempts will be reset after a failed login record is created. If set to `False`, the application should maintain the number of failed login attempts for a particular user from the time the server starts/restarts. If set to `True`, the records should all equate to `LOGIN_FAILURE_LIMIT`. Default: `True`
+
+==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.
View
4 axes/__init__.py
@@ -0,0 +1,4 @@
+VERSION = (0, 1, 0, 'pre')
+
+def get_version():
+ return '%s.%s.%s-%s' % VERSION
View
21 axes/admin.py
@@ -0,0 +1,21 @@
+from django.contrib import admin
+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']
+ date_hierarchy = 'attempt_time'
+ fieldsets = (
+ (None, {
+ 'fields': ('path_info', 'failures_since_start')
+ }),
+ ('Form Data', {
+ 'fields': ('get_data', 'post_data')
+ }),
+ ('Meta Data', {
+ 'fields': ('user_agent', 'ip_address', 'http_accept')
+ })
+ )
+
+admin.site.register(AccessAttempt, AccessAttemptAdmin)
View
65 axes/decorators.py
@@ -0,0 +1,65 @@
+from axes.models import AccessAttempt
+from django.conf import settings
+
+# see if the user has overridden the failure limit
+if hasattr(settings, 'LOGIN_FAILURE_LIMIT'):
+ FAILURE_LIMIT = settings.LOGIN_FAILURE_LIMIT
+else:
+ FAILURE_LIMIT = 3
+
+# see if the user has overridden the failure reset setting
+if hasattr(settings, 'LOGIN_FAILURE_RESET'):
+ FAILURE_RESET = settings.LOGIN_FAILURE_RESET
+else:
+ FAILURE_RESET = True
+
+def query2str(items):
+ return '\n'.join(['%s=%s' % (k, v) for k,v in items])
+
+def watch_login(func, failures):
+ """
+ Used to decorate the django.contrib.admin.site.login method.
+ """
+
+ def new(*args, **kwargs):
+ request = args[0]
+
+ # call the login function
+ response = func(*args, **kwargs)
+
+ # only check when there's been an HTTP POST
+ if request.method == 'POST':
+ # see if the login was successful
+ if not response.has_header('location') and response.status_code != 302:
+ ip = request.META.get('REMOTE_ADDR', '')
+ ua = request.META.get('HTTP_USER_AGENT', '<unknown>')
+
+ key = '%s:%s' % (ip, ua)
+
+ # make sure we have an item for this key
+ try:
+ failures[key]
+ except KeyError:
+ failures[key] = 0
+
+ # add a failed attempt for this user
+ failures[key] += 1
+
+ # if we reach or surpass the failure limit, create an
+ # AccessAttempt record
+ if failures[key] >= FAILURE_LIMIT:
+ attempt = AccessAttempt.objects.create(
+ user_agent=ua,
+ ip_address=ip,
+ get_data=query2str(request.GET.items()),
+ post_data=query2str(request.POST.items()),
+ http_accept=request.META.get('HTTP_ACCEPT', '<unknown>'),
+ path_info=request.META.get('PATH_INFO', '<unknown>'),
+ failures_since_start=failures[key]
+ )
+
+ if FAILURE_RESET:
+ del(failures[key])
+
+ return response
+ return new
View
15 axes/middleware.py
@@ -0,0 +1,15 @@
+from django.contrib import admin
+from django.contrib.auth import views as auth_views
+from axes.decorators import watch_login
+
+class FailedLoginMiddleware(object):
+ failures = {}
+
+ def __init__(self, *args, **kwargs):
+ super(FailedLoginMiddleware, self).__init__(*args, **kwargs)
+
+ # watch the admin login page
+ admin.site.login = watch_login(admin.site.login, self.failures)
+
+ # and the regular auth login page
+ auth_views.login = watch_login(auth_views.login, self.failures)
View
26 axes/models.py
@@ -0,0 +1,26 @@
+from django.db import models
+from django.conf import settings
+
+if hasattr(settings, 'LOGIN_FAILURE_RESET'):
+ FAILURES_DESC = 'Failed Logins Since Server Started'
+else:
+ FAILURES_DESC = 'Failed Logins'
+
+class AccessAttempt(models.Model):
+ user_agent = models.CharField(max_length=255)
+ ip_address = models.IPAddressField('IP Address')
+ get_data = models.TextField('GET Data')
+ post_data = models.TextField('POST Data')
+ http_accept = models.CharField('HTTP Accept', max_length=255)
+ path_info = models.CharField('Path', max_length=255)
+ failures_since_start = models.PositiveIntegerField(FAILURES_DESC)
+ attempt_time = models.DateTimeField(auto_now_add=True)
+
+ def __unicode__(self):
+ return u'Attempted Access: %s' % self.attempt_time
+
+ def failures(self):
+ return self.failures_since_start
+
+ class Meta:
+ ordering = ['-attempt_time']
View
1 axes/views.py
@@ -0,0 +1 @@
+# Create your views here.
View
BIN dist/django-axes-0.1.0-pre.tar.bz2
Binary file not shown.
View
7 dist/django-axes-0.1.0-pre.tar.bz2.asc
@@ -0,0 +1,7 @@
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.9 (GNU/Linux)
+
+iEYEABECAAYFAkkR1MwACgkQPhnaFjTndGC6EQCcCrDZzQJEoimzIqTQsGg11jhY
+QE8An0i99SMVgw3jWu2nU978cqkJ8Bm9
+=iW9f
+-----END PGP SIGNATURE-----
View
BIN dist/django-axes-0.1.0-pre.tar.gz
Binary file not shown.
View
7 dist/django-axes-0.1.0-pre.tar.gz.asc
@@ -0,0 +1,7 @@
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.9 (GNU/Linux)
+
+iEYEABECAAYFAkkR1MgACgkQPhnaFjTndGCh0QCg38Qloko0PfyMslD8lm7u3QGV
+E6IAn2WO1MN8NUJ/rSNSIJsAZBlySWfO
+=QQ0n
+-----END PGP SIGNATURE-----
View
BIN dist/django-axes-0.1.0-pre.zip
Binary file not shown.
View
7 dist/django-axes-0.1.0-pre.zip.asc
@@ -0,0 +1,7 @@
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.9 (GNU/Linux)
+
+iEYEABECAAYFAkkR1NAACgkQPhnaFjTndGClvACfR7PB7XIF0tQr1yVEciZSsv5f
+GEMAoLpWw3nR82wsNrT5awDL3TfDy6xt
+=b0EF
+-----END PGP SIGNATURE-----
View
66 setup.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from distutils.core import setup
+import axes
+import sys, os
+
+def fullsplit(path, result=None):
+ """
+ Split a pathname into components (the opposite of os.path.join) in a
+ platform-neutral way.
+ """
+ if result is None:
+ result = []
+ head, tail = os.path.split(path)
+ if head == '':
+ return [tail] + result
+ if head == path:
+ return result
+ return fullsplit(head, [tail] + result)
+
+packages, data_files = [], []
+root_dir = os.path.dirname(__file__)
+if root_dir != '':
+ os.chdir(root_dir)
+axes_dir = 'axes'
+
+for path, dirs, files in os.walk(axes_dir):
+ # ignore hidden directories and files
+ for i, d in enumerate(dirs):
+ if d.startswith('.'): del dirs[i]
+
+ if '__init__.py' in files:
+ packages.append('.'.join(fullsplit(path)))
+ elif files:
+ data_files.append((path, [os.path.join(path, f) for f in files]))
+
+setup(
+ name='django-axes',
+ version=axes.get_version(),
+ url='http://code.google.com/p/django-axes/',
+ author='Josh VanderLinden',
+ author_email='codekoala@gmail.com',
+ license='MIT',
+ packages=packages,
+ data_files=data_files,
+ description="Keep track of failed login attempts in Django-powered sites.",
+ long_description="""
+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.
+""",
+ keywords='django, security, authentication',
+ classifiers=[
+ 'Development Status :: 4 - Beta',
+ 'Environment :: Web Environment',
+ 'Framework :: Django',
+ 'Intended Audience :: Developers',
+ 'Intended Audience :: System Administrators',
+ 'License :: OSI Approved :: MIT License',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Topic :: Internet :: Log Analysis',
+ 'Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware',
+ 'Topic :: Security',
+ 'Topic :: System :: Logging',
+ ]
+)

0 comments on commit 404f02b

Please sign in to comment.
Something went wrong with that request. Please try again.