Initial import

--HG--
extra : convert_revision : svn%3A6515f4ec-ab5a-11dd-8fd9-859366ca643a/trunk%402
This commit is contained in:
wheaties.box 2008-11-05 22:52:40 +00:00
parent d564fcc481
commit 404f02b85c
17 changed files with 331 additions and 0 deletions

21
LICENSE Normal file
View file

@ -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
MANIFEST Normal file
View file

@ -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
MANIFEST.in Normal file
View file

@ -0,0 +1 @@
recursive-include axes *.py

82
README Normal file
View file

@ -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
axes/__init__.py Normal file
View file

@ -0,0 +1,4 @@
VERSION = (0, 1, 0, 'pre')
def get_version():
return '%s.%s.%s-%s' % VERSION

21
axes/admin.py Normal file
View file

@ -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
axes/decorators.py Normal file
View file

@ -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
axes/middleware.py Normal file
View file

@ -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
axes/models.py Normal file
View file

@ -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
axes/views.py Normal file
View file

@ -0,0 +1 @@
# Create your views here.

BIN
dist/django-axes-0.1.0-pre.tar.bz2 vendored Normal file

Binary file not shown.

View file

@ -0,0 +1,7 @@
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)
iEYEABECAAYFAkkR1MwACgkQPhnaFjTndGC6EQCcCrDZzQJEoimzIqTQsGg11jhY
QE8An0i99SMVgw3jWu2nU978cqkJ8Bm9
=iW9f
-----END PGP SIGNATURE-----

BIN
dist/django-axes-0.1.0-pre.tar.gz vendored Normal file

Binary file not shown.

7
dist/django-axes-0.1.0-pre.tar.gz.asc vendored Normal file
View file

@ -0,0 +1,7 @@
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)
iEYEABECAAYFAkkR1MgACgkQPhnaFjTndGCh0QCg38Qloko0PfyMslD8lm7u3QGV
E6IAn2WO1MN8NUJ/rSNSIJsAZBlySWfO
=QQ0n
-----END PGP SIGNATURE-----

BIN
dist/django-axes-0.1.0-pre.zip vendored Normal file

Binary file not shown.

7
dist/django-axes-0.1.0-pre.zip.asc vendored Normal file
View file

@ -0,0 +1,7 @@
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)
iEYEABECAAYFAkkR1NAACgkQPhnaFjTndGClvACfR7PB7XIF0tQr1yVEciZSsv5f
GEMAoLpWw3nR82wsNrT5awDL3TfDy6xt
=b0EF
-----END PGP SIGNATURE-----

66
setup.py Normal file
View file

@ -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',
]
)