From 0351b8be368ace348f8ac54ab41435266a2e3ac2 Mon Sep 17 00:00:00 2001 From: Brian Kephart Date: Sun, 13 May 2018 18:18:50 -0500 Subject: [PATCH] Support ActiveSupport::RedisCacheStore --- CHANGELOG.md | 2 ++ lib/rack/attack.rb | 25 ++++++++-------- lib/rack/attack/store_proxy.rb | 4 +-- .../store_proxy/redis_cache_store_proxy.rb | 30 +++++++++++++++++++ spec/integration/rack_attack_cache_spec.rb | 5 +++- 5 files changed, 51 insertions(+), 15 deletions(-) create mode 100644 lib/rack/attack/store_proxy/redis_cache_store_proxy.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b93015d..e09d049b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +- Adds support for ActiveSupport::RedisCacheStore + ## [5.2.0] - 2018-03-29 ### Added diff --git a/lib/rack/attack.rb b/lib/rack/attack.rb index c7596ef0..a9cf5dd4 100644 --- a/lib/rack/attack.rb +++ b/lib/rack/attack.rb @@ -8,18 +8,19 @@ class Rack::Attack class MisconfiguredStoreError < StandardError; end class MissingStoreError < StandardError; end - autoload :Cache, 'rack/attack/cache' - autoload :Check, 'rack/attack/check' - autoload :Throttle, 'rack/attack/throttle' - autoload :Safelist, 'rack/attack/safelist' - autoload :Blocklist, 'rack/attack/blocklist' - autoload :Track, 'rack/attack/track' - autoload :StoreProxy, 'rack/attack/store_proxy' - autoload :DalliProxy, 'rack/attack/store_proxy/dalli_proxy' - autoload :MemCacheProxy, 'rack/attack/store_proxy/mem_cache_proxy' - autoload :RedisStoreProxy, 'rack/attack/store_proxy/redis_store_proxy' - autoload :Fail2Ban, 'rack/attack/fail2ban' - autoload :Allow2Ban, 'rack/attack/allow2ban' + autoload :Cache, 'rack/attack/cache' + autoload :Check, 'rack/attack/check' + autoload :Throttle, 'rack/attack/throttle' + autoload :Safelist, 'rack/attack/safelist' + autoload :Blocklist, 'rack/attack/blocklist' + autoload :Track, 'rack/attack/track' + autoload :StoreProxy, 'rack/attack/store_proxy' + autoload :DalliProxy, 'rack/attack/store_proxy/dalli_proxy' + autoload :MemCacheProxy, 'rack/attack/store_proxy/mem_cache_proxy' + autoload :RedisStoreProxy, 'rack/attack/store_proxy/redis_store_proxy' + autoload :RedisCacheStoreProxy, 'rack/attack/store_proxy/redis_cache_store_proxy' + autoload :Fail2Ban, 'rack/attack/fail2ban' + autoload :Allow2Ban, 'rack/attack/allow2ban' class << self attr_accessor :notifier, :blocklisted_response, :throttled_response diff --git a/lib/rack/attack/store_proxy.rb b/lib/rack/attack/store_proxy.rb index d83cab98..62a7e684 100644 --- a/lib/rack/attack/store_proxy.rb +++ b/lib/rack/attack/store_proxy.rb @@ -1,9 +1,9 @@ module Rack class Attack module StoreProxy - PROXIES = [DalliProxy, MemCacheProxy, RedisStoreProxy].freeze + PROXIES = [DalliProxy, MemCacheProxy, RedisStoreProxy, RedisCacheStoreProxy].freeze - ACTIVE_SUPPORT_WRAPPER_CLASSES = Set.new(['ActiveSupport::Cache::MemCacheStore', 'ActiveSupport::Cache::RedisStore']).freeze + ACTIVE_SUPPORT_WRAPPER_CLASSES = Set.new(['ActiveSupport::Cache::MemCacheStore', 'ActiveSupport::Cache::RedisStore', 'ActiveSupport::Cache::RedisCacheStore']).freeze ACTIVE_SUPPORT_CLIENTS = Set.new(['Redis::Store', 'Dalli::Client', 'MemCache']).freeze def self.build(store) diff --git a/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb b/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb new file mode 100644 index 00000000..319445e2 --- /dev/null +++ b/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb @@ -0,0 +1,30 @@ +require 'delegate' + +module Rack + class Attack + module StoreProxy + class RedisCacheStoreProxy < SimpleDelegator + def self.handle?(store) + defined?(::ActiveSupport::Cache::RedisCacheStore) && store.is_a?(::ActiveSupport::Cache::RedisCacheStore) + end + + def increment(name, amount, options = {}) + # Redis doesn't check expiration on the INCRBY command. See https://redis.io/commands/expire + count = redis.pipelined do + redis.incrby(name, amount) + redis.expire(name, options[:expires_in]) if options[:expires_in] + end + count.first + end + + def read(name, options = {}) + super(name, options.merge!({ raw: true })) + end + + def write(name, value, options = {}) + super(name, value, options.merge!({ raw: true })) + end + end + end + end +end diff --git a/spec/integration/rack_attack_cache_spec.rb b/spec/integration/rack_attack_cache_spec.rb index 86e40237..2e9f9e55 100644 --- a/spec/integration/rack_attack_cache_spec.rb +++ b/spec/integration/rack_attack_cache_spec.rb @@ -2,7 +2,7 @@ describe Rack::Attack::Cache do # A convenience method for deleting a key from cache. - # Slightly differnet than @cache.delete, which adds a prefix. + # Slightly different than @cache.delete, which adds a prefix. def delete(key) if @cache.store.respond_to?(:delete) @cache.store.delete(key) @@ -18,6 +18,7 @@ def sleep_until_expired require 'active_support/cache/dalli_store' require 'active_support/cache/mem_cache_store' require 'active_support/cache/redis_store' + require 'active_support/cache/redis_cache_store' if ActiveSupport.version.to_s.to_f >= 5.2 require 'connection_pool' cache_stores = [ @@ -30,6 +31,8 @@ def sleep_until_expired Redis::Store.new ] + cache_stores << ActiveSupport::Cache::RedisCacheStore.new if defined?(ActiveSupport::Cache::RedisCacheStore) + cache_stores.each do |store| store = Rack::Attack::StoreProxy.build(store)