Files
kong--kong/kong/plugins/acl/groups.lua
T
2026-07-13 12:32:21 +08:00

215 lines
5.9 KiB
Lua

local EMPTY = require("kong.tools.table").EMPTY
local kong = kong
local type = type
local mt_cache = { __mode = "k" }
local setmetatable = setmetatable
local consumer_groups_cache = setmetatable({}, mt_cache)
local consumer_in_groups_cache = setmetatable({}, mt_cache)
local function load_groups_into_memory(consumer_pk)
local groups
local len = 0
for row, err in kong.db.acls:each_for_consumer(consumer_pk) do
if err then
return nil, err
end
if not groups then
groups = {}
end
len = len + 1
groups[len] = row
end
if len == 0 then
return EMPTY
end
return groups
end
--- Returns the database records with groups the consumer belongs to
-- @param consumer_id (string) the consumer for which to fetch the groups it belongs to
-- @return table with group records (empty table if none), or nil+error
local function get_consumer_groups_raw(consumer_id)
local cache_key = kong.db.acls:cache_key(consumer_id)
local raw_groups, err = kong.cache:get(cache_key, nil,
load_groups_into_memory,
{ id = consumer_id })
if err then
return nil, err
end
if raw_groups and #raw_groups > 0 then
return raw_groups
end
-- use EMPTY to be able to use it as a cache key, since a new table would
-- immediately be collected again and not allow for negative caching.
return EMPTY
end
--- Returns a table with all group names a consumer belongs to.
-- The table will have an array part to iterate over, and a hash part
-- where each group name is indexed by itself. Eg.
-- {
-- [1] = "users",
-- [2] = "admins",
-- users = "users",
-- admins = "admins",
-- }
-- If there are no groups defined, it will return an empty table
-- @param consumer_id (string) the consumer for which to fetch the groups it belongs to
-- @return table with groups, nil or nil+error
local function get_consumer_groups(consumer_id)
local raw_groups, err = get_consumer_groups_raw(consumer_id)
if not raw_groups then
return nil, err
end
local groups = consumer_groups_cache[raw_groups]
if not groups then
if raw_groups == EMPTY then
consumer_groups_cache[raw_groups] = EMPTY
else
groups = {}
consumer_groups_cache[raw_groups] = groups
for i = 1, #raw_groups do
local group = raw_groups[i].group
groups[i] = group
groups[group] = group
end
end
end
if groups == EMPTY then
return nil
end
return groups
end
--- checks whether a consumer-group-list is part of a given list of groups.
-- @param groups_to_check (table) an array of group names. Note: since the
-- results will be cached by this table, always use the same table for the
-- same set of groups!
-- @param consumer_groups (table) list of consumer groups (result from
-- `get_consumer_groups`)
-- @return (boolean) whether the consumer is part of any of the groups.
local function consumer_in_groups(groups_to_check, consumer_groups)
-- 1st level cache on "groups_to_check"
local result1 = consumer_in_groups_cache[groups_to_check]
if result1 == nil then
result1 = setmetatable({}, mt_cache)
consumer_in_groups_cache[groups_to_check] = result1
end
-- 2nd level cache on "consumer_groups"
local result2 = result1[consumer_groups]
if result2 ~= nil then
return result2
end
-- not found, so validate and populate 2nd level cache
result2 = false
for i = 1, #groups_to_check do
if consumer_groups[groups_to_check[i]] then
result2 = true
break
end
end
result1[consumer_groups] = result2
return result2
end
--- Gets the currently identified consumer for the request.
-- Checks both consumer and if not found the credentials.
-- @return consumer_id (string), or alternatively `nil` if no consumer was
-- authenticated.
local function get_current_consumer_id()
return (kong.client.get_consumer() or EMPTY).id or
(kong.client.get_credential() or EMPTY).consumer_id
end
--- Returns a table with all group names.
-- The table will have an array part to iterate over, and a hash part
-- where each group name is indexed by itself. Eg.
-- {
-- [1] = "users",
-- [2] = "admins",
-- users = "users",
-- admins = "admins",
-- }
-- If there are no authenticated_groups defined, it will return nil
-- @return table with groups or nil
local function get_authenticated_groups()
local authenticated_groups = kong.ctx.shared.authenticated_groups
if type(authenticated_groups) ~= "table" then
authenticated_groups = ngx.ctx.authenticated_groups
if authenticated_groups == nil then
return nil
end
if type(authenticated_groups) ~= "table" then
kong.log.warn("invalid authenticated_groups, a table was expected")
return nil
end
end
local groups = {}
for i = 1, #authenticated_groups do
groups[i] = authenticated_groups[i]
groups[authenticated_groups[i]] = authenticated_groups[i]
end
return groups
end
--- checks whether a group-list is part of a given list of groups.
-- @param groups_to_check (table) an array of group names.
-- @param groups (table) list of groups (result from
-- `get_authenticated_groups`)
-- @return (boolean) whether the authenticated group is part of any of the
-- groups.
local function group_in_groups(groups_to_check, groups)
for i = 1, #groups_to_check do
if groups[groups_to_check[i]] then
return true
end
end
end
local function warmup_groups_cache(consumer_id)
local cache_key = kong.db.acls:cache_key(consumer_id)
local _, err = kong.cache:get(cache_key, nil,
load_groups_into_memory,
{ id = consumer_id })
if err then
return nil, err
end
end
return {
get_current_consumer_id = get_current_consumer_id,
get_consumer_groups = get_consumer_groups,
get_authenticated_groups = get_authenticated_groups,
consumer_in_groups = consumer_in_groups,
group_in_groups = group_in_groups,
warmup_groups_cache = warmup_groups_cache,
}