rack-wwwhisper 1.0.3.pre → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
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