lockbox_middleware 1.5.1 → 1.6.2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/hmac_request.rb +145 -0
- data/lib/lockbox_cache.rb +7 -17
- data/lib/lockbox_middleware.rb +97 -86
- data/spec/lib/lockbox_middleware_spec.rb +71 -11
- data/spec/support/mocha.rb +1 -1
- metadata +23 -8
data/lib/hmac_request.rb
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'dnclabs-auth-hmac'
|
3
|
+
require 'auth-hmac'
|
4
|
+
require 'digest/md5'
|
5
|
+
|
6
|
+
class HmacRequest
|
7
|
+
attr_accessor :request, :env, :body, :hmac_id, :hmac_hash
|
8
|
+
undef :method
|
9
|
+
|
10
|
+
@@valid_date_window = 600 # seconds
|
11
|
+
|
12
|
+
HTTP_HEADER_TO_ENV_MAP = { 'Content-Type' => 'CONTENT_TYPE',
|
13
|
+
'Content-MD5' => 'CONTENT_MD5',
|
14
|
+
'Date' => 'HTTP_DATE',
|
15
|
+
'Method' => 'REQUEST_METHOD',
|
16
|
+
'Authorization' => 'HTTP_AUTHORIZATION' }
|
17
|
+
|
18
|
+
def self.new_from_rack_env(env)
|
19
|
+
r = self.new(env)
|
20
|
+
return r
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.new_from_rails_request(request)
|
24
|
+
r = self.new(request.headers)
|
25
|
+
#pull stuff out of X-Referer, which is where the middleware sticks it
|
26
|
+
HTTP_HEADER_TO_ENV_MAP.each_pair do |h,e|
|
27
|
+
r.env[e] = r.env["X-Referer-#{h}"] unless r.env["X-Referer-#{h}"].blank?
|
28
|
+
end
|
29
|
+
|
30
|
+
return r
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize(env)
|
34
|
+
@request = Rack::Request.new(env)
|
35
|
+
@env = @request.env
|
36
|
+
@body = @request.body if has_body?(@env['REQUEST_METHOD'])
|
37
|
+
end
|
38
|
+
|
39
|
+
def [](key)
|
40
|
+
@request[key]
|
41
|
+
end
|
42
|
+
|
43
|
+
def path
|
44
|
+
#use Referer if it's there, which it will be when this gets called while hitting the AuthenticationController
|
45
|
+
if @env['Referer'].to_s =~ /^(?:http:\/\/)?[^\/]*(\/.*)$/
|
46
|
+
return $1
|
47
|
+
end
|
48
|
+
#if we're in the middleware, it won't be there but we can use the request's path to the same effect
|
49
|
+
return @request.path
|
50
|
+
end
|
51
|
+
|
52
|
+
def has_body?(method)
|
53
|
+
["PUT","POST"].include?(method)
|
54
|
+
end
|
55
|
+
|
56
|
+
def hmac_id
|
57
|
+
get_hmac_vals if @hmac_id.nil?
|
58
|
+
@hmac_id
|
59
|
+
end
|
60
|
+
|
61
|
+
def hmac_hash
|
62
|
+
get_hmac_vals if @hmac_hash.nil?
|
63
|
+
@hmac_hash
|
64
|
+
end
|
65
|
+
|
66
|
+
def get_hmac_vals
|
67
|
+
@env['HTTP_AUTHORIZATION'].to_s =~ /^AuthHMAC ([^:]+):(.*)$/
|
68
|
+
@hmac_id = $1
|
69
|
+
@hmac_hash = $2
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
def hmac_auth(credential_store)
|
74
|
+
authhmac = AuthHMAC.new(credential_store)
|
75
|
+
if authhmac.authenticated?(self) && (@env['HTTP_DATE'].blank? || self.date_is_recent? )
|
76
|
+
credential_store[self.hmac_id]
|
77
|
+
else
|
78
|
+
log_auth_error(credential_store[self.hmac_id])
|
79
|
+
return false
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def date_is_recent?()
|
84
|
+
req_date = nil
|
85
|
+
|
86
|
+
begin
|
87
|
+
req_date = Time.httpdate(@env['HTTP_DATE'])
|
88
|
+
rescue Exception => ex
|
89
|
+
if ex.message =~ /not RFC 2616 compliant/
|
90
|
+
# try rfc2822
|
91
|
+
req_date = Time.rfc2822(@env['HTTP_DATE'])
|
92
|
+
else
|
93
|
+
raise ex
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
if Time.now.to_i - req_date.to_i >= @@valid_date_window
|
98
|
+
log "Request date #{req_date} is more than #{@@valid_date_window} seconds old"
|
99
|
+
return false
|
100
|
+
else
|
101
|
+
return true
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
#these are the X-Referer-Headers that get passed along to lockbox from the middleware for authentication
|
106
|
+
def get_xreferer_auth_headers()
|
107
|
+
headers = {}
|
108
|
+
headers['Referer'] = "#{@env['rack.url_scheme']}://#{@env['SERVER_NAME']}#{@env['PATH_INFO']}"
|
109
|
+
headers['Referer'] << "?#{@env['QUERY_STRING']}" unless @env['QUERY_STRING'].blank?
|
110
|
+
HTTP_HEADER_TO_ENV_MAP.each_pair do |h,e|
|
111
|
+
headers["X-Referer-#{h}"] = @env[e] unless @env[e].blank?
|
112
|
+
end
|
113
|
+
headers['X-Referer-Content-MD5'] = Digest::MD5.hexdigest(@request.body.read) if @env['CONTENT_TYPE']
|
114
|
+
headers["X-Referer-Date"] = @env['HTTP_X_AUTHHMAC_REQUEST_DATE'] unless @env['HTTP_X_AUTHHMAC_REQUEST_DATE'].blank?
|
115
|
+
headers
|
116
|
+
end
|
117
|
+
|
118
|
+
def log_auth_error(key)
|
119
|
+
log "Logging Lockbox HMAC authorization error:"
|
120
|
+
log "Path: #{self.path}"
|
121
|
+
|
122
|
+
HTTP_HEADER_TO_ENV_MAP.values.each do |header|
|
123
|
+
log "#{header}: #{@env[header]}"
|
124
|
+
end
|
125
|
+
|
126
|
+
log "HMAC Canonical String: #{ AuthHMAC::CanonicalString.new(self).inspect}"
|
127
|
+
|
128
|
+
if self.hmac_id.nil?
|
129
|
+
log("HMAC failed because request is not signed")
|
130
|
+
elsif key
|
131
|
+
log("HMAC failed - expected #{AuthHMAC.signature(self,key)} but was #{self.hmac_hash}")
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
def log(msg)
|
137
|
+
logger = nil
|
138
|
+
if defined?(Rails.logger)
|
139
|
+
Rails.logger.error msg
|
140
|
+
else
|
141
|
+
$stdout.puts msg
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
data/lib/lockbox_cache.rb
CHANGED
@@ -3,22 +3,8 @@ require 'forwardable'
|
|
3
3
|
module LockBoxCache
|
4
4
|
class Cache
|
5
5
|
extend Forwardable
|
6
|
-
def_delegators :@cache, :write, :read, :delete
|
6
|
+
def_delegators :@cache, :write, :read, :delete, :clear
|
7
7
|
|
8
|
-
class RailsCache
|
9
|
-
def write(key, value)
|
10
|
-
Rails.cache.write(key, value)
|
11
|
-
end
|
12
|
-
|
13
|
-
def read(key)
|
14
|
-
Rails.cache.read(key)
|
15
|
-
end
|
16
|
-
|
17
|
-
def delete(key)
|
18
|
-
Rails.cache.delete(key)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
8
|
class HashCache
|
23
9
|
def initialize
|
24
10
|
@store = Hash.new
|
@@ -35,14 +21,18 @@ module LockBoxCache
|
|
35
21
|
def delete(key)
|
36
22
|
@store.delete(key)
|
37
23
|
end
|
24
|
+
|
25
|
+
def clear
|
26
|
+
@store = {}
|
27
|
+
end
|
38
28
|
end
|
39
29
|
|
40
30
|
def initialize(use_rails_cache=true)
|
41
31
|
if use_rails_cache && defined?(Rails)
|
42
|
-
@cache =
|
32
|
+
@cache = Rails.cache
|
43
33
|
else
|
44
34
|
@cache = HashCache.new
|
45
35
|
end
|
46
36
|
end
|
47
37
|
end
|
48
|
-
end
|
38
|
+
end
|
data/lib/lockbox_middleware.rb
CHANGED
@@ -1,36 +1,35 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
|
3
|
-
require 'httparty'
|
2
|
+
require 'httpotato'
|
4
3
|
require 'lockbox_cache'
|
5
|
-
require '
|
4
|
+
require 'hmac_request'
|
6
5
|
|
7
6
|
class LockBox
|
8
|
-
include
|
7
|
+
include HTTPotato
|
9
8
|
include LockBoxCache
|
9
|
+
|
10
|
+
attr_accessor :cache
|
11
|
+
|
10
12
|
@@config = nil
|
13
|
+
@@protected_paths = nil
|
11
14
|
|
12
15
|
def self.config
|
13
16
|
return @@config if @@config
|
14
|
-
if
|
15
|
-
|
17
|
+
#use rails config if it's there
|
18
|
+
if defined?(Rails) && Rails.root
|
19
|
+
config_file = Rails.root.join('config','lockbox.yml')
|
20
|
+
@@config = YAML.load_file(config_file)[Rails.env]
|
16
21
|
else
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
environment = Rails.env if defined? Rails
|
22
|
-
environment ||= ENV['RACK_ENV']
|
23
|
-
environment ||= 'test'
|
24
|
-
if !environment.nil?
|
25
|
-
if !yaml_config['all'].nil?
|
22
|
+
env = ENV['RACK_ENV'] || "test"
|
23
|
+
config_file = File.join(Dir.pwd, 'config','lockbox.yml')
|
24
|
+
all_configs = YAML.load_file(config_file)
|
25
|
+
if !all_configs['all'].nil?
|
26
26
|
$stderr.puts "The 'all' environment is deprecated in lockbox.yml; use built-in yaml convention instead."
|
27
|
-
|
28
|
-
return_config.merge!(yaml_config[environment])
|
27
|
+
@@config = all_configs['all'].merge!(all_configs[env])
|
29
28
|
else
|
30
|
-
|
29
|
+
@@config = all_configs[env]
|
31
30
|
end
|
32
31
|
end
|
33
|
-
@@config
|
32
|
+
return @@config
|
34
33
|
end
|
35
34
|
|
36
35
|
base_uri config['base_uri']
|
@@ -43,64 +42,65 @@ class LockBox
|
|
43
42
|
def call(env)
|
44
43
|
dup.call!(env)
|
45
44
|
end
|
46
|
-
|
45
|
+
|
46
|
+
def cache_string_for_key(api_key)
|
47
|
+
"lockbox_key_#{api_key}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def cache_string_for_hmac(hmac_id)
|
51
|
+
"lockbox_hmac_#{hmac_id.gsub(/[^a-z0-9]/i,'_')}"
|
52
|
+
end
|
53
|
+
|
47
54
|
def protected_paths
|
48
|
-
self.class.config['protect_paths'].map
|
49
|
-
Regexp.new(path)
|
50
|
-
end
|
55
|
+
@@protect_paths ||= self.class.config['protect_paths'].map{ |path| Regexp.new(path) }
|
51
56
|
end
|
52
57
|
|
53
58
|
def call!(env)
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
if key.blank?
|
63
|
-
key = 'hmac'
|
59
|
+
protected_path = protected_paths.detect{|path| env['PATH_INFO'] =~ path}
|
60
|
+
#if the requested path is protected, it needs to be authenticated
|
61
|
+
if protected_path
|
62
|
+
request = HmacRequest.new_from_rack_env(env)
|
63
|
+
if !request['key'].nil?
|
64
|
+
auth = auth_via_key(request['key'], request)
|
65
|
+
else
|
66
|
+
auth = auth_via_hmac(request)
|
64
67
|
end
|
65
|
-
|
66
|
-
auth
|
67
|
-
authorized = auth[:authorized]
|
68
|
-
auth_headers = auth[:headers]
|
69
|
-
|
70
|
-
if authorized
|
68
|
+
|
69
|
+
if auth[:authorized]
|
71
70
|
app_response = @app.call(env)
|
72
|
-
|
73
|
-
response_headers = app_headers.merge(auth_headers)
|
74
|
-
return [app_response[0], response_headers, app_response[2]]
|
71
|
+
return [app_response[0], app_response[1].merge(auth[:headers]), app_response[2]]
|
75
72
|
else
|
76
73
|
message = "Access Denied"
|
77
74
|
return [401, {'Content-Type' => 'text/plain', 'Content-Length' => "#{message.length}"}, [message]]
|
78
75
|
end
|
79
|
-
|
80
|
-
end
|
81
|
-
unless path_protected
|
76
|
+
else
|
82
77
|
#pass everything else straight through to app
|
83
78
|
return @app.call(env)
|
84
79
|
end
|
85
80
|
end
|
86
81
|
|
87
|
-
def
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
82
|
+
def auth_via_key(api_key, request)
|
83
|
+
cached_auth = check_key_cache(api_key)
|
84
|
+
# currently we don't cache forward headers
|
85
|
+
return {:authorized => cached_auth, :headers => {}} if cached_auth
|
86
|
+
auth_response = self.class.get("/authentication/#{api_key}", {:headers => request.get_xreferer_auth_headers, :request => {:application_name => LockBox.config['application_name']}})
|
87
|
+
authorized = (auth_response.code == 200)
|
88
|
+
cache_key_response_if_allowed(api_key, auth_response) if authorized
|
89
|
+
{:authorized => authorized, :headers => response_headers(auth_response)}
|
90
|
+
end
|
91
|
+
|
92
|
+
def auth_via_hmac(hmac_request)
|
93
|
+
cached_auth = check_hmac_cache(hmac_request)
|
94
|
+
return {:authorized => cached_auth, :headers => {}} if cached_auth
|
95
|
+
auth_response = self.class.get("/authentication/hmac", {:headers => hmac_request.get_xreferer_auth_headers, :request => {:application_name => LockBox.config['application_name']}})
|
96
96
|
authorized = (auth_response.code == 200)
|
97
|
-
|
97
|
+
cache_hmac_response_if_allowed(hmac_request, auth_response) if authorized
|
98
98
|
{:authorized => authorized, :headers => response_headers(auth_response)}
|
99
99
|
end
|
100
|
-
|
100
|
+
|
101
101
|
private
|
102
|
-
|
103
|
-
def
|
102
|
+
|
103
|
+
def cache_key_response_if_allowed(api_key, auth_response)
|
104
104
|
cache_control = auth_response.headers['Cache-Control'].split(/,\s*/)
|
105
105
|
cache_max_age = 0
|
106
106
|
cache_public = false
|
@@ -113,52 +113,63 @@ class LockBox
|
|
113
113
|
end
|
114
114
|
caching_allowed = (cache_max_age > 0 && cache_public)
|
115
115
|
expiration = Time.at(Time.now.to_i + cache_max_age)
|
116
|
-
|
116
|
+
@cache.write(cache_string_for_key(api_key), expiration.to_i) if caching_allowed
|
117
117
|
end
|
118
118
|
|
119
|
-
def
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
119
|
+
def cache_hmac_response_if_allowed(hmac_request, auth_response)
|
120
|
+
cache_control = auth_response.headers['Cache-Control'].split(/,\s*/)
|
121
|
+
cache_max_age = 0
|
122
|
+
cache_public = false
|
123
|
+
cache_control.each do |c|
|
124
|
+
if c =~ /^max-age=\s*(\d+)$/
|
125
|
+
cache_max_age = $1.to_i
|
126
|
+
elsif c == 'public'
|
127
|
+
cache_public = true
|
126
128
|
end
|
127
129
|
end
|
128
|
-
|
130
|
+
caching_allowed = (cache_max_age > 0 && cache_public)
|
131
|
+
expiration = Time.at(Time.now.to_i + cache_max_age)
|
132
|
+
if caching_allowed
|
133
|
+
api_key = auth_response.headers['X-LockBox-API-Key']
|
134
|
+
@cache.write(cache_string_for_hmac(hmac_request.hmac_id), [api_key, expiration.to_i])
|
135
|
+
end
|
129
136
|
end
|
130
137
|
|
131
|
-
def
|
138
|
+
def response_headers(auth_response)
|
132
139
|
headers = {}
|
133
|
-
headers
|
134
|
-
|
135
|
-
headers['X-Referer-Content-MD5'] = Digest::MD5.hexdigest(Rack::Request.new(env).body.read) if env['CONTENT_TYPE']
|
136
|
-
{'Content-Type' => 'CONTENT_TYPE', 'Date' => 'HTTP_DATE', 'Method' => 'REQUEST_METHOD',
|
137
|
-
'Authorization' => 'HTTP_AUTHORIZATION'}.each_pair do |h,e|
|
138
|
-
headers["X-Referer-#{h}"] = env[e] unless env[e].blank?
|
140
|
+
auth_response.headers.each_pair do |h,v|
|
141
|
+
headers[h] = v if h =~ /^X-RateLimit-|^X-LockBox-/
|
139
142
|
end
|
140
|
-
headers["X-Referer-Date"] = env['HTTP_X_AUTHHMAC_REQUEST_DATE'] unless env['HTTP_X_AUTHHMAC_REQUEST_DATE'].blank?
|
141
143
|
headers
|
142
144
|
end
|
143
|
-
|
144
|
-
def cache_key(api_key)
|
145
|
-
"lockbox_#{api_key}"
|
146
|
-
end
|
147
145
|
|
148
|
-
def
|
149
|
-
expiration = @cache.read(
|
146
|
+
def check_key_cache(api_key)
|
147
|
+
expiration = @cache.read(cache_string_for_key(api_key))
|
150
148
|
return nil if expiration.nil?
|
151
149
|
expiration = Time.at(expiration)
|
152
150
|
if expiration <= Time.now
|
153
|
-
@cache.delete(
|
151
|
+
@cache.delete(cache_string_for_key(api_key))
|
154
152
|
nil
|
155
|
-
|
153
|
+
else
|
156
154
|
true
|
157
155
|
end
|
158
156
|
end
|
159
157
|
|
160
|
-
def
|
161
|
-
|
158
|
+
def check_hmac_cache(hmac_request)
|
159
|
+
hmac_id, hmac_hash = hmac_request.hmac_id, hmac_request.hmac_hash
|
160
|
+
return nil if hmac_id.nil? || hmac_hash.nil?
|
161
|
+
cached_val = @cache.read(cache_string_for_hmac(hmac_id))
|
162
|
+
return nil if cached_val.nil?
|
163
|
+
key, expiration = cached_val
|
164
|
+
expiration = Time.at(expiration)
|
165
|
+
if expiration <= Time.now
|
166
|
+
@cache.delete(cache_string_for_hmac(hmac_id))
|
167
|
+
nil
|
168
|
+
else
|
169
|
+
#as long as the request is signed correctly, no need to contact the lockbox server to verify
|
170
|
+
#just see if the request is signed properly and let it through if it is
|
171
|
+
return true if hmac_request.hmac_auth({hmac_id => key}) == key
|
172
|
+
return nil
|
173
|
+
end
|
162
174
|
end
|
163
|
-
|
164
175
|
end
|
@@ -99,7 +99,7 @@ describe 'LockBox' do
|
|
99
99
|
end
|
100
100
|
end
|
101
101
|
|
102
|
-
context "hitting API actions" do
|
102
|
+
context "hitting API actions with key-based authentication" do
|
103
103
|
before :each do
|
104
104
|
@max_age = 3600
|
105
105
|
successful_response = mock("MockResponse")
|
@@ -214,9 +214,7 @@ describe 'LockBox' do
|
|
214
214
|
@path = "/api/some_controller/some_action"
|
215
215
|
|
216
216
|
hmac_request = Net::HTTP::Get.new(@path, {'Date' => Time.now.httpdate})
|
217
|
-
|
218
|
-
store.stubs(:[]).with('key-id').returns("123456")
|
219
|
-
authhmac = AuthHMAC.new(store)
|
217
|
+
authhmac = AuthHMAC.new({"key-id" => "123456"})
|
220
218
|
authhmac.sign!(hmac_request, 'key-id')
|
221
219
|
@hmac_headers = hmac_request.to_hash
|
222
220
|
end
|
@@ -228,7 +226,7 @@ describe 'LockBox' do
|
|
228
226
|
get @path
|
229
227
|
last_response.status.should == 200
|
230
228
|
end
|
231
|
-
|
229
|
+
|
232
230
|
it "should return 200 for a valid HMAC request from a .NET client" do
|
233
231
|
# first test w/ a Date header too, then test w/o a separate Date header
|
234
232
|
@hmac_headers['X-AuthHMAC-Request-Date'] = @hmac_headers['Date']
|
@@ -238,7 +236,7 @@ describe 'LockBox' do
|
|
238
236
|
get @path
|
239
237
|
last_response.status.should == 200
|
240
238
|
end
|
241
|
-
|
239
|
+
|
242
240
|
it "should return 200 for a valid HMAC request from a .NET client with no Date header" do
|
243
241
|
@hmac_headers['X-AuthHMAC-Request-Date'] = @hmac_headers.delete('Date')
|
244
242
|
@hmac_headers.each_pair do |key,value|
|
@@ -256,8 +254,9 @@ describe 'LockBox' do
|
|
256
254
|
get @path
|
257
255
|
last_response.status.should == 401
|
258
256
|
end
|
257
|
+
|
259
258
|
end
|
260
|
-
|
259
|
+
|
261
260
|
context "hitting API actions via POST requests with HMAC auth" do
|
262
261
|
before :each do
|
263
262
|
@content = "" # TODO: Rack::Test sucks at some stuff, like setting the request body when making a POST
|
@@ -280,9 +279,7 @@ describe 'LockBox' do
|
|
280
279
|
|
281
280
|
hmac_request = Net::HTTP::Post.new(@path, {'Date' => Time.now.httpdate})
|
282
281
|
hmac_request.body = @content
|
283
|
-
|
284
|
-
store.stubs(:[]).with('key-id').returns("123456")
|
285
|
-
authhmac = AuthHMAC.new(store)
|
282
|
+
authhmac = AuthHMAC.new({"key-id" => "123456"})
|
286
283
|
authhmac.sign!(hmac_request, 'key-id')
|
287
284
|
@hmac_headers = hmac_request.to_hash
|
288
285
|
end
|
@@ -303,8 +300,9 @@ describe 'LockBox' do
|
|
303
300
|
post @path, @content
|
304
301
|
last_response.status.should == 401
|
305
302
|
end
|
303
|
+
|
306
304
|
end
|
307
|
-
|
305
|
+
|
308
306
|
context "hitting actions without API" do
|
309
307
|
|
310
308
|
it "should not try to authenticate a request that doesn't start with /api" do
|
@@ -314,5 +312,67 @@ describe 'LockBox' do
|
|
314
312
|
end
|
315
313
|
|
316
314
|
end
|
315
|
+
|
316
|
+
context "hitting API actions with HMAC auth caching" do
|
317
|
+
before :each do
|
318
|
+
Time.stubs(:now).returns(Time.parse("2010-05-10 16:30:00 EDT"))
|
319
|
+
@response_with_caching = mock("MockResponse")
|
320
|
+
@response_with_caching.stubs(:code).returns(200)
|
321
|
+
@response_with_caching.stubs(:headers).returns({'Cache-Control' => "public,max-age=3600,must-revalidate", "X-LockBox-API-Key" => "123456"})
|
322
|
+
|
323
|
+
@response_without_caching = mock("MockResponse")
|
324
|
+
@response_without_caching.stubs(:code).returns(200)
|
325
|
+
@response_without_caching.stubs(:headers).returns({'Cache-Control' => 'public, no-cache', "X-LockBox-API-Key" => "123456"})
|
326
|
+
|
327
|
+
@bad_response = mock("MockResponse")
|
328
|
+
@bad_response.stubs(:code).returns(401)
|
329
|
+
@bad_response.stubs(:headers).returns({'Cache-Control' => 'public, no-cache'})
|
330
|
+
|
331
|
+
@path = "/api/some_controller/some_action"
|
332
|
+
|
333
|
+
hmac_request = Net::HTTP::Get.new(@path, {'Date' => Time.now.httpdate, 'Referer' =>'http://example.org/api/some_controller/some_action' })
|
334
|
+
authhmac = AuthHMAC.new({'key-id' => '123456'})
|
335
|
+
authhmac.sign!(hmac_request, 'key-id')
|
336
|
+
@hmac_headers = hmac_request.to_hash
|
337
|
+
@hmac_headers.each_pair do |key,value|
|
338
|
+
header key, value
|
339
|
+
end
|
340
|
+
|
341
|
+
end
|
342
|
+
|
343
|
+
after :each do
|
344
|
+
app.cache.clear
|
345
|
+
end
|
346
|
+
|
347
|
+
|
348
|
+
it "should cache lockbox responses for max-age when Cache-Control allows it" do
|
349
|
+
LockBox.stubs(:get).returns(@response_with_caching)
|
350
|
+
get @path
|
351
|
+
last_response.status.should == 200
|
352
|
+
LockBox.stubs(:get).returns(@bad_response)
|
353
|
+
get @path
|
354
|
+
last_response.status.should == 200
|
355
|
+
end
|
356
|
+
|
357
|
+
it "should expire cached lockbox responses when max-age seconds have passed" do
|
358
|
+
LockBox.stubs(:get).returns(@response_with_caching)
|
359
|
+
get @path
|
360
|
+
last_response.status.should == 200
|
361
|
+
expired_time = Time.at(Time.now.to_i + 3600)
|
362
|
+
Time.stubs(:now).returns(expired_time)
|
363
|
+
LockBox.stubs(:get).returns(@bad_response)
|
364
|
+
get @path
|
365
|
+
last_response.status.should == 401
|
366
|
+
end
|
367
|
+
|
368
|
+
it "should not cache lockbox responses when Cache-Control does not allow it" do
|
369
|
+
LockBox.stubs(:get).returns(@response_without_caching)
|
370
|
+
get @path
|
371
|
+
last_response.status.should == 200
|
372
|
+
LockBox.stubs(:get).returns(@bad_response)
|
373
|
+
get @path
|
374
|
+
last_response.status.should == 401
|
375
|
+
end
|
376
|
+
end
|
317
377
|
|
318
378
|
end
|
data/spec/support/mocha.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lockbox_middleware
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
5
|
-
prerelease:
|
4
|
+
hash: 11
|
5
|
+
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 1.
|
8
|
+
- 6
|
9
|
+
- 2
|
10
|
+
version: 1.6.2
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Chris Gill
|
@@ -19,7 +19,7 @@ autorequire:
|
|
19
19
|
bindir: bin
|
20
20
|
cert_chain: []
|
21
21
|
|
22
|
-
date:
|
22
|
+
date: 2011-03-02 00:00:00 -05:00
|
23
23
|
default_executable:
|
24
24
|
dependencies:
|
25
25
|
- !ruby/object:Gem::Dependency
|
@@ -37,7 +37,7 @@ dependencies:
|
|
37
37
|
type: :runtime
|
38
38
|
version_requirements: *id001
|
39
39
|
- !ruby/object:Gem::Dependency
|
40
|
-
name:
|
40
|
+
name: httpotato
|
41
41
|
prerelease: false
|
42
42
|
requirement: &id002 !ruby/object:Gem::Requirement
|
43
43
|
none: false
|
@@ -50,6 +50,20 @@ dependencies:
|
|
50
50
|
version: "0"
|
51
51
|
type: :runtime
|
52
52
|
version_requirements: *id002
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: dnclabs-auth-hmac
|
55
|
+
prerelease: false
|
56
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 3
|
62
|
+
segments:
|
63
|
+
- 0
|
64
|
+
version: "0"
|
65
|
+
type: :runtime
|
66
|
+
version_requirements: *id003
|
53
67
|
description: Rack middleware for the LockBox centralized API authorization service. Brought to you by the DNC Innovation Lab.
|
54
68
|
email: innovationlab@dnc.org
|
55
69
|
executables: []
|
@@ -62,6 +76,7 @@ extra_rdoc_files:
|
|
62
76
|
files:
|
63
77
|
- lib/lockbox_cache.rb
|
64
78
|
- lib/lockbox_middleware.rb
|
79
|
+
- lib/hmac_request.rb
|
65
80
|
- LICENSE
|
66
81
|
- README.rdoc
|
67
82
|
- spec/lib/lockbox_cache_spec.rb
|
@@ -100,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
100
115
|
requirements: []
|
101
116
|
|
102
117
|
rubyforge_project:
|
103
|
-
rubygems_version: 1.
|
118
|
+
rubygems_version: 1.6.0
|
104
119
|
signing_key:
|
105
120
|
specification_version: 3
|
106
121
|
summary: Rack middleware for the LockBox centralized API authorization service.
|