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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: aaa518c3408420a39bd3c41bd822cdda6309beb2
4
- data.tar.gz: 8fa4c9d5141e4d6c4df8600175f44ba6803ebe9f
3
+ metadata.gz: 4d3e08e4dddeda03c7f6ae34307de611a84d848c
4
+ data.tar.gz: a6353f11742d77141c3e0e62d2a48e7367275391
5
5
  SHA512:
6
- metadata.gz: 633c772b16e08fad8fb93a7d1bf5d62a398abb534a27ce0d44df843242c5e1077112e98b1eb7e2d3849da952fec1754cd2e8451195c1a2c86ac567d69652b859
7
- data.tar.gz: 8e0265d2d82db72ac68871dcf0eaf974809638d5a52514f99392b731fe09050ab07968c5e538a35d7bba2b7892cc62b3d6b7b401fe8a05c4648f5ad57a092abf
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 preflight-hit
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
- Rack::Cors processed a request
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 3/4
31
+ # Rails 5
32
32
 
33
- config.middleware.insert_before 0, "Rack::Cors" do
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
- config.middleware.insert_before 0, Rack::Cors do
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
- Refer to [rails 3 example](https://github.com/cyu/rack-cors/tree/master/examples/rails3) and [rails 4 example](https://github.com/cyu/rack-cors/tree/master/examples/rails4) for more details.
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
- #### Resource
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.
@@ -2,13 +2,26 @@ require 'logger'
2
2
 
3
3
  module Rack
4
4
  class Cors
5
- ENV_KEY = 'rack.cors'.freeze
5
+ HTTP_ORIGIN = 'HTTP_ORIGIN'.freeze
6
+ HTTP_X_ORIGIN = 'HTTP_X_ORIGIN'.freeze
6
7
 
7
- ORIGIN_HEADER_KEY = 'HTTP_ORIGIN'.freeze
8
- ORIGIN_X_HEADER_KEY = 'HTTP_X_ORIGIN'.freeze
9
- PATH_INFO_HEADER_KEY = 'PATH_INFO'.freeze
10
- VARY_HEADER_KEY = 'Vary'.freeze
11
- DEFAULT_VARY_HEADERS = ['Origin'].freeze
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[ORIGIN_HEADER_KEY] ||= env[ORIGIN_X_HEADER_KEY] if env[ORIGIN_X_HEADER_KEY]
63
+ env[HTTP_ORIGIN] ||= env[HTTP_X_ORIGIN] if env[HTTP_X_ORIGIN]
51
64
 
52
65
  add_headers = nil
53
- if env[ORIGIN_HEADER_KEY]
66
+ if env[HTTP_ORIGIN]
54
67
  debug(env) do
55
68
  [ 'Incoming Headers:',
56
- " Origin: #{env[ORIGIN_HEADER_KEY]}",
57
- " Access-Control-Request-Method: #{env['HTTP_ACCESS_CONTROL_REQUEST_METHOD']}",
58
- " Access-Control-Request-Headers: #{env['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}"
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['REQUEST_METHOD'] == 'OPTIONS' and env['HTTP_ACCESS_CONTROL_REQUEST_METHOD']
62
- if headers = process_preflight(env)
63
- debug(env) do
64
- "Preflight Headers:\n" +
65
- headers.collect{|kv| " #{kv.join(': ')}"}.join("\n")
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[PATH_INFO_HEADER_KEY] gets changed after that and it won't match.
78
- # (At least in rails 4.1.6)
79
- vary_resource = resource_for_path(env[PATH_INFO_HEADER_KEY])
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[VARY_HEADER_KEY]
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[VARY_HEADER_KEY] = ((vary ? vary.split(/,\s*/) : []) + cors_vary_headers).uniq.join(', ')
116
+ headers[VARY] = ((vary ? vary.split(/,\s*/) : []) + cors_vary_headers).uniq.join(', ')
105
117
  end
106
118
 
107
- if debug? && result = env[ENV_KEY]
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['rack.logger']
129
- env['rack.logger']
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
- resource, error = match_resource(env)
142
- if resource
143
- Result.preflight_hit(env)
144
- preflight = resource.process_preflight(env)
145
- preflight
153
+ result = Result.preflight(env)
146
154
 
147
- else
148
- Result.preflight_miss(env, error)
149
- nil
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[PATH_INFO_HEADER_KEY]
177
- origin = env[ORIGIN_HEADER_KEY]
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[ENV_KEY] = r
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[ENV_KEY] = r
240
+ env[RACK_CORS] = r
221
241
  end
222
242
 
223
- def self.preflight_hit(env)
243
+ def self.preflight(env)
224
244
  r = Result.new
225
245
  r.preflight = true
226
- r.hit = true
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.collect do |n|
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 === effective_source
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 = opts[:credentials].nil? ? true : opts[: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
- return nil if invalid_method_request?(env) || invalid_headers_request?(env)
340
- {'Content-Type' => 'text/plain'}.merge(to_preflight_headers(env))
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[ORIGIN_HEADER_KEY]),
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? && !credentials
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['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']
366
- h.merge!('Access-Control-Allow-Headers' => env['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])
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
@@ -1,5 +1,5 @@
1
1
  module Rack
2
2
  class Cors
3
- VERSION = "0.4.1"
3
+ VERSION = "1.0.0"
4
4
  end
5
5
  end
@@ -1,4 +1,4 @@
1
- CORS_SERVER = 'cors-server:3000'
1
+ CORS_SERVER = '127.0.0.1.xip.io:9292'
2
2
 
3
3
  describe 'CORS', ->
4
4
 
@@ -1,8 +1,8 @@
1
- // Generated by CoffeeScript 1.7.1
1
+ // Generated by CoffeeScript 1.12.6
2
2
  (function() {
3
3
  var CORS_SERVER;
4
4
 
5
- CORS_SERVER = 'cors-server:3000';
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) {
@@ -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
- cors_request
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
- should_render_cors_failure
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
- cors_request '/options', :method => :options
78
+ successful_cors_request '/options', :method => :options
53
79
  end
54
80
 
55
81
  it 'should support regex origins configuration' do
56
- cors_request :origin => 'http://192.168.0.1:1234'
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
- cors_request :origin => 'http://subdomain.example.com'
86
+ successful_cors_request :origin => 'http://subdomain.example.com'
61
87
  end
62
88
 
63
89
  it 'should support proc origins configuration' do
64
- cors_request '/proc-origin', :origin => 'http://10.10.10.10:3000'
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
- should_render_cors_failure
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
- should_render_cors_success
102
+ last_response.must_render_cors_success
77
103
  end
78
104
 
79
105
  it 'should support expose header configuration' do
80
- cors_request '/expose_single_header'
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
- cors_request '/expose_multiple_headers'
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
- should_render_cors_failure
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
- cors_request '/vary_test'
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
- cors_request '/', :origin => "http://192.168.0.3:8080"
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
- cors_request '/public_without_credentials', :origin => "http://192.168.1.3:8080"
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
- cors_request '/multi-allow-config', :origin => "http://mucho-grande.com"
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
- cors_request '/multi-allow-config', :origin => "http://192.168.1.3:8080"
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
- should_render_cors_failure
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
- cors_request '/conditional', :origin => 'http://192.168.0.1:1234'
162
+ successful_cors_request '/conditional', :origin => 'http://192.168.0.1:1234'
137
163
  end
138
164
 
139
- describe 'logging' do
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
- should_render_cors_failure
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
- should_render_cors_failure
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
- should_render_cors_failure
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
- should_render_cors_success
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
- should_render_cors_success
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
- should_render_cors_success
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
- should_render_cors_success
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
- should_render_cors_success
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
- should_render_cors_success
250
- last_response.headers['Access-Control-Allow-Origin'].must_equal 'http://locohost:3000'
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
- should_render_cors_success
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
- should_render_cors_success
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
- should_render_cors_success
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
- cors_request '/public', origin: 'content://com.company.app'
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
- cors_request origin: "http://example.net"
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
- cors_request origin: "http://example.net"
323
- last_response.headers['X-Rack-CORS-Original-Access-Control-Allow-Origin'].must_equal "http://example.net"
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
- should_render_cors_success
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
@@ -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
@@ -0,0 +1,8 @@
1
+ require 'rack/cors'
2
+
3
+ use Rack::Cors do
4
+ allow do
5
+ origins '*'
6
+ resource '/public', credentials: true
7
+ end
8
+ end
@@ -47,4 +47,9 @@ use Rack::Cors do
47
47
  origins '*'
48
48
  resource '/multi-allow-config', :max_age => 300, :credentials => false
49
49
  end
50
+
51
+ allow do
52
+ origins ''
53
+ resource '/blank-origin'
54
+ end
50
55
  end
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.1
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-02-01 00:00:00.000000000 Z
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