diff --git a/README.md b/README.md index 5f11079..b4eadc9 100644 --- a/README.md +++ b/README.md @@ -252,7 +252,8 @@ Rack::Attack.track("special_agent", limit: 6, period: 60) do |req| end # Track it using ActiveSupport::Notification -ActiveSupport::Notifications.subscribe("rack.attack") do |name, start, finish, request_id, req| +ActiveSupport::Notifications.subscribe("rack.attack") do |name, start, finish, request_id, payload| + req = payload[:request] if req.env['rack.attack.matched'] == "special_agent" && req.env['rack.attack.match_type'] == :track Rails.logger.info "special_agent: #{req.path}" STATSD.increment("special_agent") @@ -330,8 +331,8 @@ Rack::Attack uses the [ActiveSupport::Notifications](http://api.rubyonrails.org/ You can subscribe to 'rack.attack' events and log it, graph it, etc: ```ruby -ActiveSupport::Notifications.subscribe('rack.attack') do |name, start, finish, request_id, req| - puts req.inspect +ActiveSupport::Notifications.subscribe('rack.attack') do |name, start, finish, request_id, payload| + puts payload[:request].inspect end ``` diff --git a/lib/rack/attack.rb b/lib/rack/attack.rb index 30a9ccf..66638a4 100644 --- a/lib/rack/attack.rb +++ b/lib/rack/attack.rb @@ -114,7 +114,7 @@ class Rack::Attack end def instrument(request) - notifier.instrument('rack.attack', request) if notifier + notifier.instrument('rack.attack', request: request) if notifier end def cache diff --git a/spec/acceptance/blocking_ip_spec.rb b/spec/acceptance/blocking_ip_spec.rb index 72c599c..85dde63 100644 --- a/spec/acceptance/blocking_ip_spec.rb +++ b/spec/acceptance/blocking_ip_spec.rb @@ -21,9 +21,9 @@ describe "Blocking an IP" do notified = false notification_type = nil - ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, request| + ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, payload| notified = true - notification_type = request.env["rack.attack.match_type"] + notification_type = payload[:request].env["rack.attack.match_type"] end get "/", {}, "REMOTE_ADDR" => "5.6.7.8" diff --git a/spec/acceptance/blocking_spec.rb b/spec/acceptance/blocking_spec.rb index f245a6b..83b5202 100644 --- a/spec/acceptance/blocking_spec.rb +++ b/spec/acceptance/blocking_spec.rb @@ -23,9 +23,9 @@ describe "#blocklist" do notification_matched = nil notification_type = nil - ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, request| - notification_matched = request.env["rack.attack.matched"] - notification_type = request.env["rack.attack.match_type"] + 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" diff --git a/spec/acceptance/blocking_subnet_spec.rb b/spec/acceptance/blocking_subnet_spec.rb index 13b4f91..7143445 100644 --- a/spec/acceptance/blocking_subnet_spec.rb +++ b/spec/acceptance/blocking_subnet_spec.rb @@ -27,9 +27,9 @@ describe "Blocking an IP subnet" do notified = false notification_type = nil - ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, request| + ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, payload| notified = true - notification_type = request.env["rack.attack.match_type"] + notification_type = payload[:request].env["rack.attack.match_type"] end get "/", {}, "REMOTE_ADDR" => "5.6.7.8" diff --git a/spec/acceptance/safelisting_ip_spec.rb b/spec/acceptance/safelisting_ip_spec.rb index eecd5b3..c0c8203 100644 --- a/spec/acceptance/safelisting_ip_spec.rb +++ b/spec/acceptance/safelisting_ip_spec.rb @@ -36,8 +36,8 @@ describe "Safelist an IP" do it "notifies when the request is safe" do notification_type = nil - ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, request| - notification_type = request.env["rack.attack.match_type"] + ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, payload| + notification_type = payload[:request].env["rack.attack.match_type"] end get "/admin", {}, "REMOTE_ADDR" => "5.6.7.8" diff --git a/spec/acceptance/safelisting_spec.rb b/spec/acceptance/safelisting_spec.rb index 8f4289d..420fc9a 100644 --- a/spec/acceptance/safelisting_spec.rb +++ b/spec/acceptance/safelisting_spec.rb @@ -39,9 +39,9 @@ describe "#safelist" do notification_matched = nil notification_type = nil - ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, request| - notification_matched = request.env["rack.attack.matched"] - notification_type = request.env["rack.attack.match_type"] + 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" diff --git a/spec/acceptance/safelisting_subnet_spec.rb b/spec/acceptance/safelisting_subnet_spec.rb index 920f265..22c60f5 100644 --- a/spec/acceptance/safelisting_subnet_spec.rb +++ b/spec/acceptance/safelisting_subnet_spec.rb @@ -36,8 +36,8 @@ describe "Safelisting an IP subnet" do it "notifies when the request is safe" do notification_type = nil - ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, request| - notification_type = request.env["rack.attack.match_type"] + ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, payload| + notification_type = payload[:request].env["rack.attack.match_type"] end get "/admin", {}, "REMOTE_ADDR" => "5.6.0.0" diff --git a/spec/acceptance/throttling_spec.rb b/spec/acceptance/throttling_spec.rb index 47bc5d8..947e4ed 100644 --- a/spec/acceptance/throttling_spec.rb +++ b/spec/acceptance/throttling_spec.rb @@ -123,11 +123,11 @@ describe "#throttle" do notification_data = nil notification_discriminator = nil - ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, request| - notification_matched = request.env["rack.attack.matched"] - notification_type = request.env["rack.attack.match_type"] - notification_data = request.env['rack.attack.match_data'] - notification_discriminator = request.env['rack.attack.match_discriminator'] + 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"] + notification_data = payload[:request].env['rack.attack.match_data'] + notification_discriminator = payload[:request].env['rack.attack.match_discriminator'] end get "/", {}, "REMOTE_ADDR" => "5.6.7.8" diff --git a/spec/acceptance/track_spec.rb b/spec/acceptance/track_spec.rb index 7f4b393..928cc31 100644 --- a/spec/acceptance/track_spec.rb +++ b/spec/acceptance/track_spec.rb @@ -9,9 +9,9 @@ describe "#track" do notification_matched = nil notification_type = nil - ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, request| - notification_matched = request.env["rack.attack.matched"] - notification_type = request.env["rack.attack.match_type"] + 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" diff --git a/spec/acceptance/track_throttle_spec.rb b/spec/acceptance/track_throttle_spec.rb index 7446ce4..f3f8a80 100644 --- a/spec/acceptance/track_throttle_spec.rb +++ b/spec/acceptance/track_throttle_spec.rb @@ -12,9 +12,9 @@ describe "#track with throttle-ish options" do notification_matched = nil notification_type = nil - ActiveSupport::Notifications.subscribe("rack.attack") do |_name, _start, _finish, _id, request| - notification_matched = request.env["rack.attack.matched"] - notification_type = request.env["rack.attack.match_type"] + 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" => "1.2.3.4" diff --git a/spec/rack_attack_instrumentation_spec.rb b/spec/rack_attack_instrumentation_spec.rb new file mode 100644 index 0000000..88883d0 --- /dev/null +++ b/spec/rack_attack_instrumentation_spec.rb @@ -0,0 +1,31 @@ +# ActiveSupport::Subscribers added in ~> 4.0.2.0 +if ActiveSupport::VERSION::MAJOR > 3 + require_relative 'spec_helper' + require 'active_support/subscriber' + class CustomSubscriber < ActiveSupport::Subscriber + def rack(event) + # Do virtually (but not) nothing. + event.inspect + end + end + + describe 'Rack::Attack.instrument' do + before do + @period = 60 # Use a long period; failures due to cache key rotation less likely + Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new + Rack::Attack.throttle('ip/sec', limit: 1, period: @period) { |req| req.ip } + end + + describe "with throttling" do + before do + ActiveSupport::Notifications.stub(:notifier, ActiveSupport::Notifications::Fanout.new) do + CustomSubscriber.attach_to("attack") + 2.times { get '/', {}, 'REMOTE_ADDR' => '1.2.3.4' } + end + end + it 'should instrument without error' do + last_response.status.must_equal 429 + end + end + end +end