summaryrefslogtreecommitdiff
path: root/redis/asyncio
diff options
context:
space:
mode:
authorBar Shaul <88437685+barshaul@users.noreply.github.com>2022-11-10 12:38:47 +0200
committerGitHub <noreply@github.com>2022-11-10 12:38:47 +0200
commitbb06ccd52924800ac501d17c8a42038c8e5c5770 (patch)
treedf9fa0ae2c2553ecc3779b3f7166d6cad4855c03 /redis/asyncio
parentfb647430f00cc7bb67c978e75f2dabc661567779 (diff)
downloadredis-py-bb06ccd52924800ac501d17c8a42038c8e5c5770.tar.gz
CredentialsProvider class added to support password rotation (#2261)
* A CredentialsProvider class has been added to allow the user to add his own provider for password rotation * Moved CredentialsProvider to a separate file, added type hints * Changed username and password to properties * Added: StaticCredentialProvider, examples, tests Changed: CredentialsProvider to CredentialProvider Fixed: calling AUTH only with password * Changed private members' prefix to __ * fixed linters * fixed auth test * fixed credential test * Raise an error if username or password are passed along with credential_provider * fixing linters * fixing test * Changed dundered to single per side underscore * Changed Connection class members username and password to properties to enable backward compatibility with changing the members value on existing connection. * Reverting last commit and adding backward compatibility to 'username' and 'password' inside on_connect function * Refactored CredentialProvider class * Fixing tuple type to Tuple * Fixing optional string members in UsernamePasswordCredentialProvider * Fixed credential test * Added credential provider support to AsyncRedis * linters * linters * linters * linters - black Co-authored-by: dvora-h <67596500+dvora-h@users.noreply.github.com> Co-authored-by: dvora-h <dvora.heller@redis.com>
Diffstat (limited to 'redis/asyncio')
-rw-r--r--redis/asyncio/client.py3
-rw-r--r--redis/asyncio/cluster.py3
-rw-r--r--redis/asyncio/connection.py41
3 files changed, 36 insertions, 11 deletions
diff --git a/redis/asyncio/client.py b/redis/asyncio/client.py
index 619ee11..c085571 100644
--- a/redis/asyncio/client.py
+++ b/redis/asyncio/client.py
@@ -46,6 +46,7 @@ from redis.commands import (
list_or_args,
)
from redis.compat import Protocol, TypedDict
+from redis.credentials import CredentialProvider
from redis.exceptions import (
ConnectionError,
ExecAbortError,
@@ -174,6 +175,7 @@ class Redis(
retry: Optional[Retry] = None,
auto_close_connection_pool: bool = True,
redis_connect_func=None,
+ credential_provider: Optional[CredentialProvider] = None,
):
"""
Initialize a new Redis client.
@@ -199,6 +201,7 @@ class Redis(
"db": db,
"username": username,
"password": password,
+ "credential_provider": credential_provider,
"socket_timeout": socket_timeout,
"encoding": encoding,
"encoding_errors": encoding_errors,
diff --git a/redis/asyncio/cluster.py b/redis/asyncio/cluster.py
index 97f4151..57aafbd 100644
--- a/redis/asyncio/cluster.py
+++ b/redis/asyncio/cluster.py
@@ -40,6 +40,7 @@ from redis.cluster import (
)
from redis.commands import READ_COMMANDS, AsyncRedisClusterCommands
from redis.crc import REDIS_CLUSTER_HASH_SLOTS, key_slot
+from redis.credentials import CredentialProvider
from redis.exceptions import (
AskError,
BusyLoadingError,
@@ -220,6 +221,7 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
# Client related kwargs
db: Union[str, int] = 0,
path: Optional[str] = None,
+ credential_provider: Optional[CredentialProvider] = None,
username: Optional[str] = None,
password: Optional[str] = None,
client_name: Optional[str] = None,
@@ -266,6 +268,7 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
"connection_class": Connection,
"parser_class": ClusterParser,
# Client related kwargs
+ "credential_provider": credential_provider,
"username": username,
"password": password,
"client_name": client_name,
diff --git a/redis/asyncio/connection.py b/redis/asyncio/connection.py
index 1288bb6..df066c4 100644
--- a/redis/asyncio/connection.py
+++ b/redis/asyncio/connection.py
@@ -29,6 +29,7 @@ import async_timeout
from redis.asyncio.retry import Retry
from redis.backoff import NoBackoff
from redis.compat import Protocol, TypedDict
+from redis.credentials import CredentialProvider, UsernamePasswordCredentialProvider
from redis.exceptions import (
AuthenticationError,
AuthenticationWrongNumberOfArgsError,
@@ -416,6 +417,7 @@ class Connection:
"db",
"username",
"client_name",
+ "credential_provider",
"password",
"socket_timeout",
"socket_connect_timeout",
@@ -465,14 +467,23 @@ class Connection:
retry: Optional[Retry] = None,
redis_connect_func: Optional[ConnectCallbackT] = None,
encoder_class: Type[Encoder] = Encoder,
+ credential_provider: Optional[CredentialProvider] = None,
):
+ if (username or password) and credential_provider is not None:
+ raise DataError(
+ "'username' and 'password' cannot be passed along with 'credential_"
+ "provider'. Please provide only one of the following arguments: \n"
+ "1. 'password' and (optional) 'username'\n"
+ "2. 'credential_provider'"
+ )
self.pid = os.getpid()
self.host = host
self.port = int(port)
self.db = db
- self.username = username
self.client_name = client_name
+ self.credential_provider = credential_provider
self.password = password
+ self.username = username
self.socket_timeout = socket_timeout
self.socket_connect_timeout = socket_connect_timeout or socket_timeout or None
self.socket_keepalive = socket_keepalive
@@ -637,14 +648,13 @@ class Connection:
"""Initialize the connection, authenticate and select a database"""
self._parser.on_connect(self)
- # if username and/or password are set, authenticate
- if self.username or self.password:
- auth_args: Union[Tuple[str], Tuple[str, str]]
- if self.username:
- auth_args = (self.username, self.password or "")
- else:
- # Mypy bug: https://github.com/python/mypy/issues/10944
- auth_args = (self.password or "",)
+ # if credential provider or username and/or password are set, authenticate
+ if self.credential_provider or (self.username or self.password):
+ cred_provider = (
+ self.credential_provider
+ or UsernamePasswordCredentialProvider(self.username, self.password)
+ )
+ auth_args = cred_provider.get_credentials()
# avoid checking health here -- PING will fail if we try
# to check the health prior to the AUTH
await self.send_command("AUTH", *auth_args, check_health=False)
@@ -656,7 +666,7 @@ class Connection:
# server seems to be < 6.0.0 which expects a single password
# arg. retry auth with just the password.
# https://github.com/andymccurdy/redis-py/issues/1274
- await self.send_command("AUTH", self.password, check_health=False)
+ await self.send_command("AUTH", auth_args[-1], check_health=False)
auth_response = await self.read_response()
if str_if_bytes(auth_response) != "OK":
@@ -1014,18 +1024,27 @@ class UnixDomainSocketConnection(Connection): # lgtm [py/missing-call-to-init]
client_name: str = None,
retry: Optional[Retry] = None,
redis_connect_func=None,
+ credential_provider: Optional[CredentialProvider] = None,
):
"""
Initialize a new UnixDomainSocketConnection.
To specify a retry policy, first set `retry_on_timeout` to `True`
then set `retry` to a valid `Retry` object
"""
+ if (username or password) and credential_provider is not None:
+ raise DataError(
+ "'username' and 'password' cannot be passed along with 'credential_"
+ "provider'. Please provide only one of the following arguments: \n"
+ "1. 'password' and (optional) 'username'\n"
+ "2. 'credential_provider'"
+ )
self.pid = os.getpid()
self.path = path
self.db = db
- self.username = username
self.client_name = client_name
+ self.credential_provider = credential_provider
self.password = password
+ self.username = username
self.socket_timeout = socket_timeout
self.socket_connect_timeout = socket_connect_timeout or socket_timeout or None
self.retry_on_timeout = retry_on_timeout