kong/spec/03-plugins/13-cors/01-access_spec.lua (942 lines of code) (raw):

local helpers = require "spec.helpers" local cjson = require "cjson" local inspect = require "inspect" local tablex = require "pl.tablex" local CORS_DEFAULT_METHODS = "GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS,TRACE,CONNECT" local function sortedpairs(t) local ks = tablex.keys(t) table.sort(ks) local i = 0 return function() i = i + 1 return ks[i], t[ks[i]] end end for _, strategy in helpers.each_strategy() do describe("Plugin: cors (access) [#" .. strategy .. "]", function() local proxy_client local regex_testcases = { { -- single entry, host only: ignore value, always return configured data origins = { "foo.test" }, tests = { ["http://evil.test"] = "foo.test", ["http://foo.test"] = "foo.test", ["http://foo.test.evil.test"] = "foo.test", ["http://something.foo.test"] = "foo.test", ["http://evilfoo.test"] = "foo.test", ["http://foo.test:80"] = "foo.test", ["http://foo.test:8000"] = "foo.test", ["https://foo.test:8000"] = "foo.test", ["http://foo.test:90"] = "foo.test", ["http://foobtest"] = "foo.test", ["https://bar.test:1234"] = "foo.test", }, }, { -- single entry, full domain (not regex): ignore value, always return configured data origins = { "https://bar.test:1234" }, tests = { ["http://evil.test"] = "https://bar.test:1234", ["http://foo.test"] = "https://bar.test:1234", ["http://foo.test.evil.test"] = "https://bar.test:1234", ["http://something.foo.test"] = "https://bar.test:1234", ["http://evilfoo.test"] = "https://bar.test:1234", ["http://foo.test:80"] = "https://bar.test:1234", ["http://foo.test:8000"] = "https://bar.test:1234", ["https://foo.test:8000"] = "https://bar.test:1234", ["http://foo.test:90"] = "https://bar.test:1234", ["http://foobtest"] = "https://bar.test:1234", ["https://bar.test:1234"] = "https://bar.test:1234", }, }, { -- single entry, simple regex without ":": anchored match on host only origins = { "foo\\.test" }, tests = { ["http://evil.test"] = false, ["http://foo.test"] = true, ["http://foo.test.evil.test"] = false, ["http://something.foo.test"] = false, ["http://evilfoo.test"] = false, ["http://foo.test:80"] = "http://foo.test", ["http://foo.test:8000"] = true, ["https://foo.test:8000"] = true, ["http://foo.test:90"] = true, ["http://foobtest"] = false, ["https://bar.test:1234"] = false, }, }, { -- single entry, subdomain regex without ":": anchored match on host only origins = { "(.*[./])?foo\\.test" }, tests = { ["http://evil.test"] = false, ["http://foo.test"] = true, ["http://foo.test.evil.test"] = false, ["http://something.foo.test"] = true, ["http://evilfoo.test"] = false, ["http://foo.test:80"] = "http://foo.test", ["http://foo.test:8000"] = true, ["https://foo.test:8000"] = true, ["http://foo.test:90"] = true, ["http://foobtest"] = false, ["https://bar.test:1234"] = false, }, }, { -- single entry, any-scheme subdomain regex with port: anchored match with scheme and port origins = { "(.*[./])?foo\\.test:8000" }, tests = { ["http://evil.test"] = false, ["http://foo.test"] = false, ["http://foo.test.evil.test"] = false, ["http://something.foo.test"] = false, ["http://evilfoo.test"] = false, ["http://foo.test:80"] = false, ["http://foo.test:8000"] = true, ["https://foo.test:8000"] = true, ["http://foo.test:90"] = false, ["http://foobtest"] = false, ["https://bar.test:1234"] = false, }, }, { -- single entry, https subdomain regex with port: anchored match with scheme and port origins = { "https://(.*[.])?foo\\.test:8000" }, tests = { ["http://evil.test"] = false, ["http://foo.test"] = false, ["http://foo.test.evil.test"] = false, ["http://something.foo.test"] = false, ["http://foo.test:80"] = false, ["http://foo.test:8000"] = false, ["https://foo.test:8000"] = true, ["http://foo.test:90"] = false, ["http://foobtest"] = false, ["https://bar.test:1234"] = false, }, }, { -- single entry, explicitly anchored https subdomain regex with port: anchored match with scheme and port origins = { "^http://(.*[.])?foo\\.test(:(80|90))?$" }, tests = { ["http://evil.test"] = false, ["http://foo.test"] = true, ["http://foo.test.evil.test"] = false, ["http://something.foo.test"] = true, ["http://foo.test:80"] = "http://foo.test", ["http://foo.test:8000"] = false, ["https://foo.test:8000"] = false, ["http://foo.test:90"] = true, ["http://foobtest"] = false, ["https://bar.test:1234"] = false, }, }, { -- multiple entries, host only (not regex): match on full normalized domain (i.e. all fail) origins = { "foo.test", "bar.test" }, tests = { ["http://evil.test"] = false, ["http://foo.test"] = false, ["http://foo.test.evil.test"] = false, ["http://foo.test:80"] = false, ["http://foo.test:8000"] = false, ["http://foo.test:90"] = false, ["http://foobtest"] = false, ["https://bar.test:1234"] = false, }, }, { -- multiple entries, full domain (not regex): match on full normalized domain origins = { "http://foo.test", "https://bar.test:1234" }, tests = { ["http://evil.test"] = false, ["http://foo.test"] = true, ["http://foo.test.evil.test"] = false, ["http://foo.test:80"] = "http://foo.test", ["http://foo.test:8000"] = false, ["http://foo.test:90"] = false, ["http://foobtest"] = false, ["https://bar.test:1234"] = true, }, }, { -- multiple entries, simple regex without ":": anchored match on host only origins = { "bar.test", "foo\\.test" }, tests = { ["http://evil.test"] = false, ["http://foo.test"] = true, ["http://foo.test.evil.test"] = false, ["http://something.foo.test"] = false, ["http://foo.test:80"] = "http://foo.test", ["http://foo.test:8000"] = true, ["http://foo.test:90"] = true, ["http://foobtest"] = false, ["https://bar.test:1234"] = false, }, }, { -- multiple entries, subdomain regex without ":": anchored match on host only origins = { "bar.test", "(.*\\.)?foo\\.test" }, tests = { ["http://evil.test"] = false, ["http://foo.test"] = true, ["http://foo.test.evil.test"] = false, ["http://something.foo.test"] = true, ["http://foo.test:80"] = "http://foo.test", ["http://foo.test:8000"] = true, ["http://foo.test:90"] = true, ["http://foobtest"] = false, ["https://bar.test:1234"] = false, }, }, { -- multiple entries, any-scheme subdomain regex with ":": anchored match with scheme and port origins = { "bar.test", "(.*[./])?foo\\.test:8000" }, tests = { ["http://evil.test"] = false, ["http://foo.test"] = false, ["http://foo.test.evil.test"] = false, ["http://something.foo.test"] = false, ["http://foo.test:80"] = false, ["http://foo.test:8000"] = true, ["https://foo.test:8000"] = true, ["http://foo.test:90"] = false, ["http://foobtest"] = false, ["https://bar.test:1234"] = false, }, }, { -- multiple entries, https subdomain regex with ":": anchored match with scheme and port origins = { "bar.test", "https://(.*\\.)?foo\\.test:8000" }, tests = { ["http://evil.test"] = false, ["http://foo.test"] = false, ["http://foo.test.evil.test"] = false, ["http://something.foo.test"] = false, ["http://foo.test:80"] = false, ["http://foo.test:8000"] = false, ["https://foo.test:8000"] = true, ["http://foo.test:90"] = false, ["http://foobtest"] = false, ["https://bar.test:1234"] = false, }, }, } lazy_setup(function() local bp = helpers.get_db_utils(strategy, nil, { "error-generator-last" }) local route1 = bp.routes:insert({ hosts = { "cors1.com" }, }) local route2 = bp.routes:insert({ hosts = { "cors2.com" }, }) local route3 = bp.routes:insert({ hosts = { "cors3.com" }, }) local route4 = bp.routes:insert({ hosts = { "cors4.com" }, }) local route5 = bp.routes:insert({ hosts = { "cors5.com" }, }) local route6 = bp.routes:insert({ hosts = { "cors6.com" }, }) local route7 = bp.routes:insert({ hosts = { "cors7.com" }, }) local route8 = bp.routes:insert({ hosts = { "cors-empty-origins.com" }, }) local route9 = bp.routes:insert({ hosts = { "cors9.com" }, }) local route10 = bp.routes:insert({ hosts = { "cors10.com" }, }) local route11 = bp.routes:insert({ hosts = { "cors11.com" }, }) local route12 = bp.routes:insert({ hosts = { "cors12.com" }, }) local mock_upstream = bp.services:insert { host = helpers.mock_upstream_hostname, port = helpers.mock_upstream_port, } local route_upstream = bp.routes:insert({ hosts = { "cors-upstream.com" }, service = mock_upstream }) local mock_service = bp.services:insert { host = "127.0.0.2", port = 26865, } local route_timeout = bp.routes:insert { hosts = { "cors-timeout.com" }, service = mock_service, } local route_error = bp.routes:insert { hosts = { "cors-error.com" }, } bp.plugins:insert { name = "cors", route = { id = route1.id }, } bp.plugins:insert { name = "cors", route = { id = route2.id }, config = { origins = { "example.com" }, methods = { "GET" }, headers = { "origin", "type", "accepts" }, exposed_headers = { "x-auth-token" }, max_age = 23, credentials = true } } bp.plugins:insert { name = "cors", route = { id = route3.id }, config = { origins = { "example.com" }, methods = { "GET" }, headers = { "origin", "type", "accepts" }, exposed_headers = { "x-auth-token" }, max_age = 23, preflight_continue = true } } bp.plugins:insert { name = "cors", route = { id = route4.id }, } bp.plugins:insert { name = "key-auth", route = { id = route4.id } } bp.plugins:insert { name = "cors", route = { id = route5.id }, config = { origins = { "*" }, credentials = true } } bp.plugins:insert { name = "cors", route = { id = route6.id }, config = { origins = { "example.com", "example.org" }, methods = { "GET" }, headers = { "origin", "type", "accepts" }, exposed_headers = { "x-auth-token" }, max_age = 23, preflight_continue = true } } bp.plugins:insert { name = "cors", route = { id = route7.id }, config = { origins = { "*" }, credentials = false } } bp.plugins:insert { name = "cors", route = { id = route8.id }, config = { origins = {}, } } bp.plugins:insert { name = "cors", route = { id = route9.id }, config = { origins = { [[.*\.?example(?:-foo)?.com]] }, } } bp.plugins:insert { name = "cors", route = { id = route10.id }, config = { origins = { "http://my-site.com", "http://my-other-site.com" }, } } bp.plugins:insert { name = "cors", route = { id = route11.id }, config = { origins = { "http://my-site.com", "https://my-other-site.com:9000" }, } } bp.plugins:insert { name = "cors", route = { id = route12.id }, config = { credentials = true, preflight_continue = false, max_age = 1728000, headers = { "DNT", "X-CustomHeader", "Keep-Alive", "User-Agent", "X-Requested-With", "If-Modified-Since", "Cache-Control", "Content-Type", "Authorization" }, methods = ngx.null, origins = { "a.xxx.com", "allowed-domain.test" }, } } bp.plugins:insert { name = "cors", route = { id = route_timeout.id }, config = { origins = { "example.com" }, methods = { "GET" }, headers = { "origin", "type", "accepts" }, exposed_headers = { "x-auth-token" }, max_age = 10, preflight_continue = true } } bp.plugins:insert { name = "cors", route = { id = route_error.id }, config = { origins = { "example.com" }, methods = { "GET" }, headers = { "origin", "type", "accepts" }, exposed_headers = { "x-auth-token" }, max_age = 10, preflight_continue = true } } bp.plugins:insert { name = "cors", route = { id = route_upstream.id }, config = { origins = { "example.com" }, methods = { "GET" }, headers = { "origin", "type", "accepts" }, exposed_headers = { "x-auth-token" }, max_age = 10, preflight_continue = true } } bp.plugins:insert { name = "error-generator-last", route = { id = route_error.id }, config = { access = true, }, } for i, testcase in ipairs(regex_testcases) do local route = bp.routes:insert({ hosts = { "cors-regex-" .. i .. ".test" }, }) bp.plugins:insert { name = "cors", route = { id = route.id }, config = { origins = testcase.origins, } } end assert(helpers.start_kong({ database = strategy, nginx_conf = "spec/fixtures/custom_nginx.template", })) end) lazy_teardown(function() helpers.stop_kong() end) before_each(function() proxy_client = helpers.proxy_client() end) after_each(function() if proxy_client then proxy_client:close() end end) describe("HTTP method: OPTIONS", function() for i, testcase in ipairs(regex_testcases) do local host = "cors-regex-" .. i .. ".test" for origin, accept in sortedpairs(testcase.tests) do it("given " .. origin .. ", " .. inspect(testcase.origins) .. " will " .. (accept and "accept" or "reject"), function() local res = assert(proxy_client:send { method = "OPTIONS", headers = { ["Host"] = host, ["Origin"] = origin, ["Access-Control-Request-Method"] = "GET", } }) assert.res_status(200, res) if accept then assert.equal(CORS_DEFAULT_METHODS, res.headers["Access-Control-Allow-Methods"]) assert.equal(accept == true and origin or accept, res.headers["Access-Control-Allow-Origin"]) assert.is_nil(res.headers["Access-Control-Allow-Headers"]) assert.is_nil(res.headers["Access-Control-Expose-Headers"]) assert.is_nil(res.headers["Access-Control-Allow-Credentials"]) assert.is_nil(res.headers["Access-Control-Max-Age"]) else assert.is_nil(res.headers["Access-Control-Allow-Origin"]) end end) end end it("gives appropriate defaults", function() local res = assert(proxy_client:send { method = "OPTIONS", headers = { ["Host"] = "cors1.com", ["Origin"] = "origin1.com", ["Access-Control-Request-Method"] = "GET", } }) assert.res_status(200, res) assert.equal("0", res.headers["Content-Length"]) assert.equal(CORS_DEFAULT_METHODS, res.headers["Access-Control-Allow-Methods"]) assert.equal("*", res.headers["Access-Control-Allow-Origin"]) assert.is_nil(res.headers["Access-Control-Allow-Headers"]) assert.is_nil(res.headers["Access-Control-Expose-Headers"]) assert.is_nil(res.headers["Access-Control-Allow-Credentials"]) assert.is_nil(res.headers["Access-Control-Max-Age"]) assert.is_nil(res.headers["Vary"]) end) it("gives * wildcard when config.origins is empty", function() -- this test covers a regression introduced in 0.10.1, where -- the 'multiple_origins' migration would always insert a table -- (that might be empty) in the 'config.origins' field, and the -- * wildcard would only been sent when said table was **nil**, -- and not necessarily empty. local res = assert(proxy_client:send { method = "OPTIONS", headers = { ["Host"] = "cors-empty-origins.com", ["Origin"] = "empty-origin.com", ["Access-Control-Request-Method"] = "GET", } }) assert.res_status(200, res) assert.equal("0", res.headers["Content-Length"]) assert.equal(CORS_DEFAULT_METHODS, res.headers["Access-Control-Allow-Methods"]) assert.equal("*", res.headers["Access-Control-Allow-Origin"]) assert.is_nil(res.headers["Access-Control-Allow-Headers"]) assert.is_nil(res.headers["Access-Control-Expose-Headers"]) assert.is_nil(res.headers["Access-Control-Allow-Credentials"]) assert.is_nil(res.headers["Access-Control-Max-Age"]) assert.is_nil(res.headers["Vary"]) end) it("gives appropriate defaults when origin is explicitly set to *", function() local res = assert(proxy_client:send { method = "OPTIONS", headers = { ["Host"] = "cors5.com", ["Origin"] = "origin5.com", ["Access-Control-Request-Method"] = "GET", } }) assert.res_status(200, res) assert.equal("0", res.headers["Content-Length"]) assert.equal(CORS_DEFAULT_METHODS, res.headers["Access-Control-Allow-Methods"]) assert.equal("origin5.com", res.headers["Access-Control-Allow-Origin"]) assert.equal("true", res.headers["Access-Control-Allow-Credentials"]) assert.equal("Origin", res.headers["Vary"]) assert.is_nil(res.headers["Access-Control-Allow-Headers"]) assert.is_nil(res.headers["Access-Control-Expose-Headers"]) assert.is_nil(res.headers["Access-Control-Max-Age"]) end) it("accepts config options", function() local res = assert(proxy_client:send { method = "OPTIONS", headers = { ["Host"] = "cors2.com", ["Origin"] = "origin5.com", ["Access-Control-Request-Method"] = "GET", } }) assert.res_status(200, res) assert.equal("0", res.headers["Content-Length"]) assert.equal("GET", res.headers["Access-Control-Allow-Methods"]) assert.equal("example.com", res.headers["Access-Control-Allow-Origin"]) assert.equal("23", res.headers["Access-Control-Max-Age"]) assert.equal("true", res.headers["Access-Control-Allow-Credentials"]) assert.equal("origin,type,accepts", res.headers["Access-Control-Allow-Headers"]) assert.equal("Origin", res.headers["Vary"]) assert.is_nil(res.headers["Access-Control-Expose-Headers"]) end) it("preflight_continue enabled", function() local res = assert(proxy_client:send { method = "OPTIONS", path = "/status/201", headers = { ["Host"] = "cors3.com" } }) local body = assert.res_status(201, res) local json = cjson.decode(body) assert.equal(201, json.code) end) it("replies with request-headers if present in request", function() local res = assert(proxy_client:send { method = "OPTIONS", headers = { ["Host"] = "cors5.com", ["Origin"] = "origin5.com", ["Access-Control-Request-Headers"] = "origin,accepts", ["Access-Control-Request-Method"] = "GET", } }) assert.res_status(200, res) assert.equal("0", res.headers["Content-Length"]) assert.equal("origin,accepts", res.headers["Access-Control-Allow-Headers"]) end) it("properly validates flat strings", function() -- Legitimate origins local res = assert(proxy_client:send { method = "OPTIONS", headers = { ["Host"] = "cors10.com", ["Origin"] = "http://my-site.com" } }) assert.res_status(200, res) assert.equal("http://my-site.com", res.headers["Access-Control-Allow-Origin"]) -- Illegitimate origins res = assert(proxy_client:send { method = "OPTIONS", headers = { ["Host"] = "cors10.com", ["Origin"] = "http://bad-guys.com" } }) assert.res_status(200, res) assert.is_nil(res.headers["Access-Control-Allow-Origin"]) -- Tricky illegitimate origins res = assert(proxy_client:send { method = "OPTIONS", headers = { ["Host"] = "cors10.com", ["Origin"] = "http://my-site.com.bad-guys.com" } }) assert.res_status(200, res) assert.is_nil(res.headers["Access-Control-Allow-Origin"]) end) end) describe("HTTP method: others", function() it("gives appropriate defaults", function() local res = assert(proxy_client:send { method = "GET", headers = { ["Host"] = "cors1.com" } }) assert.res_status(200, res) assert.equal("*", res.headers["Access-Control-Allow-Origin"]) assert.is_nil(res.headers["Access-Control-Allow-Methods"]) assert.is_nil(res.headers["Access-Control-Allow-Headers"]) assert.is_nil(res.headers["Access-Control-Expose-Headers"]) assert.is_nil(res.headers["Access-Control-Allow-Credentials"]) assert.is_nil(res.headers["Access-Control-Max-Age"]) assert.is_nil(res.headers["Vary"]) end) it("proxies a non-preflight OPTIONS request", function() local res = assert(proxy_client:send { method = "OPTIONS", path = "/anything", headers = { ["Host"] = "cors1.com" } }) local body = assert.res_status(200, res) local json = cjson.decode(body) assert.equal("OPTIONS", json.vars.request_method) assert.equal("*", res.headers["Access-Control-Allow-Origin"]) assert.is_nil(res.headers["Access-Control-Allow-Methods"]) assert.is_nil(res.headers["Access-Control-Allow-Headers"]) assert.is_nil(res.headers["Access-Control-Expose-Headers"]) assert.is_nil(res.headers["Access-Control-Allow-Credentials"]) assert.is_nil(res.headers["Access-Control-Max-Age"]) assert.is_nil(res.headers["Vary"]) end) it("accepts config options", function() local res = assert(proxy_client:send { method = "GET", headers = { ["Host"] = "cors2.com" } }) assert.res_status(200, res) assert.equal("example.com", res.headers["Access-Control-Allow-Origin"]) assert.equal("x-auth-token", res.headers["Access-Control-Expose-Headers"]) assert.equal("true", res.headers["Access-Control-Allow-Credentials"]) assert.equal("Origin", res.headers["Vary"]) assert.is_nil(res.headers["Access-Control-Allow-Methods"]) assert.is_nil(res.headers["Access-Control-Allow-Headers"]) assert.is_nil(res.headers["Access-Control-Max-Age"]) end) it("works even when upstream timeouts", function() local res = assert(proxy_client:send { method = "GET", headers = { ["Host"] = "cors-timeout.com" } }) assert.res_status(502, res) assert.equal("example.com", res.headers["Access-Control-Allow-Origin"]) assert.equal("x-auth-token", res.headers["Access-Control-Expose-Headers"]) assert.equal("Origin", res.headers["Vary"]) assert.is_nil(res.headers["Access-Control-Allow-Credentials"]) assert.is_nil(res.headers["Access-Control-Allow-Methods"]) assert.is_nil(res.headers["Access-Control-Allow-Headers"]) assert.is_nil(res.headers["Access-Control-Max-Age"]) end) it("works even when a runtime error occurs", function() local res = assert(proxy_client:send { method = "GET", headers = { ["Host"] = "cors-error.com" } }) assert.res_status(500, res) assert.equal("example.com", res.headers["Access-Control-Allow-Origin"]) assert.equal("x-auth-token", res.headers["Access-Control-Expose-Headers"]) assert.equal("Origin", res.headers["Vary"]) assert.is_nil(res.headers["Access-Control-Allow-Credentials"]) assert.is_nil(res.headers["Access-Control-Allow-Methods"]) assert.is_nil(res.headers["Access-Control-Allow-Headers"]) assert.is_nil(res.headers["Access-Control-Max-Age"]) end) it("works with 404 responses", function() local res = assert(proxy_client:send { method = "GET", path = "/asdasdasd", headers = { ["Host"] = "cors1.com" } }) assert.res_status(404, res) assert.equal("*", res.headers["Access-Control-Allow-Origin"]) assert.is_nil(res.headers["Access-Control-Allow-Methods"]) assert.is_nil(res.headers["Access-Control-Allow-Headers"]) assert.is_nil(res.headers["Access-Control-Expose-Headers"]) assert.is_nil(res.headers["Access-Control-Allow-Credentials"]) assert.is_nil(res.headers["Access-Control-Max-Age"]) assert.is_nil(res.headers["Vary"]) end) it("works with 40x responses returned by another plugin", function() local res = assert(proxy_client:send { method = "GET", headers = { ["Host"] = "cors4.com" } }) assert.res_status(401, res) assert.equal("*", res.headers["Access-Control-Allow-Origin"]) assert.is_nil(res.headers["Access-Control-Allow-Methods"]) assert.is_nil(res.headers["Access-Control-Allow-Headers"]) assert.is_nil(res.headers["Access-Control-Expose-Headers"]) assert.is_nil(res.headers["Access-Control-Allow-Credentials"]) assert.is_nil(res.headers["Access-Control-Max-Age"]) assert.is_nil(res.headers["Vary"]) end) it("sets CORS orgin based on origin host", function() local res = assert(proxy_client:send { method = "GET", headers = { ["Host"] = "cors6.com", ["Origin"] = "example.com" } }) assert.res_status(200, res) assert.equal("example.com", res.headers["Access-Control-Allow-Origin"]) assert.equal("Origin", res.headers["Vary"]) local domains = { ["example.com"] = true, ["www.example.com"] = true, ["example-foo.com"] = true, ["www.example-foo.com"] = true, ["www.example-fo0.com"] = false, } for domain in pairs(domains) do local res = assert(proxy_client:send { method = "GET", headers = { ["Host"] = "cors9.com", ["Origin"] = domain } }) assert.res_status(200, res) assert.equal(domains[domain] and domain or nil, res.headers["Access-Control-Allow-Origin"]) assert.equal("Origin", res.headers["Vary"]) end end) it("sets Vary and don't override existing Vary header", function() local res = assert(proxy_client:send { method = "GET", path = "/response-headers?vary=Accept-Encoding", headers = { ["Host"] = "cors-upstream.com", ["Origin"] = "example.com", } }) assert.res_status(200, res) assert.same({"Accept-Encoding", "Origin"}, res.headers["Vary"]) end) it("does not automatically parse the host", function() local res = assert(proxy_client:send { method = "GET", headers = { ["Host"] = "cors6.com", ["Origin"] = "http://example.com" } }) assert.res_status(200, res) assert.is_nil(res.headers["Access-Control-Allow-Origin"]) -- With a different transport too local res = assert(proxy_client:send { method = "GET", headers = { ["Host"] = "cors6.com", ["Origin"] = "https://example.com" } }) assert.res_status(200, res) assert.is_nil(res.headers["Access-Control-Allow-Origin"]) end) it("validates scheme and port", function() local res = assert(proxy_client:send { method = "GET", headers = { ["Host"] = "cors11.com", ["Origin"] = "http://my-site.com" } }) assert.res_status(200, res) assert.equals("http://my-site.com", res.headers["Access-Control-Allow-Origin"]) local res = assert(proxy_client:send { method = "GET", headers = { ["Host"] = "cors11.com", ["Origin"] = "http://my-site.com:80" } }) assert.res_status(200, res) assert.equals("http://my-site.com", res.headers["Access-Control-Allow-Origin"]) local res = assert(proxy_client:send { method = "GET", headers = { ["Host"] = "cors11.com", ["Origin"] = "http://my-site.com:8000" } }) assert.res_status(200, res) assert.is_nil(res.headers["Access-Control-Allow-Origin"]) res = assert(proxy_client:send { method = "GET", headers = { ["Host"] = "cors11.com", ["Origin"] = "https://my-site.com" } }) assert.res_status(200, res) assert.is_nil(res.headers["Access-Control-Allow-Origin"]) local res = assert(proxy_client:send { method = "GET", headers = { ["Host"] = "cors11.com", ["Origin"] = "https://my-other-site.com:9000" } }) assert.res_status(200, res) assert.equals("https://my-other-site.com:9000", res.headers["Access-Control-Allow-Origin"]) local res = assert(proxy_client:send { method = "GET", headers = { ["Host"] = "cors11.com", ["Origin"] = "https://my-other-site.com:9001" } }) assert.res_status(200, res) assert.is_nil(res.headers["Access-Control-Allow-Origin"]) end) it("does not sets CORS origin if origin host is not in origin_domains list", function() local res = assert(proxy_client:send { method = "GET", headers = { ["Host"] = "cors6.com", ["Origin"] = "http://www.example.net" } }) assert.res_status(200, res) assert.is_nil(res.headers["Access-Control-Allow-Origin"]) end) it("responds with the requested Origin when config.credentials=true", function() local res = assert(proxy_client:send { method = "GET", headers = { ["Host"] = "cors5.com", ["Origin"] = "http://www.example.net" } }) assert.res_status(200, res) assert.equals("http://www.example.net", res.headers["Access-Control-Allow-Origin"]) assert.equals("true", res.headers["Access-Control-Allow-Credentials"]) assert.equal("Origin", res.headers["Vary"]) end) it("responds with the requested Origin (including port) when config.credentials=true", function() local res = assert(proxy_client:send { method = "GET", headers = { ["Host"] = "cors5.com", ["Origin"] = "http://www.example.net:3000" } }) assert.res_status(200, res) assert.equals("http://www.example.net:3000", res.headers["Access-Control-Allow-Origin"]) assert.equals("true", res.headers["Access-Control-Allow-Credentials"]) assert.equal("Origin", res.headers["Vary"]) end) it("responds with * when config.credentials=false", function() local res = assert(proxy_client:send { method = "GET", headers = { ["Host"] = "cors7.com", ["Origin"] = "http://www.example.net" } }) assert.res_status(200, res) assert.equals("*", res.headers["Access-Control-Allow-Origin"]) assert.is_nil(res.headers["Access-Control-Allow-Credentials"]) assert.is_nil(res.headers["Vary"]) end) it("removes upstream ACAO header when no match is found", function() local res = proxy_client:get("/response-headers", { query = ngx.encode_args({ ["Response-Header"] = "is-added", ["Access-Control-Allow-Origin"] = "*", }), headers = { ["Host"] = "cors12.com", ["Origin"] = "allowed-domain.test", } }) local body = assert.res_status(200, res) local json = assert(cjson.decode(body)) assert.equal("is-added", res.headers["Response-Header"]) assert.equal("allowed-domain.test", res.headers["Access-Control-Allow-Origin"]) assert.equal("true", res.headers["Access-Control-Allow-Credentials"]) assert.equal("Origin", res.headers["Vary"]) assert.equal("allowed-domain.test", json.headers["origin"]) local res = proxy_client:get("/response-headers", { query = ngx.encode_args({ ["Response-Header"] = "is-added", ["Access-Control-Allow-Origin"] = "*", }), headers = { ["Host"] = "cors12.com", ["Origin"] = "disallowed-domain.test", } }) local body = assert.res_status(200, res) local json = assert(cjson.decode(body)) assert.equal("is-added", res.headers["Response-Header"]) assert.equal(nil, res.headers["Access-Control-Allow-Origin"]) assert.equal("true", res.headers["Access-Control-Allow-Credentials"]) assert.equal("disallowed-domain.test", json.headers["origin"]) end) end) end) end