class Rack::ExclusiveVerbs

Rack middleware implementing an IP whitelist of HTTP verbs

Usage:

use Rack::ExclusiveVerbs do
  resolver { Socket.ip_address_list.select { |addr| addr.ipv4_private? }.collect(&:ip_address) }
  allow only: '10.0.0.1', to: [:put, :post]
  allow only: '10.0.0.1', to: :post
  allow only: '10.0.0.1', to: :be_safe ## get, head, options, trace
  allow only: '10.0.0.1', to: :be_unsafe ## delete, patch, post, put
  allow range: '10.0.0.0/24', to: [:put, :post]
  allow range: '10.0.0.0/24', to: :post
  allow range: '10.0.0.0/24', to: :be_safe ## get, head, options, trace
  allow range: '10.0.0.0/24', to: :be_unsafe ## delete, patch, post, put
end

Public Class Methods

new(app, &block) click to toggle source
# File lib/rack/exclusive_verbs.rb, line 21
def initialize(app, &block)
  @app = app
  @rules = {}
  @resolve = Proc.new { |request| [IPAddr.new(request.ip)] }
  instance_eval(&block)
end

Protected Class Methods

VERSION() click to toggle source
# File lib/rack/exclusive_verbs.rb, line 97
def VERSION; "1.0.0"; end

Public Instance Methods

call(env) click to toggle source
# File lib/rack/exclusive_verbs.rb, line 28
def call(env)
  if is_allowed?(env)
    @app.call(env)
  else
    [403, {"Content-Type" => "text/plain"}, ["403 Forbidden"]]
  end
end

Protected Instance Methods

allow(config) click to toggle source
# File lib/rack/exclusive_verbs.rb, line 57
def allow(config)
  options = {
    only: nil,
    range: nil,
    to: [:head, :get, :options, :trace]
  }.merge(config)

  compound_verbs = {
    be_safe: [:get, :head, :options, :trace],
    be_unsafe: [:delete, :patch, :post, :put]
  }

  # Use options[:only] as a syntax sugar, prefer options[:range]
  options[:range] ||= options[:only]

  # Replace options[:to] values of :be_unsafe or :be_safe with real verbs
  if compound_verbs.has_key?(options[:to])
    options[:to] = compound_verbs[options[:to]]
  end

  # Wrap options[:to] in an array, if only a symbol was passed
  unless options[:to].kind_of?(Array)
    options[:to] = [options[:to]]
  end

  options[:to].each do |verb|
    unless @rules.has_key?(verb.to_sym)
      @rules[verb.to_sym] = Set.new
    end

    ip = IPAddr.new(options[:range])
    @rules[verb.to_sym].add(ip)
  end
end
is_allowed?(env) click to toggle source
# File lib/rack/exclusive_verbs.rb, line 38
def is_allowed?(env)
  request = Rack::Request.new(env)

  verb = request.request_method.downcase.to_sym
  ips = [@resolve.call(request)].flatten.map do |ip|
    if ip.kind_of?(IPAddr)
      ip
    else
      IPAddr.new(ip)
    end
  end

  if @rules.has_key?(verb)
    return @rules[verb].any? { |rule| ips.any? { |ip| rule.include?(ip) } }
  else
    return false
  end
end
resolver(&block) click to toggle source
# File lib/rack/exclusive_verbs.rb, line 92
def resolver(&block)
  @resolve = block;
end