mirror of
https://github.com/jazzband/django-defender.git
synced 2026-03-16 22:10:32 +00:00
Merge pull request #12 from kencochrane/add_celery
Add Celery option for writing to database
This commit is contained in:
commit
0402878f2f
14 changed files with 129 additions and 23 deletions
2
.coveragerc
Normal file
2
.coveragerc
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[run]
|
||||
omit = *_settings.py
|
||||
10
.landscape.yaml
Normal file
10
.landscape.yaml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
doc-warnings: no
|
||||
test-warnings: no
|
||||
strictness: high
|
||||
max-line-length: 80
|
||||
uses:
|
||||
- django
|
||||
- celery
|
||||
autodetect: yes
|
||||
ignore-patterns:
|
||||
- .*_settings.py$
|
||||
|
|
@ -9,6 +9,7 @@ python:
|
|||
|
||||
env:
|
||||
- DJANGO=Django==1.4.17
|
||||
- DJANGO=Django==1.5.12
|
||||
- DJANGO=Django==1.6.9
|
||||
- DJANGO=Django==1.7.2
|
||||
|
||||
|
|
@ -19,6 +20,7 @@ install:
|
|||
- pip install -q $DJANGO
|
||||
- pip install coveralls
|
||||
- pip install mockredispy
|
||||
- pip install celery
|
||||
- python setup.py develop
|
||||
|
||||
script:
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ to improve the login.
|
|||
requirements
|
||||
============
|
||||
|
||||
- django: 1.4.x, 1.6.x, 1.7.x
|
||||
- django: 1.4.x, 1.5.x, 1.6.x, 1.7.x
|
||||
- redis
|
||||
- python: 2.6.x, 2.7.x, 3.3.x, 3.4.x, PyPy
|
||||
|
||||
|
|
@ -263,6 +263,9 @@ Default: ``redis://localhost:6379/0``
|
|||
(Example with password: ``redis://:mypassword@localhost:6379/0``)
|
||||
* ``DEFENDER_PROTECTED_LOGINS``: Tuple: Used by ``ViewDecoratorMiddleware`` to decide
|
||||
which login urls need protecting. Default: ``('/accounts/login/',)``
|
||||
* ``DEFENDER_USE_CELERY``: Boolean: If you want to use Celery to store the login
|
||||
attempt to the database, set to True. If False, it is saved inline.
|
||||
Default: ``False``
|
||||
|
||||
Running Tests
|
||||
=============
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
doc-warnings: yes
|
||||
test-warnings: no
|
||||
strictness: veryhigh
|
||||
max-line-length: 80
|
||||
uses:
|
||||
- django
|
||||
autodetect: yes
|
||||
|
|
@ -50,3 +50,5 @@ LOCKOUT_URL = get_setting('DEFENDER_LOCKOUT_URL')
|
|||
|
||||
PROTECTED_LOGINS = get_setting('DEFENDER_PROTECTED_LOGINS',
|
||||
('/accounts/login/',))
|
||||
|
||||
USE_CELERY = get_setting('DEFENDER_USE_CELERY', False)
|
||||
|
|
|
|||
14
defender/data.py
Normal file
14
defender/data.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
from .models import AccessAttempt
|
||||
|
||||
|
||||
def store_login_attempt(user_agent, ip_address, username,
|
||||
http_accept, path_info, login_valid):
|
||||
""" Store the login attempt to the db. """
|
||||
AccessAttempt.objects.create(
|
||||
user_agent=user_agent,
|
||||
ip_address=ip_address,
|
||||
username=username,
|
||||
http_accept=http_accept,
|
||||
path_info=path_info,
|
||||
login_valid=login_valid,
|
||||
)
|
||||
|
|
@ -34,7 +34,7 @@ def watch_login(func):
|
|||
|
||||
# ideally make this background task, but to keep simple, keeping
|
||||
# it inline for now.
|
||||
utils.add_login_attempt(request, not login_unsuccessful)
|
||||
utils.add_login_attempt_to_db(request, not login_unsuccessful)
|
||||
|
||||
if utils.check_request(request, login_unsuccessful):
|
||||
return response
|
||||
|
|
|
|||
15
defender/tasks.py
Normal file
15
defender/tasks.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
from . import config
|
||||
from .data import store_login_attempt
|
||||
|
||||
# not sure how to get this to look better. ideally we want to dynamically
|
||||
# apply the celery decorator based on the USE_CELERY setting.
|
||||
|
||||
if config.USE_CELERY:
|
||||
from celery import shared_task
|
||||
|
||||
@shared_task()
|
||||
def add_login_attempt_task(user_agent, ip_address, username,
|
||||
http_accept, path_info, login_valid):
|
||||
""" Create a record for the login attempt """
|
||||
store_login_attempt(user_agent, ip_address, username,
|
||||
http_accept, path_info, login_valid)
|
||||
|
|
@ -39,3 +39,22 @@ DEFENDER_COOLOFF_TIME = 2
|
|||
DEFENDER_REDIS_URL = None
|
||||
# use mock redis in unit tests locally.
|
||||
DEFENDER_MOCK_REDIS = True
|
||||
|
||||
# celery settings
|
||||
CELERY_ALWAYS_EAGER = True
|
||||
BROKER_BACKEND = 'memory'
|
||||
BROKER_URL = 'memory://'
|
||||
|
||||
import os
|
||||
|
||||
from celery import Celery
|
||||
|
||||
# set the default Django settings module for the 'celery' program.
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'defender.test_settings')
|
||||
|
||||
app = Celery('defender')
|
||||
|
||||
# Using a string here means the worker will not have to
|
||||
# pickle the object when using Windows.
|
||||
app.config_from_object('django.conf:settings')
|
||||
app.autodiscover_tasks(lambda: INSTALLED_APPS)
|
||||
|
|
|
|||
|
|
@ -294,7 +294,7 @@ class AccessAttemptTest(TestCase):
|
|||
response = self._login()
|
||||
self.assertContains(response, LOGIN_FORM_KEY)
|
||||
self.assertEquals(AccessAttempt.objects.count(), 1)
|
||||
self.assertIsNotNone(AccessAttempt.objects.all()[0])
|
||||
self.assertIsNotNone(str(AccessAttempt.objects.all()[0]))
|
||||
|
||||
def test_is_valid_ip(self):
|
||||
""" Test the is_valid_ip() method
|
||||
|
|
@ -413,6 +413,7 @@ class AccessAttemptTest(TestCase):
|
|||
req = HttpRequest()
|
||||
req.META['HTTP_X_PROXIED'] = '1.2.3.4'
|
||||
self.assertEqual(utils.get_ip(req), '1.2.3.4')
|
||||
|
||||
req = HttpRequest()
|
||||
req.META['HTTP_X_PROXIED'] = '1.2.3.4, 5.6.7.8, 127.0.0.1'
|
||||
self.assertEqual(utils.get_ip(req), '1.2.3.4')
|
||||
|
|
@ -450,6 +451,7 @@ class AccessAttemptTest(TestCase):
|
|||
)
|
||||
|
||||
def test_admin(self):
|
||||
""" test the admin pages for this app """
|
||||
from .admin import AccessAttemptAdmin
|
||||
AccessAttemptAdmin
|
||||
|
||||
|
|
@ -484,3 +486,23 @@ class AccessAttemptTest(TestCase):
|
|||
self.assertContains(response, LOGIN_FORM_KEY)
|
||||
response = self.client.get(ADMIN_LOGIN_URL)
|
||||
self.assertNotContains(response, self.LOCKED_MESSAGE)
|
||||
|
||||
@patch('defender.config.USE_CELERY', True)
|
||||
def test_use_celery(self):
|
||||
""" Check that use celery works"""
|
||||
|
||||
self.assertEquals(AccessAttempt.objects.count(), 0)
|
||||
|
||||
for i in range(0, int(config.FAILURE_LIMIT)):
|
||||
response = self._login()
|
||||
# Check if we are in the same login page
|
||||
self.assertContains(response, LOGIN_FORM_KEY)
|
||||
|
||||
# So, we shouldn't have gotten a lock-out yet.
|
||||
# But we should get one now
|
||||
response = self._login()
|
||||
self.assertContains(response, self.LOCKED_MESSAGE)
|
||||
|
||||
self.assertEquals(AccessAttempt.objects.count(),
|
||||
config.FAILURE_LIMIT+1)
|
||||
self.assertIsNotNone(str(AccessAttempt.objects.all()[0]))
|
||||
|
|
|
|||
|
|
@ -39,3 +39,22 @@ DEFENDER_COOLOFF_TIME = 2
|
|||
DEFENDER_REDIS_URL = "redis://localhost:6379/1"
|
||||
# don't use mock redis in unit tests, we will use real redis on travis.
|
||||
DEFENDER_MOCK_REDIS = False
|
||||
|
||||
# Celery settings:
|
||||
CELERY_ALWAYS_EAGER = True
|
||||
BROKER_BACKEND = 'memory'
|
||||
BROKER_URL = 'memory://'
|
||||
|
||||
import os
|
||||
|
||||
from celery import Celery
|
||||
|
||||
# set the default Django settings module for the 'celery' program.
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'defender.travis_settings')
|
||||
|
||||
app = Celery('defender')
|
||||
|
||||
# Using a string here means the worker will not have to
|
||||
# pickle the object when using Windows.
|
||||
app.config_from_object('django.conf:settings')
|
||||
app.autodiscover_tasks(lambda: INSTALLED_APPS)
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ from django.http import HttpResponseRedirect
|
|||
from django.shortcuts import render_to_response
|
||||
from django.template import RequestContext
|
||||
|
||||
from .models import AccessAttempt
|
||||
from .connection import get_redis_connection
|
||||
from . import config
|
||||
from .data import store_login_attempt
|
||||
|
||||
redis_server = get_redis_connection()
|
||||
|
||||
|
|
@ -233,14 +233,19 @@ def check_request(request, login_unsuccessful):
|
|||
return record_failed_attempt(ip_address, username)
|
||||
|
||||
|
||||
def add_login_attempt(request, login_valid):
|
||||
""" Create a record for the login attempt """
|
||||
AccessAttempt.objects.create(
|
||||
user_agent=request.META.get('HTTP_USER_AGENT',
|
||||
'<unknown>')[:255],
|
||||
ip_address=get_ip(request),
|
||||
username=request.POST.get(config.USERNAME_FORM_FIELD, None),
|
||||
http_accept=request.META.get('HTTP_ACCEPT', '<unknown>'),
|
||||
path_info=request.META.get('PATH_INFO', '<unknown>'),
|
||||
login_valid=login_valid,
|
||||
)
|
||||
def add_login_attempt_to_db(request, login_valid):
|
||||
""" Create a record for the login attempt If using celery call celery
|
||||
task, if not, call the method normally """
|
||||
user_agent = request.META.get('HTTP_USER_AGENT', '<unknown>')[:255]
|
||||
ip_address = get_ip(request)
|
||||
username = request.POST.get(config.USERNAME_FORM_FIELD, None)
|
||||
http_accept = request.META.get('HTTP_ACCEPT', '<unknown>')
|
||||
path_info = request.META.get('PATH_INFO', '<unknown>')
|
||||
|
||||
if config.USE_CELERY:
|
||||
from .tasks import add_login_attempt_task
|
||||
add_login_attempt_task.delay(user_agent, ip_address, username,
|
||||
http_accept, path_info, login_valid)
|
||||
else:
|
||||
store_login_attempt(user_agent, ip_address, username,
|
||||
http_accept, path_info, login_valid)
|
||||
|
|
|
|||
2
setup.py
2
setup.py
|
|
@ -38,5 +38,5 @@ setup(name='django-defender',
|
|||
license='Apache 2',
|
||||
packages=['defender'],
|
||||
install_requires=['django==1.6.8', 'redis==2.10.3', 'hiredis==0.1.4', ],
|
||||
tests_require=['mock', 'mockredispy', 'coverage'],
|
||||
tests_require=['mock', 'mockredispy', 'coverage', 'celery'],
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue