diff --git a/redis/asyncio/connection.py b/redis/asyncio/connection.py index 6823dcb853..db8c2408e2 100644 --- a/redis/asyncio/connection.py +++ b/redis/asyncio/connection.py @@ -87,6 +87,15 @@ class _Sentinel(enum.Enum): "exports one or more module-side data " "types, can't unload" ) +# user send an AUTH cmd to a server without authorization configured +NO_AUTH_SET_ERROR = { + # Redis >= 6.0 + "AUTH called without any password " + "configured for the default user. Are you sure " + "your configuration is correct?": AuthenticationError, + # Redis < 6.0 + "Client sent AUTH, but no password is set": AuthenticationError, +} class _HiredisReaderArgs(TypedDict, total=False): @@ -160,7 +169,9 @@ class BaseParser: MODULE_EXPORTS_DATA_TYPES_ERROR: ModuleError, NO_SUCH_MODULE_ERROR: ModuleError, MODULE_UNLOAD_NOT_POSSIBLE_ERROR: ModuleError, + **NO_AUTH_SET_ERROR, }, + "WRONGPASS": AuthenticationError, "EXECABORT": ExecAbortError, "LOADING": BusyLoadingError, "NOSCRIPT": NoScriptError, diff --git a/redis/connection.py b/redis/connection.py index 3438bafe57..f708663fee 100755 --- a/redis/connection.py +++ b/redis/connection.py @@ -80,6 +80,15 @@ "exports one or more module-side data " "types, can't unload" ) +# user send an AUTH cmd to a server without authorization configured +NO_AUTH_SET_ERROR = { + # Redis >= 6.0 + "AUTH called without any password " + "configured for the default user. Are you sure " + "your configuration is correct?": AuthenticationError, + # Redis < 6.0 + "Client sent AUTH, but no password is set": AuthenticationError, +} class Encoder: @@ -127,7 +136,6 @@ class BaseParser: EXCEPTION_CLASSES = { "ERR": { "max number of clients reached": ConnectionError, - "Client sent AUTH, but no password is set": AuthenticationError, "invalid password": AuthenticationError, # some Redis server versions report invalid command syntax # in lowercase @@ -141,7 +149,9 @@ class BaseParser: MODULE_EXPORTS_DATA_TYPES_ERROR: ModuleError, NO_SUCH_MODULE_ERROR: ModuleError, MODULE_UNLOAD_NOT_POSSIBLE_ERROR: ModuleError, + **NO_AUTH_SET_ERROR, }, + "WRONGPASS": AuthenticationError, "EXECABORT": ExecAbortError, "LOADING": BusyLoadingError, "NOSCRIPT": NoScriptError, diff --git a/tests/test_commands.py b/tests/test_commands.py index f9134d858b..a213b41854 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -68,6 +68,14 @@ def test_case_insensitive_command_names(self, r): class TestRedisCommands: @skip_if_redis_enterprise() def test_auth(self, r, request): + # sending an AUTH command before setting a user/password on the + # server should return an AuthenticationError + with pytest.raises(exceptions.AuthenticationError): + r.auth("some_password") + + with pytest.raises(exceptions.AuthenticationError): + r.auth("some_password", "some_user") + # first, test for default user (`username` is supposed to be optional) default_username = "default" temp_pass = "temp_pass" @@ -81,9 +89,19 @@ def test_auth(self, r, request): def teardown(): try: - r.auth(temp_pass) - except exceptions.ResponseError: - r.auth("default", "") + # this is needed because after an AuthenticationError the connection + # is closed, and if we send an AUTH command a new connection is + # created, but in this case we'd get an "Authentication required" + # error when switching to the db 9 because we're not authenticated yet + # setting the password on the connection itself triggers the + # authentication in the connection's `on_connect` method + r.connection.password = temp_pass + except AttributeError: + # connection field is not set in Redis Cluster, but that's ok + # because the problem discussed above does not apply to Redis Cluster + pass + + r.auth(temp_pass) r.config_set("requirepass", "") r.acl_deluser(username) @@ -95,7 +113,7 @@ def teardown(): assert r.auth(username=username, password="strong_password") is True - with pytest.raises(exceptions.ResponseError): + with pytest.raises(exceptions.AuthenticationError): r.auth(username=username, password="wrong_password") def test_command_on_invalid_key_type(self, r): diff --git a/tests/test_connection_pool.py b/tests/test_connection_pool.py index a836f5b2c7..4c135106fc 100644 --- a/tests/test_connection_pool.py +++ b/tests/test_connection_pool.py @@ -545,22 +545,40 @@ def test_connect_from_url_unix(self): ) @skip_if_redis_enterprise() - def test_connect_no_auth_supplied_when_required(self, r): + def test_connect_no_auth_configured(self, r): """ - AuthenticationError should be raised when the server requires a - password but one isn't supplied. + AuthenticationError should be raised when the server is not configured with auth + but credentials are supplied by the user. """ + # Redis < 6 with pytest.raises(redis.AuthenticationError): r.execute_command( "DEBUG", "ERROR", "ERR Client sent AUTH, but no password is set" ) + # Redis >= 6 + with pytest.raises(redis.AuthenticationError): + r.execute_command( + "DEBUG", + "ERROR", + "ERR AUTH called without any password " + "configured for the default user. Are you sure " + "your configuration is correct?", + ) + @skip_if_redis_enterprise() - def test_connect_invalid_password_supplied(self, r): - "AuthenticationError should be raised when sending the wrong password" + def test_connect_invalid_auth_credentials_supplied(self, r): + """ + AuthenticationError should be raised when sending invalid username/password + """ + # Redis < 6 with pytest.raises(redis.AuthenticationError): r.execute_command("DEBUG", "ERROR", "ERR invalid password") + # Redis >= 6 + with pytest.raises(redis.AuthenticationError): + r.execute_command("DEBUG", "ERROR", "WRONGPASS") + @pytest.mark.onlynoncluster class TestMultiConnectionClient: