rack-wwwhisper 1.0.2.pre → 1.0.3.pre

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 +69 -42
  2. data/test/test_wwwhisper.rb +62 -20
  3. metadata +2 -2
@@ -24,6 +24,14 @@ class WWWhisper
24
24
 
25
25
  def initialize(app)
26
26
  @app = app
27
+
28
+ if ENV['WWWHISPER_DISABLE'] == "1"
29
+ def self.call(env)
30
+ @app.call(env)
31
+ end
32
+ return
33
+ end
34
+
27
35
  if not ENV['WWWHISPER_URL']
28
36
  raise StandardError, 'WWWHISPER_URL environment variable not set'
29
37
  end
@@ -79,9 +87,7 @@ class WWWhisper
79
87
  end
80
88
 
81
89
  def call(env)
82
- req = Rack::Request.new(env)
83
-
84
- normalize_path req
90
+ req = Request.new(env)
85
91
 
86
92
  if req.path =~ %r{^#{@@WWWHISPER_PREFIX}auth}
87
93
  # Requests to /@@WWWHISPER_PREFIX/auth/ should not be authorized,
@@ -94,10 +100,12 @@ class WWWhisper
94
100
 
95
101
  if auth_resp.code == '200'
96
102
  debug req, 'access granted'
103
+ env['REMOTE_USER'] = auth_resp['User']
97
104
  status, headers, body = dispatch(req)
98
105
  if should_inject_iframe(status, headers)
99
106
  body = inject_iframe(headers, body)
100
107
  end
108
+ headers['User'] = auth_resp['User']
101
109
  [status, headers, body]
102
110
  else
103
111
  debug req, {
@@ -110,13 +118,51 @@ class WWWhisper
110
118
 
111
119
  private
112
120
 
113
- def debug(req, message)
114
- req.logger.debug "wwwhisper #{message}" if req.logger
121
+ class Request < Rack::Request
122
+ def initialize(env)
123
+ super(env)
124
+ normalize_path
125
+ end
126
+
127
+ def scheme
128
+ env['HTTP_X_FORWARDED_PROTO'] || 'http'
129
+ end
130
+
131
+ def host
132
+ env['HTTP_HOST']
133
+ end
134
+
135
+ def port
136
+ env['HTTP_X_FORWARDED_PORT'] || default_port(scheme)
137
+ end
138
+
139
+ def site_url
140
+ port_str = port != default_port(scheme) ? ":#{port}" : ""
141
+ "#{scheme}://#{host}#{port_str}"
142
+ end
143
+
144
+ private
145
+ def normalize_path()
146
+ self.script_name =
147
+ Addressable::URI.normalize_path(script_name).squeeze('/')
148
+ self.path_info =
149
+ Addressable::URI.normalize_path(path_info).squeeze('/')
150
+ # Avoid /foo/ /bar being combined into /foo//bar
151
+ if self.path_info[0] == ?/
152
+ self.script_name.chomp!('/')
153
+ end
154
+ end
155
+
156
+ def default_port(proto)
157
+ {
158
+ 'http' => 80,
159
+ 'https' => 443,
160
+ }[proto]
161
+ end
115
162
  end
116
163
 
117
- def normalize_path(req)
118
- req.script_name = Addressable::URI.normalize_path(req.script_name)
119
- req.path_info = Addressable::URI.normalize_path(req.path_info)
164
+ def debug(req, message)
165
+ req.logger.debug "wwwhisper #{message}" if req.logger
120
166
  end
121
167
 
122
168
  def parse_uri(uri)
@@ -127,30 +173,6 @@ class WWWhisper
127
173
  parsed_uri
128
174
  end
129
175
 
130
- def default_port(proto)
131
- {
132
- 'http' => 80,
133
- 'https' => 443,
134
- }[proto]
135
- end
136
-
137
- def proto_host_port(env)
138
- proto = env['HTTP_X_FORWARDED_PROTO'] || 'http'
139
- return proto,
140
- env['HTTP_HOST'],
141
- env['HTTP_X_FORWARDED_PORT'] || default_port(proto)
142
- end
143
-
144
- def site_url(env)
145
- proto, host, port = proto_host_port(env)
146
- port_str = if port != default_port(proto)
147
- ":#{port}"
148
- else
149
- ''
150
- end
151
- "#{proto}://#{host}#{port_str}"
152
- end
153
-
154
176
  def http_init(connection_id)
155
177
  http = Net::HTTP::Persistent.new(connection_id)
156
178
  store = OpenSSL::X509::Store.new()
@@ -164,7 +186,7 @@ class WWWhisper
164
186
  path = @aliases[path] || path
165
187
  sub_req = Net::HTTP.const_get(method).new(path)
166
188
  copy_headers(config[:forwarded_headers], rack_req.env, sub_req)
167
- sub_req['Site-Url'] = site_url(rack_req.env) if config[:send_site_url]
189
+ sub_req['Site-Url'] = rack_req.site_url if config[:send_site_url]
168
190
  uri = config[:uri]
169
191
  sub_req.basic_auth(uri.user, uri.password) if uri.user and uri.password
170
192
  sub_req
@@ -198,20 +220,23 @@ class WWWhisper
198
220
  if header == 'Location'
199
221
  location = Addressable::URI.parse(value)
200
222
  location.scheme, location.host, location.port =
201
- proto_host_port(rack_req.env)
223
+ rack_req.scheme, rack_req.host, rack_req.port
202
224
  value = location.to_s
203
225
  end
204
- rack_headers[header] = value
226
+ # If sub request returned chunked response, remove the header
227
+ # (chunks will be combined and returned with 'Content-Length).
228
+ rack_headers[header] = value if header != 'Transfer-Encoding'
205
229
  end
206
230
  return rack_headers
207
231
  end
208
232
 
209
233
  def sub_response_to_rack(rack_req, sub_resp)
210
- [
211
- sub_resp.code.to_i,
212
- sub_response_headers_to_rack(rack_req, sub_resp),
213
- [(sub_resp.read_body() or '')]
214
- ]
234
+ headers = sub_response_headers_to_rack(rack_req, sub_resp)
235
+ body = sub_resp.read_body() || ''
236
+ if body.length and not headers['Content-Length']
237
+ headers['Content-Length'] = Rack::Utils::bytesize(body).to_s
238
+ end
239
+ [ sub_resp.code.to_i, headers, [body] ]
215
240
  end
216
241
 
217
242
  def wwwhisper_auth_request(req)
@@ -237,6 +262,8 @@ class WWWhisper
237
262
  body.each { |part|
238
263
  total << part
239
264
  }
265
+ body.close if body.respond_to?(:close)
266
+
240
267
  total = total.join()
241
268
  if idx = total.rindex('</body>')
242
269
  total.insert(idx, @wwwhisper_iframe)
@@ -269,6 +296,6 @@ class WWWhisper
269
296
  end
270
297
  end
271
298
 
272
- end
299
+ end # class WWWhisper
273
300
 
274
- end
301
+ end # module
@@ -11,14 +11,20 @@ require 'rack/wwwhisper'
11
11
 
12
12
  ENV['RACK_ENV'] = 'test'
13
13
 
14
+ TEST_USER = 'foo@example.com'
15
+
14
16
  class MockBackend
17
+ include Test::Unit::Assertions
15
18
  attr_accessor :response
16
19
 
17
- def initialize()
20
+ def initialize(email)
18
21
  @response = [200, {'Content-Type' => 'text/html'}, ['Hello World']]
22
+ @expected_email = email
19
23
  end
20
24
 
21
25
  def call(env)
26
+ # Make sure remote user is set by wwwhisper.
27
+ assert_equal @expected_email, env['REMOTE_USER']
22
28
  @response
23
29
  end
24
30
  end
@@ -32,7 +38,8 @@ class TestWWWhisper < Test::Unit::TestCase
32
38
  SITE_PORT = 443
33
39
 
34
40
  def setup()
35
- @backend = MockBackend.new()
41
+ @backend = MockBackend.new(TEST_USER)
42
+ ENV.delete('WWWHISPER_DISABLE')
36
43
  ENV['WWWHISPER_URL'] = WWWHISPER_URL
37
44
  ENV['WWWHISPER_ASSETS_URL'] = WWWHISPER_ASSETS_URL
38
45
  @wwwhisper = Rack::WWWhisper.new(@backend)
@@ -69,14 +76,19 @@ class TestWWWhisper < Test::Unit::TestCase
69
76
  @wwwhisper.auth_query('/foo/bar'))
70
77
  end
71
78
 
79
+ def granted()
80
+ {:status => 200, :body => '', :headers => {'User' => TEST_USER}}
81
+ end
82
+
72
83
  def test_request_allowed
73
84
  path = '/foo/bar'
74
85
  stub_request(:get, full_url(@wwwhisper.auth_query(path))).
75
- to_return(:status => 200, :body => '', :headers => {})
86
+ to_return(granted())
76
87
 
77
88
  get path
78
89
  assert last_response.ok?
79
90
  assert_equal 'Hello World', last_response.body
91
+ assert_equal TEST_USER, last_response['User']
80
92
  assert_requested :get, full_url(@wwwhisper.auth_query(path))
81
93
  end
82
94
 
@@ -107,7 +119,7 @@ class TestWWWhisper < Test::Unit::TestCase
107
119
  def test_iframe_injected_to_html_response
108
120
  path = '/foo/bar'
109
121
  stub_request(:get, full_url(@wwwhisper.auth_query(path))).
110
- to_return(:status => 200, :body => '', :headers => {})
122
+ to_return(granted())
111
123
  # wwwhisper iframe is injected only when response is a html document
112
124
  # with <body></body>
113
125
  body = '<html><body>Hello World</body></html>'
@@ -122,7 +134,7 @@ class TestWWWhisper < Test::Unit::TestCase
122
134
  def test_iframe_not_injected_to_non_html_response
123
135
  path = '/foo/bar'
124
136
  stub_request(:get, full_url(@wwwhisper.auth_query(path))).
125
- to_return(:status => 200, :body => '', :headers => {})
137
+ to_return(granted())
126
138
  body = '<html><body>Hello World</body></html>'
127
139
  @backend.response= [200, {'Content-Type' => 'text/plain'}, [body]]
128
140
 
@@ -132,6 +144,18 @@ class TestWWWhisper < Test::Unit::TestCase
132
144
  assert_requested :get, full_url(@wwwhisper.auth_query(path))
133
145
  end
134
146
 
147
+ def test_response_body_combined
148
+ path = '/foo/bar'
149
+ stub_request(:get, full_url(@wwwhisper.auth_query(path))).
150
+ to_return(granted())
151
+ @backend.response= [200, {'Content-Type' => 'text/html'},
152
+ ['abc', 'def', 'ghi']]
153
+ get path
154
+ assert last_response.ok?
155
+ assert_equal('abcdefghi', last_response.body)
156
+ assert_requested :get, full_url(@wwwhisper.auth_query(path))
157
+ end
158
+
135
159
  def test_auth_query_not_sent_for_login_request
136
160
  path = '/wwwhisper/auth/api/login'
137
161
  stub_request(:get, full_url(path)).
@@ -146,7 +170,7 @@ class TestWWWhisper < Test::Unit::TestCase
146
170
  path = '/foo/bar'
147
171
  stub_request(:get, full_url(@wwwhisper.auth_query(path))).
148
172
  with(:headers => {'Cookie' => /wwwhisper_auth.+wwwhisper_csrf.+/}).
149
- to_return(:status => 200, :body => '', :headers => {})
173
+ to_return(granted())
150
174
 
151
175
  get(path, {},
152
176
  {'HTTP_COOKIE' => 'wwwhisper_auth=xyz; wwwhisper_csrf_token=abc'})
@@ -157,7 +181,7 @@ class TestWWWhisper < Test::Unit::TestCase
157
181
 
158
182
  def assert_path_normalized(normalized, requested, script_name=nil)
159
183
  stub_request(:get, full_url(@wwwhisper.auth_query(normalized))).
160
- to_return(:status => 200, :body => '', :headers => {})
184
+ to_return(granted())
161
185
 
162
186
  env = script_name ? { 'SCRIPT_NAME' => script_name } : {}
163
187
  get(requested, {}, env)
@@ -179,12 +203,8 @@ class TestWWWhisper < Test::Unit::TestCase
179
203
  assert_path_normalized '/', '/./././'
180
204
  assert_path_normalized '/bar', '/foo/./bar/../../bar'
181
205
  assert_path_normalized '/foo/', '/foo/bar/..'
182
-
183
- # These two do not seem to be handled correctly and consistency,
184
- # but this is not a big issue, because wwwhisper rejects such
185
- # paths.
186
- assert_path_normalized '/foo//', '/foo//'
187
- assert_path_normalized '//', '/./././/'
206
+ assert_path_normalized '/foo/', '/foo/'
207
+ assert_path_normalized '/', '/./././/'
188
208
  end
189
209
 
190
210
  def test_path_normalization_with_script_name
@@ -194,16 +214,14 @@ class TestWWWhisper < Test::Unit::TestCase
194
214
  assert_path_normalized '/baz/bar/hello', '/bar/hello', '/foo/../baz'
195
215
 
196
216
  assert_path_normalized '/foo/baz/bar/hello', 'bar/hello', '/foo/./baz'
197
-
198
- # Not handled too well (see comment above).
199
- assert_path_normalized '/foo//', '/', '/foo/'
200
- assert_path_normalized '//bar/hello', '/bar/hello', '/foo/..'
217
+ assert_path_normalized '/foo/', '/', '/foo/'
218
+ assert_path_normalized '/bar/hello', '/bar/hello', '/foo/..'
201
219
  end
202
220
 
203
221
  def test_admin_request
204
222
  path = '/wwwhisper/admin/api/users/xyz'
205
223
  stub_request(:get, full_url(@wwwhisper.auth_query(path))).
206
- to_return(:status => 200, :body => '', :headers => {})
224
+ to_return(granted())
207
225
  stub_request(:delete, full_url(path)).
208
226
  # Test that a header with multiple '-' is correctly passed
209
227
  with(:headers => {'X-Requested-With' => 'XMLHttpRequest'}).
@@ -232,7 +250,7 @@ class TestWWWhisper < Test::Unit::TestCase
232
250
  # assets server.
233
251
  stub_request(:get, full_url(@wwwhisper.auth_query(path))).
234
252
  with(:headers => {'Site-Url' => "#{SITE_PROTO}://#{SITE_HOST}"}).
235
- to_return(:status => 200, :body => '', :headers => {})
253
+ to_return(granted())
236
254
  stub_request(:get, full_assets_url(path)).
237
255
  with { |request| request.headers['Site-Url'] == nil}.
238
256
  to_return(:status => 200, :body => 'Admin page', :headers => {})
@@ -274,7 +292,7 @@ class TestWWWhisper < Test::Unit::TestCase
274
292
  def test_redirects
275
293
  path = '/wwwhisper/admin/index.html'
276
294
  stub_request(:get, full_url(@wwwhisper.auth_query(path))).
277
- to_return(:status => 200, :body => '', :headers => {})
295
+ to_return(granted())
278
296
  stub_request(:get, full_assets_url(path)).
279
297
  to_return(:status => 303, :body => 'Admin page moved',
280
298
  :headers => {'Location' => 'https://new.location/foo/bar'})
@@ -289,4 +307,28 @@ class TestWWWhisper < Test::Unit::TestCase
289
307
  assert_requested :get, full_assets_url(path)
290
308
  end
291
309
 
310
+ def test_disable_wwwhisper
311
+ ENV.delete('WWWHISPER_URL')
312
+ ENV['WWWHISPER_DISABLE'] = "1"
313
+ # Configure MockBackend to make sure REMOTE_USER is not set.
314
+ @wwwhisper = Rack::WWWhisper.new(MockBackend.new(nil))
315
+
316
+ path = '/foo/bar'
317
+ get path
318
+ assert last_response.ok?
319
+ assert_equal 'Hello World', last_response.body
320
+ assert_nil last_response['User']
321
+ end
322
+
323
+ def test_chunked_encoding_from_wwwhisper_removed
324
+ path = '/foo/bar'
325
+ stub_request(:get, full_url(@wwwhisper.auth_query(path))).
326
+ to_return(:status => 401, :body => 'Login required',
327
+ :headers => {'Transfer-Encoding' => 'chunked'})
328
+ get path
329
+ assert_equal 401, last_response.status
330
+ assert_nil last_response['Transfer-Encoding']
331
+ assert_not_nil last_response['Content-Length']
332
+ end
333
+
292
334
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-wwwhisper
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2.pre
4
+ version: 1.0.3.pre
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -131,7 +131,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
131
131
  version: '0'
132
132
  segments:
133
133
  - 0
134
- hash: 3933889389209313993
134
+ hash: 1268452761752086879
135
135
  required_rubygems_version: !ruby/object:Gem::Requirement
136
136
  none: false
137
137
  requirements: