diff --git a/README.rst b/README.rst index 937693c..068ab94 100644 --- a/README.rst +++ b/README.rst @@ -357,6 +357,8 @@ These should be defined in your ``settings.py`` file. * ``DEFENDER_REDIS_URL``\ : String: the redis url for defender. [Default: ``redis://localhost:6379/0``\ ] (Example with password: ``redis://:mypassword@localhost:6379/0``\ ) +* ``DEFENDER_REDIS_PASSWORD_QUOTE``\ : Boolean: if special character in redis password(like '@'), we can quote password(urllib.quote_plus("password!@#")), and set to True. + [Default: ``False``\ ] * ``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 diff --git a/defender/config.py b/defender/config.py index 18afde4..cc6b710 100644 --- a/defender/config.py +++ b/defender/config.py @@ -11,6 +11,9 @@ def get_setting(variable, default=None): # redis server host DEFENDER_REDIS_URL = get_setting("DEFENDER_REDIS_URL") +# redis password quote for special character +DEFENDER_REDIS_PASSWORD_QUOTE = get_setting("DEFENDER_REDIS_PASSWORD_QUOTE", False) + # reuse declared cache from django settings DEFENDER_REDIS_NAME = get_setting("DEFENDER_REDIS_NAME") diff --git a/defender/connection.py b/defender/connection.py index 172c6fe..de00d94 100644 --- a/defender/connection.py +++ b/defender/connection.py @@ -35,7 +35,8 @@ def get_redis_connection(): # django_redis.cache.RedisCache case (django-redis package) return cache.client.get_client(True) else: # pragma: no cover - redis_config = parse_redis_url(config.DEFENDER_REDIS_URL) + redis_config = parse_redis_url( + config.DEFENDER_REDIS_URL, config.DEFENDER_REDIS_PASSWORD_QUOTE) return redis.StrictRedis( host=redis_config.get("HOST"), port=redis_config.get("PORT"), @@ -45,7 +46,7 @@ def get_redis_connection(): ) -def parse_redis_url(url): +def parse_redis_url(url, password_quote): """Parses a redis URL.""" # create config with some sane defaults @@ -68,7 +69,10 @@ def parse_redis_url(url): if path: redis_config.update({"DB": int(path)}) if url.password: - redis_config.update({"PASSWORD": url.password}) + password = url.password + if password_quote: + password = urlparse.unquote(password) + redis_config.update({"PASSWORD": password}) if url.hostname: redis_config.update({"HOST": url.hostname}) if url.port: diff --git a/defender/tests.py b/defender/tests.py index 9609f86..598d64c 100644 --- a/defender/tests.py +++ b/defender/tests.py @@ -491,54 +491,69 @@ class AccessAttemptTest(DefenderTestCase): def test_parse_redis_url(self): """ test the parse_redis_url method """ # full regular - conf = parse_redis_url("redis://user:password@localhost2:1234/2") + conf = parse_redis_url("redis://user:password@localhost2:1234/2", False) self.assertEqual(conf.get("HOST"), "localhost2") self.assertEqual(conf.get("DB"), 2) self.assertEqual(conf.get("PASSWORD"), "password") self.assertEqual(conf.get("PORT"), 1234) # full non local - conf = parse_redis_url("redis://user:pass@www.localhost.com:1234/2") + conf = parse_redis_url( + "redis://user:pass@www.localhost.com:1234/2", False) self.assertEqual(conf.get("HOST"), "www.localhost.com") self.assertEqual(conf.get("DB"), 2) self.assertEqual(conf.get("PASSWORD"), "pass") self.assertEqual(conf.get("PORT"), 1234) # no user name - conf = parse_redis_url("redis://password@localhost2:1234/2") + conf = parse_redis_url("redis://password@localhost2:1234/2", False) self.assertEqual(conf.get("HOST"), "localhost2") self.assertEqual(conf.get("DB"), 2) self.assertEqual(conf.get("PASSWORD"), None) self.assertEqual(conf.get("PORT"), 1234) # no user name 2 with colon - conf = parse_redis_url("redis://:password@localhost2:1234/2") + conf = parse_redis_url("redis://:password@localhost2:1234/2", False) self.assertEqual(conf.get("HOST"), "localhost2") self.assertEqual(conf.get("DB"), 2) self.assertEqual(conf.get("PASSWORD"), "password") self.assertEqual(conf.get("PORT"), 1234) # Empty - conf = parse_redis_url(None) + conf = parse_redis_url(None, False) self.assertEqual(conf.get("HOST"), "localhost") self.assertEqual(conf.get("DB"), 0) self.assertEqual(conf.get("PASSWORD"), None) self.assertEqual(conf.get("PORT"), 6379) # no db - conf = parse_redis_url("redis://:password@localhost2:1234") + conf = parse_redis_url("redis://:password@localhost2:1234", False) self.assertEqual(conf.get("HOST"), "localhost2") self.assertEqual(conf.get("DB"), 0) self.assertEqual(conf.get("PASSWORD"), "password") self.assertEqual(conf.get("PORT"), 1234) # no password - conf = parse_redis_url("redis://localhost2:1234/0") + conf = parse_redis_url("redis://localhost2:1234/0", False) self.assertEqual(conf.get("HOST"), "localhost2") self.assertEqual(conf.get("DB"), 0) self.assertEqual(conf.get("PASSWORD"), None) self.assertEqual(conf.get("PORT"), 1234) + # password with special character and set the password_quote = True + conf = parse_redis_url("redis://:calmkart%23%40%21@localhost:6379/0", True) + self.assertEqual(conf.get("HOST"), "localhost") + self.assertEqual(conf.get("DB"), 0) + self.assertEqual(conf.get("PASSWORD"), "calmkart#@!") + self.assertEqual(conf.get("PORT"), 6379) + + # password without special character and set the password_quote = True + conf = parse_redis_url("redis://:password@localhost2:1234", True) + self.assertEqual(conf.get("HOST"), "localhost2") + self.assertEqual(conf.get("DB"), 0) + self.assertEqual(conf.get("PASSWORD"), "password") + self.assertEqual(conf.get("PORT"), 1234) + @patch("defender.config.DEFENDER_REDIS_NAME", "default") def test_get_redis_connection_django_conf(self): """ get the redis connection """