rack-wwwhisper 1.0.3.pre → 1.0.4
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/rack/wwwhisper.rb +49 -68
- data/test/test_wwwhisper.rb +61 -26
- metadata +9 -6
data/lib/rack/wwwhisper.rb
CHANGED
@@ -10,72 +10,55 @@ require 'rack/utils'
|
|
10
10
|
|
11
11
|
module Rack
|
12
12
|
|
13
|
+
class NoPublicCache
|
14
|
+
def initialize(app)
|
15
|
+
@app = app
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(env)
|
19
|
+
status, headers, body = @app.call(env)
|
20
|
+
if cache_control = headers['Cache-Control']
|
21
|
+
# If caching is enabled, make sure it is private.
|
22
|
+
if (not cache_control.gsub!(/public/, 'private') and
|
23
|
+
cache_control.index(/max-age\s*=\s*0*[1-9]/))
|
24
|
+
cache_control.insert(0, 'private, ')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
[status, headers, body]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
13
31
|
class WWWhisper
|
14
|
-
@@DEFAULT_ASSETS_URL = 'https://c693db817dca7e162673-39ba3573e09a1fa9bea151a745461b70.ssl.cf1.rackcdn.com'
|
15
32
|
@@WWWHISPER_PREFIX = '/wwwhisper/'
|
16
|
-
|
17
|
-
|
33
|
+
@@AUTH_COOKIES_PREFIX = 'wwwhisper'
|
34
|
+
@@FORWARDED_HEADERS = ['Accept', 'Accept-Language', 'Cookie', 'X-CSRFToken',
|
35
|
+
'X-Requested-With']
|
18
36
|
@@DEFAULT_IFRAME = \
|
19
|
-
%Q[<iframe id="wwwhisper-iframe" src="%s"
|
20
|
-
width="340" height="29" frameborder="0" scrolling="no"
|
37
|
+
%Q[<iframe id="wwwhisper-iframe" src="%s" width="340" height="29"
|
21
38
|
style="position:fixed; overflow:hidden; border:0px; bottom:0px;
|
22
39
|
right:0px; z-index:11235; background-color:transparent;"> </iframe>
|
23
40
|
]
|
24
41
|
|
25
42
|
def initialize(app)
|
26
43
|
@app = app
|
27
|
-
|
28
44
|
if ENV['WWWHISPER_DISABLE'] == "1"
|
29
45
|
def self.call(env)
|
30
46
|
@app.call(env)
|
31
47
|
end
|
32
48
|
return
|
33
49
|
end
|
50
|
+
@app = NoPublicCache.new(app)
|
34
51
|
|
35
52
|
if not ENV['WWWHISPER_URL']
|
36
53
|
raise StandardError, 'WWWHISPER_URL environment variable not set'
|
37
54
|
end
|
38
55
|
|
39
|
-
|
40
|
-
wwwhisper_uri = parse_uri(ENV['WWWHISPER_URL'])
|
56
|
+
@http = http_init('wwwhisper')
|
57
|
+
@wwwhisper_uri = parse_uri(ENV['WWWHISPER_URL'])
|
41
58
|
|
42
59
|
@wwwhisper_iframe = ENV['WWWHISPER_IFRAME'] ||
|
43
60
|
sprintf(@@DEFAULT_IFRAME, wwwhisper_path('auth/overlay.html'))
|
44
61
|
@wwwhisper_iframe_bytesize = Rack::Utils::bytesize(@wwwhisper_iframe)
|
45
|
-
|
46
|
-
@request_config = {
|
47
|
-
# TODO: probably now auth can be removed.
|
48
|
-
:auth => {
|
49
|
-
:forwarded_headers => ['Accept', 'Accept-Language', 'Cookie'],
|
50
|
-
:http => wwwhisper_http,
|
51
|
-
:uri => wwwhisper_uri,
|
52
|
-
:send_site_url => true,
|
53
|
-
},
|
54
|
-
:api => {
|
55
|
-
:forwarded_headers => ['Accept', 'Accept-Language', 'Cookie',
|
56
|
-
'X-CSRFToken', 'X-Requested-With'],
|
57
|
-
:http => wwwhisper_http,
|
58
|
-
:uri => wwwhisper_uri,
|
59
|
-
:send_site_url => true,
|
60
|
-
},
|
61
|
-
:assets => {
|
62
|
-
# Don't pass Accept-Encoding to get uncompressed response (so
|
63
|
-
# iframe can be inserted to it).
|
64
|
-
:forwarded_headers => ['Accept', 'Accept-Language'],
|
65
|
-
:http => http_init('wwwhisper-assets'),
|
66
|
-
:uri => parse_uri(ENV['WWWHISPER_ASSETS_URL'] || @@DEFAULT_ASSETS_URL),
|
67
|
-
:send_site_url => false,
|
68
|
-
},
|
69
|
-
}
|
70
|
-
|
71
|
-
@aliases = {}
|
72
|
-
{
|
73
|
-
'auth/login' => 'auth/login.html',
|
74
|
-
'auth/logout' => 'auth/logout.html',
|
75
|
-
'admin/' => 'admin/index.html',
|
76
|
-
}.each do |k, v|
|
77
|
-
@aliases[wwwhisper_path(k)] = wwwhisper_path(v)
|
78
|
-
end
|
79
62
|
end
|
80
63
|
|
81
64
|
def wwwhisper_path(suffix)
|
@@ -89,7 +72,7 @@ class WWWhisper
|
|
89
72
|
def call(env)
|
90
73
|
req = Request.new(env)
|
91
74
|
|
92
|
-
if req.path =~ %r{^#{
|
75
|
+
if req.path =~ %r{^#{wwwhisper_path('auth')}}
|
93
76
|
# Requests to /@@WWWHISPER_PREFIX/auth/ should not be authorized,
|
94
77
|
# every visitor can access login pages.
|
95
78
|
return dispatch(req)
|
@@ -128,12 +111,17 @@ class WWWhisper
|
|
128
111
|
env['HTTP_X_FORWARDED_PROTO'] || 'http'
|
129
112
|
end
|
130
113
|
|
131
|
-
def
|
114
|
+
def host_with_port
|
132
115
|
env['HTTP_HOST']
|
133
116
|
end
|
134
117
|
|
118
|
+
def host
|
119
|
+
host_with_port.to_s.gsub(/:\d+\z/, '')
|
120
|
+
end
|
121
|
+
|
135
122
|
def port
|
136
|
-
env['HTTP_X_FORWARDED_PORT'] ||
|
123
|
+
env['HTTP_X_FORWARDED_PORT'] || host_with_port.split(/:/)[1] ||
|
124
|
+
default_port(scheme)
|
137
125
|
end
|
138
126
|
|
139
127
|
def site_url
|
@@ -182,24 +170,25 @@ class WWWhisper
|
|
182
170
|
return http
|
183
171
|
end
|
184
172
|
|
185
|
-
def sub_request_init(
|
186
|
-
path = @aliases[path] || path
|
173
|
+
def sub_request_init(rack_req, method, path)
|
187
174
|
sub_req = Net::HTTP.const_get(method).new(path)
|
188
|
-
copy_headers(
|
189
|
-
sub_req['Site-Url'] = rack_req.site_url
|
190
|
-
|
191
|
-
|
175
|
+
copy_headers(@@FORWARDED_HEADERS, rack_req.env, sub_req)
|
176
|
+
sub_req['Site-Url'] = rack_req.site_url
|
177
|
+
if @wwwhisper_uri.user and @wwwhisper_uri.password
|
178
|
+
sub_req.basic_auth(@wwwhisper_uri.user, @wwwhisper_uri.password)
|
179
|
+
end
|
192
180
|
sub_req
|
193
181
|
end
|
194
182
|
|
195
|
-
def has_value(dict, key)
|
196
|
-
dict[key] != nil and !dict[key].empty?
|
197
|
-
end
|
198
|
-
|
199
183
|
def copy_headers(headers_names, env, sub_req)
|
200
184
|
headers_names.each do |header|
|
201
185
|
key = "HTTP_#{header.upcase}".gsub(/-/, '_')
|
202
|
-
|
186
|
+
value = env[key]
|
187
|
+
if value and key == 'HTTP_COOKIE'
|
188
|
+
# Pass only wwwhisper's cookies to the wwwhisper service.
|
189
|
+
value = value.scan(/#{@@AUTH_COOKIES_PREFIX}-[^;]*(?:;|$)/).join(' ')
|
190
|
+
end
|
191
|
+
sub_req[header] = value if value and not value.empty?
|
203
192
|
end
|
204
193
|
end
|
205
194
|
|
@@ -240,9 +229,8 @@ class WWWhisper
|
|
240
229
|
end
|
241
230
|
|
242
231
|
def wwwhisper_auth_request(req)
|
243
|
-
|
244
|
-
|
245
|
-
config[:http].request(config[:uri], auth_req)
|
232
|
+
auth_req = sub_request_init(req, 'Get', auth_query(req.path))
|
233
|
+
@http.request(@wwwhisper_uri, auth_req)
|
246
234
|
end
|
247
235
|
|
248
236
|
def should_inject_iframe(status, headers)
|
@@ -277,18 +265,11 @@ class WWWhisper
|
|
277
265
|
if orig_req.path =~ %r{^#{@@WWWHISPER_PREFIX}}
|
278
266
|
debug orig_req, "passing request to wwwhisper service #{orig_req.path}"
|
279
267
|
|
280
|
-
config =
|
281
|
-
if orig_req.path =~ %r{^#{@@WWWHISPER_PREFIX}(auth|admin)/api/}
|
282
|
-
@request_config[:api]
|
283
|
-
else
|
284
|
-
@request_config[:assets]
|
285
|
-
end
|
286
|
-
|
287
268
|
method = orig_req.request_method.capitalize
|
288
|
-
sub_req = sub_request_init(
|
269
|
+
sub_req = sub_request_init(orig_req, method, orig_req.fullpath)
|
289
270
|
copy_body(orig_req, sub_req)
|
290
271
|
|
291
|
-
sub_resp =
|
272
|
+
sub_resp = @http.request(@wwwhisper_uri, sub_req)
|
292
273
|
sub_response_to_rack(orig_req, sub_resp)
|
293
274
|
else
|
294
275
|
debug orig_req, 'passing request to Rack stack'
|
data/test/test_wwwhisper.rb
CHANGED
@@ -27,12 +27,16 @@ class MockBackend
|
|
27
27
|
assert_equal @expected_email, env['REMOTE_USER']
|
28
28
|
@response
|
29
29
|
end
|
30
|
+
|
31
|
+
def add_response_header(key, value)
|
32
|
+
@response[1][key] = value
|
33
|
+
end
|
34
|
+
|
30
35
|
end
|
31
36
|
|
32
37
|
class TestWWWhisper < Test::Unit::TestCase
|
33
38
|
include Rack::Test::Methods
|
34
39
|
WWWHISPER_URL = 'https://example.com'
|
35
|
-
WWWHISPER_ASSETS_URL = 'https://assets.example.com'
|
36
40
|
SITE_PROTO = 'https'
|
37
41
|
SITE_HOST = 'bar.io'
|
38
42
|
SITE_PORT = 443
|
@@ -41,7 +45,6 @@ class TestWWWhisper < Test::Unit::TestCase
|
|
41
45
|
@backend = MockBackend.new(TEST_USER)
|
42
46
|
ENV.delete('WWWHISPER_DISABLE')
|
43
47
|
ENV['WWWHISPER_URL'] = WWWHISPER_URL
|
44
|
-
ENV['WWWHISPER_ASSETS_URL'] = WWWHISPER_ASSETS_URL
|
45
48
|
@wwwhisper = Rack::WWWhisper.new(@backend)
|
46
49
|
end
|
47
50
|
|
@@ -53,10 +56,6 @@ class TestWWWhisper < Test::Unit::TestCase
|
|
53
56
|
"#{WWWHISPER_URL}#{path}"
|
54
57
|
end
|
55
58
|
|
56
|
-
def full_assets_url(path)
|
57
|
-
"#{WWWHISPER_ASSETS_URL}#{path}"
|
58
|
-
end
|
59
|
-
|
60
59
|
def test_wwwhisper_url_required
|
61
60
|
ENV.delete('WWWHISPER_URL')
|
62
61
|
assert_raise(StandardError) {
|
@@ -169,11 +168,29 @@ class TestWWWhisper < Test::Unit::TestCase
|
|
169
168
|
def test_auth_cookies_passed_to_wwwhisper()
|
170
169
|
path = '/foo/bar'
|
171
170
|
stub_request(:get, full_url(@wwwhisper.auth_query(path))).
|
172
|
-
with(:headers => {
|
171
|
+
with(:headers => {
|
172
|
+
'Cookie' => /wwwhisper-auth=xyz; wwwhisper-csrftoken=abc/
|
173
|
+
}).
|
173
174
|
to_return(granted())
|
174
175
|
|
175
176
|
get(path, {},
|
176
|
-
{'HTTP_COOKIE' => '
|
177
|
+
{'HTTP_COOKIE' => 'wwwhisper-auth=xyz; wwwhisper-csrftoken=abc'})
|
178
|
+
assert last_response.ok?
|
179
|
+
assert_equal 'Hello World', last_response.body
|
180
|
+
assert_requested :get, full_url(@wwwhisper.auth_query(path))
|
181
|
+
end
|
182
|
+
|
183
|
+
def test_non_wwwhisper_cookies_not_passed_to_wwwhisper()
|
184
|
+
path = '/foo/bar'
|
185
|
+
stub_request(:get, full_url(@wwwhisper.auth_query(path))).
|
186
|
+
with(:headers => {
|
187
|
+
'Cookie' => /wwwhisper-auth=xyz; wwwhisper-csrftoken=abc/
|
188
|
+
}).
|
189
|
+
to_return(granted())
|
190
|
+
|
191
|
+
get(path, {},
|
192
|
+
{'HTTP_COOKIE' => 'session=123; wwwhisper-auth=xyz;' +
|
193
|
+
'settings=foobar; wwwhisper-csrftoken=abc'})
|
177
194
|
assert last_response.ok?
|
178
195
|
assert_equal 'Hello World', last_response.body
|
179
196
|
assert_requested :get, full_url(@wwwhisper.auth_query(path))
|
@@ -246,13 +263,12 @@ class TestWWWhisper < Test::Unit::TestCase
|
|
246
263
|
def test_site_url
|
247
264
|
path = '/wwwhisper/admin/index.html'
|
248
265
|
|
249
|
-
# Site-Url header should be sent to wwwhisper backend
|
250
|
-
# assets server.
|
266
|
+
# Site-Url header should be sent to wwwhisper backend.
|
251
267
|
stub_request(:get, full_url(@wwwhisper.auth_query(path))).
|
252
268
|
with(:headers => {'Site-Url' => "#{SITE_PROTO}://#{SITE_HOST}"}).
|
253
269
|
to_return(granted())
|
254
|
-
stub_request(:get,
|
255
|
-
with {
|
270
|
+
stub_request(:get, full_url(path)).
|
271
|
+
with(:headers => {'Site-Url' => "#{SITE_PROTO}://#{SITE_HOST}"}).
|
256
272
|
to_return(:status => 200, :body => 'Admin page', :headers => {})
|
257
273
|
|
258
274
|
get path
|
@@ -260,7 +276,21 @@ class TestWWWhisper < Test::Unit::TestCase
|
|
260
276
|
assert_equal 200, last_response.status
|
261
277
|
assert_equal 'Admin page', last_response.body
|
262
278
|
assert_requested :get, full_url(@wwwhisper.auth_query(path))
|
263
|
-
assert_requested :get,
|
279
|
+
assert_requested :get, full_url(path)
|
280
|
+
end
|
281
|
+
|
282
|
+
def test_host_with_port
|
283
|
+
path = '/foo'
|
284
|
+
host = 'localhost:5000'
|
285
|
+
stub_request(:get, full_url(@wwwhisper.auth_query(path))).
|
286
|
+
# Test makes sure that port is not repeated in Site-Url.
|
287
|
+
with(:headers => {'Site-Url' => "#{SITE_PROTO}://#{host}"}).
|
288
|
+
to_return(:status => 401, :body => 'Login required', :headers => {})
|
289
|
+
|
290
|
+
# TODO: start here
|
291
|
+
get(path, {}, {'HTTP_HOST' => host, 'HTTP_X_FORWARDED_PORT' => '5000'})
|
292
|
+
assert_equal 401, last_response.status
|
293
|
+
assert_requested :get, full_url(@wwwhisper.auth_query(path))
|
264
294
|
end
|
265
295
|
|
266
296
|
def test_site_url_with_non_default_port
|
@@ -278,22 +308,11 @@ class TestWWWhisper < Test::Unit::TestCase
|
|
278
308
|
assert_requested :get, full_url(@wwwhisper.auth_query(path))
|
279
309
|
end
|
280
310
|
|
281
|
-
def test_aliases
|
282
|
-
requested_path = '/wwwhisper/auth/login'
|
283
|
-
expected_path = requested_path + '.html'
|
284
|
-
stub_request(:get, full_assets_url(expected_path)).
|
285
|
-
to_return(:status => 200, :body => 'Login', :headers => {})
|
286
|
-
|
287
|
-
get requested_path
|
288
|
-
assert last_response.ok?
|
289
|
-
assert_equal 'Login', last_response.body
|
290
|
-
end
|
291
|
-
|
292
311
|
def test_redirects
|
293
312
|
path = '/wwwhisper/admin/index.html'
|
294
313
|
stub_request(:get, full_url(@wwwhisper.auth_query(path))).
|
295
314
|
to_return(granted())
|
296
|
-
stub_request(:get,
|
315
|
+
stub_request(:get, full_url(path)).
|
297
316
|
to_return(:status => 303, :body => 'Admin page moved',
|
298
317
|
:headers => {'Location' => 'https://new.location/foo/bar'})
|
299
318
|
|
@@ -304,7 +323,7 @@ class TestWWWhisper < Test::Unit::TestCase
|
|
304
323
|
assert_equal("#{SITE_PROTO}://#{SITE_HOST}:#{SITE_PORT}/foo/bar",
|
305
324
|
last_response['Location'])
|
306
325
|
assert_requested :get, full_url(@wwwhisper.auth_query(path))
|
307
|
-
assert_requested :get,
|
326
|
+
assert_requested :get, full_url(path)
|
308
327
|
end
|
309
328
|
|
310
329
|
def test_disable_wwwhisper
|
@@ -331,4 +350,20 @@ class TestWWWhisper < Test::Unit::TestCase
|
|
331
350
|
assert_not_nil last_response['Content-Length']
|
332
351
|
end
|
333
352
|
|
353
|
+
def test_public_caching_disabled()
|
354
|
+
path = '/foo/bar'
|
355
|
+
stub_request(:get, full_url(@wwwhisper.auth_query(path))).
|
356
|
+
to_return(granted())
|
357
|
+
@backend.add_response_header('Cache-Control', 'public, max-age=60')
|
358
|
+
|
359
|
+
get path
|
360
|
+
assert last_response.ok?
|
361
|
+
assert_equal 'private, max-age=60', last_response['Cache-Control']
|
362
|
+
|
363
|
+
@backend.add_response_header('Cache-Control', 'max-age=60')
|
364
|
+
get path
|
365
|
+
assert last_response.ok?
|
366
|
+
assert_equal 'private, max-age=60', last_response['Cache-Control']
|
367
|
+
end
|
368
|
+
|
334
369
|
end
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-wwwhisper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
5
|
-
prerelease:
|
4
|
+
version: 1.0.4
|
5
|
+
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Jan Wrobel
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-01-
|
12
|
+
date: 2013-01-23 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rack
|
@@ -131,13 +131,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
131
131
|
version: '0'
|
132
132
|
segments:
|
133
133
|
- 0
|
134
|
-
hash:
|
134
|
+
hash: -4405127786806447004
|
135
135
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
136
136
|
none: false
|
137
137
|
requirements:
|
138
|
-
- - ! '
|
138
|
+
- - ! '>='
|
139
139
|
- !ruby/object:Gem::Version
|
140
|
-
version:
|
140
|
+
version: '0'
|
141
|
+
segments:
|
142
|
+
- 0
|
143
|
+
hash: -4405127786806447004
|
141
144
|
requirements: []
|
142
145
|
rubyforge_project:
|
143
146
|
rubygems_version: 1.8.24
|