Skip to content

Commit

Permalink
Initial import
Browse files Browse the repository at this point in the history
--HG--
extra : convert_revision : svn%3A6515f4ec-ab5a-11dd-8fd9-859366ca643a/trunk%402
  • Loading branch information
wheaties.box committed Nov 5, 2008
1 parent d564fcc commit 404f02b
Show file tree
Hide file tree
Showing 17 changed files with 331 additions and 0 deletions.
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -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.
8 changes: 8 additions & 0 deletions MANIFEST
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
recursive-include axes *.py
82 changes: 82 additions & 0 deletions README
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 4 additions & 0 deletions axes/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
VERSION = (0, 1, 0, 'pre')

def get_version():
return '%s.%s.%s-%s' % VERSION
21 changes: 21 additions & 0 deletions axes/admin.py
Original file line number Diff line number Diff line change
@@ -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)
65 changes: 65 additions & 0 deletions axes/decorators.py
Original file line number Diff line number Diff line change
@@ -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
15 changes: 15 additions & 0 deletions axes/middleware.py
Original file line number Diff line number Diff line change
@@ -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)
26 changes: 26 additions & 0 deletions axes/models.py
Original file line number Diff line number Diff line change
@@ -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']
1 change: 1 addition & 0 deletions axes/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Create your views here.
Binary file added dist/django-axes-0.1.0-pre.tar.bz2
Binary file not shown.
7 changes: 7 additions & 0 deletions dist/django-axes-0.1.0-pre.tar.bz2.asc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)

iEYEABECAAYFAkkR1MwACgkQPhnaFjTndGC6EQCcCrDZzQJEoimzIqTQsGg11jhY
QE8An0i99SMVgw3jWu2nU978cqkJ8Bm9
=iW9f
-----END PGP SIGNATURE-----
Binary file added dist/django-axes-0.1.0-pre.tar.gz
Binary file not shown.
7 changes: 7 additions & 0 deletions dist/django-axes-0.1.0-pre.tar.gz.asc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)

iEYEABECAAYFAkkR1MgACgkQPhnaFjTndGCh0QCg38Qloko0PfyMslD8lm7u3QGV
E6IAn2WO1MN8NUJ/rSNSIJsAZBlySWfO
=QQ0n
-----END PGP SIGNATURE-----
Binary file added dist/django-axes-0.1.0-pre.zip
Binary file not shown.
7 changes: 7 additions & 0 deletions dist/django-axes-0.1.0-pre.zip.asc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)

iEYEABECAAYFAkkR1NAACgkQPhnaFjTndGClvACfR7PB7XIF0tQr1yVEciZSsv5f
GEMAoLpWw3nR82wsNrT5awDL3TfDy6xt
=b0EF
-----END PGP SIGNATURE-----
66 changes: 66 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -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.