rack-attack 6.2.2 → 6.3.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: 5479926deb14a57eebadf44bc0450c119c10c57844e6e887a6536a0dd79ddcc3
4
- data.tar.gz: a7c6d3162887c8d7e29b7ba390c281be9757c7f9a022c7e705e2029f3d51d091
3
+ metadata.gz: cba47e380843d184fd3df1af08b252aca3fc2411de7ccbd62a9b7da6ee933b72
4
+ data.tar.gz: 0dc5300a553830ca7e1cfa84de814fd743e608b9ec0140fd6b1145c421085ba7
5
5
  SHA512:
6
- metadata.gz: 1ff52d16e17933058af32dbe0aeeb704914fb28331f9df3b2bd90fc1820f32f7cd60c9f254506fea6bf1845f850fa72f77dff57fb4a5ecaf4ce619497688bb00
7
- data.tar.gz: 6f9a3f5adfddc7cd55274c24a7c9a4ff5e498324421abd3ef43cc5707f478a79824c0a5fb14d2a189128a22f42ffc1b144e06bd3bfbfb9e35de60f406edf6793
6
+ metadata.gz: 71fd5eace9c851dab06317f3f4f4f28a2cbc20dd20c663228fbd073a052b4f30c4de87a06109ce9cf6b7395fc547b0c99b6f4e579285a699be40a93a9511452f
7
+ data.tar.gz: 5139f0be932f94273dba2d8d59ccbcb0e14ca92656b68d126099a124f04160c2c426e72f330b9e3c5dd8ed560f2fb292aa68dde2c32f2238d36c8c7e95422d01
data/README.md CHANGED
@@ -342,6 +342,11 @@ end
342
342
  While Rack::Attack's primary focus is minimizing harm from abusive clients, it
343
343
  can also be used to return rate limit data that's helpful for well-behaved clients.
344
344
 
345
+ If you want to return to user how many seconds to wait until he can start sending requests again, this can be done through enabling `Retry-After` header:
346
+ ```ruby
347
+ Rack::Attack.throttled_response_retry_after_header = true
348
+ ```
349
+
345
350
  Here's an example response that includes conventional `RateLimit-*` headers:
346
351
 
347
352
  ```ruby
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -2,9 +2,10 @@
2
2
 
3
3
  require 'rack'
4
4
  require 'forwardable'
5
+ require 'rack/attack/cache'
6
+ require 'rack/attack/configuration'
5
7
  require 'rack/attack/path_normalizer'
6
8
  require 'rack/attack/request'
7
- require "ipaddr"
8
9
 
9
10
  require 'rack/attack/railtie' if defined?(::Rails)
10
11
 
@@ -13,8 +14,8 @@ module Rack
13
14
  class Error < StandardError; end
14
15
  class MisconfiguredStoreError < Error; end
15
16
  class MissingStoreError < Error; end
17
+ class IncompatibleStoreError < Error; end
16
18
 
17
- autoload :Cache, 'rack/attack/cache'
18
19
  autoload :Check, 'rack/attack/check'
19
20
  autoload :Throttle, 'rack/attack/throttle'
20
21
  autoload :Safelist, 'rack/attack/safelist'
@@ -31,82 +32,8 @@ module Rack
31
32
  autoload :Allow2Ban, 'rack/attack/allow2ban'
32
33
 
33
34
  class << self
34
- attr_accessor :enabled, :notifier, :blocklisted_response, :throttled_response,
35
- :anonymous_blocklists, :anonymous_safelists
36
-
37
- def safelist(name = nil, &block)
38
- safelist = Safelist.new(name, &block)
39
-
40
- if name
41
- safelists[name] = safelist
42
- else
43
- anonymous_safelists << safelist
44
- end
45
- end
46
-
47
- def blocklist(name = nil, &block)
48
- blocklist = Blocklist.new(name, &block)
49
-
50
- if name
51
- blocklists[name] = blocklist
52
- else
53
- anonymous_blocklists << blocklist
54
- end
55
- end
56
-
57
- def blocklist_ip(ip_address)
58
- anonymous_blocklists << Blocklist.new { |request| IPAddr.new(ip_address).include?(IPAddr.new(request.ip)) }
59
- end
60
-
61
- def safelist_ip(ip_address)
62
- anonymous_safelists << Safelist.new { |request| IPAddr.new(ip_address).include?(IPAddr.new(request.ip)) }
63
- end
64
-
65
- def throttle(name, options, &block)
66
- throttles[name] = Throttle.new(name, options, &block)
67
- end
68
-
69
- def track(name, options = {}, &block)
70
- tracks[name] = Track.new(name, options, &block)
71
- end
72
-
73
- def safelists
74
- @safelists ||= {}
75
- end
76
-
77
- def blocklists
78
- @blocklists ||= {}
79
- end
80
-
81
- def throttles
82
- @throttles ||= {}
83
- end
84
-
85
- def tracks
86
- @tracks ||= {}
87
- end
88
-
89
- def safelisted?(request)
90
- anonymous_safelists.any? { |safelist| safelist.matched_by?(request) } ||
91
- safelists.any? { |_name, safelist| safelist.matched_by?(request) }
92
- end
93
-
94
- def blocklisted?(request)
95
- anonymous_blocklists.any? { |blocklist| blocklist.matched_by?(request) } ||
96
- blocklists.any? { |_name, blocklist| blocklist.matched_by?(request) }
97
- end
98
-
99
- def throttled?(request)
100
- throttles.any? do |_name, throttle|
101
- throttle.matched_by?(request)
102
- end
103
- end
104
-
105
- def tracked?(request)
106
- tracks.each_value do |track|
107
- track.matched_by?(request)
108
- end
109
- end
35
+ attr_accessor :enabled, :notifier
36
+ attr_reader :configuration
110
37
 
111
38
  def instrument(request)
112
39
  if notifier
@@ -122,34 +49,48 @@ module Rack
122
49
  @cache ||= Cache.new
123
50
  end
124
51
 
125
- def clear_configuration
126
- @safelists = {}
127
- @blocklists = {}
128
- @throttles = {}
129
- @tracks = {}
130
- self.anonymous_blocklists = []
131
- self.anonymous_safelists = []
132
- end
133
-
134
52
  def clear!
135
53
  warn "[DEPRECATION] Rack::Attack.clear! is deprecated. Please use Rack::Attack.clear_configuration instead"
136
- clear_configuration
137
- end
54
+ @configuration.clear_configuration
55
+ end
56
+
57
+ def reset!
58
+ cache.reset!
59
+ end
60
+
61
+ extend Forwardable
62
+ def_delegators(
63
+ :@configuration,
64
+ :safelist,
65
+ :blocklist,
66
+ :blocklist_ip,
67
+ :safelist_ip,
68
+ :throttle,
69
+ :track,
70
+ :blocklisted_response,
71
+ :blocklisted_response=,
72
+ :throttled_response,
73
+ :throttled_response=,
74
+ :throttled_response_retry_after_header,
75
+ :throttled_response_retry_after_header=,
76
+ :clear_configuration,
77
+ :safelists,
78
+ :blocklists,
79
+ :throttles,
80
+ :tracks
81
+ )
138
82
  end
139
83
 
140
84
  # Set defaults
141
85
  @enabled = true
142
- @anonymous_blocklists = []
143
- @anonymous_safelists = []
144
86
  @notifier = ActiveSupport::Notifications if defined?(ActiveSupport::Notifications)
145
- @blocklisted_response = lambda { |_env| [403, { 'Content-Type' => 'text/plain' }, ["Forbidden\n"]] }
146
- @throttled_response = lambda do |env|
147
- retry_after = (env['rack.attack.match_data'] || {})[:period]
148
- [429, { 'Content-Type' => 'text/plain', 'Retry-After' => retry_after.to_s }, ["Retry later\n"]]
149
- end
87
+ @configuration = Configuration.new
88
+
89
+ attr_reader :configuration
150
90
 
151
91
  def initialize(app)
152
92
  @app = app
93
+ @configuration = self.class.configuration
153
94
  end
154
95
 
155
96
  def call(env)
@@ -159,19 +100,16 @@ module Rack
159
100
  env['PATH_INFO'] = PathNormalizer.normalize_path(env['PATH_INFO'])
160
101
  request = Rack::Attack::Request.new(env)
161
102
 
162
- if safelisted?(request)
103
+ if configuration.safelisted?(request)
163
104
  @app.call(env)
164
- elsif blocklisted?(request)
165
- self.class.blocklisted_response.call(env)
166
- elsif throttled?(request)
167
- self.class.throttled_response.call(env)
105
+ elsif configuration.blocklisted?(request)
106
+ configuration.blocklisted_response.call(env)
107
+ elsif configuration.throttled?(request)
108
+ configuration.throttled_response.call(env)
168
109
  else
169
- tracked?(request)
110
+ configuration.tracked?(request)
170
111
  @app.call(env)
171
112
  end
172
113
  end
173
-
174
- extend Forwardable
175
- def_delegators self, :safelisted?, :blocklisted?, :throttled?, :tracked?
176
114
  end
177
115
  end
@@ -41,6 +41,17 @@ module Rack
41
41
  store.delete("#{prefix}:#{unprefixed_key}")
42
42
  end
43
43
 
44
+ def reset!
45
+ if store.respond_to?(:delete_matched)
46
+ store.delete_matched("#{prefix}*")
47
+ else
48
+ raise(
49
+ Rack::Attack::IncompatibleStoreError,
50
+ "Configured store #{store.class.name} doesn't respond to #delete_matched method"
51
+ )
52
+ end
53
+ end
54
+
44
55
  private
45
56
 
46
57
  def key_and_expiry(unprefixed_key, period)
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ipaddr"
4
+
5
+ module Rack
6
+ class Attack
7
+ class Configuration
8
+ DEFAULT_BLOCKLISTED_RESPONSE = lambda { |_env| [403, { 'Content-Type' => 'text/plain' }, ["Forbidden\n"]] }
9
+
10
+ DEFAULT_THROTTLED_RESPONSE = lambda do |env|
11
+ if Rack::Attack.configuration.throttled_response_retry_after_header
12
+ match_data = env['rack.attack.match_data']
13
+ now = match_data[:epoch_time]
14
+ retry_after = match_data[:period] - (now % match_data[:period])
15
+
16
+ [429, { 'Content-Type' => 'text/plain', 'Retry-After' => retry_after.to_s }, ["Retry later\n"]]
17
+ else
18
+ [429, { 'Content-Type' => 'text/plain' }, ["Retry later\n"]]
19
+ end
20
+ end
21
+
22
+ attr_reader :safelists, :blocklists, :throttles, :anonymous_blocklists, :anonymous_safelists
23
+ attr_accessor :blocklisted_response, :throttled_response, :throttled_response_retry_after_header
24
+
25
+ def initialize
26
+ set_defaults
27
+ end
28
+
29
+ def safelist(name = nil, &block)
30
+ safelist = Safelist.new(name, &block)
31
+
32
+ if name
33
+ @safelists[name] = safelist
34
+ else
35
+ @anonymous_safelists << safelist
36
+ end
37
+ end
38
+
39
+ def blocklist(name = nil, &block)
40
+ blocklist = Blocklist.new(name, &block)
41
+
42
+ if name
43
+ @blocklists[name] = blocklist
44
+ else
45
+ @anonymous_blocklists << blocklist
46
+ end
47
+ end
48
+
49
+ def blocklist_ip(ip_address)
50
+ @anonymous_blocklists << Blocklist.new { |request| IPAddr.new(ip_address).include?(IPAddr.new(request.ip)) }
51
+ end
52
+
53
+ def safelist_ip(ip_address)
54
+ @anonymous_safelists << Safelist.new { |request| IPAddr.new(ip_address).include?(IPAddr.new(request.ip)) }
55
+ end
56
+
57
+ def throttle(name, options, &block)
58
+ @throttles[name] = Throttle.new(name, options, &block)
59
+ end
60
+
61
+ def track(name, options = {}, &block)
62
+ @tracks[name] = Track.new(name, options, &block)
63
+ end
64
+
65
+ def safelisted?(request)
66
+ @anonymous_safelists.any? { |safelist| safelist.matched_by?(request) } ||
67
+ @safelists.any? { |_name, safelist| safelist.matched_by?(request) }
68
+ end
69
+
70
+ def blocklisted?(request)
71
+ @anonymous_blocklists.any? { |blocklist| blocklist.matched_by?(request) } ||
72
+ @blocklists.any? { |_name, blocklist| blocklist.matched_by?(request) }
73
+ end
74
+
75
+ def throttled?(request)
76
+ @throttles.any? do |_name, throttle|
77
+ throttle.matched_by?(request)
78
+ end
79
+ end
80
+
81
+ def tracked?(request)
82
+ @tracks.each_value do |track|
83
+ track.matched_by?(request)
84
+ end
85
+ end
86
+
87
+ def clear_configuration
88
+ set_defaults
89
+ end
90
+
91
+ private
92
+
93
+ def set_defaults
94
+ @safelists = {}
95
+ @blocklists = {}
96
+ @throttles = {}
97
+ @tracks = {}
98
+ @anonymous_blocklists = []
99
+ @anonymous_safelists = []
100
+ @throttled_response_retry_after_header = false
101
+
102
+ @blocklisted_response = DEFAULT_BLOCKLISTED_RESPONSE
103
+ @throttled_response = DEFAULT_THROTTLED_RESPONSE
104
+ end
105
+ end
106
+ end
107
+ end
@@ -15,33 +15,17 @@ module Rack
15
15
  #
16
16
  # So in order to workaround this we use RedisCacheStore#write (which sets expiration) to initialize
17
17
  # the counter. After that we continue using the original RedisCacheStore#increment.
18
- rescuing do
19
- if options[:expires_in] && !read(name)
20
- write(name, amount, options)
18
+ if options[:expires_in] && !read(name)
19
+ write(name, amount, options)
21
20
 
22
- amount
23
- else
24
- super
25
- end
21
+ amount
22
+ else
23
+ super
26
24
  end
27
25
  end
28
26
 
29
- def read(*_args)
30
- rescuing { super }
31
- end
32
-
33
27
  def write(name, value, options = {})
34
- rescuing do
35
- super(name, value, options.merge!(raw: true))
36
- end
37
- end
38
-
39
- private
40
-
41
- def rescuing
42
- yield
43
- rescue Redis::BaseError
44
- nil
28
+ super(name, value, options.merge!(raw: true))
45
29
  end
46
30
  end
47
31
  end
@@ -43,11 +43,24 @@ module Rack
43
43
  rescuing { del(key) }
44
44
  end
45
45
 
46
+ def delete_matched(matcher, _options = nil)
47
+ cursor = "0"
48
+
49
+ rescuing do
50
+ # Fetch keys in batches using SCAN to avoid blocking the Redis server.
51
+ loop do
52
+ cursor, keys = scan(cursor, match: matcher, count: 1000)
53
+ del(*keys) unless keys.empty?
54
+ break if cursor == "0"
55
+ end
56
+ end
57
+ end
58
+
46
59
  private
47
60
 
48
61
  def rescuing
49
62
  yield
50
- rescue Redis::BaseError
63
+ rescue Redis::BaseConnectionError
51
64
  nil
52
65
  end
53
66
  end
@@ -23,34 +23,50 @@ module Rack
23
23
 
24
24
  def matched_by?(request)
25
25
  discriminator = block.call(request)
26
+
26
27
  return false unless discriminator
27
28
 
28
- current_period = period.respond_to?(:call) ? period.call(request) : period
29
- current_limit = limit.respond_to?(:call) ? limit.call(request) : limit
30
- key = "#{name}:#{discriminator}"
31
- count = cache.count(key, current_period)
32
- epoch_time = cache.last_epoch_time
29
+ current_period = period_for(request)
30
+ current_limit = limit_for(request)
31
+ count = cache.count("#{name}:#{discriminator}", current_period)
33
32
 
34
33
  data = {
35
34
  discriminator: discriminator,
36
35
  count: count,
37
36
  period: current_period,
38
37
  limit: current_limit,
39
- epoch_time: epoch_time
38
+ epoch_time: cache.last_epoch_time
40
39
  }
41
40
 
42
- (request.env['rack.attack.throttle_data'] ||= {})[name] = data
43
-
44
41
  (count > current_limit).tap do |throttled|
42
+ annotate_request_with_throttle_data(request, data)
45
43
  if throttled
46
- request.env['rack.attack.matched'] = name
47
- request.env['rack.attack.match_discriminator'] = discriminator
48
- request.env['rack.attack.match_type'] = type
49
- request.env['rack.attack.match_data'] = data
44
+ annotate_request_with_matched_data(request, data)
50
45
  Rack::Attack.instrument(request)
51
46
  end
52
47
  end
53
48
  end
49
+
50
+ private
51
+
52
+ def period_for(request)
53
+ period.respond_to?(:call) ? period.call(request) : period
54
+ end
55
+
56
+ def limit_for(request)
57
+ limit.respond_to?(:call) ? limit.call(request) : limit
58
+ end
59
+
60
+ def annotate_request_with_throttle_data(request, data)
61
+ (request.env['rack.attack.throttle_data'] ||= {})[name] = data
62
+ end
63
+
64
+ def annotate_request_with_matched_data(request, data)
65
+ request.env['rack.attack.matched'] = name
66
+ request.env['rack.attack.match_discriminator'] = data[:discriminator]
67
+ request.env['rack.attack.match_type'] = type
68
+ request.env['rack.attack.match_data'] = data
69
+ end
54
70
  end
55
71
  end
56
72
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Rack
4
4
  class Attack
5
- VERSION = '6.2.2'
5
+ VERSION = '6.3.0'
6
6
  end
7
7
  end
@@ -20,7 +20,7 @@ describe "#throttle" do
20
20
  get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
21
21
 
22
22
  assert_equal 429, last_response.status
23
- assert_equal "60", last_response.headers["Retry-After"]
23
+ assert_nil last_response.headers["Retry-After"]
24
24
  assert_equal "Retry later\n", last_response.body
25
25
 
26
26
  get "/", {}, "REMOTE_ADDR" => "5.6.7.8"
@@ -34,6 +34,24 @@ describe "#throttle" do
34
34
  end
35
35
  end
36
36
 
37
+ it "returns correct Retry-After header if enabled" do
38
+ Rack::Attack.throttled_response_retry_after_header = true
39
+
40
+ Rack::Attack.throttle("by ip", limit: 1, period: 60) do |request|
41
+ request.ip
42
+ end
43
+
44
+ Timecop.freeze(Time.at(0)) do
45
+ get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
46
+ assert_equal 200, last_response.status
47
+ end
48
+
49
+ Timecop.freeze(Time.at(25)) do
50
+ get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
51
+ assert_equal "35", last_response.headers["Retry-After"]
52
+ end
53
+ end
54
+
37
55
  it "supports limit to be dynamic" do
38
56
  # Could be used to have different rate limits for authorized
39
57
  # vs general requests
@@ -13,7 +13,11 @@ OfflineExamples = Minitest::SharedExamples.new do
13
13
  end
14
14
 
15
15
  it 'should count' do
16
- @cache.send(:do_count, 'rack::attack::cache-test-key', 1)
16
+ @cache.count('cache-test-key', 1)
17
+ end
18
+
19
+ it 'should delete' do
20
+ @cache.delete('cache-test-key')
17
21
  end
18
22
  end
19
23
 
@@ -29,6 +33,18 @@ if defined?(::ActiveSupport::Cache::RedisStore)
29
33
  end
30
34
  end
31
35
 
36
+ if defined?(Redis) && defined?(ActiveSupport::Cache::RedisCacheStore) && Redis::VERSION >= '4'
37
+ describe 'when Redis is offline' do
38
+ include OfflineExamples
39
+
40
+ before do
41
+ @cache = Rack::Attack::Cache.new
42
+ # Use presumably unused port for Redis client
43
+ @cache.store = ActiveSupport::Cache::RedisCacheStore.new(host: '127.0.0.1', port: 3333)
44
+ end
45
+ end
46
+ end
47
+
32
48
  if defined?(::Dalli)
33
49
  describe 'when Memcached is offline' do
34
50
  include OfflineExamples
@@ -46,6 +62,23 @@ if defined?(::Dalli)
46
62
  end
47
63
  end
48
64
 
65
+ if defined?(::Dalli) && defined?(::ActiveSupport::Cache::MemCacheStore)
66
+ describe 'when Memcached is offline' do
67
+ include OfflineExamples
68
+
69
+ before do
70
+ Dalli.logger.level = Logger::FATAL
71
+
72
+ @cache = Rack::Attack::Cache.new
73
+ @cache.store = ActiveSupport::Cache::MemCacheStore.new('127.0.0.1:22122')
74
+ end
75
+
76
+ after do
77
+ Dalli.logger.level = Logger::INFO
78
+ end
79
+ end
80
+ end
81
+
49
82
  if defined?(Redis)
50
83
  describe 'when Redis is offline' do
51
84
  include OfflineExamples
@@ -99,4 +99,26 @@ describe 'Rack::Attack' do
99
99
  end
100
100
  end
101
101
  end
102
+
103
+ describe 'reset!' do
104
+ it 'raises an error when is not supported by cache store' do
105
+ Rack::Attack.cache.store = Class.new
106
+ assert_raises(Rack::Attack::IncompatibleStoreError) do
107
+ Rack::Attack.reset!
108
+ end
109
+ end
110
+
111
+ if defined?(Redis)
112
+ it 'should delete rack attack keys' do
113
+ redis = Redis.new
114
+ redis.set('key', 'value')
115
+ redis.set("#{Rack::Attack.cache.prefix}::key", 'value')
116
+ Rack::Attack.cache.store = redis
117
+ Rack::Attack.reset!
118
+
119
+ _(redis.get('key')).must_equal 'value'
120
+ _(redis.get("#{Rack::Attack.cache.prefix}::key")).must_be_nil
121
+ end
122
+ end
123
+ end
102
124
  end
@@ -57,10 +57,6 @@ describe 'Rack::Attack.throttle' do
57
57
 
58
58
  _(last_request.env['rack.attack.match_discriminator']).must_equal('1.2.3.4')
59
59
  end
60
-
61
- it 'should set a Retry-After header' do
62
- _(last_response.headers['Retry-After']).must_equal @period.to_s
63
- end
64
60
  end
65
61
  end
66
62
 
@@ -30,16 +30,11 @@ class MiniTest::Spec
30
30
 
31
31
  before do
32
32
  Rails.cache = nil
33
- @_original_throttled_response = Rack::Attack.throttled_response
34
- @_original_blocklisted_response = Rack::Attack.blocklisted_response
35
33
  end
36
34
 
37
35
  after do
38
36
  Rack::Attack.clear_configuration
39
37
  Rack::Attack.instance_variable_set(:@cache, nil)
40
-
41
- Rack::Attack.throttled_response = @_original_throttled_response
42
- Rack::Attack.blocklisted_response = @_original_blocklisted_response
43
38
  end
44
39
 
45
40
  def app
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.2.2
4
+ version: 6.3.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: 2019-12-18 00:00:00.000000000 Z
11
+ date: 2020-04-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -126,14 +126,14 @@ dependencies:
126
126
  requirements:
127
127
  - - '='
128
128
  - !ruby/object:Gem::Version
129
- version: 0.75.0
129
+ version: 0.78.0
130
130
  type: :development
131
131
  prerelease: false
132
132
  version_requirements: !ruby/object:Gem::Requirement
133
133
  requirements:
134
134
  - - '='
135
135
  - !ruby/object:Gem::Version
136
- version: 0.75.0
136
+ version: 0.78.0
137
137
  - !ruby/object:Gem::Dependency
138
138
  name: rubocop-performance
139
139
  requirement: !ruby/object:Gem::Requirement
@@ -204,11 +204,13 @@ extra_rdoc_files: []
204
204
  files:
205
205
  - README.md
206
206
  - Rakefile
207
+ - bin/setup
207
208
  - lib/rack/attack.rb
208
209
  - lib/rack/attack/allow2ban.rb
209
210
  - lib/rack/attack/blocklist.rb
210
211
  - lib/rack/attack/cache.rb
211
212
  - lib/rack/attack/check.rb
213
+ - lib/rack/attack/configuration.rb
212
214
  - lib/rack/attack/fail2ban.rb
213
215
  - lib/rack/attack/path_normalizer.rb
214
216
  - lib/rack/attack/railtie.rb
@@ -289,50 +291,50 @@ required_rubygems_version: !ruby/object:Gem::Requirement
289
291
  - !ruby/object:Gem::Version
290
292
  version: '0'
291
293
  requirements: []
292
- rubygems_version: 3.1.1
294
+ rubygems_version: 3.1.2
293
295
  signing_key:
294
296
  specification_version: 4
295
297
  summary: Block & throttle abusive requests
296
298
  test_files:
297
- - spec/rack_attack_spec.rb
298
- - spec/fail2ban_spec.rb
299
- - spec/allow2ban_spec.rb
300
- - spec/support/cache_store_helper.rb
301
- - spec/rack_attack_instrumentation_spec.rb
302
- - spec/rack_attack_throttle_spec.rb
303
299
  - spec/integration/offline_spec.rb
304
- - spec/rack_attack_dalli_proxy_spec.rb
305
- - spec/acceptance/fail2ban_spec.rb
306
- - spec/acceptance/allow2ban_spec.rb
300
+ - spec/rack_attack_path_normalizer_spec.rb
301
+ - spec/acceptance/safelisting_subnet_spec.rb
307
302
  - spec/acceptance/rails_middleware_spec.rb
308
- - spec/acceptance/throttling_spec.rb
309
303
  - spec/acceptance/track_throttle_spec.rb
304
+ - spec/acceptance/cache_store_config_for_fail2ban_spec.rb
305
+ - spec/acceptance/cache_store_config_with_rails_spec.rb
306
+ - spec/acceptance/cache_store_config_for_allow2ban_spec.rb
307
+ - spec/acceptance/safelisting_ip_spec.rb
308
+ - spec/acceptance/track_spec.rb
310
309
  - spec/acceptance/blocking_subnet_spec.rb
311
310
  - spec/acceptance/blocking_ip_spec.rb
312
- - spec/acceptance/cache_store_config_with_rails_spec.rb
313
- - spec/acceptance/cache_store_config_for_fail2ban_spec.rb
314
- - spec/acceptance/safelisting_subnet_spec.rb
311
+ - spec/acceptance/allow2ban_spec.rb
312
+ - spec/acceptance/throttling_spec.rb
313
+ - spec/acceptance/blocking_spec.rb
314
+ - spec/acceptance/customizing_throttled_response_spec.rb
315
315
  - spec/acceptance/extending_request_object_spec.rb
316
316
  - spec/acceptance/safelisting_spec.rb
317
- - spec/acceptance/customizing_throttled_response_spec.rb
318
- - spec/acceptance/safelisting_ip_spec.rb
319
- - spec/acceptance/cache_store_config_for_allow2ban_spec.rb
320
- - spec/acceptance/customizing_blocked_response_spec.rb
321
317
  - spec/acceptance/cache_store_config_for_throttle_spec.rb
322
- - spec/acceptance/blocking_spec.rb
323
- - spec/acceptance/stores/redis_spec.rb
324
- - spec/acceptance/stores/active_support_redis_cache_store_pooled_spec.rb
318
+ - spec/acceptance/fail2ban_spec.rb
319
+ - spec/acceptance/stores/active_support_mem_cache_store_pooled_spec.rb
320
+ - spec/acceptance/stores/active_support_redis_cache_store_spec.rb
325
321
  - spec/acceptance/stores/active_support_memory_store_spec.rb
322
+ - spec/acceptance/stores/active_support_redis_store_spec.rb
323
+ - spec/acceptance/stores/active_support_mem_cache_store_spec.rb
324
+ - spec/acceptance/stores/active_support_redis_cache_store_pooled_spec.rb
326
325
  - spec/acceptance/stores/connection_pool_dalli_client_spec.rb
327
- - spec/acceptance/stores/active_support_redis_cache_store_spec.rb
328
326
  - spec/acceptance/stores/active_support_dalli_store_spec.rb
329
- - spec/acceptance/stores/active_support_mem_cache_store_pooled_spec.rb
330
- - spec/acceptance/stores/active_support_mem_cache_store_spec.rb
331
- - spec/acceptance/stores/dalli_client_spec.rb
332
327
  - spec/acceptance/stores/redis_store_spec.rb
333
- - spec/acceptance/stores/active_support_redis_store_spec.rb
334
- - spec/acceptance/track_spec.rb
335
- - spec/rack_attack_path_normalizer_spec.rb
328
+ - spec/acceptance/stores/dalli_client_spec.rb
329
+ - spec/acceptance/stores/redis_spec.rb
330
+ - spec/acceptance/customizing_blocked_response_spec.rb
331
+ - spec/spec_helper.rb
332
+ - spec/allow2ban_spec.rb
333
+ - spec/rack_attack_instrumentation_spec.rb
334
+ - spec/rack_attack_dalli_proxy_spec.rb
335
+ - spec/rack_attack_spec.rb
336
+ - spec/rack_attack_throttle_spec.rb
336
337
  - spec/rack_attack_request_spec.rb
338
+ - spec/fail2ban_spec.rb
337
339
  - spec/rack_attack_track_spec.rb
338
- - spec/spec_helper.rb
340
+ - spec/support/cache_store_helper.rb