secure_escrow 0.0.1 → 0.0.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/Gemfile.lock +0 -2
- data/Readme.md +19 -13
- data/lib/assets/javascripts/secure_escrow.js +18 -19
- data/lib/secure_escrow/middleware.rb +75 -44
- data/lib/secure_escrow/railtie.rb +18 -12
- data/lib/secure_escrow/version.rb +1 -1
- data/secure_escrow.gemspec +1 -1
- data/spec/middleware_spec.rb +180 -49
- data/spec/mock_engine.rb +13 -10
- data/spec/mock_rack_app.rb +6 -0
- data/spec/mock_redis.rb +1 -1
- data/spec/spec_helper.rb +1 -0
- metadata +7 -5
data/Gemfile.lock
CHANGED
@@ -2,7 +2,6 @@ PATH
|
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
4
|
secure_escrow (0.0.1)
|
5
|
-
redis
|
6
5
|
|
7
6
|
GEM
|
8
7
|
remote: http://rubygems.org/
|
@@ -65,7 +64,6 @@ GEM
|
|
65
64
|
rake (0.9.2.2)
|
66
65
|
rb-appscript (0.6.1)
|
67
66
|
rb-fsevent (0.4.3.1)
|
68
|
-
redis (2.2.2)
|
69
67
|
rspec (2.7.0)
|
70
68
|
rspec-core (~> 2.7.0)
|
71
69
|
rspec-expectations (~> 2.7.0)
|
data/Readme.md
CHANGED
@@ -35,16 +35,18 @@ SecureEscrow has 5 integration points with a Rails application
|
|
35
35
|
- Forms generated by views
|
36
36
|
|
37
37
|
### Install as Middleware
|
38
|
-
Add the SecureEscrow::Middleware around your Rails application. It must be configured with
|
38
|
+
Add the SecureEscrow::Middleware around your Rails application. It must be configured with both a Rack endpoint to call, and a Rails app instance to retrieve routes and configuration information from.
|
39
39
|
In this example, the <tt>Awesome::Application.config.redis</tt> value is available after the <tt>environment</tt> file has been run.
|
40
40
|
|
41
|
-
Example <tt>config.
|
41
|
+
Example <tt>config/initializers/secure_escrow.rb</tt>:
|
42
42
|
|
43
43
|
````ruby
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
44
|
+
Awesome::Application.middleware.insert_before(
|
45
|
+
Rack::Lock,
|
46
|
+
SecureEscrow::Middleware,
|
47
|
+
Awesome::Application,
|
48
|
+
store: Awesome::Application.config.redis
|
49
|
+
)
|
48
50
|
````
|
49
51
|
|
50
52
|
### Configure Domain Information
|
@@ -54,12 +56,14 @@ may differ for various environments. For example, in <tt>development.rb</tt> you
|
|
54
56
|
|
55
57
|
````ruby
|
56
58
|
# = SecureEscrow config =
|
57
|
-
config.
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
59
|
+
config.secure_escrow = {
|
60
|
+
secure_domain_name: 'www.secure-awesome.devlocal',
|
61
|
+
secure_domain_protocol: 'http',
|
62
|
+
secure_domain_port: 3000,
|
63
|
+
insecure_domain_name: 'www.insecure-awesome.devlocal',
|
64
|
+
insecure_domain_protocol: 'http',
|
65
|
+
insecure_domain_port: 3000,
|
66
|
+
}
|
63
67
|
`````
|
64
68
|
|
65
69
|
### Declare Escrowed Routes
|
@@ -80,10 +84,12 @@ escrow 'signin' => 'sessions#create', as: :user_session
|
|
80
84
|
get 'signout'=> 'sessions#destroy', as: :destroy_user_session
|
81
85
|
````
|
82
86
|
|
87
|
+
Alternatively, SecureEscrow can be configured to escrow any POST action without explicitly declaring it within the application. Just provide `allow_non_escrow_routes: true` to the initial SecureEscrow configuration.
|
88
|
+
|
83
89
|
### Deliver JavaScript assets
|
84
90
|
SecureEscrow integrates in the Rails asset pipeline. Just add the following to your <tt>application.js</tt>.
|
85
91
|
|
86
|
-
````
|
92
|
+
````javascript
|
87
93
|
// SecureEscrow
|
88
94
|
// ======================
|
89
95
|
//= require secure_escrow
|
@@ -14,12 +14,18 @@
|
|
14
14
|
// Consumes iframe content and bubbles it up as an ajax response
|
15
15
|
function onLoad() {
|
16
16
|
var payload_error,
|
17
|
-
|
17
|
+
inner_text, http_status, wrapper_json, json, response;
|
18
18
|
|
19
19
|
try {
|
20
|
-
|
20
|
+
inner_text = $(iframe[0].contentWindow.document).find("#response").text();
|
21
21
|
try {
|
22
|
-
|
22
|
+
wrapper_json = JSON.parse(inner_text);
|
23
|
+
http_status = wrapper_json.status;
|
24
|
+
try {
|
25
|
+
json = JSON.parse(wrapper_json.body);
|
26
|
+
} catch(_3) {
|
27
|
+
payload_error = 'Error parsing original body';
|
28
|
+
}
|
23
29
|
} catch(_2) {
|
24
30
|
payload_error = 'Error parsing JSON response';
|
25
31
|
}
|
@@ -27,23 +33,18 @@
|
|
27
33
|
payload_error = 'Error accessing response body';
|
28
34
|
}
|
29
35
|
|
30
|
-
|
31
36
|
response = {
|
32
|
-
responseText:
|
37
|
+
responseText: wrapper_json.body
|
33
38
|
};
|
34
39
|
|
35
|
-
if (
|
36
|
-
|
37
|
-
} else {
|
38
|
-
response.responseJSON = json;
|
39
|
-
}
|
40
|
-
|
41
|
-
if (json && json.success) {
|
42
|
-
form.trigger('ajax:success', response);
|
40
|
+
if (http_status >= 200 && http_status < 300) {
|
41
|
+
form.trigger('ajax:success', [json]);
|
43
42
|
} else {
|
44
|
-
form.trigger('ajax:error', response);
|
43
|
+
form.trigger('ajax:error', [response]);
|
45
44
|
}
|
46
|
-
form.trigger('ajax:complete', response);
|
45
|
+
form.trigger('ajax:complete', [response]);
|
46
|
+
|
47
|
+
iframe.remove();
|
47
48
|
}
|
48
49
|
|
49
50
|
iframe.load(onLoad);
|
@@ -57,10 +58,8 @@
|
|
57
58
|
escrow = form.data('escrow'),
|
58
59
|
isEscrow = escrow !== undefined;
|
59
60
|
|
60
|
-
if (isEscrow) {
|
61
|
-
|
62
|
-
handleRemoteEscrowSubmission(form);
|
63
|
-
}
|
61
|
+
if (isEscrow && 'iframe' === escrow) {
|
62
|
+
handleRemoteEscrowSubmission(form);
|
64
63
|
}
|
65
64
|
|
66
65
|
});
|
@@ -9,8 +9,11 @@ module SecureEscrow
|
|
9
9
|
POST = 'POST'
|
10
10
|
GET = 'GET'
|
11
11
|
COOKIE_SEPARATOR = ';'
|
12
|
+
EXPIRE_COOKIE = Time.gm(1979, 1, 1)
|
12
13
|
RAILS_ROUTES = 'action_dispatch.routes'
|
13
14
|
LOCATION = 'Location'
|
15
|
+
CONTENT_TYPE = 'Content-Type'
|
16
|
+
JSON_CONTENT = /^application\/json/
|
14
17
|
ESCROW_MATCH = /^(.+)\.(.+)$/
|
15
18
|
TTL = 180 # Seconds until proxied response expires
|
16
19
|
NONCE = 'nonce'
|
@@ -21,9 +24,11 @@ module SecureEscrow
|
|
21
24
|
end
|
22
25
|
|
23
26
|
class Middleware
|
24
|
-
def initialize
|
25
|
-
@
|
26
|
-
@
|
27
|
+
def initialize next_app, rails_app, config
|
28
|
+
@next_app = next_app
|
29
|
+
@rails_app = rails_app
|
30
|
+
@config = config
|
31
|
+
@store = config[:store]
|
27
32
|
end
|
28
33
|
|
29
34
|
def call env
|
@@ -31,7 +36,7 @@ module SecureEscrow
|
|
31
36
|
end
|
32
37
|
|
33
38
|
def presenter env
|
34
|
-
Presenter.new @
|
39
|
+
Presenter.new @next_app, @rails_app, @config, env
|
35
40
|
end
|
36
41
|
|
37
42
|
def handle_presenter e
|
@@ -49,12 +54,14 @@ module SecureEscrow
|
|
49
54
|
class Presenter
|
50
55
|
include MiddlewareConstants
|
51
56
|
|
52
|
-
attr_reader :
|
57
|
+
attr_reader :next_app, :rails_app, :store, :config, :env
|
53
58
|
|
54
|
-
def initialize
|
55
|
-
@
|
56
|
-
@
|
57
|
-
@
|
59
|
+
def initialize next_app, rails_app, config, env
|
60
|
+
@next_app = next_app
|
61
|
+
@rails_app = rails_app
|
62
|
+
@config = config
|
63
|
+
@store = config[:store]
|
64
|
+
@env = env
|
58
65
|
end
|
59
66
|
|
60
67
|
def serve_response_from_escrow?
|
@@ -70,9 +77,11 @@ module SecureEscrow
|
|
70
77
|
end
|
71
78
|
|
72
79
|
def store_response_in_escrow?
|
73
|
-
|
74
|
-
|
75
|
-
|
80
|
+
return false unless POST == env[REQUEST_METHOD]
|
81
|
+
recognized = recognize_path
|
82
|
+
config[:allow_non_escrow_routes] ?
|
83
|
+
recognized :
|
84
|
+
recognized && recognized[:escrow]
|
76
85
|
end
|
77
86
|
|
78
87
|
def serve_response_from_escrow!
|
@@ -83,7 +92,20 @@ module SecureEscrow
|
|
83
92
|
# Destroy the stored value
|
84
93
|
store.del key
|
85
94
|
|
86
|
-
|
95
|
+
status, headers, body = value[RESPONSE]
|
96
|
+
|
97
|
+
if headers[CONTENT_TYPE] && JSON_CONTENT.match(headers[CONTENT_TYPE])
|
98
|
+
body = [
|
99
|
+
"<html><body><script id=\"response\" type=\"text/x-json\">%s</script></body></html>" %
|
100
|
+
{ status: status, body: body.join.to_s }.to_json
|
101
|
+
]
|
102
|
+
headers[CONTENT_TYPE] = "text/html; charset=utf-8"
|
103
|
+
status = 200
|
104
|
+
end
|
105
|
+
|
106
|
+
expire_cookie_token!(headers)
|
107
|
+
|
108
|
+
[ status, headers, body ]
|
87
109
|
else
|
88
110
|
# HTTP Status Code 403 - Forbidden
|
89
111
|
return [ 403, {}, [ BAD_NONCE ] ]
|
@@ -101,7 +123,10 @@ module SecureEscrow
|
|
101
123
|
id, nonce = store_in_escrow status, header, response
|
102
124
|
token = "#{id}.#{nonce}"
|
103
125
|
|
104
|
-
response_headers = {
|
126
|
+
response_headers = {
|
127
|
+
LOCATION => redirect_to_location(token),
|
128
|
+
CONTENT_TYPE => header[CONTENT_TYPE]
|
129
|
+
}
|
105
130
|
set_cookie_token!(response_headers, token) if homogenous_host_names?
|
106
131
|
|
107
132
|
# HTTP Status Code 303 - See Other
|
@@ -147,7 +172,8 @@ module SecureEscrow
|
|
147
172
|
# Serialze the nonce and Rack response triplet,
|
148
173
|
# store in Redis, and set TTL
|
149
174
|
key = escrow_key id
|
150
|
-
|
175
|
+
|
176
|
+
store.setex key, TTL, value.to_json
|
151
177
|
|
152
178
|
[ id, nonce ]
|
153
179
|
end
|
@@ -156,21 +182,15 @@ module SecureEscrow
|
|
156
182
|
[ UUID.generate, SecureRandom.hex(4) ]
|
157
183
|
end
|
158
184
|
|
159
|
-
|
160
|
-
|
161
|
-
Rack::Utils.set_cookie_header!(headers, DATA_KEY,
|
162
|
-
value: token,
|
163
|
-
httponly: true)
|
185
|
+
def call_result
|
186
|
+
@call_result ||= next_app.call env
|
164
187
|
end
|
165
188
|
|
166
189
|
def redirect_to_location token = nil
|
167
|
-
routes = app.routes
|
168
|
-
config = app.config
|
169
|
-
|
170
190
|
redirect_to_options = {
|
171
|
-
protocol:
|
172
|
-
host:
|
173
|
-
port:
|
191
|
+
protocol: rails_config[:insecure_domain_protocol] || request.protocol,
|
192
|
+
host: rails_config[:insecure_domain_name] || request.host,
|
193
|
+
port: rails_config[:insecure_domain_port] || request.port,
|
174
194
|
}
|
175
195
|
|
176
196
|
if token && !homogenous_host_names?
|
@@ -181,37 +201,50 @@ module SecureEscrow
|
|
181
201
|
recognize_path.merge(redirect_to_options))
|
182
202
|
end
|
183
203
|
|
204
|
+
private
|
205
|
+
def rails_config
|
206
|
+
@rails_config ||= rails_app.config.secure_escrow
|
207
|
+
end
|
208
|
+
|
209
|
+
def set_cookie_token! headers, token
|
210
|
+
Rack::Utils.set_cookie_header! headers, DATA_KEY,
|
211
|
+
value: token,
|
212
|
+
httponly: true
|
213
|
+
end
|
214
|
+
|
215
|
+
def expire_cookie_token! headers
|
216
|
+
Rack::Utils.set_cookie_header! headers, DATA_KEY,
|
217
|
+
value: "",
|
218
|
+
httponly: true,
|
219
|
+
expires: EXPIRE_COOKIE
|
220
|
+
end
|
221
|
+
|
184
222
|
def rewrite_location_header! header
|
185
223
|
return unless header[LOCATION]
|
186
224
|
|
187
|
-
config = app.config
|
188
|
-
routes = app.routes
|
189
|
-
|
190
225
|
# Rewrite redirect to secure domain
|
191
226
|
header[LOCATION] = routes.url_for(
|
192
227
|
routes.recognize_path(header[LOCATION]).merge(
|
193
|
-
host:
|
194
|
-
protocol:
|
195
|
-
port:
|
228
|
+
host: rails_config[:insecure_domain_name],
|
229
|
+
protocol: rails_config[:insecure_domain_protocol],
|
230
|
+
port: rails_config[:insecure_domain_port],
|
196
231
|
))
|
197
232
|
|
198
233
|
header
|
199
234
|
end
|
200
235
|
|
201
|
-
def
|
202
|
-
@
|
203
|
-
end
|
204
|
-
|
205
|
-
def rails_routes
|
206
|
-
@rails_routes ||= app.routes
|
236
|
+
def routes
|
237
|
+
@routes ||= rails_app.routes
|
207
238
|
end
|
208
239
|
|
209
240
|
# TODO: Examine the performance implications of parsing the
|
210
241
|
# Cookie / Query payload this early in the stack
|
211
242
|
def escrow_id_and_nonce
|
212
|
-
data = (homogenous_host_names? ?
|
243
|
+
data = Array((homogenous_host_names? ?
|
213
244
|
Rack::Utils.parse_query(env[HTTP_COOKIE], COOKIE_SEPARATOR) :
|
214
|
-
Rack::Utils.parse_query(env[QUERY_STRING]))[DATA_KEY]
|
245
|
+
Rack::Utils.parse_query(env[QUERY_STRING]))[DATA_KEY]).find do |e|
|
246
|
+
e.match ESCROW_MATCH
|
247
|
+
end
|
215
248
|
|
216
249
|
return unless data
|
217
250
|
match = data.match ESCROW_MATCH
|
@@ -221,18 +254,16 @@ module SecureEscrow
|
|
221
254
|
end
|
222
255
|
|
223
256
|
def homogenous_host_names?
|
224
|
-
|
225
|
-
config.secure_domain_name == config.insecure_domain_name
|
257
|
+
rails_config[:secure_domain_name] == rails_config[:insecure_domain_name]
|
226
258
|
end
|
227
259
|
|
228
260
|
def recognize_path
|
229
261
|
begin
|
230
|
-
|
262
|
+
routes.recognize_path(
|
231
263
|
env[REQUEST_PATH],
|
232
264
|
method: env[REQUEST_METHOD]
|
233
265
|
)
|
234
266
|
rescue ActionController::RoutingError
|
235
|
-
{}
|
236
267
|
end
|
237
268
|
end
|
238
269
|
|
@@ -4,44 +4,50 @@ require "action_pack"
|
|
4
4
|
module SecureEscrow
|
5
5
|
module Railtie
|
6
6
|
module Routing
|
7
|
-
def escrow
|
7
|
+
def escrow *args, &block
|
8
|
+
options = args.extract_options!
|
8
9
|
defaults = options[:defaults] || {}
|
9
10
|
defaults[:escrow] = true
|
10
|
-
|
11
|
+
options[:defaults] = defaults
|
12
|
+
args.push options
|
13
|
+
post *args, &block
|
11
14
|
end
|
12
15
|
end
|
13
16
|
|
14
17
|
module ActionViewHelper
|
15
18
|
DATA_ESCROW = 'data-escrow'
|
16
19
|
IFRAME = 'iframe'
|
17
|
-
POST = '
|
20
|
+
POST = 'POST'
|
18
21
|
|
19
22
|
def escrow_form_for record, options = {}, &proc
|
20
23
|
options[:html] ||= {}
|
21
24
|
|
22
|
-
stringy_record =
|
25
|
+
stringy_record = case record
|
26
|
+
when String, Symbol then true
|
27
|
+
else false
|
28
|
+
end
|
23
29
|
apply_form_for_options!(record, options) unless stringy_record
|
24
30
|
|
25
31
|
|
26
|
-
form_for record, escrow_options(options), &proc
|
32
|
+
form_for record, escrow_options(options, POST), &proc
|
27
33
|
end
|
28
34
|
|
29
35
|
def escrow_form_tag url_for_options = {}, options = {}, &block
|
30
|
-
form_tag url_for_options, escrow_options(options), &block
|
36
|
+
form_tag url_for_options, escrow_options(options, POST), &block
|
31
37
|
end
|
32
38
|
|
33
39
|
private
|
34
|
-
def escrow_options options
|
40
|
+
def escrow_options options, method
|
35
41
|
# Rewrite URL to point to secure domain
|
36
42
|
app = Rails.application
|
37
|
-
config = app.config
|
43
|
+
config = app.config.secure_escrow
|
38
44
|
|
39
45
|
submission_url = controller.url_for(
|
40
|
-
app.routes.recognize_path(options[:url]).
|
46
|
+
app.routes.recognize_path(options[:url], method: method).
|
41
47
|
merge(
|
42
|
-
host: config.
|
43
|
-
protocol: config.
|
44
|
-
port: config.
|
48
|
+
host: config[:secure_domain_name] || request.host,
|
49
|
+
protocol: config[:secure_domain_protocol] || request.protocol,
|
50
|
+
port: config[:secure_domain_port] || request.port,
|
45
51
|
))
|
46
52
|
|
47
53
|
options[:url] = submission_url
|
data/secure_escrow.gemspec
CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
|
|
7
7
|
s.version = SecureEscrow::VERSION
|
8
8
|
s.authors = ["Duncan Beevers"]
|
9
9
|
s.email = ["duncan@dweebd.com"]
|
10
|
-
s.homepage = ""
|
10
|
+
s.homepage = "https://github.com/duncanbeevers/SecureEscrow"
|
11
11
|
s.summary = "Secure AJAX-style actions for Rails applications"
|
12
12
|
s.description = "SecureEscrow provides a content proxy for Rails applications allowing POSTing to secure actions from insecure domains without full-page refreshes"
|
13
13
|
|
data/spec/middleware_spec.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
2
|
include SecureEscrow::MiddlewareConstants
|
3
3
|
|
4
|
-
describe
|
5
|
-
let(:
|
4
|
+
describe SecureEscrow::Middleware do
|
5
|
+
let(:rack_app) { MockRackApp.new }
|
6
|
+
let(:rails_app) { MockEngine.new }
|
6
7
|
let(:store) { MockRedis.new }
|
7
|
-
let(:
|
8
|
-
let(:
|
8
|
+
let(:config) { { store: store } }
|
9
|
+
let(:middleware) { SecureEscrow::Middleware.new rack_app, rails_app, config }
|
10
|
+
let(:presenter) { SecureEscrow::Middleware::Presenter.new rack_app, rails_app, config, env }
|
9
11
|
let(:env) { {} }
|
10
12
|
|
11
13
|
context 'as a Rack application' do
|
@@ -14,10 +16,16 @@ describe 'SecureEscrow::Middleware' do
|
|
14
16
|
end
|
15
17
|
|
16
18
|
it 'should handle_presenter with wrapped environment' do
|
17
|
-
middleware.should_receive(:
|
18
|
-
|
19
|
-
|
20
|
-
|
19
|
+
middleware.should_receive(:handle_presenter).
|
20
|
+
with(duck_type(
|
21
|
+
:serve_response_from_escrow?,
|
22
|
+
:serve_response_from_escrow!,
|
23
|
+
:response_is_redirect?,
|
24
|
+
:redirect_to_response!,
|
25
|
+
:store_response_in_escrow?,
|
26
|
+
:store_response_in_escrow_and_redirect!,
|
27
|
+
:serve_response_from_application!
|
28
|
+
)).once
|
21
29
|
|
22
30
|
middleware.call(env)
|
23
31
|
end
|
@@ -118,21 +126,21 @@ describe 'SecureEscrow::Middleware' do
|
|
118
126
|
|
119
127
|
describe 'response_is_redirect?' do
|
120
128
|
it 'should not include status codes less than 300' do
|
121
|
-
|
129
|
+
rack_app.should_receive(:call).
|
122
130
|
once.with(env).and_return([ 299, {}, [ '' ] ])
|
123
131
|
|
124
132
|
presenter.response_is_redirect?.should be_false
|
125
133
|
end
|
126
134
|
|
127
135
|
it 'should not include status codes greater than 399' do
|
128
|
-
|
136
|
+
rack_app.should_receive(:call).
|
129
137
|
once.with(env).and_return([ 400, {}, [ '' ] ])
|
130
138
|
|
131
139
|
presenter.response_is_redirect?.should be_false
|
132
140
|
end
|
133
141
|
|
134
142
|
it 'should include 304' do
|
135
|
-
|
143
|
+
rack_app.should_receive(:call).
|
136
144
|
once.with(env).and_return([ 304, {}, [ '' ] ])
|
137
145
|
|
138
146
|
presenter.response_is_redirect?.should be_true
|
@@ -148,11 +156,8 @@ describe 'SecureEscrow::Middleware' do
|
|
148
156
|
it 'should not store non-existent routes' do
|
149
157
|
presenter.env[REQUEST_METHOD] = POST
|
150
158
|
|
151
|
-
|
152
|
-
|
153
|
-
and_raise(
|
154
|
-
ActionController::RoutingError.new("No route matches #{env[REQUEST_PATH]}")
|
155
|
-
)
|
159
|
+
rails_app.routes.stub!(:recognize_path).
|
160
|
+
and_raise(ActionController::RoutingError.new("No route matches #{env[REQUEST_PATH]}"))
|
156
161
|
|
157
162
|
presenter.store_response_in_escrow?.should be_false
|
158
163
|
end
|
@@ -160,17 +165,35 @@ describe 'SecureEscrow::Middleware' do
|
|
160
165
|
it 'should not store non-escrow routes' do
|
161
166
|
presenter.env[REQUEST_METHOD] = POST
|
162
167
|
|
163
|
-
|
168
|
+
rails_app.routes.should_receive(:recognize_path).
|
164
169
|
once.with(env[REQUEST_PATH], { method: POST }).
|
165
170
|
and_return(controller: 'session', action: 'create')
|
166
171
|
|
167
172
|
presenter.store_response_in_escrow?.should be_false
|
168
173
|
end
|
169
174
|
|
175
|
+
describe 'configured not to check for escrowable routes' do
|
176
|
+
let(:config) { { store: store, allow_non_escrow_routes: true } }
|
177
|
+
|
178
|
+
it 'should store https existent, non-escrow routes' do
|
179
|
+
presenter.env[REQUEST_METHOD] = POST
|
180
|
+
|
181
|
+
presenter.store_response_in_escrow?.should be_true
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'should not store non-existent routes' do
|
185
|
+
presenter.env[REQUEST_METHOD] = POST
|
186
|
+
rails_app.routes.stub!(:recognize_path).
|
187
|
+
and_raise(ActionController::RoutingError.new("No route matches #{env[REQUEST_PATH]}"))
|
188
|
+
|
189
|
+
presenter.store_response_in_escrow?.should be_false
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
170
193
|
it 'should store escrow routes' do
|
171
194
|
presenter.env[REQUEST_METHOD] = POST
|
172
195
|
|
173
|
-
|
196
|
+
rails_app.routes.should_receive(:recognize_path).
|
174
197
|
once.with(env[REQUEST_PATH], { method: POST }).
|
175
198
|
and_return(controller: 'session', action: 'create', escrow: true)
|
176
199
|
|
@@ -196,50 +219,118 @@ describe 'SecureEscrow::Middleware' do
|
|
196
219
|
presenter.serve_response_from_escrow!
|
197
220
|
end
|
198
221
|
|
199
|
-
|
200
|
-
|
201
|
-
|
222
|
+
context 'when the response type is not application/json' do
|
223
|
+
it 'should deliver the original response code' do
|
224
|
+
response = [ 403, {}, [ 'text' ] ]
|
225
|
+
store_in_escrow store, 'id', 'nonce', response
|
226
|
+
set_escrow_cookie presenter, 'id', 'nonce'
|
227
|
+
|
228
|
+
status, headers, body = presenter.serve_response_from_escrow!
|
229
|
+
status.should eq 403
|
230
|
+
end
|
231
|
+
|
232
|
+
it 'should deliver the content-type header' do
|
233
|
+
response = [ 403, { 'Content-Type' => 'application/x-waterbuffalo' }, [ 'text' ] ]
|
234
|
+
store_in_escrow store, 'id', 'nonce', response
|
235
|
+
set_escrow_cookie presenter, 'id', 'nonce'
|
236
|
+
|
237
|
+
status, headers, body = presenter.serve_response_from_escrow!
|
238
|
+
headers['Content-Type'].should eq 'application/x-waterbuffalo'
|
239
|
+
end
|
240
|
+
|
241
|
+
it 'should deliver the original response body' do
|
242
|
+
response = [ 403, { 'Content-Type' => 'application/x-waterbuffalo' }, [ 'text' ] ]
|
243
|
+
store_in_escrow store, 'id', 'nonce', response
|
244
|
+
set_escrow_cookie presenter, 'id', 'nonce'
|
245
|
+
|
246
|
+
status, headers, body = presenter.serve_response_from_escrow!
|
247
|
+
body.should eq [ 'text' ]
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
context 'when the response type is application/json; charset=utf-8' do
|
252
|
+
let(:response_headers) { { 'Content-Type' => 'application/json; charset=utf-8' } }
|
253
|
+
it 'should deliver the response with status code 200' do
|
254
|
+
response = [ 403, response_headers, [ 'text' ] ]
|
255
|
+
store_in_escrow store, 'id', 'nonce', response
|
256
|
+
set_escrow_cookie presenter, 'id', 'nonce'
|
257
|
+
|
258
|
+
status, headers, body = presenter.serve_response_from_escrow!
|
259
|
+
status.should eq 200
|
260
|
+
end
|
261
|
+
|
262
|
+
it 'should deliver the headers with content-type text/html; charset=utf-8' do
|
263
|
+
response = [ 403, response_headers, [ 'text' ] ]
|
264
|
+
store_in_escrow store, 'id', 'nonce', response
|
265
|
+
set_escrow_cookie presenter, 'id', 'nonce'
|
266
|
+
|
267
|
+
status, headers, body = presenter.serve_response_from_escrow!
|
268
|
+
headers['Content-Type'].should eq "text/html; charset=utf-8"
|
269
|
+
end
|
270
|
+
|
271
|
+
it 'should wrap the response in html and json' do
|
272
|
+
response = [ 403, response_headers, [ 'text' ] ]
|
273
|
+
store_in_escrow store, 'id', 'nonce', response
|
274
|
+
set_escrow_cookie presenter, 'id', 'nonce'
|
275
|
+
|
276
|
+
json_representation = "{\"status\":403,\"body\":\"text\"}"
|
277
|
+
|
278
|
+
status, headers, body = presenter.serve_response_from_escrow!
|
279
|
+
body.join.should eq "<html><body><script id=\"response\" type=\"text/x-json\">#{json_representation}</script></body></html>"
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
it 'should delete the secure escrow cookie' do
|
284
|
+
store_in_escrow store, 'id', 'nonce', [
|
285
|
+
200, {
|
286
|
+
"Set-Cookie" => "_everloop_session=persists"
|
287
|
+
},
|
288
|
+
[ "" ]
|
289
|
+
]
|
290
|
+
|
202
291
|
set_escrow_cookie presenter, 'id', 'nonce'
|
203
292
|
|
204
|
-
presenter.serve_response_from_escrow
|
293
|
+
status, headers, body = presenter.serve_response_from_escrow!
|
294
|
+
headers["Set-Cookie"].should eq "_everloop_session=persists\nsecure_escrow=; expires=Mon, 01-Jan-1979 00:00:00 GMT; HttpOnly"
|
205
295
|
end
|
206
|
-
|
296
|
+
end
|
207
297
|
|
208
298
|
describe 'redirect_to_response!' do
|
209
299
|
it 'should use status code from application' do
|
210
300
|
response = [ 315, {}, [ '' ] ]
|
211
|
-
|
301
|
+
rack_app.should_receive(:call).
|
212
302
|
once.with(env).and_return(response)
|
213
303
|
|
214
304
|
presenter.redirect_to_response!.should eq response
|
215
305
|
end
|
216
306
|
|
217
307
|
it 'should rewrite location' do
|
308
|
+
config = rails_app.config.secure_escrow
|
218
309
|
original_location = "%s://%s:%s/path/" % [
|
219
|
-
|
220
|
-
|
221
|
-
|
310
|
+
config[:secure_domain_protocol],
|
311
|
+
config[:secure_domain_name],
|
312
|
+
config[:secure_domain_port],
|
222
313
|
]
|
223
314
|
expected_location = "%s://%s:%s/path/" % [
|
224
|
-
|
225
|
-
|
226
|
-
|
315
|
+
config[:insecure_domain_protocol],
|
316
|
+
config[:insecure_domain_name],
|
317
|
+
config[:insecure_domain_port],
|
227
318
|
]
|
228
319
|
|
229
320
|
original_response = [ 315, { LOCATION => original_location }, [ '' ] ]
|
230
|
-
|
321
|
+
rack_app.should_receive(:call).
|
231
322
|
once.with(env).and_return(original_response)
|
232
323
|
|
233
|
-
|
324
|
+
rails_app.routes.should_receive(:recognize_path).
|
234
325
|
once.with(original_location).
|
235
326
|
and_return(controller: 'sessions', action: 'create')
|
236
|
-
|
327
|
+
rails_app.routes.should_receive(:url_for).
|
237
328
|
once.with(
|
238
329
|
controller: 'sessions',
|
239
330
|
action: 'create',
|
240
|
-
host:
|
241
|
-
protocol:
|
242
|
-
port:
|
331
|
+
host: config[:insecure_domain_name],
|
332
|
+
protocol: config[:insecure_domain_protocol],
|
333
|
+
port: config[:insecure_domain_port],
|
243
334
|
).and_return(expected_location)
|
244
335
|
|
245
336
|
presenter.redirect_to_response![1][LOCATION].should eq expected_location
|
@@ -254,6 +345,31 @@ describe 'SecureEscrow::Middleware' do
|
|
254
345
|
presenter.store_response_in_escrow_and_redirect![0].should eq 303
|
255
346
|
end
|
256
347
|
|
348
|
+
describe 'headers' do
|
349
|
+
it 'should set Location header to redirect location' do
|
350
|
+
mock_location = mock('redirect_to_location')
|
351
|
+
presenter.should_receive(:redirect_to_location).
|
352
|
+
once.and_return(mock_location)
|
353
|
+
|
354
|
+
headers = presenter.store_response_in_escrow_and_redirect![1]
|
355
|
+
headers['Location'].should eq mock_location
|
356
|
+
end
|
357
|
+
|
358
|
+
it 'should set Location header to redirect location' do
|
359
|
+
mock_content_type = mock('content_type')
|
360
|
+
|
361
|
+
presenter.stub!(
|
362
|
+
redirect_to_location: '/',
|
363
|
+
store_in_escrow: [ 'id', 'nonce' ])
|
364
|
+
|
365
|
+
presenter.should_receive(:call_result).
|
366
|
+
and_return([ 200, { 'Content-Type' => mock_content_type}, '' ])
|
367
|
+
|
368
|
+
headers = presenter.store_response_in_escrow_and_redirect![1]
|
369
|
+
headers['Content-Type'].should eq mock_content_type
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
257
373
|
context 'when insecure_domain_name is different from secure_domain_name' do
|
258
374
|
let(:app) {
|
259
375
|
MockEngine.new(
|
@@ -279,7 +395,7 @@ describe 'SecureEscrow::Middleware' do
|
|
279
395
|
describe 'serve_response_from_application!' do
|
280
396
|
it 'should serve response from application' do
|
281
397
|
response = [ 200, {}, [ '' ] ]
|
282
|
-
|
398
|
+
rack_app.should_receive(:call).
|
283
399
|
once.with(env).and_return(response)
|
284
400
|
presenter.serve_response_from_application!.should eq response
|
285
401
|
end
|
@@ -332,10 +448,11 @@ describe 'SecureEscrow::Middleware' do
|
|
332
448
|
end
|
333
449
|
|
334
450
|
it 'should rewrite domain of redirect to secure domain' do
|
451
|
+
config = rails_app.config.secure_escrow
|
335
452
|
original_redirect_url = "%s://%s:%s" % [
|
336
|
-
|
337
|
-
|
338
|
-
|
453
|
+
config[:secure_domain_protocol],
|
454
|
+
config[:secure_domain_name],
|
455
|
+
config[:secure_domain_port],
|
339
456
|
]
|
340
457
|
rewritten_redirect_url = 'boo'
|
341
458
|
|
@@ -344,16 +461,16 @@ describe 'SecureEscrow::Middleware' do
|
|
344
461
|
presenter.should_receive(:generate_id_and_nonce).
|
345
462
|
once.with.and_return([ 'id', 'nonce'])
|
346
463
|
|
347
|
-
|
464
|
+
rails_app.routes.should_receive(:recognize_path).
|
348
465
|
once.with(original_redirect_url).
|
349
466
|
and_return(controller: 'sessions', action: 'create')
|
350
467
|
|
351
|
-
|
468
|
+
rails_app.routes.should_receive(:url_for).
|
352
469
|
once.with(
|
353
470
|
controller: 'sessions', action: 'create',
|
354
|
-
host:
|
355
|
-
protocol:
|
356
|
-
port:
|
471
|
+
host: config[:insecure_domain_name],
|
472
|
+
protocol: config[:insecure_domain_protocol],
|
473
|
+
port: config[:insecure_domain_port],
|
357
474
|
).and_return(rewritten_redirect_url)
|
358
475
|
|
359
476
|
expected_stored_value = {
|
@@ -362,8 +479,8 @@ describe 'SecureEscrow::Middleware' do
|
|
362
479
|
}.to_json
|
363
480
|
|
364
481
|
key = presenter.escrow_key 'id'
|
365
|
-
store.should_receive(:
|
366
|
-
once.with(key, expected_stored_value)
|
482
|
+
store.should_receive(:setex).
|
483
|
+
once.with(key, TTL, expected_stored_value)
|
367
484
|
|
368
485
|
presenter.store_in_escrow(
|
369
486
|
303,
|
@@ -376,7 +493,7 @@ describe 'SecureEscrow::Middleware' do
|
|
376
493
|
|
377
494
|
describe 'escrow_id and escrow_nonce' do
|
378
495
|
context 'when insecure_domain_name is different from secure_domain_name' do
|
379
|
-
let(:
|
496
|
+
let(:rails_app) {
|
380
497
|
MockEngine.new(
|
381
498
|
secure_domain_name: 'www.ssl-example.com',
|
382
499
|
insecure_domain_name: 'www.example.com'
|
@@ -405,6 +522,13 @@ describe 'SecureEscrow::Middleware' do
|
|
405
522
|
presenter.escrow_id.should eq 'id'
|
406
523
|
presenter.escrow_nonce.should eq 'nonce'
|
407
524
|
end
|
525
|
+
|
526
|
+
it 'should select first suitable escrow key from cookie' do
|
527
|
+
set_multi_escrow_cookie presenter, "A", "B", "C", "D"
|
528
|
+
|
529
|
+
presenter.escrow_id.should eq "A"
|
530
|
+
presenter.escrow_nonce.should eq "B"
|
531
|
+
end
|
408
532
|
end
|
409
533
|
|
410
534
|
end
|
@@ -420,6 +544,13 @@ def set_escrow_cookie presenter, id = 'id', nonce = 'nonce'
|
|
420
544
|
set_escrow_env HTTP_COOKIE, presenter, id, nonce
|
421
545
|
end
|
422
546
|
|
547
|
+
def set_multi_escrow_cookie presenter, id1 = 'id1', nonce1 = 'nonce1', id2 = 'id2', nonce2 = 'nonce2'
|
548
|
+
presenter.env[HTTP_COOKIE] = "%s=%s.%s; %s=%s.%s" % [
|
549
|
+
SecureEscrow::MiddlewareConstants::DATA_KEY, id1, nonce1,
|
550
|
+
SecureEscrow::MiddlewareConstants::DATA_KEY, id2, nonce2
|
551
|
+
]
|
552
|
+
end
|
553
|
+
|
423
554
|
def set_escrow_env key, presenter, id, nonce
|
424
555
|
presenter.env[key] = "%s=%s.%s" % [
|
425
556
|
SecureEscrow::MiddlewareConstants::DATA_KEY,
|
@@ -427,7 +558,7 @@ def set_escrow_env key, presenter, id, nonce
|
|
427
558
|
]
|
428
559
|
end
|
429
560
|
|
430
|
-
def store_in_escrow store, id = 'id', nonce = 'nonce', response = []
|
561
|
+
def store_in_escrow store, id = 'id', nonce = 'nonce', response = [ 200, {}, [ "" ] ]
|
431
562
|
store.set(
|
432
563
|
presenter.escrow_key(id),
|
433
564
|
ActiveSupport::JSON.encode(
|
data/spec/mock_engine.rb
CHANGED
@@ -12,16 +12,16 @@ class MockEngine
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def config extra_config = {}
|
15
|
-
@config ||= Config.new
|
16
|
-
{
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
15
|
+
@config ||= Config.new.tap do |config|
|
16
|
+
config.secure_escrow = {
|
17
|
+
secure_domain_name: 'www.example.com',
|
18
|
+
secure_domain_protocol: 'https',
|
19
|
+
secure_domain_port: 443,
|
20
|
+
insecure_domain_name: 'www.example.com',
|
21
|
+
insecure_domain_protocol: 'http',
|
22
|
+
insecure_domain_port: 80
|
23
|
+
}.merge extra_config
|
24
|
+
end
|
25
25
|
end
|
26
26
|
|
27
27
|
def routes
|
@@ -32,6 +32,9 @@ class MockEngine
|
|
32
32
|
end
|
33
33
|
|
34
34
|
class Routes
|
35
|
+
def recognize_path path, options = {}
|
36
|
+
{}
|
37
|
+
end
|
35
38
|
end
|
36
39
|
end
|
37
40
|
|
data/spec/mock_redis.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: secure_escrow
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2012-02-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
16
|
-
requirement: &
|
16
|
+
requirement: &70114553359420 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,7 +21,7 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70114553359420
|
25
25
|
description: SecureEscrow provides a content proxy for Rails applications allowing
|
26
26
|
POSTing to secure actions from insecure domains without full-page refreshes
|
27
27
|
email:
|
@@ -47,9 +47,10 @@ files:
|
|
47
47
|
- secure_escrow.gemspec
|
48
48
|
- spec/middleware_spec.rb
|
49
49
|
- spec/mock_engine.rb
|
50
|
+
- spec/mock_rack_app.rb
|
50
51
|
- spec/mock_redis.rb
|
51
52
|
- spec/spec_helper.rb
|
52
|
-
homepage:
|
53
|
+
homepage: https://github.com/duncanbeevers/SecureEscrow
|
53
54
|
licenses: []
|
54
55
|
post_install_message:
|
55
56
|
rdoc_options: []
|
@@ -76,5 +77,6 @@ summary: Secure AJAX-style actions for Rails applications
|
|
76
77
|
test_files:
|
77
78
|
- spec/middleware_spec.rb
|
78
79
|
- spec/mock_engine.rb
|
80
|
+
- spec/mock_rack_app.rb
|
79
81
|
- spec/mock_redis.rb
|
80
82
|
- spec/spec_helper.rb
|