Merge pull request #181 from renee-travisci/language_change_suggestion

Rename blacklist/whitelist to blocklist/safelist.
This commit is contained in:
Aaron Suggs 2016-07-04 21:08:18 -04:00 committed by GitHub
commit 4f462c33dc
13 changed files with 121 additions and 75 deletions

2
.gitignore vendored
View file

@ -3,3 +3,5 @@ bin
.bundle .bundle
*.gem *.gem
*.gemfile.lock *.gemfile.lock
.ruby-version
.ruby-gemset

View file

@ -27,7 +27,7 @@
## v4.2.0 - 26 Oct 2014 ## v4.2.0 - 26 Oct 2014
- Throttle's `period` argument now takes a proc as well as a number (thanks @gsamokovarov) - Throttle's `period` argument now takes a proc as well as a number (thanks @gsamokovarov)
- Invoke the `#call` method on `blacklist_response` and `throttle_response` instead of `#[]`, as per the Rack spec. (thanks @gsamokovarov) - Invoke the `#call` method on `blocklist_response` and `throttle_response` instead of `#[]`, as per the Rack spec. (thanks @gsamokovarov)
## v4.1.1 - 11 Sept 2014 ## v4.1.1 - 11 Sept 2014
- Fix a race condition in throttles that could allow more requests than intended. - Fix a race condition in throttles that could allow more requests than intended.
@ -35,7 +35,7 @@
## v4.1.0 - 22 May 2014 ## v4.1.0 - 22 May 2014
- Tracks take an optional limit and period to only notify once a threshold - Tracks take an optional limit and period to only notify once a threshold
is reached (similar to throttles). Thanks @chiliburger! is reached (similar to throttles). Thanks @chiliburger!
- Default throttled & blacklist responses have Content-Type: text/plain - Default throttled & blocklist responses have Content-Type: text/plain
- Rack::Attack.clear! resets tracks - Rack::Attack.clear! resets tracks
## v4.0.1 - 14 May 2014 ## v4.0.1 - 14 May 2014
@ -49,7 +49,7 @@
* Test more dalli versions. * Test more dalli versions.
## v3.0.0 - 15 March 2014 ## v3.0.0 - 15 March 2014
* Change default blacklisted response to 403 Forbidden (thanks @carpodaster). * Change default blocklisted response to 403 Forbidden (thanks @carpodaster).
* Fail gracefully when Redis store is not available; rescue exeption and don't * Fail gracefully when Redis store is not available; rescue exeption and don't
throttle request. (thanks @wkimeria) throttle request. (thanks @wkimeria)
* TravisCI runs integration tests. * TravisCI runs integration tests.
@ -62,7 +62,7 @@
## v2.2.1 - 13 August 2013 ## v2.2.1 - 13 August 2013
* Add license to gemspec * Add license to gemspec
* Support ruby version 1.9.2 * Support ruby version 1.9.2
* Change default blacklisted response code from 503 to 401; throttled response * Change default blocklisted response code from 503 to 401; throttled response
from 503 to 429. from 503 to 429.
## v2.2.0 - 20 June 2013 ## v2.2.0 - 20 June 2013

View file

@ -2,7 +2,7 @@
*Rack middleware for blocking & throttling abusive requests* *Rack middleware for blocking & throttling abusive requests*
Rack::Attack is a rack middleware to protect your web app from bad clients. Rack::Attack is a rack middleware to protect your web app from bad clients.
It allows *whitelisting*, *blacklisting*, *throttling*, and *tracking* based on arbitrary properties of the request. It allows *safelisting*, *blocklisting*, *throttling*, and *tracking* based on arbitrary properties of the request.
Throttle and fail2ban state is stored in a configurable cache (e.g. `Rails.cache`), presumably backed by memcached or redis ([at least gem v3.0.0](https://rubygems.org/gems/redis)). Throttle and fail2ban state is stored in a configurable cache (e.g. `Rails.cache`), presumably backed by memcached or redis ([at least gem v3.0.0](https://rubygems.org/gems/redis)).
@ -53,14 +53,14 @@ Optionally configure the cache store for throttling or fail2ban filtering:
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new # defaults to Rails.cache Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new # defaults to Rails.cache
``` ```
Note that `Rack::Attack.cache` is only used for throttling and fail2ban filtering; not blacklisting & whitelisting. Your cache store must implement `increment` and `write` like [ActiveSupport::Cache::Store](http://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html). Note that `Rack::Attack.cache` is only used for throttling and fail2ban filtering; not blocklisting & safelisting. Your cache store must implement `increment` and `write` like [ActiveSupport::Cache::Store](http://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html).
## How it works ## How it works
The Rack::Attack middleware compares each request against *whitelists*, *blacklists*, *throttles*, and *tracks* that you define. There are none by default. The Rack::Attack middleware compares each request against *safelists*, *blocklists*, *throttles*, and *tracks* that you define. There are none by default.
* If the request matches any **whitelist**, it is allowed. * If the request matches any **safelist**, it is allowed.
* Otherwise, if the request matches any **blacklist**, it is blocked. * Otherwise, if the request matches any **blocklist**, it is blocked.
* Otherwise, if the request matches any **throttle**, a counter is incremented in the Rack::Attack.cache. If any throttle's limit is exceeded, the request is blocked. * Otherwise, if the request matches any **throttle**, a counter is incremented in the Rack::Attack.cache. If any throttle's limit is exceeded, the request is blocked.
* Otherwise, all **tracks** are checked, and the request is allowed. * Otherwise, all **tracks** are checked, and the request is allowed.
@ -70,10 +70,10 @@ The algorithm is actually more concise in code: See [Rack::Attack.call](https://
def call(env) def call(env)
req = Rack::Attack::Request.new(env) req = Rack::Attack::Request.new(env)
if whitelisted?(req) if safelisted?(req)
@app.call(env) @app.call(env)
elsif blacklisted?(req) elsif blocklisted?(req)
self.class.blacklisted_response.call(env) self.class.blocklisted_response.call(env)
elsif throttled?(req) elsif throttled?(req)
self.class.throttled_response.call(env) self.class.throttled_response.call(env)
else else
@ -93,47 +93,47 @@ can cleanly monkey patch helper methods onto the
## Usage ## Usage
Define whitelists, blacklists, throttles, and tracks as blocks that return truthy values if matched, falsy otherwise. In a Rails app Define safelists, blocklists, throttles, and tracks as blocks that return truthy values if matched, falsy otherwise. In a Rails app
these go in an initializer in `config/initializers/`. these go in an initializer in `config/initializers/`.
A [Rack::Request](http://www.rubydoc.info/gems/rack/Rack/Request) object is passed to the block (named 'req' in the examples). A [Rack::Request](http://www.rubydoc.info/gems/rack/Rack/Request) object is passed to the block (named 'req' in the examples).
### Whitelists ### safelists
```ruby ```ruby
# Always allow requests from localhost # Always allow requests from localhost
# (blacklist & throttles are skipped) # (blocklist & throttles are skipped)
Rack::Attack.whitelist('allow from localhost') do |req| Rack::Attack.safelist('allow from localhost') do |req|
# Requests are allowed if the return value is truthy # Requests are allowed if the return value is truthy
'127.0.0.1' == req.ip || '::1' == req.ip '127.0.0.1' == req.ip || '::1' == req.ip
end end
``` ```
### Blacklists ### blocklists
```ruby ```ruby
# Block requests from 1.2.3.4 # Block requests from 1.2.3.4
Rack::Attack.blacklist('block 1.2.3.4') do |req| Rack::Attack.blocklist('block 1.2.3.4') do |req|
# Requests are blocked if the return value is truthy # Requests are blocked if the return value is truthy
'1.2.3.4' == req.ip '1.2.3.4' == req.ip
end end
# Block logins from a bad user agent # Block logins from a bad user agent
Rack::Attack.blacklist('block bad UA logins') do |req| Rack::Attack.blocklist('block bad UA logins') do |req|
req.path == '/login' && req.post? && req.user_agent == 'BadUA' req.path == '/login' && req.post? && req.user_agent == 'BadUA'
end end
``` ```
#### Fail2Ban #### Fail2Ban
`Fail2Ban.filter` can be used within a blacklist to block all requests from misbehaving clients. `Fail2Ban.filter` can be used within a blocklist to block all requests from misbehaving clients.
This pattern is inspired by [fail2ban](http://www.fail2ban.org/wiki/index.php/Main_Page). This pattern is inspired by [fail2ban](http://www.fail2ban.org/wiki/index.php/Main_Page).
See the [fail2ban documentation](http://www.fail2ban.org/wiki/index.php/MANUAL_0_8#Jail_Options) for more details on See the [fail2ban documentation](http://www.fail2ban.org/wiki/index.php/MANUAL_0_8#Jail_Options) for more details on
how the parameters work. For multiple filters, be sure to put each filter in a separate blacklist and use a unique discriminator for each fail2ban filter. how the parameters work. For multiple filters, be sure to put each filter in a separate blocklist and use a unique discriminator for each fail2ban filter.
```ruby ```ruby
# Block suspicious requests for '/etc/password' or wordpress specific paths. # Block suspicious requests for '/etc/password' or wordpress specific paths.
# After 3 blocked requests in 10 minutes, block all requests from that IP for 5 minutes. # After 3 blocked requests in 10 minutes, block all requests from that IP for 5 minutes.
Rack::Attack.blacklist('fail2ban pentesters') do |req| Rack::Attack.blocklist('fail2ban pentesters') do |req|
# `filter` returns truthy value if request fails, or if it's from a previously banned IP # `filter` returns truthy value if request fails, or if it's from a previously banned IP
# so the request is blocked # so the request is blocked
Rack::Attack::Fail2Ban.filter("pentesters-#{req.ip}", :maxretry => 3, :findtime => 10.minutes, :bantime => 5.minutes) do Rack::Attack::Fail2Ban.filter("pentesters-#{req.ip}", :maxretry => 3, :findtime => 10.minutes, :bantime => 5.minutes) do
@ -147,7 +147,7 @@ Rack::Attack.blacklist('fail2ban pentesters') do |req|
end end
``` ```
Note that `Fail2Ban` filters are not automatically scoped to the blacklist, so when using multiple filters in an application the scoping must be added to the discriminator e.g. `"pentest:#{req.ip}"`. Note that `Fail2Ban` filters are not automatically scoped to the blocklist, so when using multiple filters in an application the scoping must be added to the discriminator e.g. `"pentest:#{req.ip}"`.
#### Allow2Ban #### Allow2Ban
`Allow2Ban.filter` works the same way as the `Fail2Ban.filter` except that it *allows* requests from misbehaving `Allow2Ban.filter` works the same way as the `Fail2Ban.filter` except that it *allows* requests from misbehaving
@ -155,7 +155,7 @@ clients until such time as they reach maxretry at which they are cut off as per
```ruby ```ruby
# Lockout IP addresses that are hammering your login page. # Lockout IP addresses that are hammering your login page.
# After 20 requests in 1 minute, block all requests from that IP for 1 hour. # After 20 requests in 1 minute, block all requests from that IP for 1 hour.
Rack::Attack.blacklist('allow2ban login scrapers') do |req| Rack::Attack.blocklist('allow2ban login scrapers') do |req|
# `filter` returns false value if request is to your login page (but still # `filter` returns false value if request is to your login page (but still
# increments the count) so request below the limit are not blocked until # increments the count) so request below the limit are not blocked until
# they hit the limit. At that point, filter will return true and block. # they hit the limit. At that point, filter will return true and block.
@ -220,12 +220,12 @@ end
## Responses ## Responses
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). Customize the response of blocklisted and throttled requests using an object that adheres to the [Rack app interface](http://rack.rubyforge.org/doc/SPEC.html).
```ruby ```ruby
Rack::Attack.blacklisted_response = lambda do |env| Rack::Attack.blocklisted_response = lambda do |env|
# Using 503 because it may make attacker think that they have successfully # Using 503 because it may make attacker think that they have successfully
# DOSed the site. Rack::Attack returns 403 for blacklists by default # DOSed the site. Rack::Attack returns 403 for blocklists by default
[ 503, {}, ['Blocked']] [ 503, {}, ['Blocked']]
end end
@ -274,7 +274,7 @@ but it depends on how many checks you've configured, and how long they take.
Throttles usually require a network roundtrip to your cache server(s), Throttles usually require a network roundtrip to your cache server(s),
so try to keep the number of throttle checks per request low. so try to keep the number of throttle checks per request low.
If a request is blacklisted or throttled, the response is a very simple Rack response. If a request is blocklisted or throttled, the response is a very simple Rack response.
A single typical ruby web server thread can block several hundred requests per second. A single typical ruby web server thread can block several hundred requests per second.
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). 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).

View file

@ -15,12 +15,12 @@ Rack::Attack.throttle "logins/email", :limit => 2, :period => 60 do |req|
req.post? && req.path == "/login" && req.params['email'] req.post? && req.path == "/login" && req.params['email']
end end
# Blacklist bad IPs from accessing admin pages # blocklist bad IPs from accessing admin pages
Rack::Attack.blacklist "bad_ips from logging in" do |req| Rack::Attack.blocklist "bad_ips from logging in" do |req|
req.path =~ /^\/admin/ && bad_ips.include?(req.ip) req.path =~ /^\/admin/ && bad_ips.include?(req.ip)
end end
# Whitelist a User-Agent # safelist a User-Agent
Rack::Attack.whitelist 'internal user agent' do |req| Rack::Attack.safelist 'internal user agent' do |req|
req.user_agent == 'InternalUserAgent' req.user_agent == 'InternalUserAgent'
end end

View file

@ -6,8 +6,8 @@ class Rack::Attack
autoload :PathNormalizer, 'rack/attack/path_normalizer' autoload :PathNormalizer, 'rack/attack/path_normalizer'
autoload :Check, 'rack/attack/check' autoload :Check, 'rack/attack/check'
autoload :Throttle, 'rack/attack/throttle' autoload :Throttle, 'rack/attack/throttle'
autoload :Whitelist, 'rack/attack/whitelist' autoload :Safelist, 'rack/attack/safelist'
autoload :Blacklist, 'rack/attack/blacklist' autoload :Blocklist, 'rack/attack/blocklist'
autoload :Track, 'rack/attack/track' autoload :Track, 'rack/attack/track'
autoload :StoreProxy, 'rack/attack/store_proxy' autoload :StoreProxy, 'rack/attack/store_proxy'
autoload :DalliProxy, 'rack/attack/store_proxy/dalli_proxy' autoload :DalliProxy, 'rack/attack/store_proxy/dalli_proxy'
@ -19,14 +19,24 @@ class Rack::Attack
class << self class << self
attr_accessor :notifier, :blacklisted_response, :throttled_response attr_accessor :notifier, :blocklisted_response, :throttled_response
def safelist(name, &block)
self.safelists[name] = Safelist.new(name, block)
end
def whitelist(name, &block) def whitelist(name, &block)
self.whitelists[name] = Whitelist.new(name, block) warn "[DEPRECATION] 'whitelist' is deprecated. Please use 'safelist' instead."
safelist(name, &block)
end
def blocklist(name, &block)
self.blocklists[name] = Blocklist.new(name, block)
end end
def blacklist(name, &block) def blacklist(name, &block)
self.blacklists[name] = Blacklist.new(name, block) warn "[DEPRECATION] 'blacklist' is deprecated. Please use 'blocklist' instead."
blocklist(name, &block)
end end
def throttle(name, options, &block) def throttle(name, options, &block)
@ -37,23 +47,43 @@ class Rack::Attack
self.tracks[name] = Track.new(name, options, block) self.tracks[name] = Track.new(name, options, block)
end end
def whitelists; @whitelists ||= {}; end def safelists; @safelists ||= {}; end
def blacklists; @blacklists ||= {}; end def blocklists; @blocklists ||= {}; end
def throttles; @throttles ||= {}; end def throttles; @throttles ||= {}; end
def tracks; @tracks ||= {}; end def tracks; @tracks ||= {}; end
def whitelisted?(req) def whitelists
whitelists.any? do |name, whitelist| warn "[DEPRECATION] 'whitelists' is deprecated. Please use 'safelists' instead."
whitelist[req] safelists
end
def blacklists
warn "[DEPRECATION] 'blacklists' is deprecated. Please use 'blocklists' instead."
blocklists
end
def safelisted?(req)
safelists.any? do |name, safelist|
safelist[req]
end end
end end
def blacklisted?(req) def whitelisted?
blacklists.any? do |name, blacklist| warn "[DEPRECATION] 'whitelisted?' is deprecated. Please use 'safelisted?' instead."
blacklist[req] safelisted?
end
def blocklisted?(req)
blocklists.any? do |name, blocklist|
blocklist[req]
end end
end end
def blacklisted?
warn "[DEPRECATION] 'blacklisted?' is deprecated. Please use 'blocklisted?' instead."
blocklisted?
end
def throttled?(req) def throttled?(req)
throttles.any? do |name, throttle| throttles.any? do |name, throttle|
throttle[req] throttle[req]
@ -75,14 +105,14 @@ class Rack::Attack
end end
def clear! def clear!
@whitelists, @blacklists, @throttles, @tracks = {}, {}, {}, {} @safelists, @blocklists, @throttles, @tracks = {}, {}, {}, {}
end end
end end
# Set defaults # Set defaults
@notifier = ActiveSupport::Notifications if defined?(ActiveSupport::Notifications) @notifier = ActiveSupport::Notifications if defined?(ActiveSupport::Notifications)
@blacklisted_response = lambda {|env| [403, {'Content-Type' => 'text/plain'}, ["Forbidden\n"]] } @blocklisted_response = lambda {|env| [403, {'Content-Type' => 'text/plain'}, ["Forbidden\n"]] }
@throttled_response = lambda {|env| @throttled_response = lambda {|env|
retry_after = (env['rack.attack.match_data'] || {})[:period] retry_after = (env['rack.attack.match_data'] || {})[:period]
[429, {'Content-Type' => 'text/plain', 'Retry-After' => retry_after.to_s}, ["Retry later\n"]] [429, {'Content-Type' => 'text/plain', 'Retry-After' => retry_after.to_s}, ["Retry later\n"]]
@ -96,10 +126,10 @@ class Rack::Attack
env['PATH_INFO'] = PathNormalizer.normalize_path(env['PATH_INFO']) env['PATH_INFO'] = PathNormalizer.normalize_path(env['PATH_INFO'])
req = Rack::Attack::Request.new(env) req = Rack::Attack::Request.new(env)
if whitelisted?(req) if safelisted?(req)
@app.call(env) @app.call(env)
elsif blacklisted?(req) elsif blocklisted?(req)
self.class.blacklisted_response.call(env) self.class.blocklisted_response.call(env)
elsif throttled?(req) elsif throttled?(req)
self.class.throttled_response.call(env) self.class.throttled_response.call(env)
else else
@ -109,8 +139,8 @@ class Rack::Attack
end end
extend Forwardable extend Forwardable
def_delegators self, :whitelisted?, def_delegators self, :safelisted?,
:blacklisted?, :blocklisted?,
:throttled?, :throttled?,
:tracked? :tracked?
end end

View file

@ -1,9 +1,9 @@
module Rack module Rack
class Attack class Attack
class Whitelist < Check class Blocklist < Check
def initialize(name, block) def initialize(name, block)
super super
@type = :whitelist @type = :blocklist
end end
end end

View file

@ -8,7 +8,7 @@ module Rack
maxretry = options[:maxretry] or raise ArgumentError, "Must pass maxretry option" maxretry = options[:maxretry] or raise ArgumentError, "Must pass maxretry option"
if banned?(discriminator) if banned?(discriminator)
# Return true for blacklist # Return true for blocklist
true true
elsif yield elsif yield
fail!(discriminator, bantime, findtime, maxretry) fail!(discriminator, bantime, findtime, maxretry)

View file

@ -9,7 +9,7 @@
# end # end
# end # end
# #
# Rack::Attack.whitelist("localhost") {|req| req.localhost? } # Rack::Attack.safelist("localhost") {|req| req.localhost? }
# #
module Rack module Rack
class Attack class Attack

View file

@ -1,12 +1,11 @@
module Rack module Rack
class Attack class Attack
class Blacklist < Check class Safelist < Check
def initialize(name, block) def initialize(name, block)
super super
@type = :blacklist @type = :safelist
end end
end end
end end
end end

View file

@ -7,7 +7,7 @@ describe 'Rack::Attack.Allow2Ban' do
@bantime = 60 @bantime = 60
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
@f2b_options = {:bantime => @bantime, :findtime => @findtime, :maxretry => 2} @f2b_options = {:bantime => @bantime, :findtime => @findtime, :maxretry => 2}
Rack::Attack.blacklist('pentest') do |req| Rack::Attack.blocklist('pentest') do |req|
Rack::Attack::Allow2Ban.filter(req.ip, @f2b_options){req.query_string =~ /OMGHAX/} Rack::Attack::Allow2Ban.filter(req.ip, @f2b_options){req.query_string =~ /OMGHAX/}
end end
end end

View file

@ -7,7 +7,7 @@ describe 'Rack::Attack.Fail2Ban' do
@bantime = 60 @bantime = 60
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
@f2b_options = {:bantime => @bantime, :findtime => @findtime, :maxretry => 2} @f2b_options = {:bantime => @bantime, :findtime => @findtime, :maxretry => 2}
Rack::Attack.blacklist('pentest') do |req| Rack::Attack.blocklist('pentest') do |req|
Rack::Attack::Fail2Ban.filter(req.ip, @f2b_options){req.query_string =~ /OMGHAX/} Rack::Attack::Fail2Ban.filter(req.ip, @f2b_options){req.query_string =~ /OMGHAX/}
end end
end end

View file

@ -9,7 +9,7 @@ describe 'Rack::Attack' do
end end
end end
Rack::Attack.whitelist('valid IP') do |req| Rack::Attack.safelist('valid IP') do |req|
req.remote_ip == "127.0.0.1" req.remote_ip == "127.0.0.1"
end end
end end

View file

@ -5,7 +5,7 @@ describe 'Rack::Attack' do
describe 'normalizing paths' do describe 'normalizing paths' do
before do before do
Rack::Attack.blacklist("banned_path") {|req| req.path == '/foo' } Rack::Attack.blocklist("banned_path") {|req| req.path == '/foo' }
end end
it 'blocks requests with trailing slash' do it 'blocks requests with trailing slash' do
@ -14,47 +14,62 @@ describe 'Rack::Attack' do
end end
end end
describe 'blacklist' do describe 'blocklist' do
before do before do
@bad_ip = '1.2.3.4' @bad_ip = '1.2.3.4'
Rack::Attack.blacklist("ip #{@bad_ip}") {|req| req.ip == @bad_ip } Rack::Attack.blocklist("ip #{@bad_ip}") {|req| req.ip == @bad_ip }
end end
it('has a blacklist') { it('has a blocklist') {
Rack::Attack.blacklists.key?("ip #{@bad_ip}").must_equal true Rack::Attack.blocklists.key?("ip #{@bad_ip}").must_equal true
}
it('has a blacklist with a deprication warning') {
stdout, stderror = capture_io do
Rack::Attack.blacklists.key?("ip #{@bad_ip}").must_equal true
end
assert_match "[DEPRECATION] 'blacklists' is deprecated. Please use 'blocklists' instead.", stderror
} }
describe "a bad request" do describe "a bad request" do
before { get '/', {}, 'REMOTE_ADDR' => @bad_ip } before { get '/', {}, 'REMOTE_ADDR' => @bad_ip }
it "should return a blacklist response" do it "should return a blocklist response" do
get '/', {}, 'REMOTE_ADDR' => @bad_ip get '/', {}, 'REMOTE_ADDR' => @bad_ip
last_response.status.must_equal 403 last_response.status.must_equal 403
last_response.body.must_equal "Forbidden\n" last_response.body.must_equal "Forbidden\n"
end end
it "should tag the env" do it "should tag the env" do
last_request.env['rack.attack.matched'].must_equal "ip #{@bad_ip}" last_request.env['rack.attack.matched'].must_equal "ip #{@bad_ip}"
last_request.env['rack.attack.match_type'].must_equal :blacklist last_request.env['rack.attack.match_type'].must_equal :blocklist
end end
allow_ok_requests allow_ok_requests
end end
describe "and whitelist" do describe "and safelist" do
before do before do
@good_ua = 'GoodUA' @good_ua = 'GoodUA'
Rack::Attack.whitelist("good ua") {|req| req.user_agent == @good_ua } Rack::Attack.safelist("good ua") {|req| req.user_agent == @good_ua }
end end
it('has a whitelist'){ Rack::Attack.whitelists.key?("good ua") } it('has a safelist'){ Rack::Attack.safelists.key?("good ua") }
describe "with a request match both whitelist & blacklist" do
it('has a whitelist with a deprication warning') {
stdout, stderror = capture_io do
Rack::Attack.whitelists.key?("good ua")
end
assert_match "[DEPRECATION] 'whitelists' is deprecated. Please use 'safelists' instead.", stderror
}
describe "with a request match both safelist & blocklist" do
before { get '/', {}, 'REMOTE_ADDR' => @bad_ip, 'HTTP_USER_AGENT' => @good_ua } before { get '/', {}, 'REMOTE_ADDR' => @bad_ip, 'HTTP_USER_AGENT' => @good_ua }
it "should allow whitelists before blacklists" do it "should allow safelists before blocklists" do
get '/', {}, 'REMOTE_ADDR' => @bad_ip, 'HTTP_USER_AGENT' => @good_ua get '/', {}, 'REMOTE_ADDR' => @bad_ip, 'HTTP_USER_AGENT' => @good_ua
last_response.status.must_equal 200 last_response.status.must_equal 200
end end
it "should tag the env" do it "should tag the env" do
last_request.env['rack.attack.matched'].must_equal 'good ua' last_request.env['rack.attack.matched'].must_equal 'good ua'
last_request.env['rack.attack.match_type'].must_equal :whitelist last_request.env['rack.attack.match_type'].must_equal :safelist
end end
end end
end end