mirror of
https://github.com/jazzband/django-axes.git
synced 2026-03-16 22:30:23 +00:00
Initial import
--HG-- extra : convert_revision : svn%3A6515f4ec-ab5a-11dd-8fd9-859366ca643a/trunk%402
This commit is contained in:
parent
d564fcc481
commit
404f02b85c
17 changed files with 331 additions and 0 deletions
21
LICENSE
Normal file
21
LICENSE
Normal 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
8
MANIFEST
Normal 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
1
MANIFEST.in
Normal file
|
|
@ -0,0 +1 @@
|
|||
recursive-include axes *.py
|
||||
82
README
Normal file
82
README
Normal 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
4
axes/__init__.py
Normal 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
21
axes/admin.py
Normal 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
65
axes/decorators.py
Normal 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
15
axes/middleware.py
Normal 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
26
axes/models.py
Normal 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
1
axes/views.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
# Create your views here.
|
||||
BIN
dist/django-axes-0.1.0-pre.tar.bz2
vendored
Normal file
BIN
dist/django-axes-0.1.0-pre.tar.bz2
vendored
Normal file
Binary file not shown.
7
dist/django-axes-0.1.0-pre.tar.bz2.asc
vendored
Normal file
7
dist/django-axes-0.1.0-pre.tar.bz2.asc
vendored
Normal 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
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
7
dist/django-axes-0.1.0-pre.tar.gz.asc
vendored
Normal 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
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
7
dist/django-axes-0.1.0-pre.zip.asc
vendored
Normal 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
66
setup.py
Normal 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',
|
||||
]
|
||||
)
|
||||
Loading…
Reference in a new issue