rack-attack 4.3.1 → 5.4.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.
- checksums.yaml +5 -5
- data/README.md +230 -113
- data/Rakefile +11 -3
- data/bin/setup +8 -0
- data/lib/rack/attack.rb +121 -48
- data/lib/rack/attack/allow2ban.rb +2 -1
- data/lib/rack/attack/{whitelist.rb → blocklist.rb} +2 -3
- data/lib/rack/attack/cache.rb +24 -5
- data/lib/rack/attack/check.rb +6 -8
- data/lib/rack/attack/fail2ban.rb +3 -2
- data/lib/rack/attack/path_normalizer.rb +6 -11
- data/lib/rack/attack/request.rb +1 -1
- data/lib/rack/attack/{blacklist.rb → safelist.rb} +2 -4
- data/lib/rack/attack/store_proxy.rb +13 -12
- data/lib/rack/attack/store_proxy/dalli_proxy.rb +2 -3
- data/lib/rack/attack/store_proxy/mem_cache_proxy.rb +50 -0
- data/lib/rack/attack/store_proxy/mem_cache_store_proxy.rb +19 -0
- data/lib/rack/attack/store_proxy/redis_cache_store_proxy.rb +35 -0
- data/lib/rack/attack/store_proxy/redis_proxy.rb +54 -0
- data/lib/rack/attack/store_proxy/redis_store_proxy.rb +5 -24
- data/lib/rack/attack/throttle.rb +16 -12
- data/lib/rack/attack/track.rb +3 -3
- data/lib/rack/attack/version.rb +1 -1
- data/spec/acceptance/allow2ban_spec.rb +71 -0
- data/spec/acceptance/blocking_ip_spec.rb +38 -0
- data/spec/acceptance/blocking_spec.rb +41 -0
- data/spec/acceptance/blocking_subnet_spec.rb +44 -0
- data/spec/acceptance/cache_store_config_for_allow2ban_spec.rb +126 -0
- data/spec/acceptance/cache_store_config_for_fail2ban_spec.rb +121 -0
- data/spec/acceptance/cache_store_config_for_throttle_spec.rb +48 -0
- data/spec/acceptance/cache_store_config_with_rails_spec.rb +31 -0
- data/spec/acceptance/customizing_blocked_response_spec.rb +41 -0
- data/spec/acceptance/customizing_throttled_response_spec.rb +59 -0
- data/spec/acceptance/extending_request_object_spec.rb +34 -0
- data/spec/acceptance/fail2ban_spec.rb +76 -0
- data/spec/acceptance/safelisting_ip_spec.rb +48 -0
- data/spec/acceptance/safelisting_spec.rb +53 -0
- data/spec/acceptance/safelisting_subnet_spec.rb +48 -0
- data/spec/acceptance/stores/active_support_dalli_store_spec.rb +19 -0
- data/spec/acceptance/stores/active_support_mem_cache_store_pooled_spec.rb +22 -0
- data/spec/acceptance/stores/active_support_mem_cache_store_spec.rb +18 -0
- data/spec/acceptance/stores/active_support_memory_store_spec.rb +16 -0
- data/spec/acceptance/stores/active_support_redis_cache_store_pooled_spec.rb +18 -0
- data/spec/acceptance/stores/active_support_redis_cache_store_spec.rb +18 -0
- data/spec/acceptance/stores/active_support_redis_store_spec.rb +18 -0
- data/spec/acceptance/stores/connection_pool_dalli_client_spec.rb +22 -0
- data/spec/acceptance/stores/dalli_client_spec.rb +19 -0
- data/spec/acceptance/stores/redis_spec.rb +20 -0
- data/spec/acceptance/stores/redis_store_spec.rb +18 -0
- data/spec/acceptance/throttling_spec.rb +159 -0
- data/spec/acceptance/track_spec.rb +27 -0
- data/spec/acceptance/track_throttle_spec.rb +53 -0
- data/spec/allow2ban_spec.rb +10 -9
- data/spec/fail2ban_spec.rb +12 -10
- data/spec/integration/offline_spec.rb +21 -23
- data/spec/rack_attack_dalli_proxy_spec.rb +0 -2
- data/spec/rack_attack_request_spec.rb +2 -2
- data/spec/rack_attack_spec.rb +53 -18
- data/spec/rack_attack_throttle_spec.rb +45 -13
- data/spec/rack_attack_track_spec.rb +11 -8
- data/spec/spec_helper.rb +35 -14
- data/spec/support/cache_store_helper.rb +82 -0
- metadata +161 -61
- data/spec/integration/rack_attack_cache_spec.rb +0 -119
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require_relative "../spec_helper"
|
|
2
|
+
|
|
3
|
+
describe "Blocking an IP" do
|
|
4
|
+
before do
|
|
5
|
+
Rack::Attack.blocklist_ip("1.2.3.4")
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
it "forbids request if IP matches" do
|
|
9
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
|
10
|
+
|
|
11
|
+
assert_equal 403, last_response.status
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "succeeds if IP doesn't match" do
|
|
15
|
+
get "/", {}, "REMOTE_ADDR" => "5.6.7.8"
|
|
16
|
+
|
|
17
|
+
assert_equal 200, last_response.status
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "notifies when the request is blocked" do
|
|
21
|
+
notified = false
|
|
22
|
+
notification_type = nil
|
|
23
|
+
|
|
24
|
+
ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, request|
|
|
25
|
+
notified = true
|
|
26
|
+
notification_type = request.env["rack.attack.match_type"]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
get "/", {}, "REMOTE_ADDR" => "5.6.7.8"
|
|
30
|
+
|
|
31
|
+
refute notified
|
|
32
|
+
|
|
33
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
|
34
|
+
|
|
35
|
+
assert notified
|
|
36
|
+
assert_equal :blocklist, notification_type
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require_relative "../spec_helper"
|
|
2
|
+
|
|
3
|
+
describe "#blocklist" do
|
|
4
|
+
before do
|
|
5
|
+
Rack::Attack.blocklist("block 1.2.3.4") do |request|
|
|
6
|
+
request.ip == "1.2.3.4"
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it "forbids request if blocklist condition is true" do
|
|
11
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
|
12
|
+
|
|
13
|
+
assert_equal 403, last_response.status
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "succeeds if blocklist condition is false" do
|
|
17
|
+
get "/", {}, "REMOTE_ADDR" => "5.6.7.8"
|
|
18
|
+
|
|
19
|
+
assert_equal 200, last_response.status
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "notifies when the request is blocked" do
|
|
23
|
+
notification_matched = nil
|
|
24
|
+
notification_type = nil
|
|
25
|
+
|
|
26
|
+
ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, request|
|
|
27
|
+
notification_matched = request.env["rack.attack.matched"]
|
|
28
|
+
notification_type = request.env["rack.attack.match_type"]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
get "/", {}, "REMOTE_ADDR" => "5.6.7.8"
|
|
32
|
+
|
|
33
|
+
assert_nil notification_matched
|
|
34
|
+
assert_nil notification_type
|
|
35
|
+
|
|
36
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
|
37
|
+
|
|
38
|
+
assert_equal "block 1.2.3.4", notification_matched
|
|
39
|
+
assert_equal :blocklist, notification_type
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require_relative "../spec_helper"
|
|
2
|
+
|
|
3
|
+
describe "Blocking an IP subnet" do
|
|
4
|
+
before do
|
|
5
|
+
Rack::Attack.blocklist_ip("1.2.3.4/31")
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
it "forbids request if IP is inside the subnet" do
|
|
9
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
|
10
|
+
|
|
11
|
+
assert_equal 403, last_response.status
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "forbids request for another IP in the subnet" do
|
|
15
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.5"
|
|
16
|
+
|
|
17
|
+
assert_equal 403, last_response.status
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "succeeds if IP is outside the subnet" do
|
|
21
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.6"
|
|
22
|
+
|
|
23
|
+
assert_equal 200, last_response.status
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "notifies when the request is blocked" do
|
|
27
|
+
notified = false
|
|
28
|
+
notification_type = nil
|
|
29
|
+
|
|
30
|
+
ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, request|
|
|
31
|
+
notified = true
|
|
32
|
+
notification_type = request.env["rack.attack.match_type"]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
get "/", {}, "REMOTE_ADDR" => "5.6.7.8"
|
|
36
|
+
|
|
37
|
+
refute notified
|
|
38
|
+
|
|
39
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
|
40
|
+
|
|
41
|
+
assert notified
|
|
42
|
+
assert_equal :blocklist, notification_type
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
require_relative "../spec_helper"
|
|
2
|
+
require "minitest/stub_const"
|
|
3
|
+
|
|
4
|
+
describe "Cache store config when using allow2ban" do
|
|
5
|
+
before do
|
|
6
|
+
Rack::Attack.blocklist("allow2ban pentesters") do |request|
|
|
7
|
+
Rack::Attack::Allow2Ban.filter(request.ip, maxretry: 2, findtime: 30, bantime: 60) do
|
|
8
|
+
request.path.include?("scarce-resource")
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "gives semantic error if no store was configured" do
|
|
14
|
+
assert_raises(Rack::Attack::MissingStoreError) do
|
|
15
|
+
get "/scarce-resource"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "gives semantic error if store is missing #read method" do
|
|
20
|
+
raised_exception = nil
|
|
21
|
+
|
|
22
|
+
fake_store_class = Class.new do
|
|
23
|
+
def write(key, value)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def increment(key, count, options = {})
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
Object.stub_const(:FakeStore, fake_store_class) do
|
|
31
|
+
Rack::Attack.cache.store = FakeStore.new
|
|
32
|
+
|
|
33
|
+
raised_exception = assert_raises(Rack::Attack::MisconfiguredStoreError) do
|
|
34
|
+
get "/scarce-resource"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
assert_equal "Configured store FakeStore doesn't respond to #read method", raised_exception.message
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "gives semantic error if store is missing #write method" do
|
|
42
|
+
raised_exception = nil
|
|
43
|
+
|
|
44
|
+
fake_store_class = Class.new do
|
|
45
|
+
def read(key)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def increment(key, count, options = {})
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
Object.stub_const(:FakeStore, fake_store_class) do
|
|
53
|
+
Rack::Attack.cache.store = FakeStore.new
|
|
54
|
+
|
|
55
|
+
raised_exception = assert_raises(Rack::Attack::MisconfiguredStoreError) do
|
|
56
|
+
get "/scarce-resource"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
assert_equal "Configured store FakeStore doesn't respond to #write method", raised_exception.message
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "gives semantic error if store is missing #increment method" do
|
|
64
|
+
raised_exception = nil
|
|
65
|
+
|
|
66
|
+
fake_store_class = Class.new do
|
|
67
|
+
def read(key)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def write(key, value)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
Object.stub_const(:FakeStore, fake_store_class) do
|
|
75
|
+
Rack::Attack.cache.store = FakeStore.new
|
|
76
|
+
|
|
77
|
+
raised_exception = assert_raises(Rack::Attack::MisconfiguredStoreError) do
|
|
78
|
+
get "/scarce-resource"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
assert_equal "Configured store FakeStore doesn't respond to #increment method", raised_exception.message
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it "works with any object that responds to #read, #write and #increment" do
|
|
86
|
+
fake_store_class = Class.new do
|
|
87
|
+
attr_accessor :backend
|
|
88
|
+
|
|
89
|
+
def initialize
|
|
90
|
+
@backend = {}
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def read(key)
|
|
94
|
+
@backend[key]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def write(key, value, _options = {})
|
|
98
|
+
@backend[key] = value
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def increment(key, _count, _options = {})
|
|
102
|
+
@backend[key] ||= 0
|
|
103
|
+
@backend[key] += 1
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
Object.stub_const(:FakeStore, fake_store_class) do
|
|
108
|
+
Rack::Attack.cache.store = FakeStore.new
|
|
109
|
+
|
|
110
|
+
get "/"
|
|
111
|
+
assert_equal 200, last_response.status
|
|
112
|
+
|
|
113
|
+
get "/scarce-resource"
|
|
114
|
+
assert_equal 200, last_response.status
|
|
115
|
+
|
|
116
|
+
get "/scarce-resource"
|
|
117
|
+
assert_equal 200, last_response.status
|
|
118
|
+
|
|
119
|
+
get "/scarce-resource"
|
|
120
|
+
assert_equal 403, last_response.status
|
|
121
|
+
|
|
122
|
+
get "/"
|
|
123
|
+
assert_equal 403, last_response.status
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
require_relative "../spec_helper"
|
|
2
|
+
require "minitest/stub_const"
|
|
3
|
+
|
|
4
|
+
describe "Cache store config when using fail2ban" do
|
|
5
|
+
before do
|
|
6
|
+
Rack::Attack.blocklist("fail2ban pentesters") do |request|
|
|
7
|
+
Rack::Attack::Fail2Ban.filter(request.ip, maxretry: 2, findtime: 30, bantime: 60) do
|
|
8
|
+
request.path.include?("private-place")
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "gives semantic error if no store was configured" do
|
|
14
|
+
assert_raises(Rack::Attack::MissingStoreError) do
|
|
15
|
+
get "/private-place"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "gives semantic error if store is missing #read method" do
|
|
20
|
+
raised_exception = nil
|
|
21
|
+
|
|
22
|
+
fake_store_class = Class.new do
|
|
23
|
+
def write(key, value)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def increment(key, count, options = {})
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
Object.stub_const(:FakeStore, fake_store_class) do
|
|
31
|
+
Rack::Attack.cache.store = FakeStore.new
|
|
32
|
+
|
|
33
|
+
raised_exception = assert_raises(Rack::Attack::MisconfiguredStoreError) do
|
|
34
|
+
get "/private-place"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
assert_equal "Configured store FakeStore doesn't respond to #read method", raised_exception.message
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "gives semantic error if store is missing #write method" do
|
|
42
|
+
raised_exception = nil
|
|
43
|
+
|
|
44
|
+
fake_store_class = Class.new do
|
|
45
|
+
def read(key)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def increment(key, count, options = {})
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
Object.stub_const(:FakeStore, fake_store_class) do
|
|
53
|
+
Rack::Attack.cache.store = FakeStore.new
|
|
54
|
+
|
|
55
|
+
raised_exception = assert_raises(Rack::Attack::MisconfiguredStoreError) do
|
|
56
|
+
get "/private-place"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
assert_equal "Configured store FakeStore doesn't respond to #write method", raised_exception.message
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "gives semantic error if store is missing #increment method" do
|
|
64
|
+
raised_exception = nil
|
|
65
|
+
|
|
66
|
+
fake_store_class = Class.new do
|
|
67
|
+
def read(key)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def write(key, value)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
Object.stub_const(:FakeStore, fake_store_class) do
|
|
75
|
+
Rack::Attack.cache.store = FakeStore.new
|
|
76
|
+
|
|
77
|
+
raised_exception = assert_raises(Rack::Attack::MisconfiguredStoreError) do
|
|
78
|
+
get "/private-place"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
assert_equal "Configured store FakeStore doesn't respond to #increment method", raised_exception.message
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it "works with any object that responds to #read, #write and #increment" do
|
|
86
|
+
FakeStore = Class.new do
|
|
87
|
+
attr_accessor :backend
|
|
88
|
+
|
|
89
|
+
def initialize
|
|
90
|
+
@backend = {}
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def read(key)
|
|
94
|
+
@backend[key]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def write(key, value, _options = {})
|
|
98
|
+
@backend[key] = value
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def increment(key, _count, _options = {})
|
|
102
|
+
@backend[key] ||= 0
|
|
103
|
+
@backend[key] += 1
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
Rack::Attack.cache.store = FakeStore.new
|
|
108
|
+
|
|
109
|
+
get "/"
|
|
110
|
+
assert_equal 200, last_response.status
|
|
111
|
+
|
|
112
|
+
get "/private-place"
|
|
113
|
+
assert_equal 403, last_response.status
|
|
114
|
+
|
|
115
|
+
get "/private-place"
|
|
116
|
+
assert_equal 403, last_response.status
|
|
117
|
+
|
|
118
|
+
get "/"
|
|
119
|
+
assert_equal 403, last_response.status
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require_relative "../spec_helper"
|
|
2
|
+
|
|
3
|
+
describe "Cache store config when throttling without Rails" do
|
|
4
|
+
before do
|
|
5
|
+
Rack::Attack.throttle("by ip", limit: 1, period: 60) do |request|
|
|
6
|
+
request.ip
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it "gives semantic error if no store was configured" do
|
|
11
|
+
assert_raises(Rack::Attack::MissingStoreError) do
|
|
12
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "gives semantic error if incompatible store was configured" do
|
|
17
|
+
Rack::Attack.cache.store = Object.new
|
|
18
|
+
|
|
19
|
+
assert_raises(Rack::Attack::MisconfiguredStoreError) do
|
|
20
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "works with any object that responds to #increment" do
|
|
25
|
+
basic_store_class = Class.new do
|
|
26
|
+
attr_accessor :counts
|
|
27
|
+
|
|
28
|
+
def initialize
|
|
29
|
+
@counts = {}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def increment(key, _count, _options)
|
|
33
|
+
@counts[key] ||= 0
|
|
34
|
+
@counts[key] += 1
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
Rack::Attack.cache.store = basic_store_class.new
|
|
39
|
+
|
|
40
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
|
41
|
+
|
|
42
|
+
assert_equal 200, last_response.status
|
|
43
|
+
|
|
44
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
|
45
|
+
|
|
46
|
+
assert_equal 429, last_response.status
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require_relative "../spec_helper"
|
|
2
|
+
require "minitest/stub_const"
|
|
3
|
+
require "ostruct"
|
|
4
|
+
|
|
5
|
+
describe "Cache store config with Rails" do
|
|
6
|
+
before do
|
|
7
|
+
Rack::Attack.throttle("by ip", limit: 1, period: 60) do |request|
|
|
8
|
+
request.ip
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it "fails when Rails.cache is not set" do
|
|
13
|
+
Object.stub_const(:Rails, OpenStruct.new(cache: nil)) do
|
|
14
|
+
assert_raises(Rack::Attack::MissingStoreError) do
|
|
15
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "works when Rails.cache is set" do
|
|
21
|
+
Object.stub_const(:Rails, OpenStruct.new(cache: ActiveSupport::Cache::MemoryStore.new)) do
|
|
22
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
|
23
|
+
|
|
24
|
+
assert_equal 200, last_response.status
|
|
25
|
+
|
|
26
|
+
get "/", {}, "REMOTE_ADDR" => "1.2.3.4"
|
|
27
|
+
|
|
28
|
+
assert_equal 429, last_response.status
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|