rack-attack 6.1.0 → 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,17 +15,33 @@ 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
- if options[:expires_in] && !read(name)
19
- write(name, amount, options)
18
+ rescuing do
19
+ if options[:expires_in] && !read(name)
20
+ write(name, amount, options)
20
21
 
21
- amount
22
- else
23
- super
22
+ amount
23
+ else
24
+ super
25
+ end
24
26
  end
25
27
  end
26
28
 
29
+ def read(*_args)
30
+ rescuing { super }
31
+ end
32
+
27
33
  def write(name, value, options = {})
28
- super(name, value, options.merge!(raw: true))
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
29
45
  end
30
46
  end
31
47
  end
@@ -7,11 +7,12 @@ module Rack
7
7
 
8
8
  attr_reader :name, :limit, :period, :block, :type
9
9
  def initialize(name, options, &block)
10
- @name, @block = name, block
10
+ @name = name
11
+ @block = block
11
12
  MANDATORY_OPTIONS.each do |opt|
12
13
  raise ArgumentError, "Must pass #{opt.inspect} option" unless options[opt]
13
14
  end
14
- @limit = options[:limit]
15
+ @limit = options[:limit]
15
16
  @period = options[:period].respond_to?(:call) ? options[:period] : options[:period].to_i
16
17
  @type = options.fetch(:type, :throttle)
17
18
  end
@@ -8,11 +8,12 @@ module Rack
8
8
  def initialize(name, options = {}, &block)
9
9
  options[:type] = :track
10
10
 
11
- if options[:limit] && options[:period]
12
- @filter = Throttle.new(name, options, &block)
13
- else
14
- @filter = Check.new(name, options, &block)
15
- end
11
+ @filter =
12
+ if options[:limit] && options[:period]
13
+ Throttle.new(name, options, &block)
14
+ else
15
+ Check.new(name, options, &block)
16
+ end
16
17
  end
17
18
 
18
19
  def matched_by?(request)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Rack
4
4
  class Attack
5
- VERSION = '6.1.0'
5
+ VERSION = '6.2.0'
6
6
  end
7
7
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../spec_helper"
4
+
5
+ if defined?(Rails)
6
+ describe "Middleware for Rails" do
7
+ before do
8
+ @app = Class.new(Rails::Application) do
9
+ config.eager_load = false
10
+ config.logger = Logger.new(nil) # avoid creating the log/ directory automatically
11
+ config.cache_store = :null_store # avoid creating tmp/ directory for cache
12
+ end
13
+ end
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 added explicitly" do
22
+ @app.config.middleware.use(Rack::Attack)
23
+ @app.initialize!
24
+ assert_equal 1, @app.middleware.count(Rack::Attack)
25
+ end
26
+
27
+ it "is not added when it was explicitly deleted" do
28
+ @app.config.middleware.delete(Rack::Attack)
29
+ @app.initialize!
30
+ refute @app.middleware.include?(Rack::Attack)
31
+ end
32
+ end
33
+
34
+ if Gem::Version.new(Rails::VERSION::STRING) < Gem::Version.new("5.1")
35
+ it "is not used by default" do
36
+ @app.initialize!
37
+ assert_equal 0, @app.middleware.count(Rack::Attack)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -15,8 +15,6 @@ if defined?(::ConnectionPool) && defined?(::Dalli)
15
15
  Rack::Attack.cache.store.clear
16
16
  end
17
17
 
18
- it_works_for_cache_backed_features(fetch_from_store: ->(key) {
19
- Rack::Attack.cache.store.read(key)
20
- })
18
+ it_works_for_cache_backed_features(fetch_from_store: ->(key) { Rack::Attack.cache.store.read(key) })
21
19
  end
22
20
  end
@@ -2,7 +2,13 @@
2
2
 
3
3
  require_relative "../../spec_helper"
4
4
 
5
- if defined?(::ConnectionPool) && defined?(::Redis) && Gem::Version.new(::Redis::VERSION) >= Gem::Version.new("4") && defined?(::ActiveSupport::Cache::RedisCacheStore)
5
+ should_run =
6
+ defined?(::ConnectionPool) &&
7
+ defined?(::Redis) &&
8
+ Gem::Version.new(::Redis::VERSION) >= Gem::Version.new("4") &&
9
+ defined?(::ActiveSupport::Cache::RedisCacheStore)
10
+
11
+ if should_run
6
12
  require_relative "../../support/cache_store_helper"
7
13
  require "timecop"
8
14
 
@@ -2,7 +2,12 @@
2
2
 
3
3
  require_relative "../../spec_helper"
4
4
 
5
- if defined?(::Redis) && Gem::Version.new(::Redis::VERSION) >= Gem::Version.new("4") && defined?(::ActiveSupport::Cache::RedisCacheStore)
5
+ should_run =
6
+ defined?(::Redis) &&
7
+ Gem::Version.new(::Redis::VERSION) >= Gem::Version.new("4") &&
8
+ defined?(::ActiveSupport::Cache::RedisCacheStore)
9
+
10
+ if should_run
6
11
  require_relative "../../support/cache_store_helper"
7
12
  require "timecop"
8
13
 
@@ -17,8 +17,8 @@ if defined?(::Dalli) && defined?(::ConnectionPool)
17
17
  Rack::Attack.cache.store.with { |client| client.flush_all }
18
18
  end
19
19
 
20
- it_works_for_cache_backed_features(fetch_from_store: ->(key) {
21
- Rack::Attack.cache.store.with { |client| client.fetch(key) }
22
- })
20
+ it_works_for_cache_backed_features(
21
+ fetch_from_store: ->(key) { Rack::Attack.cache.store.with { |client| client.fetch(key) } }
22
+ )
23
23
  end
24
24
  end
@@ -20,7 +20,8 @@ describe 'Rack::Attack.Allow2Ban' do
20
20
  describe 'making ok request' do
21
21
  it 'succeeds' do
22
22
  get '/', {}, 'REMOTE_ADDR' => '1.2.3.4'
23
- last_response.status.must_equal 200
23
+
24
+ _(last_response.status).must_equal 200
24
25
  end
25
26
  end
26
27
 
@@ -29,17 +30,18 @@ describe 'Rack::Attack.Allow2Ban' do
29
30
  before { get '/?foo=OMGHAX', {}, 'REMOTE_ADDR' => '1.2.3.4' }
30
31
 
31
32
  it 'succeeds' do
32
- last_response.status.must_equal 200
33
+ _(last_response.status).must_equal 200
33
34
  end
34
35
 
35
36
  it 'increases fail count' do
36
37
  key = "rack::attack:#{Time.now.to_i / @findtime}:allow2ban:count:1.2.3.4"
37
- @cache.store.read(key).must_equal 1
38
+
39
+ _(@cache.store.read(key)).must_equal 1
38
40
  end
39
41
 
40
42
  it 'is not banned' do
41
43
  key = "rack::attack:allow2ban:1.2.3.4"
42
- @cache.store.read(key).must_be_nil
44
+ _(@cache.store.read(key)).must_be_nil
43
45
  end
44
46
  end
45
47
 
@@ -51,17 +53,17 @@ describe 'Rack::Attack.Allow2Ban' do
51
53
  end
52
54
 
53
55
  it 'succeeds' do
54
- last_response.status.must_equal 200
56
+ _(last_response.status).must_equal 200
55
57
  end
56
58
 
57
59
  it 'increases fail count' do
58
60
  key = "rack::attack:#{Time.now.to_i / @findtime}:allow2ban:count:1.2.3.4"
59
- @cache.store.read(key).must_equal 2
61
+ _(@cache.store.read(key)).must_equal 2
60
62
  end
61
63
 
62
64
  it 'is banned' do
63
65
  key = "rack::attack:allow2ban:ban:1.2.3.4"
64
- @cache.store.read(key).must_equal 1
66
+ _(@cache.store.read(key)).must_equal 1
65
67
  end
66
68
  end
67
69
  end
@@ -77,7 +79,8 @@ describe 'Rack::Attack.Allow2Ban' do
77
79
  describe 'making request for other discriminator' do
78
80
  it 'succeeds' do
79
81
  get '/', {}, 'REMOTE_ADDR' => '2.2.3.4'
80
- last_response.status.must_equal 200
82
+
83
+ _(last_response.status).must_equal 200
81
84
  end
82
85
  end
83
86
 
@@ -87,17 +90,17 @@ describe 'Rack::Attack.Allow2Ban' do
87
90
  end
88
91
 
89
92
  it 'fails' do
90
- last_response.status.must_equal 403
93
+ _(last_response.status).must_equal 403
91
94
  end
92
95
 
93
96
  it 'does not increase fail count' do
94
97
  key = "rack::attack:#{Time.now.to_i / @findtime}:allow2ban:count:1.2.3.4"
95
- @cache.store.read(key).must_equal 2
98
+ _(@cache.store.read(key)).must_equal 2
96
99
  end
97
100
 
98
101
  it 'is still banned' do
99
102
  key = "rack::attack:allow2ban:ban:1.2.3.4"
100
- @cache.store.read(key).must_equal 1
103
+ _(@cache.store.read(key)).must_equal 1
101
104
  end
102
105
  end
103
106
 
@@ -107,17 +110,17 @@ describe 'Rack::Attack.Allow2Ban' do
107
110
  end
108
111
 
109
112
  it 'fails' do
110
- last_response.status.must_equal 403
113
+ _(last_response.status).must_equal 403
111
114
  end
112
115
 
113
116
  it 'does not increase fail count' do
114
117
  key = "rack::attack:#{Time.now.to_i / @findtime}:allow2ban:count:1.2.3.4"
115
- @cache.store.read(key).must_equal 2
118
+ _(@cache.store.read(key)).must_equal 2
116
119
  end
117
120
 
118
121
  it 'is still banned' do
119
122
  key = "rack::attack:allow2ban:ban:1.2.3.4"
120
- @cache.store.read(key).must_equal 1
123
+ _(@cache.store.read(key)).must_equal 1
121
124
  end
122
125
  end
123
126
  end
@@ -20,7 +20,7 @@ describe 'Rack::Attack.Fail2Ban' do
20
20
  describe 'making ok request' do
21
21
  it 'succeeds' do
22
22
  get '/', {}, 'REMOTE_ADDR' => '1.2.3.4'
23
- last_response.status.must_equal 200
23
+ _(last_response.status).must_equal 200
24
24
  end
25
25
  end
26
26
 
@@ -29,17 +29,17 @@ describe 'Rack::Attack.Fail2Ban' do
29
29
  before { get '/?foo=OMGHAX', {}, 'REMOTE_ADDR' => '1.2.3.4' }
30
30
 
31
31
  it 'fails' do
32
- last_response.status.must_equal 403
32
+ _(last_response.status).must_equal 403
33
33
  end
34
34
 
35
35
  it 'increases fail count' do
36
36
  key = "rack::attack:#{Time.now.to_i / @findtime}:fail2ban:count:1.2.3.4"
37
- @cache.store.read(key).must_equal 1
37
+ _(@cache.store.read(key)).must_equal 1
38
38
  end
39
39
 
40
40
  it 'is not banned' do
41
41
  key = "rack::attack:fail2ban:1.2.3.4"
42
- @cache.store.read(key).must_be_nil
42
+ _(@cache.store.read(key)).must_be_nil
43
43
  end
44
44
  end
45
45
 
@@ -51,17 +51,17 @@ describe 'Rack::Attack.Fail2Ban' do
51
51
  end
52
52
 
53
53
  it 'fails' do
54
- last_response.status.must_equal 403
54
+ _(last_response.status).must_equal 403
55
55
  end
56
56
 
57
57
  it 'increases fail count' do
58
58
  key = "rack::attack:#{Time.now.to_i / @findtime}:fail2ban:count:1.2.3.4"
59
- @cache.store.read(key).must_equal 2
59
+ _(@cache.store.read(key)).must_equal 2
60
60
  end
61
61
 
62
62
  it 'is banned' do
63
63
  key = "rack::attack:fail2ban:ban:1.2.3.4"
64
- @cache.store.read(key).must_equal 1
64
+ _(@cache.store.read(key)).must_equal 1
65
65
  end
66
66
  end
67
67
 
@@ -73,7 +73,7 @@ describe 'Rack::Attack.Fail2Ban' do
73
73
  end
74
74
 
75
75
  it 'succeeds' do
76
- last_response.status.must_equal 200
76
+ _(last_response.status).must_equal 200
77
77
  end
78
78
 
79
79
  it 'resets fail count' do
@@ -82,7 +82,7 @@ describe 'Rack::Attack.Fail2Ban' do
82
82
  end
83
83
 
84
84
  it 'IP is not banned' do
85
- Rack::Attack::Fail2Ban.banned?('1.2.3.4').must_equal false
85
+ _(Rack::Attack::Fail2Ban.banned?('1.2.3.4')).must_equal false
86
86
  end
87
87
  end
88
88
  end
@@ -98,7 +98,8 @@ describe 'Rack::Attack.Fail2Ban' do
98
98
  describe 'making request for other discriminator' do
99
99
  it 'succeeds' do
100
100
  get '/', {}, 'REMOTE_ADDR' => '2.2.3.4'
101
- last_response.status.must_equal 200
101
+
102
+ _(last_response.status).must_equal 200
102
103
  end
103
104
  end
104
105
 
@@ -108,17 +109,17 @@ describe 'Rack::Attack.Fail2Ban' do
108
109
  end
109
110
 
110
111
  it 'fails' do
111
- last_response.status.must_equal 403
112
+ _(last_response.status).must_equal 403
112
113
  end
113
114
 
114
115
  it 'does not increase fail count' do
115
116
  key = "rack::attack:#{Time.now.to_i / @findtime}:fail2ban:count:1.2.3.4"
116
- @cache.store.read(key).must_equal 2
117
+ _(@cache.store.read(key)).must_equal 2
117
118
  end
118
119
 
119
120
  it 'is still banned' do
120
121
  key = "rack::attack:fail2ban:ban:1.2.3.4"
121
- @cache.store.read(key).must_equal 1
122
+ _(@cache.store.read(key)).must_equal 1
122
123
  end
123
124
  end
124
125
 
@@ -128,17 +129,17 @@ describe 'Rack::Attack.Fail2Ban' do
128
129
  end
129
130
 
130
131
  it 'fails' do
131
- last_response.status.must_equal 403
132
+ _(last_response.status).must_equal 403
132
133
  end
133
134
 
134
135
  it 'does not increase fail count' do
135
136
  key = "rack::attack:#{Time.now.to_i / @findtime}:fail2ban:count:1.2.3.4"
136
- @cache.store.read(key).must_equal 2
137
+ _(@cache.store.read(key)).must_equal 2
137
138
  end
138
139
 
139
140
  it 'is still banned' do
140
141
  key = "rack::attack:fail2ban:ban:1.2.3.4"
141
- @cache.store.read(key).must_equal 1
142
+ _(@cache.store.read(key)).must_equal 1
142
143
  end
143
144
  end
144
145
  end
@@ -34,7 +34,7 @@ if ActiveSupport::VERSION::MAJOR > 3
34
34
  end
35
35
 
36
36
  it 'should instrument without error' do
37
- last_response.status.must_equal 429
37
+ _(last_response.status).must_equal 429
38
38
  assert_equal 1, CustomSubscriber.notification_count
39
39
  end
40
40
  end
@@ -6,14 +6,14 @@ describe Rack::Attack::PathNormalizer do
6
6
  subject { Rack::Attack::PathNormalizer }
7
7
 
8
8
  it 'should have a normalize_path method' do
9
- subject.normalize_path('/foo').must_equal '/foo'
9
+ _(subject.normalize_path('/foo')).must_equal '/foo'
10
10
  end
11
11
 
12
12
  describe 'FallbackNormalizer' do
13
13
  subject { Rack::Attack::FallbackPathNormalizer }
14
14
 
15
15
  it '#normalize_path does not change the path' do
16
- subject.normalize_path('').must_equal ''
16
+ _(subject.normalize_path('')).must_equal ''
17
17
  end
18
18
  end
19
19
  end
@@ -12,7 +12,7 @@ describe 'Rack::Attack' do
12
12
 
13
13
  it 'blocks requests with trailing slash' do
14
14
  get '/foo/'
15
- last_response.status.must_equal 403
15
+ _(last_response.status).must_equal 403
16
16
  end
17
17
  end
18
18
 
@@ -22,21 +22,21 @@ describe 'Rack::Attack' do
22
22
  Rack::Attack.blocklist("ip #{@bad_ip}") { |req| req.ip == @bad_ip }
23
23
  end
24
24
 
25
- it('has a blocklist') {
26
- Rack::Attack.blocklists.key?("ip #{@bad_ip}").must_equal true
27
- }
25
+ it 'has a blocklist' do
26
+ _(Rack::Attack.blocklists.key?("ip #{@bad_ip}")).must_equal true
27
+ end
28
28
 
29
29
  describe "a bad request" do
30
30
  before { get '/', {}, 'REMOTE_ADDR' => @bad_ip }
31
31
 
32
32
  it "should return a blocklist response" do
33
- last_response.status.must_equal 403
34
- last_response.body.must_equal "Forbidden\n"
33
+ _(last_response.status).must_equal 403
34
+ _(last_response.body).must_equal "Forbidden\n"
35
35
  end
36
36
 
37
37
  it "should tag the env" do
38
- last_request.env['rack.attack.matched'].must_equal "ip #{@bad_ip}"
39
- last_request.env['rack.attack.match_type'].must_equal :blocklist
38
+ _(last_request.env['rack.attack.matched']).must_equal "ip #{@bad_ip}"
39
+ _(last_request.env['rack.attack.match_type']).must_equal :blocklist
40
40
  end
41
41
 
42
42
  it_allows_ok_requests
@@ -54,25 +54,48 @@ describe 'Rack::Attack' do
54
54
  before { get '/', {}, 'REMOTE_ADDR' => @bad_ip, 'HTTP_USER_AGENT' => @good_ua }
55
55
 
56
56
  it "should allow safelists before blocklists" do
57
- last_response.status.must_equal 200
57
+ _(last_response.status).must_equal 200
58
58
  end
59
59
 
60
60
  it "should tag the env" do
61
- last_request.env['rack.attack.matched'].must_equal 'good ua'
62
- last_request.env['rack.attack.match_type'].must_equal :safelist
61
+ _(last_request.env['rack.attack.matched']).must_equal 'good ua'
62
+ _(last_request.env['rack.attack.match_type']).must_equal :safelist
63
63
  end
64
64
  end
65
65
  end
66
66
 
67
67
  describe '#blocklisted_response' do
68
68
  it 'should exist' do
69
- Rack::Attack.blocklisted_response.must_respond_to :call
69
+ _(Rack::Attack.blocklisted_response).must_respond_to :call
70
70
  end
71
71
  end
72
72
 
73
73
  describe '#throttled_response' do
74
74
  it 'should exist' do
75
- Rack::Attack.throttled_response.must_respond_to :call
75
+ _(Rack::Attack.throttled_response).must_respond_to :call
76
+ end
77
+ end
78
+ end
79
+
80
+ describe 'enabled' do
81
+ it 'should be enabled by default' do
82
+ _(Rack::Attack.enabled).must_equal true
83
+ end
84
+
85
+ it 'should directly pass request when disabled' do
86
+ bad_ip = '1.2.3.4'
87
+ Rack::Attack.blocklist("ip #{bad_ip}") { |req| req.ip == bad_ip }
88
+
89
+ get '/', {}, 'REMOTE_ADDR' => bad_ip
90
+ _(last_response.status).must_equal 403
91
+
92
+ prev_enabled = Rack::Attack.enabled
93
+ begin
94
+ Rack::Attack.enabled = false
95
+ get '/', {}, 'REMOTE_ADDR' => bad_ip
96
+ _(last_response.status).must_equal 200
97
+ ensure
98
+ Rack::Attack.enabled = prev_enabled
76
99
  end
77
100
  end
78
101
  end