diff --git a/Appraisals b/Appraisals index 9ca0281a..6114f5cc 100644 --- a/Appraisals +++ b/Appraisals @@ -42,26 +42,41 @@ end appraise "active_support_8-1_redis_cache_store" do gem "activesupport", "~> 8.1.0" + # Direct version requirement on connection_pool + # can be removed once https://github.com/rails/rails#56291 is fixed and released + gem "connection_pool", "~> 2.5" gem "redis", "~> 5.0" end appraise "active_support_8-0_redis_cache_store" do gem "activesupport", "~> 8.0.0" + # Direct version requirement on connection_pool + # can be removed once https://github.com/rails/rails#56291 is fixed and released + gem "connection_pool", "~> 2.5" gem "redis", "~> 5.0" end appraise "active_support_7-2_redis_cache_store" do gem "activesupport", "~> 7.2.0" + # Direct version requirement on connection_pool + # can be removed once https://github.com/rails/rails#56291 is fixed and released + gem "connection_pool", "~> 2.5" gem "redis", "~> 5.0" end appraise "active_support_7-1_redis_cache_store" do gem "activesupport", "~> 7.1.0" + # Direct version requirement on connection_pool + # can be removed once https://github.com/rails/rails#56291 is fixed and released + gem "connection_pool", "~> 2.5" gem "redis", "~> 5.0" end appraise "active_support_7-0_redis_cache_store" do gem "activesupport", "~> 7.0.0" + # Direct version requirement on connection_pool + # can be removed once https://github.com/rails/rails#56291 is fixed and released + gem "connection_pool", "~> 2.5" gem "redis", "~> 5.0" end diff --git a/gemfiles/active_support_7_0_redis_cache_store.gemfile b/gemfiles/active_support_7_0_redis_cache_store.gemfile index a94cfe88..f4e44cb7 100644 --- a/gemfiles/active_support_7_0_redis_cache_store.gemfile +++ b/gemfiles/active_support_7_0_redis_cache_store.gemfile @@ -3,6 +3,7 @@ source "https://rubygems.org" gem "activesupport", "~> 7.0.0" +gem "connection_pool", "~> 2.5" gem "redis", "~> 5.0" group :maintenance, optional: true do diff --git a/gemfiles/active_support_7_1_redis_cache_store.gemfile b/gemfiles/active_support_7_1_redis_cache_store.gemfile index a0602ba5..f0b0ae68 100644 --- a/gemfiles/active_support_7_1_redis_cache_store.gemfile +++ b/gemfiles/active_support_7_1_redis_cache_store.gemfile @@ -3,6 +3,7 @@ source "https://rubygems.org" gem "activesupport", "~> 7.1.0" +gem "connection_pool", "~> 2.5" gem "redis", "~> 5.0" group :maintenance, optional: true do diff --git a/gemfiles/active_support_7_2_redis_cache_store.gemfile b/gemfiles/active_support_7_2_redis_cache_store.gemfile index 3c433092..f0ec3a90 100644 --- a/gemfiles/active_support_7_2_redis_cache_store.gemfile +++ b/gemfiles/active_support_7_2_redis_cache_store.gemfile @@ -3,6 +3,7 @@ source "https://rubygems.org" gem "activesupport", "~> 7.2.0" +gem "connection_pool", "~> 2.5" gem "redis", "~> 5.0" group :maintenance, optional: true do diff --git a/gemfiles/active_support_8_0_redis_cache_store.gemfile b/gemfiles/active_support_8_0_redis_cache_store.gemfile index b813cb38..e192fda3 100644 --- a/gemfiles/active_support_8_0_redis_cache_store.gemfile +++ b/gemfiles/active_support_8_0_redis_cache_store.gemfile @@ -3,6 +3,7 @@ source "https://rubygems.org" gem "activesupport", "~> 8.0.0" +gem "connection_pool", "~> 2.5" gem "redis", "~> 5.0" group :maintenance, optional: true do diff --git a/gemfiles/active_support_8_1_redis_cache_store.gemfile b/gemfiles/active_support_8_1_redis_cache_store.gemfile index 57e0ff21..1dd14ed8 100644 --- a/gemfiles/active_support_8_1_redis_cache_store.gemfile +++ b/gemfiles/active_support_8_1_redis_cache_store.gemfile @@ -3,6 +3,7 @@ source "https://rubygems.org" gem "activesupport", "~> 8.1.0" +gem "connection_pool", "~> 2.5" gem "redis", "~> 5.0" group :maintenance, optional: true do diff --git a/rack-attack.gemspec b/rack-attack.gemspec index 4d03a71b..03efa98a 100644 --- a/rack-attack.gemspec +++ b/rack-attack.gemspec @@ -44,9 +44,4 @@ Gem::Specification.new do |s| if RUBY_ENGINE == "ruby" s.add_development_dependency 'byebug', '~> 11.0' end - - s.add_development_dependency "activesupport" - # Fix activesupport Direct version requirement on connection_pool - # can be removed once https://github.com/rails/rails/issues/56291 is ixed and released - s.add_development_dependency "connection_pool", "~> 2.5" end diff --git a/spec/acceptance/allow2ban_spec.rb b/spec/acceptance/allow2ban_spec.rb index 6de18d07..b4670ab8 100644 --- a/spec/acceptance/allow2ban_spec.rb +++ b/spec/acceptance/allow2ban_spec.rb @@ -5,7 +5,7 @@ describe "allow2ban" do before do - Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new + Rack::Attack.cache.store = SimpleMemoryStore.new Rack::Attack.blocklist("allow2ban pentesters") do |request| Rack::Attack::Allow2Ban.filter(request.ip, maxretry: 2, findtime: 30, bantime: 60) do diff --git a/spec/acceptance/blocking_ip_spec.rb b/spec/acceptance/blocking_ip_spec.rb index 4e41f29b..ff3d8dab 100644 --- a/spec/acceptance/blocking_ip_spec.rb +++ b/spec/acceptance/blocking_ip_spec.rb @@ -27,19 +27,21 @@ assert_equal 200, last_response.status end - it "notifies when the request is blocked" do - ActiveSupport::Notifications.subscribe("blocklist.rack_attack") do |_name, _start, _finish, _id, payload| - notifications.push(payload) - end + if defined?(::ActiveSupport::Notifications) + it "notifies when the request is blocked" do + ActiveSupport::Notifications.subscribe("blocklist.rack_attack") do |_name, _start, _finish, _id, payload| + notifications.push(payload) + end - get "/", {}, "REMOTE_ADDR" => "5.6.7.8" + get "/", {}, "REMOTE_ADDR" => "5.6.7.8" - assert notifications.empty? + assert notifications.empty? - get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" - assert_equal 1, notifications.size - notification = notifications.pop - assert_equal :blocklist, notification[:request].env["rack.attack.match_type"] + assert_equal 1, notifications.size + notification = notifications.pop + assert_equal :blocklist, notification[:request].env["rack.attack.match_type"] + end end end diff --git a/spec/acceptance/blocking_spec.rb b/spec/acceptance/blocking_spec.rb index 2ea9f67a..3e4e0846 100644 --- a/spec/acceptance/blocking_spec.rb +++ b/spec/acceptance/blocking_spec.rb @@ -23,21 +23,23 @@ assert_equal 200, last_response.status end - it "notifies when the request is blocked" do - ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, payload| - notifications.push(payload) - end + if defined?(::ActiveSupport::Notifications) + it "notifies when the request is blocked" do + ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, payload| + notifications.push(payload) + end - get "/", {}, "REMOTE_ADDR" => "5.6.7.8" + get "/", {}, "REMOTE_ADDR" => "5.6.7.8" - assert notifications.empty? + assert notifications.empty? - get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" - assert_equal 1, notifications.size - notification = notifications.pop - assert_nil notification[:request].env["rack.attack.matched"] - assert_equal :blocklist, notification[:request].env["rack.attack.match_type"] + assert_equal 1, notifications.size + notification = notifications.pop + assert_nil notification[:request].env["rack.attack.matched"] + assert_equal :blocklist, notification[:request].env["rack.attack.match_type"] + end end end @@ -62,20 +64,22 @@ assert_equal 200, last_response.status end - it "notifies when the request is blocked" do - ActiveSupport::Notifications.subscribe("blocklist.rack_attack") do |_name, _start, _finish, _id, payload| - notifications.push(payload) - end + if defined?(::ActiveSupport::Notifications) + it "notifies when the request is blocked" do + ActiveSupport::Notifications.subscribe("blocklist.rack_attack") do |_name, _start, _finish, _id, payload| + notifications.push(payload) + end - get "/", {}, "REMOTE_ADDR" => "5.6.7.8" + get "/", {}, "REMOTE_ADDR" => "5.6.7.8" - assert notifications.empty? + assert notifications.empty? - get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" - assert_equal 1, notifications.size - notification = notifications.pop - assert_equal "block 1.2.3.4", notification[:request].env["rack.attack.matched"] - assert_equal :blocklist, notification[:request].env["rack.attack.match_type"] + assert_equal 1, notifications.size + notification = notifications.pop + assert_equal "block 1.2.3.4", notification[:request].env["rack.attack.matched"] + assert_equal :blocklist, notification[:request].env["rack.attack.match_type"] + end end end diff --git a/spec/acceptance/blocking_subnet_spec.rb b/spec/acceptance/blocking_subnet_spec.rb index 9fe30598..b89a17d7 100644 --- a/spec/acceptance/blocking_subnet_spec.rb +++ b/spec/acceptance/blocking_subnet_spec.rb @@ -27,19 +27,21 @@ assert_equal 200, last_response.status end - it "notifies when the request is blocked" do - ActiveSupport::Notifications.subscribe("blocklist.rack_attack") do |_name, _start, _finish, _id, payload| - notifications.push(payload) - end + if defined?(::ActiveSupport::Notifications) + it "notifies when the request is blocked" do + ActiveSupport::Notifications.subscribe("blocklist.rack_attack") do |_name, _start, _finish, _id, payload| + notifications.push(payload) + end - get "/", {}, "REMOTE_ADDR" => "5.6.7.8" + get "/", {}, "REMOTE_ADDR" => "5.6.7.8" - assert notifications.empty? + assert notifications.empty? - get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" - assert_equal 1, notifications.size - notification = notifications.pop - assert_equal :blocklist, notification[:request].env["rack.attack.match_type"] + assert_equal 1, notifications.size + notification = notifications.pop + assert_equal :blocklist, notification[:request].env["rack.attack.match_type"] + end end end diff --git a/spec/acceptance/cache_store_config_with_rails_spec.rb b/spec/acceptance/cache_store_config_with_rails_spec.rb index 66bb76a8..23cb1046 100644 --- a/spec/acceptance/cache_store_config_with_rails_spec.rb +++ b/spec/acceptance/cache_store_config_with_rails_spec.rb @@ -4,32 +4,34 @@ require "minitest/stub_const" require "ostruct" -describe "Cache store config with Rails" do - before do - Rack::Attack.throttle("by ip", limit: 1, period: 60) do |request| - request.ip +if defined?(::ActiveSupport::Cache::MemoryStore) + describe "Cache store config with Rails" do + before do + Rack::Attack.throttle("by ip", limit: 1, period: 60) do |request| + request.ip + end end - end - unless defined?(Rails) - it "fails when Rails.cache is not set" do - Object.stub_const(:Rails, OpenStruct.new(cache: nil)) do - assert_raises(Rack::Attack::MissingStoreError) do - get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + unless defined?(Rails) + it "fails when Rails.cache is not set" do + Object.stub_const(:Rails, OpenStruct.new(cache: nil)) do + assert_raises(Rack::Attack::MissingStoreError) do + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + end end end end - end - it "works when Rails.cache is set" do - Object.stub_const(:Rails, OpenStruct.new(cache: ActiveSupport::Cache::MemoryStore.new)) do - get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + it "works when Rails.cache is set" do + Object.stub_const(:Rails, OpenStruct.new(cache: ActiveSupport::Cache::MemoryStore.new)) do + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" - assert_equal 200, last_response.status + assert_equal 200, last_response.status - get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" - assert_equal 429, last_response.status + assert_equal 429, last_response.status + end end end end diff --git a/spec/acceptance/customizing_throttled_response_spec.rb b/spec/acceptance/customizing_throttled_response_spec.rb index 0990975e..1f27adbd 100644 --- a/spec/acceptance/customizing_throttled_response_spec.rb +++ b/spec/acceptance/customizing_throttled_response_spec.rb @@ -4,7 +4,7 @@ describe "Customizing throttled response" do before do - Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new + Rack::Attack.cache.store = SimpleMemoryStore.new Rack::Attack.throttle("by ip", limit: 1, period: 60) do |request| request.ip diff --git a/spec/acceptance/fail2ban_spec.rb b/spec/acceptance/fail2ban_spec.rb index 74c01f57..7ebe5bec 100644 --- a/spec/acceptance/fail2ban_spec.rb +++ b/spec/acceptance/fail2ban_spec.rb @@ -7,7 +7,7 @@ let(:notifications) { [] } before do - Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new + Rack::Attack.cache.store = SimpleMemoryStore.new Rack::Attack.blocklist("fail2ban pentesters") do |request| Rack::Attack::Fail2Ban.filter(request.ip, maxretry: 2, findtime: 30, bantime: 60) do @@ -78,43 +78,45 @@ end end - it "notifies when the request is blocked" do - ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, payload| - notifications.push(payload) - end + if defined?(::ActiveSupport::Notifications) + it "notifies when the request is blocked" do + ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, payload| + notifications.push(payload) + end - get "/" + get "/" - assert_equal 200, last_response.status - assert notifications.empty? + assert_equal 200, last_response.status + assert notifications.empty? - get "/private-place" + get "/private-place" - assert_equal 403, last_response.status - assert_equal 1, notifications.size - notification = notifications.pop - assert_equal 'fail2ban pentesters', notification[:request].env["rack.attack.matched"] - assert_equal :blocklist, notification[:request].env["rack.attack.match_type"] + assert_equal 403, last_response.status + assert_equal 1, notifications.size + notification = notifications.pop + assert_equal 'fail2ban pentesters', notification[:request].env["rack.attack.matched"] + assert_equal :blocklist, notification[:request].env["rack.attack.match_type"] - get "/" + get "/" - assert_equal 200, last_response.status - assert notifications.empty? + assert_equal 200, last_response.status + assert notifications.empty? - get "/private-place" + get "/private-place" - assert_equal 403, last_response.status - assert_equal 1, notifications.size - notification = notifications.pop - assert_equal 'fail2ban pentesters', notification[:request].env["rack.attack.matched"] - assert_equal :blocklist, notification[:request].env["rack.attack.match_type"] + assert_equal 403, last_response.status + assert_equal 1, notifications.size + notification = notifications.pop + assert_equal 'fail2ban pentesters', notification[:request].env["rack.attack.matched"] + assert_equal :blocklist, notification[:request].env["rack.attack.match_type"] - get "/" + get "/" - assert_equal 403, last_response.status - assert_equal 1, notifications.size - notification = notifications.pop - assert_equal 'fail2ban pentesters', notification[:request].env["rack.attack.matched"] - assert_equal :blocklist, notification[:request].env["rack.attack.match_type"] + assert_equal 403, last_response.status + assert_equal 1, notifications.size + notification = notifications.pop + assert_equal 'fail2ban pentesters', notification[:request].env["rack.attack.matched"] + assert_equal :blocklist, notification[:request].env["rack.attack.match_type"] + end end end diff --git a/spec/acceptance/safelisting_ip_spec.rb b/spec/acceptance/safelisting_ip_spec.rb index e04ca6e5..fd7cc8ce 100644 --- a/spec/acceptance/safelisting_ip_spec.rb +++ b/spec/acceptance/safelisting_ip_spec.rb @@ -43,16 +43,18 @@ assert_equal 200, last_response.status end - it "notifies when the request is safe" do - ActiveSupport::Notifications.subscribe("safelist.rack_attack") do |_name, _start, _finish, _id, payload| - notifications.push(payload) + if defined?(::ActiveSupport::Notifications) + it "notifies when the request is safe" do + ActiveSupport::Notifications.subscribe("safelist.rack_attack") do |_name, _start, _finish, _id, payload| + notifications.push(payload) + end + + get "/admin", {}, "REMOTE_ADDR" => "5.6.7.8" + + assert_equal 200, last_response.status + assert_equal 1, notifications.size + notification = notifications.pop + assert_equal :safelist, notification[:request].env["rack.attack.match_type"] end - - get "/admin", {}, "REMOTE_ADDR" => "5.6.7.8" - - assert_equal 200, last_response.status - assert_equal 1, notifications.size - notification = notifications.pop - assert_equal :safelist, notification[:request].env["rack.attack.match_type"] end end diff --git a/spec/acceptance/safelisting_spec.rb b/spec/acceptance/safelisting_spec.rb index f00fada7..810b24d5 100644 --- a/spec/acceptance/safelisting_spec.rb +++ b/spec/acceptance/safelisting_spec.rb @@ -39,18 +39,20 @@ assert_equal 200, last_response.status end - it "notifies when the request is safe" do - ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, payload| - notifications.push(payload) + if defined?(::ActiveSupport::Notifications) + it "notifies when the request is safe" do + ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, payload| + notifications.push(payload) + end + + get "/safe_space", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal 200, last_response.status + assert_equal 1, notifications.size + notification = notifications.pop + assert_nil notification[:request].env["rack.attack.matched"] + assert_equal :safelist, notification[:request].env["rack.attack.match_type"] end - - get "/safe_space", {}, "REMOTE_ADDR" => "1.2.3.4" - - assert_equal 200, last_response.status - assert_equal 1, notifications.size - notification = notifications.pop - assert_nil notification[:request].env["rack.attack.matched"] - assert_equal :safelist, notification[:request].env["rack.attack.match_type"] end end @@ -91,17 +93,19 @@ assert_equal 200, last_response.status end - it "notifies when the request is safe" do - ActiveSupport::Notifications.subscribe("safelist.rack_attack") do |_name, _start, _finish, _id, payload| - notifications.push(payload) - end + if defined?(::ActiveSupport::Notifications) + it "notifies when the request is safe" do + ActiveSupport::Notifications.subscribe("safelist.rack_attack") do |_name, _start, _finish, _id, payload| + notifications.push(payload) + end - get "/safe_space", {}, "REMOTE_ADDR" => "1.2.3.4" + get "/safe_space", {}, "REMOTE_ADDR" => "1.2.3.4" - assert_equal 200, last_response.status - assert_equal 1, notifications.size - notification = notifications.pop - assert_equal "safe path", notification[:request].env["rack.attack.matched"] - assert_equal :safelist, notification[:request].env["rack.attack.match_type"] + assert_equal 200, last_response.status + assert_equal 1, notifications.size + notification = notifications.pop + assert_equal "safe path", notification[:request].env["rack.attack.matched"] + assert_equal :safelist, notification[:request].env["rack.attack.match_type"] + end end end diff --git a/spec/acceptance/safelisting_subnet_spec.rb b/spec/acceptance/safelisting_subnet_spec.rb index baeb7e46..f56cb0cc 100644 --- a/spec/acceptance/safelisting_subnet_spec.rb +++ b/spec/acceptance/safelisting_subnet_spec.rb @@ -37,16 +37,18 @@ assert_equal 200, last_response.status end - it "notifies when the request is safe" do - ActiveSupport::Notifications.subscribe("safelist.rack_attack") do |_name, _start, _finish, _id, payload| - notifications.push(payload) + if defined?(::ActiveSupport::Notifications) + it "notifies when the request is safe" do + ActiveSupport::Notifications.subscribe("safelist.rack_attack") do |_name, _start, _finish, _id, payload| + notifications.push(payload) + end + + get "/admin", {}, "REMOTE_ADDR" => "5.6.0.0" + + assert_equal 200, last_response.status + assert_equal 1, notifications.size + notification = notifications.pop + assert_equal :safelist, notification[:request].env["rack.attack.match_type"] end - - get "/admin", {}, "REMOTE_ADDR" => "5.6.0.0" - - assert_equal 200, last_response.status - assert_equal 1, notifications.size - notification = notifications.pop - assert_equal :safelist, notification[:request].env["rack.attack.match_type"] end end diff --git a/spec/acceptance/stores/active_support_mem_cache_store_spec.rb b/spec/acceptance/stores/active_support_mem_cache_store_spec.rb index ccfe3db7..f997efc6 100644 --- a/spec/acceptance/stores/active_support_mem_cache_store_spec.rb +++ b/spec/acceptance/stores/active_support_mem_cache_store_spec.rb @@ -2,7 +2,7 @@ require_relative "../../spec_helper" -if defined?(::Dalli) +if defined?(::Dalli) && defined?(::ActiveSupport::Cache::MemCacheStore) require_relative "../../support/cache_store_helper" describe "ActiveSupport::Cache::MemCacheStore as a cache backend" do diff --git a/spec/acceptance/stores/active_support_memory_store_spec.rb b/spec/acceptance/stores/active_support_memory_store_spec.rb index 4ed81e7f..4a219b82 100644 --- a/spec/acceptance/stores/active_support_memory_store_spec.rb +++ b/spec/acceptance/stores/active_support_memory_store_spec.rb @@ -1,16 +1,19 @@ # frozen_string_literal: true require_relative "../../spec_helper" -require_relative "../../support/cache_store_helper" -describe "ActiveSupport::Cache::MemoryStore as a cache backend" do - before do - Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new - end +if defined?(::ActiveSupport::Cache::MemoryStore) + require_relative "../../support/cache_store_helper" - after do - Rack::Attack.cache.store.clear - end + describe "ActiveSupport::Cache::MemoryStore as a cache backend" do + before do + Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new + end - it_works_for_cache_backed_features(fetch_from_store: ->(key) { Rack::Attack.cache.store.fetch(key) }) + after do + Rack::Attack.cache.store.clear + end + + it_works_for_cache_backed_features(fetch_from_store: ->(key) { Rack::Attack.cache.store.fetch(key) }) + end end diff --git a/spec/acceptance/stores/dalli_client_spec.rb b/spec/acceptance/stores/dalli_client_spec.rb index 2b273740..f213a030 100644 --- a/spec/acceptance/stores/dalli_client_spec.rb +++ b/spec/acceptance/stores/dalli_client_spec.rb @@ -18,17 +18,19 @@ it_works_for_cache_backed_features(fetch_from_store: ->(key) { Rack::Attack.cache.store.fetch(key) }) end - describe "ConnectionPool with Dalli::Client as a cache backend" do - before do - Rack::Attack.cache.store = ConnectionPool.new { Dalli::Client.new } + if defined?(::ConnectionPool) + describe "ConnectionPool with Dalli::Client as a cache backend" do + before do + Rack::Attack.cache.store = ConnectionPool.new { Dalli::Client.new } + end + + after do + Rack::Attack.cache.store.with { |client| client.flush_all } + end + + it_works_for_cache_backed_features( + fetch_from_store: ->(key) { Rack::Attack.cache.store.with { |client| client.fetch(key) } } + ) end - - after do - Rack::Attack.cache.store.with { |client| client.flush_all } - end - - it_works_for_cache_backed_features( - fetch_from_store: ->(key) { Rack::Attack.cache.store.with { |client| client.fetch(key) } } - ) end end diff --git a/spec/acceptance/throttling_spec.rb b/spec/acceptance/throttling_spec.rb index ca81d1c5..475b2090 100644 --- a/spec/acceptance/throttling_spec.rb +++ b/spec/acceptance/throttling_spec.rb @@ -7,7 +7,7 @@ let(:notifications) { [] } before do - Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new + Rack::Attack.cache.store = SimpleMemoryStore.new end it "allows one request per minute by IP" do @@ -135,36 +135,38 @@ end end - it "notifies when the request is throttled" do - Rack::Attack.throttle("by ip", limit: 1, period: 60) do |request| - request.ip - end + if defined?(::ActiveSupport::Notifications) + it "notifies when the request is throttled" do + Rack::Attack.throttle("by ip", limit: 1, period: 60) do |request| + request.ip + end - ActiveSupport::Notifications.subscribe("throttle.rack_attack") do |_name, _start, _finish, _id, payload| - notifications.push(payload) - end + ActiveSupport::Notifications.subscribe("throttle.rack_attack") do |_name, _start, _finish, _id, payload| + notifications.push(payload) + end - get "/", {}, "REMOTE_ADDR" => "5.6.7.8" + get "/", {}, "REMOTE_ADDR" => "5.6.7.8" - assert_equal 200, last_response.status - assert notifications.empty? + assert_equal 200, last_response.status + assert notifications.empty? - get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" - assert_equal 200, last_response.status - assert notifications.empty? + assert_equal 200, last_response.status + assert notifications.empty? - get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" - assert_equal 429, last_response.status + assert_equal 429, last_response.status - assert_equal 1, notifications.size - notification = notifications.pop - assert_equal "by ip", notification[:request].env["rack.attack.matched"] - assert_equal :throttle, notification[:request].env["rack.attack.match_type"] - assert_equal 60, notification[:request].env["rack.attack.match_data"][:period] - assert_equal 1, notification[:request].env["rack.attack.match_data"][:limit] - assert_equal 2, notification[:request].env["rack.attack.match_data"][:count] - assert_equal "1.2.3.4", notification[:request].env["rack.attack.match_discriminator"] + assert_equal 1, notifications.size + notification = notifications.pop + assert_equal "by ip", notification[:request].env["rack.attack.matched"] + assert_equal :throttle, notification[:request].env["rack.attack.match_type"] + assert_equal 60, notification[:request].env["rack.attack.match_data"][:period] + assert_equal 1, notification[:request].env["rack.attack.match_data"][:limit] + assert_equal 2, notification[:request].env["rack.attack.match_data"][:count] + assert_equal "1.2.3.4", notification[:request].env["rack.attack.match_discriminator"] + end end end diff --git a/spec/acceptance/track_spec.rb b/spec/acceptance/track_spec.rb index 62d1551a..bde2ad7e 100644 --- a/spec/acceptance/track_spec.rb +++ b/spec/acceptance/track_spec.rb @@ -5,24 +5,26 @@ describe "#track" do let(:notifications) { [] } - it "notifies when track block returns true" do - Rack::Attack.track("ip 1.2.3.4") do |request| - request.ip == "1.2.3.4" - end + if defined?(::ActiveSupport::Notifications) + it "notifies when track block returns true" do + Rack::Attack.track("ip 1.2.3.4") do |request| + request.ip == "1.2.3.4" + end - ActiveSupport::Notifications.subscribe("track.rack_attack") do |_name, _start, _finish, _id, payload| - notifications.push(payload) - end + ActiveSupport::Notifications.subscribe("track.rack_attack") do |_name, _start, _finish, _id, payload| + notifications.push(payload) + end - get "/", {}, "REMOTE_ADDR" => "5.6.7.8" + get "/", {}, "REMOTE_ADDR" => "5.6.7.8" - assert notifications.empty? + assert notifications.empty? - get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" - assert_equal 1, notifications.size - notification = notifications.pop - assert_equal "ip 1.2.3.4", notification[:request].env["rack.attack.matched"] - assert_equal :track, notification[:request].env["rack.attack.match_type"] + assert_equal 1, notifications.size + notification = notifications.pop + assert_equal "ip 1.2.3.4", notification[:request].env["rack.attack.matched"] + assert_equal :track, notification[:request].env["rack.attack.match_type"] + end end end diff --git a/spec/acceptance/track_throttle_spec.rb b/spec/acceptance/track_throttle_spec.rb index e6aa553a..ff64fc1f 100644 --- a/spec/acceptance/track_throttle_spec.rb +++ b/spec/acceptance/track_throttle_spec.rb @@ -6,44 +6,46 @@ describe "#track with throttle-ish options" do let(:notifications) { [] } - it "notifies when throttle goes over the limit without actually throttling requests" do - Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new + if defined?(::ActiveSupport::Notifications) + it "notifies when throttle goes over the limit without actually throttling requests" do + Rack::Attack.cache.store = SimpleMemoryStore.new - Rack::Attack.track("by ip", limit: 1, period: 60) do |request| - request.ip - end + Rack::Attack.track("by ip", limit: 1, period: 60) do |request| + request.ip + end - ActiveSupport::Notifications.subscribe("track.rack_attack") do |_name, _start, _finish, _id, payload| - notifications.push(payload) - end + ActiveSupport::Notifications.subscribe("track.rack_attack") do |_name, _start, _finish, _id, payload| + notifications.push(payload) + end - get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" - assert notifications.empty? + assert notifications.empty? - assert_equal 200, last_response.status + assert_equal 200, last_response.status - get "/", {}, "REMOTE_ADDR" => "5.6.7.8" + get "/", {}, "REMOTE_ADDR" => "5.6.7.8" - assert notifications.empty? + assert notifications.empty? - assert_equal 200, last_response.status + assert_equal 200, last_response.status - get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" - assert_equal 1, notifications.size - notification = notifications.pop - assert_equal "by ip", notification[:request].env["rack.attack.matched"] - assert_equal :track, notification[:request].env["rack.attack.match_type"] + assert_equal 1, notifications.size + notification = notifications.pop + assert_equal "by ip", notification[:request].env["rack.attack.matched"] + assert_equal :track, notification[:request].env["rack.attack.match_type"] - assert_equal 200, last_response.status + assert_equal 200, last_response.status - Timecop.travel(60) do - get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + Timecop.travel(60) do + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" - assert notifications.empty? + assert notifications.empty? - assert_equal 200, last_response.status + assert_equal 200, last_response.status + end end end end diff --git a/spec/allow2ban_spec.rb b/spec/allow2ban_spec.rb index 105e0ad0..f0947d0b 100644 --- a/spec/allow2ban_spec.rb +++ b/spec/allow2ban_spec.rb @@ -8,7 +8,7 @@ @cache = Rack::Attack.cache @findtime = 60 @bantime = 60 - Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new + Rack::Attack.cache.store = SimpleMemoryStore.new @f2b_options = { bantime: @bantime, findtime: @findtime, maxretry: 2 } Rack::Attack.blocklist('pentest') do |req| diff --git a/spec/fail2ban_spec.rb b/spec/fail2ban_spec.rb index 6a9d9bcf..aeeea908 100644 --- a/spec/fail2ban_spec.rb +++ b/spec/fail2ban_spec.rb @@ -8,7 +8,7 @@ @cache = Rack::Attack.cache @findtime = 60 @bantime = 60 - Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new + Rack::Attack.cache.store = SimpleMemoryStore.new @f2b_options = { bantime: @bantime, findtime: @findtime, maxretry: 2 } Rack::Attack.blocklist('pentest') do |req| diff --git a/spec/integration/offline_spec.rb b/spec/integration/offline_spec.rb index 85429a42..c480d7e8 100644 --- a/spec/integration/offline_spec.rb +++ b/spec/integration/offline_spec.rb @@ -1,8 +1,13 @@ # frozen_string_literal: true -require 'active_support/cache' require_relative '../spec_helper' +begin + require 'active_support/cache' +rescue LoadError + # ActiveSupport is optional +end + OfflineExamples = Minitest::SharedExamples.new do it 'should write' do @cache.write('cache-test-key', 'foobar', 1) diff --git a/spec/rack_attack_instrumentation_spec.rb b/spec/rack_attack_instrumentation_spec.rb index d2291f77..cae13234 100644 --- a/spec/rack_attack_instrumentation_spec.rb +++ b/spec/rack_attack_instrumentation_spec.rb @@ -1,39 +1,41 @@ # frozen_string_literal: true require_relative "spec_helper" -require 'active_support' -require 'active_support/subscriber' -class CustomSubscriber < ActiveSupport::Subscriber - @notification_count = 0 +require 'active_support/subscriber' +if defined?(::ActiveSupport::Subscriber) - class << self - attr_accessor :notification_count - end + class CustomSubscriber < ActiveSupport::Subscriber + @notification_count = 0 - def throttle(_event) - self.class.notification_count += 1 - end -end + class << self + attr_accessor :notification_count + end -describe 'Rack::Attack.instrument' do - before do - @period = 60 # Use a long period; failures due to cache key rotation less likely - Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new - Rack::Attack.throttle('ip/sec', limit: 1, period: @period) { |req| req.ip } + def throttle(_event) + self.class.notification_count += 1 + end end - describe "with throttling" do + describe 'Rack::Attack.instrument' do before do - ActiveSupport::Notifications.stub(:notifier, ActiveSupport::Notifications::Fanout.new) do - CustomSubscriber.attach_to("rack_attack") - 2.times { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' } - end + @period = 60 # Use a long period; failures due to cache key rotation less likely + Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new + Rack::Attack.throttle('ip/sec', limit: 1, period: @period) { |req| req.ip } end - it 'should instrument without error' do - _(last_response.status).must_equal 429 - assert_equal 1, CustomSubscriber.notification_count + describe "with throttling" do + before do + ActiveSupport::Notifications.stub(:notifier, ActiveSupport::Notifications::Fanout.new) do + CustomSubscriber.attach_to("rack_attack") + 2.times { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' } + end + end + + it 'should instrument without error' do + _(last_response.status).must_equal 429 + assert_equal 1, CustomSubscriber.notification_count + end end end end diff --git a/spec/rack_attack_throttle_spec.rb b/spec/rack_attack_throttle_spec.rb index 1bf7f32f..9cd0ab51 100644 --- a/spec/rack_attack_throttle_spec.rb +++ b/spec/rack_attack_throttle_spec.rb @@ -6,7 +6,7 @@ describe 'Rack::Attack.throttle' do before do @period = 60 - Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new + Rack::Attack.cache.store = SimpleMemoryStore.new Rack::Attack.throttle('ip/sec', limit: 1, period: @period) { |req| req.ip } end @@ -70,7 +70,7 @@ describe 'Rack::Attack.throttle with limit as proc' do before do @period = 60 - Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new + Rack::Attack.cache.store = SimpleMemoryStore.new Rack::Attack.throttle('ip/sec', limit: lambda { |_req| 1 }, period: @period) { |req| req.ip } end @@ -104,7 +104,7 @@ describe 'Rack::Attack.throttle with period as proc' do before do @period = 60 - Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new + Rack::Attack.cache.store = SimpleMemoryStore.new Rack::Attack.throttle('ip/sec', limit: lambda { |_req| 1 }, period: lambda { |_req| @period }) { |req| req.ip } end @@ -139,7 +139,7 @@ describe 'Rack::Attack.throttle with block returning nil' do before do @period = 60 - Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new + Rack::Attack.cache.store = SimpleMemoryStore.new Rack::Attack.throttle('ip/sec', limit: 1, period: @period) { |_| nil } end @@ -170,7 +170,7 @@ "PERSON@example.com ", " person@example.com\r\n ", ] - Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new + Rack::Attack.cache.store = SimpleMemoryStore.new Rack::Attack.throttle('logins/email', limit: 4, period: @period) do |req| if req.path == '/login' && req.post? req.params['email'] diff --git a/spec/rack_attack_track_spec.rb b/spec/rack_attack_track_spec.rb index 0013c15b..37f23dab 100644 --- a/spec/rack_attack_track_spec.rb +++ b/spec/rack_attack_track_spec.rb @@ -3,8 +3,6 @@ require_relative 'spec_helper' describe 'Rack::Attack.track' do - let(:notifications) { [] } - before do Rack::Attack.track("everything") { |_req| true } end @@ -18,20 +16,24 @@ _(last_request.env['rack.attack.match_type']).must_equal :track end - describe "with a notification subscriber and two tracks" do - before do - # A second track - Rack::Attack.track("homepage") { |req| req.path == "/" } + if defined?(::ActiveSupport::Notifications) + describe "with a notification subscriber and two tracks" do + let(:notifications) { [] } - ActiveSupport::Notifications.subscribe("track.rack_attack") do |_name, _start, _finish, _id, payload| - notifications.push(payload) - end + before do + # A second track + Rack::Attack.track("homepage") { |req| req.path == "/" } - get "/" - end + ActiveSupport::Notifications.subscribe("track.rack_attack") do |_name, _start, _finish, _id, payload| + notifications.push(payload) + end - it "should notify twice" do - _(notifications.size).must_equal 2 + get "/" + end + + it "should notify twice" do + _(notifications.size).must_equal 2 + end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 894de6a1..ee17781f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,22 +1,68 @@ # frozen_string_literal: true +def safe_require(name) + require name +rescue LoadError + nil +end + require "bundler/setup" require "logger" require "minitest/autorun" require "minitest/pride" require "rack/test" -require "active_support" +safe_require "active_support" require "rack/attack" -if RUBY_ENGINE == "ruby" - require "byebug" +# Simple in-memory cache store for testing without ActiveSupport dependency +class SimpleMemoryStore + def initialize + @data = {} + end + + def read(key) + entry = @data[key] + return nil unless entry + return nil if entry[:expires_at] && entry[:expires_at] < Time.now + + entry[:value] + end + + def write(key, value, options = {}) + expires_at = options[:expires_in] ? Time.now + options[:expires_in] : nil + @data[key] = { value: value, expires_at: expires_at } + true + end + + def increment(key, amount = 1, options = {}) + current = read(key) + if current.nil? + nil + else + new_value = current.to_i + amount + write(key, new_value, options) + new_value + end + end + + def delete(key) + @data.delete(key) + end + + def delete_matched(matcher) + @data.keys.each do |key| + @data.delete(key) if key.match?(matcher) + end + end + + def clear + @data.clear + end end -def safe_require(name) - require name -rescue LoadError - nil +if RUBY_ENGINE == "ruby" + require "byebug" end safe_require "connection_pool"