From 6cbc6ad9d2c0900e102b3a46690380c4a6505b86 Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Tue, 6 Feb 2024 09:21:04 -0500 Subject: [PATCH] redis: Add username Redis introduced ACL feature in 4.0.0, and this feature is supported by redis-py since 3.4.0[1]. When ACL is enabled, authentication requires username in addition to password. This adds the username argument to Redis backend and Redis Sentinel backend, so that username can be passed in arguments along with password, instead of using client_kwargs and sentinel_kwargs. [1] https://github.com/redis/redis-py/commit/8df8cd54d135380ad8b3b8807a67a3e6915b0b49 Closes: #251 Pull-request: https://github.com/sqlalchemy/dogpile.cache/pull/251 Pull-request-sha: 172c195b05194cca6a982bf7e5314291fe36798c Change-Id: I4c60316bafca0d600f26b13e867f2b569c1087f5 --- docs/build/unreleased/251.rst | 7 +++++++ dogpile/cache/backends/redis.py | 16 +++++++++++++++- tests/cache/test_redis_backend.py | 31 +++++++++++++++++++++++++------ tox.ini | 4 ++-- 4 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 docs/build/unreleased/251.rst diff --git a/docs/build/unreleased/251.rst b/docs/build/unreleased/251.rst new file mode 100644 index 0000000..36c2e40 --- /dev/null +++ b/docs/build/unreleased/251.rst @@ -0,0 +1,7 @@ +.. change:: + :tags: usecase, redis + + Added new paramref:`.RedisBackend.username` parameter. This is used for + authentication in Redis when RBAC is enabled. Also added new + paramref:`.RedisSentinelBackend.username` parameter, which is used for + authentication in both Redis and Sentinel when RBAC is enabled. diff --git a/dogpile/cache/backends/redis.py b/dogpile/cache/backends/redis.py index 6df265f..5bde578 100644 --- a/dogpile/cache/backends/redis.py +++ b/dogpile/cache/backends/redis.py @@ -45,11 +45,13 @@ class RedisBackend(BytesBackend): Arguments accepted in the arguments dictionary: :param url: string. If provided, will override separate - host/password/port/db params. The format is that accepted by + host/username/password/port/db params. The format is that accepted by ``StrictRedis.from_url()``. :param host: string, default is ``localhost``. + :param username: string, default is no username. + :param password: string, default is no password. :param port: integer, default is ``6379``. @@ -95,6 +97,8 @@ class RedisBackend(BytesBackend): .. versionadded:: 1.1.6 Added ``connection_kwargs`` parameter. + .. versionadded:: 1.3.1 Added ``username`` parameter. + """ def __init__(self, arguments): @@ -102,6 +106,7 @@ def __init__(self, arguments): self._imports() self.url = arguments.pop("url", None) self.host = arguments.pop("host", "localhost") + self.username = arguments.pop("username", None) self.password = arguments.pop("password", None) self.port = arguments.pop("port", 6379) self.db = arguments.pop("db", 0) @@ -149,6 +154,7 @@ def _create_client(self): else: args.update( host=self.host, + username=self.username, password=self.password, port=self.port, db=self.db, @@ -247,6 +253,10 @@ class RedisSentinelBackend(RedisBackend): Arguments accepted in the arguments dictionary: + :param username: string, default is no username. + + :param password: string, default is no password. + :param db: integer, default is ``0``. :param redis_expiration_time: integer, number of seconds after setting @@ -292,6 +302,8 @@ class RedisSentinelBackend(RedisBackend): asynchronous runners, as they run in a different thread than the one used to create the lock. + .. versionadded:: 1.3.1 Added ``username`` parameter. + """ def __init__(self, arguments): @@ -317,10 +329,12 @@ def _imports(self): def _create_client(self): sentinel_kwargs = {} sentinel_kwargs.update(self.sentinel_kwargs) + sentinel_kwargs.setdefault("username", self.username) sentinel_kwargs.setdefault("password", self.password) connection_kwargs = {} connection_kwargs.update(self.connection_kwargs) + connection_kwargs.setdefault("username", self.username) connection_kwargs.setdefault("password", self.password) if self.db is not None: diff --git a/tests/cache/test_redis_backend.py b/tests/cache/test_redis_backend.py index e17dac7..d304aab 100644 --- a/tests/cache/test_redis_backend.py +++ b/tests/cache/test_redis_backend.py @@ -149,20 +149,22 @@ def test_connect_with_defaults(self, MockStrictRedis): # The defaults, used if keys are missing from the arguments dict. arguments = { "host": "localhost", - "password": None, "port": 6379, "db": 0, } - self._test_helper(MockStrictRedis, arguments, {}) + expected = arguments.copy() + expected.update({"username": None, "password": None}) + self._test_helper(MockStrictRedis, expected, arguments) def test_connect_with_basics(self, MockStrictRedis): arguments = { "host": "127.0.0.1", - "password": None, "port": 6379, "db": 0, } - self._test_helper(MockStrictRedis, arguments) + expected = arguments.copy() + expected.update({"username": None, "password": None}) + self._test_helper(MockStrictRedis, expected, arguments) def test_connect_with_password(self, MockStrictRedis): arguments = { @@ -171,6 +173,22 @@ def test_connect_with_password(self, MockStrictRedis): "port": 6379, "db": 0, } + expected = arguments.copy() + expected.update( + { + "username": None, + } + ) + self._test_helper(MockStrictRedis, expected, arguments) + + def test_connect_with_username_and_password(self, MockStrictRedis): + arguments = { + "host": "127.0.0.1", + "username": "redis", + "password": "some password", + "port": 6379, + "db": 0, + } self._test_helper(MockStrictRedis, arguments) def test_connect_with_socket_timeout(self, MockStrictRedis): @@ -178,10 +196,11 @@ def test_connect_with_socket_timeout(self, MockStrictRedis): "host": "127.0.0.1", "port": 6379, "socket_timeout": 0.5, - "password": None, "db": 0, } - self._test_helper(MockStrictRedis, arguments) + expected = arguments.copy() + expected.update({"username": None, "password": None}) + self._test_helper(MockStrictRedis, expected, arguments) def test_connect_with_connection_pool(self, MockStrictRedis): pool = Mock() diff --git a/tox.ini b/tox.ini index 170421b..24c8c09 100644 --- a/tox.ini +++ b/tox.ini @@ -44,8 +44,8 @@ deps= {memcached}: python-memcached {memcached}: python-binary-memcached>=0.29.0 {memcached}: pymemcache>=3.5.0 - {redis}: redis - {redis_sentinel}: redis + {redis}: redis>=3.4.0 + {redis_sentinel}: redis>=3.4.0 {cov}: pytest-cov commands=