Skip to content

Commit

Permalink
fix(keyauth) return 401 when missing API key
Browse files Browse the repository at this point in the history
fix Kong#354


Former-commit-id: b0bf47985f006eb4ad1a94572c6231f47fc26b66
  • Loading branch information
thibaultcha committed Jul 1, 2015
1 parent 9b69cf5 commit cb2e4ff
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 49 deletions.
19 changes: 12 additions & 7 deletions kong/plugins/keyauth/access.lua
Original file line number Diff line number Diff line change
Expand Up @@ -118,14 +118,13 @@ local retrieve_credentials = {
function _M.execute(conf)
if not conf or skip_authentication(ngx.req.get_headers()) then return end

local credential
local key, key_found, credential
for _, v in ipairs({ constants.AUTHENTICATION.QUERY, constants.AUTHENTICATION.HEADER }) do
local key = retrieve_credentials[v](ngx.req, conf)

-- Make sure we are not sending an empty table to find_by_keys
key = retrieve_credentials[v](ngx.req, conf)
if key then
key_found = true
credential = cache.get_or_set(cache.keyauth_credential_key(key), function()
local credentials, err = dao.keyauth_credentials:find_by_keys { key = key }
local credentials, err = dao.keyauth_credentials:find_by_keys {key = key}
local result
if err then
return responses.send_HTTP_INTERNAL_SERVER_ERROR(err)
Expand All @@ -134,19 +133,25 @@ function _M.execute(conf)
end
return result
end)
if credential then break end
end
end

if credential then break end
-- No key found in the request's headers or parameters
if not key_found then
ngx.ctx.stop_phases = true
return responses.send_HTTP_UNAUTHORIZED("No API key found in headers or querystring")
end

-- No key found in the DB, this credential is invalid
if not credential then
ngx.ctx.stop_phases = true -- interrupt other phases of this request
return responses.send_HTTP_FORBIDDEN("Invalid authentication credentials")
end

-- Retrieve consumer
local consumer = cache.get_or_set(cache.consumer_key(credential.consumer_id), function()
local result, err = dao.consumers:find_by_primary_key({ id = credential.consumer_id })
local result, err = dao.consumers:find_by_primary_key({id = credential.consumer_id})
if err then
return responses.send_HTTP_INTERNAL_SERVER_ERROR(err)
end
Expand Down
8 changes: 6 additions & 2 deletions kong/tools/responses.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ local _M = {
HTTP_CREATED = 201,
HTTP_NO_CONTENT = 204,
HTTP_BAD_REQUEST = 400,
HTTP_UNAUTHORIZED = 401,
HTTP_FORBIDDEN = 403,
HTTP_NOT_FOUND = 404,
HTTP_METHOD_NOT_ALLOWED = 405,
Expand All @@ -23,12 +24,15 @@ local _M = {
-- Define some rules that will ALWAYS be applied to some status codes.
-- Ex: 204 must not have content, but if 404 has no content then "Not found" will be set.
local response_default_content = {
[_M.status_codes.HTTP_NOT_FOUND] = function(content)
return content and content or "Not found"
[_M.status_codes.HTTP_UNAUTHORIZED] = function(content)
return content or "Unauthorized"
end,
[_M.status_codes.HTTP_NO_CONTENT] = function(content)
return nil
end,
[_M.status_codes.HTTP_NOT_FOUND] = function(content)
return content or "Not found"
end,
[_M.status_codes.HTTP_INTERNAL_SERVER_ERROR] = function(content)
return "An unexpected error occurred"
end,
Expand Down
87 changes: 47 additions & 40 deletions spec/plugins/keyauth/access_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@ describe("Authentication Plugin", function()
spec_helper.prepare_db()
spec_helper.insert_fixtures {
api = {
{ name = "tests auth 1", public_dns = "keyauth1.com", target_url = "http://mockbin.com" },
{ name = "tests auth 2", public_dns = "keyauth2.com", target_url = "http://mockbin.com" }
{name = "tests auth 1", public_dns = "keyauth1.com", target_url = "http://mockbin.com"},
{name = "tests auth 2", public_dns = "keyauth2.com", target_url = "http://mockbin.com"}
},
consumer = {
{ username = "auth_tests_consumer" }
{username = "auth_tests_consumer"}
},
plugin_configuration = {
{ name = "keyauth", value = { key_names = { "apikey" }}, __api = 1 },
{ name = "keyauth", value = {key_names = {"apikey"}, hide_credentials = true}, __api = 2 }
{name = "keyauth", value = {key_names = {"apikey"}}, __api = 1},
{name = "keyauth", value = {key_names = {"apikey"}, hide_credentials = true}, __api = 2}
},
keyauth_credential = {
{ key = "apikey123", __consumer = 1 }
{key = "apikey123", __consumer = 1}
}
}

Expand All @@ -38,119 +38,126 @@ describe("Authentication Plugin", function()
it("should return invalid credentials when the credential value is wrong", function()
local response, status = http_client.get(STUB_GET_URL, {apikey = "asd"}, {host = "keyauth1.com"})
local body = cjson.decode(response)
assert.are.equal(403, status)
assert.are.equal("Invalid authentication credentials", body.message)
assert.equal(403, status)
assert.equal("Invalid authentication credentials", body.message)
end)

it("should return invalid credentials when the credential parameter name is wrong in GET", function()
it("should reply 401 when the credential parameter is missing", function()
local response, status = http_client.get(STUB_GET_URL, {apikey123 = "apikey123"}, {host = "keyauth1.com"})
local body = cjson.decode(response)
assert.are.equal(403, status)
assert.are.equal("Invalid authentication credentials", body.message)
assert.equal(401, status)
assert.equal("No API key found in headers or querystring", body.message)
end)

it("should return invalid credentials when the credential parameter name is wrong in POST", function()
it("should reply 401 when the credential parameter name is wrong in GET", function()
local response, status = http_client.get(STUB_GET_URL, {apikey123 = "apikey123"}, {host = "keyauth1.com"})
local body = cjson.decode(response)
assert.equal(401, status)
assert.equal("No API key found in headers or querystring", body.message)
end)

it("should reply 401 when the credential parameter name is wrong in POST", function()
local response, status = http_client.post(STUB_POST_URL, {apikey123 = "apikey123"}, {host = "keyauth1.com"})
local body = cjson.decode(response)
assert.are.equal(403, status)
assert.are.equal("Invalid authentication credentials", body.message)
assert.equal(401, status)
assert.equal("No API key found in headers or querystring", body.message)
end)

it("should pass with GET", function()
local response, status = http_client.get(STUB_GET_URL, {apikey = "apikey123"}, {host = "keyauth1.com"})
assert.are.equal(200, status)
assert.equal(200, status)
local parsed_response = cjson.decode(response)
assert.are.equal("apikey123", parsed_response.queryString.apikey)
assert.equal("apikey123", parsed_response.queryString.apikey)
end)

it("should pass with POST", function()
local response, status = http_client.post(STUB_POST_URL, {apikey = "apikey123"}, {host = "keyauth1.com"})
assert.are.equal(200, status)
assert.equal(200, status)
local parsed_response = cjson.decode(response)
assert.are.equal("apikey123", parsed_response.postData.params.apikey)
assert.equal("apikey123", parsed_response.postData.params.apikey)
end)

it("should return invalid credentials when the credential parameter name is wrong in GET header", function()
it("should reply 401 when the credential parameter name is wrong in GET header", function()
local response, status = http_client.get(STUB_GET_URL, {}, {host = "keyauth1.com", apikey123 = "apikey123"})
local body = cjson.decode(response)
assert.are.equal(403, status)
assert.are.equal("Invalid authentication credentials", body.message)
assert.equal(401, status)
assert.equal("No API key found in headers or querystring", body.message)
end)

it("should return invalid credentials when the credential parameter name is wrong in POST header", function()
it("should reply 401 when the credential parameter name is wrong in POST header", function()
local response, status = http_client.post(STUB_POST_URL, {}, {host = "keyauth1.com", apikey123 = "apikey123"})
local body = cjson.decode(response)
assert.are.equal(403, status)
assert.are.equal("Invalid authentication credentials", body.message)
assert.equal(401, status)
assert.equal("No API key found in headers or querystring", body.message)
end)

it("should set right headers", function()
local response, status = http_client.post(STUB_POST_URL, {apikey = "apikey123"}, {host = "keyauth1.com"})
assert.are.equal(200, status)
assert.equal(200, status)
local parsed_response = cjson.decode(response)
assert.truthy(parsed_response.headers["x-consumer-id"])
assert.truthy(parsed_response.headers["x-consumer-username"])
assert.are.equal("auth_tests_consumer", parsed_response.headers["x-consumer-username"])
assert.equal("auth_tests_consumer", parsed_response.headers["x-consumer-username"])
end)

describe("Hide credentials", function()

it("should pass with POST and hide credentials", function()
local response, status = http_client.post(STUB_POST_URL, {apikey = "apikey123", wot = "wat"}, {host = "keyauth2.com"})
assert.are.equal(200, status)
assert.equal(200, status)
local parsed_response = cjson.decode(response)
assert.falsy(parsed_response.postData.params.apikey)
assert.are.equal("wat", parsed_response.postData.params.wot)
assert.equal("wat", parsed_response.postData.params.wot)
end)

it("should pass with POST multipart and hide credentials", function()
local response, status = http_client.post_multipart(STUB_POST_URL, {apikey = "apikey123", wot = "wat"}, {host = "keyauth2.com"})
assert.are.equal(200, status)
assert.equal(200, status)
local parsed_response = cjson.decode(response)
assert.falsy(parsed_response.postData.params.apikey)
assert.are.equal("wat", parsed_response.postData.params.wot)
assert.equal("wat", parsed_response.postData.params.wot)
end)

it("should pass with GET and hide credentials", function()
local response, status = http_client.get(STUB_GET_URL, {}, {host = "keyauth2.com", apikey = "apikey123"})
assert.are.equal(200, status)
assert.equal(200, status)
local parsed_response = cjson.decode(response)
assert.falsy(parsed_response.headers.apikey)
end)

it("should pass with GET and hide credentials and another param", function()
local response, status = http_client.get(STUB_GET_URL, {}, {host = "keyauth2.com", apikey = "apikey123", foo = "bar"})
assert.are.equal(200, status)
assert.equal(200, status)
local parsed_response = cjson.decode(response)
assert.falsy(parsed_response.headers.apikey)
assert.are.equal("bar", parsed_response.headers.foo)
assert.equal("bar", parsed_response.headers.foo)
end)

it("should not pass with GET and hide credentials", function()
local response, status = http_client.get(STUB_GET_URL, {}, {host = "keyauth2.com", apikey = "apikey123123"})
local body = cjson.decode(response)
assert.are.equal(403, status)
assert.are.equal("Invalid authentication credentials", body.message)
assert.equal(403, status)
assert.equal("Invalid authentication credentials", body.message)
end)

it("should pass with GET and hide credentials and another param", function()
local response, status = http_client.get(STUB_GET_URL, {}, {host = "keyauth2.com", apikey = "apikey123", wot = "wat"})
assert.are.equal(200, status)
assert.equal(200, status)
local parsed_response = cjson.decode(response)
assert.falsy(parsed_response.headers.apikey)
assert.are.equal("wat", parsed_response.headers.wot)
assert.equal("wat", parsed_response.headers.wot)
end)

it("should not pass with GET and hide credentials", function()
local response, status = http_client.get(STUB_GET_URL, {}, {host = "keyauth2.com", apikey = "apikey123123"})
local body = cjson.decode(response)
assert.are.equal(403, status)
assert.are.equal("Invalid authentication credentials", body.message)
assert.equal(403, status)
assert.equal("Invalid authentication credentials", body.message)
end)

it("should pass with GET and hide credentials in querystring", function()
local response, status = http_client.get(STUB_GET_URL, {apikey = "apikey123"}, {host = "keyauth2.com"})
assert.are.equal(200, status)
assert.equal(200, status)
local parsed_response = cjson.decode(response)
assert.falsy(parsed_response.queryString.apikey)
end)
Expand Down

0 comments on commit cb2e4ff

Please sign in to comment.