rack-cors 0.4.1 → 1.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.
Potentially problematic release.
This version of rack-cors might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG +23 -5
- data/README.md +10 -10
- data/lib/rack/cors.rb +98 -71
- data/lib/rack/cors/version.rb +1 -1
- data/test/cors/test.cors.coffee +1 -1
- data/test/cors/test.cors.js +2 -2
- data/test/unit/cors_test.rb +120 -52
- data/test/unit/dsl_test.rb +11 -0
- data/test/unit/insecure.ru +8 -0
- data/test/unit/test.ru +5 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4d3e08e4dddeda03c7f6ae34307de611a84d848c
|
4
|
+
data.tar.gz: a6353f11742d77141c3e0e62d2a48e7367275391
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c613462413784ba147dc5efd45b355ae26c5c133637d428ddfe1aabd739c8c5d43a4b85827c7984448daf8baccf3ff1325a83cbb59a95145f0c5e25009fd700d
|
7
|
+
data.tar.gz: 9d0887852ae6b1144cf15f34b630ed22ab5f05099b00fdad950c7df0f989424f343ecb471bf538f2f244eb037e7e1d1159565c8e770bd37bf983b6e770c14a0d
|
data/CHANGELOG
CHANGED
@@ -1,18 +1,37 @@
|
|
1
1
|
# Change Log
|
2
2
|
All notable changes to this project will be documented in this file.
|
3
3
|
|
4
|
+
## 1.0.0 - 2017-07-15
|
5
|
+
### Security
|
6
|
+
- Don't implicitly accept 'null' origins when 'file://' is specified
|
7
|
+
(https://github.com/cyu/rack-cors/pull/134)
|
8
|
+
- Ignore '' origins (https://github.com/cyu/rack-cors/issues/139)
|
9
|
+
- Default credentials option on resources to false
|
10
|
+
(https://github.com/cyu/rack-cors/issues/95)
|
11
|
+
- Don't allow credentials option to be true if '*' is specified is origin
|
12
|
+
(https://github.com/cyu/rack-cors/pull/142)
|
13
|
+
- Don't reflect Origin header when '*' is specified as origin
|
14
|
+
(https://github.com/cyu/rack-cors/pull/142)
|
15
|
+
|
16
|
+
### Fixed
|
17
|
+
- Don't respond immediately on non-matching preflight requests instead of
|
18
|
+
sending them through the app (https://github.com/cyu/rack-cors/pull/106)
|
19
|
+
|
4
20
|
## 0.4.1 - 2017-02-01
|
5
21
|
### Fixed
|
6
|
-
- Return miss result in X-Rack-CORS instead of incorrectly returning
|
22
|
+
- Return miss result in X-Rack-CORS instead of incorrectly returning
|
23
|
+
preflight-hit
|
7
24
|
|
8
25
|
## 0.4.0 - 2015-04-15
|
9
26
|
### Changed
|
10
27
|
- Don't set HTTP_ORIGIN with HTTP_X_ORIGIN if nil
|
28
|
+
|
11
29
|
### Added
|
12
30
|
- Calculate vary headers for non-CORS resources
|
13
31
|
- Support custom vary headers for resource
|
14
32
|
- Support :if option for resource
|
15
33
|
- Support :any as a possible value for :methods option
|
34
|
+
|
16
35
|
### Fixed
|
17
36
|
- Don't symbolize incoming HTTP request methods
|
18
37
|
|
@@ -23,10 +42,9 @@ All notable changes to this project will be documented in this file.
|
|
23
42
|
## 0.3.0 - 2014-10-19
|
24
43
|
### Added
|
25
44
|
- Added support for defining a logger with a Proc
|
26
|
-
- Return a X-Rack-CORS header when in debug mode detailing how
|
27
|
-
|
28
|
-
- Added support for non HTTP/HTTPS origins when just a domain is
|
29
|
-
is specified
|
45
|
+
- Return a X-Rack-CORS header when in debug mode detailing how Rack::Cors
|
46
|
+
processed a request
|
47
|
+
- Added support for non HTTP/HTTPS origins when just a domain is specified
|
30
48
|
|
31
49
|
### Changed
|
32
50
|
- Changed the log level of the fallback logger to DEBUG
|
data/README.md
CHANGED
@@ -28,28 +28,29 @@ module YourApp
|
|
28
28
|
|
29
29
|
# ...
|
30
30
|
|
31
|
-
# Rails
|
31
|
+
# Rails 5
|
32
32
|
|
33
|
-
config.middleware.insert_before 0,
|
33
|
+
config.middleware.insert_before 0, Rack::Cors do
|
34
34
|
allow do
|
35
35
|
origins '*'
|
36
36
|
resource '*', :headers => :any, :methods => [:get, :post, :options]
|
37
37
|
end
|
38
38
|
end
|
39
|
-
|
40
|
-
# Rails 5
|
41
39
|
|
42
|
-
|
40
|
+
# Rails 3/4
|
41
|
+
|
42
|
+
config.middleware.insert_before 0, "Rack::Cors" do
|
43
43
|
allow do
|
44
44
|
origins '*'
|
45
45
|
resource '*', :headers => :any, :methods => [:get, :post, :options]
|
46
46
|
end
|
47
47
|
end
|
48
|
-
|
48
|
+
|
49
49
|
end
|
50
50
|
end
|
51
51
|
```
|
52
|
-
|
52
|
+
|
53
|
+
We use `insert_before` to make sure `Rack::Cors` runs at the beginning of the stack to make sure it isn't interfered with with other middleware (see `Rack::Cache` note in **Common Gotchas** section). Check out the [rails 4 example](https://github.com/cyu/rack-cors/tree/master/examples/rails4) and [rails 3 example](https://github.com/cyu/rack-cors/tree/master/examples/rails3).
|
53
54
|
|
54
55
|
See The [Rails Guide to Rack](http://guides.rubyonrails.org/rails_on_rack.html) for more details on rack middlewares or watch the [railscast](http://railscasts.com/episodes/151-rack-middleware).
|
55
56
|
|
@@ -98,13 +99,12 @@ Additionally, origins can be specified dynamically via a block of the following
|
|
98
99
|
origins { |source, env| true || false }
|
99
100
|
```
|
100
101
|
|
101
|
-
|
102
|
-
A Resource path can be specified as exact string match (`/path/to/file.txt`) or with a '\*' wildcard (`/all/files/in/*`). A resource can take the following options:
|
102
|
+
A Resource path can be specified as exact string match (`/path/to/file.txt`) or with a '\*' wildcard (`/all/files/in/*`). To include all of a directory's files and the files in its subdirectories, use this form: `/assets/**/*`. A resource can take the following options:
|
103
103
|
|
104
104
|
* **methods** (string or array or `:any`): The HTTP methods allowed for the resource.
|
105
105
|
* **headers** (string or array or `:any`): The HTTP headers that will be allowed in the CORS resource request. Use `:any` to allow for any headers in the actual request.
|
106
106
|
* **expose** (string or array): The HTTP headers in the resource response can be exposed to the client.
|
107
|
-
* **credentials** (boolean): Sets the `Access-Control-Allow-Credentials` response header.
|
107
|
+
* **credentials** (boolean, default: `false`): Sets the `Access-Control-Allow-Credentials` response header. **Note:** If a wildcard (`*`) origin is specified, this option cannot be set to `true`. Read this [security article](http://web-in-security.blogspot.de/2017/07/cors-misconfigurations-on-large-scale.html) for more information.
|
108
108
|
* **max_age** (number): Sets the `Access-Control-Max-Age` response header.
|
109
109
|
* **if** (Proc): If the result of the proc is true, will process the request as a valid CORS request.
|
110
110
|
* **vary** (string or array): A list of HTTP headers to add to the 'Vary' header.
|
data/lib/rack/cors.rb
CHANGED
@@ -2,13 +2,26 @@ require 'logger'
|
|
2
2
|
|
3
3
|
module Rack
|
4
4
|
class Cors
|
5
|
-
|
5
|
+
HTTP_ORIGIN = 'HTTP_ORIGIN'.freeze
|
6
|
+
HTTP_X_ORIGIN = 'HTTP_X_ORIGIN'.freeze
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
HTTP_ACCESS_CONTROL_REQUEST_METHOD = 'HTTP_ACCESS_CONTROL_REQUEST_METHOD'.freeze
|
9
|
+
HTTP_ACCESS_CONTROL_REQUEST_HEADERS = 'HTTP_ACCESS_CONTROL_REQUEST_HEADERS'.freeze
|
10
|
+
|
11
|
+
PATH_INFO = 'PATH_INFO'.freeze
|
12
|
+
REQUEST_METHOD = 'REQUEST_METHOD'.freeze
|
13
|
+
|
14
|
+
RACK_LOGGER = 'rack.logger'.freeze
|
15
|
+
RACK_CORS =
|
16
|
+
# retaining the old key for backwards compatibility
|
17
|
+
ENV_KEY = 'rack.cors'.freeze
|
18
|
+
|
19
|
+
OPTIONS = 'OPTIONS'.freeze
|
20
|
+
VARY = 'Vary'.freeze
|
21
|
+
CONTENT_TYPE = 'Content-Type'.freeze
|
22
|
+
TEXT_PLAIN = 'text/plain'.freeze
|
23
|
+
|
24
|
+
DEFAULT_VARY_HEADERS = ['Origin'].freeze
|
12
25
|
|
13
26
|
def initialize(app, opts={}, &block)
|
14
27
|
@app = app
|
@@ -47,25 +60,24 @@ module Rack
|
|
47
60
|
end
|
48
61
|
|
49
62
|
def call(env)
|
50
|
-
env[
|
63
|
+
env[HTTP_ORIGIN] ||= env[HTTP_X_ORIGIN] if env[HTTP_X_ORIGIN]
|
51
64
|
|
52
65
|
add_headers = nil
|
53
|
-
if env[
|
66
|
+
if env[HTTP_ORIGIN]
|
54
67
|
debug(env) do
|
55
68
|
[ 'Incoming Headers:',
|
56
|
-
" Origin: #{env[
|
57
|
-
" Access-Control-Request-Method: #{env[
|
58
|
-
" Access-Control-Request-Headers: #{env[
|
69
|
+
" Origin: #{env[HTTP_ORIGIN]}",
|
70
|
+
" Access-Control-Request-Method: #{env[HTTP_ACCESS_CONTROL_REQUEST_METHOD]}",
|
71
|
+
" Access-Control-Request-Headers: #{env[HTTP_ACCESS_CONTROL_REQUEST_HEADERS]}"
|
59
72
|
].join("\n")
|
60
73
|
end
|
61
|
-
if env[
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
end
|
67
|
-
return [200, headers, []]
|
74
|
+
if env[REQUEST_METHOD] == OPTIONS and env[HTTP_ACCESS_CONTROL_REQUEST_METHOD]
|
75
|
+
headers = process_preflight(env)
|
76
|
+
debug(env) do
|
77
|
+
"Preflight Headers:\n" +
|
78
|
+
headers.collect{|kv| " #{kv.join(': ')}"}.join("\n")
|
68
79
|
end
|
80
|
+
return [200, headers, []]
|
69
81
|
else
|
70
82
|
add_headers = process_cors(env)
|
71
83
|
end
|
@@ -74,9 +86,9 @@ module Rack
|
|
74
86
|
end
|
75
87
|
|
76
88
|
# This call must be done BEFORE calling the app because for some reason
|
77
|
-
# env[
|
78
|
-
#
|
79
|
-
vary_resource = resource_for_path(env[
|
89
|
+
# env[PATH_INFO] gets changed after that and it won't match. (At least
|
90
|
+
# in rails 4.1.6)
|
91
|
+
vary_resource = resource_for_path(env[PATH_INFO])
|
80
92
|
|
81
93
|
status, headers, body = @app.call env
|
82
94
|
|
@@ -95,16 +107,16 @@ module Rack
|
|
95
107
|
# response to be different depending on the Origin header value.
|
96
108
|
# Better explained here: http://www.fastly.com/blog/best-practices-for-using-the-vary-header/
|
97
109
|
if vary_resource
|
98
|
-
vary = headers[
|
110
|
+
vary = headers[VARY]
|
99
111
|
cors_vary_headers = if vary_resource.vary_headers && vary_resource.vary_headers.any?
|
100
112
|
vary_resource.vary_headers
|
101
113
|
else
|
102
114
|
DEFAULT_VARY_HEADERS
|
103
115
|
end
|
104
|
-
headers[
|
116
|
+
headers[VARY] = ((vary ? vary.split(/,\s*/) : []) + cors_vary_headers).uniq.join(', ')
|
105
117
|
end
|
106
118
|
|
107
|
-
if debug? && result = env[
|
119
|
+
if debug? && result = env[RACK_CORS]
|
108
120
|
result.append_header(headers)
|
109
121
|
end
|
110
122
|
|
@@ -125,8 +137,8 @@ module Rack
|
|
125
137
|
elsif defined?(Rails) && Rails.logger
|
126
138
|
Rails.logger
|
127
139
|
|
128
|
-
elsif env[
|
129
|
-
env[
|
140
|
+
elsif env[RACK_LOGGER]
|
141
|
+
env[RACK_LOGGER]
|
130
142
|
|
131
143
|
else
|
132
144
|
::Logger.new(STDOUT).tap { |logger| logger.level = ::Logger::Severity::DEBUG }
|
@@ -138,16 +150,15 @@ module Rack
|
|
138
150
|
end
|
139
151
|
|
140
152
|
def process_preflight(env)
|
141
|
-
|
142
|
-
if resource
|
143
|
-
Result.preflight_hit(env)
|
144
|
-
preflight = resource.process_preflight(env)
|
145
|
-
preflight
|
153
|
+
result = Result.preflight(env)
|
146
154
|
|
147
|
-
|
148
|
-
|
149
|
-
|
155
|
+
resource, error = match_resource(env)
|
156
|
+
unless resource
|
157
|
+
result.miss(error)
|
158
|
+
return {}
|
150
159
|
end
|
160
|
+
|
161
|
+
return resource.process_preflight(env, result)
|
151
162
|
end
|
152
163
|
|
153
164
|
def process_cors(env)
|
@@ -173,8 +184,8 @@ module Rack
|
|
173
184
|
end
|
174
185
|
|
175
186
|
def match_resource(env)
|
176
|
-
path = env[
|
177
|
-
origin = env[
|
187
|
+
path = env[PATH_INFO]
|
188
|
+
origin = env[HTTP_ORIGIN]
|
178
189
|
|
179
190
|
origin_matched = false
|
180
191
|
all_resources.each do |r|
|
@@ -195,6 +206,10 @@ module Rack
|
|
195
206
|
MISS_NO_ORIGIN = 'no-origin'.freeze
|
196
207
|
MISS_NO_PATH = 'no-path'.freeze
|
197
208
|
|
209
|
+
MISS_NO_METHOD = 'no-method'.freeze
|
210
|
+
MISS_DENY_METHOD = 'deny-method'.freeze
|
211
|
+
MISS_DENY_HEADER = 'deny-header'.freeze
|
212
|
+
|
198
213
|
attr_accessor :preflight, :hit, :miss_reason
|
199
214
|
|
200
215
|
def hit?
|
@@ -205,11 +220,16 @@ module Rack
|
|
205
220
|
!!preflight
|
206
221
|
end
|
207
222
|
|
223
|
+
def miss(reason)
|
224
|
+
self.hit = false
|
225
|
+
self.miss_reason = reason
|
226
|
+
end
|
227
|
+
|
208
228
|
def self.hit(env)
|
209
229
|
r = Result.new
|
210
230
|
r.preflight = false
|
211
231
|
r.hit = true
|
212
|
-
env[
|
232
|
+
env[RACK_CORS] = r
|
213
233
|
end
|
214
234
|
|
215
235
|
def self.miss(env, reason)
|
@@ -217,23 +237,15 @@ module Rack
|
|
217
237
|
r.preflight = false
|
218
238
|
r.hit = false
|
219
239
|
r.miss_reason = reason
|
220
|
-
env[
|
240
|
+
env[RACK_CORS] = r
|
221
241
|
end
|
222
242
|
|
223
|
-
def self.
|
243
|
+
def self.preflight(env)
|
224
244
|
r = Result.new
|
225
245
|
r.preflight = true
|
226
|
-
|
227
|
-
env[ENV_KEY] = r
|
246
|
+
env[RACK_CORS] = r
|
228
247
|
end
|
229
248
|
|
230
|
-
def self.preflight_miss(env, reason)
|
231
|
-
r = Result.new
|
232
|
-
r.preflight = true
|
233
|
-
r.hit = false
|
234
|
-
r.miss_reason = reason
|
235
|
-
env[ENV_KEY] = r
|
236
|
-
end
|
237
249
|
|
238
250
|
def append_header(headers)
|
239
251
|
headers[HEADER_KEY] = if hit?
|
@@ -248,6 +260,9 @@ module Rack
|
|
248
260
|
end
|
249
261
|
|
250
262
|
class Resources
|
263
|
+
|
264
|
+
attr_reader :resources
|
265
|
+
|
251
266
|
def initialize
|
252
267
|
@origins = []
|
253
268
|
@resources = []
|
@@ -255,7 +270,7 @@ module Rack
|
|
255
270
|
end
|
256
271
|
|
257
272
|
def origins(*args, &blk)
|
258
|
-
@origins = args.flatten.
|
273
|
+
@origins = args.flatten.reject{ |s| s == '' }.map do |n|
|
259
274
|
case n
|
260
275
|
when Regexp,
|
261
276
|
/^https?:\/\//,
|
@@ -278,13 +293,11 @@ module Rack
|
|
278
293
|
def allow_origin?(source,env = {})
|
279
294
|
return true if public_resources?
|
280
295
|
|
281
|
-
effective_source = (source == 'null' ? 'file://' : source)
|
282
|
-
|
283
296
|
return !! @origins.detect do |origin|
|
284
297
|
if origin.is_a?(Proc)
|
285
298
|
origin.call(source,env)
|
286
299
|
else
|
287
|
-
origin ===
|
300
|
+
origin === source
|
288
301
|
end
|
289
302
|
end
|
290
303
|
end
|
@@ -300,11 +313,20 @@ module Rack
|
|
300
313
|
end
|
301
314
|
|
302
315
|
class Resource
|
316
|
+
class CorsMisconfigurationError < StandardError
|
317
|
+
def message
|
318
|
+
"Allowing credentials for wildcard origins is insecure."\
|
319
|
+
" Please specify more restrictive origins or set 'credentials' to false in your CORS configuration."
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
303
323
|
attr_accessor :path, :methods, :headers, :expose, :max_age, :credentials, :pattern, :if_proc, :vary_headers
|
304
324
|
|
305
325
|
def initialize(public_resource, path, opts={})
|
326
|
+
raise CorsMisconfigurationError if public_resource && opts[:credentials] == true
|
327
|
+
|
306
328
|
self.path = path
|
307
|
-
self.credentials =
|
329
|
+
self.credentials = public_resource ? false : (opts[:credentials] == true)
|
308
330
|
self.max_age = opts[:max_age] || 1728000
|
309
331
|
self.pattern = compile(path)
|
310
332
|
self.if_proc = opts[:if]
|
@@ -323,7 +345,7 @@ module Rack
|
|
323
345
|
else
|
324
346
|
ensure_enum(opts[:methods]) || [:get]
|
325
347
|
end.map{|e| e.to_s }
|
326
|
-
|
348
|
+
|
327
349
|
self.expose = opts[:expose] ? [opts[:expose]].flatten : nil
|
328
350
|
end
|
329
351
|
|
@@ -335,14 +357,29 @@ module Rack
|
|
335
357
|
matches_path?(path) && (if_proc.nil? || if_proc.call(env))
|
336
358
|
end
|
337
359
|
|
338
|
-
def process_preflight(env)
|
339
|
-
|
340
|
-
|
360
|
+
def process_preflight(env, result)
|
361
|
+
headers = {CONTENT_TYPE => TEXT_PLAIN}
|
362
|
+
|
363
|
+
request_method = env[HTTP_ACCESS_CONTROL_REQUEST_METHOD]
|
364
|
+
if request_method.nil?
|
365
|
+
result.miss(Result::MISS_NO_METHOD) and return headers
|
366
|
+
end
|
367
|
+
if !methods.include?(request_method.downcase)
|
368
|
+
result.miss(Result::MISS_DENY_METHOD) and return headers
|
369
|
+
end
|
370
|
+
|
371
|
+
request_headers = env[HTTP_ACCESS_CONTROL_REQUEST_HEADERS]
|
372
|
+
if request_headers && !allow_headers?(request_headers)
|
373
|
+
result.miss(Result::MISS_DENY_HEADER) and return headers
|
374
|
+
end
|
375
|
+
|
376
|
+
result.hit = true
|
377
|
+
headers.merge(to_preflight_headers(env))
|
341
378
|
end
|
342
379
|
|
343
380
|
def to_headers(env)
|
344
381
|
h = {
|
345
|
-
'Access-Control-Allow-Origin' => origin_for_response_header(env[
|
382
|
+
'Access-Control-Allow-Origin' => origin_for_response_header(env[HTTP_ORIGIN]),
|
346
383
|
'Access-Control-Allow-Methods' => methods.collect{|m| m.to_s.upcase}.join(', '),
|
347
384
|
'Access-Control-Expose-Headers' => expose.nil? ? '' : expose.join(', '),
|
348
385
|
'Access-Control-Max-Age' => max_age.to_s }
|
@@ -356,28 +393,18 @@ module Rack
|
|
356
393
|
end
|
357
394
|
|
358
395
|
def origin_for_response_header(origin)
|
359
|
-
return '*' if public_resource?
|
396
|
+
return '*' if public_resource?
|
360
397
|
origin
|
361
398
|
end
|
362
399
|
|
363
400
|
def to_preflight_headers(env)
|
364
401
|
h = to_headers(env)
|
365
|
-
if env[
|
366
|
-
h.merge!('Access-Control-Allow-Headers' => env[
|
402
|
+
if env[HTTP_ACCESS_CONTROL_REQUEST_HEADERS]
|
403
|
+
h.merge!('Access-Control-Allow-Headers' => env[HTTP_ACCESS_CONTROL_REQUEST_HEADERS])
|
367
404
|
end
|
368
405
|
h
|
369
406
|
end
|
370
407
|
|
371
|
-
def invalid_method_request?(env)
|
372
|
-
request_method = env['HTTP_ACCESS_CONTROL_REQUEST_METHOD']
|
373
|
-
request_method.nil? || !methods.include?(request_method.downcase)
|
374
|
-
end
|
375
|
-
|
376
|
-
def invalid_headers_request?(env)
|
377
|
-
request_headers = env['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']
|
378
|
-
request_headers && !allow_headers?(request_headers)
|
379
|
-
end
|
380
|
-
|
381
408
|
def allow_headers?(request_headers)
|
382
409
|
return false if headers.nil?
|
383
410
|
headers == :any || begin
|
data/lib/rack/cors/version.rb
CHANGED
data/test/cors/test.cors.coffee
CHANGED
data/test/cors/test.cors.js
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
// Generated by CoffeeScript 1.
|
1
|
+
// Generated by CoffeeScript 1.12.6
|
2
2
|
(function() {
|
3
3
|
var CORS_SERVER;
|
4
4
|
|
5
|
-
CORS_SERVER = '
|
5
|
+
CORS_SERVER = '127.0.0.1.xip.io:9292';
|
6
6
|
|
7
7
|
describe('CORS', function() {
|
8
8
|
it('should allow access to dynamic resource', function(done) {
|
data/test/unit/cors_test.rb
CHANGED
@@ -16,6 +16,32 @@ end
|
|
16
16
|
Rack::Test::Methods.class_eval do
|
17
17
|
def_delegator :current_session, :options
|
18
18
|
end
|
19
|
+
|
20
|
+
module MiniTest::Assertions
|
21
|
+
def assert_cors_success(response)
|
22
|
+
assert !response.headers['Access-Control-Allow-Origin'].nil?, "Expected a successful CORS response"
|
23
|
+
end
|
24
|
+
|
25
|
+
def assert_not_cors_success(response)
|
26
|
+
assert response.headers['Access-Control-Allow-Origin'].nil?, "Expected a failed CORS response"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class CaptureResult
|
31
|
+
def initialize(app, options = {})
|
32
|
+
@app = app
|
33
|
+
@result_holder = options[:holder]
|
34
|
+
end
|
35
|
+
|
36
|
+
def call(env)
|
37
|
+
response = @app.call(env)
|
38
|
+
@result_holder.cors_result = env[Rack::Cors::RACK_CORS]
|
39
|
+
return response
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
Rack::MockResponse.infect_an_assertion :assert_cors_success, :must_render_cors_success, :only_one_argument
|
44
|
+
Rack::MockResponse.infect_an_assertion :assert_not_cors_success, :wont_render_cors_success, :only_one_argument
|
19
45
|
|
20
46
|
describe Rack::Cors do
|
21
47
|
include Rack::Test::Methods
|
@@ -25,10 +51,10 @@ describe Rack::Cors do
|
|
25
51
|
def load_app(name)
|
26
52
|
test = self
|
27
53
|
Rack::Builder.new do
|
54
|
+
use CaptureResult, :holder => test
|
28
55
|
eval File.read(File.dirname(__FILE__) + "/#{name}.ru")
|
29
56
|
map('/') do
|
30
57
|
run proc { |env|
|
31
|
-
test.cors_result = env[Rack::Cors::ENV_KEY]
|
32
58
|
[200, {'Content-Type' => 'text/html'}, ['success']]
|
33
59
|
}
|
34
60
|
end
|
@@ -38,84 +64,84 @@ describe Rack::Cors do
|
|
38
64
|
let(:app) { load_app('test') }
|
39
65
|
|
40
66
|
it 'should support simple CORS request' do
|
41
|
-
|
67
|
+
successful_cors_request
|
42
68
|
cors_result.must_be :hit
|
43
69
|
end
|
44
70
|
|
45
71
|
it "should not return CORS headers if Origin header isn't present" do
|
46
72
|
get '/'
|
47
|
-
|
73
|
+
last_response.wont_render_cors_success
|
48
74
|
cors_result.wont_be :hit
|
49
75
|
end
|
50
76
|
|
51
77
|
it 'should support OPTIONS CORS request' do
|
52
|
-
|
78
|
+
successful_cors_request '/options', :method => :options
|
53
79
|
end
|
54
80
|
|
55
81
|
it 'should support regex origins configuration' do
|
56
|
-
|
82
|
+
successful_cors_request :origin => 'http://192.168.0.1:1234'
|
57
83
|
end
|
58
84
|
|
59
85
|
it 'should support subdomain example' do
|
60
|
-
|
86
|
+
successful_cors_request :origin => 'http://subdomain.example.com'
|
61
87
|
end
|
62
88
|
|
63
89
|
it 'should support proc origins configuration' do
|
64
|
-
|
90
|
+
successful_cors_request '/proc-origin', :origin => 'http://10.10.10.10:3000'
|
65
91
|
end
|
66
92
|
|
67
93
|
it 'should not mix up path rules across origins' do
|
68
94
|
header 'Origin', 'http://10.10.10.10:3000'
|
69
95
|
get '/' # / is configured in a separate rule block
|
70
|
-
|
96
|
+
last_response.wont_render_cors_success
|
71
97
|
end
|
72
98
|
|
73
99
|
it 'should support alternative X-Origin header' do
|
74
100
|
header 'X-Origin', 'http://localhost:3000'
|
75
101
|
get '/'
|
76
|
-
|
102
|
+
last_response.must_render_cors_success
|
77
103
|
end
|
78
104
|
|
79
105
|
it 'should support expose header configuration' do
|
80
|
-
|
106
|
+
successful_cors_request '/expose_single_header'
|
81
107
|
last_response.headers['Access-Control-Expose-Headers'].must_equal 'expose-test'
|
82
108
|
end
|
83
109
|
|
84
110
|
it 'should support expose multiple header configuration' do
|
85
|
-
|
111
|
+
successful_cors_request '/expose_multiple_headers'
|
86
112
|
last_response.headers['Access-Control-Expose-Headers'].must_equal 'expose-test-1, expose-test-2'
|
87
113
|
end
|
88
114
|
|
89
115
|
# Explanation: http://www.fastly.com/blog/best-practices-for-using-the-vary-header/
|
90
116
|
it "should add Vary header if resource matches even if Origin header isn't present" do
|
91
117
|
get '/'
|
92
|
-
|
118
|
+
last_response.wont_render_cors_success
|
93
119
|
last_response.headers['Vary'].must_equal 'Origin'
|
94
120
|
end
|
95
121
|
|
96
122
|
it "should add Vary header based on :vary option" do
|
97
|
-
|
123
|
+
successful_cors_request '/vary_test'
|
98
124
|
last_response.headers['Vary'].must_equal 'Origin, Host'
|
99
125
|
end
|
100
126
|
|
101
127
|
it 'should add Vary header if Access-Control-Allow-Origin header was added and if it is specific' do
|
102
|
-
|
128
|
+
successful_cors_request '/', :origin => "http://192.168.0.3:8080"
|
103
129
|
last_response.headers['Access-Control-Allow-Origin'].must_equal 'http://192.168.0.3:8080'
|
104
130
|
last_response.headers['Vary'].wont_be_nil
|
105
131
|
end
|
106
132
|
|
107
133
|
it 'should add Vary header even if Access-Control-Allow-Origin header was added and it is generic (*)' do
|
108
|
-
|
134
|
+
successful_cors_request '/public_without_credentials', :origin => "http://192.168.1.3:8080"
|
109
135
|
last_response.headers['Access-Control-Allow-Origin'].must_equal '*'
|
110
136
|
last_response.headers['Vary'].must_equal 'Origin'
|
111
137
|
end
|
112
138
|
|
113
139
|
it 'should support multi allow configurations for the same resource' do
|
114
|
-
|
140
|
+
successful_cors_request '/multi-allow-config', :origin => "http://mucho-grande.com"
|
115
141
|
last_response.headers['Access-Control-Allow-Origin'].must_equal 'http://mucho-grande.com'
|
116
142
|
last_response.headers['Vary'].must_equal 'Origin'
|
117
143
|
|
118
|
-
|
144
|
+
successful_cors_request '/multi-allow-config', :origin => "http://192.168.1.3:8080"
|
119
145
|
last_response.headers['Access-Control-Allow-Origin'].must_equal '*'
|
120
146
|
last_response.headers['Vary'].must_equal 'Origin'
|
121
147
|
end
|
@@ -128,15 +154,25 @@ describe Rack::Cors do
|
|
128
154
|
it "should not apply CORS headers if it does not match conditional on resource" do
|
129
155
|
header 'Origin', 'http://192.168.0.1:1234'
|
130
156
|
get '/conditional'
|
131
|
-
|
157
|
+
last_response.wont_render_cors_success
|
132
158
|
end
|
133
159
|
|
134
160
|
it "should apply CORS headers if it does match conditional on resource" do
|
135
161
|
header 'X-OK', '1'
|
136
|
-
|
162
|
+
successful_cors_request '/conditional', :origin => 'http://192.168.0.1:1234'
|
137
163
|
end
|
138
164
|
|
139
|
-
|
165
|
+
it "should not allow everything if Origin is configured as blank string" do
|
166
|
+
cors_request '/blank-origin', origin: "http://example.net"
|
167
|
+
last_response.wont_render_cors_success
|
168
|
+
end
|
169
|
+
|
170
|
+
it "should not allow credentials for public resources" do
|
171
|
+
successful_cors_request '/public'
|
172
|
+
last_response.headers['Access-Control-Allow-Credentials'].must_be_nil
|
173
|
+
end
|
174
|
+
|
175
|
+
describe 'logging' do
|
140
176
|
it 'should not log debug messages if debug option is false' do
|
141
177
|
app = mock
|
142
178
|
app.stubs(:call).returns(200, {}, [''])
|
@@ -204,82 +240,118 @@ describe Rack::Cors do
|
|
204
240
|
describe 'preflight requests' do
|
205
241
|
it 'should fail if origin is invalid' do
|
206
242
|
preflight_request('http://allyourdataarebelongtous.com', '/')
|
207
|
-
|
243
|
+
last_response.wont_render_cors_success
|
208
244
|
cors_result.wont_be :hit
|
209
245
|
cors_result.must_be :preflight
|
210
246
|
end
|
211
247
|
|
212
248
|
it 'should fail if Access-Control-Request-Method is not allowed' do
|
213
249
|
preflight_request('http://localhost:3000', '/get-only', :method => :post)
|
214
|
-
|
250
|
+
last_response.wont_render_cors_success
|
251
|
+
cors_result.miss_reason.must_equal Rack::Cors::Result::MISS_DENY_METHOD
|
252
|
+
cors_result.wont_be :hit
|
253
|
+
cors_result.must_be :preflight
|
215
254
|
end
|
216
255
|
|
217
256
|
it 'should fail if header is not allowed' do
|
218
257
|
preflight_request('http://localhost:3000', '/single_header', :headers => 'Fooey')
|
219
|
-
|
258
|
+
last_response.wont_render_cors_success
|
259
|
+
cors_result.miss_reason.must_equal Rack::Cors::Result::MISS_DENY_HEADER
|
260
|
+
cors_result.wont_be :hit
|
261
|
+
cors_result.must_be :preflight
|
220
262
|
end
|
221
263
|
|
222
264
|
it 'should allow any header if headers = :any' do
|
223
265
|
preflight_request('http://localhost:3000', '/', :headers => 'Fooey')
|
224
|
-
|
266
|
+
last_response.must_render_cors_success
|
225
267
|
end
|
226
268
|
|
227
269
|
it 'should allow any method if methods = :any' do
|
228
270
|
preflight_request('http://localhost:3000', '/', :methods => :any)
|
229
|
-
|
271
|
+
last_response.must_render_cors_success
|
230
272
|
end
|
231
273
|
|
232
274
|
it 'should allow header case insensitive match' do
|
233
275
|
preflight_request('http://localhost:3000', '/single_header', :headers => 'X-Domain-Token')
|
234
|
-
|
276
|
+
last_response.must_render_cors_success
|
235
277
|
end
|
236
278
|
|
237
279
|
it 'should allow multiple headers match' do
|
238
280
|
# Webkit style
|
239
281
|
preflight_request('http://localhost:3000', '/two_headers', :headers => 'X-Requested-With, X-Domain-Token')
|
240
|
-
|
282
|
+
last_response.must_render_cors_success
|
241
283
|
|
242
284
|
# Gecko style
|
243
285
|
preflight_request('http://localhost:3000', '/two_headers', :headers => 'x-requested-with,x-domain-token')
|
244
|
-
|
286
|
+
last_response.must_render_cors_success
|
245
287
|
end
|
246
288
|
|
247
289
|
it 'should * origin should allow any origin' do
|
248
290
|
preflight_request('http://locohost:3000', '/public')
|
249
|
-
|
250
|
-
last_response.headers['Access-Control-Allow-Origin'].must_equal '
|
291
|
+
last_response.must_render_cors_success
|
292
|
+
last_response.headers['Access-Control-Allow-Origin'].must_equal '*'
|
251
293
|
end
|
252
294
|
|
253
295
|
it 'should * origin should allow any origin, and set * if no credentials required' do
|
254
296
|
preflight_request('http://locohost:3000', '/public_without_credentials')
|
255
|
-
|
297
|
+
last_response.must_render_cors_success
|
256
298
|
last_response.headers['Access-Control-Allow-Origin'].must_equal '*'
|
257
299
|
end
|
258
300
|
|
259
|
-
it 'should "null" origin, allowed as "file://", returned as "null" in header' do
|
260
|
-
preflight_request('null', '/')
|
261
|
-
should_render_cors_success
|
262
|
-
last_response.headers['Access-Control-Allow-Origin'].must_equal 'null'
|
263
|
-
end
|
264
|
-
|
265
301
|
it 'should return "file://" as header with "file://" as origin' do
|
266
302
|
preflight_request('file://', '/')
|
267
|
-
|
303
|
+
last_response.must_render_cors_success
|
268
304
|
last_response.headers['Access-Control-Allow-Origin'].must_equal 'file://'
|
269
305
|
end
|
270
306
|
|
271
307
|
it 'should return a Content-Type' do
|
272
308
|
preflight_request('http://localhost:3000', '/')
|
273
|
-
|
309
|
+
last_response.must_render_cors_success
|
274
310
|
last_response.headers['Content-Type'].wont_be_nil
|
275
311
|
end
|
312
|
+
|
313
|
+
describe '' do
|
314
|
+
|
315
|
+
let(:app) do
|
316
|
+
test = self
|
317
|
+
Rack::Builder.new do
|
318
|
+
use CaptureResult, holder: test
|
319
|
+
use Rack::Cors, debug: true, logger: Logger.new(StringIO.new) do
|
320
|
+
allow do
|
321
|
+
origins '*'
|
322
|
+
resource '/', :methods => :post
|
323
|
+
end
|
324
|
+
end
|
325
|
+
map('/') do
|
326
|
+
run ->(env) { [500, {'Content-Type' => 'text/plain'}, ['FAIL!']] }
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
it "should not send failed preflight requests thru the app" do
|
332
|
+
preflight_request('http://localhost', '/', :method => :unsupported)
|
333
|
+
last_response.wont_render_cors_success
|
334
|
+
last_response.status.must_equal 200
|
335
|
+
cors_result.must_be :preflight
|
336
|
+
cors_result.wont_be :hit
|
337
|
+
cors_result.miss_reason.must_equal Rack::Cors::Result::MISS_DENY_METHOD
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
describe "with insecure configuration" do
|
343
|
+
let(:app) { load_app('insecure') }
|
344
|
+
|
345
|
+
it "should raise an error" do
|
346
|
+
proc { cors_request '/public' }.must_raise Rack::Cors::Resource::CorsMisconfigurationError
|
347
|
+
end
|
276
348
|
end
|
277
349
|
|
278
350
|
describe "with non HTTP config" do
|
279
351
|
let(:app) { load_app("non_http") }
|
280
352
|
|
281
353
|
it 'should support non http/https origins' do
|
282
|
-
|
354
|
+
successful_cors_request '/public', origin: 'content://com.company.app'
|
283
355
|
end
|
284
356
|
end
|
285
357
|
|
@@ -314,13 +386,13 @@ describe Rack::Cors do
|
|
314
386
|
end
|
315
387
|
|
316
388
|
it "should return app header" do
|
317
|
-
|
389
|
+
successful_cors_request origin: "http://example.net"
|
318
390
|
last_response.headers['Access-Control-Allow-Origin'].must_equal "http://foo.net"
|
319
391
|
end
|
320
392
|
|
321
393
|
it "should return original headers if in debug" do
|
322
|
-
|
323
|
-
last_response.headers['X-Rack-CORS-Original-Access-Control-Allow-Origin'].must_equal "
|
394
|
+
successful_cors_request origin: "http://example.net"
|
395
|
+
last_response.headers['X-Rack-CORS-Original-Access-Control-Allow-Origin'].must_equal "*"
|
324
396
|
end
|
325
397
|
end
|
326
398
|
|
@@ -333,7 +405,11 @@ describe Rack::Cors do
|
|
333
405
|
|
334
406
|
header 'Origin', opts[:origin]
|
335
407
|
current_session.__send__ opts[:method], path, {}, test: self
|
336
|
-
|
408
|
+
end
|
409
|
+
|
410
|
+
def successful_cors_request(*args)
|
411
|
+
cors_request(*args)
|
412
|
+
last_response.must_render_cors_success
|
337
413
|
end
|
338
414
|
|
339
415
|
def preflight_request(origin, path, opts = {})
|
@@ -346,12 +422,4 @@ describe Rack::Cors do
|
|
346
422
|
end
|
347
423
|
options path
|
348
424
|
end
|
349
|
-
|
350
|
-
def should_render_cors_success
|
351
|
-
last_response.headers['Access-Control-Allow-Origin'].wont_be_nil
|
352
|
-
end
|
353
|
-
|
354
|
-
def should_render_cors_failure
|
355
|
-
last_response.headers['Access-Control-Allow-Origin'].must_be_nil
|
356
|
-
end
|
357
425
|
end
|
data/test/unit/dsl_test.rb
CHANGED
@@ -55,4 +55,15 @@ describe Rack::Cors, 'DSL' do
|
|
55
55
|
|
56
56
|
resources.first.allow_origin?('file://').must_equal true
|
57
57
|
end
|
58
|
+
|
59
|
+
it 'should default credentials option to false' do
|
60
|
+
cors = Rack::Cors.new(Proc.new {}) do
|
61
|
+
allow do
|
62
|
+
origins 'example.net'
|
63
|
+
resource '/', :headers => :any
|
64
|
+
end
|
65
|
+
end
|
66
|
+
resources = cors.send :all_resources
|
67
|
+
resources.first.resources.first.credentials.must_equal false
|
68
|
+
end
|
58
69
|
end
|
data/test/unit/test.ru
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-cors
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Calvin Yu
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-07-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -106,6 +106,7 @@ files:
|
|
106
106
|
- test/cors/test.cors.js
|
107
107
|
- test/unit/cors_test.rb
|
108
108
|
- test/unit/dsl_test.rb
|
109
|
+
- test/unit/insecure.ru
|
109
110
|
- test/unit/non_http.ru
|
110
111
|
- test/unit/test.ru
|
111
112
|
homepage: https://github.com/cyu/rack-cors
|
@@ -141,5 +142,6 @@ test_files:
|
|
141
142
|
- test/cors/test.cors.js
|
142
143
|
- test/unit/cors_test.rb
|
143
144
|
- test/unit/dsl_test.rb
|
145
|
+
- test/unit/insecure.ru
|
144
146
|
- test/unit/non_http.ru
|
145
147
|
- test/unit/test.ru
|