Class: Rack::Contrib::Sign::Middleware

Inherits:
Object
  • Object
show all
Defined in:
lib/rack/contrib/sign/middleware.rb

Instance Method Summary collapse

Constructor Details

#initialize(app, opts) ⇒ Middleware

Returns a new instance of Middleware.



6
7
8
9
10
11
12
13
# 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

Instance Method Details

#build_receipt(env, credentials) ⇒ Object

Extract environmental data into a Receipt



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# 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) ⇒ Object



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 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) ⇒ Object

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



75
76
77
78
79
# 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) ⇒ Object

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



97
98
99
100
101
102
103
104
105
106
107
108
109
110
# 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) ⇒ Object

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



83
84
85
86
87
88
89
90
91
92
93
# 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) ⇒ Object



112
113
114
115
116
# 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