rack-attack 6.0.0 → 6.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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