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