Skip to content

Commit

Permalink
feat(cors) match configured origins as a regular expression
Browse files Browse the repository at this point in the history
Use the ngx.re API to match configured origins as regular expressions
against the client Origin header. In cases where a single origin is
configured for the plugin, set the ACAO header if the configuration
contains only non-PCRE metacharacters; otherwise, treat the single
configured origin as a though multiple origins were configured, by
iterating through the array and setting the ACAO header based on the
client Origin.
  • Loading branch information
p0pr0ck5 committed May 15, 2017
1 parent 9f5a58e commit 900ec4e
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 8 deletions.
23 changes: 16 additions & 7 deletions kong/plugins/cors/handler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,30 @@ local function configure_origin(ngx, conf)
if n_origins == 1 then
if conf.origins[1] == "*" then
ngx.ctx.cors_allow_all = true
ngx.header["Access-Control-Allow-Origin"] = "*"
return
end

ngx.header["Vary"] = "Origin"

else
ngx.header["Vary"] = "Origin"
-- if this doesnt look like a regex, set the ACAO header directly
-- otherwise, we'll fall through to an iterative search and
-- set the ACAO header based on the client Origin
local from, _, err = re_find(conf.origins[1], "^[A-Za-z0-9.:/-]+$", "jo")
if err then
ngx.log(ngx.ERR, "[cors] could not inspect origin for type: ", err)
end

ngx.header["Access-Control-Allow-Origin"] = conf.origins[1]
return
if from then
ngx.header["Access-Control-Allow-Origin"] = conf.origins[1]
return
end
end

local req_origin = ngx.var.http_origin
if req_origin then
for _, domain in ipairs(conf.origins) do
local from, _, err = re_find(req_origin,
[[\Q]] .. domain .. [[\E$]],
"jo")
local from, _, err = re_find(req_origin, domain, "jo")
if err then
ngx.log(ngx.ERR, "[cors] could not search for domain: ", err)
end
Expand Down
16 changes: 15 additions & 1 deletion kong/plugins/cors/schema.lua
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
local re_match = ngx.re.match

local check_regex = function(value)
if value and (#value > 1 or value[1] ~= "*") then
for _, origin in ipairs(value) do
local _, err = re_match("just a string to test", origin)
if err then
return false, "origin '" .. origin .. "' is not a valid regex"
end
end
end
return true
end

return {
no_consumer = true,
fields = {
origins = { type = "array" },
origins = { type = "array", func = check_regex },
headers = { type = "array" },
exposed_headers = { type = "array" },
methods = { type = "array", enum = { "HEAD", "GET", "POST", "PUT", "PATCH", "DELETE" } },
Expand Down
35 changes: 35 additions & 0 deletions spec/03-plugins/14-cors/01-access_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ describe("Plugin: cors (access)", function()
hosts = { "cors-empty-origins.com" },
upstream_url = "http://mockbin.com"
})
local api9 = assert(helpers.dao.apis:insert {
name = "api-9",
hosts = { "cors9.com" },
upstream_url = "http://mockbin.com"
})


assert(helpers.dao.plugins:insert {
name = "cors",
Expand Down Expand Up @@ -125,6 +131,14 @@ describe("Plugin: cors (access)", function()
}
})

assert(helpers.dao.plugins:insert {
name = "cors",
api_id = api9.id,
config = {
origins = { [[.*\.?example(?:-foo)?.com]] },
}
})

assert(helpers.start_kong())
client = helpers.proxy_client()
end)
Expand Down Expand Up @@ -309,6 +323,27 @@ describe("Plugin: cors (access)", function()
})
assert.res_status(200, res)
assert.equal("http://www.example.com", res.headers["Access-Control-Allow-Origin"])

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, v in pairs(domains) do
local res = assert(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"])
end
end)

it("does not sets CORS orgin if origin host is not in origin_domains list", function()
Expand Down
35 changes: 35 additions & 0 deletions spec/03-plugins/14-cors/02-schema_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
local validate_entity = require("kong.dao.schemas_validation").validate_entity
local cors_schema = require "kong.plugins.cors.schema"

describe("cors schema", function()
it("validates '*'", function()
local ok, err = validate_entity({ origins = { "*" } }, cors_schema)

assert.True(ok)
assert.is_nil(err)
end)

it("validates what looks like a domain", function()
local ok, err = validate_entity({ origins = { "example.com" } }, cors_schema)

assert.True(ok)
assert.is_nil(err)
end)

it("validates what looks like a regex", function()
local ok, err = validate_entity({ origins = { [[.*\.example(?:-foo)?\.com]] } }, cors_schema)

assert.True(ok)
assert.is_nil(err)
end)

describe("errors", function()
it("with invalid regex in origins", function()
local mock_origins = { [[.*.example.com]], [[invalid_**regex]] }
local ok, err = validate_entity({ origins = mock_origins }, cors_schema)

assert.False(ok)
assert.equals("origin '" .. mock_origins[2] .. "' is not a valid regex", err.origins)
end)
end)
end)

0 comments on commit 900ec4e

Please sign in to comment.