lib/lockbox_middleware.rb in lockbox_middleware-1.5.1 vs lib/lockbox_middleware.rb in lockbox_middleware-1.6.2
- old
+ new
@@ -1,38 +1,37 @@
require 'rubygems'
-gem 'dnclabs-httparty'
-require 'httparty'
+require 'httpotato'
require 'lockbox_cache'
-require 'digest/md5'
+require 'hmac_request'
class LockBox
- include HTTParty
+ include HTTPotato
include LockBoxCache
+
+ attr_accessor :cache
+
@@config = nil
+ @@protected_paths = nil
def self.config
return @@config if @@config
- if defined?(Rails)
- root_dir = Rails.root
+ #use rails config if it's there
+ if defined?(Rails) && Rails.root
+ config_file = Rails.root.join('config','lockbox.yml')
+ @@config = YAML.load_file(config_file)[Rails.env]
else
- root_dir = '.'
- end
- yaml_config = YAML.load_file(File.join(root_dir,'config','lockbox.yml'))
- return_config = {}
- environment = Rails.env if defined? Rails
- environment ||= ENV['RACK_ENV']
- environment ||= 'test'
- if !environment.nil?
- if !yaml_config['all'].nil?
+ env = ENV['RACK_ENV'] || "test"
+ config_file = File.join(Dir.pwd, 'config','lockbox.yml')
+ all_configs = YAML.load_file(config_file)
+ if !all_configs['all'].nil?
$stderr.puts "The 'all' environment is deprecated in lockbox.yml; use built-in yaml convention instead."
- return_config = yaml_config['all']
- return_config.merge!(yaml_config[environment])
+ @@config = all_configs['all'].merge!(all_configs[env])
else
- return_config = yaml_config[environment]
+ @@config = all_configs[env]
end
end
- @@config = return_config
+ return @@config
end
base_uri config['base_uri']
def initialize(app)
@@ -41,68 +40,69 @@
end
def call(env)
dup.call!(env)
end
-
+
+ def cache_string_for_key(api_key)
+ "lockbox_key_#{api_key}"
+ end
+
+ def cache_string_for_hmac(hmac_id)
+ "lockbox_hmac_#{hmac_id.gsub(/[^a-z0-9]/i,'_')}"
+ end
+
def protected_paths
- self.class.config['protect_paths'].map do |path|
- Regexp.new(path)
- end
+ @@protect_paths ||= self.class.config['protect_paths'].map{ |path| Regexp.new(path) }
end
def call!(env)
- #attempt to authenticate any requests to /api
- request = Rack::Request.new(env)
- path_protected = false
- protected_paths.each do |path|
- if env['PATH_INFO'] =~ path
- path_protected = true
- authorized = false
- key = request['key']
- if key.blank?
- key = 'hmac'
+ protected_path = protected_paths.detect{|path| env['PATH_INFO'] =~ path}
+ #if the requested path is protected, it needs to be authenticated
+ if protected_path
+ request = HmacRequest.new_from_rack_env(env)
+ if !request['key'].nil?
+ auth = auth_via_key(request['key'], request)
+ else
+ auth = auth_via_hmac(request)
end
-
- auth = auth_response(key,env)
- authorized = auth[:authorized]
- auth_headers = auth[:headers]
-
- if authorized
+
+ if auth[:authorized]
app_response = @app.call(env)
- app_headers = app_response[1]
- response_headers = app_headers.merge(auth_headers)
- return [app_response[0], response_headers, app_response[2]]
+ return [app_response[0], app_response[1].merge(auth[:headers]), app_response[2]]
else
message = "Access Denied"
return [401, {'Content-Type' => 'text/plain', 'Content-Length' => "#{message.length}"}, [message]]
end
- end
- end
- unless path_protected
+ else
#pass everything else straight through to app
return @app.call(env)
end
end
- def auth_response(api_key, env={})
- if api_key != 'hmac'
- cached_auth = auth_cache(api_key)
- if !cached_auth.nil?
- # currently we don't cache forward headers
- return {:authorized => cached_auth, :headers => {}}
- end
- end
- auth_response = self.class.get("/authentication/#{api_key}", {:headers => auth_headers(env), :request => {:application_name => LockBox.config['application_name']}})
+ def auth_via_key(api_key, request)
+ cached_auth = check_key_cache(api_key)
+ # currently we don't cache forward headers
+ return {:authorized => cached_auth, :headers => {}} if cached_auth
+ auth_response = self.class.get("/authentication/#{api_key}", {:headers => request.get_xreferer_auth_headers, :request => {:application_name => LockBox.config['application_name']}})
authorized = (auth_response.code == 200)
- cache_response_if_allowed(api_key, auth_response) if authorized
+ cache_key_response_if_allowed(api_key, auth_response) if authorized
{:authorized => authorized, :headers => response_headers(auth_response)}
end
-
+
+ def auth_via_hmac(hmac_request)
+ cached_auth = check_hmac_cache(hmac_request)
+ return {:authorized => cached_auth, :headers => {}} if cached_auth
+ auth_response = self.class.get("/authentication/hmac", {:headers => hmac_request.get_xreferer_auth_headers, :request => {:application_name => LockBox.config['application_name']}})
+ authorized = (auth_response.code == 200)
+ cache_hmac_response_if_allowed(hmac_request, auth_response) if authorized
+ {:authorized => authorized, :headers => response_headers(auth_response)}
+ end
+
private
-
- def cache_response_if_allowed(api_key, auth_response)
+
+ def cache_key_response_if_allowed(api_key, auth_response)
cache_control = auth_response.headers['Cache-Control'].split(/,\s*/)
cache_max_age = 0
cache_public = false
cache_control.each do |c|
if c =~ /^max-age=\s*(\d+)$/
@@ -111,54 +111,65 @@
cache_public = true
end
end
caching_allowed = (cache_max_age > 0 && cache_public)
expiration = Time.at(Time.now.to_i + cache_max_age)
- cache_auth(api_key,expiration) if caching_allowed
+ @cache.write(cache_string_for_key(api_key), expiration.to_i) if caching_allowed
end
- def response_headers(auth_response)
- headers = {}
- auth_response.headers.each_pair do |h,v|
- if h =~ /^X-RateLimit-/
- headers[h] = v
- elsif h =~ /^X-LockBox-/
- headers[h] = v
+ def cache_hmac_response_if_allowed(hmac_request, auth_response)
+ cache_control = auth_response.headers['Cache-Control'].split(/,\s*/)
+ cache_max_age = 0
+ cache_public = false
+ cache_control.each do |c|
+ if c =~ /^max-age=\s*(\d+)$/
+ cache_max_age = $1.to_i
+ elsif c == 'public'
+ cache_public = true
end
end
- headers
+ caching_allowed = (cache_max_age > 0 && cache_public)
+ expiration = Time.at(Time.now.to_i + cache_max_age)
+ if caching_allowed
+ api_key = auth_response.headers['X-LockBox-API-Key']
+ @cache.write(cache_string_for_hmac(hmac_request.hmac_id), [api_key, expiration.to_i])
+ end
end
- def auth_headers(env)
+ def response_headers(auth_response)
headers = {}
- headers['Referer'] = "#{env['rack.url_scheme']}://#{env['SERVER_NAME']}#{env['PATH_INFO']}"
- headers['Referer'] << "?#{env['QUERY_STRING']}" unless env['QUERY_STRING'].blank?
- headers['X-Referer-Content-MD5'] = Digest::MD5.hexdigest(Rack::Request.new(env).body.read) if env['CONTENT_TYPE']
- {'Content-Type' => 'CONTENT_TYPE', 'Date' => 'HTTP_DATE', 'Method' => 'REQUEST_METHOD',
- 'Authorization' => 'HTTP_AUTHORIZATION'}.each_pair do |h,e|
- headers["X-Referer-#{h}"] = env[e] unless env[e].blank?
+ auth_response.headers.each_pair do |h,v|
+ headers[h] = v if h =~ /^X-RateLimit-|^X-LockBox-/
end
- headers["X-Referer-Date"] = env['HTTP_X_AUTHHMAC_REQUEST_DATE'] unless env['HTTP_X_AUTHHMAC_REQUEST_DATE'].blank?
headers
end
-
- def cache_key(api_key)
- "lockbox_#{api_key}"
- end
- def auth_cache(api_key)
- expiration = @cache.read(cache_key(api_key))
+ def check_key_cache(api_key)
+ expiration = @cache.read(cache_string_for_key(api_key))
return nil if expiration.nil?
expiration = Time.at(expiration)
if expiration <= Time.now
- @cache.delete(cache_key(api_key))
+ @cache.delete(cache_string_for_key(api_key))
nil
- elsif expiration > Time.now
+ else
true
end
end
- def cache_auth(api_key,expiration)
- @cache.write(cache_key(api_key),expiration.to_i)
+ def check_hmac_cache(hmac_request)
+ hmac_id, hmac_hash = hmac_request.hmac_id, hmac_request.hmac_hash
+ return nil if hmac_id.nil? || hmac_hash.nil?
+ cached_val = @cache.read(cache_string_for_hmac(hmac_id))
+ return nil if cached_val.nil?
+ key, expiration = cached_val
+ expiration = Time.at(expiration)
+ if expiration <= Time.now
+ @cache.delete(cache_string_for_hmac(hmac_id))
+ nil
+ else
+ #as long as the request is signed correctly, no need to contact the lockbox server to verify
+ #just see if the request is signed properly and let it through if it is
+ return true if hmac_request.hmac_auth({hmac_id => key}) == key
+ return nil
+ end
end
-
end