rack-attack 6.0.0 → 6.3.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.
@@ -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
@@ -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
@@ -45,3 +61,32 @@ if defined?(::Dalli)
45
61
  end
46
62
  end
47
63
  end
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
+
82
+ if defined?(Redis)
83
+ describe 'when Redis is offline' do
84
+ include OfflineExamples
85
+
86
+ before do
87
+ @cache = Rack::Attack::Cache.new
88
+ # Use presumably unused port for Redis client
89
+ @cache.store = Redis.new(host: '127.0.0.1', port: 3333)
90
+ end
91
+ end
92
+ 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,70 @@ 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
99
+ end
100
+ end
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
76
121
  end
77
122
  end
78
123
  end
@@ -18,12 +18,19 @@ describe 'Rack::Attack.throttle' do
18
18
 
19
19
  it 'should set the counter for one request' do
20
20
  key = "rack::attack:#{Time.now.to_i / @period}:ip/sec:1.2.3.4"
21
- Rack::Attack.cache.store.read(key).must_equal 1
21
+ _(Rack::Attack.cache.store.read(key)).must_equal 1
22
22
  end
23
23
 
24
24
  it 'should populate throttle data' do
25
- data = { count: 1, limit: 1, period: @period, epoch_time: Rack::Attack.cache.last_epoch_time.to_i }
26
- last_request.env['rack.attack.throttle_data']['ip/sec'].must_equal data
25
+ data = {
26
+ count: 1,
27
+ limit: 1,
28
+ period: @period,
29
+ epoch_time: Rack::Attack.cache.last_epoch_time.to_i,
30
+ discriminator: "1.2.3.4"
31
+ }
32
+
33
+ _(last_request.env['rack.attack.throttle_data']['ip/sec']).must_equal data
27
34
  end
28
35
  end
29
36
 
@@ -33,18 +40,22 @@ describe 'Rack::Attack.throttle' do
33
40
  end
34
41
 
35
42
  it 'should block the last request' do
36
- last_response.status.must_equal 429
43
+ _(last_response.status).must_equal 429
37
44
  end
38
45
 
39
46
  it 'should tag the env' do
40
- last_request.env['rack.attack.matched'].must_equal 'ip/sec'
41
- last_request.env['rack.attack.match_type'].must_equal :throttle
42
- 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)
43
- last_request.env['rack.attack.match_discriminator'].must_equal('1.2.3.4')
44
- end
45
-
46
- it 'should set a Retry-After header' do
47
- last_response.headers['Retry-After'].must_equal @period.to_s
47
+ _(last_request.env['rack.attack.matched']).must_equal 'ip/sec'
48
+ _(last_request.env['rack.attack.match_type']).must_equal :throttle
49
+
50
+ _(last_request.env['rack.attack.match_data']).must_equal(
51
+ count: 2,
52
+ limit: 1,
53
+ period: @period,
54
+ epoch_time: Rack::Attack.cache.last_epoch_time.to_i,
55
+ discriminator: "1.2.3.4"
56
+ )
57
+
58
+ _(last_request.env['rack.attack.match_discriminator']).must_equal('1.2.3.4')
48
59
  end
49
60
  end
50
61
  end
@@ -63,12 +74,19 @@ describe 'Rack::Attack.throttle with limit as proc' do
63
74
 
64
75
  it 'should set the counter for one request' do
65
76
  key = "rack::attack:#{Time.now.to_i / @period}:ip/sec:1.2.3.4"
66
- Rack::Attack.cache.store.read(key).must_equal 1
77
+ _(Rack::Attack.cache.store.read(key)).must_equal 1
67
78
  end
68
79
 
69
80
  it 'should populate throttle data' do
70
- data = { count: 1, limit: 1, period: @period, epoch_time: Rack::Attack.cache.last_epoch_time.to_i }
71
- last_request.env['rack.attack.throttle_data']['ip/sec'].must_equal data
81
+ data = {
82
+ count: 1,
83
+ limit: 1,
84
+ period: @period,
85
+ epoch_time: Rack::Attack.cache.last_epoch_time.to_i,
86
+ discriminator: "1.2.3.4"
87
+ }
88
+
89
+ _(last_request.env['rack.attack.throttle_data']['ip/sec']).must_equal data
72
90
  end
73
91
  end
74
92
  end
@@ -87,12 +105,19 @@ describe 'Rack::Attack.throttle with period as proc' do
87
105
 
88
106
  it 'should set the counter for one request' do
89
107
  key = "rack::attack:#{Time.now.to_i / @period}:ip/sec:1.2.3.4"
90
- Rack::Attack.cache.store.read(key).must_equal 1
108
+ _(Rack::Attack.cache.store.read(key)).must_equal 1
91
109
  end
92
110
 
93
111
  it 'should populate throttle data' do
94
- data = { count: 1, limit: 1, period: @period, epoch_time: Rack::Attack.cache.last_epoch_time.to_i }
95
- last_request.env['rack.attack.throttle_data']['ip/sec'].must_equal data
112
+ data = {
113
+ count: 1,
114
+ limit: 1,
115
+ period: @period,
116
+ epoch_time: Rack::Attack.cache.last_epoch_time.to_i,
117
+ discriminator: "1.2.3.4"
118
+ }
119
+
120
+ _(last_request.env['rack.attack.throttle_data']['ip/sec']).must_equal data
96
121
  end
97
122
  end
98
123
  end
@@ -25,8 +25,9 @@ describe 'Rack::Attack.track' do
25
25
 
26
26
  it "should tag the env" do
27
27
  get '/'
28
- last_request.env['rack.attack.matched'].must_equal 'everything'
29
- last_request.env['rack.attack.match_type'].must_equal :track
28
+
29
+ _(last_request.env['rack.attack.matched']).must_equal 'everything'
30
+ _(last_request.env['rack.attack.match_type']).must_equal :track
30
31
  end
31
32
 
32
33
  describe "with a notification subscriber and two tracks" do
@@ -43,21 +44,23 @@ describe 'Rack::Attack.track' do
43
44
  end
44
45
 
45
46
  it "should notify twice" do
46
- Counter.check.must_equal 2
47
+ _(Counter.check).must_equal 2
47
48
  end
48
49
  end
49
50
 
50
51
  describe "without limit and period options" do
51
52
  it "should assign the track filter to a Check instance" do
52
53
  track = Rack::Attack.track("homepage") { |req| req.path == "/" }
53
- track.filter.class.must_equal Rack::Attack::Check
54
+
55
+ _(track.filter.class).must_equal Rack::Attack::Check
54
56
  end
55
57
  end
56
58
 
57
59
  describe "with limit and period options" do
58
60
  it "should assign the track filter to a Throttle instance" do
59
61
  track = Rack::Attack.track("homepage", limit: 10, period: 10) { |req| req.path == "/" }
60
- track.filter.class.must_equal Rack::Attack::Throttle
62
+
63
+ _(track.filter.class).must_equal Rack::Attack::Throttle
61
64
  end
62
65
  end
63
66
  end