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.
Files changed (3) hide show
  1. data/lib/rack/wwwhisper.rb +49 -68
  2. data/test/test_wwwhisper.rb +61 -26
  3. metadata +9 -6
@@ -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
- #@@AUTH_COOKIES_PREFIX = 'wwwhisper'
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
- wwwhisper_http = http_init('wwwhisper')
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{^#{@@WWWHISPER_PREFIX}auth}
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 host
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'] || default_port(scheme)
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(config, rack_req, method, path)
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(config[:forwarded_headers], rack_req.env, sub_req)
189
- sub_req['Site-Url'] = rack_req.site_url if config[:send_site_url]
190
- uri = config[:uri]
191
- sub_req.basic_auth(uri.user, uri.password) if uri.user and uri.password
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
- sub_req[header] = env[key] if has_value(env, key)
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
- config = @request_config[:auth]
244
- auth_req = sub_request_init(config, req, 'Get', auth_query(req.path))
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(config, orig_req, method, orig_req.fullpath)
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 = config[:http].request(config[:uri], sub_req)
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'
@@ -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 => {'Cookie' => /wwwhisper_auth.+wwwhisper_csrf.+/}).
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' => 'wwwhisper_auth=xyz; wwwhisper_csrf_token=abc'})
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 but not to
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, full_assets_url(path)).
255
- with { |request| request.headers['Site-Url'] == nil}.
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, full_assets_url(path)
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, full_assets_url(path)).
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, full_assets_url(path)
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.3.pre
5
- prerelease: 6
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-11 00:00:00.000000000 Z
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: 1268452761752086879
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: 1.3.1
140
+ version: '0'
141
+ segments:
142
+ - 0
143
+ hash: -4405127786806447004
141
144
  requirements: []
142
145
  rubyforge_project:
143
146
  rubygems_version: 1.8.24