From 404f02b85cdca1afe5cff9427cb799a9f418313f Mon Sep 17 00:00:00 2001 From: "wheaties.box" Date: Wed, 5 Nov 2008 22:52:40 +0000 Subject: [PATCH] Initial import --HG-- extra : convert_revision : svn%3A6515f4ec-ab5a-11dd-8fd9-859366ca643a/trunk%402 --- LICENSE | 21 +++++++ MANIFEST | 8 +++ MANIFEST.in | 1 + README | 82 +++++++++++++++++++++++++ axes/__init__.py | 4 ++ axes/admin.py | 21 +++++++ axes/decorators.py | 65 ++++++++++++++++++++ axes/middleware.py | 15 +++++ axes/models.py | 26 ++++++++ axes/views.py | 1 + dist/django-axes-0.1.0-pre.tar.bz2 | Bin 0 -> 4170 bytes dist/django-axes-0.1.0-pre.tar.bz2.asc | 7 +++ dist/django-axes-0.1.0-pre.tar.gz | Bin 0 -> 4164 bytes dist/django-axes-0.1.0-pre.tar.gz.asc | 7 +++ dist/django-axes-0.1.0-pre.zip | Bin 0 -> 5991 bytes dist/django-axes-0.1.0-pre.zip.asc | 7 +++ setup.py | 66 ++++++++++++++++++++ 17 files changed, 331 insertions(+) create mode 100644 LICENSE create mode 100644 MANIFEST create mode 100644 MANIFEST.in create mode 100644 README create mode 100644 axes/__init__.py create mode 100644 axes/admin.py create mode 100644 axes/decorators.py create mode 100644 axes/middleware.py create mode 100644 axes/models.py create mode 100644 axes/views.py create mode 100644 dist/django-axes-0.1.0-pre.tar.bz2 create mode 100644 dist/django-axes-0.1.0-pre.tar.bz2.asc create mode 100644 dist/django-axes-0.1.0-pre.tar.gz create mode 100644 dist/django-axes-0.1.0-pre.tar.gz.asc create mode 100644 dist/django-axes-0.1.0-pre.zip create mode 100644 dist/django-axes-0.1.0-pre.zip.asc create mode 100644 setup.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3095056 --- /dev/null +++ b/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. diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..3b2ab43 --- /dev/null +++ b/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 diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..dba3732 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +recursive-include axes *.py diff --git a/README b/README new file mode 100644 index 0000000..75c471a --- /dev/null +++ b/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. \ No newline at end of file diff --git a/axes/__init__.py b/axes/__init__.py new file mode 100644 index 0000000..df00547 --- /dev/null +++ b/axes/__init__.py @@ -0,0 +1,4 @@ +VERSION = (0, 1, 0, 'pre') + +def get_version(): + return '%s.%s.%s-%s' % VERSION \ No newline at end of file diff --git a/axes/admin.py b/axes/admin.py new file mode 100644 index 0000000..dc623b7 --- /dev/null +++ b/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) \ No newline at end of file diff --git a/axes/decorators.py b/axes/decorators.py new file mode 100644 index 0000000..3d86e60 --- /dev/null +++ b/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', '') + + 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', ''), + path_info=request.META.get('PATH_INFO', ''), + failures_since_start=failures[key] + ) + + if FAILURE_RESET: + del(failures[key]) + + return response + return new \ No newline at end of file diff --git a/axes/middleware.py b/axes/middleware.py new file mode 100644 index 0000000..9caa4e1 --- /dev/null +++ b/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) diff --git a/axes/models.py b/axes/models.py new file mode 100644 index 0000000..859cd35 --- /dev/null +++ b/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'] \ No newline at end of file diff --git a/axes/views.py b/axes/views.py new file mode 100644 index 0000000..60f00ef --- /dev/null +++ b/axes/views.py @@ -0,0 +1 @@ +# Create your views here. diff --git a/dist/django-axes-0.1.0-pre.tar.bz2 b/dist/django-axes-0.1.0-pre.tar.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..9cc15a6e18e2958e74b4840d6a282dfce8aac46c GIT binary patch literal 4170 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 Uc{G}J!Fk2pkxmpO3H>-UK>Uuil>h($ literal 0 HcmV?d00001 diff --git a/dist/django-axes-0.1.0-pre.tar.bz2.asc b/dist/django-axes-0.1.0-pre.tar.bz2.asc new file mode 100644 index 0000000..0326fc5 --- /dev/null +++ b/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----- diff --git a/dist/django-axes-0.1.0-pre.tar.gz b/dist/django-axes-0.1.0-pre.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..ecb51c5ae88fe27c82d3458d37a57536fc292086 GIT binary patch literal 4164 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 O`uYd0Gl5P3PyhgJGa7FI literal 0 HcmV?d00001 diff --git a/dist/django-axes-0.1.0-pre.tar.gz.asc b/dist/django-axes-0.1.0-pre.tar.gz.asc new file mode 100644 index 0000000..82b8052 --- /dev/null +++ b/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----- diff --git a/dist/django-axes-0.1.0-pre.zip b/dist/django-axes-0.1.0-pre.zip new file mode 100644 index 0000000000000000000000000000000000000000..207f955100cdd9c5eabc4a0ace5cc7dd93870c7f GIT binary patch literal 5991 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 z zg%63F%GKSiKCYt7G`4;m1)_z{9qsWd5^6A;PEddD^KU|B&`wSqGF=gVd7bEwGfvt; zeyCuDia%vBqBYGJy%F)s$s|6H&Ugts&NMaTV{?oq>9j!L6r^)uko&ntRFzGyI_70W zOYqL@V_)fp++Be0=fkPzP z07P0LZ8=Ortn7hWMS)7HQ)f0s4Dd5W*GoPBe5VM1x%say;}~Z~NheTZtn$Y-Pi)D? z+;Z&Q69)s1=*x~+fep&rNQkSLBXpuGS~q)Ma5R)ep;o}2h=+wQJOS;BY>?v%V6(7d z`&{jl!O;ZTdfB_$rcxAipk3Ibn|e07fv%ZaFIUPM7p(9GTuQadI4pJu)db}SdQLa< z`H3D!e~qXYeesU0Wap-GUv@cp@s&`0_ruNY{ld*PieDafy7us#v}G&DwTEXg0RYy& zd)U&(5$g2Q!SU*LZGyzGV+oj-2Gc44%ar)lHj3SS<@Pgvt%KzOA7s4js>)5`Wcz;NTgOgqUbkw`N-?41&RnVA_W; z77oFq_zgVPc^4@Ge(|jLlV!y^5i=Nm>{_*XUgbyg|w#-Pg`uv>sV6+Gk_;{fI3IMp810O-o$5iOz_Lh#MdorV}$$% zrEBwQUtb)5H?z4p)CuZgZhmcPB6kwPqr+p}GW{XJA#K4iZ9qI42b_;iF)`uJhZ3&m zv+qIj!R>EOeCSdPObDg5UK#5czsh}AVqAo$P*>27CM+PdBufDN`WHVLh=ibQbnnkC z7v1&sUtInS3i0}>Xu#g%|Y}v0@KT#)#S$!om$a1gM@NI=**R#HT&nE?2j5LH*7 z#CXo8oup=?76)0=e63a}=TPwsf*ILNbSi!1$=~2(kEPzF*EyS}Ci&nI;xnYDi_HaA zx>N9qPhx21SPE)NgyR(?;>LO+*`(=yGk(9Yk}X=?pi-TGAJh~+Hd4XB z<=*^)#4#I_YcFCf*z9V+WM`?}tQhOk{qsDP0P+cO7o~>ldr^dg}T!>z|41 zch;X;xL>T)>xJu|S^vMG`CGykOCU(D^lGyl=@@4!DP_7_l<;;%pXfAp)Rii7*}O@ixl;#&KDKLZ@_ EFHt>pTL1t6 literal 0 HcmV?d00001 diff --git a/dist/django-axes-0.1.0-pre.zip.asc b/dist/django-axes-0.1.0-pre.zip.asc new file mode 100644 index 0000000..0f13f5c --- /dev/null +++ b/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----- diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..9576848 --- /dev/null +++ b/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', + ] +)