diff --git a/CHANGELOG.md b/CHANGELOG.md index e81cbd0..1d13918 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- Allow `password` to be a callable. Makes it easy to implement short lived password authentication strategies. - Fix a thread safety issue in `hiredis-client` when using the `pubsub` client concurrently. # 0.22.2 diff --git a/README.md b/README.md index 13ff00a..cdcf575 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ redis.call("GET", "mykey") - `db`: The database to select after connecting, defaults to `0`. - `id` ID for the client connection, assigns name to current connection by sending `CLIENT SETNAME`. - `username` Username to authenticate against server, defaults to `"default"`. -- `password` Password to authenticate against server. +- `password` Password to authenticate against server. Can either be a String or a callable that recieve `username` as argument and return a passowrd as a String. - `timeout`: The general timeout in seconds, default to `1.0`. - `connect_timeout`: The connection timeout, takes precedence over the general timeout when connecting to the server. - `read_timeout`: The read timeout, takes precedence over the general timeout when reading responses from the server. diff --git a/lib/redis_client/config.rb b/lib/redis_client/config.rb index 91ac0dd..32bff05 100644 --- a/lib/redis_client/config.rb +++ b/lib/redis_client/config.rb @@ -12,8 +12,8 @@ class Config DEFAULT_DB = 0 module Common - attr_reader :db, :password, :id, :ssl, :ssl_params, :command_builder, :inherit_socket, - :connect_timeout, :read_timeout, :write_timeout, :driver, :connection_prelude, :protocol, + attr_reader :db, :id, :ssl, :ssl_params, :command_builder, :inherit_socket, + :connect_timeout, :read_timeout, :write_timeout, :driver, :protocol, :middlewares_stack, :custom, :circuit_breaker alias_method :ssl?, :ssl @@ -70,7 +70,7 @@ def initialize( reconnect_attempts = Array.new(reconnect_attempts, 0).freeze if reconnect_attempts.is_a?(Integer) @reconnect_attempts = reconnect_attempts - @connection_prelude = build_connection_prelude + @connection_prelude = (build_connection_prelude unless @password.respond_to?(:call)) circuit_breaker = CircuitBreaker.new(**circuit_breaker) if circuit_breaker.is_a?(Hash) if @circuit_breaker = circuit_breaker @@ -87,6 +87,22 @@ def initialize( @middlewares_stack = middlewares_stack end + def connection_prelude + if @password.respond_to?(:call) + build_connection_prelude + else + @connection_prelude + end + end + + def password + if @password.respond_to?(:call) + @password.call(username) + else + @password + end + end + def username @username || DEFAULT_USERNAME end @@ -151,17 +167,18 @@ def server_url def build_connection_prelude prelude = [] + pass = password if protocol == 3 - prelude << if @password - ["HELLO", "3", "AUTH", @username || DEFAULT_USERNAME, @password] + prelude << if pass + ["HELLO", "3", "AUTH", username, pass] else ["HELLO", "3"] end - elsif @password + elsif pass prelude << if @username && !@username.empty? - ["AUTH", @username, @password] + ["AUTH", @username, pass] else - ["AUTH", @password] + ["AUTH", pass] end end diff --git a/test/redis_client/config_test.rb b/test/redis_client/config_test.rb index 74c1085..3d5c89b 100644 --- a/test/redis_client/config_test.rb +++ b/test/redis_client/config_test.rb @@ -218,5 +218,23 @@ def test_custom_field config = Config.new(custom: { foo: "bar" }) assert_equal({ foo: "bar" }, config.custom) end + + def test_callable_password + called = 0 + argument = nil + password_callable = lambda do |username| + called += 1 + argument = username + username.upcase.reverse + end + config = Config.new( + username: "george", + password: password_callable, + ) + + assert_equal [%w(HELLO 3 AUTH george EGROEG)], config.connection_prelude + assert_equal 1, called + assert_equal "george", argument + end end end