lockbox_middleware 1.5.1 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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.
|