class Rack::Contrib::Sign::Middleware

Public Class Methods

new(app, opts) click to toggle source
# File lib/rack/contrib/sign/middleware.rb, line 6
def initialize app, opts

  @app = app
  @logger = opts[:logger]
  @realm = opts[:realm]
  @credentials = opts[:credentials] || {}
  @header_prefix = (opts[:prefix] || "").gsub(/-/, '_').downcase
end

Public Instance Methods

build_receipt(env, credentials) click to toggle source

Extract environmental data into a Receipt

# File lib/rack/contrib/sign/middleware.rb, line 48
def build_receipt env, credentials
  req = Rack::Request.new(env)
  receipt = Rack::Contrib::Sign::Receipt.new

  port = ''
  unless (
    (req.scheme == 'http' && req.port.to_s == '80') ||
    (req.scheme == 'https' && req.port.to_s == '443'))
    port = ':' + req.port.to_s
  end

  receipt.host = req.scheme + '://' + req.host + port
  receipt.uri = env['REQUEST_URI']
  receipt.request_method = env['REQUEST_METHOD']
  receipt.body = extract_body env
  receipt.content_type = env['HTTP_CONTENT_TYPE'] || ''

  extract_headers(env).each { |h,v| receipt.headers[h] = v }

  receipt.api_key = credentials[:key]
  receipt.api_secret = get_secret(credentials[:key])

  receipt
end
call(env) click to toggle source
# File lib/rack/contrib/sign/middleware.rb, line 15
def call env
  creds = extract_credentials env['HTTP_AUTHORIZATION']
  unless creds
    @logger.info "Denied: Authorization header not present or invalid."
    return [401, {}, []]
  end

  receipt = build_receipt env, creds
  unless receipt.api_secret
    @logger.info "Denied: API key not recognized."
    return [401, {}, []]
  end

  sign = receipt.to_s

  digest = OpenSSL::Digest::Digest.new('sha1')
  validation = OpenSSL::HMAC.hexdigest(digest, receipt.api_secret, sign)

  unless validation == creds[:signature]
    @logger.error "Denied: Authorization signature does not match"
    @logger.info "Denied: EXPECTED: %s GOT: %s" % [
      validation,
      creds[:signature]
    ]
    @logger.debug "Generated signing data:"
    @logger.debug sign
    return [401, {}, []]
  end

  @app.call env
end
extract_body(env) click to toggle source

Extract the body from the environment, ensuring to rewind the input back to zero, so future access gets the arguments.

# File lib/rack/contrib/sign/middleware.rb, line 75
def extract_body env
  env['rack.input'].read
ensure
  env['rack.input'].rewind
end
extract_credentials(auth_header) click to toggle source

Pass in the Authorization header, and get back the key and signature.

# File lib/rack/contrib/sign/middleware.rb, line 97
def extract_credentials auth_header
  return false if auth_header.nil?

  pattern = /^(?<realm>.*) (?<api_key>.*):(?<signature>.*)$/
  matches = auth_header.match(pattern)

  return false if matches.nil?
  return false unless matches[:realm] == @realm

  {
    key: matches[:api_key],
    signature: matches[:signature]
  }
end
extract_headers(env) click to toggle source

Extract all the headers with our Prefix from the ENV and return the hash

# File lib/rack/contrib/sign/middleware.rb, line 83
def extract_headers env
  headers = {}

  env.sort_by { |k,v| k.to_s.downcase }.each do |key,val|
    next unless key =~ /^http_#{@header_prefix}/i
    header = key.sub(/^http_/i, '').gsub(/_/, '-')
    headers[header] = val
  end

  headers
end
get_secret(api_key) click to toggle source
# File lib/rack/contrib/sign/middleware.rb, line 112
def get_secret api_key
  return false unless @credentials.has_key? api_key

  return @credentials.fetch(api_key)
end