rack-attack 6.4.0 → 6.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e7d44de650fae1c83d5a3da49dc8f304e44280f72bd209d3f78643b90d573bd8
4
- data.tar.gz: a39d0270489617a8c0a49e01868c24cc87311e80457fbc104c86e45d29978f51
3
+ metadata.gz: 81f6cce465b8441782ffaa3d446e558979de6a2ceadd9d43499b7977fa61586b
4
+ data.tar.gz: a2ee9b82f1144d483e7d26a893594ab6cada4dc52d98eb08d7615a38b49face8
5
5
  SHA512:
6
- metadata.gz: 7d9d965cc672bba8ab2b9f333746e32091363d6b65bf290104c248799a811f272ad8388e7f7b3d870d382e9c80a1003a300f9380c2d8082195972817146a281d
7
- data.tar.gz: fbfa381116824ea4de492b66408d15bd708692a74275548c9b167868c0bee566f79a216046213c43a8eb3117b869e86ac99f989e5e4c045c30267db2981b2c6b
6
+ metadata.gz: b90fa7841d1e5319b820d4bca9c1fc8d266fc98c06f2a936ce6d31949d9fdd7ddab9183f82c5bbbc31e3e4280bba8e786bbe155f146fdcfdbf53ad263bfec63f
7
+ data.tar.gz: c91be495fbf25444632a1734f40f9fa85b35bf19d921a129ca402aceadf0d30b2a7d5797283ca7ac68e8f323569fa3edc1be98bcecd2c72aa15c3956dd611455
data/README.md CHANGED
@@ -71,12 +71,7 @@ Or install it yourself as:
71
71
 
72
72
  Then tell your ruby web application to use rack-attack as a middleware.
73
73
 
74
- a) For __rails__ applications with versions >= 5.1 it is used by default. For older rails versions you should enable it explicitly:
75
- ```ruby
76
- # In config/application.rb
77
-
78
- config.middleware.use Rack::Attack
79
- ```
74
+ a) For __rails__ applications it is used by default.
80
75
 
81
76
  You can disable it permanently (like for specific environment) or temporarily (can be useful for specific test cases) by writing:
82
77
 
data/lib/rack/attack.rb CHANGED
@@ -6,6 +6,12 @@ require 'rack/attack/cache'
6
6
  require 'rack/attack/configuration'
7
7
  require 'rack/attack/path_normalizer'
8
8
  require 'rack/attack/request'
9
+ require 'rack/attack/store_proxy/dalli_proxy'
10
+ require 'rack/attack/store_proxy/mem_cache_store_proxy'
11
+ require 'rack/attack/store_proxy/redis_proxy'
12
+ require 'rack/attack/store_proxy/redis_store_proxy'
13
+ require 'rack/attack/store_proxy/redis_cache_store_proxy'
14
+ require 'rack/attack/store_proxy/active_support_redis_store_proxy'
9
15
 
10
16
  require 'rack/attack/railtie' if defined?(::Rails)
11
17
 
@@ -21,18 +27,11 @@ module Rack
21
27
  autoload :Safelist, 'rack/attack/safelist'
22
28
  autoload :Blocklist, 'rack/attack/blocklist'
23
29
  autoload :Track, 'rack/attack/track'
24
- autoload :StoreProxy, 'rack/attack/store_proxy'
25
- autoload :DalliProxy, 'rack/attack/store_proxy/dalli_proxy'
26
- autoload :MemCacheStoreProxy, 'rack/attack/store_proxy/mem_cache_store_proxy'
27
- autoload :RedisProxy, 'rack/attack/store_proxy/redis_proxy'
28
- autoload :RedisStoreProxy, 'rack/attack/store_proxy/redis_store_proxy'
29
- autoload :RedisCacheStoreProxy, 'rack/attack/store_proxy/redis_cache_store_proxy'
30
- autoload :ActiveSupportRedisStoreProxy, 'rack/attack/store_proxy/active_support_redis_store_proxy'
31
30
  autoload :Fail2Ban, 'rack/attack/fail2ban'
32
31
  autoload :Allow2Ban, 'rack/attack/allow2ban'
33
32
 
34
33
  class << self
35
- attr_accessor :enabled, :notifier
34
+ attr_accessor :enabled, :notifier, :throttle_discriminator_normalizer
36
35
  attr_reader :configuration
37
36
 
38
37
  def instrument(request)
@@ -84,6 +83,9 @@ module Rack
84
83
  # Set defaults
85
84
  @enabled = true
86
85
  @notifier = ActiveSupport::Notifications if defined?(ActiveSupport::Notifications)
86
+ @throttle_discriminator_normalizer = lambda do |discriminator|
87
+ discriminator.to_s.strip.downcase
88
+ end
87
89
  @configuration = Configuration.new
88
90
 
89
91
  attr_reader :configuration
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'delegate'
4
+
5
+ module Rack
6
+ class Attack
7
+ class BaseProxy < SimpleDelegator
8
+ class << self
9
+ def proxies
10
+ @@proxies ||= []
11
+ end
12
+
13
+ def inherited(klass)
14
+ proxies << klass
15
+ end
16
+
17
+ def lookup(store)
18
+ proxies.find { |proxy| proxy.handle?(store) }
19
+ end
20
+
21
+ def handle?(_store)
22
+ raise NotImplementedError
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -14,7 +14,12 @@ module Rack
14
14
  attr_reader :store
15
15
 
16
16
  def store=(store)
17
- @store = StoreProxy.build(store)
17
+ @store =
18
+ if (proxy = BaseProxy.lookup(store))
19
+ proxy.new(store)
20
+ else
21
+ store
22
+ end
18
23
  end
19
24
 
20
25
  def count(unprefixed_key, period)
@@ -4,9 +4,7 @@ module Rack
4
4
  class Attack
5
5
  class Railtie < ::Rails::Railtie
6
6
  initializer "rack-attack.middleware" do |app|
7
- if Gem::Version.new(::Rails::VERSION::STRING) >= Gem::Version.new("5.1")
8
- app.middleware.use(Rack::Attack)
9
- end
7
+ app.middleware.use(Rack::Attack)
10
8
  end
11
9
  end
12
10
  end
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'delegate'
3
+ require 'rack/attack/base_proxy'
4
4
 
5
5
  module Rack
6
6
  class Attack
7
7
  module StoreProxy
8
- class ActiveSupportRedisStoreProxy < SimpleDelegator
8
+ class ActiveSupportRedisStoreProxy < BaseProxy
9
9
  def self.handle?(store)
10
10
  defined?(::Redis) &&
11
11
  defined?(::ActiveSupport::Cache::RedisStore) &&
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'delegate'
3
+ require 'rack/attack/base_proxy'
4
4
 
5
5
  module Rack
6
6
  class Attack
7
7
  module StoreProxy
8
- class DalliProxy < SimpleDelegator
8
+ class DalliProxy < BaseProxy
9
9
  def self.handle?(store)
10
10
  return false unless defined?(::Dalli)
11
11
 
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'delegate'
3
+ require 'rack/attack/base_proxy'
4
4
 
5
5
  module Rack
6
6
  class Attack
7
7
  module StoreProxy
8
- class MemCacheStoreProxy < SimpleDelegator
8
+ class MemCacheStoreProxy < BaseProxy
9
9
  def self.handle?(store)
10
10
  defined?(::Dalli) &&
11
11
  defined?(::ActiveSupport::Cache::MemCacheStore) &&
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'delegate'
3
+ require 'rack/attack/base_proxy'
4
4
 
5
5
  module Rack
6
6
  class Attack
7
7
  module StoreProxy
8
- class RedisCacheStoreProxy < SimpleDelegator
8
+ class RedisCacheStoreProxy < BaseProxy
9
9
  def self.handle?(store)
10
10
  store.class.name == "ActiveSupport::Cache::RedisCacheStore"
11
11
  end
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'delegate'
3
+ require 'rack/attack/base_proxy'
4
4
 
5
5
  module Rack
6
6
  class Attack
7
7
  module StoreProxy
8
- class RedisProxy < SimpleDelegator
8
+ class RedisProxy < BaseProxy
9
9
  def initialize(*args)
10
10
  if Gem::Version.new(Redis::VERSION) < Gem::Version.new("3")
11
11
  warn 'RackAttack requires Redis gem >= 3.0.0.'
@@ -15,7 +15,7 @@ module Rack
15
15
  end
16
16
 
17
17
  def self.handle?(store)
18
- defined?(::Redis) && store.is_a?(::Redis)
18
+ defined?(::Redis) && store.class == ::Redis
19
19
  end
20
20
 
21
21
  def read(key)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'delegate'
3
+ require 'rack/attack/store_proxy/redis_proxy'
4
4
 
5
5
  module Rack
6
6
  class Attack
@@ -23,8 +23,7 @@ module Rack
23
23
  end
24
24
 
25
25
  def matched_by?(request)
26
- discriminator = block.call(request)
27
-
26
+ discriminator = discriminator_for(request)
28
27
  return false unless discriminator
29
28
 
30
29
  current_period = period_for(request)
@@ -50,6 +49,14 @@ module Rack
50
49
 
51
50
  private
52
51
 
52
+ def discriminator_for(request)
53
+ discriminator = block.call(request)
54
+ if discriminator && Rack::Attack.throttle_discriminator_normalizer
55
+ discriminator = Rack::Attack.throttle_discriminator_normalizer.call(discriminator)
56
+ end
57
+ discriminator
58
+ end
59
+
53
60
  def period_for(request)
54
61
  period.respond_to?(:call) ? period.call(request) : period
55
62
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Rack
4
4
  class Attack
5
- VERSION = '6.4.0'
5
+ VERSION = '6.5.0'
6
6
  end
7
7
  end
@@ -12,24 +12,9 @@ if defined?(Rails)
12
12
  end
13
13
  end
14
14
 
15
- if Gem::Version.new(Rails::VERSION::STRING) >= Gem::Version.new("5.1")
16
- it "is used by default" do
17
- @app.initialize!
18
- assert_equal 1, @app.middleware.count(Rack::Attack)
19
- end
20
-
21
- it "is not added when it was explicitly deleted" do
22
- @app.config.middleware.delete(Rack::Attack)
23
- @app.initialize!
24
- refute @app.middleware.include?(Rack::Attack)
25
- end
26
- end
27
-
28
- if Gem::Version.new(Rails::VERSION::STRING) < Gem::Version.new("5.1")
29
- it "is not used by default" do
30
- @app.initialize!
31
- assert_equal 0, @app.middleware.count(Rack::Attack)
32
- end
15
+ it "is used by default" do
16
+ @app.initialize!
17
+ assert @app.middleware.include?(Rack::Attack)
33
18
  end
34
19
  end
35
20
  end
@@ -144,3 +144,47 @@ describe 'Rack::Attack.throttle with block retuning nil' do
144
144
  end
145
145
  end
146
146
  end
147
+
148
+ describe 'Rack::Attack.throttle with throttle_discriminator_normalizer' do
149
+ before do
150
+ @period = 60
151
+ @emails = [
152
+ "person@example.com",
153
+ "PERSON@example.com ",
154
+ " person@example.com\r\n ",
155
+ ]
156
+ Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
157
+ Rack::Attack.throttle('logins/email', limit: 4, period: @period) do |req|
158
+ if req.path == '/login' && req.post?
159
+ req.params['email']
160
+ end
161
+ end
162
+ end
163
+
164
+ it 'should not differentiate requests when throttle_discriminator_normalizer is enabled' do
165
+ post_logins
166
+ key = "rack::attack:#{Time.now.to_i / @period}:logins/email:person@example.com"
167
+ _(Rack::Attack.cache.store.read(key)).must_equal 3
168
+ end
169
+
170
+ it 'should differentiate requests when throttle_discriminator_normalizer is disabled' do
171
+ begin
172
+ prev = Rack::Attack.throttle_discriminator_normalizer
173
+ Rack::Attack.throttle_discriminator_normalizer = nil
174
+
175
+ post_logins
176
+ @emails.each do |email|
177
+ key = "rack::attack:#{Time.now.to_i / @period}:logins/email:#{email}"
178
+ _(Rack::Attack.cache.store.read(key)).must_equal 1
179
+ end
180
+ ensure
181
+ Rack::Attack.throttle_discriminator_normalizer = prev
182
+ end
183
+ end
184
+
185
+ def post_logins
186
+ @emails.each do |email|
187
+ post '/login', email: email
188
+ end
189
+ end
190
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-attack
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.4.0
4
+ version: 6.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron Suggs
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-01-24 00:00:00.000000000 Z
11
+ date: 2021-02-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -206,6 +206,7 @@ files:
206
206
  - Rakefile
207
207
  - lib/rack/attack.rb
208
208
  - lib/rack/attack/allow2ban.rb
209
+ - lib/rack/attack/base_proxy.rb
209
210
  - lib/rack/attack/blocklist.rb
210
211
  - lib/rack/attack/cache.rb
211
212
  - lib/rack/attack/check.rb
@@ -215,7 +216,6 @@ files:
215
216
  - lib/rack/attack/railtie.rb
216
217
  - lib/rack/attack/request.rb
217
218
  - lib/rack/attack/safelist.rb
218
- - lib/rack/attack/store_proxy.rb
219
219
  - lib/rack/attack/store_proxy/active_support_redis_store_proxy.rb
220
220
  - lib/rack/attack/store_proxy/dalli_proxy.rb
221
221
  - lib/rack/attack/store_proxy/mem_cache_store_proxy.rb
@@ -290,7 +290,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
290
290
  - !ruby/object:Gem::Version
291
291
  version: '0'
292
292
  requirements: []
293
- rubygems_version: 3.2.6
293
+ rubygems_version: 3.2.8
294
294
  signing_key:
295
295
  specification_version: 4
296
296
  summary: Block & throttle abusive requests
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Rack
4
- class Attack
5
- module StoreProxy
6
- PROXIES = [
7
- DalliProxy,
8
- MemCacheStoreProxy,
9
- RedisStoreProxy,
10
- RedisProxy,
11
- RedisCacheStoreProxy,
12
- ActiveSupportRedisStoreProxy
13
- ].freeze
14
-
15
- def self.build(store)
16
- klass = PROXIES.find { |proxy| proxy.handle?(store) }
17
- klass ? klass.new(store) : store
18
- end
19
- end
20
- end
21
- end