rack-cors 0.4.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.

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