Skip to content

Commit

Permalink
redis: Add username
Browse files Browse the repository at this point in the history
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] redis/redis-py@8df8cd5

Closes: #251
Pull-request: #251
Pull-request-sha: 172c195

Change-Id: I4c60316bafca0d600f26b13e867f2b569c1087f5
  • Loading branch information
kajinamit authored and sqla-tester committed Feb 6, 2024
1 parent 6bf3d05 commit 6cbc6ad
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 9 deletions.
7 changes: 7 additions & 0 deletions 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.
16 changes: 15 additions & 1 deletion dogpile/cache/backends/redis.py
Expand Up @@ -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``.
Expand Down Expand Up @@ -95,13 +97,16 @@ class RedisBackend(BytesBackend):
.. versionadded:: 1.1.6 Added ``connection_kwargs`` parameter.
.. versionadded:: 1.3.1 Added ``username`` parameter.
"""

def __init__(self, arguments):
arguments = arguments.copy()
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)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand All @@ -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:
Expand Down
31 changes: 25 additions & 6 deletions tests/cache/test_redis_backend.py
Expand Up @@ -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 = {
Expand All @@ -171,17 +173,34 @@ 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):
arguments = {
"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()
Expand Down
4 changes: 2 additions & 2 deletions tox.ini
Expand Up @@ -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=
Expand Down

0 comments on commit 6cbc6ad

Please sign in to comment.