fixing issue #219 don't add redis username by default (#227)

* fixing issue #219 don't add Redis username by default
This commit is contained in:
Ken Cochrane 2023-02-23 09:59:52 -05:00 committed by GitHub
parent a4b3f9f332
commit b0f90e690a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 90 additions and 16 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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')