Taking redis client from django.core.cache (#82)

* new setting that point to an already configured redis client

* taking redis client from django cache setting

* adding informative exception

* dropping django 1.6 support

* dropping django 1.7 support

* adding tests

* removing old coverage stuff + pep8 fixes

* ups, wrong package

* supporting multiple backends

* adding documentation

* dropping python 2.6 support
This commit is contained in:
Francisco Rivera 2017-06-24 20:17:15 -03:00 committed by Ken Cochrane
parent a59cbca0f6
commit d2b712eade
7 changed files with 47 additions and 34 deletions

View file

@ -1,15 +1,12 @@
language: python
python:
- "2.6"
- "2.7"
- "3.3"
- "3.4"
- "3.5"
env:
- DJANGO=1.6
- DJANGO=1.7
- DJANGO=1.8
- DJANGO=1.9
- DJANGO=1.10
@ -22,6 +19,7 @@ install:
- pip install -q django~=$DJANGO.0
- pip install coveralls
- pip install mockredispy
- pip install django-redis-cache
- pip install 'celery<4'
- python setup.py develop
@ -31,26 +29,12 @@ script:
matrix:
exclude:
- python: "2.6"
env: DJANGO=1.7
- python: "2.6"
env: DJANGO=1.8
- python: "2.6"
env: DJANGO=1.9
- python: "2.6"
env: DJANGO=1.10
- python: "2.6"
env: DJANGO=1.11
- python: "3.3"
env: DJANGO=1.9
- python: "3.3"
env: DJANGO=1.10
- python: "3.3"
env: DJANGO=1.11
- python: "3.5"
env: DJANGO=1.6
- python: "3.5"
env: DJANGO=1.7
after_success:
- coveralls --verbose

View file

@ -151,7 +151,7 @@ to improve the login.
requirements
============
- django: 1.6.x, 1.7.x, 1.8.x, 1.9.x, 1.10.x, 1.11.x
- django: 1.8.x, 1.9.x, 1.10.x, 1.11.x
- redis
- python: 2.6.x, 2.7.x, 3.3.x, 3.4.x, 3.5.x, 3.6.x, PyPy
@ -332,6 +332,8 @@ locked out.
* ``DEFENDER_REDIS_URL``: String: the redis url for defender.
[Default: ``redis://localhost:6379/0``]
(Example with password: ``redis://:mypassword@localhost:6379/0``)
* ``DEFENDER_REDIS_NAME``: String: the name of your cache client on the CACHES django setting. If set, ``DEFENDER_REDIS_URL`` will be ignored.
[Default: ``None``]
* ``DEFENDER_STORE_ACCESS_ATTEMPTS``: Boolean: If you want to store the login
attempt to the database, set to True. If False, it is not saved
[Default: ``True``]

View file

@ -10,6 +10,9 @@ def get_setting(variable, default=None):
# redis server host
DEFENDER_REDIS_URL = get_setting('DEFENDER_REDIS_URL')
# reuse declared cache from django settings
DEFENDER_REDIS_NAME = get_setting('DEFENDER_REDIS_NAME')
MOCK_REDIS = get_setting('DEFENDER_MOCK_REDIS', False)
# see if the user has overridden the failure limit

View file

@ -1,3 +1,5 @@
from django.core.cache import caches
from django.core.cache.backends.base import InvalidCacheBackendError
import mockredis
import redis
try:
@ -12,12 +14,25 @@ urlparse.uses_netloc.append("redis")
MOCKED_REDIS = mockredis.mock_strict_redis_client()
INVALID_CACHE_ERROR_MSG = 'The cache {} was not found on the django cache settings.'
def get_redis_connection():
""" Get the redis connection if not using mock """
if config.MOCK_REDIS: # pragma: no cover
return MOCKED_REDIS # pragma: no cover
elif config.DEFENDER_REDIS_NAME: # pragma: no cover
try:
cache = caches[config.DEFENDER_REDIS_NAME]
except InvalidCacheBackendError:
raise KeyError(INVALID_CACHE_ERROR_MSG.format(config.DEFENDER_REDIS_NAME))
# every redis backend implement it own way to get the low level client
try:
# redis_cache.RedisCache case (django-redis-cache package)
return cache.get_master_client()
except AttributeError:
# django_redis.cache.RedisCache case (django-redis package)
return cache._client
else: # pragma: no cover
redis_config = parse_redis_url(config.DEFENDER_REDIS_URL)
return redis.StrictRedis(

View file

@ -9,10 +9,10 @@ from django import get_version
from django.contrib.auth.models import User
from django.contrib.auth.models import AnonymousUser
from django.contrib.sessions.backends.db import SessionStore
from django.core.urlresolvers import NoReverseMatch
from django.core.urlresolvers import reverse
from django.http import HttpRequest
from django.test.client import RequestFactory
from redis.client import Redis
from . import utils
from . import config
@ -20,14 +20,8 @@ from .connection import parse_redis_url, get_redis_connection
from .models import AccessAttempt
from .test import DefenderTestCase, DefenderTransactionTestCase
# Django >= 1.7 compatibility
try:
LOGIN_FORM_KEY = '<form action="/admin/login/" method="post"'
' id="login-form">'
ADMIN_LOGIN_URL = reverse('admin:login')
except NoReverseMatch:
ADMIN_LOGIN_URL = reverse('admin:index')
LOGIN_FORM_KEY = 'this_is_the_login_form'
LOGIN_FORM_KEY = '<form action="/admin/login/" method="post" id="login-form">'
ADMIN_LOGIN_URL = reverse('admin:login')
DJANGO_VERSION = StrictVersion(get_version())
@ -46,7 +40,7 @@ class AccessAttemptTest(DefenderTestCase):
""" Returns a random str """
chars = string.ascii_uppercase + string.digits
return ''.join(random.choice(chars) for x in range(20))
return ''.join(random.choice(chars) for _ in range(20))
def _login(self, username=None, password=None, user_agent='test-browser',
remote_addr='127.0.0.1'):
@ -193,7 +187,7 @@ class AccessAttemptTest(DefenderTestCase):
""" Test an user with blocked ip cannot login with another username
"""
for i in range(0, config.FAILURE_LIMIT + 1):
response = self._login(username=VALID_USERNAME)
self._login(username=VALID_USERNAME)
# try to login with a different user
response = self._login(username='myuser')
@ -205,7 +199,7 @@ class AccessAttemptTest(DefenderTestCase):
"""
for i in range(0, config.FAILURE_LIMIT + 1):
ip = '74.125.239.{0}.'.format(i)
response = self._login(username=VALID_USERNAME, remote_addr=ip)
self._login(username=VALID_USERNAME, remote_addr=ip)
# try to login with a different ip
response = self._login(username=VALID_USERNAME, remote_addr='8.8.8.8')
@ -457,6 +451,16 @@ class AccessAttemptTest(DefenderTestCase):
self.assertEqual(conf.get('PASSWORD'), None)
self.assertEqual(conf.get('PORT'), 1234)
@patch('defender.config.DEFENDER_REDIS_NAME', 'default')
def test_get_redis_connection_django_conf(self):
redis_client = get_redis_connection()
self.assertIsInstance(redis_client, Redis)
@patch('defender.config.DEFENDER_REDIS_NAME', 'bad-key')
def test_get_redis_connection_django_conf_wrong_key(self):
error_msg = 'The cache bad-key was not found on the django cache settings.'
self.assertRaisesMessage(KeyError, error_msg, get_redis_connection)
def test_get_ip_address_from_request(self):
req = HttpRequest()
req.META['REMOTE_ADDR'] = '1.2.3.4'
@ -589,8 +593,7 @@ class AccessAttemptTest(DefenderTestCase):
response = self._login()
self.assertContains(response, self.LOCKED_MESSAGE)
self.assertEqual(AccessAttempt.objects.count(),
config.FAILURE_LIMIT + 1)
self.assertEqual(AccessAttempt.objects.count(), config.FAILURE_LIMIT + 1)
self.assertIsNotNone(str(AccessAttempt.objects.all()[0]))
@patch('defender.config.LOCKOUT_BY_IP_USERNAME', True)

View file

@ -7,6 +7,12 @@ DATABASES = {
}
}
CACHES = {
'default': {
'BACKEND': 'redis_cache.RedisCache',
'LOCATION': 'localhost:6379',
}
}
SITE_ID = 1

View file

@ -69,7 +69,7 @@ setup(name='django-defender',
include_package_data=True,
packages=get_packages('defender'),
package_data=get_package_data('defender'),
install_requires=['Django>=1.6,<=1.10', 'redis>=2.10.3,<3.0',
install_requires=['Django>=1.8,<=1.10', 'redis>=2.10.3,<3.0',
'hiredis>=0.2.0,<1.0', 'mockredispy>=2.9.0.11,<3.0'],
tests_require=['mock', 'mockredispy', 'coverage', 'celery'],
tests_require=['mock', 'mockredispy', 'coverage', 'celery', 'django-redis-cache'],
)