Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract Configuration class #446

Merged
merged 1 commit into from
Oct 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 19 additions & 102 deletions lib/rack/attack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class Error < StandardError; end
class MisconfiguredStoreError < Error; end
class MissingStoreError < Error; end

autoload :Configuration, 'rack/attack/configuration'
autoload :Cache, 'rack/attack/cache'
autoload :Check, 'rack/attack/check'
autoload :Throttle, 'rack/attack/throttle'
Expand All @@ -31,82 +32,8 @@ class MissingStoreError < Error; end
autoload :Allow2Ban, 'rack/attack/allow2ban'

class << self
attr_accessor :enabled, :notifier, :blocklisted_response, :throttled_response,
:anonymous_blocklists, :anonymous_safelists

def safelist(name = nil, &block)
safelist = Safelist.new(name, &block)

if name
safelists[name] = safelist
else
anonymous_safelists << safelist
end
end

def blocklist(name = nil, &block)
blocklist = Blocklist.new(name, &block)

if name
blocklists[name] = blocklist
else
anonymous_blocklists << blocklist
end
end

def blocklist_ip(ip_address)
anonymous_blocklists << Blocklist.new { |request| IPAddr.new(ip_address).include?(IPAddr.new(request.ip)) }
end

def safelist_ip(ip_address)
anonymous_safelists << Safelist.new { |request| IPAddr.new(ip_address).include?(IPAddr.new(request.ip)) }
end

def throttle(name, options, &block)
throttles[name] = Throttle.new(name, options, &block)
end

def track(name, options = {}, &block)
tracks[name] = Track.new(name, options, &block)
end

def safelists
@safelists ||= {}
end

def blocklists
@blocklists ||= {}
end

def throttles
@throttles ||= {}
end

def tracks
@tracks ||= {}
end

def safelisted?(request)
anonymous_safelists.any? { |safelist| safelist.matched_by?(request) } ||
safelists.any? { |_name, safelist| safelist.matched_by?(request) }
end

def blocklisted?(request)
anonymous_blocklists.any? { |blocklist| blocklist.matched_by?(request) } ||
blocklists.any? { |_name, blocklist| blocklist.matched_by?(request) }
end

def throttled?(request)
throttles.any? do |_name, throttle|
throttle.matched_by?(request)
end
end

def tracked?(request)
tracks.each_value do |track|
track.matched_by?(request)
end
end
attr_accessor :enabled, :notifier
attr_reader :configuration

def instrument(request)
if notifier
Expand All @@ -122,34 +49,27 @@ def cache
@cache ||= Cache.new
end

def clear_configuration
@safelists = {}
@blocklists = {}
@throttles = {}
@tracks = {}
self.anonymous_blocklists = []
self.anonymous_safelists = []
end

def clear!
warn "[DEPRECATION] Rack::Attack.clear! is deprecated. Please use Rack::Attack.clear_configuration instead"
clear_configuration
@configuration.clear_configuration
end

extend Forwardable
def_delegators :@configuration, :safelist, :blocklist, :blocklist_ip, :safelist_ip, :throttle, :track,
:blocklisted_response, :blocklisted_response=, :throttled_response, :throttled_response=,
:clear_configuration, :safelists, :blocklists, :throttles, :tracks
end

# Set defaults
@enabled = true
@anonymous_blocklists = []
@anonymous_safelists = []
@notifier = ActiveSupport::Notifications if defined?(ActiveSupport::Notifications)
@blocklisted_response = lambda { |_env| [403, { 'Content-Type' => 'text/plain' }, ["Forbidden\n"]] }
@throttled_response = lambda do |env|
retry_after = (env['rack.attack.match_data'] || {})[:period]
[429, { 'Content-Type' => 'text/plain', 'Retry-After' => retry_after.to_s }, ["Retry later\n"]]
end
@configuration = Configuration.new

attr_reader :configuration

def initialize(app)
@app = app
@configuration = self.class.configuration
end

def call(env)
Expand All @@ -158,19 +78,16 @@ def call(env)
env['PATH_INFO'] = PathNormalizer.normalize_path(env['PATH_INFO'])
request = Rack::Attack::Request.new(env)

if safelisted?(request)
if configuration.safelisted?(request)
@app.call(env)
elsif blocklisted?(request)
self.class.blocklisted_response.call(env)
elsif throttled?(request)
self.class.throttled_response.call(env)
elsif configuration.blocklisted?(request)
configuration.blocklisted_response.call(env)
elsif configuration.throttled?(request)
configuration.throttled_response.call(env)
else
tracked?(request)
configuration.tracked?(request)
@app.call(env)
end
end

extend Forwardable
def_delegators self, :safelisted?, :blocklisted?, :throttled?, :tracked?
end
end
92 changes: 92 additions & 0 deletions lib/rack/attack/configuration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# frozen_string_literal: true

module Rack
class Attack
class Configuration
attr_reader :safelists, :blocklists, :throttles, :anonymous_blocklists, :anonymous_safelists
attr_accessor :blocklisted_response, :throttled_response

def initialize
@safelists = {}
@blocklists = {}
@throttles = {}
@tracks = {}
@anonymous_blocklists = []
@anonymous_safelists = []

@blocklisted_response = lambda { |_env| [403, { 'Content-Type' => 'text/plain' }, ["Forbidden\n"]] }
@throttled_response = lambda do |env|
retry_after = (env['rack.attack.match_data'] || {})[:period]
[429, { 'Content-Type' => 'text/plain', 'Retry-After' => retry_after.to_s }, ["Retry later\n"]]
end
end

def safelist(name = nil, &block)
safelist = Safelist.new(name, &block)

if name
@safelists[name] = safelist
else
@anonymous_safelists << safelist
end
end

def blocklist(name = nil, &block)
blocklist = Blocklist.new(name, &block)

if name
@blocklists[name] = blocklist
else
@anonymous_blocklists << blocklist
end
end

def blocklist_ip(ip_address)
@anonymous_blocklists << Blocklist.new { |request| IPAddr.new(ip_address).include?(IPAddr.new(request.ip)) }
end

def safelist_ip(ip_address)
@anonymous_safelists << Safelist.new { |request| IPAddr.new(ip_address).include?(IPAddr.new(request.ip)) }
end

def throttle(name, options, &block)
@throttles[name] = Throttle.new(name, options, &block)
end

def track(name, options = {}, &block)
@tracks[name] = Track.new(name, options, &block)
end

def safelisted?(request)
@anonymous_safelists.any? { |safelist| safelist.matched_by?(request) } ||
@safelists.any? { |_name, safelist| safelist.matched_by?(request) }
end

def blocklisted?(request)
@anonymous_blocklists.any? { |blocklist| blocklist.matched_by?(request) } ||
@blocklists.any? { |_name, blocklist| blocklist.matched_by?(request) }
end

def throttled?(request)
@throttles.any? do |_name, throttle|
throttle.matched_by?(request)
end
end

def tracked?(request)
@tracks.each_value do |track|
track.matched_by?(request)
end
end

def clear_configuration
@safelists = {}
@blocklists = {}
@throttles = {}
@tracks = {}
@anonymous_blocklists = []
@anonymous_safelists = []
end
end
end
end