From 6addaa11d071af489c6e62e5dc714e564e14781a Mon Sep 17 00:00:00 2001 From: Gonzalo Rodriguez Date: Sun, 24 Feb 2019 21:04:51 -0300 Subject: [PATCH] feat: make blocklist/safelist name argument optional --- lib/rack/attack.rb | 46 ++++++++++++++++--------- spec/acceptance/blocking_spec.rb | 40 ++++++++++++++++++++++ spec/acceptance/safelisting_spec.rb | 52 +++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+), 16 deletions(-) diff --git a/lib/rack/attack.rb b/lib/rack/attack.rb index c77245e..e7b3c9a 100644 --- a/lib/rack/attack.rb +++ b/lib/rack/attack.rb @@ -29,24 +29,38 @@ class Rack::Attack class << self attr_accessor :notifier, :blocklisted_response, :throttled_response - def safelist(name, &block) - self.safelists[name] = Safelist.new(name, block) + def safelist(name = nil, &block) + safelist = Safelist.new(name, block) + + if name + self.safelists[name] = safelist + else + @anonymous_safelists ||= [] + @anonymous_safelists << safelist + end end - def blocklist(name, &block) - self.blocklists[name] = Blocklist.new(name, block) + def blocklist(name = nil, &block) + blocklist = Blocklist.new(name, block) + + if name + self.blocklists[name] = blocklist + else + @anonymous_blocklists ||= [] + @anonymous_blocklists << blocklist + end end def blocklist_ip(ip_address) - @ip_blocklists ||= [] + @anonymous_blocklists ||= [] ip_blocklist_proc = lambda { |request| IPAddr.new(ip_address).include?(IPAddr.new(request.ip)) } - @ip_blocklists << Blocklist.new(nil, ip_blocklist_proc) + @anonymous_blocklists << Blocklist.new(nil, ip_blocklist_proc) end def safelist_ip(ip_address) - @ip_safelists ||= [] + @anonymous_safelists ||= [] ip_safelist_proc = lambda { |request| IPAddr.new(ip_address).include?(IPAddr.new(request.ip)) } - @ip_safelists << Safelist.new(nil, ip_safelist_proc) + @anonymous_safelists << Safelist.new(nil, ip_safelist_proc) end def throttle(name, options, &block) @@ -66,12 +80,12 @@ class Rack::Attack def tracks; @tracks ||= {}; end def safelisted?(request) - ip_safelists.any? { |safelist| safelist.matched_by?(request) } || + anonymous_safelists.any? { |safelist| safelist.matched_by?(request) } || safelists.any? { |_name, safelist| safelist.matched_by?(request) } end def blocklisted?(request) - ip_blocklists.any? { |blocklist| blocklist.matched_by?(request) } || + anonymous_blocklists.any? { |blocklist| blocklist.matched_by?(request) } || blocklists.any? { |_name, blocklist| blocklist.matched_by?(request) } end @@ -97,8 +111,8 @@ class Rack::Attack def clear_configuration @safelists, @blocklists, @throttles, @tracks = {}, {}, {}, {} - @ip_blocklists = [] - @ip_safelists = [] + @anonymous_blocklists = [] + @anonymous_safelists = [] end def clear! @@ -108,12 +122,12 @@ class Rack::Attack private - def ip_blocklists - @ip_blocklists ||= [] + def anonymous_blocklists + @anonymous_blocklists ||= [] end - def ip_safelists - @ip_safelists ||= [] + def anonymous_safelists + @anonymous_safelists ||= [] end end diff --git a/spec/acceptance/blocking_spec.rb b/spec/acceptance/blocking_spec.rb index db176c8..e5b1e83 100644 --- a/spec/acceptance/blocking_spec.rb +++ b/spec/acceptance/blocking_spec.rb @@ -3,6 +3,46 @@ require_relative "../spec_helper" describe "#blocklist" do + before do + Rack::Attack.blocklist do |request| + request.ip == "1.2.3.4" + end + end + + it "forbids request if blocklist condition is true" do + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal 403, last_response.status + end + + it "succeeds if blocklist condition is false" do + get "/", {}, "REMOTE_ADDR" => "5.6.7.8" + + assert_equal 200, last_response.status + end + + it "notifies when the request is blocked" do + notification_matched = nil + notification_type = nil + + ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, payload| + notification_matched = payload[:request].env["rack.attack.matched"] + notification_type = payload[:request].env["rack.attack.match_type"] + end + + get "/", {}, "REMOTE_ADDR" => "5.6.7.8" + + assert_nil notification_matched + assert_nil notification_type + + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_nil notification_matched + assert_equal :blocklist, notification_type + end +end + +describe "#blocklist with name" do before do Rack::Attack.blocklist("block 1.2.3.4") do |request| request.ip == "1.2.3.4" diff --git a/spec/acceptance/safelisting_spec.rb b/spec/acceptance/safelisting_spec.rb index dc4d82d..ea73022 100644 --- a/spec/acceptance/safelisting_spec.rb +++ b/spec/acceptance/safelisting_spec.rb @@ -3,6 +3,58 @@ require_relative "../spec_helper" describe "#safelist" do + before do + Rack::Attack.blocklist do |request| + request.ip == "1.2.3.4" + end + + Rack::Attack.safelist do |request| + request.path == "/safe_space" + end + end + + it "forbids request if blocklist condition is true and safelist is false" do + get "/", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal 403, last_response.status + end + + it "succeeds if blocklist condition is false and safelist is false" do + get "/", {}, "REMOTE_ADDR" => "5.6.7.8" + + assert_equal 200, last_response.status + end + + it "succeeds request if blocklist condition is false and safelist is true" do + get "/safe_space", {}, "REMOTE_ADDR" => "5.6.7.8" + + assert_equal 200, last_response.status + end + + it "succeeds request if both blocklist and safelist conditions are true" do + get "/safe_space", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal 200, last_response.status + end + + it "notifies when the request is safe" do + notification_matched = nil + notification_type = nil + + ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, payload| + notification_matched = payload[:request].env["rack.attack.matched"] + notification_type = payload[:request].env["rack.attack.match_type"] + end + + get "/safe_space", {}, "REMOTE_ADDR" => "1.2.3.4" + + assert_equal 200, last_response.status + assert_nil notification_matched + assert_equal :safelist, notification_type + end +end + +describe "#safelist with name" do before do Rack::Attack.blocklist("block 1.2.3.4") do |request| request.ip == "1.2.3.4"