Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(key-auth) optionally search the request body for credentials #2493

Merged
merged 1 commit into from May 4, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 21 additions & 0 deletions kong/plugins/key-auth/handler.lua
Expand Up @@ -2,13 +2,17 @@ local cache = require "kong.tools.database_cache"
local responses = require "kong.tools.responses"
local constants = require "kong.constants"
local singletons = require "kong.singletons"
local public_tools = require "kong.tools.public"
local BasePlugin = require "kong.plugins.base_plugin"

local ngx_set_header = ngx.req.set_header
local ngx_get_headers = ngx.req.get_headers
local set_uri_args = ngx.req.set_uri_args
local get_uri_args = ngx.req.get_uri_args
local clear_header = ngx.req.clear_header
local ngx_req_read_body = ngx.req.read_body
local ngx_req_set_body_data = ngx.req.set_body_data
local ngx_encode_args = ngx.encode_args
local type = type

local _realm = 'Key realm="'.._KONG._NAME..'"'
Expand Down Expand Up @@ -66,6 +70,13 @@ local function do_authentication(conf)
local key
local headers = ngx_get_headers()
local uri_args = get_uri_args()
local body_data

-- read in the body if we want to examine POST args
if conf.key_in_body then
ngx_req_read_body()
body_data = public_tools.get_post_args()
end

-- search in headers & querystring
for i = 1, #conf.key_names do
Expand All @@ -76,12 +87,22 @@ local function do_authentication(conf)
v = uri_args[name]
end

-- search the body, if we asked to
if not v and conf.key_in_body then
v = body_data[name]
end

if type(v) == "string" then
key = v
if conf.hide_credentials then
uri_args[name] = nil
set_uri_args(uri_args)
clear_header(name)

if conf.key_in_body then
body_data[name] = nil
ngx_req_set_body_data(ngx_encode_args(body_data))
end
end
break
elseif type(v) == "table" then
Expand Down
4 changes: 4 additions & 0 deletions kong/plugins/key-auth/schema.lua
Expand Up @@ -48,5 +48,9 @@ return {
default = "",
func = check_user,
},
key_in_body = {
type = "boolean",
default = "false",
},
}
}
2 changes: 1 addition & 1 deletion spec/01-unit/07-entities_schemas_spec.lua
Expand Up @@ -637,7 +637,7 @@ describe("Entities Schemas", function()
-- Insert key-auth, whose config has some default values that should be set
local plugin = {name = "key-auth", api_id = "stub"}
local valid = validate_entity(plugin, plugins_schema, {dao = dao_stub})
assert.same({key_names = {"apikey"}, hide_credentials = false, anonymous = ""}, plugin.config)
assert.same({key_names = {"apikey"}, hide_credentials = false, anonymous = "", key_in_body = false}, plugin.config)
assert.is_true(valid)
end)
it("should be valid if no value is specified for a subfield and if the config schema has default as empty array", function()
Expand Down
4 changes: 2 additions & 2 deletions spec/02-integration/02-dao/04-constraints_spec.lua
Expand Up @@ -45,7 +45,7 @@ helpers.for_each_dao(function(kong_config)
assert.falsy(err)
assert.is_table(plugin)
assert.equal(api_fixture.id, plugin.api_id)
assert.same({hide_credentials = false, key_names = {"apikey"}, anonymous = ""}, plugin.config)
assert.same({hide_credentials = false, key_names = {"apikey"}, anonymous = "", key_in_body = false,}, plugin.config)
end)
it("insert a valid plugin bis", function()
plugin_fixture.api_id = api_fixture.id
Expand All @@ -55,7 +55,7 @@ helpers.for_each_dao(function(kong_config)
assert.falsy(err)
assert.is_table(plugin)
assert.equal(api_fixture.id, plugin.api_id)
assert.same({hide_credentials = false, key_names = {"api-key"}, anonymous = ""}, plugin.config)
assert.same({hide_credentials = false, key_names = {"api-key"}, anonymous = "", key_in_body = false}, plugin.config)
end)
describe("unique per API/Consumer", function()
it("API/Plugin", function()
Expand Down
189 changes: 165 additions & 24 deletions spec/03-plugins/10-key-auth/02-access_spec.lua
Expand Up @@ -67,6 +67,33 @@ describe("Plugin: key-auth (access)", function()
}
})

local api5 = assert(helpers.dao.apis:insert {
name = "api-5",
hosts = { "key-auth5.com" },
upstream_url = "http://mockbin.com"
})
assert(helpers.dao.plugins:insert {
name = "key-auth",
api_id = api5.id,
config = {
key_in_body = true,
}
})

local api6 = assert(helpers.dao.apis:insert {
name = "api-6",
hosts = { "key-auth6.com" },
upstream_url = "http://mockbin.com"
})
assert(helpers.dao.plugins:insert {
name = "key-auth",
api_id = api6.id,
config = {
key_in_body = true,
hide_credentials = true,
}
})

assert(helpers.start_kong())
client = helpers.proxy_client()
end)
Expand Down Expand Up @@ -138,6 +165,67 @@ describe("Plugin: key-auth (access)", function()
end)
end)

describe("key in request body", function()
it("authenticates valid credentials", function()
local res = assert(client:send {
path = "/request",
headers = {
["Host"] = "key-auth5.com",
["Content-Type"] = "application/www-form-urlencoded",
},
body = {
apikey = "kong",
}
})
assert.res_status(200, res)
end)
it("returns 403 Forbidden on invalid key", function()
local res = assert(client:send {
path = "/status/200",
headers = {
["Host"] = "key-auth5.com",
["Content-Type"] = "application/www-form-urlencoded",
},
body = {
apikey = "123",
}
})
local body = assert.res_status(403, res)
local json = cjson.decode(body)
assert.same({ message = "Invalid authentication credentials" }, json)
end)
it("handles duplicated key", function()
local res = assert(client:send {
path = "/status/200",
headers = {
["Host"] = "key-auth5.com",
["Content-Type"] = "application/www-form-urlencoded",
},
body = {
apikey = { "kong", "kong" },
},
})
local body = assert.res_status(401, res)
local json = cjson.decode(body)
assert.same({ message = "Duplicate API key found" }, json)
end)
it("only handles application/www-form-urlencoded bodies", function()
local res = assert(client:send {
path = "/status/200",
headers = {
["Host"] = "key-auth5.com",
["Content-Type"] = "application/json",
},
body = {
apikey = { "kong", "kong" },
},
})
local body = assert.res_status(401, res)
local json = cjson.decode(body)
assert.same({ message = "No API key found in headers or querystring" }, json)
end)
end)

describe("key in headers", function()
it("authenticates valid credentials", function()
local res = assert(client:send {
Expand Down Expand Up @@ -183,32 +271,85 @@ describe("Plugin: key-auth (access)", function()
end)

describe("config.hide_credentials", function()
it("false sends key to upstream", function()
local res = assert(client:send {
method = "GET",
path = "/request",
headers = {
["Host"] = "key-auth1.com",
["apikey"] = "kong"

local harness = {
queryString = {
{
headers = { Host = "key-auth1.com" },
path = "/request?apikey=kong",
method = "GET",
},
{
headers = { Host = "key-auth2.com" },
path = "/request?apikey=kong",
method = "GET",
}
})
local body = assert.res_status(200, res)
local json = cjson.decode(body)
assert.equal("kong", json.headers.apikey)
end)
it("true doesn't send key to upstream", function()
local res = assert(client:send {
method = "GET",
path = "/request",
headers = {
["Host"] = "key-auth2.com",
["apikey"] = "kong"
},
headers = {
{
headers = { Host = "key-auth1.com", ["apikey"] = "kong" },
path = "/request",
method = "GET",
},
{
headers = { Host = "key-auth2.com", ["apikey"] = "kong" },
path = "/request",
method = "GET",
}
})
local body = assert.res_status(200, res)
local json = cjson.decode(body)
assert.is_nil(json.headers.apikey)
end)
},
postData = {
{
headers = { ["Host"] = "key-auth5.com", ["Content-Type"] = "application/www-form-urlencoded" },
body = { apikey = "kong" },
method = "POST",
path = "/request",
},
{
headers = { ["Host"] = "key-auth6.com", ["Content-Type"] = "application/www-form-urlencoded" },
body = { apikey = "kong" },
method = "POST",
path = "/request",
}
}
}

for type, _ in pairs(harness) do
describe(type, function()
it("false sends key to upstream", function()
local res = assert(client:send(harness[type][1]))
local body = assert.res_status(200, res)
local json = cjson.decode(body)

-- small workaround for how mockbin sends body data
local field
if type == "postData" then
local t = json[type].text:sub(8)
field = { apikey = t ~= "" and t or nil }

else
field = json[type]
end

assert.equal("kong", field.apikey)
end)
it("true doesn't send key to upstream", function()
local res = assert(client:send(harness[type][2]))
local body = assert.res_status(200, res)
local json = cjson.decode(body)

local field
if type == "postData" then
local t = json[type].text:sub(8)
field = { apikey = t ~= "" and t or nil }

else
field = json[type]
end

assert.is_nil(field.apikey)
end)
end)
end
end)

describe("config.anonymous", function()
Expand Down