wkimeria-rack-attack 4.1.2

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.
@@ -0,0 +1,121 @@
1
+ require_relative 'spec_helper'
2
+ describe 'Rack::Attack.Fail2Ban' do
3
+ before do
4
+ # Use a long findtime; failures due to cache key rotation less likely
5
+ @cache = Rack::Attack.cache
6
+ @findtime = 60
7
+ @bantime = 60
8
+ Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
9
+ @f2b_options = {:bantime => @bantime, :findtime => @findtime, :maxretry => 2}
10
+ Rack::Attack.blacklist('pentest') do |req|
11
+ Rack::Attack::Fail2Ban.filter(req.ip, @f2b_options){req.query_string =~ /OMGHAX/}
12
+ end
13
+ end
14
+
15
+ describe 'discriminator has not been banned' do
16
+ describe 'making ok request' do
17
+ it 'succeeds' do
18
+ get '/', {}, 'REMOTE_ADDR' => '1.2.3.4'
19
+ last_response.status.must_equal 200
20
+ end
21
+ end
22
+
23
+ describe 'making failing request' do
24
+ describe 'when not at maxretry' do
25
+ before { get '/?foo=OMGHAX', {}, 'REMOTE_ADDR' => '1.2.3.4' }
26
+ it 'fails' do
27
+ last_response.status.must_equal 403
28
+ end
29
+
30
+ it 'increases fail count' do
31
+ key = "rack::attack:#{Time.now.to_i/@findtime}:fail2ban:count:1.2.3.4"
32
+ @cache.store.read(key).must_equal 1
33
+ end
34
+
35
+ it 'is not banned' do
36
+ key = "rack::attack:fail2ban:1.2.3.4"
37
+ @cache.store.read(key).must_be_nil
38
+ end
39
+ end
40
+
41
+ describe 'when at maxretry' do
42
+ before do
43
+ # maxretry is 2 - so hit with an extra failed request first
44
+ get '/?test=OMGHAX', {}, 'REMOTE_ADDR' => '1.2.3.4'
45
+ get '/?foo=OMGHAX', {}, 'REMOTE_ADDR' => '1.2.3.4'
46
+ end
47
+
48
+ it 'fails' do
49
+ last_response.status.must_equal 403
50
+ end
51
+
52
+ it 'increases fail count' do
53
+ key = "rack::attack:#{Time.now.to_i/@findtime}:fail2ban:count:1.2.3.4"
54
+ @cache.store.read(key).must_equal 2
55
+ end
56
+
57
+ it 'is banned' do
58
+ key = "rack::attack:fail2ban:ban:1.2.3.4"
59
+ @cache.store.read(key).must_equal 1
60
+ end
61
+
62
+ end
63
+ end
64
+ end
65
+
66
+ describe 'discriminator has been banned' do
67
+ before do
68
+ # maxretry is 2 - so hit enough times to get banned
69
+ get '/?test=OMGHAX', {}, 'REMOTE_ADDR' => '1.2.3.4'
70
+ get '/?foo=OMGHAX', {}, 'REMOTE_ADDR' => '1.2.3.4'
71
+ end
72
+
73
+ describe 'making request for other discriminator' do
74
+ it 'succeeds' do
75
+ get '/', {}, 'REMOTE_ADDR' => '2.2.3.4'
76
+ last_response.status.must_equal 200
77
+ end
78
+ end
79
+
80
+ describe 'making ok request' do
81
+ before do
82
+ get '/', {}, 'REMOTE_ADDR' => '1.2.3.4'
83
+ end
84
+
85
+ it 'fails' do
86
+ last_response.status.must_equal 403
87
+ end
88
+
89
+ it 'does not increase fail count' do
90
+ key = "rack::attack:#{Time.now.to_i/@findtime}:fail2ban:count:1.2.3.4"
91
+ @cache.store.read(key).must_equal 2
92
+ end
93
+
94
+ it 'is still banned' do
95
+ key = "rack::attack:fail2ban:ban:1.2.3.4"
96
+ @cache.store.read(key).must_equal 1
97
+ end
98
+ end
99
+
100
+ describe 'making failing request' do
101
+ before do
102
+ get '/?foo=OMGHAX', {}, 'REMOTE_ADDR' => '1.2.3.4'
103
+ end
104
+
105
+ it 'fails' do
106
+ last_response.status.must_equal 403
107
+ end
108
+
109
+ it 'does not increase fail count' do
110
+ key = "rack::attack:#{Time.now.to_i/@findtime}:fail2ban:count:1.2.3.4"
111
+ @cache.store.read(key).must_equal 2
112
+ end
113
+
114
+ it 'is still banned' do
115
+ key = "rack::attack:fail2ban:ban:1.2.3.4"
116
+ @cache.store.read(key).must_equal 1
117
+ end
118
+ end
119
+
120
+ end
121
+ end
@@ -0,0 +1,47 @@
1
+ require 'active_support/cache'
2
+ require 'active_support/cache/redis_store'
3
+ require 'dalli'
4
+ require_relative '../spec_helper'
5
+
6
+ OfflineExamples = Minitest::SharedExamples.new do
7
+
8
+ it 'should write' do
9
+ @cache.write('cache-test-key', 'foobar', 1)
10
+ end
11
+
12
+ it 'should read' do
13
+ @cache.read('cache-test-key')
14
+ end
15
+
16
+ it 'should count' do
17
+ @cache.send(:do_count, 'rack::attack::cache-test-key', 1)
18
+ end
19
+
20
+ end
21
+
22
+ describe 'when Redis is offline' do
23
+ include OfflineExamples
24
+
25
+ before {
26
+ @cache = Rack::Attack::Cache.new
27
+ # Use presumably unused port for Redis client
28
+ @cache.store = ActiveSupport::Cache::RedisStore.new(:host => '127.0.0.1', :port => 3333)
29
+ }
30
+
31
+ end
32
+
33
+ describe 'when Memcached is offline' do
34
+ include OfflineExamples
35
+
36
+ before {
37
+ Dalli.logger.level = Logger::FATAL
38
+
39
+ @cache = Rack::Attack::Cache.new
40
+ @cache.store = Dalli::Client.new('127.0.0.1:22122')
41
+ }
42
+
43
+ after {
44
+ Dalli.logger.level = Logger::INFO
45
+ }
46
+
47
+ end
@@ -0,0 +1,86 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe Rack::Attack::Cache do
4
+ def delete(key)
5
+ if @cache.store.respond_to?(:delete)
6
+ @cache.store.delete(key)
7
+ else
8
+ @cache.store.del(key)
9
+ end
10
+ end
11
+
12
+ def sleep_until_expired
13
+ sleep(@expires_in * 1.1) # Add 10% to reduce errors
14
+ end
15
+
16
+ require 'active_support/cache/dalli_store'
17
+ require 'active_support/cache/redis_store'
18
+ require 'connection_pool'
19
+ cache_stores = [
20
+ ActiveSupport::Cache::MemoryStore.new,
21
+ ActiveSupport::Cache::DalliStore.new("127.0.0.1"),
22
+ ActiveSupport::Cache::RedisStore.new("127.0.0.1"),
23
+ Dalli::Client.new,
24
+ ConnectionPool.new { Dalli::Client.new },
25
+ Redis::Store.new
26
+ ]
27
+
28
+ cache_stores.each do |store|
29
+ store = Rack::Attack::StoreProxy.build(store)
30
+ describe "with #{store.class}" do
31
+
32
+ before {
33
+ @cache = Rack::Attack::Cache.new
34
+ @key = "rack::attack:cache-test-key"
35
+ @expires_in = 1
36
+ @cache.store = store
37
+ delete(@key)
38
+ }
39
+
40
+ after { delete(@key) }
41
+
42
+ describe "do_count once" do
43
+ it "should be 1" do
44
+ @cache.send(:do_count, @key, @expires_in).must_equal 1
45
+ end
46
+ end
47
+
48
+ describe "do_count twice" do
49
+ it "must be 2" do
50
+ @cache.send(:do_count, @key, @expires_in)
51
+ @cache.send(:do_count, @key, @expires_in).must_equal 2
52
+ end
53
+ end
54
+ describe "do_count after expires_in" do
55
+ it "must be 1" do
56
+ @cache.send(:do_count, @key, @expires_in)
57
+ sleep_until_expired
58
+ @cache.send(:do_count, @key, @expires_in).must_equal 1
59
+ end
60
+ end
61
+
62
+ describe "write" do
63
+ it "should write a value to the store with prefix" do
64
+ @cache.write("cache-test-key", "foobar", 1)
65
+ store.read(@key).must_equal "foobar"
66
+ end
67
+ end
68
+
69
+ describe "write after expiry" do
70
+ it "must not have a value" do
71
+ @cache.write("cache-test-key", "foobar", @expires_in)
72
+ sleep_until_expired
73
+ store.read(@key).must_be :nil?
74
+ end
75
+ end
76
+
77
+ describe "read" do
78
+ it "must read the value with a prefix" do
79
+ store.write(@key, "foobar", :expires_in => @expires_in)
80
+ @cache.read("cache-test-key").must_equal "foobar"
81
+ end
82
+ end
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,53 @@
1
+ require_relative 'spec_helper'
2
+ describe 'Rack::Attack.conditional_throttle' do
3
+ before do
4
+ @period = 60 # Use a long period; failures due to cache key rotation less likely
5
+ Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
6
+ Rack::Attack.conditional_throttle('login', :limit => 1, :period => @period) { |req| req.ip }
7
+ end
8
+
9
+ it('should have a throttle'){ Rack::Attack.throttles.key?('login') }
10
+ allow_ok_requests
11
+
12
+ describe 'a single successful request' do
13
+ before { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' }
14
+ it 'should not set the counter for one request' do
15
+ key = "rack::attack:#{Time.now.to_i/@period}:login:1.2.3.4"
16
+ Rack::Attack.cache.store.read(key).must_equal nil
17
+ end
18
+ end
19
+ describe "with 2 requests" do
20
+ before do
21
+ 2.times { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' }
22
+ end
23
+ it 'should not block the last request' do
24
+ last_response.status.must_equal 200
25
+ end
26
+ end
27
+
28
+ describe 'a successful request followed by failed request' do
29
+ before { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' }
30
+ it 'should increment counter for failed request' do
31
+ key = "rack::attack:#{Time.now.to_i/@period}:login:1.2.3.4"
32
+ Rack::Attack.increment_throttle_counter('login', '1.2.3.4')
33
+ Rack::Attack.cache.store.read(key).must_equal 1
34
+ end
35
+ end
36
+
37
+ describe 'a successful request followed by two failed request followed by successful request' do
38
+ before {
39
+ get '/', {}, 'REMOTE_ADDR' => '1.2.3.4'
40
+ @key = "rack::attack:#{Time.now.to_i/@period}:login:1.2.3.4"
41
+ Rack::Attack.increment_throttle_counter('login', '1.2.3.4')
42
+ Rack::Attack.increment_throttle_counter('login', '1.2.3.4')
43
+ }
44
+ it 'should increment counter for failed request' do
45
+
46
+ Rack::Attack.cache.store.read(@key).must_equal 2
47
+ get '/', {}, 'REMOTE_ADDR' => '1.2.3.4'
48
+ Rack::Attack.cache.store.read(@key).must_equal 2
49
+ last_response.status.must_equal 429
50
+ end
51
+ end
52
+ end
53
+
@@ -0,0 +1,10 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe Rack::Attack::StoreProxy::DalliProxy do
4
+
5
+ it 'should stub Dalli::Client#with on older clients' do
6
+ proxy = Rack::Attack::StoreProxy::DalliProxy.new(Class.new)
7
+ proxy.with {} # will not raise an error
8
+ end
9
+
10
+ end
@@ -0,0 +1,19 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe 'Rack::Attack' do
4
+ describe 'helpers' do
5
+ before do
6
+ class Rack::Attack::Request
7
+ def remote_ip
8
+ ip
9
+ end
10
+ end
11
+
12
+ Rack::Attack.whitelist('valid IP') do |req|
13
+ req.remote_ip == "127.0.0.1"
14
+ end
15
+ end
16
+
17
+ allow_ok_requests
18
+ end
19
+ end
@@ -0,0 +1,50 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe 'Rack::Attack' do
4
+ allow_ok_requests
5
+
6
+ describe 'blacklist' do
7
+ before do
8
+ @bad_ip = '1.2.3.4'
9
+ Rack::Attack.blacklist("ip #{@bad_ip}") {|req| req.ip == @bad_ip }
10
+ end
11
+
12
+ it('has a blacklist') { Rack::Attack.blacklists.key?("ip #{@bad_ip}") }
13
+
14
+ describe "a bad request" do
15
+ before { get '/', {}, 'REMOTE_ADDR' => @bad_ip }
16
+ it "should return a blacklist response" do
17
+ get '/', {}, 'REMOTE_ADDR' => @bad_ip
18
+ last_response.status.must_equal 403
19
+ last_response.body.must_equal "Forbidden\n"
20
+ end
21
+ it "should tag the env" do
22
+ last_request.env['rack.attack.matched'].must_equal "ip #{@bad_ip}"
23
+ last_request.env['rack.attack.match_type'].must_equal :blacklist
24
+ end
25
+
26
+ allow_ok_requests
27
+ end
28
+
29
+ describe "and whitelist" do
30
+ before do
31
+ @good_ua = 'GoodUA'
32
+ Rack::Attack.whitelist("good ua") {|req| req.user_agent == @good_ua }
33
+ end
34
+
35
+ it('has a whitelist'){ Rack::Attack.whitelists.key?("good ua") }
36
+ describe "with a request match both whitelist & blacklist" do
37
+ before { get '/', {}, 'REMOTE_ADDR' => @bad_ip, 'HTTP_USER_AGENT' => @good_ua }
38
+ it "should allow whitelists before blacklists" do
39
+ get '/', {}, 'REMOTE_ADDR' => @bad_ip, 'HTTP_USER_AGENT' => @good_ua
40
+ last_response.status.must_equal 200
41
+ end
42
+ it "should tag the env" do
43
+ last_request.env['rack.attack.matched'].must_equal 'good ua'
44
+ last_request.env['rack.attack.match_type'].must_equal :whitelist
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ end
@@ -0,0 +1,64 @@
1
+ require_relative 'spec_helper'
2
+ describe 'Rack::Attack.throttle' do
3
+ before do
4
+ @period = 60 # Use a long period; failures due to cache key rotation less likely
5
+ Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
6
+ Rack::Attack.throttle('ip/sec', :limit => 1, :period => @period) { |req| req.ip }
7
+ end
8
+
9
+ it('should have a throttle'){ Rack::Attack.throttles.key?('ip/sec') }
10
+ allow_ok_requests
11
+
12
+ describe 'a single request' do
13
+ before { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' }
14
+ it 'should set the counter for one request' do
15
+ key = "rack::attack:#{Time.now.to_i/@period}:ip/sec:1.2.3.4"
16
+ Rack::Attack.cache.store.read(key).must_equal 1
17
+ end
18
+
19
+ it 'should populate throttle data' do
20
+ data = { :count => 1, :limit => 1, :period => @period }
21
+ last_request.env['rack.attack.throttle_data']['ip/sec'].must_equal data
22
+ end
23
+ end
24
+ describe "with 2 requests" do
25
+ before do
26
+ 2.times { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' }
27
+ end
28
+ it 'should block the last request' do
29
+ last_response.status.must_equal 429
30
+ end
31
+ it 'should tag the env' do
32
+ last_request.env['rack.attack.matched'].must_equal 'ip/sec'
33
+ last_request.env['rack.attack.match_type'].must_equal :throttle
34
+ last_request.env['rack.attack.match_data'].must_equal({:count => 2, :limit => 1, :period => @period})
35
+ last_request.env['rack.attack.match_discriminator'].must_equal('1.2.3.4')
36
+ end
37
+ it 'should set a Retry-After header' do
38
+ last_response.headers['Retry-After'].must_equal @period.to_s
39
+ end
40
+ end
41
+ end
42
+
43
+ describe 'Rack::Attack.throttle with limit as proc' do
44
+ before do
45
+ @period = 60 # Use a long period; failures due to cache key rotation less likely
46
+ Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
47
+ Rack::Attack.throttle('ip/sec', :limit => lambda {|req| 1}, :period => @period) { |req| req.ip }
48
+ end
49
+
50
+ allow_ok_requests
51
+
52
+ describe 'a single request' do
53
+ before { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' }
54
+ it 'should set the counter for one request' do
55
+ key = "rack::attack:#{Time.now.to_i/@period}:ip/sec:1.2.3.4"
56
+ Rack::Attack.cache.store.read(key).must_equal 1
57
+ end
58
+
59
+ it 'should populate throttle data' do
60
+ data = { :count => 1, :limit => 1, :period => @period }
61
+ last_request.env['rack.attack.throttle_data']['ip/sec'].must_equal data
62
+ end
63
+ end
64
+ end