mirror of
https://github.com/jazzband/django-defender.git
synced 2026-03-16 22:10:32 +00:00
* fixing issue #219 don't add Redis username by default
This commit is contained in:
parent
a4b3f9f332
commit
b0f90e690a
4 changed files with 90 additions and 16 deletions
7
.github/workflows/test.yml
vendored
7
.github/workflows/test.yml
vendored
|
|
@ -9,13 +9,16 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
max-parallel: 5
|
max-parallel: 5
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ['3.7', '3.8', '3.9', '3.10', 'pypy-3.8']
|
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', 'pypy-3.8']
|
||||||
|
redis-version: [5, 6, 7]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Start Redis
|
- name: Start Redis
|
||||||
uses: supercharge/redis-github-action@1.1.0
|
uses: supercharge/redis-github-action@1.5.0
|
||||||
|
with:
|
||||||
|
redis-version: ${{ matrix.redis-version }}
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,7 @@ Requirements
|
||||||
|
|
||||||
* Python: 3.7, 3.8, 3.9, 3.10, PyPy
|
* Python: 3.7, 3.8, 3.9, 3.10, PyPy
|
||||||
* Django: 3.x, 4.x
|
* Django: 3.x, 4.x
|
||||||
* Redis
|
* Redis: 5.x, 6.x, 7.x
|
||||||
|
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ def get_redis_connection():
|
||||||
else: # pragma: no cover
|
else: # pragma: no cover
|
||||||
redis_config = parse_redis_url(
|
redis_config = parse_redis_url(
|
||||||
config.DEFENDER_REDIS_URL, config.DEFENDER_REDIS_PASSWORD_QUOTE)
|
config.DEFENDER_REDIS_URL, config.DEFENDER_REDIS_PASSWORD_QUOTE)
|
||||||
|
|
||||||
return redis.StrictRedis(
|
return redis.StrictRedis(
|
||||||
host=redis_config.get("HOST"),
|
host=redis_config.get("HOST"),
|
||||||
port=redis_config.get("PORT"),
|
port=redis_config.get("PORT"),
|
||||||
|
|
@ -50,7 +51,6 @@ def parse_redis_url(url, password_quote=None):
|
||||||
# create config with some sane defaults
|
# create config with some sane defaults
|
||||||
redis_config = {
|
redis_config = {
|
||||||
"DB": 0,
|
"DB": 0,
|
||||||
"USERNAME": "default",
|
|
||||||
"PASSWORD": None,
|
"PASSWORD": None,
|
||||||
"HOST": "localhost",
|
"HOST": "localhost",
|
||||||
"PORT": 6379,
|
"PORT": 6379,
|
||||||
|
|
@ -60,25 +60,26 @@ def parse_redis_url(url, password_quote=None):
|
||||||
if not url:
|
if not url:
|
||||||
return redis_config
|
return redis_config
|
||||||
|
|
||||||
url = urlparse.urlparse(url)
|
purl = urlparse.urlparse(url)
|
||||||
|
|
||||||
# Remove query strings.
|
# Remove query strings.
|
||||||
path = url.path[1:]
|
path = purl.path[1:]
|
||||||
path = path.split("?", 2)[0]
|
path = path.split("?", 2)[0]
|
||||||
|
|
||||||
if path:
|
if path:
|
||||||
redis_config.update({"DB": int(path)})
|
redis_config.update({"DB": int(path)})
|
||||||
if url.password:
|
if purl.password:
|
||||||
password = url.password
|
password = purl.password
|
||||||
if password_quote:
|
if password_quote:
|
||||||
password = urlparse.unquote(password)
|
password = urlparse.unquote(password)
|
||||||
redis_config.update({"PASSWORD": password})
|
redis_config.update({"PASSWORD": password})
|
||||||
if url.hostname:
|
if purl.hostname:
|
||||||
redis_config.update({"HOST": url.hostname})
|
redis_config.update({"HOST": purl.hostname})
|
||||||
if url.username:
|
if purl.username:
|
||||||
redis_config.update({"USERNAME": url.username})
|
redis_config.update({"USERNAME": purl.username})
|
||||||
if url.port:
|
if purl.port:
|
||||||
redis_config.update({"PORT": int(url.port)})
|
redis_config.update({"PORT": int(purl.port)})
|
||||||
if url.scheme in ["https", "rediss"]:
|
if purl.scheme in ["https", "rediss"]:
|
||||||
redis_config.update({"SSL": True})
|
redis_config.update({"SSL": True})
|
||||||
|
|
||||||
return redis_config
|
return redis_config
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,12 @@ from django.contrib.sessions.backends.db import SessionStore
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
|
from django.test.testcases import TestCase
|
||||||
from redis.client import Redis
|
from redis.client import Redis
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
|
import redis
|
||||||
|
|
||||||
from defender.data import get_approx_account_lockouts_from_login_attempts
|
from defender.data import get_approx_account_lockouts_from_login_attempts
|
||||||
|
|
||||||
from . import utils
|
from . import utils
|
||||||
|
|
@ -483,6 +486,7 @@ class AccessAttemptTest(DefenderTestCase):
|
||||||
self.assertEqual(conf.get("DB"), 2)
|
self.assertEqual(conf.get("DB"), 2)
|
||||||
self.assertEqual(conf.get("PASSWORD"), "password")
|
self.assertEqual(conf.get("PASSWORD"), "password")
|
||||||
self.assertEqual(conf.get("PORT"), 1234)
|
self.assertEqual(conf.get("PORT"), 1234)
|
||||||
|
self.assertEqual(conf.get("USERNAME"), "user")
|
||||||
|
|
||||||
# full non local
|
# full non local
|
||||||
conf = parse_redis_url(
|
conf = parse_redis_url(
|
||||||
|
|
@ -491,6 +495,7 @@ class AccessAttemptTest(DefenderTestCase):
|
||||||
self.assertEqual(conf.get("DB"), 2)
|
self.assertEqual(conf.get("DB"), 2)
|
||||||
self.assertEqual(conf.get("PASSWORD"), "pass")
|
self.assertEqual(conf.get("PASSWORD"), "pass")
|
||||||
self.assertEqual(conf.get("PORT"), 1234)
|
self.assertEqual(conf.get("PORT"), 1234)
|
||||||
|
self.assertEqual(conf.get("USERNAME"), "user")
|
||||||
|
|
||||||
# no user name
|
# no user name
|
||||||
conf = parse_redis_url("redis://password@localhost2:1234/2", False)
|
conf = parse_redis_url("redis://password@localhost2:1234/2", False)
|
||||||
|
|
@ -990,7 +995,7 @@ class AccessAttemptTest(DefenderTestCase):
|
||||||
def test_approx_account_lockout_count_default_case_invalid_args_pt1(self):
|
def test_approx_account_lockout_count_default_case_invalid_args_pt1(self):
|
||||||
with self.assertRaises(Exception):
|
with self.assertRaises(Exception):
|
||||||
get_approx_account_lockouts_from_login_attempts(ip_address="127.0.0.1")
|
get_approx_account_lockouts_from_login_attempts(ip_address="127.0.0.1")
|
||||||
|
|
||||||
@patch("defender.config.DISABLE_USERNAME_LOCKOUT", True)
|
@patch("defender.config.DISABLE_USERNAME_LOCKOUT", True)
|
||||||
def test_approx_account_lockout_count_default_case_invalid_args_pt2(self):
|
def test_approx_account_lockout_count_default_case_invalid_args_pt2(self):
|
||||||
with self.assertRaises(Exception):
|
with self.assertRaises(Exception):
|
||||||
|
|
@ -1179,3 +1184,68 @@ class TestUtils(DefenderTestCase):
|
||||||
self.assertEqual(utils.remove_prefix(
|
self.assertEqual(utils.remove_prefix(
|
||||||
"defender:blocked:username:johndoe", "blocked:username:"),
|
"defender:blocked:username:johndoe", "blocked:username:"),
|
||||||
"defender:blocked:username:johndoe")
|
"defender:blocked:username:johndoe")
|
||||||
|
|
||||||
|
|
||||||
|
class TestRedisConnection(TestCase):
|
||||||
|
""" Test the redis connection parsing """
|
||||||
|
REDIS_URL_PLAIN = "redis://localhost:6379/0"
|
||||||
|
REDIS_URL_PASS = "redis://:mypass@localhost:6379/0"
|
||||||
|
REDIS_URL_NAME_PASS = "redis://myname:mypass2@localhost:6379/0"
|
||||||
|
|
||||||
|
@patch("defender.config.DEFENDER_REDIS_URL", REDIS_URL_PLAIN)
|
||||||
|
@patch("defender.config.MOCK_REDIS", False)
|
||||||
|
def test_get_redis_connection(self):
|
||||||
|
""" get redis connection plain """
|
||||||
|
redis_client = get_redis_connection()
|
||||||
|
self.assertIsInstance(redis_client, Redis)
|
||||||
|
redis_client.set('test', 0)
|
||||||
|
result = int(redis_client.get('test'))
|
||||||
|
self.assertEqual(result, 0)
|
||||||
|
redis_client.delete('test')
|
||||||
|
|
||||||
|
@patch("defender.config.DEFENDER_REDIS_URL", REDIS_URL_PASS)
|
||||||
|
@patch("defender.config.MOCK_REDIS", False)
|
||||||
|
def test_get_redis_connection_with_password(self):
|
||||||
|
""" get redis connection with password """
|
||||||
|
|
||||||
|
connection = redis.Redis()
|
||||||
|
connection.config_set('requirepass', 'mypass')
|
||||||
|
|
||||||
|
redis_client = get_redis_connection()
|
||||||
|
self.assertIsInstance(redis_client, Redis)
|
||||||
|
redis_client.set('test2', 0)
|
||||||
|
result = int(redis_client.get('test2'))
|
||||||
|
self.assertEqual(result, 0)
|
||||||
|
redis_client.delete('test2')
|
||||||
|
# clean up
|
||||||
|
redis_client.config_set('requirepass', '')
|
||||||
|
|
||||||
|
@patch("defender.config.DEFENDER_REDIS_URL", REDIS_URL_NAME_PASS)
|
||||||
|
@patch("defender.config.MOCK_REDIS", False)
|
||||||
|
def test_get_redis_connection_with_acl(self):
|
||||||
|
""" get redis connection with password and name ACL """
|
||||||
|
connection = redis.Redis()
|
||||||
|
|
||||||
|
if connection.info().get('redis_version') < '6':
|
||||||
|
# redis versions before 6 don't have acl, so skip.
|
||||||
|
return
|
||||||
|
|
||||||
|
connection.acl_setuser(
|
||||||
|
'myname',
|
||||||
|
enabled=True,
|
||||||
|
passwords=["+" + "mypass2", ],
|
||||||
|
keys="*",
|
||||||
|
commands=["+@all", ])
|
||||||
|
|
||||||
|
try:
|
||||||
|
redis_client = get_redis_connection()
|
||||||
|
self.assertIsInstance(redis_client, Redis)
|
||||||
|
redis_client.set('test3', 0)
|
||||||
|
result = int(redis_client.get('test3'))
|
||||||
|
self.assertEqual(result, 0)
|
||||||
|
redis_client.delete('test3')
|
||||||
|
except Exception as e:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
# clean up
|
||||||
|
connection.acl_deluser('myname')
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue