From 69ab514477b646e082e5f87498c7035bdbc7097c Mon Sep 17 00:00:00 2001 From: Rob Watson Date: Mon, 20 Feb 2017 14:11:32 +0000 Subject: [PATCH 001/129] Warn if Redis gem is < 3.0.0 Prefer printing a log message if this criteria is not met, instead of failing silently. --- lib/rack/attack/store_proxy/redis_store_proxy.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/rack/attack/store_proxy/redis_store_proxy.rb b/lib/rack/attack/store_proxy/redis_store_proxy.rb index be3c412..05d9ec8 100644 --- a/lib/rack/attack/store_proxy/redis_store_proxy.rb +++ b/lib/rack/attack/store_proxy/redis_store_proxy.rb @@ -4,6 +4,13 @@ module Rack class Attack module StoreProxy class RedisStoreProxy < SimpleDelegator + def initialize(*args) + major_version = Redis::VERSION.split('.').first.to_i + warn 'RackAttack requires Redis gem >= 3.0.0.' if major_version < 3 + + super(*args) + end + def self.handle?(store) defined?(::Redis::Store) && store.is_a?(::Redis::Store) end From ca739946ce66aae2788530bc354f71eb638900ca Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Thu, 25 Jan 2018 10:53:47 -0300 Subject: [PATCH 002/129] Attempt to make it easier to understand that the method is making assertions --- spec/rack_attack_request_spec.rb | 2 +- spec/rack_attack_spec.rb | 4 ++-- spec/rack_attack_throttle_spec.rb | 8 ++++---- spec/rack_attack_track_spec.rb | 2 +- spec/spec_helper.rb | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/spec/rack_attack_request_spec.rb b/spec/rack_attack_request_spec.rb index cc617ae..41dfd82 100644 --- a/spec/rack_attack_request_spec.rb +++ b/spec/rack_attack_request_spec.rb @@ -14,6 +14,6 @@ describe 'Rack::Attack' do end end - allow_ok_requests + it_allows_ok_requests end end diff --git a/spec/rack_attack_spec.rb b/spec/rack_attack_spec.rb index e2232b3..af5963b 100644 --- a/spec/rack_attack_spec.rb +++ b/spec/rack_attack_spec.rb @@ -1,7 +1,7 @@ require_relative 'spec_helper' describe 'Rack::Attack' do - allow_ok_requests + it_allows_ok_requests describe 'normalizing paths' do before do @@ -45,7 +45,7 @@ describe 'Rack::Attack' do last_request.env['rack.attack.match_type'].must_equal :blocklist end - allow_ok_requests + it_allows_ok_requests end describe "and safelist" do diff --git a/spec/rack_attack_throttle_spec.rb b/spec/rack_attack_throttle_spec.rb index 28ab26c..df6235f 100644 --- a/spec/rack_attack_throttle_spec.rb +++ b/spec/rack_attack_throttle_spec.rb @@ -9,7 +9,7 @@ describe 'Rack::Attack.throttle' do it('should have a throttle') { Rack::Attack.throttles.key?('ip/sec') } - allow_ok_requests + it_allows_ok_requests describe 'a single request' do before { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' } @@ -54,7 +54,7 @@ describe 'Rack::Attack.throttle with limit as proc' do Rack::Attack.throttle('ip/sec', :limit => lambda { |req| 1 }, :period => @period) { |req| req.ip } end - allow_ok_requests + it_allows_ok_requests describe 'a single request' do before { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' } @@ -78,7 +78,7 @@ describe 'Rack::Attack.throttle with period as proc' do Rack::Attack.throttle('ip/sec', :limit => lambda { |req| 1 }, :period => lambda { |req| @period }) { |req| req.ip } end - allow_ok_requests + it_allows_ok_requests describe 'a single request' do before { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' } @@ -102,7 +102,7 @@ describe 'Rack::Attack.throttle with block retuning nil' do Rack::Attack.throttle('ip/sec', :limit => 1, :period => @period) { |_| nil } end - allow_ok_requests + it_allows_ok_requests describe 'a single request' do before { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' } diff --git a/spec/rack_attack_track_spec.rb b/spec/rack_attack_track_spec.rb index 6bd38f3..cb2f2c9 100644 --- a/spec/rack_attack_track_spec.rb +++ b/spec/rack_attack_track_spec.rb @@ -19,7 +19,7 @@ describe 'Rack::Attack.track' do Rack::Attack.track("everything"){ |req| true } end - allow_ok_requests + it_allows_ok_requests it "should tag the env" do get '/' diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b7d535f..ccdda3d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -28,7 +28,7 @@ class MiniTest::Spec }.to_app end - def self.allow_ok_requests + def self.it_allows_ok_requests it "must allow ok requests" do get '/', {}, 'REMOTE_ADDR' => '127.0.0.1' last_response.status.must_equal 200 From 980633e1a91780e2ac332542d29427ba9699f821 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Thu, 25 Jan 2018 15:58:18 -0300 Subject: [PATCH 003/129] Adds acceptance-oriented tests --- Rakefile | 6 ++++- rack-attack.gemspec | 1 + spec/acceptance/blocking_spec.rb | 21 ++++++++++++++++ spec/acceptance/safelisting_spec.rb | 37 +++++++++++++++++++++++++++++ spec/acceptance/throttling_spec.rb | 30 +++++++++++++++++++++++ 5 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 spec/acceptance/blocking_spec.rb create mode 100644 spec/acceptance/safelisting_spec.rb create mode 100644 spec/acceptance/throttling_spec.rb diff --git a/Rakefile b/Rakefile index 300ec21..475bece 100644 --- a/Rakefile +++ b/Rakefile @@ -11,9 +11,13 @@ namespace :test do Rake::TestTask.new(:integration) do |t| t.pattern = "spec/integration/*_spec.rb" end + + Rake::TestTask.new(:acceptance) do |t| + t.pattern = "spec/acceptance/*_spec.rb" + end end desc 'Run tests' -task :test => %w[test:units test:integration] +task :test => %w[test:units test:integration test:acceptance] task :default => :test diff --git a/rack-attack.gemspec b/rack-attack.gemspec index 1fcdd42..95a833a 100644 --- a/rack-attack.gemspec +++ b/rack-attack.gemspec @@ -33,4 +33,5 @@ Gem::Specification.new do |s| s.add_development_dependency 'dalli' s.add_development_dependency 'connection_pool' s.add_development_dependency 'memcache-client' + s.add_development_dependency "timecop" end diff --git a/spec/acceptance/blocking_spec.rb b/spec/acceptance/blocking_spec.rb new file mode 100644 index 0000000..2e9a562 --- /dev/null +++ b/spec/acceptance/blocking_spec.rb @@ -0,0 +1,21 @@ +require_relative "../spec_helper" + +describe "#blocklist" do + before do + Rack::Attack.blocklist("block 1.2.3.4") do |request| + request.ip == "1.2.3.4" + end + end + + it "forbids request if blocklist condition is true" do + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal 403, last_response.status + end + + it "succeeds if blocklist condition is false" do + get "/", {}, "REMOTE_ADDR" => "5.6.7.8" + + assert_equal 200, last_response.status + end +end diff --git a/spec/acceptance/safelisting_spec.rb b/spec/acceptance/safelisting_spec.rb new file mode 100644 index 0000000..743d97e --- /dev/null +++ b/spec/acceptance/safelisting_spec.rb @@ -0,0 +1,37 @@ +require_relative "../spec_helper" + +describe "#safelist" do + before do + Rack::Attack.blocklist("block 1.2.3.4") do |request| + request.ip == "1.2.3.4" + end + + Rack::Attack.safelist("safe path") do |request| + request.path == "/safe_space" + end + end + + it "forbids request if blocklist condition is true and safelist is false" do + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal 403, last_response.status + end + + it "succeeds if blocklist condition is false and safelist is false" do + get "/", {}, "REMOTE_ADDR" => "5.6.7.8" + + assert_equal 200, last_response.status + end + + it "succeeds request if blocklist condition is false and safelist is true" do + get "/safe_space", {}, "REMOTE_ADDR" => "5.6.7.8" + + assert_equal 200, last_response.status + end + + it "succeeds request if both blocklist and safelist conditions are true" do + get "/safe_space", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal 200, last_response.status + end +end diff --git a/spec/acceptance/throttling_spec.rb b/spec/acceptance/throttling_spec.rb new file mode 100644 index 0000000..e157f50 --- /dev/null +++ b/spec/acceptance/throttling_spec.rb @@ -0,0 +1,30 @@ +require_relative "../spec_helper" +require "timecop" + +describe "#throttle" do + it "allows one request per minute by IP" do + Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new + + Rack::Attack.throttle("by ip", limit: 1, period: 60) do |request| + request.ip + end + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal 200, last_response.status + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal 429, last_response.status + + get "/", {}, "REMOTE_ADDR" => "5.6.7.8" + + assert_equal 200, last_response.status + + Timecop.travel(60) do + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal 200, last_response.status + end + end +end From 6af29fb44e5aac33a2cd74ba2daf75b242e6aef9 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Mon, 29 Jan 2018 12:19:05 -0300 Subject: [PATCH 004/129] Avoid 'defined?' buggy behavior in ruby 2.5.0. Fixes #253 'defined?' is buggy in ruby 2.5.0, which under certain circumstances users using rack-attack can hit. See issue #253. I reported (https://bugs.ruby-lang.org/issues/14407) and fixed (https://github.com/ruby/ruby/pull/1800) the issue in ruby already, but i guess i would take some time before there's a new ruby release including that fix. So for now we would need to circumvent this bug by using 'const_defined?' instead of 'defined?' for this particular case. More details: Anyone using: * ruby 2.5.0 * redis * rack-attack without redis-store and using at least one throttle * having a toplevel class named Store will hit this ruby 2.5.0 bug https://bugs.ruby-lang.org/issues/14407 That's because of the following buggy behavior of 'defined?' under ruby 2.5: ``` $ ruby -v ruby 2.5.0p0 (2017-12-25 revision 61468) [x86_64-linux] $ irb > class Redis > end => nil > class Store > end => nil > defined?(::Redis::Store) => "constant" > ::Redis::Store NameError (uninitialized constant Redis::Store Did you mean? Store) ``` --- lib/rack/attack/store_proxy/redis_store_proxy.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/rack/attack/store_proxy/redis_store_proxy.rb b/lib/rack/attack/store_proxy/redis_store_proxy.rb index c980369..1a7c20a 100644 --- a/lib/rack/attack/store_proxy/redis_store_proxy.rb +++ b/lib/rack/attack/store_proxy/redis_store_proxy.rb @@ -5,7 +5,14 @@ module Rack module StoreProxy class RedisStoreProxy < SimpleDelegator def self.handle?(store) - defined?(::Redis::Store) && store.is_a?(::Redis::Store) + # Using const_defined? for now. + # + # Go back to use defined? once this ruby issue is + # fixed and released: + # https://bugs.ruby-lang.org/issues/14407 + # + # defined?(::Redis::Store) && store.is_a?(::Redis::Store) + const_defined?("::Redis::Store") && store.is_a?(::Redis::Store) end def initialize(store) From f27432df91f77a543430224d5ebdd7330eb8278b Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 30 Jan 2018 10:08:20 -0300 Subject: [PATCH 005/129] Use Rack::Lint in tests to check any change continues complying with the rack spec --- spec/spec_helper.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b7d535f..3974343 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -23,7 +23,11 @@ class MiniTest::Spec def app Rack::Builder.new { + # Use Rack::Lint to test that rack-attack is complying with the rack spec + use Rack::Lint use Rack::Attack + use Rack::Lint + run lambda {|env| [200, {}, ['Hello World']]} }.to_app end From 7bb7a059870e156cfe27d8c963320d6aa26830e6 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Thu, 1 Feb 2018 10:06:39 -0300 Subject: [PATCH 006/129] Help users understand more clearly when the store is misconfigured --- lib/rack/attack.rb | 3 +++ lib/rack/attack/cache.rb | 17 +++++++++++------ spec/rack_attack_store_config_spec.rb | 20 ++++++++++++++++++++ spec/spec_helper.rb | 5 ++++- 4 files changed, 38 insertions(+), 7 deletions(-) create mode 100644 spec/rack_attack_store_config_spec.rb diff --git a/lib/rack/attack.rb b/lib/rack/attack.rb index 5619c20..8c9df48 100644 --- a/lib/rack/attack.rb +++ b/lib/rack/attack.rb @@ -2,6 +2,9 @@ require 'rack' require 'forwardable' class Rack::Attack + class MisconfiguredStoreError < StandardError; end + class MissingStoreError < StandardError; end + autoload :Cache, 'rack/attack/cache' autoload :PathNormalizer, 'rack/attack/path_normalizer' autoload :Check, 'rack/attack/check' diff --git a/lib/rack/attack/cache.rb b/lib/rack/attack/cache.rb index c2dd06c..aec048b 100644 --- a/lib/rack/attack/cache.rb +++ b/lib/rack/attack/cache.rb @@ -46,15 +46,20 @@ module Rack end def do_count(key, expires_in) - result = store.increment(key, 1, :expires_in => expires_in) + if store.nil? + raise Rack::Attack::MissingStoreError + elsif !store.respond_to?(:increment) + raise Rack::Attack::MisconfiguredStoreError, "Store needs to respond to #increment" + else + result = store.increment(key, 1, :expires_in => expires_in) - # NB: Some stores return nil when incrementing uninitialized values - if result.nil? - store.write(key, 1, :expires_in => expires_in) + # NB: Some stores return nil when incrementing uninitialized values + if result.nil? + store.write(key, 1, :expires_in => expires_in) + end + result || 1 end - result || 1 end - end end end diff --git a/spec/rack_attack_store_config_spec.rb b/spec/rack_attack_store_config_spec.rb new file mode 100644 index 0000000..249f2ab --- /dev/null +++ b/spec/rack_attack_store_config_spec.rb @@ -0,0 +1,20 @@ +require_relative 'spec_helper' + +describe 'Store configuration' do + it "gives clear error when store it's not configured if it's needed" do + Rack::Attack.throttle('ip/sec', limit: 1, period: 60) { |req| req.ip } + + assert_raises(Rack::Attack::MissingStoreError) do + get '/' + end + end + + it "gives clear error when store isn't configured properly" do + Rack::Attack.cache.store = Object.new + Rack::Attack.throttle('ip/sec', limit: 1, period: 60) { |req| req.ip } + + assert_raises(Rack::Attack::MisconfiguredStoreError) do + get '/' + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b7d535f..78665a2 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -19,7 +19,10 @@ class MiniTest::Spec include Rack::Test::Methods - after { Rack::Attack.clear! } + after do + Rack::Attack.clear! + Rack::Attack.instance_variable_set(:@cache, nil) + end def app Rack::Builder.new { From 53095231cc8b87dc5ecc0c437c274e5b9d018d3f Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Thu, 1 Feb 2018 15:57:18 -0300 Subject: [PATCH 007/129] Test against rails 5.2.0.rc to get early feedback without causing TravisCI build failures --- .travis.yml | 6 ++++++ Appraisals | 5 +++++ gemfiles/rails_5_2.gemfile | 14 ++++++++++++++ 3 files changed, 25 insertions(+) create mode 100644 gemfiles/rails_5_2.gemfile diff --git a/.travis.yml b/.travis.yml index 9eb7762..a6a253f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,11 +11,17 @@ before_install: - gem install bundler gemfile: + - gemfiles/rails_5_2.gemfile - gemfiles/rails_5_1.gemfile - gemfiles/rails_5_0.gemfile - gemfiles/rails_4_2.gemfile - gemfiles/dalli2.gemfile +matrix: + allow_failures: + - gemfile: gemfiles/rails_5_2.gemfile + fast_finish: true + services: - redis - memcached diff --git a/Appraisals b/Appraisals index 56fadfc..296b89c 100644 --- a/Appraisals +++ b/Appraisals @@ -1,3 +1,8 @@ +appraise 'rails_5-2' do + gem 'activesupport', '~> 5.2.0.a' + gem 'actionpack', '~> 5.2.0.a' +end + appraise 'rails_5-1' do gem 'activesupport', '~> 5.1.0' gem 'actionpack', '~> 5.1.0' diff --git a/gemfiles/rails_5_2.gemfile b/gemfiles/rails_5_2.gemfile new file mode 100644 index 0000000..f1948ff --- /dev/null +++ b/gemfiles/rails_5_2.gemfile @@ -0,0 +1,14 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "activesupport", "~> 5.2.0.a" +gem "actionpack", "~> 5.2.0.a" + +group :development do + gem "pry" + gem "guard" + gem "guard-minitest" +end + +gemspec path: "../" From d6d471fea593c0322f09b9e08d9468024ea9da58 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Fri, 2 Feb 2018 16:05:04 -0300 Subject: [PATCH 008/129] Move all dependencies to gemspec --- Gemfile | 6 ------ gemfiles/dalli2.gemfile | 6 ------ gemfiles/rails_4_2.gemfile | 6 ------ gemfiles/rails_5_0.gemfile | 6 ------ gemfiles/rails_5_1.gemfile | 6 ------ rack-attack.gemspec | 8 ++++++++ 6 files changed, 8 insertions(+), 30 deletions(-) diff --git a/Gemfile b/Gemfile index 5d00c12..fa75df1 100644 --- a/Gemfile +++ b/Gemfile @@ -1,9 +1,3 @@ source 'https://rubygems.org' gemspec - -group :development do - gem 'pry' - gem 'guard' # NB: this is necessary in newer versions - gem 'guard-minitest' -end diff --git a/gemfiles/dalli2.gemfile b/gemfiles/dalli2.gemfile index f955198..c47d5af 100644 --- a/gemfiles/dalli2.gemfile +++ b/gemfiles/dalli2.gemfile @@ -4,10 +4,4 @@ source "https://rubygems.org" gem "dalli", "~> 2.0" -group :development do - gem "pry" - gem "guard" - gem "guard-minitest" -end - gemspec path: "../" diff --git a/gemfiles/rails_4_2.gemfile b/gemfiles/rails_4_2.gemfile index d4532c5..7a03e76 100644 --- a/gemfiles/rails_4_2.gemfile +++ b/gemfiles/rails_4_2.gemfile @@ -5,10 +5,4 @@ source "https://rubygems.org" gem "activesupport", "~> 4.2.0" gem "actionpack", "~> 4.2.0" -group :development do - gem "pry" - gem "guard" - gem "guard-minitest" -end - gemspec path: "../" diff --git a/gemfiles/rails_5_0.gemfile b/gemfiles/rails_5_0.gemfile index 0fea132..c03a540 100644 --- a/gemfiles/rails_5_0.gemfile +++ b/gemfiles/rails_5_0.gemfile @@ -5,10 +5,4 @@ source "https://rubygems.org" gem "activesupport", "~> 5.0.0" gem "actionpack", "~> 5.0.0" -group :development do - gem "pry" - gem "guard" - gem "guard-minitest" -end - gemspec path: "../" diff --git a/gemfiles/rails_5_1.gemfile b/gemfiles/rails_5_1.gemfile index bb501ef..98a6051 100644 --- a/gemfiles/rails_5_1.gemfile +++ b/gemfiles/rails_5_1.gemfile @@ -5,10 +5,4 @@ source "https://rubygems.org" gem "activesupport", "~> 5.1.0" gem "actionpack", "~> 5.1.0" -group :development do - gem "pry" - gem "guard" - gem "guard-minitest" -end - gemspec path: "../" diff --git a/rack-attack.gemspec b/rack-attack.gemspec index 1fcdd42..e5fb4ba 100644 --- a/rack-attack.gemspec +++ b/rack-attack.gemspec @@ -33,4 +33,12 @@ Gem::Specification.new do |s| s.add_development_dependency 'dalli' s.add_development_dependency 'connection_pool' s.add_development_dependency 'memcache-client' + + s.add_development_dependency 'pry' + s.add_development_dependency 'guard-minitest' + # Need to explicitly depend on guard because guard-minitest doesn't declare + # the dependency intentionally + # + # See https://github.com/guard/guard-minitest/pull/131 + s.add_development_dependency 'guard' end From 70a4c1f933229f98a0d20d549abe26159280f0c7 Mon Sep 17 00:00:00 2001 From: jun_tanaka555 Date: Sun, 4 Feb 2018 16:18:54 +0900 Subject: [PATCH 009/129] fix rubocop complains about initializer file name --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b5381e7..61f9cf0 100644 --- a/README.md +++ b/README.md @@ -40,9 +40,9 @@ Or for Rackup files: use Rack::Attack ``` -Add a `rack-attack.rb` file to `config/initializers/`: +Add a `rack_attack.rb` file to `config/initializers/`: ```ruby -# In config/initializers/rack-attack.rb +# In config/initializers/rack_attack.rb class Rack::Attack # your custom configuration... end From 9dbece52729ba0c5170caad24b60874ee953eb8c Mon Sep 17 00:00:00 2001 From: Kyle d'Oliveira Date: Thu, 15 Feb 2018 12:50:42 -0800 Subject: [PATCH 010/129] Add an reader for the epoch_time variable in the cache so that it can also be returned in the data from the throttle. This is allows access to the same time that the cache uses for the count. This can be important for clients that want to provide rate limit information for well-behaved clients --- README.md | 6 +++--- lib/rack/attack/cache.rb | 7 ++++--- lib/rack/attack/throttle.rb | 5 ++++- spec/rack_attack_throttle_spec.rb | 8 ++++---- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index b5381e7..8fe6d36 100644 --- a/README.md +++ b/README.md @@ -254,13 +254,13 @@ Here's an example response that includes conventional `X-RateLimit-*` headers: ```ruby Rack::Attack.throttled_response = lambda do |env| - now = Time.now match_data = env['rack.attack.match_data'] + now = match_data[:epoch_time] headers = { 'X-RateLimit-Limit' => match_data[:limit].to_s, 'X-RateLimit-Remaining' => '0', - 'X-RateLimit-Reset' => (now + (match_data[:period] - now.to_i % match_data[:period])).to_s + 'X-RateLimit-Reset' => (now + (match_data[:period] - now % match_data[:period])).to_s } [ 429, headers, ["Throttled\n"]] @@ -271,7 +271,7 @@ end For responses that did not exceed a throttle limit, Rack::Attack annotates the env with match data: ```ruby -request.env['rack.attack.throttle_data'][name] # => { :count => n, :period => p, :limit => l } +request.env['rack.attack.throttle_data'][name] # => { :count => n, :period => p, :limit => l, :epoch_time => t } ``` ## Logging & Instrumentation diff --git a/lib/rack/attack/cache.rb b/lib/rack/attack/cache.rb index c2dd06c..73f0ca2 100644 --- a/lib/rack/attack/cache.rb +++ b/lib/rack/attack/cache.rb @@ -3,6 +3,7 @@ module Rack class Cache attr_accessor :prefix + attr_reader :last_epoch_time def initialize self.store = ::Rails.cache if defined?(::Rails.cache) @@ -39,10 +40,10 @@ module Rack private def key_and_expiry(unprefixed_key, period) - epoch_time = Time.now.to_i + @last_epoch_time = Time.now.to_i # Add 1 to expires_in to avoid timing error: http://git.io/i1PHXA - expires_in = (period - (epoch_time % period) + 1).to_i - ["#{prefix}:#{(epoch_time / period).to_i}:#{unprefixed_key}", expires_in] + expires_in = (period - (@last_epoch_time % period) + 1).to_i + ["#{prefix}:#{(@last_epoch_time / period).to_i}:#{unprefixed_key}", expires_in] end def do_count(key, expires_in) diff --git a/lib/rack/attack/throttle.rb b/lib/rack/attack/throttle.rb index 19a6519..4dc999d 100644 --- a/lib/rack/attack/throttle.rb +++ b/lib/rack/attack/throttle.rb @@ -26,12 +26,15 @@ module Rack current_limit = limit.respond_to?(:call) ? limit.call(req) : limit key = "#{name}:#{discriminator}" count = cache.count(key, current_period) + epoch_time = cache.last_epoch_time data = { :count => count, :period => current_period, - :limit => current_limit + :limit => current_limit, + :epoch_time => epoch_time } + (req.env['rack.attack.throttle_data'] ||= {})[name] = data (count > current_limit).tap do |throttled| diff --git a/spec/rack_attack_throttle_spec.rb b/spec/rack_attack_throttle_spec.rb index 28ab26c..5befbe9 100644 --- a/spec/rack_attack_throttle_spec.rb +++ b/spec/rack_attack_throttle_spec.rb @@ -20,7 +20,7 @@ describe 'Rack::Attack.throttle' do end it 'should populate throttle data' do - data = { :count => 1, :limit => 1, :period => @period } + data = { :count => 1, :limit => 1, :period => @period, epoch_time: Rack::Attack.cache.last_epoch_time.to_i } last_request.env['rack.attack.throttle_data']['ip/sec'].must_equal data end end @@ -37,7 +37,7 @@ describe 'Rack::Attack.throttle' do it 'should tag the env' do last_request.env['rack.attack.matched'].must_equal 'ip/sec' last_request.env['rack.attack.match_type'].must_equal :throttle - last_request.env['rack.attack.match_data'].must_equal({:count => 2, :limit => 1, :period => @period}) + last_request.env['rack.attack.match_data'].must_equal({:count => 2, :limit => 1, :period => @period, epoch_time: Rack::Attack.cache.last_epoch_time.to_i}) last_request.env['rack.attack.match_discriminator'].must_equal('1.2.3.4') end @@ -65,7 +65,7 @@ describe 'Rack::Attack.throttle with limit as proc' do end it 'should populate throttle data' do - data = { :count => 1, :limit => 1, :period => @period } + data = { :count => 1, :limit => 1, :period => @period, epoch_time: Rack::Attack.cache.last_epoch_time.to_i } last_request.env['rack.attack.throttle_data']['ip/sec'].must_equal data end end @@ -89,7 +89,7 @@ describe 'Rack::Attack.throttle with period as proc' do end it 'should populate throttle data' do - data = { :count => 1, :limit => 1, :period => @period } + data = { :count => 1, :limit => 1, :period => @period, epoch_time: Rack::Attack.cache.last_epoch_time.to_i } last_request.env['rack.attack.throttle_data']['ip/sec'].must_equal data end end From 993d724b64b54c18f8f7bbe3d40b3332457b1813 Mon Sep 17 00:00:00 2001 From: Alex Taylor Date: Mon, 5 Mar 2018 17:21:36 -0800 Subject: [PATCH 011/129] Mention 'match_discriminator' in README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b5381e7..12377b8 100644 --- a/README.md +++ b/README.md @@ -237,7 +237,8 @@ Rack::Attack.throttled_response = lambda do |env| # NB: you have access to the name and other data about the matched throttle # env['rack.attack.matched'], # env['rack.attack.match_type'], - # env['rack.attack.match_data'] + # env['rack.attack.match_data'], + # env['rack.attack.match_discriminator'] # Using 503 because it may make attacker think that they have successfully # DOSed the site. Rack::Attack returns 429 for throttling by default From 2406435663b12272d901e822b0268f5c4d497f2b Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Fri, 2 Feb 2018 14:15:09 -0300 Subject: [PATCH 012/129] Ability to use byebug easily while developing/testing --- .gitignore | 1 + rack-attack.gemspec | 5 +++++ spec/spec_helper.rb | 4 ++++ 3 files changed, 10 insertions(+) diff --git a/.gitignore b/.gitignore index 8624f93..1bb1e05 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ bin *.gemfile.lock .ruby-version .ruby-gemset +.byebug_history diff --git a/rack-attack.gemspec b/rack-attack.gemspec index f446cd6..07f2931 100644 --- a/rack-attack.gemspec +++ b/rack-attack.gemspec @@ -41,4 +41,9 @@ Gem::Specification.new do |s| # # See https://github.com/guard/guard-minitest/pull/131 s.add_development_dependency 'guard' + + # byebug only works with MRI + if RUBY_ENGINE == "ruby" + s.add_development_dependency 'byebug' + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 0378797..7cfc1b5 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -15,6 +15,10 @@ rescue LoadError #nothing to do here end +if RUBY_ENGINE == "ruby" + require "byebug" +end + class MiniTest::Spec include Rack::Test::Methods From 3cc5c92ff28cdbae3a691daca81ab373aaea1d24 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Fri, 9 Mar 2018 14:21:15 -0300 Subject: [PATCH 013/129] Make TravisCI builds quicker by caching 'bundle install' --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index a6a253f..a462cd1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,6 @@ language: ruby +cache: bundler + rvm: - 2.5.0 - 2.4.3 From c119186134012bfc3107ecd038b25a1c7619bc1f Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Fri, 9 Mar 2018 15:23:46 -0300 Subject: [PATCH 014/129] bump version to v5.1.0 --- lib/rack/attack/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rack/attack/version.rb b/lib/rack/attack/version.rb index 06f4df7..a01a336 100644 --- a/lib/rack/attack/version.rb +++ b/lib/rack/attack/version.rb @@ -1,5 +1,5 @@ module Rack class Attack - VERSION = '5.0.1' + VERSION = '5.1.0' end end From 83ac0f7b0d1396df830228743f80e846438ccab7 Mon Sep 17 00:00:00 2001 From: Amir Shadaab Mohammed Date: Mon, 6 Nov 2017 17:12:59 -0600 Subject: [PATCH 015/129] add require lines in README.md file --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 526feca..d05ec6c 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ Or for Rackup files: ```ruby # In config.ru +require "rack/attack" use Rack::Attack ``` From 0200ec419c0352101dd6d698207fb4fd956ecc04 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Mon, 12 Mar 2018 13:38:40 -0300 Subject: [PATCH 016/129] Let TravisCI give early feedback about ruby 2.6.0-preview1 --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index a462cd1..de1eaf9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ language: ruby cache: bundler rvm: + - 2.6.0-preview1 - 2.5.0 - 2.4.3 - 2.3.6 @@ -22,6 +23,8 @@ gemfile: matrix: allow_failures: - gemfile: gemfiles/rails_5_2.gemfile + - rvm: 2.6.0-preview1 + fast_finish: true services: From d647e388b8364910d6f9f3190c44d4dce0c5287e Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Mon, 12 Mar 2018 19:13:20 -0300 Subject: [PATCH 017/129] Test against latest jruby 9.1.16.0 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index de1eaf9..b019a2d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ rvm: - 2.4.3 - 2.3.6 - 2.2.9 - - jruby-9.1.14.0 + - jruby-9.1.16.0 before_install: - gem update --system From 84d282c510471baa9ee2dd50f17a20e09f8c4d89 Mon Sep 17 00:00:00 2001 From: Lucas Mansur Date: Mon, 12 Mar 2018 22:01:23 -0300 Subject: [PATCH 018/129] Backport releases to CHANGELOG.md --- CHANGELOG.md | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cec9633..705b7d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,27 @@ -# Changlog +# Changelog +All notable changes to this project will be documented in this file. -## [New Releases here](https://github.com/kickstarter/rack-attack/releases) +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). -This file is kept for historical documentation. -## v5.0.0.beta1 4 July 2016 +## v5.1.0 - 10 Mar 2018 + + - Fixes edge case bug when using ruby 2.5 and redis #253 (#271) + - Throws errors with better semantics when missing or misconfigured store caches to aid in developers debugging their configs (#274) + - Removed legacy code that was originally intended for Rails 3 apps (#264) + +## v5.0.1 - 11 Aug 2016 + + - Fixes arguments passed to deprecated internal methods. (#198) + +## v5.0.0 - 4 July 2016 - Deprecate `whitelist`/`blacklist` in favor of `safelist`/`blocklist`. (#181, thanks @renee-travisci). To upgrade and fix deprecations, find and replace instances of `whitelist` and `blacklist` with `safelist` and `blocklist`. If you reference `rack.attack.match_type`, note that it will have values like `:safelist`/`:blocklist`. - Remove test coverage for unsupported ruby dependencies: ruby 2.0, activesupport 3.2/4.0, and dalli 1. -## v4.4.1 17 Feb 2016 +## v4.4.1 - 17 Feb 2016 - Fix a bug affecting apps using Redis::Store and ActiveSupport that could generate an error saying dalli was a required dependency. I learned all about ActiveSupport autoloading. (#165) From 3deb14a27c21bb60b44f3d3d24c94def5e8b9c30 Mon Sep 17 00:00:00 2001 From: Lucas Mansur Date: Tue, 13 Mar 2018 12:09:25 -0300 Subject: [PATCH 019/129] Fix v5.0.0 release date --- CHANGELOG.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 705b7d2..575d02f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,6 @@ # Changelog All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](http://keepachangelog.com/) -and this project adheres to [Semantic Versioning](http://semver.org/). - ## v5.1.0 - 10 Mar 2018 @@ -15,7 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Fixes arguments passed to deprecated internal methods. (#198) -## v5.0.0 - 4 July 2016 +## v5.0.0 - 9 Aug 2016 - Deprecate `whitelist`/`blacklist` in favor of `safelist`/`blocklist`. (#181, thanks @renee-travisci). To upgrade and fix deprecations, find and replace instances of `whitelist` and `blacklist` with `safelist` and `blocklist`. If you reference `rack.attack.match_type`, note that it will have values like `:safelist`/`:blocklist`. From 569ecec7c7e36e3dec319925b49eae5bc706142c Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 13 Mar 2018 14:19:22 -0300 Subject: [PATCH 020/129] Acceptance test Rack::Attack#track --- spec/acceptance/track_spec.rb | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 spec/acceptance/track_spec.rb diff --git a/spec/acceptance/track_spec.rb b/spec/acceptance/track_spec.rb new file mode 100644 index 0000000..7f4b393 --- /dev/null +++ b/spec/acceptance/track_spec.rb @@ -0,0 +1,27 @@ +require_relative "../spec_helper" + +describe "#track" do + 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 + + notification_matched = nil + notification_type = nil + + ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, request| + notification_matched = request.env["rack.attack.matched"] + notification_type = request.env["rack.attack.match_type"] + end + + get "/", {}, "REMOTE_ADDR" => "5.6.7.8" + + assert_nil notification_matched + assert_nil notification_type + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal "ip 1.2.3.4", notification_matched + assert_equal :track, notification_type + end +end From 066434973f88963b5c3ed4ba14eb2dd39af20d7c Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 13 Mar 2018 14:43:37 -0300 Subject: [PATCH 021/129] Acceptance test Rack::Attack#track for throttle --- spec/acceptance/track_throttle_spec.rb | 45 ++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 spec/acceptance/track_throttle_spec.rb diff --git a/spec/acceptance/track_throttle_spec.rb b/spec/acceptance/track_throttle_spec.rb new file mode 100644 index 0000000..900f843 --- /dev/null +++ b/spec/acceptance/track_throttle_spec.rb @@ -0,0 +1,45 @@ +require_relative "../spec_helper" +require "timecop" + +describe "#track with throttle-ish options" do + it "notifies when throttle goes over the limit" do + Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new + + Rack::Attack.track("by ip", limit: 1, period: 60) do |request| + request.ip + end + + notification_matched = nil + notification_type = nil + + ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, request| + notification_matched = request.env["rack.attack.matched"] + notification_type = request.env["rack.attack.match_type"] + end + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_nil notification_matched + assert_nil notification_type + + get "/", {}, "REMOTE_ADDR" => "5.6.7.8" + + assert_nil notification_matched + assert_nil notification_type + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal "by ip", notification_matched + assert_equal :track, notification_type + + Timecop.travel(60) do + notification_matched = nil + notification_type = nil + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_nil notification_matched + assert_nil notification_type + end + end +end From 564cbedb36693e026a981512197e9e0702c152e0 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 13 Mar 2018 18:27:19 -0300 Subject: [PATCH 022/129] Acceptance test that tracking throttles doesn't actually throttle requests --- spec/acceptance/track_throttle_spec.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/spec/acceptance/track_throttle_spec.rb b/spec/acceptance/track_throttle_spec.rb index 900f843..7446ce4 100644 --- a/spec/acceptance/track_throttle_spec.rb +++ b/spec/acceptance/track_throttle_spec.rb @@ -2,7 +2,7 @@ require_relative "../spec_helper" require "timecop" describe "#track with throttle-ish options" do - it "notifies when throttle goes over the limit" do + it "notifies when throttle goes over the limit without actually throttling requests" do Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new Rack::Attack.track("by ip", limit: 1, period: 60) do |request| @@ -22,16 +22,22 @@ describe "#track with throttle-ish options" do assert_nil notification_matched assert_nil notification_type + assert_equal 200, last_response.status + get "/", {}, "REMOTE_ADDR" => "5.6.7.8" assert_nil notification_matched assert_nil notification_type + assert_equal 200, last_response.status + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" assert_equal "by ip", notification_matched assert_equal :track, notification_type + assert_equal 200, last_response.status + Timecop.travel(60) do notification_matched = nil notification_type = nil @@ -40,6 +46,8 @@ describe "#track with throttle-ish options" do assert_nil notification_matched assert_nil notification_type + + assert_equal 200, last_response.status end end end From 02908ce5cad8f8a0cce2d6cf0d5e1b21c69f2cb0 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Wed, 14 Mar 2018 17:40:30 -0300 Subject: [PATCH 023/129] Acceptance test cache store config for throttle without Rails --- .../cache_store_config_for_throttle_spec.rb | 48 +++++++++++++++++++ spec/rack_attack_store_config_spec.rb | 20 -------- 2 files changed, 48 insertions(+), 20 deletions(-) create mode 100644 spec/acceptance/cache_store_config_for_throttle_spec.rb delete mode 100644 spec/rack_attack_store_config_spec.rb diff --git a/spec/acceptance/cache_store_config_for_throttle_spec.rb b/spec/acceptance/cache_store_config_for_throttle_spec.rb new file mode 100644 index 0000000..9df9a6c --- /dev/null +++ b/spec/acceptance/cache_store_config_for_throttle_spec.rb @@ -0,0 +1,48 @@ +require_relative "../spec_helper" + +describe "Cache store config when throttling without Rails" do + before do + Rack::Attack.throttle("by ip", limit: 1, period: 60) do |request| + request.ip + end + end + + it "gives semantic error if no store was configured" do + assert_raises(Rack::Attack::MissingStoreError) do + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + end + end + + it "gives semantic error if incompatible store was configured" do + Rack::Attack.cache.store = Object.new + + assert_raises(Rack::Attack::MisconfiguredStoreError) do + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + end + end + + it "works with any object that responds to #increment" do + basic_store_class = Class.new do + attr_accessor :counts + + def initialize + @counts = {} + end + + def increment(key, count, options) + @counts[key] ||= 0 + @counts[key] += 1 + end + end + + Rack::Attack.cache.store = basic_store_class.new + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal 200, last_response.status + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal 429, last_response.status + end +end diff --git a/spec/rack_attack_store_config_spec.rb b/spec/rack_attack_store_config_spec.rb deleted file mode 100644 index 249f2ab..0000000 --- a/spec/rack_attack_store_config_spec.rb +++ /dev/null @@ -1,20 +0,0 @@ -require_relative 'spec_helper' - -describe 'Store configuration' do - it "gives clear error when store it's not configured if it's needed" do - Rack::Attack.throttle('ip/sec', limit: 1, period: 60) { |req| req.ip } - - assert_raises(Rack::Attack::MissingStoreError) do - get '/' - end - end - - it "gives clear error when store isn't configured properly" do - Rack::Attack.cache.store = Object.new - Rack::Attack.throttle('ip/sec', limit: 1, period: 60) { |req| req.ip } - - assert_raises(Rack::Attack::MisconfiguredStoreError) do - get '/' - end - end -end From 666dc3d894372471ffd451fc50d86727f921a81c Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Thu, 15 Mar 2018 15:24:22 -0300 Subject: [PATCH 024/129] Acceptance test ability to customize blocked/throttled responses (#298) * Acceptance test ability to customize blocked/throttled responses * Don't let customizations to blocklisted/throttled responses leak to other test cases --- .../customizing_blocked_response_spec.rb | 22 +++++++++++++++ .../customizing_throttled_response_spec.rb | 28 +++++++++++++++++++ spec/spec_helper.rb | 8 ++++++ 3 files changed, 58 insertions(+) create mode 100644 spec/acceptance/customizing_blocked_response_spec.rb create mode 100644 spec/acceptance/customizing_throttled_response_spec.rb diff --git a/spec/acceptance/customizing_blocked_response_spec.rb b/spec/acceptance/customizing_blocked_response_spec.rb new file mode 100644 index 0000000..7f3f307 --- /dev/null +++ b/spec/acceptance/customizing_blocked_response_spec.rb @@ -0,0 +1,22 @@ +require_relative "../spec_helper" + +describe "Customizing block responses" do + it "can be customized" do + Rack::Attack.blocklist("block 1.2.3.4") do |request| + request.ip == "1.2.3.4" + end + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal 403, last_response.status + + Rack::Attack.blocklisted_response = lambda do |env| + [503, {}, ["Blocked"]] + end + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal 503, last_response.status + assert_equal "Blocked", last_response.body + end +end diff --git a/spec/acceptance/customizing_throttled_response_spec.rb b/spec/acceptance/customizing_throttled_response_spec.rb new file mode 100644 index 0000000..7f66372 --- /dev/null +++ b/spec/acceptance/customizing_throttled_response_spec.rb @@ -0,0 +1,28 @@ +require_relative "../spec_helper" + +describe "Customizing throttled response" do + it "can be customized" do + Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new + + Rack::Attack.throttle("by ip", limit: 1, period: 60) do |request| + request.ip + end + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal 200, last_response.status + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal 429, last_response.status + + Rack::Attack.throttled_response = lambda do |env| + [503, {}, ["Throttled"]] + end + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal 503, last_response.status + assert_equal "Throttled", last_response.body + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 7cfc1b5..3473eba 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -23,9 +23,17 @@ class MiniTest::Spec include Rack::Test::Methods + before do + @_original_throttled_response = Rack::Attack.throttled_response + @_original_blocklisted_response = Rack::Attack.blocklisted_response + end + after do Rack::Attack.clear! Rack::Attack.instance_variable_set(:@cache, nil) + + Rack::Attack.throttled_response = @_original_throttled_response + Rack::Attack.blocklisted_response = @_original_blocklisted_response end def app From 61aba3455c751174f194ab58f342686744de9393 Mon Sep 17 00:00:00 2001 From: Lucas Mansur Date: Thu, 15 Mar 2018 16:13:48 -0300 Subject: [PATCH 025/129] Properly format Change Log (#299) * Change date format * Add links to version tags * Remove changelog.swp * Remove v prefix from version number * Add links references to CHANGELOG * Add Unreleased section * Add links to PR and Issue references * Update CHANGELOG.md --- CHANGELOG.md | 108 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 64 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 575d02f..8e582ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,92 +1,112 @@ # Changelog All notable changes to this project will be documented in this file. +## [Unreleased] -## v5.1.0 - 10 Mar 2018 - - Fixes edge case bug when using ruby 2.5 and redis #253 (#271) - - Throws errors with better semantics when missing or misconfigured store caches to aid in developers debugging their configs (#274) - - Removed legacy code that was originally intended for Rails 3 apps (#264) +## [5.1.0] - 2018-03-10 -## v5.0.1 - 11 Aug 2016 + - Fixes edge case bug when using ruby 2.5 and redis [#253](https://github.com/kickstarter/rack-attack/issues/253) ([#271](https://github.com/kickstarter/rack-attack/issues/271)) + - Throws errors with better semantics when missing or misconfigured store caches to aid in developers debugging their configs ([#274](https://github.com/kickstarter/rack-attack/issues/274)) + - Removed legacy code that was originally intended for Rails 3 apps ([#264](https://github.com/kickstarter/rack-attack/issues/264)) - - Fixes arguments passed to deprecated internal methods. (#198) +## [5.0.1] - 2016-08-11 -## v5.0.0 - 9 Aug 2016 + - Fixes arguments passed to deprecated internal methods. ([#198](https://github.com/kickstarter/rack-attack/issues/198)) - - Deprecate `whitelist`/`blacklist` in favor of `safelist`/`blocklist`. (#181, +## [5.0.0] - 2016-08-09 + + - Deprecate `whitelist`/`blacklist` in favor of `safelist`/`blocklist`. ([#181](https://github.com/kickstarter/rack-attack/issues/181), thanks @renee-travisci). To upgrade and fix deprecations, find and replace instances of `whitelist` and `blacklist` with `safelist` and `blocklist`. If you reference `rack.attack.match_type`, note that it will have values like `:safelist`/`:blocklist`. - Remove test coverage for unsupported ruby dependencies: ruby 2.0, activesupport 3.2/4.0, and dalli 1. -## v4.4.1 - 17 Feb 2016 +## [4.4.1] - 2016-02-17 - Fix a bug affecting apps using Redis::Store and ActiveSupport that could generate an error - saying dalli was a required dependency. I learned all about ActiveSupport autoloading. (#165) + saying dalli was a required dependency. I learned all about ActiveSupport autoloading. ([#165](https://github.com/kickstarter/rack-attack/issues/165)) -## v4.4.0 - 10 Feb 2016 +## [4.4.0] - 2016-02-10 - - New: support for MemCacheStore (#153). Thanks @elhu. + - New: support for MemCacheStore ([#153](https://github.com/kickstarter/rack-attack/issues/153)). Thanks @elhu. - Some documentation and test harness improvements. -## v4.3.1 - 18 Dec 2015 +## [4.3.1] - 2015-12-18 - SECURITY FIX: Normalize request paths when using ActionDispatch. Thanks Andres Riancho at @includesecurity for reporting it. - Remove support for ruby 1.9.x - Add Code of Conduct - Several documentation and testing improvements -## v4.3.0 - 22 May 2015 +## [4.3.0] - 2015-05-22 - Redis proxy passes `raw: true` (thanks @stanhu) - Redis supports `delete` method to be consistent with Dalli (thanks @stanhu) - Support the ability to reset Fail2Ban count and ban flag (thanks @stanhu) -## v4.2.0 - 26 Oct 2014 +## [4.2.0] - 2014-10-26 - Throttle's `period` argument now takes a proc as well as a number (thanks @gsamokovarov) - Invoke the `#call` method on `blocklist_response` and `throttle_response` instead of `#[]`, as per the Rack spec. (thanks @gsamokovarov) -## v4.1.1 - 11 Sept 2014 +## [4.1.1] - 2014-09-11 - Fix a race condition in throttles that could allow more requests than intended. -## v4.1.0 - 22 May 2014 +## [4.1.0] - 2014-05-22 - Tracks take an optional limit and period to only notify once a threshold is reached (similar to throttles). Thanks @chiliburger! - Default throttled & blocklist responses have Content-Type: text/plain - Rack::Attack.clear! resets tracks -## v4.0.1 - 14 May 2014 - * Add throttle discriminator to rack env (thanks @blahed) +## [4.0.1] - 2014-05-14 + - Add throttle discriminator to rack env (thanks @blahed) -## v4.0.0 - 28 April 2014 - * Implement proxy for Dalli with better Memcachier support. (thanks @hakanensari) - * Rack::Attack.new returns an instance to ease testing. (thanks @stevehodgkiss) +## [4.0.0] - 2014-04-28 + - Implement proxy for Dalli with better Memcachier support. (thanks @hakanensari) + - Rack::Attack.new returns an instance to ease testing. (thanks @stevehodgkiss) [Changing a module to a class is not backwards compatible, hence v4.0.0.] - * Use Rack::Attack::Request subclass of Rack::Request for easier extending (thanks @tristandunn) - * Test more dalli versions. + - Use Rack::Attack::Request subclass of Rack::Request for easier extending (thanks @tristandunn) + - Test more dalli versions. -## v3.0.0 - 15 March 2014 - * Change default blocklisted response to 403 Forbidden (thanks @carpodaster). - * Fail gracefully when Redis store is not available; rescue exeption and don't +## [3.0.0] - 2014-03-15 + - Change default blocklisted response to 403 Forbidden (thanks @carpodaster). + - Fail gracefully when Redis store is not available; rescue exeption and don't throttle request. (thanks @wkimeria) - * TravisCI runs integration tests. + - TravisCI runs integration tests. -## v2.3.0 - 11 October 2013 - * Allow throttle `limit` argument to be a proc. (thanks @lunks) - * Add Allow2Ban, complement of Fail2Ban. (thanks @jormon) - * Improved TravisCI testing +## [2.3.0] - 2013-10-11 + - Allow throttle `limit` argument to be a proc. (thanks @lunks) + - Add Allow2Ban, complement of Fail2Ban. (thanks @jormon) + - Improved TravisCI testing -## v2.2.1 - 13 August 2013 - * Add license to gemspec - * Support ruby version 1.9.2 - * Change default blocklisted response code from 503 to 401; throttled response +## [2.2.1] - 2013-08-13 + - Add license to gemspec + - Support ruby version 1.9.2 + - Change default blocklisted response code from 503 to 401; throttled response from 503 to 429. -## v2.2.0 - 20 June 2013 - * Fail2Ban filtering. See README for details. Thx @madlep! - * Introduce StoreProxy to more cleanly abstract cache stores. Thx @madlep. +## [2.2.0] - 2013-06-20 + - Fail2Ban filtering. See README for details. Thx @madlep! + - Introduce StoreProxy to more cleanly abstract cache stores. Thx @madlep. -## v2.1.1 - 16 May 2013 - * Start keeping changelog - * Fix `Redis::CommandError` when using ActiveSupport numeric extensions (e.g. `1.second`) - * Remove unused variable - * Extract mandatory options to constants +## 2.1.1 - 2013-05-16 + - Start keeping changelog + - Fix `Redis::CommandError` when using ActiveSupport numeric extensions (e.g. `1.second`) + - Remove unused variable + - Extract mandatory options to constants + +[Unreleased]: https://github.com/kickstarter/rack-attack/compare/v5.1.0...HEAD/ +[5.1.0]: https://github.com/kickstarter/rack-attack/compare/v5.0.1...v5.1.0/ +[5.0.1]: https://github.com/kickstarter/rack-attack/compare/v5.0.0...v5.0.1/ +[5.0.0]: https://github.com/kickstarter/rack-attack/compare/v4.4.1...v5.0.0/ +[4.4.1]: https://github.com/kickstarter/rack-attack/compare/v4.4.0...v4.4.1/ +[4.4.0]: https://github.com/kickstarter/rack-attack/compare/v4.3.1...v4.4.0/ +[4.3.1]: https://github.com/kickstarter/rack-attack/compare/v4.3.0...v4.3.1/ +[4.3.0]: https://github.com/kickstarter/rack-attack/compare/v4.2.0...v4.3.0/ +[4.2.0]: https://github.com/kickstarter/rack-attack/compare/v4.1.1...v4.2.0/ +[4.1.1]: https://github.com/kickstarter/rack-attack/compare/v4.1.0...v4.1.1/ +[4.1.0]: https://github.com/kickstarter/rack-attack/compare/v4.0.1...v4.1.0/ +[4.0.1]: https://github.com/kickstarter/rack-attack/compare/v4.0.0...v4.0.1/ +[4.0.0]: https://github.com/kickstarter/rack-attack/compare/v3.0.0...v4.0.0/ +[3.0.0]: https://github.com/kickstarter/rack-attack/compare/v2.3.0...v3.0.0/ +[2.3.0]: https://github.com/kickstarter/rack-attack/compare/v2.2.1...v2.3.0/ +[2.2.1]: https://github.com/kickstarter/rack-attack/compare/v2.2.0...v2.2.1/ +[2.2.0]: https://github.com/kickstarter/rack-attack/compare/v2.1.1...v2.2.0/ From 14c8b9261c14be57f1baa9a941b2775d4adc9cda Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Fri, 16 Mar 2018 15:29:01 -0300 Subject: [PATCH 026/129] Acceptance test fail2ban --- spec/acceptance/fail2ban_spec.rb | 76 ++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 spec/acceptance/fail2ban_spec.rb diff --git a/spec/acceptance/fail2ban_spec.rb b/spec/acceptance/fail2ban_spec.rb new file mode 100644 index 0000000..7fac437 --- /dev/null +++ b/spec/acceptance/fail2ban_spec.rb @@ -0,0 +1,76 @@ +require_relative "../spec_helper" +require "timecop" + +describe "fail2ban" do + before do + Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new + + Rack::Attack.blocklist("fail2ban pentesters") do |request| + Rack::Attack::Fail2Ban.filter(request.ip, maxretry: 2, findtime: 30, bantime: 60) do + request.path.include?("private-place") + end + end + end + + it "returns OK for many requests to non filtered path" do + get "/" + assert_equal 200, last_response.status + + get "/" + assert_equal 200, last_response.status + end + + it "forbids access to private path" do + get "/private-place" + assert_equal 403, last_response.status + end + + it "returns OK for non filtered path if yet not reached maxretry limit" do + get "/private-place" + assert_equal 403, last_response.status + + get "/" + assert_equal 200, last_response.status + end + + it "forbids all access after reaching maxretry limit" do + get "/private-place" + assert_equal 403, last_response.status + + get "/private-place" + assert_equal 403, last_response.status + + get "/" + assert_equal 403, last_response.status + end + + it "restores access after bantime elapsed" do + get "/private-place" + assert_equal 403, last_response.status + + get "/private-place" + assert_equal 403, last_response.status + + get "/" + assert_equal 403, last_response.status + + Timecop.travel(60) do + get "/" + + assert_equal 200, last_response.status + end + end + + it "does not forbid all access if maxrety condition is met but not within the findtime timespan" do + get "/private-place" + assert_equal 403, last_response.status + + Timecop.travel(31) do + get "/private-place" + assert_equal 403, last_response.status + + get "/" + assert_equal 200, last_response.status + end + end +end From 4d5a6936cec906378ca579341823edb343be9b94 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Fri, 16 Mar 2018 16:14:52 -0300 Subject: [PATCH 027/129] Acceptance test allow2ban --- spec/acceptance/allow2ban_spec.rb | 71 +++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 spec/acceptance/allow2ban_spec.rb diff --git a/spec/acceptance/allow2ban_spec.rb b/spec/acceptance/allow2ban_spec.rb new file mode 100644 index 0000000..5495c05 --- /dev/null +++ b/spec/acceptance/allow2ban_spec.rb @@ -0,0 +1,71 @@ +require_relative "../spec_helper" +require "timecop" + +describe "allow2ban" do + before do + Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new + + Rack::Attack.blocklist("allow2ban pentesters") do |request| + Rack::Attack::Allow2Ban.filter(request.ip, maxretry: 2, findtime: 30, bantime: 60) do + request.path.include?("scarce-resource") + end + end + end + + it "returns OK for many requests that doesn't match the filter" do + get "/" + assert_equal 200, last_response.status + + get "/" + assert_equal 200, last_response.status + end + + it "returns OK for first request that matches the filter" do + get "/scarce-resource" + assert_equal 200, last_response.status + end + + it "forbids all access after reaching maxretry limit" do + get "/scarce-resource" + assert_equal 200, last_response.status + + get "/scarce-resource" + assert_equal 200, last_response.status + + get "/scarce-resource" + assert_equal 403, last_response.status + + get "/" + assert_equal 403, last_response.status + end + + it "restores access after bantime elapsed" do + get "/scarce-resource" + assert_equal 200, last_response.status + + get "/scarce-resource" + assert_equal 200, last_response.status + + get "/" + assert_equal 403, last_response.status + + Timecop.travel(60) do + get "/" + + assert_equal 200, last_response.status + end + end + + it "does not forbid all access if maxrety condition is met but not within the findtime timespan" do + get "/scarce-resource" + assert_equal 200, last_response.status + + Timecop.travel(31) do + get "/scarce-resource" + assert_equal 200, last_response.status + + get "/" + assert_equal 200, last_response.status + end + end +end From 32ec6f778a3b8f0f3fc02fb549934a7d67461db0 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Fri, 16 Mar 2018 18:11:45 -0300 Subject: [PATCH 028/129] Acceptance test cache store config for fail2ban --- .../cache_store_config_for_fail2ban_spec.rb | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 spec/acceptance/cache_store_config_for_fail2ban_spec.rb diff --git a/spec/acceptance/cache_store_config_for_fail2ban_spec.rb b/spec/acceptance/cache_store_config_for_fail2ban_spec.rb new file mode 100644 index 0000000..320fb22 --- /dev/null +++ b/spec/acceptance/cache_store_config_for_fail2ban_spec.rb @@ -0,0 +1,62 @@ +require_relative "../spec_helper" + +describe "Cache store config when using fail2ban" do + before do + Rack::Attack.blocklist("fail2ban pentesters") do |request| + Rack::Attack::Fail2Ban.filter(request.ip, maxretry: 2, findtime: 30, bantime: 60) do + request.path.include?("private-place") + end + end + end + + it "gives error if no store was configured" do + assert_raises do + get "/" + end + end + + it "gives error if incompatible store was configured" do + Rack::Attack.cache.store = Object.new + + assert_raises do + get "/" + end + end + + it "works with any object that responds to #read, #write and #increment" do + basic_store_class = Class.new do + attr_accessor :backend + + def initialize + @backend = {} + end + + def read(key) + @backend[key] + end + + def write(key, value, options = {}) + @backend[key] = value + end + + def increment(key, count, options = {}) + @backend[key] ||= 0 + @backend[key] += 1 + end + end + + Rack::Attack.cache.store = basic_store_class.new + + get "/" + assert_equal 200, last_response.status + + get "/private-place" + assert_equal 403, last_response.status + + get "/private-place" + assert_equal 403, last_response.status + + get "/" + assert_equal 403, last_response.status + end +end From bde30e38d75bec7c3e7264e76cb787523f38f660 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Fri, 16 Mar 2018 18:20:27 -0300 Subject: [PATCH 029/129] Acceptance test cache store config for allow2ban --- .../cache_store_config_for_allow2ban_spec.rb | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 spec/acceptance/cache_store_config_for_allow2ban_spec.rb diff --git a/spec/acceptance/cache_store_config_for_allow2ban_spec.rb b/spec/acceptance/cache_store_config_for_allow2ban_spec.rb new file mode 100644 index 0000000..385697b --- /dev/null +++ b/spec/acceptance/cache_store_config_for_allow2ban_spec.rb @@ -0,0 +1,65 @@ +require_relative "../spec_helper" + +describe "Cache store config when using allow2ban" do + before do + Rack::Attack.blocklist("allow2ban pentesters") do |request| + Rack::Attack::Allow2Ban.filter(request.ip, maxretry: 2, findtime: 30, bantime: 60) do + request.path.include?("scarce-resource") + end + end + end + + it "gives error if no store was configured" do + assert_raises do + get "/" + end + end + + it "gives error if incompatible store was configured" do + Rack::Attack.cache.store = Object.new + + assert_raises do + get "/" + end + end + + it "works with any object that responds to #read, #write and #increment" do + basic_store_class = Class.new do + attr_accessor :backend + + def initialize + @backend = {} + end + + def read(key) + @backend[key] + end + + def write(key, value, options = {}) + @backend[key] = value + end + + def increment(key, count, options = {}) + @backend[key] ||= 0 + @backend[key] += 1 + end + end + + Rack::Attack.cache.store = basic_store_class.new + + get "/" + assert_equal 200, last_response.status + + get "/scarce-resource" + assert_equal 200, last_response.status + + get "/scarce-resource" + assert_equal 200, last_response.status + + get "/scarce-resource" + assert_equal 403, last_response.status + + get "/" + assert_equal 403, last_response.status + end +end From 3da1d36565e5e5d94f052b60f1c78e59f5500a79 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Mon, 19 Mar 2018 14:19:37 -0300 Subject: [PATCH 030/129] Fix jruby CI builds For jruby we need to stick with rubygems 2.7.4 until https://github.com/rubygems/rubygems/issues/2188 is fixed and released. Without this workaround, for jruby builds, rubygems activates jruby stdlib minitest (v5.4.1) instead of the bundled version (v5.11.3). --- .travis.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b019a2d..7efc8d3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,14 @@ rvm: - jruby-9.1.16.0 before_install: - - gem update --system + # For jruby we need to stick with rubygems 2.7.4 until + # https://github.com/rubygems/rubygems/issues/2188 + # is fixed and released. + # + # Without this workaround, for jruby builds, rubygems + # activates jruby stdlib minitest (v5.4.1) instead of the + # bundled version (v5.11.3). + - if [ "$TRAVIS_RUBY_VERSION" = "jruby-9.1.16.0" ]; then gem update --system 2.7.4; else gem update --system; fi - gem install bundler gemfile: From dd5fced799850aa0c68fc4ce6c6e72a6c40609ab Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Mon, 19 Mar 2018 15:30:12 -0300 Subject: [PATCH 031/129] Make jruby fix less error prone, by removing coupling with exact jruby version --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7efc8d3..7886e43 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,7 @@ before_install: # Without this workaround, for jruby builds, rubygems # activates jruby stdlib minitest (v5.4.1) instead of the # bundled version (v5.11.3). - - if [ "$TRAVIS_RUBY_VERSION" = "jruby-9.1.16.0" ]; then gem update --system 2.7.4; else gem update --system; fi + - if [ "${TRAVIS_RUBY_VERSION:0:5}" = "jruby" ]; then gem update --system 2.7.4; else gem update --system; fi - gem install bundler gemfile: From 5e0cd031b66651033ea8148c829fca00cd142b4c Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 20 Mar 2018 10:24:25 -0300 Subject: [PATCH 032/129] Acceptance test throttle Retry-After header --- spec/acceptance/throttling_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/acceptance/throttling_spec.rb b/spec/acceptance/throttling_spec.rb index e157f50..2ad4605 100644 --- a/spec/acceptance/throttling_spec.rb +++ b/spec/acceptance/throttling_spec.rb @@ -16,6 +16,8 @@ describe "#throttle" do get "/", {}, "REMOTE_ADDR" => "1.2.3.4" assert_equal 429, last_response.status + assert_equal "60", last_response.headers["Retry-After"] + assert_equal "Retry later\n", last_response.body get "/", {}, "REMOTE_ADDR" => "5.6.7.8" From 330d25c8324438c356e681646b7d1be2d28901b2 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 20 Mar 2018 11:42:27 -0300 Subject: [PATCH 033/129] Acceptance test cache store config when Rails is present --- rack-attack.gemspec | 2 ++ .../cache_store_config_with_rails_spec.rb | 31 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 spec/acceptance/cache_store_config_with_rails_spec.rb diff --git a/rack-attack.gemspec b/rack-attack.gemspec index 07f2931..b5a1909 100644 --- a/rack-attack.gemspec +++ b/rack-attack.gemspec @@ -23,7 +23,9 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 2.0.0' s.add_dependency 'rack' + s.add_development_dependency 'minitest' + s.add_development_dependency "minitest-stub-const" s.add_development_dependency 'rack-test' s.add_development_dependency 'rake' s.add_development_dependency 'appraisal' diff --git a/spec/acceptance/cache_store_config_with_rails_spec.rb b/spec/acceptance/cache_store_config_with_rails_spec.rb new file mode 100644 index 0000000..bd87828 --- /dev/null +++ b/spec/acceptance/cache_store_config_with_rails_spec.rb @@ -0,0 +1,31 @@ +require_relative "../spec_helper" +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 + end + end + + 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 + + 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 + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal 429, last_response.status + end + end +end From e17d2d897475bf617376266f8989e6ec5555668a Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 20 Mar 2018 19:06:54 -0300 Subject: [PATCH 034/129] Acceptance test throttling with a dynamic limit --- spec/acceptance/throttling_spec.rb | 35 +++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/spec/acceptance/throttling_spec.rb b/spec/acceptance/throttling_spec.rb index 2ad4605..e7b2361 100644 --- a/spec/acceptance/throttling_spec.rb +++ b/spec/acceptance/throttling_spec.rb @@ -2,9 +2,11 @@ require_relative "../spec_helper" require "timecop" describe "#throttle" do - it "allows one request per minute by IP" do + before do Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new + end + it "allows one request per minute by IP" do Rack::Attack.throttle("by ip", limit: 1, period: 60) do |request| request.ip end @@ -29,4 +31,35 @@ describe "#throttle" do assert_equal 200, last_response.status end end + + it "supports limit to be dynamic" do + # Could be used to have different rate limits for authorized + # vs general requests + limit_proc = lambda do |request| + if request.get_header("X-APIKey") == "private-secret" + 2 + else + 1 + end + end + + Rack::Attack.throttle("by ip", limit: limit_proc, period: 60) do |request| + request.ip + end + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + assert_equal 200, last_response.status + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + assert_equal 429, last_response.status + + get "/", {}, "REMOTE_ADDR" => "5.6.7.8", "X-APIKey" => "private-secret" + assert_equal 200, last_response.status + + get "/", {}, "REMOTE_ADDR" => "5.6.7.8", "X-APIKey" => "private-secret" + assert_equal 200, last_response.status + + get "/", {}, "REMOTE_ADDR" => "5.6.7.8", "X-APIKey" => "private-secret" + assert_equal 429, last_response.status + end end From 5004b04ac70e65399f622b7a1bdea8ede121ce65 Mon Sep 17 00:00:00 2001 From: Domenoth Date: Thu, 11 Jan 2018 17:42:17 -0800 Subject: [PATCH 035/129] Change object type yielded to ActiveSupport::Subscribers https://github.com/kickstarter/rack-attack/issues/255 Change the object type from instances of type Rack::Attack::Request to instances of type Hash. (`req` becomes `request: req`). --- README.md | 7 +++--- lib/rack/attack.rb | 2 +- spec/acceptance/track_spec.rb | 6 ++--- spec/acceptance/track_throttle_spec.rb | 6 ++--- spec/rack_attack_instrumentation_spec.rb | 31 ++++++++++++++++++++++++ 5 files changed, 42 insertions(+), 10 deletions(-) create mode 100644 spec/rack_attack_instrumentation_spec.rb diff --git a/README.md b/README.md index d977edd..a31079f 100644 --- a/README.md +++ b/README.md @@ -215,7 +215,8 @@ Rack::Attack.track("special_agent", limit: 6, period: 60) do |req| end # Track it using ActiveSupport::Notification -ActiveSupport::Notifications.subscribe("rack.attack") do |name, start, finish, request_id, req| +ActiveSupport::Notifications.subscribe("rack.attack") do |name, start, finish, request_id, payload| + req = payload[:request] if req.env['rack.attack.matched'] == "special_agent" && req.env['rack.attack.match_type'] == :track Rails.logger.info "special_agent: #{req.path}" STATSD.increment("special_agent") @@ -283,8 +284,8 @@ Rack::Attack uses the [ActiveSupport::Notifications](http://api.rubyonrails.org/ You can subscribe to 'rack.attack' events and log it, graph it, etc: ```ruby -ActiveSupport::Notifications.subscribe('rack.attack') do |name, start, finish, request_id, req| - puts req.inspect +ActiveSupport::Notifications.subscribe('rack.attack') do |name, start, finish, request_id, payload| + puts payload[:request].inspect end ``` diff --git a/lib/rack/attack.rb b/lib/rack/attack.rb index 8c9df48..0b102d7 100644 --- a/lib/rack/attack.rb +++ b/lib/rack/attack.rb @@ -100,7 +100,7 @@ class Rack::Attack end def instrument(req) - notifier.instrument('rack.attack', req) if notifier + notifier.instrument('rack.attack', request: req) if notifier end def cache diff --git a/spec/acceptance/track_spec.rb b/spec/acceptance/track_spec.rb index 7f4b393..928cc31 100644 --- a/spec/acceptance/track_spec.rb +++ b/spec/acceptance/track_spec.rb @@ -9,9 +9,9 @@ describe "#track" do notification_matched = nil notification_type = nil - ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, request| - notification_matched = request.env["rack.attack.matched"] - notification_type = request.env["rack.attack.match_type"] + ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, payload| + notification_matched = payload[:request].env["rack.attack.matched"] + notification_type = payload[:request].env["rack.attack.match_type"] end get "/", {}, "REMOTE_ADDR" => "5.6.7.8" diff --git a/spec/acceptance/track_throttle_spec.rb b/spec/acceptance/track_throttle_spec.rb index 7446ce4..f3f8a80 100644 --- a/spec/acceptance/track_throttle_spec.rb +++ b/spec/acceptance/track_throttle_spec.rb @@ -12,9 +12,9 @@ describe "#track with throttle-ish options" do notification_matched = nil notification_type = nil - ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, request| - notification_matched = request.env["rack.attack.matched"] - notification_type = request.env["rack.attack.match_type"] + ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, payload| + notification_matched = payload[:request].env["rack.attack.matched"] + notification_type = payload[:request].env["rack.attack.match_type"] end get "/", {}, "REMOTE_ADDR" => "1.2.3.4" diff --git a/spec/rack_attack_instrumentation_spec.rb b/spec/rack_attack_instrumentation_spec.rb new file mode 100644 index 0000000..88883d0 --- /dev/null +++ b/spec/rack_attack_instrumentation_spec.rb @@ -0,0 +1,31 @@ +# ActiveSupport::Subscribers added in ~> 4.0.2.0 +if ActiveSupport::VERSION::MAJOR > 3 + require_relative 'spec_helper' + require 'active_support/subscriber' + class CustomSubscriber < ActiveSupport::Subscriber + def rack(event) + # Do virtually (but not) nothing. + event.inspect + end + 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 } + end + + describe "with throttling" do + before do + ActiveSupport::Notifications.stub(:notifier, ActiveSupport::Notifications::Fanout.new) do + CustomSubscriber.attach_to("attack") + 2.times { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' } + end + end + it 'should instrument without error' do + last_response.status.must_equal 429 + end + end + end +end From 08b2cc4d950cceb80d6fbbf886ac33fd2e9737cf Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Wed, 21 Mar 2018 17:10:27 -0300 Subject: [PATCH 036/129] Acceptance test throttling with a dynamic period --- spec/acceptance/throttling_spec.rb | 50 ++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/spec/acceptance/throttling_spec.rb b/spec/acceptance/throttling_spec.rb index e7b2361..233016c 100644 --- a/spec/acceptance/throttling_spec.rb +++ b/spec/acceptance/throttling_spec.rb @@ -62,4 +62,54 @@ describe "#throttle" do get "/", {}, "REMOTE_ADDR" => "5.6.7.8", "X-APIKey" => "private-secret" assert_equal 429, last_response.status end + + it "supports period to be dynamic" do + # Could be used to have different rate limits for authorized + # vs general requests + period_proc = lambda do |request| + if request.get_header("X-APIKey") == "private-secret" + 10 + else + 30 + end + end + + Rack::Attack.throttle("by ip", limit: 1, period: period_proc) do |request| + request.ip + end + + # Using Time#at to align to start/end of periods exactly + # to achieve consistenty in different test runs + + Timecop.travel(Time.at(0)) do + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + assert_equal 200, last_response.status + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + assert_equal 429, last_response.status + end + + Timecop.travel(Time.at(10)) do + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + assert_equal 429, last_response.status + end + + Timecop.travel(Time.at(30)) do + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + assert_equal 200, last_response.status + end + + Timecop.travel(Time.at(0)) do + get "/", {}, "REMOTE_ADDR" => "5.6.7.8", "X-APIKey" => "private-secret" + assert_equal 200, last_response.status + + get "/", {}, "REMOTE_ADDR" => "5.6.7.8", "X-APIKey" => "private-secret" + assert_equal 429, last_response.status + end + + Timecop.travel(Time.at(10)) do + get "/", {}, "REMOTE_ADDR" => "5.6.7.8", "X-APIKey" => "private-secret" + assert_equal 200, last_response.status + end + end end From 0ff1b5be83189f5af5f526cbfb37f2c8ef3c7302 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Wed, 21 Mar 2018 17:29:44 -0300 Subject: [PATCH 037/129] Make throttling_spec work when running it with rails 4.2 --- spec/acceptance/throttling_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/acceptance/throttling_spec.rb b/spec/acceptance/throttling_spec.rb index 233016c..396589a 100644 --- a/spec/acceptance/throttling_spec.rb +++ b/spec/acceptance/throttling_spec.rb @@ -36,7 +36,7 @@ describe "#throttle" do # Could be used to have different rate limits for authorized # vs general requests limit_proc = lambda do |request| - if request.get_header("X-APIKey") == "private-secret" + if request.env["X-APIKey"] == "private-secret" 2 else 1 @@ -67,7 +67,7 @@ describe "#throttle" do # Could be used to have different rate limits for authorized # vs general requests period_proc = lambda do |request| - if request.get_header("X-APIKey") == "private-secret" + if request.env["X-APIKey"] == "private-secret" 10 else 30 From ba91e2341947304e0df160643592178059813be7 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Thu, 22 Mar 2018 10:16:02 -0300 Subject: [PATCH 038/129] Acceptance test ability to extend the request object --- .../extending_request_object_spec.rb | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 spec/acceptance/extending_request_object_spec.rb diff --git a/spec/acceptance/extending_request_object_spec.rb b/spec/acceptance/extending_request_object_spec.rb new file mode 100644 index 0000000..f6e0d0f --- /dev/null +++ b/spec/acceptance/extending_request_object_spec.rb @@ -0,0 +1,34 @@ +require_relative "../spec_helper" + +describe "Extending the request object" do + before do + class Rack::Attack::Request + def authorized? + env["APIKey"] == "private-secret" + end + end + + Rack::Attack.blocklist("unauthorized requests") do |request| + !request.authorized? + end + end + + # We don't want the extension to leak to other test cases + after do + class Rack::Attack::Request + remove_method :authorized? + end + end + + it "forbids request if blocklist condition is true" do + get "/" + + assert_equal 403, last_response.status + end + + it "succeeds if blocklist condition is false" do + get "/", {}, "APIKey" => "private-secret" + + assert_equal 200, last_response.status + end +end From da1f54b6fcbc0f2a4f16b85c5f6091568d5667ff Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Thu, 22 Mar 2018 11:44:41 -0300 Subject: [PATCH 039/129] Acceptance test ability to access match data in #blocklisted_response --- .../customizing_blocked_response_spec.rb | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/spec/acceptance/customizing_blocked_response_spec.rb b/spec/acceptance/customizing_blocked_response_spec.rb index 7f3f307..190f5c0 100644 --- a/spec/acceptance/customizing_blocked_response_spec.rb +++ b/spec/acceptance/customizing_blocked_response_spec.rb @@ -1,11 +1,13 @@ require_relative "../spec_helper" describe "Customizing block responses" do - it "can be customized" do + before do Rack::Attack.blocklist("block 1.2.3.4") do |request| request.ip == "1.2.3.4" end + end + it "can be customized" do get "/", {}, "REMOTE_ADDR" => "1.2.3.4" assert_equal 403, last_response.status @@ -19,4 +21,21 @@ describe "Customizing block responses" do assert_equal 503, last_response.status assert_equal "Blocked", last_response.body end + + it "exposes match data" do + matched = nil + match_type = nil + + Rack::Attack.blocklisted_response = lambda do |env| + matched = env['rack.attack.matched'] + match_type = env['rack.attack.match_type'] + + [503, {}, ["Blocked"]] + end + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal "block 1.2.3.4", matched + assert_equal :blocklist, match_type + end end From 3f5574c4e41439ff2142ecd2b287d66c7c450404 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Thu, 22 Mar 2018 11:48:56 -0300 Subject: [PATCH 040/129] Acceptance test ability to access match data in #throttled_response --- .../customizing_throttled_response_spec.rb | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/spec/acceptance/customizing_throttled_response_spec.rb b/spec/acceptance/customizing_throttled_response_spec.rb index 7f66372..5f657aa 100644 --- a/spec/acceptance/customizing_throttled_response_spec.rb +++ b/spec/acceptance/customizing_throttled_response_spec.rb @@ -1,13 +1,15 @@ require_relative "../spec_helper" describe "Customizing throttled response" do - it "can be customized" do + before do Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new Rack::Attack.throttle("by ip", limit: 1, period: 60) do |request| request.ip end + end + it "can be customized" do get "/", {}, "REMOTE_ADDR" => "1.2.3.4" assert_equal 200, last_response.status @@ -25,4 +27,33 @@ describe "Customizing throttled response" do assert_equal 503, last_response.status assert_equal "Throttled", last_response.body end + + it "exposes match data" do + matched = nil + match_type = nil + match_data = nil + match_discriminator = nil + + Rack::Attack.throttled_response = lambda do |env| + matched = env['rack.attack.matched'] + match_type = env['rack.attack.match_type'] + match_data = env['rack.attack.match_data'] + match_discriminator = env['rack.attack.match_discriminator'] + + [429, {}, ["Throttled"]] + end + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal "by ip", matched + assert_equal :throttle, match_type + assert_equal 60, match_data[:period] + assert_equal 1, match_data[:limit] + assert_equal 2, match_data[:count] + assert_equal "1.2.3.4", match_discriminator + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + assert_equal 3, match_data[:count] + end end From 0fe30e3a3deb92ce58b7d5087d7f2ce4e3b2c8fc Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Fri, 23 Mar 2018 10:58:47 -0300 Subject: [PATCH 041/129] Don't autoload when it's barely valuable to do so Rack::Attack::PathNormalizer and Rack::Attack::Request are both used in #call method, which is going to be used by every rack-attack user as long as they insert the middleware in their app. --- lib/rack/attack.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/rack/attack.rb b/lib/rack/attack.rb index 8c9df48..dd151fd 100644 --- a/lib/rack/attack.rb +++ b/lib/rack/attack.rb @@ -1,12 +1,13 @@ require 'rack' require 'forwardable' +require 'rack/attack/path_normalizer' +require 'rack/attack/request' class Rack::Attack class MisconfiguredStoreError < StandardError; end class MissingStoreError < StandardError; end autoload :Cache, 'rack/attack/cache' - autoload :PathNormalizer, 'rack/attack/path_normalizer' autoload :Check, 'rack/attack/check' autoload :Throttle, 'rack/attack/throttle' autoload :Safelist, 'rack/attack/safelist' @@ -18,7 +19,6 @@ class Rack::Attack autoload :RedisStoreProxy, 'rack/attack/store_proxy/redis_store_proxy' autoload :Fail2Ban, 'rack/attack/fail2ban' autoload :Allow2Ban, 'rack/attack/allow2ban' - autoload :Request, 'rack/attack/request' class << self From 7a87ca2ff7082afeb8a8c7c6f33039ccdd37ddf9 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Fri, 23 Mar 2018 11:39:26 -0300 Subject: [PATCH 042/129] Give clearer error message for misconfigured cache store for allow/fail2ban --- CHANGELOG.md | 4 ++ lib/rack/attack/cache.rb | 32 +++++++--- .../cache_store_config_for_allow2ban_spec.rb | 60 ++++++++++++++++--- .../cache_store_config_for_fail2ban_spec.rb | 60 ++++++++++++++++--- 4 files changed, 133 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e582ac..e114486 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added + +- Throw helpful error message when using `allow2ban` but cache store is misconfigured ([#315](https://github.com/kickstarter/rack-attack/issues/315)) +- Throw helpful error message when using `fail2ban` but cache store is misconfigured ([#315](https://github.com/kickstarter/rack-attack/issues/315)) ## [5.1.0] - 2018-03-10 diff --git a/lib/rack/attack/cache.rb b/lib/rack/attack/cache.rb index aec048b..865e869 100644 --- a/lib/rack/attack/cache.rb +++ b/lib/rack/attack/cache.rb @@ -20,6 +20,9 @@ module Rack end def read(unprefixed_key) + enforce_store_presence! + enforce_store_method_presence!(:read) + store.read("#{prefix}:#{unprefixed_key}") end @@ -46,18 +49,29 @@ module Rack end def do_count(key, expires_in) + enforce_store_presence! + enforce_store_method_presence!(:increment) + + result = store.increment(key, 1, :expires_in => expires_in) + + # NB: Some stores return nil when incrementing uninitialized values + if result.nil? + enforce_store_method_presence!(:write) + + store.write(key, 1, :expires_in => expires_in) + end + result || 1 + end + + def enforce_store_presence! if store.nil? raise Rack::Attack::MissingStoreError - elsif !store.respond_to?(:increment) - raise Rack::Attack::MisconfiguredStoreError, "Store needs to respond to #increment" - else - result = store.increment(key, 1, :expires_in => expires_in) + end + end - # NB: Some stores return nil when incrementing uninitialized values - if result.nil? - store.write(key, 1, :expires_in => expires_in) - end - result || 1 + def enforce_store_method_presence!(method_name) + if !store.respond_to?(method_name) + raise Rack::Attack::MisconfiguredStoreError, "Store needs to respond to ##{method_name}" end end end diff --git a/spec/acceptance/cache_store_config_for_allow2ban_spec.rb b/spec/acceptance/cache_store_config_for_allow2ban_spec.rb index 385697b..aa7208e 100644 --- a/spec/acceptance/cache_store_config_for_allow2ban_spec.rb +++ b/spec/acceptance/cache_store_config_for_allow2ban_spec.rb @@ -9,18 +9,64 @@ describe "Cache store config when using allow2ban" do end end - it "gives error if no store was configured" do - assert_raises do - get "/" + it "gives semantic error if no store was configured" do + assert_raises(Rack::Attack::MissingStoreError) do + get "/scarce-resource" end end - it "gives error if incompatible store was configured" do - Rack::Attack.cache.store = Object.new + it "gives semantic error if store is missing #read method" do + basic_store_class = Class.new do + def write(key, value) + end - assert_raises do - get "/" + def increment(key, count, options = {}) + end end + + Rack::Attack.cache.store = basic_store_class.new + + raised_exception = assert_raises(Rack::Attack::MisconfiguredStoreError) do + get "/scarce-resource" + end + + assert_equal "Store needs to respond to #read", raised_exception.message + end + + it "gives semantic error if store is missing #write method" do + basic_store_class = Class.new do + def read(key) + end + + def increment(key, count, options = {}) + end + end + + Rack::Attack.cache.store = basic_store_class.new + + raised_exception = assert_raises(Rack::Attack::MisconfiguredStoreError) do + get "/scarce-resource" + end + + assert_equal "Store needs to respond to #write", raised_exception.message + end + + it "gives semantic error if store is missing #increment method" do + basic_store_class = Class.new do + def read(key) + end + + def write(key, value) + end + end + + Rack::Attack.cache.store = basic_store_class.new + + raised_exception = assert_raises(Rack::Attack::MisconfiguredStoreError) do + get "/scarce-resource" + end + + assert_equal "Store needs to respond to #increment", raised_exception.message end it "works with any object that responds to #read, #write and #increment" do diff --git a/spec/acceptance/cache_store_config_for_fail2ban_spec.rb b/spec/acceptance/cache_store_config_for_fail2ban_spec.rb index 320fb22..53b43e5 100644 --- a/spec/acceptance/cache_store_config_for_fail2ban_spec.rb +++ b/spec/acceptance/cache_store_config_for_fail2ban_spec.rb @@ -9,18 +9,64 @@ describe "Cache store config when using fail2ban" do end end - it "gives error if no store was configured" do - assert_raises do - get "/" + it "gives semantic error if no store was configured" do + assert_raises(Rack::Attack::MissingStoreError) do + get "/private-place" end end - it "gives error if incompatible store was configured" do - Rack::Attack.cache.store = Object.new + it "gives semantic error if store is missing #read method" do + basic_store_class = Class.new do + def write(key, value) + end - assert_raises do - get "/" + def increment(key, count, options = {}) + end end + + Rack::Attack.cache.store = basic_store_class.new + + raised_exception = assert_raises(Rack::Attack::MisconfiguredStoreError) do + get "/private-place" + end + + assert_equal "Store needs to respond to #read", raised_exception.message + end + + it "gives semantic error if store is missing #write method" do + basic_store_class = Class.new do + def read(key) + end + + def increment(key, count, options = {}) + end + end + + Rack::Attack.cache.store = basic_store_class.new + + raised_exception = assert_raises(Rack::Attack::MisconfiguredStoreError) do + get "/private-place" + end + + assert_equal "Store needs to respond to #write", raised_exception.message + end + + it "gives semantic error if store is missing #increment method" do + basic_store_class = Class.new do + def read(key) + end + + def write(key, value) + end + end + + Rack::Attack.cache.store = basic_store_class.new + + raised_exception = assert_raises(Rack::Attack::MisconfiguredStoreError) do + get "/private-place" + end + + assert_equal "Store needs to respond to #increment", raised_exception.message end it "works with any object that responds to #read, #write and #increment" do From 21fe32b895c61daf03aebde3a4244bb43720ff03 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Mon, 26 Mar 2018 16:00:32 -0300 Subject: [PATCH 043/129] Acceptance test ability to subscribe to blocking events --- spec/acceptance/blocking_spec.rb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/spec/acceptance/blocking_spec.rb b/spec/acceptance/blocking_spec.rb index 2e9a562..f245a6b 100644 --- a/spec/acceptance/blocking_spec.rb +++ b/spec/acceptance/blocking_spec.rb @@ -18,4 +18,24 @@ describe "#blocklist" do assert_equal 200, last_response.status end + + it "notifies when the request is blocked" do + notification_matched = nil + notification_type = nil + + ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, request| + notification_matched = request.env["rack.attack.matched"] + notification_type = request.env["rack.attack.match_type"] + end + + get "/", {}, "REMOTE_ADDR" => "5.6.7.8" + + assert_nil notification_matched + assert_nil notification_type + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal "block 1.2.3.4", notification_matched + assert_equal :blocklist, notification_type + end end From 576a97c2a56dc9a79c01ece89e632c2d6815b0ba Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Mon, 26 Mar 2018 16:16:44 -0300 Subject: [PATCH 044/129] Acceptance test ability to subscribe to throttling events --- spec/acceptance/throttling_spec.rb | 44 ++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/spec/acceptance/throttling_spec.rb b/spec/acceptance/throttling_spec.rb index 396589a..47bc5d8 100644 --- a/spec/acceptance/throttling_spec.rb +++ b/spec/acceptance/throttling_spec.rb @@ -112,4 +112,48 @@ describe "#throttle" do assert_equal 200, last_response.status end end + + it "notifies when the request is throttled" do + Rack::Attack.throttle("by ip", limit: 1, period: 60) do |request| + request.ip + end + + notification_matched = nil + notification_type = nil + notification_data = nil + notification_discriminator = nil + + ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, request| + notification_matched = request.env["rack.attack.matched"] + notification_type = request.env["rack.attack.match_type"] + notification_data = request.env['rack.attack.match_data'] + notification_discriminator = request.env['rack.attack.match_discriminator'] + end + + get "/", {}, "REMOTE_ADDR" => "5.6.7.8" + + assert_equal 200, last_response.status + assert_nil notification_matched + assert_nil notification_type + assert_nil notification_data + assert_nil notification_discriminator + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal 200, last_response.status + assert_nil notification_matched + assert_nil notification_type + assert_nil notification_data + assert_nil notification_discriminator + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal 429, last_response.status + assert_equal "by ip", notification_matched + assert_equal :throttle, notification_type + assert_equal 60, notification_data[:period] + assert_equal 1, notification_data[:limit] + assert_equal 2, notification_data[:count] + assert_equal "1.2.3.4", notification_discriminator + end end From 4862ca5a0034bee8c5e0874c7d3cbf92c553e9c0 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Mon, 26 Mar 2018 16:20:10 -0300 Subject: [PATCH 045/129] Acceptance test ability to subscribe to safelisting events --- spec/acceptance/safelisting_spec.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/spec/acceptance/safelisting_spec.rb b/spec/acceptance/safelisting_spec.rb index 743d97e..8f4289d 100644 --- a/spec/acceptance/safelisting_spec.rb +++ b/spec/acceptance/safelisting_spec.rb @@ -34,4 +34,20 @@ describe "#safelist" do assert_equal 200, last_response.status end + + it "notifies when the request is safe" do + notification_matched = nil + notification_type = nil + + ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, request| + notification_matched = request.env["rack.attack.matched"] + notification_type = request.env["rack.attack.match_type"] + end + + get "/safe_space", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal 200, last_response.status + assert_equal "safe path", notification_matched + assert_equal :safelist, notification_type + end end From dccce4ee3db688ba044f6c77db90eb65f94045b2 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Mon, 26 Mar 2018 17:33:58 -0300 Subject: [PATCH 046/129] Provide shorthand to blocklist an IP --- CHANGELOG.md | 1 + lib/rack/attack.rb | 17 ++++++++++--- lib/rack/attack/check.rb | 1 + spec/acceptance/blocking_ip_spec.rb | 38 +++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 spec/acceptance/blocking_ip_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index e114486..95bd8ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. ### Added +- Shorthand for blocking IP addresses `Rack::Attack.blocklist_ip("1.2.3.4")` - Throw helpful error message when using `allow2ban` but cache store is misconfigured ([#315](https://github.com/kickstarter/rack-attack/issues/315)) - Throw helpful error message when using `fail2ban` but cache store is misconfigured ([#315](https://github.com/kickstarter/rack-attack/issues/315)) diff --git a/lib/rack/attack.rb b/lib/rack/attack.rb index dd151fd..f630167 100644 --- a/lib/rack/attack.rb +++ b/lib/rack/attack.rb @@ -37,6 +37,12 @@ class Rack::Attack self.blocklists[name] = Blocklist.new(name, block) end + def blocklist_ip(ip) + @ip_blocklists ||= [] + ip_blocklist_proc = lambda { |request| request.ip == ip } + @ip_blocklists << Blocklist.new(nil, ip_blocklist_proc) + end + def blacklist(name, &block) warn "[DEPRECATION] 'Rack::Attack.blacklist' is deprecated. Please use 'blocklist' instead." blocklist(name, &block) @@ -77,9 +83,8 @@ class Rack::Attack end def blocklisted?(req) - blocklists.any? do |name, blocklist| - blocklist[req] - end + ip_blocklists.any? { |blocklist| blocklist.match?(req) } || + blocklists.any? { |_name, blocklist| blocklist.match?(req) } end def blacklisted?(req) @@ -109,6 +114,7 @@ class Rack::Attack def clear! @safelists, @blocklists, @throttles, @tracks = {}, {}, {}, {} + @ip_blocklists = [] end def blacklisted_response=(res) @@ -121,6 +127,11 @@ class Rack::Attack blocklisted_response end + private + + def ip_blocklists + @ip_blocklists ||= [] + end end # Set defaults diff --git a/lib/rack/attack/check.rb b/lib/rack/attack/check.rb index 21451de..8ae9ba1 100644 --- a/lib/rack/attack/check.rb +++ b/lib/rack/attack/check.rb @@ -17,6 +17,7 @@ module Rack } end + alias_method :match?, :[] end end end diff --git a/spec/acceptance/blocking_ip_spec.rb b/spec/acceptance/blocking_ip_spec.rb new file mode 100644 index 0000000..72c599c --- /dev/null +++ b/spec/acceptance/blocking_ip_spec.rb @@ -0,0 +1,38 @@ +require_relative "../spec_helper" + +describe "Blocking an IP" do + before do + Rack::Attack.blocklist_ip("1.2.3.4") + end + + it "forbids request if IP matches" do + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal 403, last_response.status + end + + it "succeeds if IP doesn't match" do + get "/", {}, "REMOTE_ADDR" => "5.6.7.8" + + assert_equal 200, last_response.status + end + + it "notifies when the request is blocked" do + notified = false + notification_type = nil + + ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, request| + notified = true + notification_type = request.env["rack.attack.match_type"] + end + + get "/", {}, "REMOTE_ADDR" => "5.6.7.8" + + refute notified + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert notified + assert_equal :blocklist, notification_type + end +end From aec03047c56a66f6bfbd9dafa81c333a508d8630 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Mon, 26 Mar 2018 17:35:41 -0300 Subject: [PATCH 047/129] Provide shorthand to blocklist an entire IP subnet --- CHANGELOG.md | 1 + lib/rack/attack.rb | 2 +- spec/acceptance/blocking_subnet_spec.rb | 44 +++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 spec/acceptance/blocking_subnet_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 95bd8ce..146aee9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file. ### Added - Shorthand for blocking IP addresses `Rack::Attack.blocklist_ip("1.2.3.4")` +- Shorthand for blocking IP subnets `Rack::Attack.blocklist_ip("1.2.0.0/16")` - Throw helpful error message when using `allow2ban` but cache store is misconfigured ([#315](https://github.com/kickstarter/rack-attack/issues/315)) - Throw helpful error message when using `fail2ban` but cache store is misconfigured ([#315](https://github.com/kickstarter/rack-attack/issues/315)) diff --git a/lib/rack/attack.rb b/lib/rack/attack.rb index f630167..836f7a9 100644 --- a/lib/rack/attack.rb +++ b/lib/rack/attack.rb @@ -39,7 +39,7 @@ class Rack::Attack def blocklist_ip(ip) @ip_blocklists ||= [] - ip_blocklist_proc = lambda { |request| request.ip == ip } + ip_blocklist_proc = lambda { |request| IPAddr.new(ip).include?(IPAddr.new(request.ip)) } @ip_blocklists << Blocklist.new(nil, ip_blocklist_proc) end diff --git a/spec/acceptance/blocking_subnet_spec.rb b/spec/acceptance/blocking_subnet_spec.rb new file mode 100644 index 0000000..13b4f91 --- /dev/null +++ b/spec/acceptance/blocking_subnet_spec.rb @@ -0,0 +1,44 @@ +require_relative "../spec_helper" + +describe "Blocking an IP subnet" do + before do + Rack::Attack.blocklist_ip("1.2.3.4/31") + end + + it "forbids request if IP is inside the subnet" do + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal 403, last_response.status + end + + it "forbids request for another IP in the subnet" do + get "/", {}, "REMOTE_ADDR" => "1.2.3.5" + + assert_equal 403, last_response.status + end + + it "succeeds if IP is outside the subnet" do + get "/", {}, "REMOTE_ADDR" => "1.2.3.6" + + assert_equal 200, last_response.status + end + + it "notifies when the request is blocked" do + notified = false + notification_type = nil + + ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, request| + notified = true + notification_type = request.env["rack.attack.match_type"] + end + + get "/", {}, "REMOTE_ADDR" => "5.6.7.8" + + refute notified + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert notified + assert_equal :blocklist, notification_type + end +end From 27aab72d490002a995140faf802252ab75730a8d Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Mon, 26 Mar 2018 17:50:23 -0300 Subject: [PATCH 048/129] Provide shorthand to safelist an IP --- CHANGELOG.md | 3 +- lib/rack/attack.rb | 16 +++++++-- spec/acceptance/safelisting_ip_spec.rb | 49 ++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 spec/acceptance/safelisting_ip_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 146aee9..59223bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,9 @@ All notable changes to this project will be documented in this file. ### Added -- Shorthand for blocking IP addresses `Rack::Attack.blocklist_ip("1.2.3.4")` +- Shorthand for blocking an IP address `Rack::Attack.blocklist_ip("1.2.3.4")` - Shorthand for blocking IP subnets `Rack::Attack.blocklist_ip("1.2.0.0/16")` +- Shorthand for safelisting an IP address `Rack::Attack.safelist_ip("5.6.7.8")` - Throw helpful error message when using `allow2ban` but cache store is misconfigured ([#315](https://github.com/kickstarter/rack-attack/issues/315)) - Throw helpful error message when using `fail2ban` but cache store is misconfigured ([#315](https://github.com/kickstarter/rack-attack/issues/315)) diff --git a/lib/rack/attack.rb b/lib/rack/attack.rb index 836f7a9..4abca17 100644 --- a/lib/rack/attack.rb +++ b/lib/rack/attack.rb @@ -43,6 +43,12 @@ class Rack::Attack @ip_blocklists << Blocklist.new(nil, ip_blocklist_proc) end + def safelist_ip(ip) + @ip_safelists ||= [] + ip_safelist_proc = lambda { |request| ip == request.ip } + @ip_safelists << Safelist.new(nil, ip_safelist_proc) + end + def blacklist(name, &block) warn "[DEPRECATION] 'Rack::Attack.blacklist' is deprecated. Please use 'blocklist' instead." blocklist(name, &block) @@ -72,9 +78,8 @@ class Rack::Attack end def safelisted?(req) - safelists.any? do |name, safelist| - safelist[req] - end + ip_safelists.any? { |safelist| safelist.match?(req) } || + safelists.any? { |_name, safelist| safelist.match?(req) } end def whitelisted?(req) @@ -115,6 +120,7 @@ class Rack::Attack def clear! @safelists, @blocklists, @throttles, @tracks = {}, {}, {}, {} @ip_blocklists = [] + @ip_safelists = [] end def blacklisted_response=(res) @@ -132,6 +138,10 @@ class Rack::Attack def ip_blocklists @ip_blocklists ||= [] end + + def ip_safelists + @ip_safelists ||= [] + end end # Set defaults diff --git a/spec/acceptance/safelisting_ip_spec.rb b/spec/acceptance/safelisting_ip_spec.rb new file mode 100644 index 0000000..38faf72 --- /dev/null +++ b/spec/acceptance/safelisting_ip_spec.rb @@ -0,0 +1,49 @@ +require_relative "../spec_helper" + +describe "Safelist an IP" do + before do + Rack::Attack.blocklist("admin") do |request| + request.path == "/admin" + end + + Rack::Attack.safelist_ip("5.6.7.8") + end + + it "forbids request if blocklist condition is true and safelist is false" do + get "/admin", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal 403, last_response.status + end + + it "succeeds if blocklist condition is false and safelist is false" do + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal 200, last_response.status + end + + it "succeeds request if blocklist condition is false and safelist is true" do + get "/", {}, "REMOTE_ADDR" => "5.6.7.8" + + assert_equal 200, last_response.status + end + + it "succeeds request if both blocklist and safelist conditions are true" do + get "/admin", {}, "REMOTE_ADDR" => "5.6.7.8" + + assert_equal 200, last_response.status + end + + it "notifies when the request is safe" do + notification_type = nil + + ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, request| + notification_type = request.env["rack.attack.match_type"] + end + + get "/admin", {}, "REMOTE_ADDR" => "5.6.7.8" + + assert_equal 200, last_response.status + assert_equal :safelist, notification_type + end +end + From e907cc6b8342e72751b1cbcfac872799e3230321 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Mon, 26 Mar 2018 18:00:02 -0300 Subject: [PATCH 049/129] Provide shorthand to safelist an entire IP subnet --- CHANGELOG.md | 3 +- lib/rack/attack.rb | 2 +- spec/acceptance/safelisting_subnet_spec.rb | 48 ++++++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 spec/acceptance/safelisting_subnet_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 59223bc..afce862 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,9 @@ All notable changes to this project will be documented in this file. ### Added - Shorthand for blocking an IP address `Rack::Attack.blocklist_ip("1.2.3.4")` -- Shorthand for blocking IP subnets `Rack::Attack.blocklist_ip("1.2.0.0/16")` +- Shorthand for blocking an IP subnet `Rack::Attack.blocklist_ip("1.2.0.0/16")` - Shorthand for safelisting an IP address `Rack::Attack.safelist_ip("5.6.7.8")` +- Shorthand for safelisting an IP subnet `Rack::Attack.safelist_ip("5.6.0.0/16")` - Throw helpful error message when using `allow2ban` but cache store is misconfigured ([#315](https://github.com/kickstarter/rack-attack/issues/315)) - Throw helpful error message when using `fail2ban` but cache store is misconfigured ([#315](https://github.com/kickstarter/rack-attack/issues/315)) diff --git a/lib/rack/attack.rb b/lib/rack/attack.rb index 4abca17..cf1379d 100644 --- a/lib/rack/attack.rb +++ b/lib/rack/attack.rb @@ -45,7 +45,7 @@ class Rack::Attack def safelist_ip(ip) @ip_safelists ||= [] - ip_safelist_proc = lambda { |request| ip == request.ip } + ip_safelist_proc = lambda { |request| IPAddr.new(ip).include?(IPAddr.new(request.ip)) } @ip_safelists << Safelist.new(nil, ip_safelist_proc) end diff --git a/spec/acceptance/safelisting_subnet_spec.rb b/spec/acceptance/safelisting_subnet_spec.rb new file mode 100644 index 0000000..920f265 --- /dev/null +++ b/spec/acceptance/safelisting_subnet_spec.rb @@ -0,0 +1,48 @@ +require_relative "../spec_helper" + +describe "Safelisting an IP subnet" do + before do + Rack::Attack.blocklist("admin") do |request| + request.path == "/admin" + end + + Rack::Attack.safelist_ip("5.6.0.0/16") + end + + it "forbids request if blocklist condition is true and safelist is false" do + get "/admin", {}, "REMOTE_ADDR" => "5.7.0.0" + + assert_equal 403, last_response.status + end + + it "succeeds if blocklist condition is false and safelist is false" do + get "/", {}, "REMOTE_ADDR" => "5.7.0.0" + + assert_equal 200, last_response.status + end + + it "succeeds request if blocklist condition is false and safelist is true" do + get "/", {}, "REMOTE_ADDR" => "5.6.0.0" + + assert_equal 200, last_response.status + end + + it "succeeds request if both blocklist and safelist conditions are true" do + get "/admin", {}, "REMOTE_ADDR" => "5.6.255.255" + + assert_equal 200, last_response.status + end + + it "notifies when the request is safe" do + notification_type = nil + + ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, request| + notification_type = request.env["rack.attack.match_type"] + end + + get "/admin", {}, "REMOTE_ADDR" => "5.6.0.0" + + assert_equal 200, last_response.status + assert_equal :safelist, notification_type + end +end From 62aca946b50352f4900b2dddf1943f07cad4e9f0 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Mon, 26 Mar 2018 18:53:32 -0300 Subject: [PATCH 050/129] Require ipaddr so it works on ruby < 2.5 --- lib/rack/attack.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/rack/attack.rb b/lib/rack/attack.rb index cf1379d..35b2e35 100644 --- a/lib/rack/attack.rb +++ b/lib/rack/attack.rb @@ -2,6 +2,7 @@ require 'rack' require 'forwardable' require 'rack/attack/path_normalizer' require 'rack/attack/request' +require "ipaddr" class Rack::Attack class MisconfiguredStoreError < StandardError; end From b8c85ab2d087c9ce33d62cdda36c2da0fbd1581e Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Thu, 29 Mar 2018 11:38:27 -0300 Subject: [PATCH 051/129] Update TravisCI rubies --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7886e43..0a151bc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,10 +3,10 @@ cache: bundler rvm: - 2.6.0-preview1 - - 2.5.0 - - 2.4.3 - - 2.3.6 - - 2.2.9 + - 2.5.1 + - 2.4.4 + - 2.3.7 + - 2.2.10 - jruby-9.1.16.0 before_install: From 4c1aa8b312c1b0571dfadc448d266ef07341378e Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Thu, 29 Mar 2018 11:42:27 -0300 Subject: [PATCH 052/129] [Fixes #318] Attempt to improve README a bit (#323) * docs: Improve Getting Started section * docs: Following Getting Started show Usage to the README reader * docs: Move the configuration tip to the Usage section * docs: Move the cache store configuration comment to Usage * docs: Clarify Responses title * docs: allow2ban also uses the cache store * docs: Improve Usage docs for blocking, safelisting and throttling * docs: Don't give the impression that the gem is not being maintained when it actually is * docs: Be a bit more clear about cache store in README * docs: Attempt to be a bit more concise in the README intro * docs: Clarify sentence --- README.md | 254 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 169 insertions(+), 85 deletions(-) diff --git a/README.md b/README.md index d977edd..efb3d43 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,8 @@ -# Rack::Attack!!! +# Rack::Attack + *Rack middleware for blocking & throttling abusive requests* -Rack::Attack is a rack middleware to protect your web app from bad clients. -It allows *safelisting*, *blocklisting*, *throttling*, and *tracking* based on arbitrary properties of the request. - -Throttle and fail2ban state is stored in a configurable cache (e.g. `Rails.cache`), presumably backed by memcached or redis ([at least gem v3.0.0](https://rubygems.org/gems/redis)). +Protect your Rails and Rack apps from bad clients. Rack::Attack lets you easily decide when to *allow*, *block* and *throttle* based on properties of the request. See the [Backing & Hacking blog post](http://www.kickstarter.com/backing-and-hacking/rack-attack-protection-from-abusive-clients) introducing Rack::Attack. @@ -12,99 +10,97 @@ See the [Backing & Hacking blog post](http://www.kickstarter.com/backing-and-hac [![Build Status](https://travis-ci.org/kickstarter/rack-attack.svg?branch=master)](https://travis-ci.org/kickstarter/rack-attack) [![Code Climate](https://codeclimate.com/github/kickstarter/rack-attack.svg)](https://codeclimate.com/github/kickstarter/rack-attack) -## Looking for maintainers - -I'm looking for new maintainers to help me support Rack::Attack. Check out -[issue #219 for details](https://github.com/kickstarter/rack-attack/issues/219). - ## Getting started -Install the [rack-attack](http://rubygems.org/gems/rack-attack) gem; or add it to your Gemfile with bundler: +### 1. Installing + +Add this line to your application's Gemfile: ```ruby # In your Gemfile + gem 'rack-attack' ``` -Tell your app to use the Rack::Attack middleware. -For Rails apps: + +And then execute: + + $ bundle + +Or install it yourself as: + + $ gem install rack-attack + +### 2. Plugging into the application + +Then tell your ruby web application to use rack-attack as a middleware. + +a) For __rails__ applications: ```ruby # In config/application.rb + config.middleware.use Rack::Attack ``` -Or for Rackup files: +b) For __rack__ applications: ```ruby # In config.ru + require "rack/attack" use Rack::Attack ``` -Add a `rack_attack.rb` file to `config/initializers/`: -```ruby -# In config/initializers/rack_attack.rb -class Rack::Attack - # your custom configuration... -end -``` +__IMPORTANT__: By default, rack-attack won't perform any blocking or throttling, until you specifically tell it what to protect against by configuring some rules. + +## Usage *Tip:* The example in the wiki is a great way to get started: [Example Configuration](https://github.com/kickstarter/rack-attack/wiki/Example-Configuration) -Optionally configure the cache store for throttling or fail2ban filtering: +Define rules by calling `Rack::Attack` public methods, in any file that runs when your application is being initialized. For rails applications this means creating a new file named `config/initializers/rack_attack.rb` and writing your rules there. + +### Safelisting + +Safelists have the most precedence, so any request matching a safelist would be allowed despite matching any number of blocklists or throttles. + +#### `safelist_ip(ip_address_string)` + +E.g. ```ruby -Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new # defaults to Rails.cache +# config/initializers/rack_attack.rb (for rails app) + +Rack::Attack.safelist_ip("5.6.7.8") ``` -Note that `Rack::Attack.cache` is only used for throttling and fail2ban filtering; not blocklisting & safelisting. Your cache store must implement `increment` and `write` like [ActiveSupport::Cache::Store](http://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html). +#### `safelist_ip(ip_subnet_string)` -## How it works - -The Rack::Attack middleware compares each request against *safelists*, *blocklists*, *throttles*, and *tracks* that you define. There are none by default. - - * If the request matches any **safelist**, it is allowed. - * Otherwise, if the request matches any **blocklist**, it is blocked. - * Otherwise, if the request matches any **throttle**, a counter is incremented in the Rack::Attack.cache. If any throttle's limit is exceeded, the request is blocked. - * Otherwise, all **tracks** are checked, and the request is allowed. - -The algorithm is actually more concise in code: See [Rack::Attack.call](https://github.com/kickstarter/rack-attack/blob/master/lib/rack/attack.rb): +E.g. ```ruby -def call(env) - req = Rack::Attack::Request.new(env) +# config/initializers/rack_attack.rb (for rails app) - if safelisted?(req) - @app.call(env) - elsif blocklisted?(req) - self.class.blocklisted_response.call(env) - elsif throttled?(req) - self.class.throttled_response.call(env) - else - tracked?(req) - @app.call(env) - end +Rack::Attack.safelist_ip("5.6.7.0/24") +``` + +#### `safelist(name, &block)` + +Name your custom safelist and make your ruby-block argument return a truthy value if you want the request to be blocked, and falsy otherwise. + +The request object is a [Rack::Request](http://www.rubydoc.info/gems/rack/Rack/Request). + +E.g. + +```ruby +# config/initializers/rack_attack.rb (for rails apps) + +# Provided that trusted users use an HTTP request header named APIKey +Rack::Attack.safelist("mark any authenticated access safe") do |request| + # Requests are allowed if the return value is truthy + request.env["APIKey"] == "secret-string" end -``` -Note: `Rack::Attack::Request` is just a subclass of `Rack::Request` so that you -can cleanly monkey patch helper methods onto the -[request object](https://github.com/kickstarter/rack-attack/blob/master/lib/rack/attack/request.rb). - -## About Tracks - -`Rack::Attack.track` doesn't affect request processing. Tracks are an easy way to log and measure requests matching arbitrary attributes. - -## Usage - -Define safelists, blocklists, throttles, and tracks as blocks that return truthy values if matched, falsy otherwise. In a Rails app -these go in an initializer in `config/initializers/`. -A [Rack::Request](http://www.rubydoc.info/gems/rack/Rack/Request) object is passed to the block (named 'req' in the examples). - -### Safelists - -```ruby # Always allow requests from localhost # (blocklist & throttles are skipped) Rack::Attack.safelist('allow from localhost') do |req| @@ -113,16 +109,44 @@ Rack::Attack.safelist('allow from localhost') do |req| end ``` -### Blocklists +### Blocking + +#### `blocklist_ip(ip_address_string)` + +E.g. ```ruby -# Block requests from 1.2.3.4 -Rack::Attack.blocklist('block 1.2.3.4') do |req| +# config/initializers/rack_attack.rb (for rails apps) + +Rack::Attack.blocklist_ip("1.2.3.4") +``` + +#### `blocklist_ip(ip_subnet_string)` + +E.g. + +```ruby +# config/initializers/rack_attack.rb (for rails apps) + +Rack::Attack.blocklist_ip("1.2.0.0/16") +``` + +#### `blocklist(name, &block)` + +Name your custom blocklist and make your ruby-block argument returna a truthy value if you want the request to be blocked, and falsy otherwise. + +The request object is a [Rack::Request](http://www.rubydoc.info/gems/rack/Rack/Request). + +E.g. + +```ruby +# config/initializers/rack_attack.rb (for rails apps) + +Rack::Attack.blocklist("block all access to admin") do |request| # Requests are blocked if the return value is truthy - '1.2.3.4' == req.ip + request.path.start_with?("/admin") end -# Block logins from a bad user agent Rack::Attack.blocklist('block bad UA logins') do |req| req.path == '/login' && req.post? && req.user_agent == 'BadUA' end @@ -135,6 +159,8 @@ This pattern is inspired by [fail2ban](http://www.fail2ban.org/wiki/index.php/Ma See the [fail2ban documentation](http://www.fail2ban.org/wiki/index.php/MANUAL_0_8#Jail_Options) for more details on how the parameters work. For multiple filters, be sure to put each filter in a separate blocklist and use a unique discriminator for each fail2ban filter. +Fail2ban state is stored in a [configurable cache](#cache-store-configuration) (which defaults to `Rails.cache` if present). + ```ruby # Block suspicious requests for '/etc/password' or wordpress specific paths. # After 3 blocked requests in 10 minutes, block all requests from that IP for 5 minutes. @@ -155,8 +181,12 @@ end Note that `Fail2Ban` filters are not automatically scoped to the blocklist, so when using multiple filters in an application the scoping must be added to the discriminator e.g. `"pentest:#{req.ip}"`. #### Allow2Ban + `Allow2Ban.filter` works the same way as the `Fail2Ban.filter` except that it *allows* requests from misbehaving clients until such time as they reach maxretry at which they are cut off as per normal. + +Allow2ban state is stored in a [configurable cache](#cache-store-configuration) (which defaults to `Rails.cache` if present). + ```ruby # Lockout IP addresses that are hammering your login page. # After 20 requests in 1 minute, block all requests from that IP for 1 hour. @@ -171,33 +201,40 @@ Rack::Attack.blocklist('allow2ban login scrapers') do |req| end ``` +### Throttling -### Throttles +Throttle state is stored in a [configurable cache](#cache-store-configuration) (which defaults to `Rails.cache` if present). + +#### `throttle(name, options, &block)` + +Name your custom throttle, provide `limit` and `period` as options, and make your ruby-block argument return the __discriminator__. This discriminator is how you tell rack-attack whether you're limiting per IP address, per user email or any other. + +The request object is a [Rack::Request](http://www.rubydoc.info/gems/rack/Rack/Request). + +E.g. ```ruby -# Throttle requests to 5 requests per second per ip -Rack::Attack.throttle('req/ip', limit: 5, period: 1.second) do |req| - # If the return value is truthy, the cache key for the return value - # is incremented and compared with the limit. In this case: - # "rack::attack:#{Time.now.to_i/1.second}:req/ip:#{req.ip}" - # - # If falsy, the cache key is neither incremented nor checked. +# config/initializers/rack_attack.rb (for rails apps) - req.ip +Rack::Attack.throttle("requests by ip", limit: 5, period: 2) do |request| + request.ip end # Throttle login attempts for a given email parameter to 6 reqs/minute # Return the email as a discriminator on POST /login requests -Rack::Attack.throttle('logins/email', limit: 6, period: 60) do |req| - req.params['email'] if req.path == '/login' && req.post? +Rack::Attack.throttle('limit logins per email', limit: 6, period: 60) do |req| + if req.path == '/login' && req.post? + req.params['email'] + end end # You can also set a limit and period using a proc. For instance, after # Rack::Auth::Basic has authenticated the user: -limit_proc = proc {|req| req.env["REMOTE_USER"] == "admin" ? 100 : 1} -period_proc = proc {|req| req.env["REMOTE_USER"] == "admin" ? 1.second : 1.minute} -Rack::Attack.throttle('req/ip', limit: limit_proc, period: period_proc) do |req| - req.ip +limit_proc = proc { |req| req.env["REMOTE_USER"] == "admin" ? 100 : 1 } +period_proc = proc { |req| req.env["REMOTE_USER"] == "admin" ? 1 : 60 } + +Rack::Attack.throttle('request per ip', limit: limit_proc, period: period_proc) do |request| + request.ip end ``` @@ -223,7 +260,17 @@ ActiveSupport::Notifications.subscribe("rack.attack") do |name, start, finish, r end ``` -## Responses +### Cache store configuration + +Throttle, allow2ban and fail2ban state is stored in a configurable cache (which defaults to `Rails.cache` if present), presumably backed by memcached or redis ([at least gem v3.0.0](https://rubygems.org/gems/redis)). + +```ruby +Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new # defaults to Rails.cache +``` + +Note that `Rack::Attack.cache` is only used for throttling, allow2ban and fail2ban filtering; not blocklisting and safelisting. Your cache store must implement `increment` and `write` like [ActiveSupport::Cache::Store](http://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html). + +## Customizing responses Customize the response of blocklisted and throttled requests using an object that adheres to the [Rack app interface](http://rack.rubyforge.org/doc/SPEC.html). @@ -288,6 +335,43 @@ ActiveSupport::Notifications.subscribe('rack.attack') do |name, start, finish, r end ``` +## How it works + +The Rack::Attack middleware compares each request against *safelists*, *blocklists*, *throttles*, and *tracks* that you define. There are none by default. + + * If the request matches any **safelist**, it is allowed. + * Otherwise, if the request matches any **blocklist**, it is blocked. + * Otherwise, if the request matches any **throttle**, a counter is incremented in the Rack::Attack.cache. If any throttle's limit is exceeded, the request is blocked. + * Otherwise, all **tracks** are checked, and the request is allowed. + +The algorithm is actually more concise in code: See [Rack::Attack.call](https://github.com/kickstarter/rack-attack/blob/master/lib/rack/attack.rb): + +```ruby +def call(env) + req = Rack::Attack::Request.new(env) + + if safelisted?(req) + @app.call(env) + elsif blocklisted?(req) + self.class.blocklisted_response.call(env) + elsif throttled?(req) + self.class.throttled_response.call(env) + else + tracked?(req) + @app.call(env) + end +end +``` + +Note: `Rack::Attack::Request` is just a subclass of `Rack::Request` so that you +can cleanly monkey patch helper methods onto the +[request object](https://github.com/kickstarter/rack-attack/blob/master/lib/rack/attack/request.rb). + +### About Tracks + +`Rack::Attack.track` doesn't affect request processing. Tracks are an easy way to log and measure requests matching arbitrary attributes. + + ## Testing A note on developing and testing apps using Rack::Attack - if you are using throttling in particular, you will From cf3528e04f1259df8a5c6a7bbeea63d208c76de2 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Thu, 29 Mar 2018 11:43:45 -0300 Subject: [PATCH 053/129] Clarify in the CHANGELOG exact ruby version affected --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index afce862..5355727 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ All notable changes to this project will be documented in this file. ## [5.1.0] - 2018-03-10 - - Fixes edge case bug when using ruby 2.5 and redis [#253](https://github.com/kickstarter/rack-attack/issues/253) ([#271](https://github.com/kickstarter/rack-attack/issues/271)) + - Fixes edge case bug when using ruby 2.5.0 and redis [#253](https://github.com/kickstarter/rack-attack/issues/253) ([#271](https://github.com/kickstarter/rack-attack/issues/271)) - Throws errors with better semantics when missing or misconfigured store caches to aid in developers debugging their configs ([#274](https://github.com/kickstarter/rack-attack/issues/274)) - Removed legacy code that was originally intended for Rails 3 apps ([#264](https://github.com/kickstarter/rack-attack/issues/264)) From 389287f060a94334314c12c9d89377da24678719 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Thu, 29 Mar 2018 12:24:37 -0300 Subject: [PATCH 054/129] Bump version to 5.2.0 --- lib/rack/attack/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rack/attack/version.rb b/lib/rack/attack/version.rb index a01a336..a2f9e08 100644 --- a/lib/rack/attack/version.rb +++ b/lib/rack/attack/version.rb @@ -1,5 +1,5 @@ module Rack class Attack - VERSION = '5.1.0' + VERSION = '5.2.0' end end From 0457bf22f9fde00e5e5c6e9fcd6618108b18477d Mon Sep 17 00:00:00 2001 From: Koen Rouwhorst Date: Thu, 29 Mar 2018 18:41:40 +0200 Subject: [PATCH 055/129] Updated all non-secure HTTP URLs to HTTPS. --- CODE_OF_CONDUCT.md | 2 +- README.md | 16 ++++++++-------- lib/rack/attack/cache.rb | 2 +- lib/rack/attack/path_normalizer.rb | 2 +- rack-attack.gemspec | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index ad44a62..a05c39e 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -18,4 +18,4 @@ Instances of abusive, harassing, or otherwise unacceptable behavior may be repor :hand: :page_with_curl: -This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org) (v1.0.0), available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/) +This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org) (v1.0.0), available at [https://www.contributor-covenant.org/version/1/0/0/](https://www.contributor-covenant.org/version/1/0/0/) diff --git a/README.md b/README.md index efb3d43..12763fe 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ Protect your Rails and Rack apps from bad clients. Rack::Attack lets you easily decide when to *allow*, *block* and *throttle* based on properties of the request. -See the [Backing & Hacking blog post](http://www.kickstarter.com/backing-and-hacking/rack-attack-protection-from-abusive-clients) introducing Rack::Attack. +See the [Backing & Hacking blog post](https://www.kickstarter.com/backing-and-hacking/rack-attack-protection-from-abusive-clients) introducing Rack::Attack. -[![Gem Version](https://badge.fury.io/rb/rack-attack.svg)](http://badge.fury.io/rb/rack-attack) +[![Gem Version](https://badge.fury.io/rb/rack-attack.svg)](https://badge.fury.io/rb/rack-attack) [![Build Status](https://travis-ci.org/kickstarter/rack-attack.svg?branch=master)](https://travis-ci.org/kickstarter/rack-attack) [![Code Climate](https://codeclimate.com/github/kickstarter/rack-attack.svg)](https://codeclimate.com/github/kickstarter/rack-attack) @@ -155,8 +155,8 @@ end #### Fail2Ban `Fail2Ban.filter` can be used within a blocklist to block all requests from misbehaving clients. -This pattern is inspired by [fail2ban](http://www.fail2ban.org/wiki/index.php/Main_Page). -See the [fail2ban documentation](http://www.fail2ban.org/wiki/index.php/MANUAL_0_8#Jail_Options) for more details on +This pattern is inspired by [fail2ban](https://www.fail2ban.org/wiki/index.php/Main_Page). +See the [fail2ban documentation](https://www.fail2ban.org/wiki/index.php/MANUAL_0_8#Jail_Options) for more details on how the parameters work. For multiple filters, be sure to put each filter in a separate blocklist and use a unique discriminator for each fail2ban filter. Fail2ban state is stored in a [configurable cache](#cache-store-configuration) (which defaults to `Rails.cache` if present). @@ -272,7 +272,7 @@ Note that `Rack::Attack.cache` is only used for throttling, allow2ban and fail2b ## Customizing responses -Customize the response of blocklisted and throttled requests using an object that adheres to the [Rack app interface](http://rack.rubyforge.org/doc/SPEC.html). +Customize the response of blocklisted and throttled requests using an object that adheres to the [Rack app interface](http://www.rubydoc.info/github/rack/rack/file/SPEC). ```ruby Rack::Attack.blocklisted_response = lambda do |env| @@ -388,7 +388,7 @@ so try to keep the number of throttle checks per request low. If a request is blocklisted or throttled, the response is a very simple Rack response. A single typical ruby web server thread can block several hundred requests per second. -Rack::Attack complements tools like `iptables` and nginx's [limit_conn_zone module](http://nginx.org/en/docs/http/ngx_http_limit_conn_module.html#limit_conn_zone). +Rack::Attack complements tools like `iptables` and nginx's [limit_conn_zone module](https://nginx.org/en/docs/http/ngx_http_limit_conn_module.html#limit_conn_zone). ## Motivation @@ -408,7 +408,7 @@ adhere to the [Code of Conduct](CODE_OF_CONDUCT.md). ### Testing pull requests -To run the minitest test suite, you will need both [Redis](http://redis.io/) and +To run the minitest test suite, you will need both [Redis](https://redis.io/) and [Memcached](https://memcached.org/) running locally and bound to IP `127.0.0.1` on default ports (`6379` for Redis, and `11211` for Memcached) and able to be accessed without authentication. @@ -434,4 +434,4 @@ New releases of Rack::Attack are announced on Copyright Kickstarter, PBC. -Released under an [MIT License](http://opensource.org/licenses/MIT). +Released under an [MIT License](https://opensource.org/licenses/MIT). diff --git a/lib/rack/attack/cache.rb b/lib/rack/attack/cache.rb index 865e869..231b5c5 100644 --- a/lib/rack/attack/cache.rb +++ b/lib/rack/attack/cache.rb @@ -43,7 +43,7 @@ module Rack def key_and_expiry(unprefixed_key, period) epoch_time = Time.now.to_i - # Add 1 to expires_in to avoid timing error: http://git.io/i1PHXA + # Add 1 to expires_in to avoid timing error: https://git.io/i1PHXA expires_in = (period - (epoch_time % period) + 1).to_i ["#{prefix}:#{(epoch_time / period).to_i}:#{unprefixed_key}", expires_in] end diff --git a/lib/rack/attack/path_normalizer.rb b/lib/rack/attack/path_normalizer.rb index 4b4171e..f4e686f 100644 --- a/lib/rack/attack/path_normalizer.rb +++ b/lib/rack/attack/path_normalizer.rb @@ -2,7 +2,7 @@ class Rack::Attack # When using Rack::Attack with a Rails app, developers expect the request path # to be normalized. In particular, trailing slashes are stripped. - # (See http://git.io/v0rrR for implementation.) + # (See https://git.io/v0rrR for implementation.) # # Look for an ActionDispatch utility class that Rails folks would expect # to normalize request paths. If unavailable, use a fallback class that diff --git a/rack-attack.gemspec b/rack-attack.gemspec index b5a1909..7ac83e8 100644 --- a/rack-attack.gemspec +++ b/rack-attack.gemspec @@ -14,7 +14,7 @@ Gem::Specification.new do |s| s.email = "aaron@ktheory.com" s.files = Dir.glob("{bin,lib}/**/*") + %w(Rakefile README.md) - s.homepage = 'http://github.com/kickstarter/rack-attack' + s.homepage = 'https://github.com/kickstarter/rack-attack' s.rdoc_options = ["--charset=UTF-8"] s.require_paths = ["lib"] s.summary = %q{Block & throttle abusive requests} From 8cbd64c0e9bd1015324bdba0b1f70689286da9a7 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Thu, 29 Mar 2018 15:31:14 -0300 Subject: [PATCH 056/129] Update CHANGELOG after 5.2.0 release --- CHANGELOG.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5355727..6b93015 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,12 +3,14 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [5.2.0] - 2018-03-29 + ### Added -- Shorthand for blocking an IP address `Rack::Attack.blocklist_ip("1.2.3.4")` -- Shorthand for blocking an IP subnet `Rack::Attack.blocklist_ip("1.2.0.0/16")` -- Shorthand for safelisting an IP address `Rack::Attack.safelist_ip("5.6.7.8")` -- Shorthand for safelisting an IP subnet `Rack::Attack.safelist_ip("5.6.0.0/16")` +- Shorthand for blocking an IP address `Rack::Attack.blocklist_ip("1.2.3.4")` ([#320](https://github.com/kickstarter/rack-attack/pull/320)) +- Shorthand for blocking an IP subnet `Rack::Attack.blocklist_ip("1.2.0.0/16")` ([#320](https://github.com/kickstarter/rack-attack/pull/320)) +- Shorthand for safelisting an IP address `Rack::Attack.safelist_ip("5.6.7.8")` ([#320](https://github.com/kickstarter/rack-attack/pull/320)) +- Shorthand for safelisting an IP subnet `Rack::Attack.safelist_ip("5.6.0.0/16")` ([#320](https://github.com/kickstarter/rack-attack/pull/320)) - Throw helpful error message when using `allow2ban` but cache store is misconfigured ([#315](https://github.com/kickstarter/rack-attack/issues/315)) - Throw helpful error message when using `fail2ban` but cache store is misconfigured ([#315](https://github.com/kickstarter/rack-attack/issues/315)) @@ -101,7 +103,8 @@ All notable changes to this project will be documented in this file. - Remove unused variable - Extract mandatory options to constants -[Unreleased]: https://github.com/kickstarter/rack-attack/compare/v5.1.0...HEAD/ +[Unreleased]: https://github.com/kickstarter/rack-attack/compare/v5.2.0...HEAD/ +[5.2.0]: https://github.com/kickstarter/rack-attack/compare/v5.1.0...v5.2.0/ [5.1.0]: https://github.com/kickstarter/rack-attack/compare/v5.0.1...v5.1.0/ [5.0.1]: https://github.com/kickstarter/rack-attack/compare/v5.0.0...v5.0.1/ [5.0.0]: https://github.com/kickstarter/rack-attack/compare/v4.4.1...v5.0.0/ From 2d6ad830ac7c99cbab11e5e0b11c90f32ae223ed Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Fri, 30 Mar 2018 15:19:06 -0300 Subject: [PATCH 057/129] docs: provide some advice for users willing to contribute to the project --- CONTRIBUTING.md | 16 ++++++++++++++++ README.md | 21 +++++---------------- docs/development.md | 13 +++++++++++++ 3 files changed, 34 insertions(+), 16 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 docs/development.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..6cb4183 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,16 @@ +# Rack::Attack: Contributing + +Thank you for considering contributing to Rack::Attack. + +This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Code of Conduct](CODE_OF_CONDUCT.md). + +## How can I help? + +Any of the following is greatly appreciated: + +* Helping users by answering to their [questions](https://github.com/kickstarter/rack-attack/issues?q=is%3Aopen+is%3Aissue+label%3A%22type%3A+question%22) +* Helping users troubleshoot their [error reports](https://github.com/kickstarter/rack-attack/issues?q=is%3Aissue+is%3Aopen+label%3A%22type%3A+error+report%22) to figure out if the error is caused by an actual bug or some misconfiguration +* Giving feedback by commenting in other users [feature requests](https://github.com/kickstarter/rack-attack/issues?q=is%3Aissue+is%3Aopen+label%3A%22type%3A+feature+request%22) +* Reporting an error you are experiencing +* Suggesting a new feature you think it would be useful for many users +* If you want to work on fixing an actual issue and you don't know where to start, those labeled [good first issue](https://github.com/kickstarter/rack-attack/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) may be a good choice diff --git a/README.md b/README.md index 12763fe..78e5e36 100644 --- a/README.md +++ b/README.md @@ -402,26 +402,15 @@ less on short-term, one-off hacks to block a particular attack. ## Contributing -Pull requests and issues are greatly appreciated. This project is intended to be -a safe, welcoming space for collaboration, and contributors are expected to -adhere to the [Code of Conduct](CODE_OF_CONDUCT.md). +Check out the [Contributing guide](CONTRIBUTING.md). -### Testing pull requests +## Code of Conduct -To run the minitest test suite, you will need both [Redis](https://redis.io/) and -[Memcached](https://memcached.org/) running locally and bound to IP `127.0.0.1` on -default ports (`6379` for Redis, and `11211` for Memcached) and able to be -accessed without authentication. +This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Code of Conduct](CODE_OF_CONDUCT.md). -Install dependencies by running -```sh -bundle install -``` +## Development setup -Then run the test suite by running -```sh -bundle exec rake -``` +Check out the [Development guide](docs/development.md). ## Mailing list diff --git a/docs/development.md b/docs/development.md new file mode 100644 index 0000000..c3fec28 --- /dev/null +++ b/docs/development.md @@ -0,0 +1,13 @@ +# Rack::Attack: Development + +## Running the tests + +You will need both [Redis](https://redis.io/) and [Memcached](https://memcached.org/) running locally and bound to IP `127.0.0.1` on default ports (`6379` for Redis, and `11211` for Memcached) and able to be accessed without authentication. + +Install dependencies by running + + $ bundle install + +Then run the test suite by running + + $ bundle exec appraisal rake test From 11e9557ccb3df968ede8f863880a71e5155330b6 Mon Sep 17 00:00:00 2001 From: Lucas Mansur Date: Fri, 30 Mar 2018 16:08:00 -0300 Subject: [PATCH 058/129] [Fixes #302] Initial style guide adoption (#330) * Initial Rubocop configuration * Fix Rubocop layout offenses for lib * Fix some spec offenses * Fix leftover layout offenses --- .rubocop.yml | 5 +++++ Guardfile | 1 - lib/rack/attack.rb | 17 ++++++++--------- lib/rack/attack/allow2ban.rb | 1 + lib/rack/attack/blocklist.rb | 1 - lib/rack/attack/cache.rb | 1 - lib/rack/attack/check.rb | 3 +-- lib/rack/attack/fail2ban.rb | 3 ++- lib/rack/attack/path_normalizer.rb | 12 +++++------- lib/rack/attack/safelist.rb | 1 - lib/rack/attack/store_proxy.rb | 2 +- lib/rack/attack/store_proxy/dalli_proxy.rb | 5 ++--- lib/rack/attack/store_proxy/mem_cache_proxy.rb | 9 ++++----- .../attack/store_proxy/redis_store_proxy.rb | 6 +++--- rack-attack.gemspec | 1 + spec/acceptance/safelisting_ip_spec.rb | 1 - spec/allow2ban_spec.rb | 12 ++++++------ spec/fail2ban_spec.rb | 14 +++++++------- spec/rack_attack_dalli_proxy_spec.rb | 2 -- spec/rack_attack_spec.rb | 12 ++++++------ spec/rack_attack_throttle_spec.rb | 10 +++++----- spec/rack_attack_track_spec.rb | 8 ++++---- spec/spec_helper.rb | 5 ++--- 23 files changed, 63 insertions(+), 69 deletions(-) create mode 100644 .rubocop.yml diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..cf91fe2 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,5 @@ +AllCops: + DisabledByDefault: true + +Layout: + Enabled: true diff --git a/Guardfile b/Guardfile index ebf900b..06b986b 100644 --- a/Guardfile +++ b/Guardfile @@ -7,4 +7,3 @@ guard :minitest do watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } watch(%r{^spec/spec_helper\.rb$}) { 'spec' } end - diff --git a/lib/rack/attack.rb b/lib/rack/attack.rb index 35b2e35..ad5e1eb 100644 --- a/lib/rack/attack.rb +++ b/lib/rack/attack.rb @@ -22,7 +22,6 @@ class Rack::Attack autoload :Allow2Ban, 'rack/attack/allow2ban' class << self - attr_accessor :notifier, :blocklisted_response, :throttled_response def safelist(name, &block) @@ -64,8 +63,11 @@ class Rack::Attack end def safelists; @safelists ||= {}; end + def blocklists; @blocklists ||= {}; end + def throttles; @throttles ||= {}; end + def tracks; @tracks ||= {}; end def whitelists @@ -126,7 +128,7 @@ class Rack::Attack def blacklisted_response=(res) warn "[DEPRECATION] 'Rack::Attack.blacklisted_response=' is deprecated. Please use 'blocklisted_response=' instead." - self.blocklisted_response=(res) + self.blocklisted_response = res end def blacklisted_response @@ -147,10 +149,10 @@ class Rack::Attack # Set defaults @notifier = ActiveSupport::Notifications if defined?(ActiveSupport::Notifications) - @blocklisted_response = lambda {|env| [403, {'Content-Type' => 'text/plain'}, ["Forbidden\n"]] } - @throttled_response = lambda {|env| + @blocklisted_response = lambda { |env| [403, { 'Content-Type' => 'text/plain' }, ["Forbidden\n"]] } + @throttled_response = lambda { |env| retry_after = (env['rack.attack.match_data'] || {})[:period] - [429, {'Content-Type' => 'text/plain', 'Retry-After' => retry_after.to_s}, ["Retry later\n"]] + [429, { 'Content-Type' => 'text/plain', 'Retry-After' => retry_after.to_s }, ["Retry later\n"]] } def initialize(app) @@ -174,8 +176,5 @@ class Rack::Attack end extend Forwardable - def_delegators self, :safelisted?, - :blocklisted?, - :throttled?, - :tracked? + def_delegators self, :safelisted?, :blocklisted?, :throttled?, :tracked? end diff --git a/lib/rack/attack/allow2ban.rb b/lib/rack/attack/allow2ban.rb index 763253b..9a91086 100644 --- a/lib/rack/attack/allow2ban.rb +++ b/lib/rack/attack/allow2ban.rb @@ -3,6 +3,7 @@ module Rack class Allow2Ban < Fail2Ban class << self protected + def key_prefix 'allow2ban' end diff --git a/lib/rack/attack/blocklist.rb b/lib/rack/attack/blocklist.rb index b61a29f..3cfba54 100644 --- a/lib/rack/attack/blocklist.rb +++ b/lib/rack/attack/blocklist.rb @@ -5,7 +5,6 @@ module Rack super @type = :blocklist end - end end end diff --git a/lib/rack/attack/cache.rb b/lib/rack/attack/cache.rb index 231b5c5..0e6e6d3 100644 --- a/lib/rack/attack/cache.rb +++ b/lib/rack/attack/cache.rb @@ -1,7 +1,6 @@ module Rack class Attack class Cache - attr_accessor :prefix def initialize diff --git a/lib/rack/attack/check.rb b/lib/rack/attack/check.rb index 8ae9ba1..49857cf 100644 --- a/lib/rack/attack/check.rb +++ b/lib/rack/attack/check.rb @@ -8,7 +8,7 @@ module Rack end def [](req) - block[req].tap {|match| + block[req].tap { |match| if match req.env["rack.attack.matched"] = name req.env["rack.attack.match_type"] = type @@ -21,4 +21,3 @@ module Rack end end end - diff --git a/lib/rack/attack/fail2ban.rb b/lib/rack/attack/fail2ban.rb index 76dc59f..413808e 100644 --- a/lib/rack/attack/fail2ban.rb +++ b/lib/rack/attack/fail2ban.rb @@ -27,6 +27,7 @@ module Rack end protected + def key_prefix 'fail2ban' end @@ -40,8 +41,8 @@ module Rack true end - private + def ban!(discriminator, bantime) cache.write("#{key_prefix}:ban:#{discriminator}", 1, bantime) end diff --git a/lib/rack/attack/path_normalizer.rb b/lib/rack/attack/path_normalizer.rb index f4e686f..fd5935f 100644 --- a/lib/rack/attack/path_normalizer.rb +++ b/lib/rack/attack/path_normalizer.rb @@ -1,5 +1,4 @@ class Rack::Attack - # When using Rack::Attack with a Rails app, developers expect the request path # to be normalized. In particular, trailing slashes are stripped. # (See https://git.io/v0rrR for implementation.) @@ -15,10 +14,9 @@ class Rack::Attack end PathNormalizer = if defined?(::ActionDispatch::Journey::Router::Utils) - # For Rails apps - ::ActionDispatch::Journey::Router::Utils - else - FallbackPathNormalizer - end - + # For Rails apps + ::ActionDispatch::Journey::Router::Utils + else + FallbackPathNormalizer + end end diff --git a/lib/rack/attack/safelist.rb b/lib/rack/attack/safelist.rb index 748d422..e548b0b 100644 --- a/lib/rack/attack/safelist.rb +++ b/lib/rack/attack/safelist.rb @@ -5,7 +5,6 @@ module Rack super @type = :safelist end - end end end diff --git a/lib/rack/attack/store_proxy.rb b/lib/rack/attack/store_proxy.rb index 5ce0f52..d8edddb 100644 --- a/lib/rack/attack/store_proxy.rb +++ b/lib/rack/attack/store_proxy.rb @@ -12,8 +12,8 @@ module Rack klass ? klass.new(client) : client end - private + def self.unwrap_active_support_stores(store) # ActiveSupport::Cache::RedisStore doesn't expose any way to set an expiry, # so use the raw Redis::Store instead. diff --git a/lib/rack/attack/store_proxy/dalli_proxy.rb b/lib/rack/attack/store_proxy/dalli_proxy.rb index 703f2d6..ccaa8bf 100644 --- a/lib/rack/attack/store_proxy/dalli_proxy.rb +++ b/lib/rack/attack/store_proxy/dalli_proxy.rb @@ -28,14 +28,14 @@ module Rack rescue Dalli::DalliError end - def write(key, value, options={}) + def write(key, value, options = {}) with do |client| client.set(key, value, options.fetch(:expires_in, 0), raw: true) end rescue Dalli::DalliError end - def increment(key, amount, options={}) + def increment(key, amount, options = {}) with do |client| client.incr(key, amount, options.fetch(:expires_in, 0), amount) end @@ -58,7 +58,6 @@ module Rack end end end - end end end diff --git a/lib/rack/attack/store_proxy/mem_cache_proxy.rb b/lib/rack/attack/store_proxy/mem_cache_proxy.rb index 098e048..bc23942 100644 --- a/lib/rack/attack/store_proxy/mem_cache_proxy.rb +++ b/lib/rack/attack/store_proxy/mem_cache_proxy.rb @@ -14,21 +14,21 @@ module Rack def read(key) # Second argument: reading raw value get(key, true) - rescue MemCache::MemCacheError + rescue MemCache::MemCacheError end - def write(key, value, options={}) + def write(key, value, options = {}) # Third argument: writing raw value set(key, value, options.fetch(:expires_in, 0), true) rescue MemCache::MemCacheError end - def increment(key, amount, options={}) + def increment(key, amount, options = {}) incr(key, amount) rescue MemCache::MemCacheError end - def delete(key, options={}) + def delete(key, options = {}) with do |client| client.delete(key) end @@ -44,7 +44,6 @@ module Rack end end end - end end end diff --git a/lib/rack/attack/store_proxy/redis_store_proxy.rb b/lib/rack/attack/store_proxy/redis_store_proxy.rb index 1a7c20a..9833c26 100644 --- a/lib/rack/attack/store_proxy/redis_store_proxy.rb +++ b/lib/rack/attack/store_proxy/redis_store_proxy.rb @@ -24,7 +24,7 @@ module Rack rescue Redis::BaseError end - def write(key, value, options={}) + def write(key, value, options = {}) if (expires_in = options[:expires_in]) setex(key, expires_in, value, raw: true) else @@ -33,7 +33,7 @@ module Rack rescue Redis::BaseError end - def increment(key, amount, options={}) + def increment(key, amount, options = {}) count = nil pipelined do @@ -45,7 +45,7 @@ module Rack rescue Redis::BaseError end - def delete(key, options={}) + def delete(key, options = {}) del(key) rescue Redis::BaseError end diff --git a/rack-attack.gemspec b/rack-attack.gemspec index 7ac83e8..6803d0a 100644 --- a/rack-attack.gemspec +++ b/rack-attack.gemspec @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- + lib = File.expand_path('../lib/', __FILE__) $:.unshift lib unless $:.include?(lib) diff --git a/spec/acceptance/safelisting_ip_spec.rb b/spec/acceptance/safelisting_ip_spec.rb index 38faf72..eecd5b3 100644 --- a/spec/acceptance/safelisting_ip_spec.rb +++ b/spec/acceptance/safelisting_ip_spec.rb @@ -46,4 +46,3 @@ describe "Safelist an IP" do assert_equal :safelist, notification_type end end - diff --git a/spec/allow2ban_spec.rb b/spec/allow2ban_spec.rb index 9eeae9c..93564d2 100644 --- a/spec/allow2ban_spec.rb +++ b/spec/allow2ban_spec.rb @@ -7,10 +7,10 @@ describe 'Rack::Attack.Allow2Ban' do @findtime = 60 @bantime = 60 Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new - @f2b_options = {:bantime => @bantime, :findtime => @findtime, :maxretry => 2} + @f2b_options = { :bantime => @bantime, :findtime => @findtime, :maxretry => 2 } Rack::Attack.blocklist('pentest') do |req| - Rack::Attack::Allow2Ban.filter(req.ip, @f2b_options){req.query_string =~ /OMGHAX/} + Rack::Attack::Allow2Ban.filter(req.ip, @f2b_options) { req.query_string =~ /OMGHAX/ } end end @@ -31,7 +31,7 @@ describe 'Rack::Attack.Allow2Ban' do end it 'increases fail count' do - key = "rack::attack:#{Time.now.to_i/@findtime}:allow2ban:count:1.2.3.4" + key = "rack::attack:#{Time.now.to_i / @findtime}:allow2ban:count:1.2.3.4" @cache.store.read(key).must_equal 1 end @@ -53,7 +53,7 @@ describe 'Rack::Attack.Allow2Ban' do end it 'increases fail count' do - key = "rack::attack:#{Time.now.to_i/@findtime}:allow2ban:count:1.2.3.4" + key = "rack::attack:#{Time.now.to_i / @findtime}:allow2ban:count:1.2.3.4" @cache.store.read(key).must_equal 2 end @@ -89,7 +89,7 @@ describe 'Rack::Attack.Allow2Ban' do end it 'does not increase fail count' do - key = "rack::attack:#{Time.now.to_i/@findtime}:allow2ban:count:1.2.3.4" + key = "rack::attack:#{Time.now.to_i / @findtime}:allow2ban:count:1.2.3.4" @cache.store.read(key).must_equal 2 end @@ -109,7 +109,7 @@ describe 'Rack::Attack.Allow2Ban' do end it 'does not increase fail count' do - key = "rack::attack:#{Time.now.to_i/@findtime}:allow2ban:count:1.2.3.4" + key = "rack::attack:#{Time.now.to_i / @findtime}:allow2ban:count:1.2.3.4" @cache.store.read(key).must_equal 2 end diff --git a/spec/fail2ban_spec.rb b/spec/fail2ban_spec.rb index 9d4bcc1..e1fe7ef 100644 --- a/spec/fail2ban_spec.rb +++ b/spec/fail2ban_spec.rb @@ -7,10 +7,10 @@ describe 'Rack::Attack.Fail2Ban' do @findtime = 60 @bantime = 60 Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new - @f2b_options = {:bantime => @bantime, :findtime => @findtime, :maxretry => 2} + @f2b_options = { :bantime => @bantime, :findtime => @findtime, :maxretry => 2 } Rack::Attack.blocklist('pentest') do |req| - Rack::Attack::Fail2Ban.filter(req.ip, @f2b_options){req.query_string =~ /OMGHAX/} + Rack::Attack::Fail2Ban.filter(req.ip, @f2b_options) { req.query_string =~ /OMGHAX/ } end end @@ -31,7 +31,7 @@ describe 'Rack::Attack.Fail2Ban' do end it 'increases fail count' do - key = "rack::attack:#{Time.now.to_i/@findtime}:fail2ban:count:1.2.3.4" + key = "rack::attack:#{Time.now.to_i / @findtime}:fail2ban:count:1.2.3.4" @cache.store.read(key).must_equal 1 end @@ -53,7 +53,7 @@ describe 'Rack::Attack.Fail2Ban' do end it 'increases fail count' do - key = "rack::attack:#{Time.now.to_i/@findtime}:fail2ban:count:1.2.3.4" + key = "rack::attack:#{Time.now.to_i / @findtime}:fail2ban:count:1.2.3.4" @cache.store.read(key).must_equal 2 end @@ -75,7 +75,7 @@ describe 'Rack::Attack.Fail2Ban' do end it 'resets fail count' do - key = "rack::attack:#{Time.now.to_i/@findtime}:fail2ban:count:1.2.3.4" + key = "rack::attack:#{Time.now.to_i / @findtime}:fail2ban:count:1.2.3.4" assert_nil @cache.store.read(key) end @@ -110,7 +110,7 @@ describe 'Rack::Attack.Fail2Ban' do end it 'does not increase fail count' do - key = "rack::attack:#{Time.now.to_i/@findtime}:fail2ban:count:1.2.3.4" + key = "rack::attack:#{Time.now.to_i / @findtime}:fail2ban:count:1.2.3.4" @cache.store.read(key).must_equal 2 end @@ -130,7 +130,7 @@ describe 'Rack::Attack.Fail2Ban' do end it 'does not increase fail count' do - key = "rack::attack:#{Time.now.to_i/@findtime}:fail2ban:count:1.2.3.4" + key = "rack::attack:#{Time.now.to_i / @findtime}:fail2ban:count:1.2.3.4" @cache.store.read(key).must_equal 2 end diff --git a/spec/rack_attack_dalli_proxy_spec.rb b/spec/rack_attack_dalli_proxy_spec.rb index 50a2b9c..9065354 100644 --- a/spec/rack_attack_dalli_proxy_spec.rb +++ b/spec/rack_attack_dalli_proxy_spec.rb @@ -1,10 +1,8 @@ require_relative 'spec_helper' describe Rack::Attack::StoreProxy::DalliProxy do - it 'should stub Dalli::Client#with on older clients' do proxy = Rack::Attack::StoreProxy::DalliProxy.new(Class.new) proxy.with {} # will not raise an error end - end diff --git a/spec/rack_attack_spec.rb b/spec/rack_attack_spec.rb index 38733b2..3869b71 100644 --- a/spec/rack_attack_spec.rb +++ b/spec/rack_attack_spec.rb @@ -5,7 +5,7 @@ describe 'Rack::Attack' do describe 'normalizing paths' do before do - Rack::Attack.blocklist("banned_path") {|req| req.path == '/foo' } + Rack::Attack.blocklist("banned_path") { |req| req.path == '/foo' } end it 'blocks requests with trailing slash' do @@ -17,7 +17,7 @@ describe 'Rack::Attack' do describe 'blocklist' do before do @bad_ip = '1.2.3.4' - Rack::Attack.blocklist("ip #{@bad_ip}") {|req| req.ip == @bad_ip } + Rack::Attack.blocklist("ip #{@bad_ip}") { |req| req.ip == @bad_ip } end it('has a blocklist') { @@ -25,7 +25,7 @@ describe 'Rack::Attack' do } it('has a blacklist with a deprication warning') { - _, stderror = capture_io do + _, stderror = capture_io do Rack::Attack.blacklists.key?("ip #{@bad_ip}").must_equal true end assert_match "[DEPRECATION] 'Rack::Attack.blacklists' is deprecated. Please use 'blocklists' instead.", stderror @@ -50,13 +50,13 @@ describe 'Rack::Attack' do describe "and safelist" do before do @good_ua = 'GoodUA' - Rack::Attack.safelist("good ua") {|req| req.user_agent == @good_ua } + Rack::Attack.safelist("good ua") { |req| req.user_agent == @good_ua } end it('has a safelist') { Rack::Attack.safelists.key?("good ua") } it('has a whitelist with a deprication warning') { - _, stderror = capture_io do + _, stderror = capture_io do Rack::Attack.whitelists.key?("good ua") end assert_match "[DEPRECATION] 'Rack::Attack.whitelists' is deprecated. Please use 'safelists' instead.", stderror @@ -82,7 +82,7 @@ describe 'Rack::Attack' do end it 'should give a deprication warning for blacklisted_response' do - _, stderror = capture_io do + _, stderror = capture_io do Rack::Attack.blacklisted_response end assert_match "[DEPRECATION] 'Rack::Attack.blacklisted_response' is deprecated. Please use 'blocklisted_response' instead.", stderror diff --git a/spec/rack_attack_throttle_spec.rb b/spec/rack_attack_throttle_spec.rb index df6235f..8a0df19 100644 --- a/spec/rack_attack_throttle_spec.rb +++ b/spec/rack_attack_throttle_spec.rb @@ -15,7 +15,7 @@ describe 'Rack::Attack.throttle' do before { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' } it 'should set the counter for one request' do - key = "rack::attack:#{Time.now.to_i/@period}:ip/sec:1.2.3.4" + key = "rack::attack:#{Time.now.to_i / @period}:ip/sec:1.2.3.4" Rack::Attack.cache.store.read(key).must_equal 1 end @@ -37,7 +37,7 @@ describe 'Rack::Attack.throttle' do it 'should tag the env' do last_request.env['rack.attack.matched'].must_equal 'ip/sec' last_request.env['rack.attack.match_type'].must_equal :throttle - last_request.env['rack.attack.match_data'].must_equal({:count => 2, :limit => 1, :period => @period}) + last_request.env['rack.attack.match_data'].must_equal({ :count => 2, :limit => 1, :period => @period }) last_request.env['rack.attack.match_discriminator'].must_equal('1.2.3.4') end @@ -60,7 +60,7 @@ describe 'Rack::Attack.throttle with limit as proc' do before { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' } it 'should set the counter for one request' do - key = "rack::attack:#{Time.now.to_i/@period}:ip/sec:1.2.3.4" + key = "rack::attack:#{Time.now.to_i / @period}:ip/sec:1.2.3.4" Rack::Attack.cache.store.read(key).must_equal 1 end @@ -84,7 +84,7 @@ describe 'Rack::Attack.throttle with period as proc' do before { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' } it 'should set the counter for one request' do - key = "rack::attack:#{Time.now.to_i/@period}:ip/sec:1.2.3.4" + key = "rack::attack:#{Time.now.to_i / @period}:ip/sec:1.2.3.4" Rack::Attack.cache.store.read(key).must_equal 1 end @@ -108,7 +108,7 @@ describe 'Rack::Attack.throttle with block retuning nil' do before { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' } it 'should not set the counter' do - key = "rack::attack:#{Time.now.to_i/@period}:ip/sec:1.2.3.4" + key = "rack::attack:#{Time.now.to_i / @period}:ip/sec:1.2.3.4" assert_nil Rack::Attack.cache.store.read(key) end diff --git a/spec/rack_attack_track_spec.rb b/spec/rack_attack_track_spec.rb index cb2f2c9..8e0f9c6 100644 --- a/spec/rack_attack_track_spec.rb +++ b/spec/rack_attack_track_spec.rb @@ -16,7 +16,7 @@ describe 'Rack::Attack.track' do end before do - Rack::Attack.track("everything"){ |req| true } + Rack::Attack.track("everything") { |req| true } end it_allows_ok_requests @@ -31,7 +31,7 @@ describe 'Rack::Attack.track' do before do Counter.reset # A second track - Rack::Attack.track("homepage"){ |req| req.path == "/"} + Rack::Attack.track("homepage") { |req| req.path == "/" } ActiveSupport::Notifications.subscribe("rack.attack") do |*args| Counter.incr @@ -47,14 +47,14 @@ describe 'Rack::Attack.track' do describe "without limit and period options" do it "should assign the track filter to a Check instance" do - tracker = Rack::Attack.track("homepage") { |req| req.path == "/"} + tracker = Rack::Attack.track("homepage") { |req| req.path == "/" } tracker.filter.class.must_equal Rack::Attack::Check end end describe "with limit and period options" do it "should assign the track filter to a Throttle instance" do - tracker = Rack::Attack.track("homepage", :limit => 10, :period => 10) { |req| req.path == "/"} + tracker = Rack::Attack.track("homepage", :limit => 10, :period => 10) { |req| req.path == "/" } tracker.filter.class.must_equal Rack::Attack::Throttle end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3473eba..7d62673 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -12,7 +12,7 @@ require "rack/attack" begin require 'pry' rescue LoadError - #nothing to do here + # nothing to do here end if RUBY_ENGINE == "ruby" @@ -20,7 +20,6 @@ if RUBY_ENGINE == "ruby" end class MiniTest::Spec - include Rack::Test::Methods before do @@ -43,7 +42,7 @@ class MiniTest::Spec use Rack::Attack use Rack::Lint - run lambda {|env| [200, {}, ['Hello World']]} + run lambda { |env| [200, {}, ['Hello World']] } }.to_app end From a6158b4358c4f313c84281244d2aacaf40859167 Mon Sep 17 00:00:00 2001 From: Lucas Mansur Date: Fri, 30 Mar 2018 16:29:28 -0300 Subject: [PATCH 059/129] Add a section on Style Guide in the CONTRIBUTING guide --- CONTRIBUTING.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6cb4183..f6f248f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,3 +14,13 @@ Any of the following is greatly appreciated: * Reporting an error you are experiencing * Suggesting a new feature you think it would be useful for many users * If you want to work on fixing an actual issue and you don't know where to start, those labeled [good first issue](https://github.com/kickstarter/rack-attack/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) may be a good choice + +## Style Guide + +As an effort to keep the codebase consistent, we encourage the use of [Rubocop](https://github.com/bbatsov/rubocop). +This tool helps us abstract most of the decisions we have to make when coding. + +To check your code, simply type `rubocop` in the shell. The resulting output are all the offenses currently present in the code. + +It is highly recommended that you integrate a linter with your editor. +This way you receive real time feedback about your code. Most editors have some kind of plugin for that. From f56967f92382574f5a76a6b3569c2b74d0484ec2 Mon Sep 17 00:00:00 2001 From: Spencer Norman Date: Fri, 6 Apr 2018 08:19:53 -0500 Subject: [PATCH 060/129] fixed spelling issue, returna to return (#334) * fixed spelling issue, returna to returns * fixed the spelling properly this time, returns to return --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 78e5e36..5f11079 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,7 @@ Rack::Attack.blocklist_ip("1.2.0.0/16") #### `blocklist(name, &block)` -Name your custom blocklist and make your ruby-block argument returna a truthy value if you want the request to be blocked, and falsy otherwise. +Name your custom blocklist and make your ruby-block argument return a truthy value if you want the request to be blocked, and falsy otherwise. The request object is a [Rack::Request](http://www.rubydoc.info/gems/rack/Rack/Request). From e7792aadd71c8b7ce5b42e906d608f431279e640 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 10 Apr 2018 10:33:26 -0400 Subject: [PATCH 061/129] Remove obsolete piece of autogenerated appraisal gemfile --- gemfiles/rails_5_2.gemfile | 6 ------ 1 file changed, 6 deletions(-) diff --git a/gemfiles/rails_5_2.gemfile b/gemfiles/rails_5_2.gemfile index f1948ff..98ef9e7 100644 --- a/gemfiles/rails_5_2.gemfile +++ b/gemfiles/rails_5_2.gemfile @@ -5,10 +5,4 @@ source "https://rubygems.org" gem "activesupport", "~> 5.2.0.a" gem "actionpack", "~> 5.2.0.a" -group :development do - gem "pry" - gem "guard" - gem "guard-minitest" -end - gemspec path: "../" From a8200ea95aa71914a8a26a22f7bf86aca1a124d1 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 10 Apr 2018 10:35:14 -0400 Subject: [PATCH 062/129] Explicitly test against supported rack versions --- .travis.yml | 2 ++ Appraisals | 8 ++++++++ gemfiles/rack_1_6.gemfile | 7 +++++++ gemfiles/rack_2_0.gemfile | 7 +++++++ 4 files changed, 24 insertions(+) create mode 100644 gemfiles/rack_1_6.gemfile create mode 100644 gemfiles/rack_2_0.gemfile diff --git a/.travis.yml b/.travis.yml index 0a151bc..3be689c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,8 @@ before_install: - gem install bundler gemfile: + - gemfiles/rack_2_0.gemfile + - gemfiles/rack_1_6.gemfile - gemfiles/rails_5_2.gemfile - gemfiles/rails_5_1.gemfile - gemfiles/rails_5_0.gemfile diff --git a/Appraisals b/Appraisals index 296b89c..7b03cd3 100644 --- a/Appraisals +++ b/Appraisals @@ -1,3 +1,11 @@ +appraise "rack_2_0" do + gem "rack", "~> 2.0.4" +end + +appraise "rack_1_6" do + gem "rack", "~> 1.6.9" +end + appraise 'rails_5-2' do gem 'activesupport', '~> 5.2.0.a' gem 'actionpack', '~> 5.2.0.a' diff --git a/gemfiles/rack_1_6.gemfile b/gemfiles/rack_1_6.gemfile new file mode 100644 index 0000000..18b3acf --- /dev/null +++ b/gemfiles/rack_1_6.gemfile @@ -0,0 +1,7 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "rack", "~> 1.6.9" + +gemspec path: "../" diff --git a/gemfiles/rack_2_0.gemfile b/gemfiles/rack_2_0.gemfile new file mode 100644 index 0000000..9915a10 --- /dev/null +++ b/gemfiles/rack_2_0.gemfile @@ -0,0 +1,7 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "rack", "~> 2.0.4" + +gemspec path: "../" From 2e9eb6716fbcaaeb9590f5d939e2fa861f8639da Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 10 Apr 2018 11:00:49 -0400 Subject: [PATCH 063/129] Don't allow failures against rails 5.2, it's out now --- .travis.yml | 1 - Appraisals | 4 ++-- gemfiles/rails_5_2.gemfile | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3be689c..dbb0b1f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,7 +31,6 @@ gemfile: matrix: allow_failures: - - gemfile: gemfiles/rails_5_2.gemfile - rvm: 2.6.0-preview1 fast_finish: true diff --git a/Appraisals b/Appraisals index 7b03cd3..b9adb10 100644 --- a/Appraisals +++ b/Appraisals @@ -7,8 +7,8 @@ appraise "rack_1_6" do end appraise 'rails_5-2' do - gem 'activesupport', '~> 5.2.0.a' - gem 'actionpack', '~> 5.2.0.a' + gem 'activesupport', '~> 5.2.0' + gem 'actionpack', '~> 5.2.0' end appraise 'rails_5-1' do diff --git a/gemfiles/rails_5_2.gemfile b/gemfiles/rails_5_2.gemfile index 98ef9e7..41b8b1b 100644 --- a/gemfiles/rails_5_2.gemfile +++ b/gemfiles/rails_5_2.gemfile @@ -2,7 +2,7 @@ source "https://rubygems.org" -gem "activesupport", "~> 5.2.0.a" -gem "actionpack", "~> 5.2.0.a" +gem "activesupport", "~> 5.2.0" +gem "actionpack", "~> 5.2.0" gemspec path: "../" From 8a6bb6bc5e5274d358d57861e17f26d9d8ca8607 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 10 Apr 2018 11:43:46 -0400 Subject: [PATCH 064/129] Drop support for rails 5.0, it's an unmaintained series now --- .travis.yml | 1 - Appraisals | 5 ----- gemfiles/rails_5_0.gemfile | 8 -------- 3 files changed, 14 deletions(-) delete mode 100644 gemfiles/rails_5_0.gemfile diff --git a/.travis.yml b/.travis.yml index dbb0b1f..612ece6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,7 +25,6 @@ gemfile: - gemfiles/rack_1_6.gemfile - gemfiles/rails_5_2.gemfile - gemfiles/rails_5_1.gemfile - - gemfiles/rails_5_0.gemfile - gemfiles/rails_4_2.gemfile - gemfiles/dalli2.gemfile diff --git a/Appraisals b/Appraisals index b9adb10..839a9f4 100644 --- a/Appraisals +++ b/Appraisals @@ -16,11 +16,6 @@ appraise 'rails_5-1' do gem 'actionpack', '~> 5.1.0' end -appraise 'rails_5-0' do - gem 'activesupport', '~> 5.0.0' - gem 'actionpack', '~> 5.0.0' -end - appraise 'rails_4-2' do gem 'activesupport', '~> 4.2.0' gem 'actionpack', '~> 4.2.0' diff --git a/gemfiles/rails_5_0.gemfile b/gemfiles/rails_5_0.gemfile deleted file mode 100644 index c03a540..0000000 --- a/gemfiles/rails_5_0.gemfile +++ /dev/null @@ -1,8 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "activesupport", "~> 5.0.0" -gem "actionpack", "~> 5.0.0" - -gemspec path: "../" From 4491e321803f039b20f282493f17846559fac2d2 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 17 Apr 2018 01:03:13 -0400 Subject: [PATCH 065/129] Enable Bundler rubocop cops --- .rubocop.yml | 2 ++ Appraisals | 6 +++--- gemfiles/rails_4_2.gemfile | 2 +- gemfiles/rails_5_1.gemfile | 2 +- gemfiles/rails_5_2.gemfile | 2 +- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index cf91fe2..2f136cb 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,5 +1,7 @@ AllCops: DisabledByDefault: true +Bundler: + Enabled: true Layout: Enabled: true diff --git a/Appraisals b/Appraisals index 839a9f4..f0495db 100644 --- a/Appraisals +++ b/Appraisals @@ -7,18 +7,18 @@ appraise "rack_1_6" do end appraise 'rails_5-2' do - gem 'activesupport', '~> 5.2.0' gem 'actionpack', '~> 5.2.0' + gem 'activesupport', '~> 5.2.0' end appraise 'rails_5-1' do - gem 'activesupport', '~> 5.1.0' gem 'actionpack', '~> 5.1.0' + gem 'activesupport', '~> 5.1.0' end appraise 'rails_4-2' do - gem 'activesupport', '~> 4.2.0' gem 'actionpack', '~> 4.2.0' + gem 'activesupport', '~> 4.2.0' end appraise 'dalli2' do diff --git a/gemfiles/rails_4_2.gemfile b/gemfiles/rails_4_2.gemfile index 7a03e76..3937ece 100644 --- a/gemfiles/rails_4_2.gemfile +++ b/gemfiles/rails_4_2.gemfile @@ -2,7 +2,7 @@ source "https://rubygems.org" -gem "activesupport", "~> 4.2.0" gem "actionpack", "~> 4.2.0" +gem "activesupport", "~> 4.2.0" gemspec path: "../" diff --git a/gemfiles/rails_5_1.gemfile b/gemfiles/rails_5_1.gemfile index 98a6051..d05d8e4 100644 --- a/gemfiles/rails_5_1.gemfile +++ b/gemfiles/rails_5_1.gemfile @@ -2,7 +2,7 @@ source "https://rubygems.org" -gem "activesupport", "~> 5.1.0" gem "actionpack", "~> 5.1.0" +gem "activesupport", "~> 5.1.0" gemspec path: "../" diff --git a/gemfiles/rails_5_2.gemfile b/gemfiles/rails_5_2.gemfile index 41b8b1b..d12b8c6 100644 --- a/gemfiles/rails_5_2.gemfile +++ b/gemfiles/rails_5_2.gemfile @@ -2,7 +2,7 @@ source "https://rubygems.org" -gem "activesupport", "~> 5.2.0" gem "actionpack", "~> 5.2.0" +gem "activesupport", "~> 5.2.0" gemspec path: "../" From 29424fb993760e5820579d754ec14f3017775fec Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 17 Apr 2018 14:01:07 -0400 Subject: [PATCH 066/129] Enable Gemspec rubocop cops Run rubocop against oldest supported ruby --- .rubocop.yml | 5 +++++ rack-attack.gemspec | 19 ++++++++++--------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 2f136cb..91492ab 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,7 +1,12 @@ AllCops: + TargetRubyVersion: 2.2 DisabledByDefault: true Bundler: Enabled: true + +Gemspec: + Enabled: true + Layout: Enabled: true diff --git a/rack-attack.gemspec b/rack-attack.gemspec index 6803d0a..85aa1c6 100644 --- a/rack-attack.gemspec +++ b/rack-attack.gemspec @@ -21,24 +21,25 @@ Gem::Specification.new do |s| s.summary = %q{Block & throttle abusive requests} s.test_files = Dir.glob("spec/**/*") - s.required_ruby_version = '>= 2.0.0' + s.required_ruby_version = '>= 2.2' s.add_dependency 'rack' + s.add_development_dependency 'actionpack', '>= 3.0.0' + s.add_development_dependency 'activesupport', '>= 3.0.0' + s.add_development_dependency 'appraisal' + s.add_development_dependency 'connection_pool' + s.add_development_dependency 'dalli' + s.add_development_dependency 'guard-minitest' + s.add_development_dependency 'memcache-client' s.add_development_dependency 'minitest' s.add_development_dependency "minitest-stub-const" + s.add_development_dependency 'pry' s.add_development_dependency 'rack-test' s.add_development_dependency 'rake' - s.add_development_dependency 'appraisal' - s.add_development_dependency 'activesupport', '>= 3.0.0' - s.add_development_dependency 'actionpack', '>= 3.0.0' s.add_development_dependency 'redis-activesupport' - s.add_development_dependency 'dalli' - s.add_development_dependency 'connection_pool' - s.add_development_dependency 'memcache-client' s.add_development_dependency "timecop" - s.add_development_dependency 'pry' - s.add_development_dependency 'guard-minitest' + # Need to explicitly depend on guard because guard-minitest doesn't declare # the dependency intentionally # From ef24d262c8f2368834a9e9ea7c5191013c6fb9c4 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 17 Apr 2018 14:13:35 -0400 Subject: [PATCH 067/129] Enable Performance rubocop cops --- .rubocop.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.rubocop.yml b/.rubocop.yml index 91492ab..4fc4787 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -10,3 +10,6 @@ Gemspec: Layout: Enabled: true + +Performance: + Enabled: true From 9faf6cd32b363738848e207490cd55bf17f059c2 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 17 Apr 2018 14:16:17 -0400 Subject: [PATCH 068/129] Enable Security rubocop cops --- .rubocop.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.rubocop.yml b/.rubocop.yml index 4fc4787..456f6d1 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -13,3 +13,6 @@ Layout: Performance: Enabled: true + +Security: + Enabled: true From d01c3e61d4d726cd462fbf25b539eef64c07347c Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 17 Apr 2018 15:59:46 -0400 Subject: [PATCH 069/129] Enforce every rack-attack contributor runs same rubocop version --- CONTRIBUTING.md | 2 +- rack-attack.gemspec | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f6f248f..d664561 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,7 +20,7 @@ Any of the following is greatly appreciated: As an effort to keep the codebase consistent, we encourage the use of [Rubocop](https://github.com/bbatsov/rubocop). This tool helps us abstract most of the decisions we have to make when coding. -To check your code, simply type `rubocop` in the shell. The resulting output are all the offenses currently present in the code. +To check your code, simply type `bundle exec rubocop` in the shell. The resulting output are all the offenses currently present in the code. It is highly recommended that you integrate a linter with your editor. This way you receive real time feedback about your code. Most editors have some kind of plugin for that. diff --git a/rack-attack.gemspec b/rack-attack.gemspec index 85aa1c6..f63c35c 100644 --- a/rack-attack.gemspec +++ b/rack-attack.gemspec @@ -38,6 +38,7 @@ Gem::Specification.new do |s| s.add_development_dependency 'rack-test' s.add_development_dependency 'rake' s.add_development_dependency 'redis-activesupport' + s.add_development_dependency "rubocop", "0.55.0" s.add_development_dependency "timecop" # Need to explicitly depend on guard because guard-minitest doesn't declare From 52ec80692da39c33230fb341fcc89a6d8794fa37 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 17 Apr 2018 16:26:09 -0400 Subject: [PATCH 070/129] Enable Lint rubocop cops --- .rubocop.yml | 10 ++++++++++ lib/rack/attack.rb | 4 ++-- lib/rack/attack/store_proxy.rb | 2 -- lib/rack/attack/store_proxy/mem_cache_proxy.rb | 4 ++-- lib/rack/attack/store_proxy/redis_store_proxy.rb | 2 +- .../cache_store_config_for_allow2ban_spec.rb | 4 ++-- .../acceptance/cache_store_config_for_fail2ban_spec.rb | 4 ++-- .../acceptance/cache_store_config_for_throttle_spec.rb | 2 +- spec/acceptance/customizing_blocked_response_spec.rb | 2 +- spec/acceptance/customizing_throttled_response_spec.rb | 2 +- spec/rack_attack_throttle_spec.rb | 4 ++-- spec/rack_attack_track_spec.rb | 4 ++-- spec/spec_helper.rb | 2 +- 13 files changed, 27 insertions(+), 19 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 456f6d1..4631b4a 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,6 +1,8 @@ AllCops: TargetRubyVersion: 2.2 DisabledByDefault: true + Exclude: + - "examples/instrumentation.rb" Bundler: Enabled: true @@ -16,3 +18,11 @@ Performance: Security: Enabled: true + +Lint: + Enabled: true + +# TODO +# Remove cop disabling and fix offenses +Lint/HandleExceptions: + Enabled: false diff --git a/lib/rack/attack.rb b/lib/rack/attack.rb index ad5e1eb..c7596ef 100644 --- a/lib/rack/attack.rb +++ b/lib/rack/attack.rb @@ -101,7 +101,7 @@ class Rack::Attack end def throttled?(req) - throttles.any? do |name, throttle| + throttles.any? do |_name, throttle| throttle[req] end end @@ -149,7 +149,7 @@ class Rack::Attack # Set defaults @notifier = ActiveSupport::Notifications if defined?(ActiveSupport::Notifications) - @blocklisted_response = lambda { |env| [403, { 'Content-Type' => 'text/plain' }, ["Forbidden\n"]] } + @blocklisted_response = lambda { |_env| [403, { 'Content-Type' => 'text/plain' }, ["Forbidden\n"]] } @throttled_response = lambda { |env| retry_after = (env['rack.attack.match_data'] || {})[:period] [429, { 'Content-Type' => 'text/plain', 'Retry-After' => retry_after.to_s }, ["Retry later\n"]] diff --git a/lib/rack/attack/store_proxy.rb b/lib/rack/attack/store_proxy.rb index d8edddb..d83cab9 100644 --- a/lib/rack/attack/store_proxy.rb +++ b/lib/rack/attack/store_proxy.rb @@ -12,8 +12,6 @@ module Rack klass ? klass.new(client) : client end - private - def self.unwrap_active_support_stores(store) # ActiveSupport::Cache::RedisStore doesn't expose any way to set an expiry, # so use the raw Redis::Store instead. diff --git a/lib/rack/attack/store_proxy/mem_cache_proxy.rb b/lib/rack/attack/store_proxy/mem_cache_proxy.rb index bc23942..4675789 100644 --- a/lib/rack/attack/store_proxy/mem_cache_proxy.rb +++ b/lib/rack/attack/store_proxy/mem_cache_proxy.rb @@ -23,12 +23,12 @@ module Rack rescue MemCache::MemCacheError end - def increment(key, amount, options = {}) + def increment(key, amount, _options = {}) incr(key, amount) rescue MemCache::MemCacheError end - def delete(key, options = {}) + def delete(key, _options = {}) with do |client| client.delete(key) end diff --git a/lib/rack/attack/store_proxy/redis_store_proxy.rb b/lib/rack/attack/store_proxy/redis_store_proxy.rb index 9833c26..1741b99 100644 --- a/lib/rack/attack/store_proxy/redis_store_proxy.rb +++ b/lib/rack/attack/store_proxy/redis_store_proxy.rb @@ -45,7 +45,7 @@ module Rack rescue Redis::BaseError end - def delete(key, options = {}) + def delete(key, _options = {}) del(key) rescue Redis::BaseError end diff --git a/spec/acceptance/cache_store_config_for_allow2ban_spec.rb b/spec/acceptance/cache_store_config_for_allow2ban_spec.rb index aa7208e..4b6cb11 100644 --- a/spec/acceptance/cache_store_config_for_allow2ban_spec.rb +++ b/spec/acceptance/cache_store_config_for_allow2ban_spec.rb @@ -81,11 +81,11 @@ describe "Cache store config when using allow2ban" do @backend[key] end - def write(key, value, options = {}) + def write(key, value, _options = {}) @backend[key] = value end - def increment(key, count, options = {}) + def increment(key, _count, _options = {}) @backend[key] ||= 0 @backend[key] += 1 end diff --git a/spec/acceptance/cache_store_config_for_fail2ban_spec.rb b/spec/acceptance/cache_store_config_for_fail2ban_spec.rb index 53b43e5..4f978a1 100644 --- a/spec/acceptance/cache_store_config_for_fail2ban_spec.rb +++ b/spec/acceptance/cache_store_config_for_fail2ban_spec.rb @@ -81,11 +81,11 @@ describe "Cache store config when using fail2ban" do @backend[key] end - def write(key, value, options = {}) + def write(key, value, _options = {}) @backend[key] = value end - def increment(key, count, options = {}) + def increment(key, _count, _options = {}) @backend[key] ||= 0 @backend[key] += 1 end diff --git a/spec/acceptance/cache_store_config_for_throttle_spec.rb b/spec/acceptance/cache_store_config_for_throttle_spec.rb index 9df9a6c..524048b 100644 --- a/spec/acceptance/cache_store_config_for_throttle_spec.rb +++ b/spec/acceptance/cache_store_config_for_throttle_spec.rb @@ -29,7 +29,7 @@ describe "Cache store config when throttling without Rails" do @counts = {} end - def increment(key, count, options) + def increment(key, _count, _options) @counts[key] ||= 0 @counts[key] += 1 end diff --git a/spec/acceptance/customizing_blocked_response_spec.rb b/spec/acceptance/customizing_blocked_response_spec.rb index 190f5c0..cf297b0 100644 --- a/spec/acceptance/customizing_blocked_response_spec.rb +++ b/spec/acceptance/customizing_blocked_response_spec.rb @@ -12,7 +12,7 @@ describe "Customizing block responses" do assert_equal 403, last_response.status - Rack::Attack.blocklisted_response = lambda do |env| + Rack::Attack.blocklisted_response = lambda do |_env| [503, {}, ["Blocked"]] end diff --git a/spec/acceptance/customizing_throttled_response_spec.rb b/spec/acceptance/customizing_throttled_response_spec.rb index 5f657aa..61acc01 100644 --- a/spec/acceptance/customizing_throttled_response_spec.rb +++ b/spec/acceptance/customizing_throttled_response_spec.rb @@ -18,7 +18,7 @@ describe "Customizing throttled response" do assert_equal 429, last_response.status - Rack::Attack.throttled_response = lambda do |env| + Rack::Attack.throttled_response = lambda do |_env| [503, {}, ["Throttled"]] end diff --git a/spec/rack_attack_throttle_spec.rb b/spec/rack_attack_throttle_spec.rb index 8a0df19..0361c7c 100644 --- a/spec/rack_attack_throttle_spec.rb +++ b/spec/rack_attack_throttle_spec.rb @@ -51,7 +51,7 @@ describe 'Rack::Attack.throttle with limit as proc' 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 => lambda { |req| 1 }, :period => @period) { |req| req.ip } + Rack::Attack.throttle('ip/sec', :limit => lambda { |_req| 1 }, :period => @period) { |req| req.ip } end it_allows_ok_requests @@ -75,7 +75,7 @@ describe 'Rack::Attack.throttle with period as proc' 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 => lambda { |req| 1 }, :period => lambda { |req| @period }) { |req| req.ip } + Rack::Attack.throttle('ip/sec', :limit => lambda { |_req| 1 }, :period => lambda { |_req| @period }) { |req| req.ip } end it_allows_ok_requests diff --git a/spec/rack_attack_track_spec.rb b/spec/rack_attack_track_spec.rb index 8e0f9c6..24a408f 100644 --- a/spec/rack_attack_track_spec.rb +++ b/spec/rack_attack_track_spec.rb @@ -16,7 +16,7 @@ describe 'Rack::Attack.track' do end before do - Rack::Attack.track("everything") { |req| true } + Rack::Attack.track("everything") { |_req| true } end it_allows_ok_requests @@ -33,7 +33,7 @@ describe 'Rack::Attack.track' do # A second track Rack::Attack.track("homepage") { |req| req.path == "/" } - ActiveSupport::Notifications.subscribe("rack.attack") do |*args| + ActiveSupport::Notifications.subscribe("rack.attack") do |*_args| Counter.incr end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 7d62673..8f8a71d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -42,7 +42,7 @@ class MiniTest::Spec use Rack::Attack use Rack::Lint - run lambda { |env| [200, {}, ['Hello World']] } + run lambda { |_env| [200, {}, ['Hello World']] } }.to_app end From b5b4164967685cbd4a2058bfa203df543f714c2d Mon Sep 17 00:00:00 2001 From: Brian Kephart Date: Sun, 13 May 2018 16:44:20 -0500 Subject: [PATCH 071/129] replace `const_defined?` with `defined?` --- lib/rack/attack/store_proxy/redis_store_proxy.rb | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/lib/rack/attack/store_proxy/redis_store_proxy.rb b/lib/rack/attack/store_proxy/redis_store_proxy.rb index 1741b99..f9f2c5b 100644 --- a/lib/rack/attack/store_proxy/redis_store_proxy.rb +++ b/lib/rack/attack/store_proxy/redis_store_proxy.rb @@ -5,14 +5,7 @@ module Rack module StoreProxy class RedisStoreProxy < SimpleDelegator def self.handle?(store) - # Using const_defined? for now. - # - # Go back to use defined? once this ruby issue is - # fixed and released: - # https://bugs.ruby-lang.org/issues/14407 - # - # defined?(::Redis::Store) && store.is_a?(::Redis::Store) - const_defined?("::Redis::Store") && store.is_a?(::Redis::Store) + defined?(::Redis::Store) && store.is_a?(::Redis::Store) end def initialize(store) From 8ca804e66815afd2d08876782934d15f186b24b6 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Thu, 17 May 2018 13:44:52 -0300 Subject: [PATCH 072/129] Acceptance test use of ActiveSupport::Cache::RedisCacheStore --- Rakefile | 2 +- .../stores/redis_cache_store_spec.rb | 70 +++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 spec/acceptance/stores/redis_cache_store_spec.rb diff --git a/Rakefile b/Rakefile index 475bece..52b39f2 100644 --- a/Rakefile +++ b/Rakefile @@ -13,7 +13,7 @@ namespace :test do end Rake::TestTask.new(:acceptance) do |t| - t.pattern = "spec/acceptance/*_spec.rb" + t.pattern = "spec/acceptance/**/*_spec.rb" end end diff --git a/spec/acceptance/stores/redis_cache_store_spec.rb b/spec/acceptance/stores/redis_cache_store_spec.rb new file mode 100644 index 0000000..4e47126 --- /dev/null +++ b/spec/acceptance/stores/redis_cache_store_spec.rb @@ -0,0 +1,70 @@ +require_relative "../../spec_helper" + +if ActiveSupport.version >= Gem::Version.new("5.2.0") + describe "RedisCacheStore as a cache backend" do + before do + Rack::Attack.cache.store = ActiveSupport::Cache::RedisCacheStore.new + end + + after do + Rack::Attack.cache.store.clear + end + + it "works for throttle" do + Rack::Attack.throttle("by ip", limit: 1, period: 60) do |request| + request.ip + end + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal 200, last_response.status + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal 429, last_response.status + end + + it "works for fail2ban" do + Rack::Attack.blocklist("fail2ban pentesters") do |request| + Rack::Attack::Fail2Ban.filter(request.ip, maxretry: 2, findtime: 30, bantime: 60) do + request.path.include?("private-place") + end + end + + get "/" + assert_equal 200, last_response.status + + get "/private-place" + assert_equal 403, last_response.status + + get "/private-place" + assert_equal 403, last_response.status + + get "/" + assert_equal 403, last_response.status + end + + it "works for allow2ban" do + Rack::Attack.blocklist("allow2ban pentesters") do |request| + Rack::Attack::Allow2Ban.filter(request.ip, maxretry: 2, findtime: 30, bantime: 60) do + request.path.include?("scarce-resource") + end + end + + get "/" + assert_equal 200, last_response.status + + get "/scarce-resource" + assert_equal 200, last_response.status + + get "/scarce-resource" + assert_equal 200, last_response.status + + get "/scarce-resource" + assert_equal 403, last_response.status + + get "/" + assert_equal 403, last_response.status + end + end +end From 379cd39dc982bbb0d4ebf7f8eb192db2f153b70c Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Fri, 18 May 2018 14:10:07 -0300 Subject: [PATCH 073/129] Acceptance test use of ActiveSupport::Cache::MemCacheStore --- .../acceptance/stores/mem_cache_store_spec.rb | 14 +++++ .../stores/redis_cache_store_spec.rb | 58 +------------------ spec/support/cache_store_helper.rb | 58 +++++++++++++++++++ 3 files changed, 74 insertions(+), 56 deletions(-) create mode 100644 spec/acceptance/stores/mem_cache_store_spec.rb create mode 100644 spec/support/cache_store_helper.rb diff --git a/spec/acceptance/stores/mem_cache_store_spec.rb b/spec/acceptance/stores/mem_cache_store_spec.rb new file mode 100644 index 0000000..2ee8832 --- /dev/null +++ b/spec/acceptance/stores/mem_cache_store_spec.rb @@ -0,0 +1,14 @@ +require_relative "../../spec_helper" +require_relative "../../support/cache_store_helper" + +describe "MemCacheStore as a cache backend" do + before do + Rack::Attack.cache.store = ActiveSupport::Cache::MemCacheStore.new + end + + after do + Rack::Attack.cache.store.flush_all + end + + it_works_for_cache_backed_features +end diff --git a/spec/acceptance/stores/redis_cache_store_spec.rb b/spec/acceptance/stores/redis_cache_store_spec.rb index 4e47126..de36275 100644 --- a/spec/acceptance/stores/redis_cache_store_spec.rb +++ b/spec/acceptance/stores/redis_cache_store_spec.rb @@ -1,4 +1,5 @@ require_relative "../../spec_helper" +require_relative "../../support/cache_store_helper" if ActiveSupport.version >= Gem::Version.new("5.2.0") describe "RedisCacheStore as a cache backend" do @@ -10,61 +11,6 @@ if ActiveSupport.version >= Gem::Version.new("5.2.0") Rack::Attack.cache.store.clear end - it "works for throttle" do - Rack::Attack.throttle("by ip", limit: 1, period: 60) do |request| - request.ip - end - - get "/", {}, "REMOTE_ADDR" => "1.2.3.4" - - assert_equal 200, last_response.status - - get "/", {}, "REMOTE_ADDR" => "1.2.3.4" - - assert_equal 429, last_response.status - end - - it "works for fail2ban" do - Rack::Attack.blocklist("fail2ban pentesters") do |request| - Rack::Attack::Fail2Ban.filter(request.ip, maxretry: 2, findtime: 30, bantime: 60) do - request.path.include?("private-place") - end - end - - get "/" - assert_equal 200, last_response.status - - get "/private-place" - assert_equal 403, last_response.status - - get "/private-place" - assert_equal 403, last_response.status - - get "/" - assert_equal 403, last_response.status - end - - it "works for allow2ban" do - Rack::Attack.blocklist("allow2ban pentesters") do |request| - Rack::Attack::Allow2Ban.filter(request.ip, maxretry: 2, findtime: 30, bantime: 60) do - request.path.include?("scarce-resource") - end - end - - get "/" - assert_equal 200, last_response.status - - get "/scarce-resource" - assert_equal 200, last_response.status - - get "/scarce-resource" - assert_equal 200, last_response.status - - get "/scarce-resource" - assert_equal 403, last_response.status - - get "/" - assert_equal 403, last_response.status - end + it_works_for_cache_backed_features end end diff --git a/spec/support/cache_store_helper.rb b/spec/support/cache_store_helper.rb new file mode 100644 index 0000000..2ca7606 --- /dev/null +++ b/spec/support/cache_store_helper.rb @@ -0,0 +1,58 @@ +class Minitest::Spec + def self.it_works_for_cache_backed_features + it "works for throttle" do + Rack::Attack.throttle("by ip", limit: 1, period: 60) do |request| + request.ip + end + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + assert_equal 200, last_response.status + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + assert_equal 429, last_response.status + end + + it "works for fail2ban" do + Rack::Attack.blocklist("fail2ban pentesters") do |request| + Rack::Attack::Fail2Ban.filter(request.ip, maxretry: 2, findtime: 30, bantime: 60) do + request.path.include?("private-place") + end + end + + get "/" + assert_equal 200, last_response.status + + get "/private-place" + assert_equal 403, last_response.status + + get "/private-place" + assert_equal 403, last_response.status + + get "/" + assert_equal 403, last_response.status + end + + it "works for allow2ban" do + Rack::Attack.blocklist("allow2ban pentesters") do |request| + Rack::Attack::Allow2Ban.filter(request.ip, maxretry: 2, findtime: 30, bantime: 60) do + request.path.include?("scarce-resource") + end + end + + get "/" + assert_equal 200, last_response.status + + get "/scarce-resource" + assert_equal 200, last_response.status + + get "/scarce-resource" + assert_equal 200, last_response.status + + get "/scarce-resource" + assert_equal 403, last_response.status + + get "/" + assert_equal 403, last_response.status + end + end +end From a99722bf4bca9ca89f73ff4ab76c07e74dd9abc0 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Fri, 18 May 2018 18:23:59 -0300 Subject: [PATCH 074/129] Avoid user confusion by renaming .clear! to .clear_configuration --- lib/rack/attack.rb | 7 ++++++- spec/spec_helper.rb | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/rack/attack.rb b/lib/rack/attack.rb index c7596ef..8334dc6 100644 --- a/lib/rack/attack.rb +++ b/lib/rack/attack.rb @@ -120,12 +120,17 @@ class Rack::Attack @cache ||= Cache.new end - def clear! + def clear_configuration @safelists, @blocklists, @throttles, @tracks = {}, {}, {}, {} @ip_blocklists = [] @ip_safelists = [] end + def clear! + warn "[DEPRECATION] Rack::Attack.clear! is deprecated. Please use Rack::Attack.clear_configuration instead" + clear_configuration + end + def blacklisted_response=(res) warn "[DEPRECATION] 'Rack::Attack.blacklisted_response=' is deprecated. Please use 'blocklisted_response=' instead." self.blocklisted_response = res diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8f8a71d..3f19b8f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -28,7 +28,7 @@ class MiniTest::Spec end after do - Rack::Attack.clear! + Rack::Attack.clear_configuration Rack::Attack.instance_variable_set(:@cache, nil) Rack::Attack.throttled_response = @_original_throttled_response From 8f3bf216db5a6d0a10438f183b6ba488bc0108c0 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 19 Jun 2018 11:36:40 -0300 Subject: [PATCH 075/129] Acceptance test MemCacheStore as a store backend doesn't leak keys --- .../acceptance/stores/mem_cache_store_spec.rb | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/spec/acceptance/stores/mem_cache_store_spec.rb b/spec/acceptance/stores/mem_cache_store_spec.rb index 2ee8832..d8684bd 100644 --- a/spec/acceptance/stores/mem_cache_store_spec.rb +++ b/spec/acceptance/stores/mem_cache_store_spec.rb @@ -1,6 +1,8 @@ require_relative "../../spec_helper" require_relative "../../support/cache_store_helper" +require "timecop" + describe "MemCacheStore as a cache backend" do before do Rack::Attack.cache.store = ActiveSupport::Cache::MemCacheStore.new @@ -11,4 +13,26 @@ describe "MemCacheStore as a cache backend" do end it_works_for_cache_backed_features + + it "doesn't leak keys" do + Rack::Attack.throttle("by ip", limit: 1, period: 1) do |request| + request.ip + end + + key = nil + + # Freeze time during these statement to be sure that the key used by rack attack is the same + # we pre-calculate in local variable `key` + Timecop.freeze do + key = "rack::attack:#{Time.now.to_i}:by ip:1.2.3.4" + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + end + + assert_equal "1", Rack::Attack.cache.store.get(key) + + sleep 2.1 + + assert_nil Rack::Attack.cache.store.get(key) + end end From d831f2490e2a90c1840f0b1a28512d565b265af6 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 19 Jun 2018 12:12:45 -0300 Subject: [PATCH 076/129] Acceptance test RedisCacheStore as a store backend doesn't leak keys --- .../stores/redis_cache_store_spec.rb | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/spec/acceptance/stores/redis_cache_store_spec.rb b/spec/acceptance/stores/redis_cache_store_spec.rb index de36275..b0e84e9 100644 --- a/spec/acceptance/stores/redis_cache_store_spec.rb +++ b/spec/acceptance/stores/redis_cache_store_spec.rb @@ -1,6 +1,8 @@ require_relative "../../spec_helper" require_relative "../../support/cache_store_helper" +require "timecop" + if ActiveSupport.version >= Gem::Version.new("5.2.0") describe "RedisCacheStore as a cache backend" do before do @@ -12,5 +14,28 @@ if ActiveSupport.version >= Gem::Version.new("5.2.0") end it_works_for_cache_backed_features + + it "doesn't leak keys" do + Rack::Attack.throttle("by ip", limit: 1, period: 1) do |request| + request.ip + end + + key = nil + + # Freeze time during these statement to be sure that the key used by rack attack is the same + # we pre-calculate in local variable `key` + Timecop.freeze do + key = "rack::attack:#{Time.now.to_i}:by ip:1.2.3.4" + + # puts key + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + end + + assert_equal "1", Rack::Attack.cache.store.fetch(key) + + sleep 2.1 + + assert_nil Rack::Attack.cache.store.fetch(key) + end end end From 4cc8d7d854c61295220ae002bed20e97f6f36e4b Mon Sep 17 00:00:00 2001 From: Brian Kephart Date: Sun, 13 May 2018 18:18:50 -0500 Subject: [PATCH 077/129] 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 6b93015..e09d049 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 8334dc6..1140f91 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 d83cab9..62a7e68 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 0000000..319445e --- /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 86e4023..2e9f9e5 100644 --- a/spec/integration/rack_attack_cache_spec.rb +++ b/spec/integration/rack_attack_cache_spec.rb @@ -2,7 +2,7 @@ require_relative '../spec_helper' 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 @@ describe Rack::Attack::Cache do 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 @@ describe Rack::Attack::Cache do 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) From 7438e5122e868ac3fe2a3455b64b312f65a628d3 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 19 Jun 2018 14:00:20 -0300 Subject: [PATCH 078/129] Non-leak acceptance test should only care about presence, not exact value --- spec/acceptance/stores/mem_cache_store_spec.rb | 2 +- spec/acceptance/stores/redis_cache_store_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/acceptance/stores/mem_cache_store_spec.rb b/spec/acceptance/stores/mem_cache_store_spec.rb index d8684bd..6c593f1 100644 --- a/spec/acceptance/stores/mem_cache_store_spec.rb +++ b/spec/acceptance/stores/mem_cache_store_spec.rb @@ -29,7 +29,7 @@ describe "MemCacheStore as a cache backend" do get "/", {}, "REMOTE_ADDR" => "1.2.3.4" end - assert_equal "1", Rack::Attack.cache.store.get(key) + assert Rack::Attack.cache.store.get(key) sleep 2.1 diff --git a/spec/acceptance/stores/redis_cache_store_spec.rb b/spec/acceptance/stores/redis_cache_store_spec.rb index b0e84e9..b819749 100644 --- a/spec/acceptance/stores/redis_cache_store_spec.rb +++ b/spec/acceptance/stores/redis_cache_store_spec.rb @@ -31,7 +31,7 @@ if ActiveSupport.version >= Gem::Version.new("5.2.0") get "/", {}, "REMOTE_ADDR" => "1.2.3.4" end - assert_equal "1", Rack::Attack.cache.store.fetch(key) + assert Rack::Attack.cache.store.fetch(key) sleep 2.1 From dac926cf6842b880cd9f5c1105850c2889879176 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 19 Jun 2018 15:49:20 -0300 Subject: [PATCH 079/129] Bump version to v5.3.0 --- CHANGELOG.md | 11 +++++++++-- lib/rack/attack/version.rb | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e09d049..55e9ae1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,13 @@ All notable changes to this project will be documented in this file. ## [Unreleased] -- Adds support for ActiveSupport::RedisCacheStore +_No significant changes since last release yet. Stay tuned_ :radio: + +## [5.3.0] - 2018-06-19 + +### Added + +- Add support for [`ActiveSupport::Cache::RedisCacheStore`](http://api.rubyonrails.org/classes/ActiveSupport/Cache/RedisCacheStore.html) as a store backend ([#340](https://github.com/kickstarter/rack-attack/pull/340) and [#350](https://github.com/kickstarter/rack-attack/pull/350)) ## [5.2.0] - 2018-03-29 @@ -105,7 +111,8 @@ All notable changes to this project will be documented in this file. - Remove unused variable - Extract mandatory options to constants -[Unreleased]: https://github.com/kickstarter/rack-attack/compare/v5.2.0...HEAD/ +[Unreleased]: https://github.com/kickstarter/rack-attack/compare/v5.3.0...HEAD/ +[5.3.0]: https://github.com/kickstarter/rack-attack/compare/v5.2.0...v5.3.0/ [5.2.0]: https://github.com/kickstarter/rack-attack/compare/v5.1.0...v5.2.0/ [5.1.0]: https://github.com/kickstarter/rack-attack/compare/v5.0.1...v5.1.0/ [5.0.1]: https://github.com/kickstarter/rack-attack/compare/v5.0.0...v5.0.1/ diff --git a/lib/rack/attack/version.rb b/lib/rack/attack/version.rb index a2f9e08..c836469 100644 --- a/lib/rack/attack/version.rb +++ b/lib/rack/attack/version.rb @@ -1,5 +1,5 @@ module Rack class Attack - VERSION = '5.2.0' + VERSION = '5.3.0' end end From 60300145d8a7fd89af25c8754e36d3151413c70d Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 19 Jun 2018 17:19:42 -0300 Subject: [PATCH 080/129] Enable rubocop Lint cops --- .rubocop.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.rubocop.yml b/.rubocop.yml index 4631b4a..b7575cc 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -13,6 +13,9 @@ Gemspec: Layout: Enabled: true +Lint: + Enabled: true + Performance: Enabled: true From 972a19006aced95efefd6c1ecf9dc8ebadb26198 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 19 Jun 2018 17:25:36 -0300 Subject: [PATCH 081/129] Enable a couple of rubocop Style cops --- .rubocop.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.rubocop.yml b/.rubocop.yml index b7575cc..ed37697 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -25,6 +25,12 @@ Security: Lint: Enabled: true +Style/FrozenStringLiteralComment: + Enabled: true + +Style/RedundantFreeze: + Enabled: true + # TODO # Remove cop disabling and fix offenses Lint/HandleExceptions: From e6854bcb023169030cdbf3c6d6ad89e68dcdad3e Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 19 Jun 2018 17:36:21 -0300 Subject: [PATCH 082/129] Enable rubocop Naming cops --- .rubocop.yml | 5 +++++ lib/rack/attack.rb | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index ed37697..5ca4365 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -16,6 +16,11 @@ Layout: Lint: Enabled: true +Naming: + Enabled: true + Exclude: + - "lib/rack/attack/path_normalizer.rb" + Performance: Enabled: true diff --git a/lib/rack/attack.rb b/lib/rack/attack.rb index 1140f91..4a96778 100644 --- a/lib/rack/attack.rb +++ b/lib/rack/attack.rb @@ -38,15 +38,15 @@ class Rack::Attack self.blocklists[name] = Blocklist.new(name, block) end - def blocklist_ip(ip) + def blocklist_ip(ip_address) @ip_blocklists ||= [] - ip_blocklist_proc = lambda { |request| IPAddr.new(ip).include?(IPAddr.new(request.ip)) } + ip_blocklist_proc = lambda { |request| IPAddr.new(ip_address).include?(IPAddr.new(request.ip)) } @ip_blocklists << Blocklist.new(nil, ip_blocklist_proc) end - def safelist_ip(ip) + def safelist_ip(ip_address) @ip_safelists ||= [] - ip_safelist_proc = lambda { |request| IPAddr.new(ip).include?(IPAddr.new(request.ip)) } + ip_safelist_proc = lambda { |request| IPAddr.new(ip_address).include?(IPAddr.new(request.ip)) } @ip_safelists << Safelist.new(nil, ip_safelist_proc) end From 1e9d601483791b90d8ef6a3c21b14c8bb31eda1c Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 19 Jun 2018 17:52:16 -0300 Subject: [PATCH 083/129] Run rubocop checks when running rake default task --- Rakefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index 52b39f2..961943b 100644 --- a/Rakefile +++ b/Rakefile @@ -2,6 +2,9 @@ require "rubygems" require "bundler/setup" require 'bundler/gem_tasks' require 'rake/testtask' +require "rubocop/rake_task" + +RuboCop::RakeTask.new namespace :test do Rake::TestTask.new(:units) do |t| @@ -20,4 +23,4 @@ end desc 'Run tests' task :test => %w[test:units test:integration test:acceptance] -task :default => :test +task :default => [:rubocop, :test] From 86eb9f9e0a6ed4a265a16946f56a7f452fc7c9f5 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 19 Jun 2018 17:55:24 -0300 Subject: [PATCH 084/129] Enable Style/BracesAroundHashParameters rubocop cop --- .rubocop.yml | 3 +++ lib/rack/attack/store_proxy/redis_cache_store_proxy.rb | 4 ++-- spec/rack_attack_throttle_spec.rb | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 5ca4365..1d679e3 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -30,6 +30,9 @@ Security: Lint: Enabled: true +Style/BracesAroundHashParameters: + Enabled: true + Style/FrozenStringLiteralComment: Enabled: true 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 319445e..8bb3f46 100644 --- a/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb +++ b/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb @@ -18,11 +18,11 @@ module Rack end def read(name, options = {}) - super(name, options.merge!({ raw: true })) + super(name, options.merge!(raw: true)) end def write(name, value, options = {}) - super(name, value, options.merge!({ raw: true })) + super(name, value, options.merge!(raw: true)) end end end diff --git a/spec/rack_attack_throttle_spec.rb b/spec/rack_attack_throttle_spec.rb index 0361c7c..b32891a 100644 --- a/spec/rack_attack_throttle_spec.rb +++ b/spec/rack_attack_throttle_spec.rb @@ -37,7 +37,7 @@ describe 'Rack::Attack.throttle' do it 'should tag the env' do last_request.env['rack.attack.matched'].must_equal 'ip/sec' last_request.env['rack.attack.match_type'].must_equal :throttle - last_request.env['rack.attack.match_data'].must_equal({ :count => 2, :limit => 1, :period => @period }) + last_request.env['rack.attack.match_data'].must_equal(:count => 2, :limit => 1, :period => @period) last_request.env['rack.attack.match_discriminator'].must_equal('1.2.3.4') end From d5e585680fb04401150197192de0edaa070cb7b2 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Wed, 20 Jun 2018 10:29:13 -0300 Subject: [PATCH 085/129] Fix CircleCI rubocop runs by excluding vendor/ folder rubocop default configuration exclude vendor/ folder, but in order to get the default we need to merge arrays, given that we are also setting Exclude --- .rubocop.yml | 4 ++++ rack-attack.gemspec | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.rubocop.yml b/.rubocop.yml index 1d679e3..84e396f 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,3 +1,7 @@ +inherit_mode: + merge: + - Exclude + AllCops: TargetRubyVersion: 2.2 DisabledByDefault: true diff --git a/rack-attack.gemspec b/rack-attack.gemspec index f63c35c..233e9ea 100644 --- a/rack-attack.gemspec +++ b/rack-attack.gemspec @@ -38,7 +38,7 @@ Gem::Specification.new do |s| s.add_development_dependency 'rack-test' s.add_development_dependency 'rake' s.add_development_dependency 'redis-activesupport' - s.add_development_dependency "rubocop", "0.55.0" + s.add_development_dependency "rubocop", "0.57.2" s.add_development_dependency "timecop" # Need to explicitly depend on guard because guard-minitest doesn't declare From 326ab8e098d453ff90157c002d975f6cd8578ad8 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Wed, 20 Jun 2018 14:25:49 -0300 Subject: [PATCH 086/129] Temporary fix for rubocop during CI builds --- .rubocop.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.rubocop.yml b/.rubocop.yml index 84e396f..cc0e049 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -7,6 +7,12 @@ AllCops: DisabledByDefault: true Exclude: - "examples/instrumentation.rb" + # Remove the following line once we are able to make bundler install gems to /vendor/bundle instead + # of /gemfiles/vendor/bundle during TravisCI builds. The reason that happens for now is because + # bundler 1.x only installs relative to the Gemfile (which during CI builds is always one inside gemfiles/ folder) + # instead of the CWD. Bundler 2.x will add support to install relative to CWD + # (see https://github.com/bundler/bundler/pull/5803). + - "gemfiles/vendor/**/*" Bundler: Enabled: true From e83080458635c5ad3b0ff10b72be2e5328fdaa7f Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Wed, 20 Jun 2018 17:47:21 -0300 Subject: [PATCH 087/129] Acceptance test pooled RedisCacheStore as a backend store --- .../stores/redis_cache_store_pooled_spec.rb | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 spec/acceptance/stores/redis_cache_store_pooled_spec.rb diff --git a/spec/acceptance/stores/redis_cache_store_pooled_spec.rb b/spec/acceptance/stores/redis_cache_store_pooled_spec.rb new file mode 100644 index 0000000..cd54b30 --- /dev/null +++ b/spec/acceptance/stores/redis_cache_store_pooled_spec.rb @@ -0,0 +1,40 @@ +require_relative "../../spec_helper" +require_relative "../../support/cache_store_helper" + +require "timecop" + +if ActiveSupport.version >= Gem::Version.new("5.2.0") + describe "RedisCacheStore (pooled) as a cache backend" do + before do + Rack::Attack.cache.store = ActiveSupport::Cache::RedisCacheStore.new(pool_size: 2) + end + + after do + Rack::Attack.cache.store.clear + end + + it_works_for_cache_backed_features + + it "doesn't leak keys" do + Rack::Attack.throttle("by ip", limit: 1, period: 1) do |request| + request.ip + end + + key = nil + + # Freeze time during these statement to be sure that the key used by rack attack is the same + # we pre-calculate in local variable `key` + Timecop.freeze do + key = "rack::attack:#{Time.now.to_i}:by ip:1.2.3.4" + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + end + + assert Rack::Attack.cache.store.fetch(key) + + sleep 2.1 + + assert_nil Rack::Attack.cache.store.fetch(key) + end + end +end From 3caee5c3ca6e91ff3670f7a62f858229a3c36772 Mon Sep 17 00:00:00 2001 From: Alexey Vasiliev Date: Wed, 20 Jun 2018 22:11:46 +0300 Subject: [PATCH 088/129] Fix usage of RedisCacheStore for rails 5.2.0 --- lib/rack/attack/store_proxy/redis_cache_store_proxy.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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 8bb3f46..dcf085f 100644 --- a/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb +++ b/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb @@ -10,11 +10,13 @@ module Rack 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] + redis.with do |r| + count = r.pipelined do + r.incrby(name, amount) + r.expire(name, options[:expires_in]) if options[:expires_in] + end + count.first end - count.first end def read(name, options = {}) From 3af7394b6ac6ae97e2cd63eda18daa92a29d0e6b Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Wed, 20 Jun 2018 18:32:55 -0300 Subject: [PATCH 089/129] Refactor RedisCacheStoreProxy to unlearn everything about redis client details to make it less prone to bugs in the future Let RedisCacheStoreProxy only know and assume things about RedisCacheStore API. Don't let it know anything about the specific redis client behind the scenes, that's the job of RedisCacheStore only, not ours. --- .../store_proxy/redis_cache_store_proxy.rb | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) 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 dcf085f..2b506f3 100644 --- a/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb +++ b/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb @@ -9,13 +9,16 @@ module Rack end def increment(name, amount, options = {}) - # Redis doesn't check expiration on the INCRBY command. See https://redis.io/commands/expire - redis.with do |r| - count = r.pipelined do - r.incrby(name, amount) - r.expire(name, options[:expires_in]) if options[:expires_in] - end - count.first + # RedisCacheStore#increment ignores options[:expires_in]. + # + # So in order to workaround this we use RedisCacheStore#write (which sets expiration) to initialize + # the counter. After that we continue using the original RedisCacheStore#increment. + if options[:expires_in] && !read(name) + write(name, 1, options) + + 1 + else + super end end From ca2e75293780d150f9bd6ab79a6c381b2640f20d Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Wed, 20 Jun 2018 19:14:02 -0300 Subject: [PATCH 090/129] Honor amount argument instead of hard coding counter --- lib/rack/attack/store_proxy/redis_cache_store_proxy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 2b506f3..f14e678 100644 --- a/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb +++ b/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb @@ -14,7 +14,7 @@ module Rack # So in order to workaround this we use RedisCacheStore#write (which sets expiration) to initialize # the counter. After that we continue using the original RedisCacheStore#increment. if options[:expires_in] && !read(name) - write(name, 1, options) + write(name, amount, options) 1 else From 2c1cbc323ee8dfca4d43e02d43af023ae2b0da3f Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Wed, 20 Jun 2018 19:14:37 -0300 Subject: [PATCH 091/129] Default increment amount to 1 as RedisCacheStore --- lib/rack/attack/store_proxy/redis_cache_store_proxy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f14e678..810994c 100644 --- a/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb +++ b/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb @@ -8,7 +8,7 @@ module Rack defined?(::ActiveSupport::Cache::RedisCacheStore) && store.is_a?(::ActiveSupport::Cache::RedisCacheStore) end - def increment(name, amount, options = {}) + def increment(name, amount = 1, options = {}) # RedisCacheStore#increment ignores options[:expires_in]. # # So in order to workaround this we use RedisCacheStore#write (which sets expiration) to initialize From 196868130a043629d948ecfc1c7bbc177fd4ef5a Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Wed, 20 Jun 2018 19:49:15 -0300 Subject: [PATCH 092/129] Bump version to v5.3.1 --- CHANGELOG.md | 9 ++++++++- lib/rack/attack/version.rb | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55e9ae1..16092a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. _No significant changes since last release yet. Stay tuned_ :radio: +## [5.3.1] - 2018-06-20 + +### Fixed + +- Make [`ActiveSupport::Cache::RedisCacheStore`](http://api.rubyonrails.org/classes/ActiveSupport/Cache/RedisCacheStore.html) also work as excepted when initialized with pool options (e.g. `pool_size`) + ## [5.3.0] - 2018-06-19 ### Added @@ -111,7 +117,8 @@ _No significant changes since last release yet. Stay tuned_ :radio: - Remove unused variable - Extract mandatory options to constants -[Unreleased]: https://github.com/kickstarter/rack-attack/compare/v5.3.0...HEAD/ +[Unreleased]: https://github.com/kickstarter/rack-attack/compare/v5.3.1...HEAD/ +[5.3.1]: https://github.com/kickstarter/rack-attack/compare/v5.3.0...v5.3.1/ [5.3.0]: https://github.com/kickstarter/rack-attack/compare/v5.2.0...v5.3.0/ [5.2.0]: https://github.com/kickstarter/rack-attack/compare/v5.1.0...v5.2.0/ [5.1.0]: https://github.com/kickstarter/rack-attack/compare/v5.0.1...v5.1.0/ diff --git a/lib/rack/attack/version.rb b/lib/rack/attack/version.rb index c836469..8b695ff 100644 --- a/lib/rack/attack/version.rb +++ b/lib/rack/attack/version.rb @@ -1,5 +1,5 @@ module Rack class Attack - VERSION = '5.3.0' + VERSION = '5.3.1' end end From d8b88cfb84d9633a5f7a200996d1634f618b7e2b Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Thu, 21 Jun 2018 11:24:54 -0300 Subject: [PATCH 093/129] Honor amount argument instead of hard coding counter (part 2) See commit ca2e75293780d150f9bd6ab79a6c381b2640f20d --- lib/rack/attack/store_proxy/redis_cache_store_proxy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 810994c..f99de36 100644 --- a/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb +++ b/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb @@ -16,7 +16,7 @@ module Rack if options[:expires_in] && !read(name) write(name, amount, options) - 1 + amount else super end From 1504a1f00cdd66efe60956896e493be8574cd318 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Thu, 21 Jun 2018 11:39:35 -0300 Subject: [PATCH 094/129] Add useful metadata links to rubygems.org via gemspec --- rack-attack.gemspec | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rack-attack.gemspec b/rack-attack.gemspec index 233e9ea..9a42e4c 100644 --- a/rack-attack.gemspec +++ b/rack-attack.gemspec @@ -21,6 +21,12 @@ Gem::Specification.new do |s| s.summary = %q{Block & throttle abusive requests} s.test_files = Dir.glob("spec/**/*") + s.metadata = { + "bug_tracker_uri" => "https://github.com/kickstarter/rack-attack/issues", + "changelog_uri" => "https://github.com/kickstarter/rack-attack/blob/master/CHANGELOG.md", + "source_code_uri" => "https://github.com/kickstarter/rack-attack" + } + s.required_ruby_version = '>= 2.2' s.add_dependency 'rack' From 08861f8d17d43673d32bc388f7625bfa6095a4ba Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Thu, 21 Jun 2018 14:33:24 -0300 Subject: [PATCH 095/129] Attempt to improve code legibility/clarity/semantics (#357) * attempt to improve semantics for legibility * Attempt to improve legibility by simplifying * Make it more clear that we're calling procs/blocks here * Enable rubocop Style/BlockDelimiters cop * Prefer 'request' over 'req' abbreviation for legibility/clarity * Instances of Track named 'track' not 'tracker' --- .rubocop.yml | 3 ++ lib/rack/attack.rb | 44 +++++++++++----------- lib/rack/attack/check.rb | 14 +++---- lib/rack/attack/throttle.rb | 20 +++++----- lib/rack/attack/track.rb | 6 +-- spec/integration/offline_spec.rb | 12 +++--- spec/integration/rack_attack_cache_spec.rb | 4 +- spec/rack_attack_track_spec.rb | 8 ++-- spec/spec_helper.rb | 4 +- 9 files changed, 58 insertions(+), 57 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index cc0e049..14b26e5 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -40,6 +40,9 @@ Security: Lint: Enabled: true +Style/BlockDelimiters: + Enabled: true + Style/BracesAroundHashParameters: Enabled: true diff --git a/lib/rack/attack.rb b/lib/rack/attack.rb index 4a96778..30a9ccf 100644 --- a/lib/rack/attack.rb +++ b/lib/rack/attack.rb @@ -81,40 +81,40 @@ class Rack::Attack blocklists end - def safelisted?(req) - ip_safelists.any? { |safelist| safelist.match?(req) } || - safelists.any? { |_name, safelist| safelist.match?(req) } + def safelisted?(request) + ip_safelists.any? { |safelist| safelist.matched_by?(request) } || + safelists.any? { |_name, safelist| safelist.matched_by?(request) } end - def whitelisted?(req) + def whitelisted?(request) warn "[DEPRECATION] 'Rack::Attack.whitelisted?' is deprecated. Please use 'safelisted?' instead." - safelisted?(req) + safelisted?(request) end - def blocklisted?(req) - ip_blocklists.any? { |blocklist| blocklist.match?(req) } || - blocklists.any? { |_name, blocklist| blocklist.match?(req) } + def blocklisted?(request) + ip_blocklists.any? { |blocklist| blocklist.matched_by?(request) } || + blocklists.any? { |_name, blocklist| blocklist.matched_by?(request) } end - def blacklisted?(req) + def blacklisted?(request) warn "[DEPRECATION] 'Rack::Attack.blacklisted?' is deprecated. Please use 'blocklisted?' instead." - blocklisted?(req) + blocklisted?(request) end - def throttled?(req) + def throttled?(request) throttles.any? do |_name, throttle| - throttle[req] + throttle.matched_by?(request) end end - def tracked?(req) - tracks.each_value do |tracker| - tracker[req] + def tracked?(request) + tracks.each_value do |track| + track.matched_by?(request) end end - def instrument(req) - notifier.instrument('rack.attack', req) if notifier + def instrument(request) + notifier.instrument('rack.attack', request) if notifier end def cache @@ -167,16 +167,16 @@ class Rack::Attack def call(env) env['PATH_INFO'] = PathNormalizer.normalize_path(env['PATH_INFO']) - req = Rack::Attack::Request.new(env) + request = Rack::Attack::Request.new(env) - if safelisted?(req) + if safelisted?(request) @app.call(env) - elsif blocklisted?(req) + elsif blocklisted?(request) self.class.blocklisted_response.call(env) - elsif throttled?(req) + elsif throttled?(request) self.class.throttled_response.call(env) else - tracked?(req) + tracked?(request) @app.call(env) end end diff --git a/lib/rack/attack/check.rb b/lib/rack/attack/check.rb index 49857cf..b24a873 100644 --- a/lib/rack/attack/check.rb +++ b/lib/rack/attack/check.rb @@ -7,17 +7,15 @@ module Rack @type = options.fetch(:type, nil) end - def [](req) - block[req].tap { |match| + def matched_by?(request) + block.call(request).tap do |match| if match - req.env["rack.attack.matched"] = name - req.env["rack.attack.match_type"] = type - Rack::Attack.instrument(req) + request.env["rack.attack.matched"] = name + request.env["rack.attack.match_type"] = type + Rack::Attack.instrument(request) end - } + end end - - alias_method :match?, :[] end end end diff --git a/lib/rack/attack/throttle.rb b/lib/rack/attack/throttle.rb index 19a6519..b89e114 100644 --- a/lib/rack/attack/throttle.rb +++ b/lib/rack/attack/throttle.rb @@ -18,12 +18,12 @@ module Rack Rack::Attack.cache end - def [](req) - discriminator = block[req] + def matched_by?(request) + discriminator = block.call(request) return false unless discriminator - current_period = period.respond_to?(:call) ? period.call(req) : period - current_limit = limit.respond_to?(:call) ? limit.call(req) : limit + current_period = period.respond_to?(:call) ? period.call(request) : period + current_limit = limit.respond_to?(:call) ? limit.call(request) : limit key = "#{name}:#{discriminator}" count = cache.count(key, current_period) @@ -32,15 +32,15 @@ module Rack :period => current_period, :limit => current_limit } - (req.env['rack.attack.throttle_data'] ||= {})[name] = data + (request.env['rack.attack.throttle_data'] ||= {})[name] = data (count > current_limit).tap do |throttled| if throttled - req.env['rack.attack.matched'] = name - req.env['rack.attack.match_discriminator'] = discriminator - req.env['rack.attack.match_type'] = type - req.env['rack.attack.match_data'] = data - Rack::Attack.instrument(req) + request.env['rack.attack.matched'] = name + request.env['rack.attack.match_discriminator'] = discriminator + request.env['rack.attack.match_type'] = type + request.env['rack.attack.match_data'] = data + Rack::Attack.instrument(request) end end end diff --git a/lib/rack/attack/track.rb b/lib/rack/attack/track.rb index e0039e3..123bfa4 100644 --- a/lib/rack/attack/track.rb +++ b/lib/rack/attack/track.rb @@ -1,8 +1,6 @@ module Rack class Attack class Track - extend Forwardable - attr_reader :filter def initialize(name, options = {}, block) @@ -15,7 +13,9 @@ module Rack end end - def_delegator :@filter, :[] + def matched_by?(request) + filter.matched_by?(request) + end end end end diff --git a/spec/integration/offline_spec.rb b/spec/integration/offline_spec.rb index f47fb31..ebaf16a 100644 --- a/spec/integration/offline_spec.rb +++ b/spec/integration/offline_spec.rb @@ -20,24 +20,24 @@ end describe 'when Redis is offline' do include OfflineExamples - before { + before do @cache = Rack::Attack::Cache.new # Use presumably unused port for Redis client @cache.store = ActiveSupport::Cache::RedisStore.new(:host => '127.0.0.1', :port => 3333) - } + end end describe 'when Memcached is offline' do include OfflineExamples - before { + before do Dalli.logger.level = Logger::FATAL @cache = Rack::Attack::Cache.new @cache.store = Dalli::Client.new('127.0.0.1:22122') - } + end - after { + after do Dalli.logger.level = Logger::INFO - } + end end diff --git a/spec/integration/rack_attack_cache_spec.rb b/spec/integration/rack_attack_cache_spec.rb index 2e9f9e5..98f5568 100644 --- a/spec/integration/rack_attack_cache_spec.rb +++ b/spec/integration/rack_attack_cache_spec.rb @@ -37,13 +37,13 @@ describe Rack::Attack::Cache do store = Rack::Attack::StoreProxy.build(store) describe "with #{store.class}" do - before { + before do @cache = Rack::Attack::Cache.new @key = "rack::attack:cache-test-key" @expires_in = 1 @cache.store = store delete(@key) - } + end after { delete(@key) } diff --git a/spec/rack_attack_track_spec.rb b/spec/rack_attack_track_spec.rb index 24a408f..352561d 100644 --- a/spec/rack_attack_track_spec.rb +++ b/spec/rack_attack_track_spec.rb @@ -47,15 +47,15 @@ describe 'Rack::Attack.track' do describe "without limit and period options" do it "should assign the track filter to a Check instance" do - tracker = Rack::Attack.track("homepage") { |req| req.path == "/" } - tracker.filter.class.must_equal Rack::Attack::Check + track = Rack::Attack.track("homepage") { |req| req.path == "/" } + track.filter.class.must_equal Rack::Attack::Check end end describe "with limit and period options" do it "should assign the track filter to a Throttle instance" do - tracker = Rack::Attack.track("homepage", :limit => 10, :period => 10) { |req| req.path == "/" } - tracker.filter.class.must_equal Rack::Attack::Throttle + track = Rack::Attack.track("homepage", :limit => 10, :period => 10) { |req| req.path == "/" } + track.filter.class.must_equal Rack::Attack::Throttle end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3f19b8f..6b6f661 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -36,14 +36,14 @@ class MiniTest::Spec end def app - Rack::Builder.new { + Rack::Builder.new do # Use Rack::Lint to test that rack-attack is complying with the rack spec use Rack::Lint use Rack::Attack use Rack::Lint run lambda { |_env| [200, {}, ['Hello World']] } - }.to_app + end.to_app end def self.it_allows_ok_requests From 75ed952d430de616c94aca9c104077424608bcf7 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Thu, 21 Jun 2018 14:47:29 -0300 Subject: [PATCH 096/129] Remove guard-minitest as a dev dependency --- Guardfile | 9 --------- rack-attack.gemspec | 7 ------- 2 files changed, 16 deletions(-) delete mode 100644 Guardfile diff --git a/Guardfile b/Guardfile deleted file mode 100644 index 06b986b..0000000 --- a/Guardfile +++ /dev/null @@ -1,9 +0,0 @@ -# A sample Guardfile -# More info at https://github.com/guard/guard#readme - -guard :minitest do - # with Minitest::Spec - watch(%r{^spec/(.*)_spec\.rb$}) - watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } - watch(%r{^spec/spec_helper\.rb$}) { 'spec' } -end diff --git a/rack-attack.gemspec b/rack-attack.gemspec index 233e9ea..45a8b0f 100644 --- a/rack-attack.gemspec +++ b/rack-attack.gemspec @@ -30,7 +30,6 @@ Gem::Specification.new do |s| s.add_development_dependency 'appraisal' s.add_development_dependency 'connection_pool' s.add_development_dependency 'dalli' - s.add_development_dependency 'guard-minitest' s.add_development_dependency 'memcache-client' s.add_development_dependency 'minitest' s.add_development_dependency "minitest-stub-const" @@ -41,12 +40,6 @@ Gem::Specification.new do |s| s.add_development_dependency "rubocop", "0.57.2" s.add_development_dependency "timecop" - # Need to explicitly depend on guard because guard-minitest doesn't declare - # the dependency intentionally - # - # See https://github.com/guard/guard-minitest/pull/131 - s.add_development_dependency 'guard' - # byebug only works with MRI if RUBY_ENGINE == "ruby" s.add_development_dependency 'byebug' From 0c26efc187e60258c4f6ea57eda8ce49ff0644c9 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Thu, 21 Jun 2018 14:49:45 -0300 Subject: [PATCH 097/129] Remove pry as a dev dependency. Using byebug. --- rack-attack.gemspec | 1 - spec/spec_helper.rb | 6 ------ 2 files changed, 7 deletions(-) diff --git a/rack-attack.gemspec b/rack-attack.gemspec index 45a8b0f..eb9336a 100644 --- a/rack-attack.gemspec +++ b/rack-attack.gemspec @@ -33,7 +33,6 @@ Gem::Specification.new do |s| s.add_development_dependency 'memcache-client' s.add_development_dependency 'minitest' s.add_development_dependency "minitest-stub-const" - s.add_development_dependency 'pry' s.add_development_dependency 'rack-test' s.add_development_dependency 'rake' s.add_development_dependency 'redis-activesupport' diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6b6f661..7d2c851 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -9,12 +9,6 @@ require 'action_dispatch' require "rack/attack" -begin - require 'pry' -rescue LoadError - # nothing to do here -end - if RUBY_ENGINE == "ruby" require "byebug" end From aca0fb6f1d700634df3ca98dc02209da8ce7311a Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Thu, 21 Jun 2018 14:59:27 -0300 Subject: [PATCH 098/129] Clarify in gemspec existence of two group of semantically distict dev deps --- rack-attack.gemspec | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/rack-attack.gemspec b/rack-attack.gemspec index eb9336a..a021f10 100644 --- a/rack-attack.gemspec +++ b/rack-attack.gemspec @@ -25,17 +25,11 @@ Gem::Specification.new do |s| s.add_dependency 'rack' - s.add_development_dependency 'actionpack', '>= 3.0.0' - s.add_development_dependency 'activesupport', '>= 3.0.0' s.add_development_dependency 'appraisal' - s.add_development_dependency 'connection_pool' - s.add_development_dependency 'dalli' - s.add_development_dependency 'memcache-client' s.add_development_dependency 'minitest' s.add_development_dependency "minitest-stub-const" s.add_development_dependency 'rack-test' s.add_development_dependency 'rake' - s.add_development_dependency 'redis-activesupport' s.add_development_dependency "rubocop", "0.57.2" s.add_development_dependency "timecop" @@ -43,4 +37,13 @@ Gem::Specification.new do |s| if RUBY_ENGINE == "ruby" s.add_development_dependency 'byebug' end + + # The following are potential runtime dependencies users may have, + # which rack-attack uses only for testing compatibility in test suite. + s.add_development_dependency 'actionpack', '>= 3.0.0' + s.add_development_dependency 'activesupport', '>= 3.0.0' + s.add_development_dependency 'connection_pool' + s.add_development_dependency 'dalli' + s.add_development_dependency 'memcache-client' + s.add_development_dependency 'redis-activesupport' end From 7a4b206dfdfc69d408f93573038e3618a653aee1 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Thu, 21 Jun 2018 15:50:49 -0300 Subject: [PATCH 099/129] Let everyone developing rack-attack use compatible/simiar dev deps versions --- rack-attack.gemspec | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/rack-attack.gemspec b/rack-attack.gemspec index a021f10..652c0d2 100644 --- a/rack-attack.gemspec +++ b/rack-attack.gemspec @@ -25,17 +25,17 @@ Gem::Specification.new do |s| s.add_dependency 'rack' - s.add_development_dependency 'appraisal' - s.add_development_dependency 'minitest' - s.add_development_dependency "minitest-stub-const" - s.add_development_dependency 'rack-test' - s.add_development_dependency 'rake' + s.add_development_dependency 'appraisal', '~> 2.2' + s.add_development_dependency 'minitest', "~> 5.11" + s.add_development_dependency "minitest-stub-const", "~> 0.6" + s.add_development_dependency 'rack-test', "~> 1.0" + s.add_development_dependency 'rake', "~> 12.3" s.add_development_dependency "rubocop", "0.57.2" - s.add_development_dependency "timecop" + s.add_development_dependency "timecop", "~> 0.9.1" # byebug only works with MRI if RUBY_ENGINE == "ruby" - s.add_development_dependency 'byebug' + s.add_development_dependency 'byebug', '~> 10.0' end # The following are potential runtime dependencies users may have, From 9257e40b9690e0fc9ded742b859476b06a34c787 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Thu, 21 Jun 2018 17:40:39 -0300 Subject: [PATCH 100/129] Make rack-test compatible again with actionpack for some appraisals runs --- Appraisals | 8 ++++++++ gemfiles/rack_1_6.gemfile | 1 + gemfiles/rails_4_2.gemfile | 1 + 3 files changed, 10 insertions(+) diff --git a/Appraisals b/Appraisals index f0495db..2ccb781 100644 --- a/Appraisals +++ b/Appraisals @@ -4,6 +4,10 @@ end appraise "rack_1_6" do gem "rack", "~> 1.6.9" + + # Override rack-test version constraint by making it more loose + # so it's compatible with actionpack 4.2.x + gem "rack-test", ">= 0.6" end appraise 'rails_5-2' do @@ -19,6 +23,10 @@ end appraise 'rails_4-2' do gem 'actionpack', '~> 4.2.0' gem 'activesupport', '~> 4.2.0' + + # Override rack-test version constraint by making it more loose + # so it's compatible with actionpack 4.2.x + gem "rack-test", ">= 0.6" end appraise 'dalli2' do diff --git a/gemfiles/rack_1_6.gemfile b/gemfiles/rack_1_6.gemfile index 18b3acf..8b636ed 100644 --- a/gemfiles/rack_1_6.gemfile +++ b/gemfiles/rack_1_6.gemfile @@ -3,5 +3,6 @@ source "https://rubygems.org" gem "rack", "~> 1.6.9" +gem "rack-test", ">= 0.6" gemspec path: "../" diff --git a/gemfiles/rails_4_2.gemfile b/gemfiles/rails_4_2.gemfile index 3937ece..eb09083 100644 --- a/gemfiles/rails_4_2.gemfile +++ b/gemfiles/rails_4_2.gemfile @@ -4,5 +4,6 @@ source "https://rubygems.org" gem "actionpack", "~> 4.2.0" gem "activesupport", "~> 4.2.0" +gem "rack-test", ">= 0.6" gemspec path: "../" From 3f590e4774772999d66170b6c0991ef070b8893f Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Fri, 22 Jun 2018 14:51:38 -0300 Subject: [PATCH 101/129] Acceptance tests already cover integration with RedisCacheStore and MemCacheStore --- spec/integration/rack_attack_cache_spec.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/spec/integration/rack_attack_cache_spec.rb b/spec/integration/rack_attack_cache_spec.rb index 98f5568..6f7b55c 100644 --- a/spec/integration/rack_attack_cache_spec.rb +++ b/spec/integration/rack_attack_cache_spec.rb @@ -16,23 +16,18 @@ describe Rack::Attack::Cache do end 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 = [ ActiveSupport::Cache::MemoryStore.new, ActiveSupport::Cache::DalliStore.new("127.0.0.1"), ActiveSupport::Cache::RedisStore.new("127.0.0.1"), - ActiveSupport::Cache::MemCacheStore.new("127.0.0.1"), Dalli::Client.new, ConnectionPool.new { Dalli::Client.new }, 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) From 92f3b7fbd0221031da42f93accb6b45e63d9a337 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Fri, 22 Jun 2018 10:52:23 -0300 Subject: [PATCH 102/129] Acceptance test ActiveSupport::Cache::RedisStore (redis-activesupport) as cache store backend --- .../stores/active_support_redis_store_spec.rb | 39 +++++++++++++++++++ spec/integration/rack_attack_cache_spec.rb | 2 - 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 spec/acceptance/stores/active_support_redis_store_spec.rb diff --git a/spec/acceptance/stores/active_support_redis_store_spec.rb b/spec/acceptance/stores/active_support_redis_store_spec.rb new file mode 100644 index 0000000..0b446e4 --- /dev/null +++ b/spec/acceptance/stores/active_support_redis_store_spec.rb @@ -0,0 +1,39 @@ +require_relative "../../spec_helper" +require_relative "../../support/cache_store_helper" + +require "redis-activesupport" +require "timecop" + +describe "ActiveSupport::Cache::RedisStore as a cache backend" do + before do + Rack::Attack.cache.store = ActiveSupport::Cache::RedisStore.new + end + + after do + Rack::Attack.cache.store.flushdb + end + + it_works_for_cache_backed_features + + it "doesn't leak keys" do + Rack::Attack.throttle("by ip", limit: 1, period: 1) do |request| + request.ip + end + + key = nil + + # Freeze time during these statement to be sure that the key used by rack attack is the same + # we pre-calculate in local variable `key` + Timecop.freeze do + key = "rack::attack:#{Time.now.to_i}:by ip:1.2.3.4" + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + end + + assert Rack::Attack.cache.store.read(key) + + sleep 2.1 + + assert_nil Rack::Attack.cache.store.read(key) + end +end diff --git a/spec/integration/rack_attack_cache_spec.rb b/spec/integration/rack_attack_cache_spec.rb index 6f7b55c..9a13720 100644 --- a/spec/integration/rack_attack_cache_spec.rb +++ b/spec/integration/rack_attack_cache_spec.rb @@ -16,13 +16,11 @@ describe Rack::Attack::Cache do end require 'active_support/cache/dalli_store' - require 'active_support/cache/redis_store' require 'connection_pool' cache_stores = [ ActiveSupport::Cache::MemoryStore.new, ActiveSupport::Cache::DalliStore.new("127.0.0.1"), - ActiveSupport::Cache::RedisStore.new("127.0.0.1"), Dalli::Client.new, ConnectionPool.new { Dalli::Client.new }, Redis::Store.new From 93b1bf4c861c2df5dfd06c5b0349c1f5c70f2cc9 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Fri, 22 Jun 2018 11:59:55 -0300 Subject: [PATCH 103/129] Acceptance test Redis::Store (redis-store) as cache store backend --- spec/acceptance/stores/redis_store_spec.rb | 39 ++++++++++++++++++++++ spec/integration/rack_attack_cache_spec.rb | 3 +- 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 spec/acceptance/stores/redis_store_spec.rb diff --git a/spec/acceptance/stores/redis_store_spec.rb b/spec/acceptance/stores/redis_store_spec.rb new file mode 100644 index 0000000..17e5a0a --- /dev/null +++ b/spec/acceptance/stores/redis_store_spec.rb @@ -0,0 +1,39 @@ +require_relative "../../spec_helper" +require_relative "../../support/cache_store_helper" + +require "redis-store" +require "timecop" + +describe "ActiveSupport::Cache::RedisStore as a cache backend" do + before do + Rack::Attack.cache.store = ::Redis::Store.new + end + + after do + Rack::Attack.cache.store.flushdb + end + + it_works_for_cache_backed_features + + it "doesn't leak keys" do + Rack::Attack.throttle("by ip", limit: 1, period: 1) do |request| + request.ip + end + + key = nil + + # Freeze time during these statement to be sure that the key used by rack attack is the same + # we pre-calculate in local variable `key` + Timecop.freeze do + key = "rack::attack:#{Time.now.to_i}:by ip:1.2.3.4" + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + end + + assert Rack::Attack.cache.store.read(key) + + sleep 2.1 + + assert_nil Rack::Attack.cache.store.read(key) + end +end diff --git a/spec/integration/rack_attack_cache_spec.rb b/spec/integration/rack_attack_cache_spec.rb index 9a13720..4e76552 100644 --- a/spec/integration/rack_attack_cache_spec.rb +++ b/spec/integration/rack_attack_cache_spec.rb @@ -22,8 +22,7 @@ describe Rack::Attack::Cache do ActiveSupport::Cache::MemoryStore.new, ActiveSupport::Cache::DalliStore.new("127.0.0.1"), Dalli::Client.new, - ConnectionPool.new { Dalli::Client.new }, - Redis::Store.new + ConnectionPool.new { Dalli::Client.new } ] cache_stores.each do |store| From bca253c674a59f0edea5c027faf78c05cc044c61 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Fri, 22 Jun 2018 12:06:46 -0300 Subject: [PATCH 104/129] Acceptance test ActiveSupport::Cache::DalliStore (via dalli) as cache store backend --- .../stores/active_support_dalli_store_spec.rb | 39 +++++++++++++++++++ spec/integration/rack_attack_cache_spec.rb | 2 - 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 spec/acceptance/stores/active_support_dalli_store_spec.rb diff --git a/spec/acceptance/stores/active_support_dalli_store_spec.rb b/spec/acceptance/stores/active_support_dalli_store_spec.rb new file mode 100644 index 0000000..b3be95f --- /dev/null +++ b/spec/acceptance/stores/active_support_dalli_store_spec.rb @@ -0,0 +1,39 @@ +require_relative "../../spec_helper" +require_relative "../../support/cache_store_helper" + +require "active_support/cache/dalli_store" +require "timecop" + +describe "ActiveSupport::Cache::DalliStore as a cache backend" do + before do + Rack::Attack.cache.store = ActiveSupport::Cache::DalliStore.new + end + + after do + Rack::Attack.cache.store.clear + end + + it_works_for_cache_backed_features + + it "doesn't leak keys" do + Rack::Attack.throttle("by ip", limit: 1, period: 1) do |request| + request.ip + end + + key = nil + + # Freeze time during these statement to be sure that the key used by rack attack is the same + # we pre-calculate in local variable `key` + Timecop.freeze do + key = "rack::attack:#{Time.now.to_i}:by ip:1.2.3.4" + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + end + + assert Rack::Attack.cache.store.fetch(key) + + sleep 2.1 + + assert_nil Rack::Attack.cache.store.fetch(key) + end +end diff --git a/spec/integration/rack_attack_cache_spec.rb b/spec/integration/rack_attack_cache_spec.rb index 4e76552..a349dbf 100644 --- a/spec/integration/rack_attack_cache_spec.rb +++ b/spec/integration/rack_attack_cache_spec.rb @@ -15,12 +15,10 @@ describe Rack::Attack::Cache do sleep(@expires_in * 1.1) # Add 10% to reduce errors end - require 'active_support/cache/dalli_store' require 'connection_pool' cache_stores = [ ActiveSupport::Cache::MemoryStore.new, - ActiveSupport::Cache::DalliStore.new("127.0.0.1"), Dalli::Client.new, ConnectionPool.new { Dalli::Client.new } ] From 831e4e9e977670b6f9978831dda5fe74bb3ea83b Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Fri, 22 Jun 2018 12:14:56 -0300 Subject: [PATCH 105/129] Acceptance test Dalli::Client (via dalli) as cache store backend --- spec/acceptance/stores/dalli_client_spec.rb | 39 +++++++++++++++++++++ spec/integration/rack_attack_cache_spec.rb | 1 - 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 spec/acceptance/stores/dalli_client_spec.rb diff --git a/spec/acceptance/stores/dalli_client_spec.rb b/spec/acceptance/stores/dalli_client_spec.rb new file mode 100644 index 0000000..e01ab3f --- /dev/null +++ b/spec/acceptance/stores/dalli_client_spec.rb @@ -0,0 +1,39 @@ +require_relative "../../spec_helper" +require_relative "../../support/cache_store_helper" + +require "dalli" +require "timecop" + +describe "Dalli::Client as a cache backend" do + before do + Rack::Attack.cache.store = Dalli::Client.new + end + + after do + Rack::Attack.cache.store.flush_all + end + + it_works_for_cache_backed_features + + it "doesn't leak keys" do + Rack::Attack.throttle("by ip", limit: 1, period: 1) do |request| + request.ip + end + + key = nil + + # Freeze time during these statement to be sure that the key used by rack attack is the same + # we pre-calculate in local variable `key` + Timecop.freeze do + key = "rack::attack:#{Time.now.to_i}:by ip:1.2.3.4" + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + end + + assert Rack::Attack.cache.store.fetch(key) + + sleep 2.1 + + assert_nil Rack::Attack.cache.store.fetch(key) + end +end diff --git a/spec/integration/rack_attack_cache_spec.rb b/spec/integration/rack_attack_cache_spec.rb index a349dbf..cbea458 100644 --- a/spec/integration/rack_attack_cache_spec.rb +++ b/spec/integration/rack_attack_cache_spec.rb @@ -19,7 +19,6 @@ describe Rack::Attack::Cache do cache_stores = [ ActiveSupport::Cache::MemoryStore.new, - Dalli::Client.new, ConnectionPool.new { Dalli::Client.new } ] From 1f05ff30d57ba200973713ef503524adf06acc0a Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Fri, 22 Jun 2018 12:22:55 -0300 Subject: [PATCH 106/129] Fix namespace of test file names --- ...che_store_spec.rb => active_support_mem_cache_store_spec.rb} | 2 +- ..._spec.rb => active_support_redis_cache_store_pooled_spec.rb} | 2 +- ...e_store_spec.rb => active_support_redis_cache_store_spec.rb} | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename spec/acceptance/stores/{mem_cache_store_spec.rb => active_support_mem_cache_store_spec.rb} (92%) rename spec/acceptance/stores/{redis_cache_store_pooled_spec.rb => active_support_redis_cache_store_pooled_spec.rb} (92%) rename spec/acceptance/stores/{redis_cache_store_spec.rb => active_support_redis_cache_store_spec.rb} (92%) diff --git a/spec/acceptance/stores/mem_cache_store_spec.rb b/spec/acceptance/stores/active_support_mem_cache_store_spec.rb similarity index 92% rename from spec/acceptance/stores/mem_cache_store_spec.rb rename to spec/acceptance/stores/active_support_mem_cache_store_spec.rb index 6c593f1..1a4e7f7 100644 --- a/spec/acceptance/stores/mem_cache_store_spec.rb +++ b/spec/acceptance/stores/active_support_mem_cache_store_spec.rb @@ -3,7 +3,7 @@ require_relative "../../support/cache_store_helper" require "timecop" -describe "MemCacheStore as a cache backend" do +describe "ActiveSupport::Cache::MemCacheStore as a cache backend" do before do Rack::Attack.cache.store = ActiveSupport::Cache::MemCacheStore.new end diff --git a/spec/acceptance/stores/redis_cache_store_pooled_spec.rb b/spec/acceptance/stores/active_support_redis_cache_store_pooled_spec.rb similarity index 92% rename from spec/acceptance/stores/redis_cache_store_pooled_spec.rb rename to spec/acceptance/stores/active_support_redis_cache_store_pooled_spec.rb index cd54b30..8c006bb 100644 --- a/spec/acceptance/stores/redis_cache_store_pooled_spec.rb +++ b/spec/acceptance/stores/active_support_redis_cache_store_pooled_spec.rb @@ -4,7 +4,7 @@ require_relative "../../support/cache_store_helper" require "timecop" if ActiveSupport.version >= Gem::Version.new("5.2.0") - describe "RedisCacheStore (pooled) as a cache backend" do + describe "ActiveSupport::Cache::RedisCacheStore (pooled) as a cache backend" do before do Rack::Attack.cache.store = ActiveSupport::Cache::RedisCacheStore.new(pool_size: 2) end diff --git a/spec/acceptance/stores/redis_cache_store_spec.rb b/spec/acceptance/stores/active_support_redis_cache_store_spec.rb similarity index 92% rename from spec/acceptance/stores/redis_cache_store_spec.rb rename to spec/acceptance/stores/active_support_redis_cache_store_spec.rb index b819749..495a6a2 100644 --- a/spec/acceptance/stores/redis_cache_store_spec.rb +++ b/spec/acceptance/stores/active_support_redis_cache_store_spec.rb @@ -4,7 +4,7 @@ require_relative "../../support/cache_store_helper" require "timecop" if ActiveSupport.version >= Gem::Version.new("5.2.0") - describe "RedisCacheStore as a cache backend" do + describe "ActiveSupport::Cache::RedisCacheStore as a cache backend" do before do Rack::Attack.cache.store = ActiveSupport::Cache::RedisCacheStore.new end From bcc1f5857e1d57acea6a9cfce35ae44b50bcec6f Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Fri, 22 Jun 2018 14:11:34 -0300 Subject: [PATCH 107/129] Acceptance test ActiveSupport::Cache::MemoryStore (via activesupport) as cache store backend --- .../active_support_memory_store_spec.rb | 38 +++++ spec/integration/rack_attack_cache_spec.rb | 137 +++++++++--------- 2 files changed, 103 insertions(+), 72 deletions(-) create mode 100644 spec/acceptance/stores/active_support_memory_store_spec.rb diff --git a/spec/acceptance/stores/active_support_memory_store_spec.rb b/spec/acceptance/stores/active_support_memory_store_spec.rb new file mode 100644 index 0000000..94de429 --- /dev/null +++ b/spec/acceptance/stores/active_support_memory_store_spec.rb @@ -0,0 +1,38 @@ +require_relative "../../spec_helper" +require_relative "../../support/cache_store_helper" + +require "timecop" + +describe "ActiveSupport::Cache::MemoryStore as a cache backend" do + before do + Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new + end + + after do + Rack::Attack.cache.store.clear + end + + it_works_for_cache_backed_features + + it "doesn't leak keys" do + Rack::Attack.throttle("by ip", limit: 1, period: 1) do |request| + request.ip + end + + key = nil + + # Freeze time during these statement to be sure that the key used by rack attack is the same + # we pre-calculate in local variable `key` + Timecop.freeze do + key = "rack::attack:#{Time.now.to_i}:by ip:1.2.3.4" + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + end + + assert Rack::Attack.cache.store.fetch(key) + + sleep 2.1 + + assert_nil Rack::Attack.cache.store.fetch(key) + end +end diff --git a/spec/integration/rack_attack_cache_spec.rb b/spec/integration/rack_attack_cache_spec.rb index cbea458..25148d8 100644 --- a/spec/integration/rack_attack_cache_spec.rb +++ b/spec/integration/rack_attack_cache_spec.rb @@ -17,96 +17,89 @@ describe Rack::Attack::Cache do require 'connection_pool' - cache_stores = [ - ActiveSupport::Cache::MemoryStore.new, - ConnectionPool.new { Dalli::Client.new } - ] + store = Rack::Attack::StoreProxy.build(ConnectionPool.new { Dalli::Client.new }) - cache_stores.each do |store| - store = Rack::Attack::StoreProxy.build(store) + describe "with #{store.class}" do + before do + @cache = Rack::Attack::Cache.new + @key = "rack::attack:cache-test-key" + @expires_in = 1 + @cache.store = store + delete(@key) + end - describe "with #{store.class}" do - before do - @cache = Rack::Attack::Cache.new - @key = "rack::attack:cache-test-key" - @expires_in = 1 - @cache.store = store - delete(@key) + after { delete(@key) } + + describe "do_count once" do + it "should be 1" do + @cache.send(:do_count, @key, @expires_in).must_equal 1 end + end - after { delete(@key) } - - describe "do_count once" do - it "should be 1" do - @cache.send(:do_count, @key, @expires_in).must_equal 1 - end + describe "do_count twice" do + it "must be 2" do + @cache.send(:do_count, @key, @expires_in) + @cache.send(:do_count, @key, @expires_in).must_equal 2 end + end - describe "do_count twice" do - it "must be 2" do - @cache.send(:do_count, @key, @expires_in) - @cache.send(:do_count, @key, @expires_in).must_equal 2 - end + describe "do_count after expires_in" do + it "must be 1" do + @cache.send(:do_count, @key, @expires_in) + sleep_until_expired + @cache.send(:do_count, @key, @expires_in).must_equal 1 end + end - describe "do_count after expires_in" do - it "must be 1" do - @cache.send(:do_count, @key, @expires_in) - sleep_until_expired - @cache.send(:do_count, @key, @expires_in).must_equal 1 - end + describe "write" do + it "should write a value to the store with prefix" do + @cache.write("cache-test-key", "foobar", 1) + store.read(@key).must_equal "foobar" end + end - describe "write" do - it "should write a value to the store with prefix" do - @cache.write("cache-test-key", "foobar", 1) - store.read(@key).must_equal "foobar" - end + describe "write after expiry" do + it "must not have a value" do + @cache.write("cache-test-key", "foobar", @expires_in) + sleep_until_expired + store.read(@key).must_be :nil? end + end - describe "write after expiry" do - it "must not have a value" do - @cache.write("cache-test-key", "foobar", @expires_in) - sleep_until_expired - store.read(@key).must_be :nil? - end + describe "read" do + it "must read the value with a prefix" do + store.write(@key, "foobar", :expires_in => @expires_in) + @cache.read("cache-test-key").must_equal "foobar" end + end - describe "read" do - it "must read the value with a prefix" do - store.write(@key, "foobar", :expires_in => @expires_in) - @cache.read("cache-test-key").must_equal "foobar" - end + describe "delete" do + it "must delete the value" do + store.write(@key, "foobar", :expires_in => @expires_in) + @cache.read('cache-test-key').must_equal "foobar" + store.delete(@key) + assert_nil @cache.read('cache-test-key') end + end - describe "delete" do - it "must delete the value" do - store.write(@key, "foobar", :expires_in => @expires_in) - @cache.read('cache-test-key').must_equal "foobar" - store.delete(@key) - assert_nil @cache.read('cache-test-key') - end + describe "cache#delete" do + it "must delete the value" do + @cache.write("cache-test-key", "foobar", 1) + store.read(@key).must_equal "foobar" + @cache.delete('cache-test-key') + store.read(@key).must_be :nil? end + end - describe "cache#delete" do - it "must delete the value" do - @cache.write("cache-test-key", "foobar", 1) - store.read(@key).must_equal "foobar" - @cache.delete('cache-test-key') - store.read(@key).must_be :nil? - end - end - - describe "reset_count" do - it "must delete the value" do - period = 1.minute - unprefixed_key = 'cache-test-key' - @cache.count(unprefixed_key, period) - period_key, _ = @cache.send(:key_and_expiry, 'cache-test-key', period) - store.read(period_key).to_i.must_equal 1 - @cache.reset_count(unprefixed_key, period) - assert_nil store.read(period_key) - end + describe "reset_count" do + it "must delete the value" do + period = 1.minute + unprefixed_key = 'cache-test-key' + @cache.count(unprefixed_key, period) + period_key, _ = @cache.send(:key_and_expiry, 'cache-test-key', period) + store.read(period_key).to_i.must_equal 1 + @cache.reset_count(unprefixed_key, period) + assert_nil store.read(period_key) end end end From 9cc49b4760ae1aeb3cff4b1502412fec6d24a051 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Fri, 22 Jun 2018 14:41:48 -0300 Subject: [PATCH 108/129] Acceptance test ConnectionPool with Dalli::Client (via connection_pool and dalli) as cache store backend --- .../connection_pool_dalli_client_spec.rb | 40 +++++++ spec/integration/rack_attack_cache_spec.rb | 106 ------------------ 2 files changed, 40 insertions(+), 106 deletions(-) create mode 100644 spec/acceptance/stores/connection_pool_dalli_client_spec.rb delete mode 100644 spec/integration/rack_attack_cache_spec.rb diff --git a/spec/acceptance/stores/connection_pool_dalli_client_spec.rb b/spec/acceptance/stores/connection_pool_dalli_client_spec.rb new file mode 100644 index 0000000..a852d03 --- /dev/null +++ b/spec/acceptance/stores/connection_pool_dalli_client_spec.rb @@ -0,0 +1,40 @@ +require_relative "../../spec_helper" +require_relative "../../support/cache_store_helper" + +require "connection_pool" +require "dalli" +require "timecop" + +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 + + it "doesn't leak keys" do + Rack::Attack.throttle("by ip", limit: 1, period: 1) do |request| + request.ip + end + + key = nil + + # Freeze time during these statement to be sure that the key used by rack attack is the same + # we pre-calculate in local variable `key` + Timecop.freeze do + key = "rack::attack:#{Time.now.to_i}:by ip:1.2.3.4" + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + end + + assert(Rack::Attack.cache.store.with { |client| client.fetch(key) }) + + sleep 2.1 + + assert_nil(Rack::Attack.cache.store.with { |client| client.fetch(key) }) + end +end diff --git a/spec/integration/rack_attack_cache_spec.rb b/spec/integration/rack_attack_cache_spec.rb deleted file mode 100644 index 25148d8..0000000 --- a/spec/integration/rack_attack_cache_spec.rb +++ /dev/null @@ -1,106 +0,0 @@ -require_relative '../spec_helper' - -describe Rack::Attack::Cache do - # A convenience method for deleting a key from cache. - # Slightly different than @cache.delete, which adds a prefix. - def delete(key) - if @cache.store.respond_to?(:delete) - @cache.store.delete(key) - else - @cache.store.del(key) - end - end - - def sleep_until_expired - sleep(@expires_in * 1.1) # Add 10% to reduce errors - end - - require 'connection_pool' - - store = Rack::Attack::StoreProxy.build(ConnectionPool.new { Dalli::Client.new }) - - describe "with #{store.class}" do - before do - @cache = Rack::Attack::Cache.new - @key = "rack::attack:cache-test-key" - @expires_in = 1 - @cache.store = store - delete(@key) - end - - after { delete(@key) } - - describe "do_count once" do - it "should be 1" do - @cache.send(:do_count, @key, @expires_in).must_equal 1 - end - end - - describe "do_count twice" do - it "must be 2" do - @cache.send(:do_count, @key, @expires_in) - @cache.send(:do_count, @key, @expires_in).must_equal 2 - end - end - - describe "do_count after expires_in" do - it "must be 1" do - @cache.send(:do_count, @key, @expires_in) - sleep_until_expired - @cache.send(:do_count, @key, @expires_in).must_equal 1 - end - end - - describe "write" do - it "should write a value to the store with prefix" do - @cache.write("cache-test-key", "foobar", 1) - store.read(@key).must_equal "foobar" - end - end - - describe "write after expiry" do - it "must not have a value" do - @cache.write("cache-test-key", "foobar", @expires_in) - sleep_until_expired - store.read(@key).must_be :nil? - end - end - - describe "read" do - it "must read the value with a prefix" do - store.write(@key, "foobar", :expires_in => @expires_in) - @cache.read("cache-test-key").must_equal "foobar" - end - end - - describe "delete" do - it "must delete the value" do - store.write(@key, "foobar", :expires_in => @expires_in) - @cache.read('cache-test-key').must_equal "foobar" - store.delete(@key) - assert_nil @cache.read('cache-test-key') - end - end - - describe "cache#delete" do - it "must delete the value" do - @cache.write("cache-test-key", "foobar", 1) - store.read(@key).must_equal "foobar" - @cache.delete('cache-test-key') - store.read(@key).must_be :nil? - end - end - - describe "reset_count" do - it "must delete the value" do - period = 1.minute - unprefixed_key = 'cache-test-key' - @cache.count(unprefixed_key, period) - period_key, _ = @cache.send(:key_and_expiry, 'cache-test-key', period) - store.read(period_key).to_i.must_equal 1 - @cache.reset_count(unprefixed_key, period) - assert_nil store.read(period_key) - end - end - end -end From 1a532c6cb3e0348aa87c87dca837dbd08576732c Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Fri, 22 Jun 2018 16:17:44 -0300 Subject: [PATCH 109/129] Remove unused development dependency (memcache-client) --- rack-attack.gemspec | 1 - 1 file changed, 1 deletion(-) diff --git a/rack-attack.gemspec b/rack-attack.gemspec index 16bbd50..9a724f7 100644 --- a/rack-attack.gemspec +++ b/rack-attack.gemspec @@ -50,6 +50,5 @@ Gem::Specification.new do |s| s.add_development_dependency 'activesupport', '>= 3.0.0' s.add_development_dependency 'connection_pool' s.add_development_dependency 'dalli' - s.add_development_dependency 'memcache-client' s.add_development_dependency 'redis-activesupport' end From 79de0d53e1a333f2c469d24008d68d229a1ae037 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Fri, 22 Jun 2018 17:30:06 -0300 Subject: [PATCH 110/129] Only require dalli when running dalli appraisal --- rack-attack.gemspec | 1 - .../stores/active_support_dalli_store_spec.rb | 58 +++++++++--------- .../active_support_mem_cache_store_spec.rb | 56 ++++++++--------- .../connection_pool_dalli_client_spec.rb | 60 ++++++++++--------- spec/acceptance/stores/dalli_client_spec.rb | 58 +++++++++--------- spec/integration/offline_spec.rb | 21 +++---- spec/spec_helper.rb | 5 ++ 7 files changed, 136 insertions(+), 123 deletions(-) diff --git a/rack-attack.gemspec b/rack-attack.gemspec index 9a724f7..588024e 100644 --- a/rack-attack.gemspec +++ b/rack-attack.gemspec @@ -49,6 +49,5 @@ Gem::Specification.new do |s| s.add_development_dependency 'actionpack', '>= 3.0.0' s.add_development_dependency 'activesupport', '>= 3.0.0' s.add_development_dependency 'connection_pool' - s.add_development_dependency 'dalli' s.add_development_dependency 'redis-activesupport' end diff --git a/spec/acceptance/stores/active_support_dalli_store_spec.rb b/spec/acceptance/stores/active_support_dalli_store_spec.rb index b3be95f..440a860 100644 --- a/spec/acceptance/stores/active_support_dalli_store_spec.rb +++ b/spec/acceptance/stores/active_support_dalli_store_spec.rb @@ -1,39 +1,41 @@ require_relative "../../spec_helper" -require_relative "../../support/cache_store_helper" -require "active_support/cache/dalli_store" -require "timecop" +if defined?(::Dalli) + require_relative "../../support/cache_store_helper" + require "active_support/cache/dalli_store" + require "timecop" -describe "ActiveSupport::Cache::DalliStore as a cache backend" do - before do - Rack::Attack.cache.store = ActiveSupport::Cache::DalliStore.new - end - - after do - Rack::Attack.cache.store.clear - end - - it_works_for_cache_backed_features - - it "doesn't leak keys" do - Rack::Attack.throttle("by ip", limit: 1, period: 1) do |request| - request.ip + describe "ActiveSupport::Cache::DalliStore as a cache backend" do + before do + Rack::Attack.cache.store = ActiveSupport::Cache::DalliStore.new end - key = nil - - # Freeze time during these statement to be sure that the key used by rack attack is the same - # we pre-calculate in local variable `key` - Timecop.freeze do - key = "rack::attack:#{Time.now.to_i}:by ip:1.2.3.4" - - get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + after do + Rack::Attack.cache.store.clear end - assert Rack::Attack.cache.store.fetch(key) + it_works_for_cache_backed_features - sleep 2.1 + it "doesn't leak keys" do + Rack::Attack.throttle("by ip", limit: 1, period: 1) do |request| + request.ip + end - assert_nil Rack::Attack.cache.store.fetch(key) + key = nil + + # Freeze time during these statement to be sure that the key used by rack attack is the same + # we pre-calculate in local variable `key` + Timecop.freeze do + key = "rack::attack:#{Time.now.to_i}:by ip:1.2.3.4" + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + end + + assert Rack::Attack.cache.store.fetch(key) + + sleep 2.1 + + assert_nil Rack::Attack.cache.store.fetch(key) + end 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 1a4e7f7..4a3ced6 100644 --- a/spec/acceptance/stores/active_support_mem_cache_store_spec.rb +++ b/spec/acceptance/stores/active_support_mem_cache_store_spec.rb @@ -1,38 +1,40 @@ require_relative "../../spec_helper" -require_relative "../../support/cache_store_helper" -require "timecop" +if defined?(::Dalli) + require_relative "../../support/cache_store_helper" + require "timecop" -describe "ActiveSupport::Cache::MemCacheStore as a cache backend" do - before do - Rack::Attack.cache.store = ActiveSupport::Cache::MemCacheStore.new - end - - after do - Rack::Attack.cache.store.flush_all - end - - it_works_for_cache_backed_features - - it "doesn't leak keys" do - Rack::Attack.throttle("by ip", limit: 1, period: 1) do |request| - request.ip + describe "ActiveSupport::Cache::MemCacheStore as a cache backend" do + before do + Rack::Attack.cache.store = ActiveSupport::Cache::MemCacheStore.new end - key = nil - - # Freeze time during these statement to be sure that the key used by rack attack is the same - # we pre-calculate in local variable `key` - Timecop.freeze do - key = "rack::attack:#{Time.now.to_i}:by ip:1.2.3.4" - - get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + after do + Rack::Attack.cache.store.flush_all end - assert Rack::Attack.cache.store.get(key) + it_works_for_cache_backed_features - sleep 2.1 + it "doesn't leak keys" do + Rack::Attack.throttle("by ip", limit: 1, period: 1) do |request| + request.ip + end - assert_nil Rack::Attack.cache.store.get(key) + key = nil + + # Freeze time during these statement to be sure that the key used by rack attack is the same + # we pre-calculate in local variable `key` + Timecop.freeze do + key = "rack::attack:#{Time.now.to_i}:by ip:1.2.3.4" + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + end + + assert Rack::Attack.cache.store.get(key) + + sleep 2.1 + + assert_nil Rack::Attack.cache.store.get(key) + end end end diff --git a/spec/acceptance/stores/connection_pool_dalli_client_spec.rb b/spec/acceptance/stores/connection_pool_dalli_client_spec.rb index a852d03..bb8bf66 100644 --- a/spec/acceptance/stores/connection_pool_dalli_client_spec.rb +++ b/spec/acceptance/stores/connection_pool_dalli_client_spec.rb @@ -1,40 +1,42 @@ require_relative "../../spec_helper" -require_relative "../../support/cache_store_helper" -require "connection_pool" -require "dalli" -require "timecop" +if defined?(::Dalli) + require_relative "../../support/cache_store_helper" + require "connection_pool" + require "dalli" + require "timecop" -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 - - it "doesn't leak keys" do - Rack::Attack.throttle("by ip", limit: 1, period: 1) do |request| - request.ip + describe "ConnectionPool with Dalli::Client as a cache backend" do + before do + Rack::Attack.cache.store = ConnectionPool.new { Dalli::Client.new } end - key = nil - - # Freeze time during these statement to be sure that the key used by rack attack is the same - # we pre-calculate in local variable `key` - Timecop.freeze do - key = "rack::attack:#{Time.now.to_i}:by ip:1.2.3.4" - - get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + after do + Rack::Attack.cache.store.with { |client| client.flush_all } end - assert(Rack::Attack.cache.store.with { |client| client.fetch(key) }) + it_works_for_cache_backed_features - sleep 2.1 + it "doesn't leak keys" do + Rack::Attack.throttle("by ip", limit: 1, period: 1) do |request| + request.ip + end - assert_nil(Rack::Attack.cache.store.with { |client| client.fetch(key) }) + key = nil + + # Freeze time during these statement to be sure that the key used by rack attack is the same + # we pre-calculate in local variable `key` + Timecop.freeze do + key = "rack::attack:#{Time.now.to_i}:by ip:1.2.3.4" + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + end + + assert(Rack::Attack.cache.store.with { |client| client.fetch(key) }) + + sleep 2.1 + + assert_nil(Rack::Attack.cache.store.with { |client| client.fetch(key) }) + end end end diff --git a/spec/acceptance/stores/dalli_client_spec.rb b/spec/acceptance/stores/dalli_client_spec.rb index e01ab3f..48a2d9f 100644 --- a/spec/acceptance/stores/dalli_client_spec.rb +++ b/spec/acceptance/stores/dalli_client_spec.rb @@ -1,39 +1,41 @@ require_relative "../../spec_helper" -require_relative "../../support/cache_store_helper" -require "dalli" -require "timecop" +if defined?(::Dalli) + require_relative "../../support/cache_store_helper" + require "dalli" + require "timecop" -describe "Dalli::Client as a cache backend" do - before do - Rack::Attack.cache.store = Dalli::Client.new - end - - after do - Rack::Attack.cache.store.flush_all - end - - it_works_for_cache_backed_features - - it "doesn't leak keys" do - Rack::Attack.throttle("by ip", limit: 1, period: 1) do |request| - request.ip + describe "Dalli::Client as a cache backend" do + before do + Rack::Attack.cache.store = Dalli::Client.new end - key = nil - - # Freeze time during these statement to be sure that the key used by rack attack is the same - # we pre-calculate in local variable `key` - Timecop.freeze do - key = "rack::attack:#{Time.now.to_i}:by ip:1.2.3.4" - - get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + after do + Rack::Attack.cache.store.flush_all end - assert Rack::Attack.cache.store.fetch(key) + it_works_for_cache_backed_features - sleep 2.1 + it "doesn't leak keys" do + Rack::Attack.throttle("by ip", limit: 1, period: 1) do |request| + request.ip + end - assert_nil Rack::Attack.cache.store.fetch(key) + key = nil + + # Freeze time during these statement to be sure that the key used by rack attack is the same + # we pre-calculate in local variable `key` + Timecop.freeze do + key = "rack::attack:#{Time.now.to_i}:by ip:1.2.3.4" + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + end + + assert Rack::Attack.cache.store.fetch(key) + + sleep 2.1 + + assert_nil Rack::Attack.cache.store.fetch(key) + end end end diff --git a/spec/integration/offline_spec.rb b/spec/integration/offline_spec.rb index ebaf16a..8d1f670 100644 --- a/spec/integration/offline_spec.rb +++ b/spec/integration/offline_spec.rb @@ -1,6 +1,5 @@ require 'active_support/cache' require 'redis-activesupport' -require 'dalli' require_relative '../spec_helper' OfflineExamples = Minitest::SharedExamples.new do @@ -27,17 +26,19 @@ describe 'when Redis is offline' do end end -describe 'when Memcached is offline' do - include OfflineExamples +if defined?(::Dalli) + describe 'when Memcached is offline' do + include OfflineExamples - before do - Dalli.logger.level = Logger::FATAL + before do + Dalli.logger.level = Logger::FATAL - @cache = Rack::Attack::Cache.new - @cache.store = Dalli::Client.new('127.0.0.1:22122') - end + @cache = Rack::Attack::Cache.new + @cache.store = Dalli::Client.new('127.0.0.1:22122') + end - after do - Dalli.logger.level = Logger::INFO + after do + Dalli.logger.level = Logger::INFO + end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 7d2c851..71ff76b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -13,6 +13,11 @@ if RUBY_ENGINE == "ruby" require "byebug" end +begin + require "dalli" +rescue LoadError +end + class MiniTest::Spec include Rack::Test::Methods From bd2ade8977a284f2d419a15defb55d02e2a5e13b Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Fri, 22 Jun 2018 17:45:58 -0300 Subject: [PATCH 111/129] Only require connection_pool running connection_pool appraisal --- Appraisals | 5 ++ gemfiles/connection_pool_dalli.gemfile | 8 +++ rack-attack.gemspec | 1 - ...e_support_redis_cache_store_pooled_spec.rb | 58 ++++++++++--------- .../connection_pool_dalli_client_spec.rb | 2 +- spec/spec_helper.rb | 5 ++ 6 files changed, 49 insertions(+), 30 deletions(-) create mode 100644 gemfiles/connection_pool_dalli.gemfile diff --git a/Appraisals b/Appraisals index 2ccb781..55b47f0 100644 --- a/Appraisals +++ b/Appraisals @@ -32,3 +32,8 @@ end appraise 'dalli2' do gem 'dalli', '~> 2.0' end + +appraise "connection_pool_dalli" do + gem "connection_pool", "~> 2.2" + gem "dalli", "~> 2.7" +end diff --git a/gemfiles/connection_pool_dalli.gemfile b/gemfiles/connection_pool_dalli.gemfile new file mode 100644 index 0000000..69dc887 --- /dev/null +++ b/gemfiles/connection_pool_dalli.gemfile @@ -0,0 +1,8 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "connection_pool", "~> 2.2" +gem "dalli", "~> 2.7" + +gemspec path: "../" diff --git a/rack-attack.gemspec b/rack-attack.gemspec index 588024e..59ba7df 100644 --- a/rack-attack.gemspec +++ b/rack-attack.gemspec @@ -48,6 +48,5 @@ Gem::Specification.new do |s| # which rack-attack uses only for testing compatibility in test suite. s.add_development_dependency 'actionpack', '>= 3.0.0' s.add_development_dependency 'activesupport', '>= 3.0.0' - s.add_development_dependency 'connection_pool' s.add_development_dependency 'redis-activesupport' end diff --git a/spec/acceptance/stores/active_support_redis_cache_store_pooled_spec.rb b/spec/acceptance/stores/active_support_redis_cache_store_pooled_spec.rb index 8c006bb..3447981 100644 --- a/spec/acceptance/stores/active_support_redis_cache_store_pooled_spec.rb +++ b/spec/acceptance/stores/active_support_redis_cache_store_pooled_spec.rb @@ -1,40 +1,42 @@ require_relative "../../spec_helper" -require_relative "../../support/cache_store_helper" -require "timecop" +if defined?(::ConnectionPool) + require_relative "../../support/cache_store_helper" + require "timecop" -if ActiveSupport.version >= Gem::Version.new("5.2.0") - describe "ActiveSupport::Cache::RedisCacheStore (pooled) as a cache backend" do - before do - Rack::Attack.cache.store = ActiveSupport::Cache::RedisCacheStore.new(pool_size: 2) - end - - after do - Rack::Attack.cache.store.clear - end - - it_works_for_cache_backed_features - - it "doesn't leak keys" do - Rack::Attack.throttle("by ip", limit: 1, period: 1) do |request| - request.ip + if ActiveSupport.version >= Gem::Version.new("5.2.0") + describe "ActiveSupport::Cache::RedisCacheStore (pooled) as a cache backend" do + before do + Rack::Attack.cache.store = ActiveSupport::Cache::RedisCacheStore.new(pool_size: 2) end - key = nil - - # Freeze time during these statement to be sure that the key used by rack attack is the same - # we pre-calculate in local variable `key` - Timecop.freeze do - key = "rack::attack:#{Time.now.to_i}:by ip:1.2.3.4" - - get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + after do + Rack::Attack.cache.store.clear end - assert Rack::Attack.cache.store.fetch(key) + it_works_for_cache_backed_features - sleep 2.1 + it "doesn't leak keys" do + Rack::Attack.throttle("by ip", limit: 1, period: 1) do |request| + request.ip + end - assert_nil Rack::Attack.cache.store.fetch(key) + key = nil + + # Freeze time during these statement to be sure that the key used by rack attack is the same + # we pre-calculate in local variable `key` + Timecop.freeze do + key = "rack::attack:#{Time.now.to_i}:by ip:1.2.3.4" + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + end + + assert Rack::Attack.cache.store.fetch(key) + + sleep 2.1 + + assert_nil Rack::Attack.cache.store.fetch(key) + end end end end diff --git a/spec/acceptance/stores/connection_pool_dalli_client_spec.rb b/spec/acceptance/stores/connection_pool_dalli_client_spec.rb index bb8bf66..15f4564 100644 --- a/spec/acceptance/stores/connection_pool_dalli_client_spec.rb +++ b/spec/acceptance/stores/connection_pool_dalli_client_spec.rb @@ -1,6 +1,6 @@ require_relative "../../spec_helper" -if defined?(::Dalli) +if defined?(::Dalli) && defined?(::ConnectionPool) require_relative "../../support/cache_store_helper" require "connection_pool" require "dalli" diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 71ff76b..4bac5f8 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -18,6 +18,11 @@ begin rescue LoadError end +begin + require "connection_pool" +rescue LoadError +end + class MiniTest::Spec include Rack::Test::Methods From a72bfb5fc782b69afdebfc5bd92871056a181a1b Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Fri, 22 Jun 2018 18:55:27 -0300 Subject: [PATCH 112/129] Only require redis stores when running their respective appraisal --- Appraisals | 19 +++++++ .../active_support_redis_cache_store.gemfile | 8 +++ ...e_support_redis_cache_store_pooled.gemfile | 9 +++ gemfiles/active_support_redis_store.gemfile | 7 +++ gemfiles/redis_store.gemfile | 7 +++ rack-attack.gemspec | 1 - ...e_support_redis_cache_store_pooled_spec.rb | 54 +++++++++--------- .../active_support_redis_cache_store_spec.rb | 6 +- .../stores/active_support_redis_store_spec.rb | 57 ++++++++++--------- spec/acceptance/stores/redis_store_spec.rb | 55 +++++++++--------- spec/integration/offline_spec.rb | 15 ++--- spec/spec_helper.rb | 15 +++++ 12 files changed, 159 insertions(+), 94 deletions(-) create mode 100644 gemfiles/active_support_redis_cache_store.gemfile create mode 100644 gemfiles/active_support_redis_cache_store_pooled.gemfile create mode 100644 gemfiles/active_support_redis_store.gemfile create mode 100644 gemfiles/redis_store.gemfile diff --git a/Appraisals b/Appraisals index 55b47f0..e9d2e21 100644 --- a/Appraisals +++ b/Appraisals @@ -37,3 +37,22 @@ appraise "connection_pool_dalli" do gem "connection_pool", "~> 2.2" gem "dalli", "~> 2.7" end + +appraise "active_support_redis_cache_store" do + gem "activesupport", "~> 5.2.0" + gem "redis", "~> 4.0" +end + +appraise "active_support_redis_cache_store_pooled" do + gem "activesupport", "~> 5.2.0" + gem "connection_pool", "~> 2.2" + gem "redis", "~> 4.0" +end + +appraise "redis_store" do + gem "redis-store", "~> 1.5" +end + +appraise "active_support_redis_store" do + gem "redis-activesupport", "~> 5.0" +end diff --git a/gemfiles/active_support_redis_cache_store.gemfile b/gemfiles/active_support_redis_cache_store.gemfile new file mode 100644 index 0000000..30e1e38 --- /dev/null +++ b/gemfiles/active_support_redis_cache_store.gemfile @@ -0,0 +1,8 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "activesupport", "~> 5.2.0" +gem "redis", "~> 4.0" + +gemspec path: "../" diff --git a/gemfiles/active_support_redis_cache_store_pooled.gemfile b/gemfiles/active_support_redis_cache_store_pooled.gemfile new file mode 100644 index 0000000..9232a9b --- /dev/null +++ b/gemfiles/active_support_redis_cache_store_pooled.gemfile @@ -0,0 +1,9 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "activesupport", "~> 5.2.0" +gem "connection_pool", "~> 2.2" +gem "redis", "~> 4.0" + +gemspec path: "../" diff --git a/gemfiles/active_support_redis_store.gemfile b/gemfiles/active_support_redis_store.gemfile new file mode 100644 index 0000000..517c70f --- /dev/null +++ b/gemfiles/active_support_redis_store.gemfile @@ -0,0 +1,7 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "redis-activesupport", "~> 5.0" + +gemspec path: "../" diff --git a/gemfiles/redis_store.gemfile b/gemfiles/redis_store.gemfile new file mode 100644 index 0000000..8aafc6d --- /dev/null +++ b/gemfiles/redis_store.gemfile @@ -0,0 +1,7 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "redis-store", "~> 1.5" + +gemspec path: "../" diff --git a/rack-attack.gemspec b/rack-attack.gemspec index 59ba7df..c1180c7 100644 --- a/rack-attack.gemspec +++ b/rack-attack.gemspec @@ -48,5 +48,4 @@ Gem::Specification.new do |s| # which rack-attack uses only for testing compatibility in test suite. s.add_development_dependency 'actionpack', '>= 3.0.0' s.add_development_dependency 'activesupport', '>= 3.0.0' - s.add_development_dependency 'redis-activesupport' end diff --git a/spec/acceptance/stores/active_support_redis_cache_store_pooled_spec.rb b/spec/acceptance/stores/active_support_redis_cache_store_pooled_spec.rb index 3447981..5295ba5 100644 --- a/spec/acceptance/stores/active_support_redis_cache_store_pooled_spec.rb +++ b/spec/acceptance/stores/active_support_redis_cache_store_pooled_spec.rb @@ -1,42 +1,40 @@ require_relative "../../spec_helper" -if defined?(::ConnectionPool) +if defined?(::ConnectionPool) && defined?(::Redis) && defined?(::ActiveSupport::Cache::RedisCacheStore) require_relative "../../support/cache_store_helper" require "timecop" - if ActiveSupport.version >= Gem::Version.new("5.2.0") - describe "ActiveSupport::Cache::RedisCacheStore (pooled) as a cache backend" do - before do - Rack::Attack.cache.store = ActiveSupport::Cache::RedisCacheStore.new(pool_size: 2) + describe "ActiveSupport::Cache::RedisCacheStore (pooled) as a cache backend" do + before do + Rack::Attack.cache.store = ActiveSupport::Cache::RedisCacheStore.new(pool_size: 2) + end + + after do + Rack::Attack.cache.store.clear + end + + it_works_for_cache_backed_features + + it "doesn't leak keys" do + Rack::Attack.throttle("by ip", limit: 1, period: 1) do |request| + request.ip end - after do - Rack::Attack.cache.store.clear + key = nil + + # Freeze time during these statement to be sure that the key used by rack attack is the same + # we pre-calculate in local variable `key` + Timecop.freeze do + key = "rack::attack:#{Time.now.to_i}:by ip:1.2.3.4" + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" end - it_works_for_cache_backed_features + assert Rack::Attack.cache.store.fetch(key) - it "doesn't leak keys" do - Rack::Attack.throttle("by ip", limit: 1, period: 1) do |request| - request.ip - end + sleep 2.1 - key = nil - - # Freeze time during these statement to be sure that the key used by rack attack is the same - # we pre-calculate in local variable `key` - Timecop.freeze do - key = "rack::attack:#{Time.now.to_i}:by ip:1.2.3.4" - - get "/", {}, "REMOTE_ADDR" => "1.2.3.4" - end - - assert Rack::Attack.cache.store.fetch(key) - - sleep 2.1 - - assert_nil Rack::Attack.cache.store.fetch(key) - end + assert_nil Rack::Attack.cache.store.fetch(key) end end end diff --git a/spec/acceptance/stores/active_support_redis_cache_store_spec.rb b/spec/acceptance/stores/active_support_redis_cache_store_spec.rb index 495a6a2..e6ecdda 100644 --- a/spec/acceptance/stores/active_support_redis_cache_store_spec.rb +++ b/spec/acceptance/stores/active_support_redis_cache_store_spec.rb @@ -1,9 +1,9 @@ require_relative "../../spec_helper" -require_relative "../../support/cache_store_helper" -require "timecop" +if defined?(::Redis) && defined?(::ActiveSupport::Cache::RedisCacheStore) + require_relative "../../support/cache_store_helper" + require "timecop" -if ActiveSupport.version >= Gem::Version.new("5.2.0") describe "ActiveSupport::Cache::RedisCacheStore as a cache backend" do before do Rack::Attack.cache.store = ActiveSupport::Cache::RedisCacheStore.new diff --git a/spec/acceptance/stores/active_support_redis_store_spec.rb b/spec/acceptance/stores/active_support_redis_store_spec.rb index 0b446e4..840a7d4 100644 --- a/spec/acceptance/stores/active_support_redis_store_spec.rb +++ b/spec/acceptance/stores/active_support_redis_store_spec.rb @@ -1,39 +1,40 @@ require_relative "../../spec_helper" -require_relative "../../support/cache_store_helper" -require "redis-activesupport" -require "timecop" +if defined?(::ActiveSupport::Cache::RedisStore) + require_relative "../../support/cache_store_helper" + require "timecop" -describe "ActiveSupport::Cache::RedisStore as a cache backend" do - before do - Rack::Attack.cache.store = ActiveSupport::Cache::RedisStore.new - end - - after do - Rack::Attack.cache.store.flushdb - end - - it_works_for_cache_backed_features - - it "doesn't leak keys" do - Rack::Attack.throttle("by ip", limit: 1, period: 1) do |request| - request.ip + describe "ActiveSupport::Cache::RedisStore as a cache backend" do + before do + Rack::Attack.cache.store = ActiveSupport::Cache::RedisStore.new end - key = nil - - # Freeze time during these statement to be sure that the key used by rack attack is the same - # we pre-calculate in local variable `key` - Timecop.freeze do - key = "rack::attack:#{Time.now.to_i}:by ip:1.2.3.4" - - get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + after do + Rack::Attack.cache.store.flushdb end - assert Rack::Attack.cache.store.read(key) + it_works_for_cache_backed_features - sleep 2.1 + it "doesn't leak keys" do + Rack::Attack.throttle("by ip", limit: 1, period: 1) do |request| + request.ip + end - assert_nil Rack::Attack.cache.store.read(key) + key = nil + + # Freeze time during these statement to be sure that the key used by rack attack is the same + # we pre-calculate in local variable `key` + Timecop.freeze do + key = "rack::attack:#{Time.now.to_i}:by ip:1.2.3.4" + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + end + + assert Rack::Attack.cache.store.read(key) + + sleep 2.1 + + assert_nil Rack::Attack.cache.store.read(key) + end end end diff --git a/spec/acceptance/stores/redis_store_spec.rb b/spec/acceptance/stores/redis_store_spec.rb index 17e5a0a..9c9fba5 100644 --- a/spec/acceptance/stores/redis_store_spec.rb +++ b/spec/acceptance/stores/redis_store_spec.rb @@ -1,39 +1,40 @@ require_relative "../../spec_helper" require_relative "../../support/cache_store_helper" -require "redis-store" -require "timecop" +if defined?(::Redis::Store) + require "timecop" -describe "ActiveSupport::Cache::RedisStore as a cache backend" do - before do - Rack::Attack.cache.store = ::Redis::Store.new - end - - after do - Rack::Attack.cache.store.flushdb - end - - it_works_for_cache_backed_features - - it "doesn't leak keys" do - Rack::Attack.throttle("by ip", limit: 1, period: 1) do |request| - request.ip + describe "ActiveSupport::Cache::RedisStore as a cache backend" do + before do + Rack::Attack.cache.store = ::Redis::Store.new end - key = nil - - # Freeze time during these statement to be sure that the key used by rack attack is the same - # we pre-calculate in local variable `key` - Timecop.freeze do - key = "rack::attack:#{Time.now.to_i}:by ip:1.2.3.4" - - get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + after do + Rack::Attack.cache.store.flushdb end - assert Rack::Attack.cache.store.read(key) + it_works_for_cache_backed_features - sleep 2.1 + it "doesn't leak keys" do + Rack::Attack.throttle("by ip", limit: 1, period: 1) do |request| + request.ip + end - assert_nil Rack::Attack.cache.store.read(key) + key = nil + + # Freeze time during these statement to be sure that the key used by rack attack is the same + # we pre-calculate in local variable `key` + Timecop.freeze do + key = "rack::attack:#{Time.now.to_i}:by ip:1.2.3.4" + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + end + + assert Rack::Attack.cache.store.read(key) + + sleep 2.1 + + assert_nil Rack::Attack.cache.store.read(key) + end end end diff --git a/spec/integration/offline_spec.rb b/spec/integration/offline_spec.rb index 8d1f670..6a6e925 100644 --- a/spec/integration/offline_spec.rb +++ b/spec/integration/offline_spec.rb @@ -1,5 +1,4 @@ require 'active_support/cache' -require 'redis-activesupport' require_relative '../spec_helper' OfflineExamples = Minitest::SharedExamples.new do @@ -16,13 +15,15 @@ OfflineExamples = Minitest::SharedExamples.new do end end -describe 'when Redis is offline' do - include OfflineExamples +if defined?(::ActiveSupport::Cache::RedisStore) + describe 'when Redis is offline' do + include OfflineExamples - before do - @cache = Rack::Attack::Cache.new - # Use presumably unused port for Redis client - @cache.store = ActiveSupport::Cache::RedisStore.new(:host => '127.0.0.1', :port => 3333) + before do + @cache = Rack::Attack::Cache.new + # Use presumably unused port for Redis client + @cache.store = ActiveSupport::Cache::RedisStore.new(:host => '127.0.0.1', :port => 3333) + end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4bac5f8..420f193 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -23,6 +23,21 @@ begin rescue LoadError end +begin + require "redis" +rescue LoadError +end + +begin + require "redis-activesupport" +rescue LoadError +end + +begin + require "redis-store" +rescue LoadError +end + class MiniTest::Spec include Rack::Test::Methods From a1ea2f9aefdec5414660e05216e0609711a8247d Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Fri, 22 Jun 2018 19:04:47 -0300 Subject: [PATCH 113/129] Avoid repetition in spec_helper --- spec/spec_helper.rb | 32 ++++++++++---------------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 420f193..20c6d47 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -13,30 +13,18 @@ if RUBY_ENGINE == "ruby" require "byebug" end -begin - require "dalli" -rescue LoadError +def safe_require(name) + begin + require name + rescue LoadError + end end -begin - require "connection_pool" -rescue LoadError -end - -begin - require "redis" -rescue LoadError -end - -begin - require "redis-activesupport" -rescue LoadError -end - -begin - require "redis-store" -rescue LoadError -end +safe_require "connection_pool" +safe_require "dalli" +safe_require "redis" +safe_require "redis-activesupport" +safe_require "redis-store" class MiniTest::Spec include Rack::Test::Methods From 5d48addd6ebc91817298e79d1033ac7df95d28c2 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Fri, 22 Jun 2018 19:20:46 -0300 Subject: [PATCH 114/129] Make TravisCI run new appraisal scenarios --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index 612ece6..24a4474 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,6 +27,11 @@ gemfile: - gemfiles/rails_5_1.gemfile - gemfiles/rails_4_2.gemfile - gemfiles/dalli2.gemfile + - gemfiles/active_support_redis_cache_store + - gemfiles/active_support_redis_cache_store_pooled + - gemfiles/active_support_redis_store + - gemfiles/connection_pool_dalli + - gemfiles/redis_store matrix: allow_failures: From 0d338227f061aeb08a4f34cfb42c4e0f401e4fea Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Mon, 25 Jun 2018 14:13:25 -0300 Subject: [PATCH 115/129] Update ruby 2.6 for TravisCI runs --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 612ece6..478a5fe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: ruby cache: bundler rvm: - - 2.6.0-preview1 + - 2.6.0-preview2 - 2.5.1 - 2.4.4 - 2.3.7 @@ -30,7 +30,7 @@ gemfile: matrix: allow_failures: - - rvm: 2.6.0-preview1 + - rvm: 2.6.0-preview2 fast_finish: true From e3213ee746f61432f8ba99ec42679e1d8df8785e Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Mon, 25 Jun 2018 16:15:02 -0300 Subject: [PATCH 116/129] Fix .travis.yml gemfile paths --- .travis.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 24a4474..7cce90c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,11 +27,11 @@ gemfile: - gemfiles/rails_5_1.gemfile - gemfiles/rails_4_2.gemfile - gemfiles/dalli2.gemfile - - gemfiles/active_support_redis_cache_store - - gemfiles/active_support_redis_cache_store_pooled - - gemfiles/active_support_redis_store - - gemfiles/connection_pool_dalli - - gemfiles/redis_store + - gemfiles/connection_pool_dalli.gemfile + - gemfiles/active_support_redis_cache_store.gemfile + - gemfiles/active_support_redis_cache_store_pooled.gemfile + - gemfiles/redis_store.gemfile + - gemfiles/active_support_redis_store.gemfile matrix: allow_failures: From ee84079768b619784a8e9b98084f263173ed10eb Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Fri, 22 Jun 2018 19:10:35 -0300 Subject: [PATCH 117/129] Fix 'redis is not part of the bundle' exception when using :memory_store When RedisCacheStore constant is referenced, activesupport autoloads and rails tries to require redis, throwing exception if not present --- lib/rack/attack/store_proxy/redis_cache_store_proxy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f99de36..d6cbdfb 100644 --- a/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb +++ b/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb @@ -5,7 +5,7 @@ module Rack module StoreProxy class RedisCacheStoreProxy < SimpleDelegator def self.handle?(store) - defined?(::ActiveSupport::Cache::RedisCacheStore) && store.is_a?(::ActiveSupport::Cache::RedisCacheStore) + defined?(::Redis) && defined?(::ActiveSupport::Cache::RedisCacheStore) && store.is_a?(::ActiveSupport::Cache::RedisCacheStore) end def increment(name, amount = 1, options = {}) From 21561bb2f529143d975220df5174da7545dd1eb5 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Mon, 25 Jun 2018 19:27:35 -0300 Subject: [PATCH 118/129] Bump version to v5.3.2 --- CHANGELOG.md | 10 +++++++++- lib/rack/attack/version.rb | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16092a1..c8c798a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,17 @@ # Changelog + All notable changes to this project will be documented in this file. ## [Unreleased] _No significant changes since last release yet. Stay tuned_ :radio: +## [5.3.2] - 2018-06-25 + +### Fixed + +- Don't raise exception `The Redis cache store requires the redis gem` when using [`ActiveSupport::Cache::MemoryStore`](http://api.rubyonrails.org/classes/ActiveSupport/Cache/MemoryStore.html) as a cache store backend + ## [5.3.1] - 2018-06-20 ### Fixed @@ -117,7 +124,8 @@ _No significant changes since last release yet. Stay tuned_ :radio: - Remove unused variable - Extract mandatory options to constants -[Unreleased]: https://github.com/kickstarter/rack-attack/compare/v5.3.1...HEAD/ +[Unreleased]: https://github.com/kickstarter/rack-attack/compare/v5.3.2...HEAD/ +[5.3.2]: https://github.com/kickstarter/rack-attack/compare/v5.3.1...v5.3.2/ [5.3.1]: https://github.com/kickstarter/rack-attack/compare/v5.3.0...v5.3.1/ [5.3.0]: https://github.com/kickstarter/rack-attack/compare/v5.2.0...v5.3.0/ [5.2.0]: https://github.com/kickstarter/rack-attack/compare/v5.1.0...v5.2.0/ diff --git a/lib/rack/attack/version.rb b/lib/rack/attack/version.rb index 8b695ff..e4ec2ea 100644 --- a/lib/rack/attack/version.rb +++ b/lib/rack/attack/version.rb @@ -1,5 +1,5 @@ module Rack class Attack - VERSION = '5.3.1' + VERSION = '5.3.2' end end From eafc13f44ae0d7fd107987f418ca9bc5bfaece85 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 26 Jun 2018 09:50:40 -0300 Subject: [PATCH 119/129] Make test run output a bit more clear by collapsing 3 separate runs into just 1 --- Rakefile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Rakefile b/Rakefile index 961943b..182e118 100644 --- a/Rakefile +++ b/Rakefile @@ -20,7 +20,8 @@ namespace :test do end end -desc 'Run tests' -task :test => %w[test:units test:integration test:acceptance] +Rake::TestTask.new(:test) do |t| + t.pattern = "spec/**/*_spec.rb" +end task :default => [:rubocop, :test] From 3c37390c635d232e791a8eafa41b9a4a1c47d950 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 26 Jun 2018 11:15:13 -0300 Subject: [PATCH 120/129] Run with latest activesupport/actionpack when developing --- Appraisals | 5 +++++ gemfiles/rack_1_6.gemfile | 2 ++ rack-attack.gemspec | 4 ++-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Appraisals b/Appraisals index e9d2e21..c800d68 100644 --- a/Appraisals +++ b/Appraisals @@ -5,6 +5,11 @@ end appraise "rack_1_6" do gem "rack", "~> 1.6.9" + # Override activesupport and actionpack version constraints by making + # it more loose so it's compatible with rack 1.6.x + gem "activesupport", ">= 4.2" + gem "actionpack", ">= 4.2" + # Override rack-test version constraint by making it more loose # so it's compatible with actionpack 4.2.x gem "rack-test", ">= 0.6" diff --git a/gemfiles/rack_1_6.gemfile b/gemfiles/rack_1_6.gemfile index 8b636ed..1c10335 100644 --- a/gemfiles/rack_1_6.gemfile +++ b/gemfiles/rack_1_6.gemfile @@ -3,6 +3,8 @@ source "https://rubygems.org" gem "rack", "~> 1.6.9" +gem "activesupport", ">= 4.2" +gem "actionpack", ">= 4.2" gem "rack-test", ">= 0.6" gemspec path: "../" diff --git a/rack-attack.gemspec b/rack-attack.gemspec index c1180c7..f2e6d9c 100644 --- a/rack-attack.gemspec +++ b/rack-attack.gemspec @@ -46,6 +46,6 @@ Gem::Specification.new do |s| # The following are potential runtime dependencies users may have, # which rack-attack uses only for testing compatibility in test suite. - s.add_development_dependency 'actionpack', '>= 3.0.0' - s.add_development_dependency 'activesupport', '>= 3.0.0' + s.add_development_dependency 'actionpack', '~> 5.2' + s.add_development_dependency 'activesupport', '~> 5.2' end From 9e2fef7abe1bc149ef5f5c4772fbca66c743f37f Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 26 Jun 2018 11:20:57 -0300 Subject: [PATCH 121/129] Make clear that bundler is a development depdendency --- rack-attack.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/rack-attack.gemspec b/rack-attack.gemspec index f2e6d9c..ca39cf0 100644 --- a/rack-attack.gemspec +++ b/rack-attack.gemspec @@ -32,6 +32,7 @@ Gem::Specification.new do |s| s.add_dependency 'rack' s.add_development_dependency 'appraisal', '~> 2.2' + s.add_development_dependency "bundler", "~> 1.16" s.add_development_dependency 'minitest', "~> 5.11" s.add_development_dependency "minitest-stub-const", "~> 0.6" s.add_development_dependency 'rack-test', "~> 1.0" From d511c9e2baf3fe547f8cb1b231d99c128dedbd86 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 26 Jun 2018 11:30:13 -0300 Subject: [PATCH 122/129] Make explicit that old rack 0.x is not supported --- rack-attack.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rack-attack.gemspec b/rack-attack.gemspec index c1180c7..7940f02 100644 --- a/rack-attack.gemspec +++ b/rack-attack.gemspec @@ -29,7 +29,7 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 2.2' - s.add_dependency 'rack' + s.add_runtime_dependency 'rack', ">= 1.0" s.add_development_dependency 'appraisal', '~> 2.2' s.add_development_dependency 'minitest', "~> 5.11" From 6a52e0e070c8eec41e887cc3904f8245cd4d783b Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 26 Jun 2018 11:40:19 -0300 Subject: [PATCH 123/129] Fix 'WARNING: open-ended dependency on rack (>= 1.0) is not recommended' --- rack-attack.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rack-attack.gemspec b/rack-attack.gemspec index 7940f02..d7fddc4 100644 --- a/rack-attack.gemspec +++ b/rack-attack.gemspec @@ -29,7 +29,7 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 2.2' - s.add_runtime_dependency 'rack', ">= 1.0" + s.add_runtime_dependency 'rack', ">= 1.0", "< 3" s.add_development_dependency 'appraisal', '~> 2.2' s.add_development_dependency 'minitest', "~> 5.11" From 596ddabb0f36984309a682eb3aca6361153373dd Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Tue, 26 Jun 2018 13:38:16 -0300 Subject: [PATCH 124/129] Fix rubocop Bundler/OrderedGems offenses --- Appraisals | 6 +++--- gemfiles/rack_1_6.gemfile | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Appraisals b/Appraisals index c800d68..00b4bca 100644 --- a/Appraisals +++ b/Appraisals @@ -3,12 +3,12 @@ appraise "rack_2_0" do end appraise "rack_1_6" do - gem "rack", "~> 1.6.9" - # Override activesupport and actionpack version constraints by making # it more loose so it's compatible with rack 1.6.x - gem "activesupport", ">= 4.2" gem "actionpack", ">= 4.2" + gem "activesupport", ">= 4.2" + + gem "rack", "~> 1.6.9" # Override rack-test version constraint by making it more loose # so it's compatible with actionpack 4.2.x diff --git a/gemfiles/rack_1_6.gemfile b/gemfiles/rack_1_6.gemfile index 1c10335..0cc0050 100644 --- a/gemfiles/rack_1_6.gemfile +++ b/gemfiles/rack_1_6.gemfile @@ -2,9 +2,9 @@ source "https://rubygems.org" -gem "rack", "~> 1.6.9" -gem "activesupport", ">= 4.2" gem "actionpack", ">= 4.2" +gem "activesupport", ">= 4.2" +gem "rack", "~> 1.6.9" gem "rack-test", ">= 0.6" gemspec path: "../" From 8315a1e7e11a10a6989ee044c52f94225e5619ff Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Mon, 25 Jun 2018 18:18:19 -0300 Subject: [PATCH 125/129] Remove support for unmaintained ruby 2.2 --- .rubocop.yml | 2 +- .travis.yml | 1 - Appraisals | 2 ++ Gemfile | 2 ++ Rakefile | 2 ++ examples/rack_attack.rb | 2 ++ gemfiles/active_support_redis_cache_store.gemfile | 2 ++ gemfiles/active_support_redis_cache_store_pooled.gemfile | 2 ++ gemfiles/active_support_redis_store.gemfile | 2 ++ gemfiles/connection_pool_dalli.gemfile | 2 ++ gemfiles/dalli2.gemfile | 2 ++ gemfiles/rack_1_6.gemfile | 2 ++ gemfiles/rack_2_0.gemfile | 2 ++ gemfiles/rails_4_2.gemfile | 2 ++ gemfiles/rails_5_1.gemfile | 2 ++ gemfiles/rails_5_2.gemfile | 2 ++ gemfiles/redis_store.gemfile | 2 ++ lib/rack/attack.rb | 2 ++ lib/rack/attack/allow2ban.rb | 2 ++ lib/rack/attack/blocklist.rb | 2 ++ lib/rack/attack/cache.rb | 2 ++ lib/rack/attack/check.rb | 2 ++ lib/rack/attack/fail2ban.rb | 2 ++ lib/rack/attack/path_normalizer.rb | 2 ++ lib/rack/attack/request.rb | 2 ++ lib/rack/attack/safelist.rb | 2 ++ lib/rack/attack/store_proxy.rb | 2 ++ lib/rack/attack/store_proxy/dalli_proxy.rb | 2 ++ lib/rack/attack/store_proxy/mem_cache_proxy.rb | 2 ++ lib/rack/attack/store_proxy/redis_cache_store_proxy.rb | 2 ++ lib/rack/attack/store_proxy/redis_store_proxy.rb | 2 ++ lib/rack/attack/throttle.rb | 2 ++ lib/rack/attack/track.rb | 2 ++ lib/rack/attack/version.rb | 2 ++ rack-attack.gemspec | 3 ++- spec/acceptance/allow2ban_spec.rb | 2 ++ spec/acceptance/blocking_ip_spec.rb | 2 ++ spec/acceptance/blocking_spec.rb | 2 ++ spec/acceptance/blocking_subnet_spec.rb | 2 ++ spec/acceptance/cache_store_config_for_allow2ban_spec.rb | 2 ++ spec/acceptance/cache_store_config_for_fail2ban_spec.rb | 2 ++ spec/acceptance/cache_store_config_for_throttle_spec.rb | 2 ++ spec/acceptance/cache_store_config_with_rails_spec.rb | 2 ++ spec/acceptance/customizing_blocked_response_spec.rb | 2 ++ spec/acceptance/customizing_throttled_response_spec.rb | 2 ++ spec/acceptance/extending_request_object_spec.rb | 2 ++ spec/acceptance/fail2ban_spec.rb | 2 ++ spec/acceptance/safelisting_ip_spec.rb | 2 ++ spec/acceptance/safelisting_spec.rb | 2 ++ spec/acceptance/safelisting_subnet_spec.rb | 2 ++ spec/acceptance/stores/active_support_dalli_store_spec.rb | 2 ++ spec/acceptance/stores/active_support_mem_cache_store_spec.rb | 2 ++ spec/acceptance/stores/active_support_memory_store_spec.rb | 2 ++ .../stores/active_support_redis_cache_store_pooled_spec.rb | 2 ++ .../acceptance/stores/active_support_redis_cache_store_spec.rb | 2 ++ spec/acceptance/stores/active_support_redis_store_spec.rb | 2 ++ spec/acceptance/stores/connection_pool_dalli_client_spec.rb | 2 ++ spec/acceptance/stores/dalli_client_spec.rb | 2 ++ spec/acceptance/stores/redis_store_spec.rb | 2 ++ spec/acceptance/throttling_spec.rb | 2 ++ spec/acceptance/track_spec.rb | 2 ++ spec/acceptance/track_throttle_spec.rb | 2 ++ spec/allow2ban_spec.rb | 2 ++ spec/fail2ban_spec.rb | 2 ++ spec/integration/offline_spec.rb | 2 ++ spec/rack_attack_dalli_proxy_spec.rb | 2 ++ spec/rack_attack_instrumentation_spec.rb | 2 ++ spec/rack_attack_path_normalizer_spec.rb | 2 ++ spec/rack_attack_request_spec.rb | 2 ++ spec/rack_attack_spec.rb | 2 ++ spec/rack_attack_throttle_spec.rb | 2 ++ spec/rack_attack_track_spec.rb | 2 ++ spec/spec_helper.rb | 2 ++ spec/support/cache_store_helper.rb | 2 ++ 74 files changed, 145 insertions(+), 3 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 14b26e5..eea40b4 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -3,7 +3,7 @@ inherit_mode: - Exclude AllCops: - TargetRubyVersion: 2.2 + TargetRubyVersion: 2.3 DisabledByDefault: true Exclude: - "examples/instrumentation.rb" diff --git a/.travis.yml b/.travis.yml index dd560bb..46bb12f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,6 @@ rvm: - 2.5.1 - 2.4.4 - 2.3.7 - - 2.2.10 - jruby-9.1.16.0 before_install: diff --git a/Appraisals b/Appraisals index 00b4bca..2cabba7 100644 --- a/Appraisals +++ b/Appraisals @@ -1,3 +1,5 @@ +# frozen_string_literal: true + appraise "rack_2_0" do gem "rack", "~> 2.0.4" end diff --git a/Gemfile b/Gemfile index fa75df1..7f4f5e9 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + source 'https://rubygems.org' gemspec diff --git a/Rakefile b/Rakefile index 182e118..f08b537 100644 --- a/Rakefile +++ b/Rakefile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rubygems" require "bundler/setup" require 'bundler/gem_tasks' diff --git a/examples/rack_attack.rb b/examples/rack_attack.rb index eea07c0..43f1348 100644 --- a/examples/rack_attack.rb +++ b/examples/rack_attack.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # NB: `req` is a Rack::Request object (basically an env hash with friendly accessor methods) # Throttle 10 requests/ip/second diff --git a/gemfiles/active_support_redis_cache_store.gemfile b/gemfiles/active_support_redis_cache_store.gemfile index 30e1e38..040271b 100644 --- a/gemfiles/active_support_redis_cache_store.gemfile +++ b/gemfiles/active_support_redis_cache_store.gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This file was generated by Appraisal source "https://rubygems.org" diff --git a/gemfiles/active_support_redis_cache_store_pooled.gemfile b/gemfiles/active_support_redis_cache_store_pooled.gemfile index 9232a9b..357ca42 100644 --- a/gemfiles/active_support_redis_cache_store_pooled.gemfile +++ b/gemfiles/active_support_redis_cache_store_pooled.gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This file was generated by Appraisal source "https://rubygems.org" diff --git a/gemfiles/active_support_redis_store.gemfile b/gemfiles/active_support_redis_store.gemfile index 517c70f..922f3cc 100644 --- a/gemfiles/active_support_redis_store.gemfile +++ b/gemfiles/active_support_redis_store.gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This file was generated by Appraisal source "https://rubygems.org" diff --git a/gemfiles/connection_pool_dalli.gemfile b/gemfiles/connection_pool_dalli.gemfile index 69dc887..8c62b05 100644 --- a/gemfiles/connection_pool_dalli.gemfile +++ b/gemfiles/connection_pool_dalli.gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This file was generated by Appraisal source "https://rubygems.org" diff --git a/gemfiles/dalli2.gemfile b/gemfiles/dalli2.gemfile index c47d5af..1628ad6 100644 --- a/gemfiles/dalli2.gemfile +++ b/gemfiles/dalli2.gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This file was generated by Appraisal source "https://rubygems.org" diff --git a/gemfiles/rack_1_6.gemfile b/gemfiles/rack_1_6.gemfile index 0cc0050..91789b4 100644 --- a/gemfiles/rack_1_6.gemfile +++ b/gemfiles/rack_1_6.gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This file was generated by Appraisal source "https://rubygems.org" diff --git a/gemfiles/rack_2_0.gemfile b/gemfiles/rack_2_0.gemfile index 9915a10..c7866b6 100644 --- a/gemfiles/rack_2_0.gemfile +++ b/gemfiles/rack_2_0.gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This file was generated by Appraisal source "https://rubygems.org" diff --git a/gemfiles/rails_4_2.gemfile b/gemfiles/rails_4_2.gemfile index eb09083..5e7dfe3 100644 --- a/gemfiles/rails_4_2.gemfile +++ b/gemfiles/rails_4_2.gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This file was generated by Appraisal source "https://rubygems.org" diff --git a/gemfiles/rails_5_1.gemfile b/gemfiles/rails_5_1.gemfile index d05d8e4..bac5d16 100644 --- a/gemfiles/rails_5_1.gemfile +++ b/gemfiles/rails_5_1.gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This file was generated by Appraisal source "https://rubygems.org" diff --git a/gemfiles/rails_5_2.gemfile b/gemfiles/rails_5_2.gemfile index d12b8c6..1873512 100644 --- a/gemfiles/rails_5_2.gemfile +++ b/gemfiles/rails_5_2.gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This file was generated by Appraisal source "https://rubygems.org" diff --git a/gemfiles/redis_store.gemfile b/gemfiles/redis_store.gemfile index 8aafc6d..0ba921a 100644 --- a/gemfiles/redis_store.gemfile +++ b/gemfiles/redis_store.gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This file was generated by Appraisal source "https://rubygems.org" diff --git a/lib/rack/attack.rb b/lib/rack/attack.rb index 66638a4..f8cd308 100644 --- a/lib/rack/attack.rb +++ b/lib/rack/attack.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack' require 'forwardable' require 'rack/attack/path_normalizer' diff --git a/lib/rack/attack/allow2ban.rb b/lib/rack/attack/allow2ban.rb index 9a91086..faa3518 100644 --- a/lib/rack/attack/allow2ban.rb +++ b/lib/rack/attack/allow2ban.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rack class Attack class Allow2Ban < Fail2Ban diff --git a/lib/rack/attack/blocklist.rb b/lib/rack/attack/blocklist.rb index 3cfba54..ee57656 100644 --- a/lib/rack/attack/blocklist.rb +++ b/lib/rack/attack/blocklist.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rack class Attack class Blocklist < Check diff --git a/lib/rack/attack/cache.rb b/lib/rack/attack/cache.rb index 0e6e6d3..37d9173 100644 --- a/lib/rack/attack/cache.rb +++ b/lib/rack/attack/cache.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rack class Attack class Cache diff --git a/lib/rack/attack/check.rb b/lib/rack/attack/check.rb index b24a873..2e5c64d 100644 --- a/lib/rack/attack/check.rb +++ b/lib/rack/attack/check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rack class Attack class Check diff --git a/lib/rack/attack/fail2ban.rb b/lib/rack/attack/fail2ban.rb index 413808e..b43c7cb 100644 --- a/lib/rack/attack/fail2ban.rb +++ b/lib/rack/attack/fail2ban.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rack class Attack class Fail2Ban diff --git a/lib/rack/attack/path_normalizer.rb b/lib/rack/attack/path_normalizer.rb index fd5935f..635f9f2 100644 --- a/lib/rack/attack/path_normalizer.rb +++ b/lib/rack/attack/path_normalizer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class Rack::Attack # When using Rack::Attack with a Rails app, developers expect the request path # to be normalized. In particular, trailing slashes are stripped. diff --git a/lib/rack/attack/request.rb b/lib/rack/attack/request.rb index ee05f89..1cac479 100644 --- a/lib/rack/attack/request.rb +++ b/lib/rack/attack/request.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Rack::Attack::Request is the same as ::Rack::Request by default. # # This is a safe place to add custom helper methods to the request object diff --git a/lib/rack/attack/safelist.rb b/lib/rack/attack/safelist.rb index e548b0b..d335be2 100644 --- a/lib/rack/attack/safelist.rb +++ b/lib/rack/attack/safelist.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rack class Attack class Safelist < Check diff --git a/lib/rack/attack/store_proxy.rb b/lib/rack/attack/store_proxy.rb index 62a7e68..c9bde97 100644 --- a/lib/rack/attack/store_proxy.rb +++ b/lib/rack/attack/store_proxy.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rack class Attack module StoreProxy diff --git a/lib/rack/attack/store_proxy/dalli_proxy.rb b/lib/rack/attack/store_proxy/dalli_proxy.rb index ccaa8bf..f3fbc6c 100644 --- a/lib/rack/attack/store_proxy/dalli_proxy.rb +++ b/lib/rack/attack/store_proxy/dalli_proxy.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'delegate' module Rack diff --git a/lib/rack/attack/store_proxy/mem_cache_proxy.rb b/lib/rack/attack/store_proxy/mem_cache_proxy.rb index 4675789..8ed6583 100644 --- a/lib/rack/attack/store_proxy/mem_cache_proxy.rb +++ b/lib/rack/attack/store_proxy/mem_cache_proxy.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rack class Attack module StoreProxy 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 d6cbdfb..c7feaa7 100644 --- a/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb +++ b/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'delegate' module Rack diff --git a/lib/rack/attack/store_proxy/redis_store_proxy.rb b/lib/rack/attack/store_proxy/redis_store_proxy.rb index f9f2c5b..44d6625 100644 --- a/lib/rack/attack/store_proxy/redis_store_proxy.rb +++ b/lib/rack/attack/store_proxy/redis_store_proxy.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'delegate' module Rack diff --git a/lib/rack/attack/throttle.rb b/lib/rack/attack/throttle.rb index b89e114..c2e6cb3 100644 --- a/lib/rack/attack/throttle.rb +++ b/lib/rack/attack/throttle.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rack class Attack class Throttle diff --git a/lib/rack/attack/track.rb b/lib/rack/attack/track.rb index 123bfa4..dba16ca 100644 --- a/lib/rack/attack/track.rb +++ b/lib/rack/attack/track.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rack class Attack class Track diff --git a/lib/rack/attack/version.rb b/lib/rack/attack/version.rb index e4ec2ea..07a1d57 100644 --- a/lib/rack/attack/version.rb +++ b/lib/rack/attack/version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rack class Attack VERSION = '5.3.2' diff --git a/rack-attack.gemspec b/rack-attack.gemspec index dd5e628..e5828f4 100644 --- a/rack-attack.gemspec +++ b/rack-attack.gemspec @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +# frozen_string_literal: true lib = File.expand_path('../lib/', __FILE__) $:.unshift lib unless $:.include?(lib) @@ -27,7 +28,7 @@ Gem::Specification.new do |s| "source_code_uri" => "https://github.com/kickstarter/rack-attack" } - s.required_ruby_version = '>= 2.2' + s.required_ruby_version = '>= 2.3' s.add_runtime_dependency 'rack', ">= 1.0", "< 3" diff --git a/spec/acceptance/allow2ban_spec.rb b/spec/acceptance/allow2ban_spec.rb index 5495c05..6de18d0 100644 --- a/spec/acceptance/allow2ban_spec.rb +++ b/spec/acceptance/allow2ban_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../spec_helper" require "timecop" diff --git a/spec/acceptance/blocking_ip_spec.rb b/spec/acceptance/blocking_ip_spec.rb index 85dde63..cfb2282 100644 --- a/spec/acceptance/blocking_ip_spec.rb +++ b/spec/acceptance/blocking_ip_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../spec_helper" describe "Blocking an IP" do diff --git a/spec/acceptance/blocking_spec.rb b/spec/acceptance/blocking_spec.rb index 83b5202..db176c8 100644 --- a/spec/acceptance/blocking_spec.rb +++ b/spec/acceptance/blocking_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../spec_helper" describe "#blocklist" do diff --git a/spec/acceptance/blocking_subnet_spec.rb b/spec/acceptance/blocking_subnet_spec.rb index 7143445..1f417d7 100644 --- a/spec/acceptance/blocking_subnet_spec.rb +++ b/spec/acceptance/blocking_subnet_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../spec_helper" describe "Blocking an IP subnet" do diff --git a/spec/acceptance/cache_store_config_for_allow2ban_spec.rb b/spec/acceptance/cache_store_config_for_allow2ban_spec.rb index 4b6cb11..09fdab8 100644 --- a/spec/acceptance/cache_store_config_for_allow2ban_spec.rb +++ b/spec/acceptance/cache_store_config_for_allow2ban_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../spec_helper" describe "Cache store config when using allow2ban" do diff --git a/spec/acceptance/cache_store_config_for_fail2ban_spec.rb b/spec/acceptance/cache_store_config_for_fail2ban_spec.rb index 4f978a1..8052fc1 100644 --- a/spec/acceptance/cache_store_config_for_fail2ban_spec.rb +++ b/spec/acceptance/cache_store_config_for_fail2ban_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../spec_helper" describe "Cache store config when using fail2ban" do diff --git a/spec/acceptance/cache_store_config_for_throttle_spec.rb b/spec/acceptance/cache_store_config_for_throttle_spec.rb index 524048b..9be6e59 100644 --- a/spec/acceptance/cache_store_config_for_throttle_spec.rb +++ b/spec/acceptance/cache_store_config_for_throttle_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../spec_helper" describe "Cache store config when throttling without Rails" do diff --git a/spec/acceptance/cache_store_config_with_rails_spec.rb b/spec/acceptance/cache_store_config_with_rails_spec.rb index bd87828..3d9ac22 100644 --- a/spec/acceptance/cache_store_config_with_rails_spec.rb +++ b/spec/acceptance/cache_store_config_with_rails_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../spec_helper" require "minitest/stub_const" require "ostruct" diff --git a/spec/acceptance/customizing_blocked_response_spec.rb b/spec/acceptance/customizing_blocked_response_spec.rb index cf297b0..fd830c2 100644 --- a/spec/acceptance/customizing_blocked_response_spec.rb +++ b/spec/acceptance/customizing_blocked_response_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../spec_helper" describe "Customizing block responses" do diff --git a/spec/acceptance/customizing_throttled_response_spec.rb b/spec/acceptance/customizing_throttled_response_spec.rb index 61acc01..5c84979 100644 --- a/spec/acceptance/customizing_throttled_response_spec.rb +++ b/spec/acceptance/customizing_throttled_response_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../spec_helper" describe "Customizing throttled response" do diff --git a/spec/acceptance/extending_request_object_spec.rb b/spec/acceptance/extending_request_object_spec.rb index f6e0d0f..a4ea1a6 100644 --- a/spec/acceptance/extending_request_object_spec.rb +++ b/spec/acceptance/extending_request_object_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../spec_helper" describe "Extending the request object" do diff --git a/spec/acceptance/fail2ban_spec.rb b/spec/acceptance/fail2ban_spec.rb index 7fac437..fde67f9 100644 --- a/spec/acceptance/fail2ban_spec.rb +++ b/spec/acceptance/fail2ban_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../spec_helper" require "timecop" diff --git a/spec/acceptance/safelisting_ip_spec.rb b/spec/acceptance/safelisting_ip_spec.rb index c0c8203..bdf7d67 100644 --- a/spec/acceptance/safelisting_ip_spec.rb +++ b/spec/acceptance/safelisting_ip_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../spec_helper" describe "Safelist an IP" do diff --git a/spec/acceptance/safelisting_spec.rb b/spec/acceptance/safelisting_spec.rb index 420fc9a..dc4d82d 100644 --- a/spec/acceptance/safelisting_spec.rb +++ b/spec/acceptance/safelisting_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../spec_helper" describe "#safelist" do diff --git a/spec/acceptance/safelisting_subnet_spec.rb b/spec/acceptance/safelisting_subnet_spec.rb index 22c60f5..50d99f6 100644 --- a/spec/acceptance/safelisting_subnet_spec.rb +++ b/spec/acceptance/safelisting_subnet_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../spec_helper" describe "Safelisting an IP subnet" do diff --git a/spec/acceptance/stores/active_support_dalli_store_spec.rb b/spec/acceptance/stores/active_support_dalli_store_spec.rb index 440a860..45ecf3d 100644 --- a/spec/acceptance/stores/active_support_dalli_store_spec.rb +++ b/spec/acceptance/stores/active_support_dalli_store_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../../spec_helper" if defined?(::Dalli) 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 4a3ced6..86de47c 100644 --- a/spec/acceptance/stores/active_support_mem_cache_store_spec.rb +++ b/spec/acceptance/stores/active_support_mem_cache_store_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../../spec_helper" if defined?(::Dalli) diff --git a/spec/acceptance/stores/active_support_memory_store_spec.rb b/spec/acceptance/stores/active_support_memory_store_spec.rb index 94de429..8657aa2 100644 --- a/spec/acceptance/stores/active_support_memory_store_spec.rb +++ b/spec/acceptance/stores/active_support_memory_store_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../../spec_helper" require_relative "../../support/cache_store_helper" diff --git a/spec/acceptance/stores/active_support_redis_cache_store_pooled_spec.rb b/spec/acceptance/stores/active_support_redis_cache_store_pooled_spec.rb index 5295ba5..13da998 100644 --- a/spec/acceptance/stores/active_support_redis_cache_store_pooled_spec.rb +++ b/spec/acceptance/stores/active_support_redis_cache_store_pooled_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../../spec_helper" if defined?(::ConnectionPool) && defined?(::Redis) && defined?(::ActiveSupport::Cache::RedisCacheStore) diff --git a/spec/acceptance/stores/active_support_redis_cache_store_spec.rb b/spec/acceptance/stores/active_support_redis_cache_store_spec.rb index e6ecdda..4303a3f 100644 --- a/spec/acceptance/stores/active_support_redis_cache_store_spec.rb +++ b/spec/acceptance/stores/active_support_redis_cache_store_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../../spec_helper" if defined?(::Redis) && defined?(::ActiveSupport::Cache::RedisCacheStore) diff --git a/spec/acceptance/stores/active_support_redis_store_spec.rb b/spec/acceptance/stores/active_support_redis_store_spec.rb index 840a7d4..9aa7d08 100644 --- a/spec/acceptance/stores/active_support_redis_store_spec.rb +++ b/spec/acceptance/stores/active_support_redis_store_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../../spec_helper" if defined?(::ActiveSupport::Cache::RedisStore) diff --git a/spec/acceptance/stores/connection_pool_dalli_client_spec.rb b/spec/acceptance/stores/connection_pool_dalli_client_spec.rb index 15f4564..1ccf5d2 100644 --- a/spec/acceptance/stores/connection_pool_dalli_client_spec.rb +++ b/spec/acceptance/stores/connection_pool_dalli_client_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../../spec_helper" if defined?(::Dalli) && defined?(::ConnectionPool) diff --git a/spec/acceptance/stores/dalli_client_spec.rb b/spec/acceptance/stores/dalli_client_spec.rb index 48a2d9f..dedd9ab 100644 --- a/spec/acceptance/stores/dalli_client_spec.rb +++ b/spec/acceptance/stores/dalli_client_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../../spec_helper" if defined?(::Dalli) diff --git a/spec/acceptance/stores/redis_store_spec.rb b/spec/acceptance/stores/redis_store_spec.rb index 9c9fba5..82898e6 100644 --- a/spec/acceptance/stores/redis_store_spec.rb +++ b/spec/acceptance/stores/redis_store_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../../spec_helper" require_relative "../../support/cache_store_helper" diff --git a/spec/acceptance/throttling_spec.rb b/spec/acceptance/throttling_spec.rb index 947e4ed..719def8 100644 --- a/spec/acceptance/throttling_spec.rb +++ b/spec/acceptance/throttling_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../spec_helper" require "timecop" diff --git a/spec/acceptance/track_spec.rb b/spec/acceptance/track_spec.rb index 928cc31..5f72db8 100644 --- a/spec/acceptance/track_spec.rb +++ b/spec/acceptance/track_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../spec_helper" describe "#track" do diff --git a/spec/acceptance/track_throttle_spec.rb b/spec/acceptance/track_throttle_spec.rb index f3f8a80..1d2c5ab 100644 --- a/spec/acceptance/track_throttle_spec.rb +++ b/spec/acceptance/track_throttle_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative "../spec_helper" require "timecop" diff --git a/spec/allow2ban_spec.rb b/spec/allow2ban_spec.rb index 93564d2..074a231 100644 --- a/spec/allow2ban_spec.rb +++ b/spec/allow2ban_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative 'spec_helper' describe 'Rack::Attack.Allow2Ban' do diff --git a/spec/fail2ban_spec.rb b/spec/fail2ban_spec.rb index e1fe7ef..91242f7 100644 --- a/spec/fail2ban_spec.rb +++ b/spec/fail2ban_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative 'spec_helper' describe 'Rack::Attack.Fail2Ban' do diff --git a/spec/integration/offline_spec.rb b/spec/integration/offline_spec.rb index 6a6e925..1f2cbfa 100644 --- a/spec/integration/offline_spec.rb +++ b/spec/integration/offline_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'active_support/cache' require_relative '../spec_helper' diff --git a/spec/rack_attack_dalli_proxy_spec.rb b/spec/rack_attack_dalli_proxy_spec.rb index 9065354..7eb23f0 100644 --- a/spec/rack_attack_dalli_proxy_spec.rb +++ b/spec/rack_attack_dalli_proxy_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative 'spec_helper' describe Rack::Attack::StoreProxy::DalliProxy do diff --git a/spec/rack_attack_instrumentation_spec.rb b/spec/rack_attack_instrumentation_spec.rb index 88883d0..1bd51a3 100644 --- a/spec/rack_attack_instrumentation_spec.rb +++ b/spec/rack_attack_instrumentation_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # ActiveSupport::Subscribers added in ~> 4.0.2.0 if ActiveSupport::VERSION::MAJOR > 3 require_relative 'spec_helper' diff --git a/spec/rack_attack_path_normalizer_spec.rb b/spec/rack_attack_path_normalizer_spec.rb index 1c5b66e..961491d 100644 --- a/spec/rack_attack_path_normalizer_spec.rb +++ b/spec/rack_attack_path_normalizer_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative 'spec_helper' describe Rack::Attack::PathNormalizer do diff --git a/spec/rack_attack_request_spec.rb b/spec/rack_attack_request_spec.rb index 41dfd82..8d4d27f 100644 --- a/spec/rack_attack_request_spec.rb +++ b/spec/rack_attack_request_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative 'spec_helper' describe 'Rack::Attack' do diff --git a/spec/rack_attack_spec.rb b/spec/rack_attack_spec.rb index 3869b71..da5aa2e 100644 --- a/spec/rack_attack_spec.rb +++ b/spec/rack_attack_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative 'spec_helper' describe 'Rack::Attack' do diff --git a/spec/rack_attack_throttle_spec.rb b/spec/rack_attack_throttle_spec.rb index b32891a..2735b7a 100644 --- a/spec/rack_attack_throttle_spec.rb +++ b/spec/rack_attack_throttle_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative 'spec_helper' describe 'Rack::Attack.throttle' do diff --git a/spec/rack_attack_track_spec.rb b/spec/rack_attack_track_spec.rb index 352561d..39c6ab9 100644 --- a/spec/rack_attack_track_spec.rb +++ b/spec/rack_attack_track_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative 'spec_helper' describe 'Rack::Attack.track' do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 20c6d47..72b2797 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "rubygems" require "bundler/setup" diff --git a/spec/support/cache_store_helper.rb b/spec/support/cache_store_helper.rb index 2ca7606..7655949 100644 --- a/spec/support/cache_store_helper.rb +++ b/spec/support/cache_store_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class Minitest::Spec def self.it_works_for_cache_backed_features it "works for throttle" do From 1c1ee918e1f2e8cb4ddef1a772c6ae9611f1c55a Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Thu, 28 Jun 2018 17:16:33 -0300 Subject: [PATCH 126/129] Remove previously deprecated public methods --- lib/rack/attack.rb | 40 ---------------------------------------- spec/rack_attack_spec.rb | 21 --------------------- 2 files changed, 61 deletions(-) diff --git a/lib/rack/attack.rb b/lib/rack/attack.rb index 66638a4..4f2241c 100644 --- a/lib/rack/attack.rb +++ b/lib/rack/attack.rb @@ -29,11 +29,6 @@ class Rack::Attack self.safelists[name] = Safelist.new(name, block) end - def whitelist(name, &block) - warn "[DEPRECATION] 'Rack::Attack.whitelist' is deprecated. Please use 'safelist' instead." - safelist(name, &block) - end - def blocklist(name, &block) self.blocklists[name] = Blocklist.new(name, block) end @@ -50,11 +45,6 @@ class Rack::Attack @ip_safelists << Safelist.new(nil, ip_safelist_proc) end - def blacklist(name, &block) - warn "[DEPRECATION] 'Rack::Attack.blacklist' is deprecated. Please use 'blocklist' instead." - blocklist(name, &block) - end - def throttle(name, options, &block) self.throttles[name] = Throttle.new(name, options, block) end @@ -71,36 +61,16 @@ class Rack::Attack def tracks; @tracks ||= {}; end - def whitelists - warn "[DEPRECATION] 'Rack::Attack.whitelists' is deprecated. Please use 'safelists' instead." - safelists - end - - def blacklists - warn "[DEPRECATION] 'Rack::Attack.blacklists' is deprecated. Please use 'blocklists' instead." - blocklists - end - def safelisted?(request) ip_safelists.any? { |safelist| safelist.matched_by?(request) } || safelists.any? { |_name, safelist| safelist.matched_by?(request) } end - def whitelisted?(request) - warn "[DEPRECATION] 'Rack::Attack.whitelisted?' is deprecated. Please use 'safelisted?' instead." - safelisted?(request) - end - def blocklisted?(request) ip_blocklists.any? { |blocklist| blocklist.matched_by?(request) } || blocklists.any? { |_name, blocklist| blocklist.matched_by?(request) } end - def blacklisted?(request) - warn "[DEPRECATION] 'Rack::Attack.blacklisted?' is deprecated. Please use 'blocklisted?' instead." - blocklisted?(request) - end - def throttled?(request) throttles.any? do |_name, throttle| throttle.matched_by?(request) @@ -132,16 +102,6 @@ class Rack::Attack clear_configuration end - def blacklisted_response=(res) - warn "[DEPRECATION] 'Rack::Attack.blacklisted_response=' is deprecated. Please use 'blocklisted_response=' instead." - self.blocklisted_response = res - end - - def blacklisted_response - warn "[DEPRECATION] 'Rack::Attack.blacklisted_response' is deprecated. Please use 'blocklisted_response' instead." - blocklisted_response - end - private def ip_blocklists diff --git a/spec/rack_attack_spec.rb b/spec/rack_attack_spec.rb index 3869b71..c31b670 100644 --- a/spec/rack_attack_spec.rb +++ b/spec/rack_attack_spec.rb @@ -24,13 +24,6 @@ describe 'Rack::Attack' do Rack::Attack.blocklists.key?("ip #{@bad_ip}").must_equal true } - it('has a blacklist with a deprication warning') { - _, stderror = capture_io do - Rack::Attack.blacklists.key?("ip #{@bad_ip}").must_equal true - end - assert_match "[DEPRECATION] 'Rack::Attack.blacklists' is deprecated. Please use 'blocklists' instead.", stderror - } - describe "a bad request" do before { get '/', {}, 'REMOTE_ADDR' => @bad_ip } @@ -55,13 +48,6 @@ describe 'Rack::Attack' do it('has a safelist') { Rack::Attack.safelists.key?("good ua") } - it('has a whitelist with a deprication warning') { - _, stderror = capture_io do - Rack::Attack.whitelists.key?("good ua") - end - assert_match "[DEPRECATION] 'Rack::Attack.whitelists' is deprecated. Please use 'safelists' instead.", stderror - } - describe "with a request match both safelist & blocklist" do before { get '/', {}, 'REMOTE_ADDR' => @bad_ip, 'HTTP_USER_AGENT' => @good_ua } @@ -80,13 +66,6 @@ describe 'Rack::Attack' do it 'should exist' do Rack::Attack.blocklisted_response.must_respond_to :call end - - it 'should give a deprication warning for blacklisted_response' do - _, stderror = capture_io do - Rack::Attack.blacklisted_response - end - assert_match "[DEPRECATION] 'Rack::Attack.blacklisted_response' is deprecated. Please use 'blocklisted_response' instead.", stderror - end end describe '#throttled_response' do From aca4078089280c5eb95418b2b6eaa287efd8aae8 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Thu, 21 Jun 2018 10:59:23 -0300 Subject: [PATCH 127/129] Remove unnecessary requires in spec_helper --- spec/spec_helper.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 72b2797..b46161e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "rubygems" require "bundler/setup" require "minitest/autorun" From 85c4c085c994fef027b68ec5c6bed349f1d17d9b Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Fri, 29 Jun 2018 15:27:20 -0300 Subject: [PATCH 128/129] Remove duplicated #initialize --- lib/rack/attack/store_proxy/redis_store_proxy.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/rack/attack/store_proxy/redis_store_proxy.rb b/lib/rack/attack/store_proxy/redis_store_proxy.rb index b932b46..4316a9b 100644 --- a/lib/rack/attack/store_proxy/redis_store_proxy.rb +++ b/lib/rack/attack/store_proxy/redis_store_proxy.rb @@ -17,10 +17,6 @@ module Rack defined?(::Redis::Store) && store.is_a?(::Redis::Store) end - def initialize(store) - super(store) - end - def read(key) get(key, raw: true) rescue Redis::BaseError From eb07d9789fa6da44f0d3d73626aba37183cb0937 Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Fri, 29 Jun 2018 15:27:36 -0300 Subject: [PATCH 129/129] Prefer Gem::Version for version comparisons --- lib/rack/attack/store_proxy/redis_store_proxy.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/rack/attack/store_proxy/redis_store_proxy.rb b/lib/rack/attack/store_proxy/redis_store_proxy.rb index 4316a9b..a7ac81a 100644 --- a/lib/rack/attack/store_proxy/redis_store_proxy.rb +++ b/lib/rack/attack/store_proxy/redis_store_proxy.rb @@ -7,8 +7,9 @@ module Rack module StoreProxy class RedisStoreProxy < SimpleDelegator def initialize(*args) - major_version = Redis::VERSION.split('.').first.to_i - warn 'RackAttack requires Redis gem >= 3.0.0.' if major_version < 3 + if Gem::Version.new(Redis::VERSION) < Gem::Version.new("3") + warn 'RackAttack requires Redis gem >= 3.0.0.' + end super(*args) end