From 64058349fd6d8ad5aaf2cb12228c7b6697ca551a Mon Sep 17 00:00:00 2001 From: Santiago Bartesaghi Date: Mon, 4 Nov 2024 10:50:26 -0300 Subject: [PATCH] Fix reset! when using namespaced cache store --- lib/rack/attack/cache.rb | 2 +- .../store_proxy/redis_cache_store_proxy.rb | 4 + lib/rack/attack/store_proxy/redis_proxy.rb | 3 +- spec/rack_attack_reset_spec.rb | 90 +++++++++++++++++++ spec/rack_attack_spec.rb | 22 ----- 5 files changed, 97 insertions(+), 24 deletions(-) create mode 100644 spec/rack_attack_reset_spec.rb diff --git a/lib/rack/attack/cache.rb b/lib/rack/attack/cache.rb index ecbd3368..9111ab8a 100644 --- a/lib/rack/attack/cache.rb +++ b/lib/rack/attack/cache.rb @@ -55,7 +55,7 @@ def delete(unprefixed_key) def reset! if store.respond_to?(:delete_matched) - store.delete_matched("#{prefix}*") + store.delete_matched(/#{prefix}*/) else raise( Rack::Attack::IncompatibleStoreError, diff --git a/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb b/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb index 4fe42002..74f665b5 100644 --- a/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb +++ b/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb @@ -33,6 +33,10 @@ def read(name, options = {}) def write(name, value, options = {}) super(name, value, options.merge!(raw: true)) end + + def delete_matched(matcher, options = nil) + super(matcher.source, options) + end end end end diff --git a/lib/rack/attack/store_proxy/redis_proxy.rb b/lib/rack/attack/store_proxy/redis_proxy.rb index 830d39de..599213ae 100644 --- a/lib/rack/attack/store_proxy/redis_proxy.rb +++ b/lib/rack/attack/store_proxy/redis_proxy.rb @@ -45,11 +45,12 @@ def delete(key, _options = {}) def delete_matched(matcher, _options = nil) cursor = "0" + source = matcher.source rescuing do # Fetch keys in batches using SCAN to avoid blocking the Redis server. loop do - cursor, keys = scan(cursor, match: matcher, count: 1000) + cursor, keys = scan(cursor, match: source, count: 1000) del(*keys) unless keys.empty? break if cursor == "0" end diff --git a/spec/rack_attack_reset_spec.rb b/spec/rack_attack_reset_spec.rb new file mode 100644 index 00000000..b9a94e39 --- /dev/null +++ b/spec/rack_attack_reset_spec.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +require_relative "spec_helper" + +describe "Rack::Attack.reset!" do + it "raises an error when is not supported by cache store" do + Rack::Attack.cache.store = Class.new + assert_raises(Rack::Attack::IncompatibleStoreError) do + Rack::Attack.reset! + end + end + + if defined?(Redis) + it "should delete rack attack keys" do + redis = Redis.new + redis.set("key", "value") + redis.set("#{Rack::Attack.cache.prefix}::key", "value") + Rack::Attack.cache.store = redis + Rack::Attack.reset! + + _(redis.get("key")).must_equal "value" + _(redis.get("#{Rack::Attack.cache.prefix}::key")).must_be_nil + end + end + + if defined?(Redis::Store) + it "should delete rack attack keys" do + redis_store = Redis::Store.new + redis_store.set("key", "value") + redis_store.set("#{Rack::Attack.cache.prefix}::key", "value") + Rack::Attack.cache.store = redis_store + Rack::Attack.reset! + + _(redis_store.get("key")).must_equal "value" + _(redis_store.get("#{Rack::Attack.cache.prefix}::key")).must_be_nil + end + end + + if defined?(Redis) && defined?(ActiveSupport::Cache::RedisCacheStore) + it "should delete rack attack keys" do + redis_cache_store = ActiveSupport::Cache::RedisCacheStore.new + redis_cache_store.write("key", "value") + redis_cache_store.write("#{Rack::Attack.cache.prefix}::key", "value") + Rack::Attack.cache.store = redis_cache_store + Rack::Attack.reset! + + _(redis_cache_store.read("key")).must_equal "value" + _(redis_cache_store.read("#{Rack::Attack.cache.prefix}::key")).must_be_nil + end + + describe "with a namespaced cache" do + it "should delete rack attack keys" do + redis_cache_store = ActiveSupport::Cache::RedisCacheStore.new(namespace: "ns") + redis_cache_store.write("key", "value") + redis_cache_store.write("#{Rack::Attack.cache.prefix}::key", "value") + Rack::Attack.cache.store = redis_cache_store + Rack::Attack.reset! + + _(redis_cache_store.read("key")).must_equal "value" + _(redis_cache_store.read("#{Rack::Attack.cache.prefix}::key")).must_be_nil + end + end + end + + if defined?(ActiveSupport::Cache::MemoryStore) + it "should delete rack attack keys" do + memory_store = ActiveSupport::Cache::MemoryStore.new + memory_store.write("key", "value") + memory_store.write("#{Rack::Attack.cache.prefix}::key", "value") + Rack::Attack.cache.store = memory_store + Rack::Attack.reset! + + _(memory_store.read("key")).must_equal "value" + _(memory_store.read("#{Rack::Attack.cache.prefix}::key")).must_be_nil + end + + describe "with a namespaced cache" do + it "should delete rack attack keys" do + memory_store = ActiveSupport::Cache::MemoryStore.new(namespace: "ns") + memory_store.write("key", "value") + memory_store.write("#{Rack::Attack.cache.prefix}::key", "value") + Rack::Attack.cache.store = memory_store + Rack::Attack.reset! + + _(memory_store.read("key")).must_equal "value" + _(memory_store.read("#{Rack::Attack.cache.prefix}::key")).must_be_nil + end + end + end +end diff --git a/spec/rack_attack_spec.rb b/spec/rack_attack_spec.rb index 40936017..acc210a6 100644 --- a/spec/rack_attack_spec.rb +++ b/spec/rack_attack_spec.rb @@ -103,26 +103,4 @@ end end end - - describe 'reset!' do - it 'raises an error when is not supported by cache store' do - Rack::Attack.cache.store = Class.new - assert_raises(Rack::Attack::IncompatibleStoreError) do - Rack::Attack.reset! - end - end - - if defined?(Redis) - it 'should delete rack attack keys' do - redis = Redis.new - redis.set('key', 'value') - redis.set("#{Rack::Attack.cache.prefix}::key", 'value') - Rack::Attack.cache.store = redis - Rack::Attack.reset! - - _(redis.get('key')).must_equal 'value' - _(redis.get("#{Rack::Attack.cache.prefix}::key")).must_be_nil - end - end - end end