-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
fix(key-auth): add missing www-authenticate headers #11794
Merged
bungle
merged 1 commit into
master
from
feat/implement-missing-www-authenticate-headers-key-auth
Mar 19, 2024
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
message: Add WWW-Authenticate headers to all 401 response in key auth plugin. | ||
type: bugfix | ||
scope: Plugin |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ local type = type | |
local error = error | ||
local ipairs = ipairs | ||
local tostring = tostring | ||
local fmt = string.format | ||
|
||
|
||
local HEADERS_CONSUMER_ID = constants.HEADERS.CONSUMER_ID | ||
|
@@ -25,14 +26,11 @@ local KeyAuthHandler = { | |
local EMPTY = {} | ||
|
||
|
||
local _realm = 'Key realm="' .. _KONG._NAME .. '"' | ||
|
||
|
||
local ERR_DUPLICATE_API_KEY = { status = 401, message = "Duplicate API key found" } | ||
local ERR_NO_API_KEY = { status = 401, message = "No API key found in request" } | ||
local ERR_INVALID_AUTH_CRED = { status = 401, message = "Unauthorized" } | ||
local ERR_INVALID_PLUGIN_CONF = { status = 500, message = "Invalid plugin configuration" } | ||
local ERR_UNEXPECTED = { status = 500, message = "An unexpected error occurred" } | ||
local ERR_DUPLICATE_API_KEY = "Duplicate API key found" | ||
local ERR_NO_API_KEY = "No API key found in request" | ||
local ERR_INVALID_AUTH_CRED = "Unauthorized" | ||
local ERR_INVALID_PLUGIN_CONF = "Invalid plugin configuration" | ||
local ERR_UNEXPECTED = "An unexpected error occurred" | ||
|
||
|
||
local function load_credential(key) | ||
|
@@ -99,13 +97,21 @@ local function get_body() | |
return body | ||
end | ||
|
||
local function server_error(message) | ||
return { status = 500, message = message } | ||
end | ||
|
||
local function unauthorized(message, www_auth_content) | ||
return { status = 401, message = message, headers = { ["WWW-Authenticate"] = www_auth_content } } | ||
end | ||
|
||
local function do_authentication(conf) | ||
if type(conf.key_names) ~= "table" then | ||
kong.log.err("no conf.key_names set, aborting plugin execution") | ||
return nil, ERR_INVALID_PLUGIN_CONF | ||
return nil, server_error(ERR_INVALID_PLUGIN_CONF) | ||
end | ||
|
||
local www_auth_content = conf.realm and fmt('Key realm="%s"', conf.realm) or 'Key' | ||
local headers = kong.request.get_headers() | ||
local query = kong.request.get_query() | ||
local key | ||
|
@@ -160,14 +166,13 @@ local function do_authentication(conf) | |
|
||
elseif type(v) == "table" then | ||
-- duplicate API key | ||
return nil, ERR_DUPLICATE_API_KEY | ||
return nil, unauthorized(ERR_DUPLICATE_API_KEY, www_auth_content) | ||
end | ||
end | ||
|
||
-- this request is missing an API key, HTTP 401 | ||
if not key or key == "" then | ||
kong.response.set_header("WWW-Authenticate", _realm) | ||
return nil, ERR_NO_API_KEY | ||
return nil, unauthorized(ERR_NO_API_KEY, www_auth_content) | ||
end | ||
|
||
-- retrieve our consumer linked to this API key | ||
|
@@ -187,8 +192,7 @@ local function do_authentication(conf) | |
|
||
-- no credential in DB, for this key, it is invalid, HTTP 401 | ||
if not credential or hit_level == 4 then | ||
|
||
return nil, ERR_INVALID_AUTH_CRED | ||
return nil, unauthorized(ERR_INVALID_AUTH_CRED, www_auth_content) | ||
end | ||
|
||
----------------------------------------- | ||
|
@@ -203,44 +207,63 @@ local function do_authentication(conf) | |
credential.consumer.id) | ||
if err then | ||
kong.log.err(err) | ||
return nil, ERR_UNEXPECTED | ||
return nil, server_error(ERR_UNEXPECTED) | ||
end | ||
|
||
set_consumer(consumer, credential) | ||
|
||
return true | ||
end | ||
|
||
local function set_anonymous_consumer(anonymous) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just some small refactoring - I've extracted from
|
||
local consumer_cache_key = kong.db.consumers:cache_key(anonymous) | ||
local consumer, err = kong.cache:get(consumer_cache_key, nil, | ||
kong.client.load_consumer, | ||
anonymous, true) | ||
if err then | ||
return error(err) | ||
end | ||
|
||
function KeyAuthHandler:access(conf) | ||
-- check if preflight request and whether it should be authenticated | ||
if not conf.run_on_preflight and kong.request.get_method() == "OPTIONS" then | ||
set_consumer(consumer) | ||
end | ||
|
||
|
||
--- When conf.anonymous is enabled we are in "logical OR" authentication flow. | ||
--- Meaning - either anonymous consumer is enabled or there are multiple auth plugins | ||
--- and we need to passthrough on failed authentication. | ||
local function logical_OR_authentication(conf) | ||
if kong.client.get_credential() then | ||
-- we're already authenticated and in "logical OR" between auth methods -- early exit | ||
return | ||
end | ||
|
||
if conf.anonymous and kong.client.get_credential() then | ||
-- we're already authenticated, and we're configured for using anonymous, | ||
-- hence we're in a logical OR between auth methods and we're already done. | ||
return | ||
local ok, _ = do_authentication(conf) | ||
if not ok then | ||
set_anonymous_consumer(conf.anonymous) | ||
end | ||
end | ||
|
||
--- When conf.anonymous is not set we are in "logical AND" authentication flow. | ||
--- Meaning - if this authentication fails the request should not be authorized | ||
--- even though other auth plugins might have successfully authorized user. | ||
local function logical_AND_authentication(conf) | ||
local ok, err = do_authentication(conf) | ||
if not ok then | ||
if conf.anonymous then | ||
-- get anonymous user | ||
local consumer_cache_key = kong.db.consumers:cache_key(conf.anonymous) | ||
local consumer, err = kong.cache:get(consumer_cache_key, nil, | ||
kong.client.load_consumer, | ||
conf.anonymous, true) | ||
if err then | ||
return error(err) | ||
end | ||
return kong.response.error(err.status, err.message, err.headers) | ||
end | ||
end | ||
|
||
set_consumer(consumer) | ||
|
||
else | ||
return kong.response.error(err.status, err.message, err.headers) | ||
end | ||
function KeyAuthHandler:access(conf) | ||
-- check if preflight request and whether it should be authenticated | ||
if not conf.run_on_preflight and kong.request.get_method() == "OPTIONS" then | ||
return | ||
end | ||
|
||
if conf.anonymous then | ||
return logical_OR_authentication(conf) | ||
else | ||
return logical_AND_authentication(conf) | ||
end | ||
end | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The original one had
tables
preallocated, this will allocate some memory on each failure. I am not sure was the original intent to avoid creating tables. But this is so small thing that I am fine with this.