1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
|
require 'rubygems'
require 'minitest/autorun'
require 'rack/test'
require 'mocha/setup'
require 'rack/cors'
require 'ostruct'
Rack::Test::Session.class_eval do
def options(uri, params = {}, env = {}, &block)
env = env_for(uri, env.merge(:method => "OPTIONS", :params => params))
process_request(uri, env, &block)
end
end
Rack::Test::Methods.class_eval do
def_delegator :current_session, :options
end
describe Rack::Cors do
include Rack::Test::Methods
attr_accessor :cors_result
def load_app(name)
test = self
Rack::Builder.new do
eval File.read(File.dirname(__FILE__) + "/#{name}.ru")
map('/') do
run proc { |env|
test.cors_result = env[Rack::Cors::ENV_KEY]
[200, {'Content-Type' => 'text/html'}, ['success']]
}
end
end
end
let(:app) { load_app('test') }
it 'should support simple CORS request' do
cors_request
cors_result.must_be :hit
end
it "should not return CORS headers if Origin header isn't present" do
get '/'
should_render_cors_failure
cors_result.wont_be :hit
end
it 'should support OPTIONS CORS request' do
cors_request '/options', :method => :options
end
it 'should support regex origins configuration' do
cors_request :origin => 'http://192.168.0.1:1234'
end
it 'should support proc origins configuration' do
cors_request '/proc-origin', :origin => 'http://10.10.10.10:3000'
end
it 'should support alternative X-Origin header' do
header 'X-Origin', 'http://localhost:3000'
get '/'
should_render_cors_success
end
it 'should support expose header configuration' do
cors_request '/expose_single_header'
last_response.headers['Access-Control-Expose-Headers'].must_equal 'expose-test'
end
it 'should support expose multiple header configuration' do
cors_request '/expose_multiple_headers'
last_response.headers['Access-Control-Expose-Headers'].must_equal 'expose-test-1, expose-test-2'
end
# Explanation: http://www.fastly.com/blog/best-practices-for-using-the-vary-header/
it "should add Vary header if resource matches even if Origin header isn't present" do
get '/'
should_render_cors_failure
last_response.headers['Vary'].must_equal 'Origin'
end
it "should add Vary header based on :vary option" do
cors_request '/vary_test'
last_response.headers['Vary'].must_equal 'Origin, Host'
end
it 'should add Vary header if Access-Control-Allow-Origin header was added and if it is specific' do
cors_request '/', :origin => "http://192.168.0.3:8080"
last_response.headers['Access-Control-Allow-Origin'].must_equal 'http://192.168.0.3:8080'
last_response.headers['Vary'].wont_be_nil
end
it 'should add Vary header even if Access-Control-Allow-Origin header was added and it is generic (*)' do
cors_request '/public_without_credentials', :origin => "http://192.168.1.3:8080"
last_response.headers['Access-Control-Allow-Origin'].must_equal '*'
last_response.headers['Vary'].must_equal 'Origin'
end
it 'should support multi allow configurations for the same resource' do
cors_request '/multi-allow-config', :origin => "http://mucho-grande.com"
last_response.headers['Access-Control-Allow-Origin'].must_equal 'http://mucho-grande.com'
last_response.headers['Vary'].must_equal 'Origin'
cors_request '/multi-allow-config', :origin => "http://192.168.1.3:8080"
last_response.headers['Access-Control-Allow-Origin'].must_equal '*'
last_response.headers['Vary'].must_equal 'Origin'
end
it "should not return CORS headers on OPTIONS request if Access-Control-Allow-Origin is not present" do
options '/get-only'
last_response.headers['Access-Control-Allow-Origin'].must_be_nil
end
it "should not apply CORS headers if it does not match conditional on resource" do
header 'Origin', 'http://192.168.0.1:1234'
get '/conditional'
should_render_cors_failure
end
it "should apply CORS headers if it does match conditional on resource" do
header 'X-OK', '1'
cors_request '/conditional', :origin => 'http://192.168.0.1:1234'
end
describe 'logging' do
it 'should not log debug messages if debug option is false' do
app = mock
app.stubs(:call).returns(200, {}, [''])
logger = mock
logger.expects(:debug).never
cors = Rack::Cors.new(app, :debug => false, :logger => logger) {}
cors.send(:debug, {}, 'testing')
end
it 'should log debug messages if debug option is true' do
app = mock
app.stubs(:call).returns(200, {}, [''])
logger = mock
logger.expects(:debug)
cors = Rack::Cors.new(app, :debug => true, :logger => logger) {}
cors.send(:debug, {}, 'testing')
end
it 'should use rack.logger if available' do
app = mock
app.stubs(:call).returns([200, {}, ['']])
logger = mock
logger.expects(:debug).at_least_once
cors = Rack::Cors.new(app, :debug => true) {}
cors.call({'rack.logger' => logger, 'HTTP_ORIGIN' => 'test.com'})
end
it 'should use logger proc' do
app = mock
app.stubs(:call).returns([200, {}, ['']])
logger = mock
logger.expects(:debug)
cors = Rack::Cors.new(app, :debug => true, :logger => proc { logger }) {}
cors.call({'HTTP_ORIGIN' => 'test.com'})
end
describe 'with Rails setup' do
after do
::Rails.logger = nil if defined?(::Rails)
end
it 'should use Rails.logger if available' do
app = mock
app.stubs(:call).returns([200, {}, ['']])
logger = mock
logger.expects(:debug)
::Rails = OpenStruct.new(:logger => logger)
cors = Rack::Cors.new(app, :debug => true) {}
cors.call({'HTTP_ORIGIN' => 'test.com'})
end
end
end
describe 'preflight requests' do
it 'should fail if origin is invalid' do
preflight_request('http://allyourdataarebelongtous.com', '/')
should_render_cors_failure
cors_result.wont_be :hit
cors_result.must_be :preflight
end
it 'should fail if Access-Control-Request-Method is not allowed' do
preflight_request('http://localhost:3000', '/get-only', :method => :post)
should_render_cors_failure
end
it 'should fail if header is not allowed' do
preflight_request('http://localhost:3000', '/single_header', :headers => 'Fooey')
should_render_cors_failure
end
it 'should allow any header if headers = :any' do
preflight_request('http://localhost:3000', '/', :headers => 'Fooey')
should_render_cors_success
end
it 'should allow any method if methods = :any' do
preflight_request('http://localhost:3000', '/', :methods => :any)
should_render_cors_success
end
it 'should allow header case insensitive match' do
preflight_request('http://localhost:3000', '/single_header', :headers => 'X-Domain-Token')
should_render_cors_success
end
it 'should allow multiple headers match' do
# Webkit style
preflight_request('http://localhost:3000', '/two_headers', :headers => 'X-Requested-With, X-Domain-Token')
should_render_cors_success
# Gecko style
preflight_request('http://localhost:3000', '/two_headers', :headers => 'x-requested-with,x-domain-token')
should_render_cors_success
end
it 'should * origin should allow any origin' do
preflight_request('http://locohost:3000', '/public')
should_render_cors_success
last_response.headers['Access-Control-Allow-Origin'].must_equal 'http://locohost:3000'
end
it 'should * origin should allow any origin, and set * if no credentials required' do
preflight_request('http://locohost:3000', '/public_without_credentials')
should_render_cors_success
last_response.headers['Access-Control-Allow-Origin'].must_equal '*'
end
it 'should "null" origin, allowed as "file://", returned as "null" in header' do
preflight_request('null', '/')
should_render_cors_success
last_response.headers['Access-Control-Allow-Origin'].must_equal 'null'
end
it 'should return "file://" as header with "file://" as origin' do
preflight_request('file://', '/')
should_render_cors_success
last_response.headers['Access-Control-Allow-Origin'].must_equal 'file://'
end
it 'should return a Content-Type' do
preflight_request('http://localhost:3000', '/')
should_render_cors_success
last_response.headers['Content-Type'].wont_be_nil
end
end
describe "with non HTTP config" do
let(:app) { load_app("non_http") }
it 'should support non http/https origins' do
cors_request '/public', origin: 'content://com.company.app'
end
end
describe 'Rack::Lint' do
def app
@app ||= Rack::Builder.new do
use Rack::Cors
use Rack::Lint
run ->(env) { [200, {'Content-Type' => 'text/html'}, ['hello']] }
end
end
it 'is lint-compliant with non-CORS request' do
get '/'
last_response.status.must_equal 200
end
end
protected
def cors_request(*args)
path = args.first.is_a?(String) ? args.first : '/'
opts = { :method => :get, :origin => 'http://localhost:3000' }
opts.merge! args.last if args.last.is_a?(Hash)
header 'Origin', opts[:origin]
current_session.__send__ opts[:method], path, {}, test: self
should_render_cors_success
end
def preflight_request(origin, path, opts = {})
header 'Origin', origin
unless opts.key?(:method) && opts[:method].nil?
header 'Access-Control-Request-Method', opts[:method] ? opts[:method].to_s.upcase : 'GET'
end
if opts[:headers]
header 'Access-Control-Request-Headers', opts[:headers]
end
options path
end
def should_render_cors_success
last_response.headers['Access-Control-Allow-Origin'].wont_be_nil
end
def should_render_cors_failure
last_response.headers['Access-Control-Allow-Origin'].must_be_nil
end
end
|