Fix reset! when using namespaced cache store (#673)

This commit is contained in:
Santiago Bartesaghi 2024-12-19 23:46:53 -03:00 committed by GitHub
parent 8d5b210fcd
commit 86650c4f7e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 97 additions and 24 deletions

View file

@ -55,7 +55,7 @@ module Rack
def reset! def reset!
if store.respond_to?(:delete_matched) if store.respond_to?(:delete_matched)
store.delete_matched("#{prefix}*") store.delete_matched(/#{prefix}*/)
else else
raise( raise(
Rack::Attack::IncompatibleStoreError, Rack::Attack::IncompatibleStoreError,

View file

@ -33,6 +33,10 @@ module Rack
def write(name, value, options = {}) def write(name, value, options = {})
super(name, value, options.merge!(raw: true)) super(name, value, options.merge!(raw: true))
end end
def delete_matched(matcher, options = nil)
super(matcher.source, options)
end
end end
end end
end end

View file

@ -45,11 +45,12 @@ module Rack
def delete_matched(matcher, _options = nil) def delete_matched(matcher, _options = nil)
cursor = "0" cursor = "0"
source = matcher.source
rescuing do rescuing do
# Fetch keys in batches using SCAN to avoid blocking the Redis server. # Fetch keys in batches using SCAN to avoid blocking the Redis server.
loop do loop do
cursor, keys = scan(cursor, match: matcher, count: 1000) cursor, keys = scan(cursor, match: source, count: 1000)
del(*keys) unless keys.empty? del(*keys) unless keys.empty?
break if cursor == "0" break if cursor == "0"
end end

View file

@ -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

View file

@ -103,26 +103,4 @@ describe 'Rack::Attack' do
end end
end 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 end