rack-attack 2.3.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rack-attack might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b294cc1911f079c5df74d5325e01fabd2a207498
4
- data.tar.gz: 99306d1db94cfeab978bef7573e1455c0e2cc428
3
+ metadata.gz: ea9a8662af6ca32da955a04479518cefb8d391f1
4
+ data.tar.gz: a81aaf8306c6652e177d06ae0c6fd1ecc538e8fb
5
5
  SHA512:
6
- metadata.gz: 12a543ddffa7cc24893c3ee302071738e8f9e39fe063e9b6610492c41d03ba5179c43bffe50ff182e880fd936a1d970ab9f6a0d6c536c2db77e89b555dd253d2
7
- data.tar.gz: b439faf86fd536d941327aa03c0d4e2d344a527857f120e09e0ab0bdec9fbbcef686d716da9c56867d4ca277c20edf7d8313c6c79d7f01c4e09ff0d929092a7a
6
+ metadata.gz: 400df3037af9a335d07edd5968782300711c018ea01890e90a8802dc2c347a347c75b64c13433b81b8bb7345a87399a4765828ed1206c7c7ac4728c0b41fbb49
7
+ data.tar.gz: 1a37808e78845769b371341e20bd1c3f018d206b36c64e1b2569aa753375b2aa01460324e5a507579bb621b76ed7fd4e957c13c1809a9c7ac6d9c2d5bbecd097
data/README.md CHANGED
@@ -18,28 +18,28 @@ See the [Backing & Hacking blog post](http://www.kickstarter.com/backing-and-hac
18
18
  Install the [rack-attack](http://rubygems.org/gems/rack-attack) gem; or add it to you Gemfile with bundler:
19
19
 
20
20
  ```ruby
21
- # In your Gemfile
22
- gem 'rack-attack'
21
+ # In your Gemfile
22
+ gem 'rack-attack'
23
23
  ```
24
24
  Tell your app to use the Rack::Attack middleware.
25
25
  For Rails 3+ apps:
26
26
 
27
27
  ```ruby
28
- # In config/application.rb
29
- config.middleware.use Rack::Attack
28
+ # In config/application.rb
29
+ config.middleware.use Rack::Attack
30
30
  ```
31
31
 
32
32
  Or for Rackup files:
33
33
 
34
34
  ```ruby
35
- # In config.ru
36
- use Rack::Attack
35
+ # In config.ru
36
+ use Rack::Attack
37
37
  ```
38
38
 
39
39
  Optionally configure the cache store for throttling:
40
40
 
41
41
  ```ruby
42
- Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new # defaults to Rails.cache
42
+ Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new # defaults to Rails.cache
43
43
  ```
44
44
 
45
45
  Note that `Rack::Attack.cache` is only used for throttling; not blacklisting & whitelisting. Your cache store must implement `increment` and `write` like [ActiveSupport::Cache::Store](http://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html).
@@ -56,20 +56,20 @@ The Rack::Attack middleware compares each request against *whitelists*, *blackli
56
56
  The algorithm is actually more concise in code: See [Rack::Attack.call](https://github.com/kickstarter/rack-attack/blob/master/lib/rack/attack.rb):
57
57
 
58
58
  ```ruby
59
- def call(env)
60
- req = Rack::Request.new(env)
61
-
62
- if whitelisted?(req)
63
- @app.call(env)
64
- elsif blacklisted?(req)
65
- blacklisted_response[env]
66
- elsif throttled?(req)
67
- throttled_response[env]
68
- else
69
- tracked?(req)
70
- @app.call(env)
71
- end
72
- end
59
+ def call(env)
60
+ req = Rack::Request.new(env)
61
+
62
+ if whitelisted?(req)
63
+ @app.call(env)
64
+ elsif blacklisted?(req)
65
+ blacklisted_response[env]
66
+ elsif throttled?(req)
67
+ throttled_response[env]
68
+ else
69
+ tracked?(req)
70
+ @app.call(env)
71
+ end
72
+ end
73
73
  ```
74
74
 
75
75
  ## About Tracks
@@ -85,27 +85,27 @@ A [Rack::Request](http://rack.rubyforge.org/doc/classes/Rack/Request.html) objec
85
85
  ### Whitelists
86
86
 
87
87
  ```ruby
88
- # Always allow requests from localhost
89
- # (blacklist & throttles are skipped)
90
- Rack::Attack.whitelist('allow from localhost') do |req|
91
- # Requests are allowed if the return value is truthy
92
- '127.0.0.1' == req.ip
93
- end
88
+ # Always allow requests from localhost
89
+ # (blacklist & throttles are skipped)
90
+ Rack::Attack.whitelist('allow from localhost') do |req|
91
+ # Requests are allowed if the return value is truthy
92
+ '127.0.0.1' == req.ip
93
+ end
94
94
  ```
95
95
 
96
96
  ### Blacklists
97
97
 
98
98
  ```ruby
99
- # Block requests from 1.2.3.4
100
- Rack::Attack.blacklist('block 1.2.3.4') do |req|
101
- # Request are blocked if the return value is truthy
102
- '1.2.3.4' == req.ip
103
- end
104
-
105
- # Block logins from a bad user agent
106
- Rack::Attack.blacklist('block bad UA logins') do |req|
107
- req.path == '/login' && req.post? && req.user_agent == 'BadUA'
108
- end
99
+ # Block requests from 1.2.3.4
100
+ Rack::Attack.blacklist('block 1.2.3.4') do |req|
101
+ # Request are blocked if the return value is truthy
102
+ '1.2.3.4' == req.ip
103
+ end
104
+
105
+ # Block logins from a bad user agent
106
+ Rack::Attack.blacklist('block bad UA logins') do |req|
107
+ req.path == '/login' && req.post? && req.user_agent == 'BadUA'
108
+ end
109
109
  ```
110
110
 
111
111
  #### Fail2Ban
@@ -116,79 +116,79 @@ See the [fail2ban documentation](http://www.fail2ban.org/wiki/index.php/MANUAL_0
116
116
  how the parameters work.
117
117
 
118
118
  ```ruby
119
- # Block requests containing '/etc/password' in the params.
120
- # After 3 blocked requests in 10 minutes, block all requests from that IP for 5 minutes.
121
- Rack::Attack.blacklist('fail2ban pentesters') do |req|
122
- # `filter` returns truthy value if request fails, or if it's from a previously banned IP
123
- # so the request is blocked
124
- Rack::Attack::Fail2Ban.filter(req.ip, :maxretry => 3, :findtime => 10.minutes, :bantime => 5.minutes) do
125
- # The count for the IP is incremented if the return value is truthy.
126
- CGI.unescape(req.query_string) =~ %r{/etc/passwd}
127
- end
128
- end
119
+ # Block requests containing '/etc/password' in the params.
120
+ # After 3 blocked requests in 10 minutes, block all requests from that IP for 5 minutes.
121
+ Rack::Attack.blacklist('fail2ban pentesters') do |req|
122
+ # `filter` returns truthy value if request fails, or if it's from a previously banned IP
123
+ # so the request is blocked
124
+ Rack::Attack::Fail2Ban.filter(req.ip, :maxretry => 3, :findtime => 10.minutes, :bantime => 5.minutes) do
125
+ # The count for the IP is incremented if the return value is truthy.
126
+ CGI.unescape(req.query_string) =~ %r{/etc/passwd}
127
+ end
128
+ end
129
129
  ```
130
130
 
131
131
  #### Allow2Ban
132
132
  `Allow2Ban.filter` works the same way as the `Fail2Ban.filter` except that it *allows* requests from misbehaving
133
133
  clients until such time as they reach maxretry at which they are cut off as per normal.
134
134
  ```ruby
135
- # Lockout IP addresses that are hammering your login page.
136
- # After 20 requests in 1 minute, block all requests from that IP for 1 hour.
137
- Rack::Attack.blacklist('allow2ban login scrapers') do |req|
138
- # `filter` returns false value if request is to your login page (but still
139
- # increments the count) so request below the limit are not blocked until
140
- # they hit the limit. At that point, filter will return true and block.
141
- Rack::Attack::Allow2Ban.filter(req.ip, :maxretry => 20, :findtime => 1.minute, :bantime => 1.hour) do
142
- # The count for the IP is incremented if the return value is truthy.
143
- req.path = '/login' and req.method == 'post'
144
- end
145
- end
135
+ # Lockout IP addresses that are hammering your login page.
136
+ # After 20 requests in 1 minute, block all requests from that IP for 1 hour.
137
+ Rack::Attack.blacklist('allow2ban login scrapers') do |req|
138
+ # `filter` returns false value if request is to your login page (but still
139
+ # increments the count) so request below the limit are not blocked until
140
+ # they hit the limit. At that point, filter will return true and block.
141
+ Rack::Attack::Allow2Ban.filter(req.ip, :maxretry => 20, :findtime => 1.minute, :bantime => 1.hour) do
142
+ # The count for the IP is incremented if the return value is truthy.
143
+ req.path == '/login' and req.post?
144
+ end
145
+ end
146
146
  ```
147
147
 
148
148
 
149
149
  ### Throttles
150
150
 
151
151
  ```ruby
152
- # Throttle requests to 5 requests per second per ip
153
- Rack::Attack.throttle('req/ip', :limit => 5, :period => 1.second) do |req|
154
- # If the return value is truthy, the cache key for the return value
155
- # is incremented and compared with the limit. In this case:
156
- # "rack::attack:#{Time.now.to_i/1.second}:req/ip:#{req.ip}"
157
- #
158
- # If falsy, the cache key is neither incremented nor checked.
159
-
160
- req.ip
161
- end
162
-
163
- # Throttle login attempts for a given email parameter to 6 reqs/minute
164
- # Return the email as a discriminator on POST /login requests
165
- Rack::Attack.throttle('logins/email', :limit => 6, :period => 60.seconds) do |req|
166
- req.params['email'] if req.path == '/login' && req.post?
167
- end
168
-
169
- # You can also set a limit using a proc instead of a number. For
170
- # instance, after Rack::Auth::Basic has authenticated the user:
171
- limit_based_on_proc = proc {|req| req.env["REMOTE_USER"] == "admin" ? 100 : 1}
172
- Rack::Attack.throttle('req/ip', :limit => limit_based_on_proc, :period => 1.second) do |req|
173
- req.ip
174
- end
152
+ # Throttle requests to 5 requests per second per ip
153
+ Rack::Attack.throttle('req/ip', :limit => 5, :period => 1.second) do |req|
154
+ # If the return value is truthy, the cache key for the return value
155
+ # is incremented and compared with the limit. In this case:
156
+ # "rack::attack:#{Time.now.to_i/1.second}:req/ip:#{req.ip}"
157
+ #
158
+ # If falsy, the cache key is neither incremented nor checked.
159
+
160
+ req.ip
161
+ end
162
+
163
+ # Throttle login attempts for a given email parameter to 6 reqs/minute
164
+ # Return the email as a discriminator on POST /login requests
165
+ Rack::Attack.throttle('logins/email', :limit => 6, :period => 60.seconds) do |req|
166
+ req.params['email'] if req.path == '/login' && req.post?
167
+ end
168
+
169
+ # You can also set a limit using a proc instead of a number. For
170
+ # instance, after Rack::Auth::Basic has authenticated the user:
171
+ limit_based_on_proc = proc {|req| req.env["REMOTE_USER"] == "admin" ? 100 : 1}
172
+ Rack::Attack.throttle('req/ip', :limit => limit_based_on_proc, :period => 1.second) do |req|
173
+ req.ip
174
+ end
175
175
  ```
176
176
 
177
177
  ### Tracks
178
178
 
179
179
  ```ruby
180
- # Track requests from a special user agent
181
- Rack::Attack.track("special_agent") do |req|
182
- req.user_agent == "SpecialAgent"
183
- end
184
-
185
- # Track it using ActiveSupport::Notification
186
- ActiveSupport::Notifications.subscribe("rack.attack") do |name, start, finish, request_id, req|
187
- if req.env['rack.attack.matched'] == "special_agent" && req.env['rack.attack.match_type'] == :track
188
- Rails.logger.info "special_agent: #{req.path}"
189
- STATSD.increment("special_agent")
190
- end
191
- end
180
+ # Track requests from a special user agent
181
+ Rack::Attack.track("special_agent") do |req|
182
+ req.user_agent == "SpecialAgent"
183
+ end
184
+
185
+ # Track it using ActiveSupport::Notification
186
+ ActiveSupport::Notifications.subscribe("rack.attack") do |name, start, finish, request_id, req|
187
+ if req.env['rack.attack.matched'] == "special_agent" && req.env['rack.attack.match_type'] == :track
188
+ Rails.logger.info "special_agent: #{req.path}"
189
+ STATSD.increment("special_agent")
190
+ end
191
+ end
192
192
  ```
193
193
 
194
194
  ## Responses
@@ -196,30 +196,30 @@ clients until such time as they reach maxretry at which they are cut off as per
196
196
  Customize the response of blacklisted and throttled requests using an object that adheres to the [Rack app interface](http://rack.rubyforge.org/doc/SPEC.html).
197
197
 
198
198
  ```ruby
199
- Rack::Attack.blacklisted_response = lambda do |env|
200
- # Using 503 because it may make attacker think that they have successfully
201
- # DOSed the site. Rack::Attack returns 401 for blacklists by default
202
- [ 503, {}, ['Blocked']]
203
- end
204
-
205
- Rack::Attack.throttled_response = lambda do |env|
206
- # name and other data about the matched throttle
207
- body = [
208
- env['rack.attack.matched'],
209
- env['rack.attack.match_type'],
210
- env['rack.attack.match_data']
211
- ].inspect
212
-
213
- # Using 503 because it may make attacker think that they have successfully
214
- # DOSed the site. Rack::Attack returns 429 for throttling by default
215
- [ 503, {}, [body]]
216
- end
199
+ Rack::Attack.blacklisted_response = lambda do |env|
200
+ # Using 503 because it may make attacker think that they have successfully
201
+ # DOSed the site. Rack::Attack returns 403 for blacklists by default
202
+ [ 503, {}, ['Blocked']]
203
+ end
204
+
205
+ Rack::Attack.throttled_response = lambda do |env|
206
+ # name and other data about the matched throttle
207
+ body = [
208
+ env['rack.attack.matched'],
209
+ env['rack.attack.match_type'],
210
+ env['rack.attack.match_data']
211
+ ].inspect
212
+
213
+ # Using 503 because it may make attacker think that they have successfully
214
+ # DOSed the site. Rack::Attack returns 429 for throttling by default
215
+ [ 503, {}, [body]]
216
+ end
217
217
  ```
218
218
 
219
219
  For responses that did not exceed a throttle limit, Rack::Attack annotates the env with match data:
220
220
 
221
221
  ```ruby
222
- request.env['rack.attack.throttle_data'][name] # => { :count => n, :period => p, :limit => l }
222
+ request.env['rack.attack.throttle_data'][name] # => { :count => n, :period => p, :limit => l }
223
223
  ```
224
224
 
225
225
  ## Logging & Instrumentation
@@ -229,9 +229,9 @@ Rack::Attack uses the [ActiveSupport::Notifications](http://api.rubyonrails.org/
229
229
  You can subscribe to 'rack.attack' events and log it, graph it, etc:
230
230
 
231
231
  ```ruby
232
- ActiveSupport::Notifications.subscribe('rack.attack') do |name, start, finish, request_id, req|
233
- puts req.inspect
234
- end
232
+ ActiveSupport::Notifications.subscribe('rack.attack') do |name, start, finish, request_id, req|
233
+ puts req.inspect
234
+ end
235
235
  ```
236
236
 
237
237
  ## Testing
@@ -250,7 +250,7 @@ so try to keep the number of throttle checks per request low.
250
250
  If a request is blacklisted or throttled, the response is a very simple Rack response.
251
251
  A single typical ruby web server thread can block several hundred requests per second.
252
252
 
253
- Rack::Attack complements tools like `iptables` and nginx's [limit_zone module](http://wiki.nginx.org/HttpLimitZoneModule).
253
+ Rack::Attack complements tools like `iptables` and nginx's [limit_conn_zone module](http://nginx.org/en/docs/http/ngx_http_limit_conn_module.html#limit_conn_zone).
254
254
 
255
255
  ## Motivation
256
256
 
data/Rakefile CHANGED
@@ -2,8 +2,17 @@ require "rubygems"
2
2
  require "bundler/setup"
3
3
  require 'rake/testtask'
4
4
 
5
- Rake::TestTask.new do |t|
6
- t.pattern = "spec/*_spec.rb"
5
+ namespace :test do
6
+ Rake::TestTask.new(:units) do |t|
7
+ t.pattern = "spec/*_spec.rb"
8
+ end
9
+
10
+ Rake::TestTask.new(:integration) do |t|
11
+ t.pattern = "spec/integration/*_spec.rb"
12
+ end
7
13
  end
8
14
 
15
+ desc 'Run tests'
16
+ task :test => %w[test:units test:integration]
17
+
9
18
  task :default => :test
data/lib/rack/attack.rb CHANGED
@@ -40,7 +40,7 @@ module Rack::Attack
40
40
 
41
41
  # Set defaults
42
42
  @notifier ||= ActiveSupport::Notifications if defined?(ActiveSupport::Notifications)
43
- @blacklisted_response ||= lambda {|env| [401, {}, ["Unauthorized\n"]] }
43
+ @blacklisted_response ||= lambda {|env| [403, {}, ["Forbidden\n"]] }
44
44
  @throttled_response ||= lambda {|env|
45
45
  retry_after = env['rack.attack.match_data'][:period] rescue nil
46
46
  [429, {'Retry-After' => retry_after.to_s}, ["Retry later\n"]]
@@ -26,6 +26,8 @@ module Rack
26
26
 
27
27
  def read(key)
28
28
  self.get(key)
29
+ rescue Redis::BaseError
30
+ nil
29
31
  end
30
32
 
31
33
  def write(key, value, options={})
@@ -34,6 +36,8 @@ module Rack
34
36
  else
35
37
  self.set(key, value)
36
38
  end
39
+ rescue Redis::BaseError
40
+ nil
37
41
  end
38
42
 
39
43
  def increment(key, amount, options={})
@@ -43,6 +47,8 @@ module Rack
43
47
  self.expire(key, options[:expires_in]) if options[:expires_in]
44
48
  end
45
49
  count.value if count
50
+ rescue Redis::BaseError
51
+ nil
46
52
  end
47
53
 
48
54
  end
@@ -1,5 +1,5 @@
1
1
  module Rack
2
2
  module Attack
3
- VERSION = '2.3.0'
3
+ VERSION = '3.0.0'
4
4
  end
5
5
  end
@@ -83,7 +83,7 @@ describe 'Rack::Attack.Allow2Ban' do
83
83
  end
84
84
 
85
85
  it 'fails' do
86
- last_response.status.must_equal 401
86
+ last_response.status.must_equal 403
87
87
  end
88
88
 
89
89
  it 'does not increase fail count' do
@@ -103,7 +103,7 @@ describe 'Rack::Attack.Allow2Ban' do
103
103
  end
104
104
 
105
105
  it 'fails' do
106
- last_response.status.must_equal 401
106
+ last_response.status.must_equal 403
107
107
  end
108
108
 
109
109
  it 'does not increase fail count' do
@@ -24,7 +24,7 @@ describe 'Rack::Attack.Fail2Ban' do
24
24
  describe 'when not at maxretry' do
25
25
  before { get '/?foo=OMGHAX', {}, 'REMOTE_ADDR' => '1.2.3.4' }
26
26
  it 'fails' do
27
- last_response.status.must_equal 401
27
+ last_response.status.must_equal 403
28
28
  end
29
29
 
30
30
  it 'increases fail count' do
@@ -46,7 +46,7 @@ describe 'Rack::Attack.Fail2Ban' do
46
46
  end
47
47
 
48
48
  it 'fails' do
49
- last_response.status.must_equal 401
49
+ last_response.status.must_equal 403
50
50
  end
51
51
 
52
52
  it 'increases fail count' do
@@ -83,7 +83,7 @@ describe 'Rack::Attack.Fail2Ban' do
83
83
  end
84
84
 
85
85
  it 'fails' do
86
- last_response.status.must_equal 401
86
+ last_response.status.must_equal 403
87
87
  end
88
88
 
89
89
  it 'does not increase fail count' do
@@ -103,7 +103,7 @@ describe 'Rack::Attack.Fail2Ban' do
103
103
  end
104
104
 
105
105
  it 'fails' do
106
- last_response.status.must_equal 401
106
+ last_response.status.must_equal 403
107
107
  end
108
108
 
109
109
  it 'does not increase fail count' do
@@ -0,0 +1,111 @@
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
+ cache_stores = [
19
+ ActiveSupport::Cache::MemoryStore.new,
20
+ ActiveSupport::Cache::DalliStore.new("localhost"),
21
+ ActiveSupport::Cache::RedisStore.new("localhost"),
22
+ Redis::Store.new
23
+ ]
24
+
25
+ cache_stores.each do |store|
26
+ store = Rack::Attack::StoreProxy.build(store)
27
+ describe "with #{store.class}" do
28
+
29
+ before {
30
+ @cache ||= Rack::Attack::Cache.new
31
+ @key = "rack::attack:cache-test-key"
32
+ @expires_in = 1
33
+ @cache.store = store
34
+ delete(@key)
35
+ }
36
+
37
+ after { delete(@key) }
38
+
39
+ describe "do_count once" do
40
+ it "should be 1" do
41
+ @cache.send(:do_count, @key, @expires_in).must_equal 1
42
+ end
43
+ end
44
+
45
+ describe "do_count twice" do
46
+ it "must be 2" do
47
+ @cache.send(:do_count, @key, @expires_in)
48
+ @cache.send(:do_count, @key, @expires_in).must_equal 2
49
+ end
50
+ end
51
+ describe "do_count after expires_in" do
52
+ it "must be 1" do
53
+ @cache.send(:do_count, @key, @expires_in)
54
+ sleep_until_expired
55
+ @cache.send(:do_count, @key, @expires_in).must_equal 1
56
+ end
57
+ end
58
+
59
+ describe "write" do
60
+ it "should write a value to the store with prefix" do
61
+ @cache.write("cache-test-key", "foobar", 1)
62
+ store.read(@key).must_equal "foobar"
63
+ end
64
+ end
65
+
66
+ describe "write after expiry" do
67
+ it "must not have a value" do
68
+ @cache.write("cache-test-key", "foobar", @expires_in)
69
+ sleep_until_expired
70
+ store.read(@key).must_be :nil?
71
+ end
72
+ end
73
+
74
+ describe "read" do
75
+ it "must read the value with a prefix" do
76
+ store.write(@key, "foobar", :expires_in => @expires_in)
77
+ @cache.read("cache-test-key").must_equal "foobar"
78
+ end
79
+ end
80
+ end
81
+
82
+ end
83
+
84
+ describe "should not error if redis is not running" do
85
+ before {
86
+ @cache = Rack::Attack::Cache.new
87
+ @key = "rack::attack:cache-test-key"
88
+ @expires_in = 1
89
+ # Use presumably unused port for Redis client
90
+ @cache.store = ActiveSupport::Cache::RedisStore.new(:host => '127.0.0.1', :port => 3333)
91
+ }
92
+ describe "write" do
93
+ it "should not raise exception" do
94
+ @cache.write("cache-test-key", "foobar", 1)
95
+ end
96
+ end
97
+
98
+ describe "read" do
99
+ it "should not raise exception" do
100
+ @cache.read("cache-test-key")
101
+ end
102
+ end
103
+
104
+ describe "do_count" do
105
+ it "should not raise exception" do
106
+ @cache.send(:do_count, @key, @expires_in)
107
+ end
108
+ end
109
+ end
110
+
111
+ end
@@ -15,7 +15,8 @@ describe 'Rack::Attack' do
15
15
  before { get '/', {}, 'REMOTE_ADDR' => @bad_ip }
16
16
  it "should return a blacklist response" do
17
17
  get '/', {}, 'REMOTE_ADDR' => @bad_ip
18
- last_response.status.must_equal 401
18
+ last_response.status.must_equal 403
19
+ last_response.body.must_equal "Forbidden\n"
19
20
  end
20
21
  it "should tag the env" do
21
22
  last_request.env['rack.attack.matched'].must_equal "ip #{@bad_ip}"
metadata CHANGED
@@ -1,125 +1,111 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-attack
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron Suggs
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-10-11 00:00:00.000000000 Z
11
+ date: 2014-03-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '>='
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: minitest
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '>='
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rack-test
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - '>='
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - '>='
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rake
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - '>='
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - '>='
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: activesupport
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - '>='
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
75
  version: 3.0.0
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - '>='
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: 3.0.0
83
- - !ruby/object:Gem::Dependency
84
- name: debugger
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - ~>
88
- - !ruby/object:Gem::Version
89
- version: '1.5'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ~>
95
- - !ruby/object:Gem::Version
96
- version: '1.5'
97
83
  - !ruby/object:Gem::Dependency
98
84
  name: redis-activesupport
99
85
  requirement: !ruby/object:Gem::Requirement
100
86
  requirements:
101
- - - '>='
87
+ - - ">="
102
88
  - !ruby/object:Gem::Version
103
89
  version: '0'
104
90
  type: :development
105
91
  prerelease: false
106
92
  version_requirements: !ruby/object:Gem::Requirement
107
93
  requirements:
108
- - - '>='
94
+ - - ">="
109
95
  - !ruby/object:Gem::Version
110
96
  version: '0'
111
97
  - !ruby/object:Gem::Dependency
112
98
  name: dalli
113
99
  requirement: !ruby/object:Gem::Requirement
114
100
  requirements:
115
- - - '>='
101
+ - - ">="
116
102
  - !ruby/object:Gem::Version
117
103
  version: '0'
118
104
  type: :development
119
105
  prerelease: false
120
106
  version_requirements: !ruby/object:Gem::Requirement
121
107
  requirements:
122
- - - '>='
108
+ - - ">="
123
109
  - !ruby/object:Gem::Version
124
110
  version: '0'
125
111
  description: A rack middleware for throttling and blocking abusive requests
@@ -128,6 +114,9 @@ executables: []
128
114
  extensions: []
129
115
  extra_rdoc_files: []
130
116
  files:
117
+ - README.md
118
+ - Rakefile
119
+ - lib/rack/attack.rb
131
120
  - lib/rack/attack/allow2ban.rb
132
121
  - lib/rack/attack/blacklist.rb
133
122
  - lib/rack/attack/cache.rb
@@ -138,12 +127,9 @@ files:
138
127
  - lib/rack/attack/track.rb
139
128
  - lib/rack/attack/version.rb
140
129
  - lib/rack/attack/whitelist.rb
141
- - lib/rack/attack.rb
142
- - Rakefile
143
- - README.md
144
130
  - spec/allow2ban_spec.rb
145
131
  - spec/fail2ban_spec.rb
146
- - spec/rack_attack_cache_spec.rb
132
+ - spec/integration/rack_attack_cache_spec.rb
147
133
  - spec/rack_attack_spec.rb
148
134
  - spec/rack_attack_throttle_spec.rb
149
135
  - spec/rack_attack_track_spec.rb
@@ -154,29 +140,29 @@ licenses:
154
140
  metadata: {}
155
141
  post_install_message:
156
142
  rdoc_options:
157
- - --charset=UTF-8
143
+ - "--charset=UTF-8"
158
144
  require_paths:
159
145
  - lib
160
146
  required_ruby_version: !ruby/object:Gem::Requirement
161
147
  requirements:
162
- - - '>='
148
+ - - ">="
163
149
  - !ruby/object:Gem::Version
164
150
  version: 1.9.2
165
151
  required_rubygems_version: !ruby/object:Gem::Requirement
166
152
  requirements:
167
- - - '>='
153
+ - - ">="
168
154
  - !ruby/object:Gem::Version
169
155
  version: '0'
170
156
  requirements: []
171
157
  rubyforge_project:
172
- rubygems_version: 2.1.6
158
+ rubygems_version: 2.2.2
173
159
  signing_key:
174
160
  specification_version: 4
175
161
  summary: Block & throttle abusive requests
176
162
  test_files:
177
163
  - spec/allow2ban_spec.rb
178
164
  - spec/fail2ban_spec.rb
179
- - spec/rack_attack_cache_spec.rb
165
+ - spec/integration/rack_attack_cache_spec.rb
180
166
  - spec/rack_attack_spec.rb
181
167
  - spec/rack_attack_throttle_spec.rb
182
168
  - spec/rack_attack_track_spec.rb
@@ -1,84 +0,0 @@
1
- require_relative 'spec_helper'
2
-
3
- if ENV['TEST_INTEGRATION']
4
- describe Rack::Attack::Cache do
5
- def delete(key)
6
- if @cache.store.respond_to?(:delete)
7
- @cache.store.delete(key)
8
- else
9
- @cache.store.del(key)
10
- end
11
- end
12
-
13
- require 'active_support/cache/dalli_store'
14
- require 'active_support/cache/redis_store'
15
- cache_stores = [
16
- ActiveSupport::Cache::MemoryStore.new,
17
- ActiveSupport::Cache::DalliStore.new("localhost"),
18
- ActiveSupport::Cache::RedisStore.new("localhost"),
19
- Redis::Store.new
20
- ]
21
-
22
- cache_stores.each do |store|
23
- store = Rack::Attack::StoreProxy.build(store)
24
- describe "with #{store.class}" do
25
-
26
- before {
27
- @cache ||= Rack::Attack::Cache.new
28
- @key = "rack::attack:cache-test-key"
29
- @expires_in = 1
30
- @cache.store = store
31
- delete(@key)
32
- }
33
-
34
- after { delete(@key) }
35
-
36
- describe "do_count once" do
37
- it "should be 1" do
38
- @cache.send(:do_count, @key, @expires_in).must_equal 1
39
- end
40
- end
41
-
42
- describe "do_count twice" do
43
- it "must be 2" do
44
- @cache.send(:do_count, @key, @expires_in)
45
- @cache.send(:do_count, @key, @expires_in).must_equal 2
46
- end
47
- end
48
- describe "do_count after expires_in" do
49
- it "must be 1" do
50
- @cache.send(:do_count, @key, @expires_in)
51
- sleep @expires_in # sigh
52
- @cache.send(:do_count, @key, @expires_in).must_equal 1
53
- end
54
- end
55
-
56
- describe "write" do
57
- it "should write a value to the store with prefix" do
58
- @cache.write("cache-test-key", "foobar", 1)
59
- store.read(@key).must_equal "foobar"
60
- end
61
- end
62
-
63
- describe "write after expiry" do
64
- it "must not have a value" do
65
- @cache.write("cache-test-key", "foobar", @expires_in)
66
- sleep @expires_in # tick... tick... tick...
67
- store.read(@key).must_be :nil?
68
- end
69
- end
70
-
71
- describe "read" do
72
- it "must read the value with a prefix" do
73
- store.write(@key, "foobar", :expires_in => @expires_in)
74
- @cache.read("cache-test-key").must_equal "foobar"
75
- end
76
- end
77
- end
78
-
79
- end
80
-
81
- end
82
- else
83
- puts 'Skipping cache store integration tests (set ENV["TEST_INTEGRATION"] to enable)'
84
- end