diff --git a/.travis.yml b/.travis.yml index 3110220..4cf05a4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/README.md b/README.md index 103c1da..3bec96d 100644 --- a/README.md +++ b/README.md @@ -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``] diff --git a/defender/config.py b/defender/config.py index c51a75a..9a43799 100644 --- a/defender/config.py +++ b/defender/config.py @@ -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 diff --git a/defender/connection.py b/defender/connection.py index fc4f571..163eae1 100644 --- a/defender/connection.py +++ b/defender/connection.py @@ -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( diff --git a/defender/tests.py b/defender/tests.py index c896214..1860e60 100644 --- a/defender/tests.py +++ b/defender/tests.py @@ -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 = '
' - 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 = '' +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) diff --git a/defender/travis_settings.py b/defender/travis_settings.py index 6193061..fed0b95 100644 --- a/defender/travis_settings.py +++ b/defender/travis_settings.py @@ -7,6 +7,12 @@ DATABASES = { } } +CACHES = { + 'default': { + 'BACKEND': 'redis_cache.RedisCache', + 'LOCATION': 'localhost:6379', + } +} SITE_ID = 1 diff --git a/setup.py b/setup.py index b8a691f..f341b42 100644 --- a/setup.py +++ b/setup.py @@ -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'], )