rack-cors 1.0.2 → 2.0.0
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.
- checksums.yaml +5 -5
- data/.rubocop.yml +31 -0
- data/.travis.yml +10 -3
- data/{CHANGELOG → CHANGELOG.md} +39 -0
- data/Gemfile +3 -1
- data/README.md +67 -42
- data/Rakefile +5 -4
- data/lib/rack/cors/resource.rb +132 -0
- data/lib/rack/cors/resources/cors_misconfiguration_error.rb +14 -0
- data/lib/rack/cors/resources.rb +62 -0
- data/lib/rack/cors/result.rb +63 -0
- data/lib/rack/cors/version.rb +3 -1
- data/lib/rack/cors.rb +110 -345
- data/rack-cors.gemspec +20 -16
- data/test/.rubocop.yml +8 -0
- data/test/cors/test.cors.coffee +9 -2
- data/test/cors/test.cors.js +22 -10
- data/test/unit/cors_test.rb +209 -151
- data/test/unit/dsl_test.rb +30 -29
- data/test/unit/insecure.ru +2 -0
- data/test/unit/non_http.ru +2 -0
- data/test/unit/test.ru +25 -19
- metadata +81 -28
data/test/unit/cors_test.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'minitest/autorun'
|
2
4
|
require 'rack/test'
|
3
5
|
require 'mocha/setup'
|
@@ -7,7 +9,7 @@ require 'ostruct'
|
|
7
9
|
Rack::Test::Session.class_eval do
|
8
10
|
unless defined? :options
|
9
11
|
def options(uri, params = {}, env = {}, &block)
|
10
|
-
env = env_for(uri, env.merge(:
|
12
|
+
env = env_for(uri, env.merge(method: 'OPTIONS', params: params))
|
11
13
|
process_request(uri, env, &block)
|
12
14
|
end
|
13
15
|
end
|
@@ -19,16 +21,16 @@ end
|
|
19
21
|
|
20
22
|
module MiniTest::Assertions
|
21
23
|
def assert_cors_success(response)
|
22
|
-
|
24
|
+
assert !response.headers['Access-Control-Allow-Origin'].nil?, 'Expected a successful CORS response'
|
23
25
|
end
|
24
26
|
|
25
27
|
def assert_not_cors_success(response)
|
26
|
-
|
28
|
+
assert response.headers['Access-Control-Allow-Origin'].nil?, 'Expected a failed CORS response'
|
27
29
|
end
|
28
30
|
end
|
29
31
|
|
30
32
|
class CaptureResult
|
31
|
-
def initialize(app, options =
|
33
|
+
def initialize(app, options = {})
|
32
34
|
@app = app
|
33
35
|
@result_holder = options[:holder]
|
34
36
|
end
|
@@ -36,7 +38,19 @@ class CaptureResult
|
|
36
38
|
def call(env)
|
37
39
|
response = @app.call(env)
|
38
40
|
@result_holder.cors_result = env[Rack::Cors::RACK_CORS]
|
39
|
-
|
41
|
+
response
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class FakeProxy
|
46
|
+
def initialize(app, _options = {})
|
47
|
+
@app = app
|
48
|
+
end
|
49
|
+
|
50
|
+
def call(env)
|
51
|
+
status, headers, body = @app.call(env)
|
52
|
+
headers['vary'] = %w[Origin User-Agent]
|
53
|
+
[status, headers, body]
|
40
54
|
end
|
41
55
|
end
|
42
56
|
|
@@ -48,15 +62,16 @@ describe Rack::Cors do
|
|
48
62
|
|
49
63
|
attr_accessor :cors_result
|
50
64
|
|
51
|
-
def load_app(name)
|
65
|
+
def load_app(name, options = {})
|
52
66
|
test = self
|
53
67
|
Rack::Builder.new do
|
54
|
-
use CaptureResult, :
|
68
|
+
use CaptureResult, holder: test
|
55
69
|
eval File.read(File.dirname(__FILE__) + "/#{name}.ru")
|
70
|
+
use FakeProxy if options[:proxy]
|
56
71
|
map('/') do
|
57
|
-
run
|
58
|
-
[200, {'Content-Type' => 'text/html'}, ['success']]
|
59
|
-
|
72
|
+
run(lambda do |_env|
|
73
|
+
[200, { 'Content-Type' => 'text/html' }, ['success']]
|
74
|
+
end)
|
60
75
|
end
|
61
76
|
end
|
62
77
|
end
|
@@ -65,120 +80,135 @@ describe Rack::Cors do
|
|
65
80
|
|
66
81
|
it 'should support simple CORS request' do
|
67
82
|
successful_cors_request
|
68
|
-
cors_result.must_be :hit
|
83
|
+
_(cors_result).must_be :hit
|
69
84
|
end
|
70
85
|
|
71
86
|
it "should not return CORS headers if Origin header isn't present" do
|
72
87
|
get '/'
|
73
|
-
last_response.wont_render_cors_success
|
74
|
-
cors_result.wont_be :hit
|
88
|
+
_(last_response).wont_render_cors_success
|
89
|
+
_(cors_result).wont_be :hit
|
75
90
|
end
|
76
91
|
|
77
92
|
it 'should support OPTIONS CORS request' do
|
78
|
-
successful_cors_request '/options', :
|
93
|
+
successful_cors_request '/options', method: :options
|
79
94
|
end
|
80
95
|
|
81
96
|
it 'should support regex origins configuration' do
|
82
|
-
successful_cors_request :
|
97
|
+
successful_cors_request origin: 'http://192.168.0.1:1234'
|
83
98
|
end
|
84
99
|
|
85
100
|
it 'should support subdomain example' do
|
86
|
-
successful_cors_request :
|
101
|
+
successful_cors_request origin: 'http://subdomain.example.com'
|
87
102
|
end
|
88
103
|
|
89
104
|
it 'should support proc origins configuration' do
|
90
|
-
successful_cors_request '/proc-origin', :
|
105
|
+
successful_cors_request '/proc-origin', origin: 'http://10.10.10.10:3000'
|
91
106
|
end
|
92
107
|
|
93
108
|
it 'should support lambda origin configuration' do
|
94
|
-
successful_cors_request '/lambda-origin', :
|
109
|
+
successful_cors_request '/lambda-origin', origin: 'http://10.10.10.10:3000'
|
95
110
|
end
|
96
111
|
|
97
112
|
it 'should support proc origins configuration (inverse)' do
|
98
|
-
cors_request '/proc-origin', :
|
99
|
-
last_response.wont_render_cors_success
|
113
|
+
cors_request '/proc-origin', origin: 'http://bad.guy'
|
114
|
+
_(last_response).wont_render_cors_success
|
100
115
|
end
|
101
116
|
|
102
117
|
it 'should not mix up path rules across origins' do
|
103
118
|
header 'Origin', 'http://10.10.10.10:3000'
|
104
119
|
get '/' # / is configured in a separate rule block
|
105
|
-
last_response.wont_render_cors_success
|
120
|
+
_(last_response).wont_render_cors_success
|
106
121
|
end
|
107
122
|
|
108
123
|
it 'should support alternative X-Origin header' do
|
109
124
|
header 'X-Origin', 'http://localhost:3000'
|
110
125
|
get '/'
|
111
|
-
last_response.must_render_cors_success
|
126
|
+
_(last_response).must_render_cors_success
|
112
127
|
end
|
113
128
|
|
114
129
|
it 'should support expose header configuration' do
|
115
130
|
successful_cors_request '/expose_single_header'
|
116
|
-
last_response.headers['Access-Control-Expose-Headers'].must_equal 'expose-test'
|
131
|
+
_(last_response.headers['Access-Control-Expose-Headers']).must_equal 'expose-test'
|
117
132
|
end
|
118
133
|
|
119
134
|
it 'should support expose multiple header configuration' do
|
120
135
|
successful_cors_request '/expose_multiple_headers'
|
121
|
-
last_response.headers['Access-Control-Expose-Headers'].must_equal 'expose-test-1, expose-test-2'
|
136
|
+
_(last_response.headers['Access-Control-Expose-Headers']).must_equal 'expose-test-1, expose-test-2'
|
122
137
|
end
|
123
138
|
|
124
139
|
# Explanation: http://www.fastly.com/blog/best-practices-for-using-the-vary-header/
|
125
140
|
it "should add Vary header if resource matches even if Origin header isn't present" do
|
126
141
|
get '/'
|
127
|
-
last_response.wont_render_cors_success
|
128
|
-
last_response.headers['Vary'].must_equal 'Origin'
|
142
|
+
_(last_response).wont_render_cors_success
|
143
|
+
_(last_response.headers['Vary']).must_equal 'Origin'
|
129
144
|
end
|
130
145
|
|
131
|
-
it
|
146
|
+
it 'should add Vary header based on :vary option' do
|
132
147
|
successful_cors_request '/vary_test'
|
133
|
-
last_response.headers['Vary'].must_equal 'Origin, Host'
|
148
|
+
_(last_response.headers['Vary']).must_equal 'Origin, Host'
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'decode URL and resolve paths before resource matching' do
|
152
|
+
header 'Origin', 'http://localhost:3000'
|
153
|
+
get '/public/a/..%2F..%2Fprivate/stuff'
|
154
|
+
_(last_response).wont_render_cors_success
|
155
|
+
end
|
156
|
+
|
157
|
+
describe 'with upstream Vary headers' do
|
158
|
+
let(:app) { load_app('test', { proxy: true }) }
|
159
|
+
|
160
|
+
it 'should add to them' do
|
161
|
+
successful_cors_request '/vary_test'
|
162
|
+
_(last_response.headers['Vary']).must_equal 'Origin, User-Agent, Host'
|
163
|
+
end
|
134
164
|
end
|
135
165
|
|
136
166
|
it 'should add Vary header if Access-Control-Allow-Origin header was added and if it is specific' do
|
137
|
-
successful_cors_request '/', :
|
138
|
-
last_response.headers['Access-Control-Allow-Origin'].must_equal 'http://192.168.0.3:8080'
|
139
|
-
last_response.headers['Vary'].wont_be_nil
|
167
|
+
successful_cors_request '/', origin: 'http://192.168.0.3:8080'
|
168
|
+
_(last_response.headers['Access-Control-Allow-Origin']).must_equal 'http://192.168.0.3:8080'
|
169
|
+
_(last_response.headers['Vary']).wont_be_nil
|
140
170
|
end
|
141
171
|
|
142
172
|
it 'should add Vary header even if Access-Control-Allow-Origin header was added and it is generic (*)' do
|
143
|
-
successful_cors_request '/public_without_credentials', :
|
144
|
-
last_response.headers['Access-Control-Allow-Origin'].must_equal '*'
|
145
|
-
last_response.headers['Vary'].must_equal 'Origin'
|
173
|
+
successful_cors_request '/public_without_credentials', origin: 'http://192.168.1.3:8080'
|
174
|
+
_(last_response.headers['Access-Control-Allow-Origin']).must_equal '*'
|
175
|
+
_(last_response.headers['Vary']).must_equal 'Origin'
|
146
176
|
end
|
147
177
|
|
148
178
|
it 'should support multi allow configurations for the same resource' do
|
149
|
-
successful_cors_request '/multi-allow-config', :
|
150
|
-
last_response.headers['Access-Control-Allow-Origin'].must_equal 'http://mucho-grande.com'
|
151
|
-
last_response.headers['Vary'].must_equal 'Origin'
|
179
|
+
successful_cors_request '/multi-allow-config', origin: 'http://mucho-grande.com'
|
180
|
+
_(last_response.headers['Access-Control-Allow-Origin']).must_equal 'http://mucho-grande.com'
|
181
|
+
_(last_response.headers['Vary']).must_equal 'Origin'
|
152
182
|
|
153
|
-
successful_cors_request '/multi-allow-config', :
|
154
|
-
last_response.headers['Access-Control-Allow-Origin'].must_equal '*'
|
155
|
-
last_response.headers['Vary'].must_equal 'Origin'
|
183
|
+
successful_cors_request '/multi-allow-config', origin: 'http://192.168.1.3:8080'
|
184
|
+
_(last_response.headers['Access-Control-Allow-Origin']).must_equal '*'
|
185
|
+
_(last_response.headers['Vary']).must_equal 'Origin'
|
156
186
|
end
|
157
187
|
|
158
|
-
it
|
188
|
+
it 'should not return CORS headers on OPTIONS request if Access-Control-Allow-Origin is not present' do
|
159
189
|
options '/get-only'
|
160
|
-
last_response.headers['Access-Control-Allow-Origin'].must_be_nil
|
190
|
+
_(last_response.headers['Access-Control-Allow-Origin']).must_be_nil
|
161
191
|
end
|
162
192
|
|
163
|
-
it
|
193
|
+
it 'should not apply CORS headers if it does not match conditional on resource' do
|
164
194
|
header 'Origin', 'http://192.168.0.1:1234'
|
165
195
|
get '/conditional'
|
166
|
-
last_response.wont_render_cors_success
|
196
|
+
_(last_response).wont_render_cors_success
|
167
197
|
end
|
168
198
|
|
169
|
-
it
|
199
|
+
it 'should apply CORS headers if it does match conditional on resource' do
|
170
200
|
header 'X-OK', '1'
|
171
|
-
successful_cors_request '/conditional', :
|
201
|
+
successful_cors_request '/conditional', origin: 'http://192.168.0.1:1234'
|
172
202
|
end
|
173
203
|
|
174
|
-
it
|
175
|
-
cors_request '/blank-origin', origin:
|
176
|
-
last_response.wont_render_cors_success
|
204
|
+
it 'should not allow everything if Origin is configured as blank string' do
|
205
|
+
cors_request '/blank-origin', origin: 'http://example.net'
|
206
|
+
_(last_response).wont_render_cors_success
|
177
207
|
end
|
178
208
|
|
179
|
-
it
|
209
|
+
it 'should not allow credentials for public resources' do
|
180
210
|
successful_cors_request '/public'
|
181
|
-
last_response.headers['Access-Control-Allow-Credentials'].must_be_nil
|
211
|
+
_(last_response.headers['Access-Control-Allow-Credentials']).must_be_nil
|
182
212
|
end
|
183
213
|
|
184
214
|
describe 'logging' do
|
@@ -189,7 +219,7 @@ describe Rack::Cors do
|
|
189
219
|
logger = mock
|
190
220
|
logger.expects(:debug).never
|
191
221
|
|
192
|
-
cors = Rack::Cors.new(app, :
|
222
|
+
cors = Rack::Cors.new(app, debug: false, logger: logger) {}
|
193
223
|
cors.send(:debug, {}, 'testing')
|
194
224
|
end
|
195
225
|
|
@@ -200,7 +230,7 @@ describe Rack::Cors do
|
|
200
230
|
logger = mock
|
201
231
|
logger.expects(:debug)
|
202
232
|
|
203
|
-
cors = Rack::Cors.new(app, :
|
233
|
+
cors = Rack::Cors.new(app, debug: true, logger: logger) {}
|
204
234
|
cors.send(:debug, {}, 'testing')
|
205
235
|
end
|
206
236
|
|
@@ -211,8 +241,8 @@ describe Rack::Cors do
|
|
211
241
|
logger = mock
|
212
242
|
logger.expects(:debug).at_least_once
|
213
243
|
|
214
|
-
cors = Rack::Cors.new(app, :
|
215
|
-
cors.call({'rack.logger' => logger, 'HTTP_ORIGIN' => 'test.com'})
|
244
|
+
cors = Rack::Cors.new(app, debug: true) {}
|
245
|
+
cors.call({ 'rack.logger' => logger, 'HTTP_ORIGIN' => 'test.com' })
|
216
246
|
end
|
217
247
|
|
218
248
|
it 'should use logger proc' do
|
@@ -222,8 +252,8 @@ describe Rack::Cors do
|
|
222
252
|
logger = mock
|
223
253
|
logger.expects(:debug)
|
224
254
|
|
225
|
-
cors = Rack::Cors.new(app, :
|
226
|
-
cors.call({'HTTP_ORIGIN' => 'test.com'})
|
255
|
+
cors = Rack::Cors.new(app, debug: true, logger: proc { logger }) {}
|
256
|
+
cors.call({ 'HTTP_ORIGIN' => 'test.com' })
|
227
257
|
end
|
228
258
|
|
229
259
|
describe 'with Rails setup' do
|
@@ -238,89 +268,118 @@ describe Rack::Cors do
|
|
238
268
|
logger = mock
|
239
269
|
logger.expects(:debug)
|
240
270
|
|
241
|
-
::Rails = OpenStruct.new(:
|
271
|
+
::Rails = OpenStruct.new(logger: logger)
|
242
272
|
|
243
|
-
cors = Rack::Cors.new(app, :
|
244
|
-
cors.call({'HTTP_ORIGIN' => 'test.com'})
|
273
|
+
cors = Rack::Cors.new(app, debug: true) {}
|
274
|
+
cors.call({ 'HTTP_ORIGIN' => 'test.com' })
|
245
275
|
end
|
246
276
|
end
|
277
|
+
|
278
|
+
it 'should use Logger if none is set' do
|
279
|
+
app = mock
|
280
|
+
app.stubs(:call).returns([200, {}, ['']])
|
281
|
+
|
282
|
+
logger = mock
|
283
|
+
Logger.expects(:new).returns(logger)
|
284
|
+
logger.expects(:tap).returns(logger)
|
285
|
+
logger.expects(:debug)
|
286
|
+
|
287
|
+
cors = Rack::Cors.new(app, debug: true) {}
|
288
|
+
cors.call({ 'HTTP_ORIGIN' => 'test.com' })
|
289
|
+
end
|
247
290
|
end
|
248
291
|
|
249
292
|
describe 'preflight requests' do
|
250
293
|
it 'should fail if origin is invalid' do
|
251
294
|
preflight_request('http://allyourdataarebelongtous.com', '/')
|
252
|
-
last_response.wont_render_cors_success
|
253
|
-
cors_result.wont_be :hit
|
254
|
-
cors_result.must_be :preflight
|
295
|
+
_(last_response).wont_render_cors_success
|
296
|
+
_(cors_result).wont_be :hit
|
297
|
+
_(cors_result).must_be :preflight
|
255
298
|
end
|
256
299
|
|
257
300
|
it 'should fail if Access-Control-Request-Method is not allowed' do
|
258
|
-
preflight_request('http://localhost:3000', '/get-only', :
|
259
|
-
last_response.wont_render_cors_success
|
260
|
-
cors_result.miss_reason.must_equal Rack::Cors::Result::MISS_DENY_METHOD
|
261
|
-
cors_result.wont_be :hit
|
262
|
-
cors_result.must_be :preflight
|
301
|
+
preflight_request('http://localhost:3000', '/get-only', method: :post)
|
302
|
+
_(last_response).wont_render_cors_success
|
303
|
+
_(cors_result.miss_reason).must_equal Rack::Cors::Result::MISS_DENY_METHOD
|
304
|
+
_(cors_result).wont_be :hit
|
305
|
+
_(cors_result).must_be :preflight
|
263
306
|
end
|
264
307
|
|
265
308
|
it 'should fail if header is not allowed' do
|
266
|
-
preflight_request('http://localhost:3000', '/single_header', :
|
267
|
-
last_response.wont_render_cors_success
|
268
|
-
cors_result.miss_reason.must_equal Rack::Cors::Result::MISS_DENY_HEADER
|
269
|
-
cors_result.wont_be :hit
|
270
|
-
cors_result.must_be :preflight
|
309
|
+
preflight_request('http://localhost:3000', '/single_header', headers: 'Fooey')
|
310
|
+
_(last_response).wont_render_cors_success
|
311
|
+
_(cors_result.miss_reason).must_equal Rack::Cors::Result::MISS_DENY_HEADER
|
312
|
+
_(cors_result).wont_be :hit
|
313
|
+
_(cors_result).must_be :preflight
|
271
314
|
end
|
272
315
|
|
273
316
|
it 'should allow any header if headers = :any' do
|
274
|
-
preflight_request('http://localhost:3000', '/', :
|
275
|
-
last_response.must_render_cors_success
|
317
|
+
preflight_request('http://localhost:3000', '/', headers: 'Fooey')
|
318
|
+
_(last_response).must_render_cors_success
|
276
319
|
end
|
277
320
|
|
278
321
|
it 'should allow any method if methods = :any' do
|
279
|
-
preflight_request('http://localhost:3000', '/', :
|
280
|
-
last_response.must_render_cors_success
|
322
|
+
preflight_request('http://localhost:3000', '/', methods: :any)
|
323
|
+
_(last_response).must_render_cors_success
|
324
|
+
end
|
325
|
+
|
326
|
+
it 'allows PATCH method' do
|
327
|
+
preflight_request('http://localhost:3000', '/', methods: [:patch])
|
328
|
+
_(last_response).must_render_cors_success
|
281
329
|
end
|
282
330
|
|
283
331
|
it 'should allow header case insensitive match' do
|
284
|
-
preflight_request('http://localhost:3000', '/single_header', :
|
285
|
-
last_response.must_render_cors_success
|
332
|
+
preflight_request('http://localhost:3000', '/single_header', headers: 'X-Domain-Token')
|
333
|
+
_(last_response).must_render_cors_success
|
286
334
|
end
|
287
335
|
|
288
336
|
it 'should allow multiple headers match' do
|
289
337
|
# Webkit style
|
290
|
-
preflight_request('http://localhost:3000', '/two_headers', :
|
291
|
-
last_response.must_render_cors_success
|
338
|
+
preflight_request('http://localhost:3000', '/two_headers', headers: 'X-Requested-With, X-Domain-Token')
|
339
|
+
_(last_response).must_render_cors_success
|
292
340
|
|
293
341
|
# Gecko style
|
294
|
-
preflight_request('http://localhost:3000', '/two_headers', :
|
295
|
-
last_response.must_render_cors_success
|
342
|
+
preflight_request('http://localhost:3000', '/two_headers', headers: 'x-requested-with,x-domain-token')
|
343
|
+
_(last_response).must_render_cors_success
|
296
344
|
end
|
297
345
|
|
298
|
-
it
|
346
|
+
it "should allow '*' origins to allow any origin" do
|
299
347
|
preflight_request('http://locohost:3000', '/public')
|
300
|
-
last_response.must_render_cors_success
|
301
|
-
last_response.headers['Access-Control-Allow-Origin'].must_equal '*'
|
348
|
+
_(last_response).must_render_cors_success
|
349
|
+
_(last_response.headers['Access-Control-Allow-Origin']).must_equal '*'
|
350
|
+
end
|
351
|
+
|
352
|
+
it "should allow '/<path>/' resource if match pattern is /<path>/*" do
|
353
|
+
preflight_request('http://localhost:3000', '/wildcard/')
|
354
|
+
_(last_response).must_render_cors_success
|
355
|
+
_(last_response.headers['Access-Control-Allow-Origin']).wont_equal nil
|
356
|
+
end
|
357
|
+
|
358
|
+
it "should allow '/<path>' resource if match pattern is /<path>/*" do
|
359
|
+
preflight_request('http://localhost:3000', '/wildcard')
|
360
|
+
_(last_response).must_render_cors_success
|
361
|
+
_(last_response.headers['Access-Control-Allow-Origin']).wont_equal nil
|
302
362
|
end
|
303
363
|
|
304
|
-
it
|
364
|
+
it "should allow '*' origin to allow any origin, and set '*' if no credentials required" do
|
305
365
|
preflight_request('http://locohost:3000', '/public_without_credentials')
|
306
|
-
last_response.must_render_cors_success
|
307
|
-
last_response.headers['Access-Control-Allow-Origin'].must_equal '*'
|
366
|
+
_(last_response).must_render_cors_success
|
367
|
+
_(last_response.headers['Access-Control-Allow-Origin']).must_equal '*'
|
308
368
|
end
|
309
369
|
|
310
370
|
it 'should return "file://" as header with "file://" as origin' do
|
311
371
|
preflight_request('file://', '/')
|
312
|
-
last_response.must_render_cors_success
|
313
|
-
last_response.headers['Access-Control-Allow-Origin'].must_equal 'file://'
|
372
|
+
_(last_response).must_render_cors_success
|
373
|
+
_(last_response.headers['Access-Control-Allow-Origin']).must_equal 'file://'
|
314
374
|
end
|
315
375
|
|
316
|
-
it '
|
317
|
-
preflight_request('
|
318
|
-
last_response.must_render_cors_success
|
319
|
-
last_response.headers['
|
376
|
+
it 'supports custom protocols in origin' do
|
377
|
+
preflight_request('custom-protocol://abcdefg', '/')
|
378
|
+
_(last_response).must_render_cors_success
|
379
|
+
_(last_response.headers['Access-Control-Allow-Origin']).must_equal 'custom-protocol://abcdefg'
|
320
380
|
end
|
321
381
|
|
322
382
|
describe '' do
|
323
|
-
|
324
383
|
let(:app) do
|
325
384
|
test = self
|
326
385
|
Rack::Builder.new do
|
@@ -328,36 +387,36 @@ describe Rack::Cors do
|
|
328
387
|
use Rack::Cors, debug: true, logger: Logger.new(StringIO.new) do
|
329
388
|
allow do
|
330
389
|
origins '*'
|
331
|
-
resource '/', :
|
390
|
+
resource '/', methods: :post
|
332
391
|
end
|
333
392
|
end
|
334
393
|
map('/') do
|
335
|
-
run ->(
|
394
|
+
run ->(_env) { [500, {}, ['FAIL!']] }
|
336
395
|
end
|
337
396
|
end
|
338
397
|
end
|
339
398
|
|
340
|
-
it
|
341
|
-
preflight_request('http://localhost', '/', :
|
342
|
-
last_response.wont_render_cors_success
|
343
|
-
last_response.status.must_equal 200
|
344
|
-
cors_result.must_be :preflight
|
345
|
-
cors_result.wont_be :hit
|
346
|
-
cors_result.miss_reason.must_equal Rack::Cors::Result::MISS_DENY_METHOD
|
399
|
+
it 'should not send failed preflight requests thru the app' do
|
400
|
+
preflight_request('http://localhost', '/', method: :unsupported)
|
401
|
+
_(last_response).wont_render_cors_success
|
402
|
+
_(last_response.status).must_equal 200
|
403
|
+
_(cors_result).must_be :preflight
|
404
|
+
_(cors_result).wont_be :hit
|
405
|
+
_(cors_result.miss_reason).must_equal Rack::Cors::Result::MISS_DENY_METHOD
|
347
406
|
end
|
348
407
|
end
|
349
408
|
end
|
350
409
|
|
351
|
-
describe
|
410
|
+
describe 'with insecure configuration' do
|
352
411
|
let(:app) { load_app('insecure') }
|
353
412
|
|
354
|
-
it
|
355
|
-
proc { cors_request '/public' }.must_raise Rack::Cors::Resource::CorsMisconfigurationError
|
413
|
+
it 'should raise an error' do
|
414
|
+
_(proc { cors_request '/public' }).must_raise Rack::Cors::Resource::CorsMisconfigurationError
|
356
415
|
end
|
357
416
|
end
|
358
417
|
|
359
|
-
describe
|
360
|
-
let(:app) { load_app(
|
418
|
+
describe 'with non HTTP config' do
|
419
|
+
let(:app) { load_app('non_http') }
|
361
420
|
|
362
421
|
it 'should support non http/https origins' do
|
363
422
|
successful_cors_request '/public', origin: 'content://com.company.app'
|
@@ -369,13 +428,13 @@ describe Rack::Cors do
|
|
369
428
|
@app ||= Rack::Builder.new do
|
370
429
|
use Rack::Cors
|
371
430
|
use Rack::Lint
|
372
|
-
run ->(
|
431
|
+
run ->(_env) { [200, { 'Content-Type' => 'text/html' }, ['hello']] }
|
373
432
|
end
|
374
433
|
end
|
375
434
|
|
376
435
|
it 'is lint-compliant with non-CORS request' do
|
377
436
|
get '/'
|
378
|
-
last_response.status.must_equal 200
|
437
|
+
_(last_response.status).must_equal 200
|
379
438
|
end
|
380
439
|
end
|
381
440
|
|
@@ -389,19 +448,19 @@ describe Rack::Cors do
|
|
389
448
|
end
|
390
449
|
end
|
391
450
|
map('/') do
|
392
|
-
run ->(
|
451
|
+
run ->(_env) { [200, { 'Access-Control-Allow-Origin' => 'http://foo.net' }, ['success']] }
|
393
452
|
end
|
394
453
|
end
|
395
454
|
end
|
396
455
|
|
397
|
-
it
|
398
|
-
successful_cors_request origin:
|
399
|
-
last_response.headers['Access-Control-Allow-Origin'].must_equal
|
456
|
+
it 'should return app header' do
|
457
|
+
successful_cors_request origin: 'http://example.net'
|
458
|
+
_(last_response.headers['Access-Control-Allow-Origin']).must_equal 'http://foo.net'
|
400
459
|
end
|
401
460
|
|
402
|
-
it
|
403
|
-
successful_cors_request origin:
|
404
|
-
last_response.headers['X-Rack-CORS-Original-Access-Control-Allow-Origin'].must_equal
|
461
|
+
it 'should return original headers if in debug' do
|
462
|
+
successful_cors_request origin: 'http://example.net'
|
463
|
+
_(last_response.headers['X-Rack-CORS-Original-Access-Control-Allow-Origin']).must_equal '*'
|
405
464
|
end
|
406
465
|
end
|
407
466
|
|
@@ -415,14 +474,14 @@ describe Rack::Cors do
|
|
415
474
|
end
|
416
475
|
end
|
417
476
|
map('/') do
|
418
|
-
run ->(
|
477
|
+
run ->(_env) { [200, { 'Content-Type' => 'text/html' }, ['hello']] }
|
419
478
|
end
|
420
479
|
end
|
421
480
|
end
|
422
481
|
|
423
482
|
it 'should succeed with CORS simple headers' do
|
424
|
-
preflight_request('http://localhost:3000', '/', :
|
425
|
-
last_response.must_render_cors_success
|
483
|
+
preflight_request('http://localhost:3000', '/', headers: 'Accept')
|
484
|
+
_(last_response).must_render_cors_success
|
426
485
|
end
|
427
486
|
end
|
428
487
|
|
@@ -436,47 +495,46 @@ describe Rack::Cors do
|
|
436
495
|
end
|
437
496
|
end
|
438
497
|
map('/') do
|
439
|
-
run ->(
|
498
|
+
run ->(_env) { [200, { 'Content-Type' => 'text/html' }, ['hello']] }
|
440
499
|
end
|
441
500
|
end
|
442
501
|
end
|
443
502
|
|
444
503
|
it 'should succeed with CORS simple headers' do
|
445
|
-
preflight_request('http://localhost:3000', '/', :
|
446
|
-
last_response.must_render_cors_success
|
447
|
-
preflight_request('http://localhost:3000', '/', :
|
448
|
-
last_response.must_render_cors_success
|
449
|
-
preflight_request('http://localhost:3000', '/', :
|
450
|
-
last_response.must_render_cors_success
|
451
|
-
preflight_request('http://localhost:3000', '/', :
|
452
|
-
last_response.must_render_cors_success
|
504
|
+
preflight_request('http://localhost:3000', '/', headers: 'Accept')
|
505
|
+
_(last_response).must_render_cors_success
|
506
|
+
preflight_request('http://localhost:3000', '/', headers: 'Accept-Language')
|
507
|
+
_(last_response).must_render_cors_success
|
508
|
+
preflight_request('http://localhost:3000', '/', headers: 'Content-Type')
|
509
|
+
_(last_response).must_render_cors_success
|
510
|
+
preflight_request('http://localhost:3000', '/', headers: 'Content-Language')
|
511
|
+
_(last_response).must_render_cors_success
|
453
512
|
end
|
454
513
|
end
|
455
514
|
|
456
515
|
protected
|
457
|
-
def cors_request(*args)
|
458
|
-
path = args.first.is_a?(String) ? args.first : '/'
|
459
516
|
|
460
|
-
|
461
|
-
|
517
|
+
def cors_request(*args)
|
518
|
+
path = args.first.is_a?(String) ? args.first : '/'
|
462
519
|
|
463
|
-
|
464
|
-
|
465
|
-
end
|
520
|
+
opts = { method: :get, origin: 'http://localhost:3000' }
|
521
|
+
opts.merge! args.last if args.last.is_a?(Hash)
|
466
522
|
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
end
|
523
|
+
header 'origin', opts[:origin]
|
524
|
+
current_session.__send__ opts[:method], path, {}, test: self
|
525
|
+
end
|
471
526
|
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
527
|
+
def successful_cors_request(*args)
|
528
|
+
cors_request(*args)
|
529
|
+
_(last_response).must_render_cors_success
|
530
|
+
end
|
531
|
+
|
532
|
+
def preflight_request(origin, path, opts = {})
|
533
|
+
header 'Origin', origin
|
534
|
+
unless opts.key?(:method) && opts[:method].nil?
|
535
|
+
header 'Access-Control-Request-Method', opts[:method] ? opts[:method].to_s.upcase : 'GET'
|
481
536
|
end
|
537
|
+
header 'Access-Control-Request-Headers', opts[:headers] if opts[:headers]
|
538
|
+
options path
|
539
|
+
end
|
482
540
|
end
|