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

Metadata insertion plugin #1962

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions kong-0.9.7-0.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -272,5 +272,9 @@ build = {
["kong.plugins.aws-lambda.handler"] = "kong/plugins/aws-lambda/handler.lua",
["kong.plugins.aws-lambda.schema"] = "kong/plugins/aws-lambda/schema.lua",
["kong.plugins.aws-lambda.v4"] = "kong/plugins/aws-lambda/v4.lua",

["kong.plugins.metadata-insertion.handler"] = "kong/plugins/metadata-insertion/handler.lua",
["kong.plugins.metadata-insertion.access"] = "kong/plugins/metadata-insertion/access.lua",
["kong.plugins.metadata-insertion.schema"] = "kong/plugins/metadata-insertion/schema.lua",
}
}
170 changes: 170 additions & 0 deletions kong/plugins/metadata-insertion/access.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
local _M = {}
local request_querystring_factory = require "kong.plugins.metadata-insertion.factory.request_querystring_factory"
local request_headers_factory = require "kong.plugins.metadata-insertion.factory.request_headers_factory"
-- local debug = require "kong.plugins.metadata-insertion.tool.debug"
local cache = require "kong.tools.database_cache"
local singletons = require "kong.singletons"
local responses = require "kong.tools.responses"
local currentUserMetadata

local function getPersistentMetadata()
-- retrieve metadata from cache or database for current user
currentUserMetadata = cache.get_or_set("metadata_keyvaluestore." .. ngx.ctx.authenticated_consumer.id, nil, function()
local metadata, err = singletons.dao.metadata_keyvaluestore:find_all({ consumer_id = ngx.ctx.authenticated_consumer.id })
if err then
return responses.send_HTTP_INTERNAL_SERVER_ERROR(err)
end
return metadata
end)

if currentUserMetadata then
return currentUserMetadata
end

return {}
end

local function resolveParameterValuePlaceholderWithMetadata(dataProvisioningName)
for _, elem in ipairs(currentUserMetadata) do
if elem.key == dataProvisioningName then
return elem.value
end
end
error("This API needs metadata that the current user does not provide.")
end

local function retrieveMetadataForConfigToken(querystringModifier)
local args = {}

for stringChunk in string.gmatch(querystringModifier, "%S+") do
table.insert(args, stringChunk)
end

assert(table.getn(args) == 2, "Invalid format")

-- Prepare arg name
local argName = args[1]
argName = argName:gsub(":", "")

-- Prepare arg value (replace placeholder name by metadata value)
local argValuePlaceholderName = args[2]
argValuePlaceholderName = argValuePlaceholderName:gsub("%%", "")
local parameterValue = resolveParameterValuePlaceholderWithMetadata(argValuePlaceholderName)
return argName, parameterValue
end


local function appendMetadataFromTransitoryStore(currentUserMetadata)

-- loop through transitory store and add the metadata in memory
-- transitory store take precedence over persitent metadata
if ngx.ctx.metadata_transitory_store and type(ngx.ctx.metadata_transitory_store) == "table" then

for _, transitoryStoreElem in ipairs(ngx.ctx.metadata_transitory_store) do

local persistentMetadataFound = false

-- replace persitent metadata with transitory store if it exist in both place
for index, persistentMetadataElem in ipairs(currentUserMetadata) do
if persistentMetadataElem.key == transitoryStoreElem.key then
currentUserMetadata[index] = transitoryStoreElem
persistentMetadataFound = true
end
end

-- if nothing found in persistent metadata, let's make sure we add the transitory store element as new
if persistentMetadataFound == false then
table.insert(currentUserMetadata, transitoryStoreElem)
end
end
end
end

local function updateQuerystring(confDataInsertion)

local RequestQuerystringFactory = request_querystring_factory:new()

RequestQuerystringFactory:mergeArgsWithRequestArgs()

-- Remove querystring(s)
if confDataInsertion.remove and confDataInsertion.remove.querystring then
for _, key in ipairs(confDataInsertion.remove.querystring) do
RequestQuerystringFactory:removeArgByKey(key)
end
end

-- Replace querystring(s)
if confDataInsertion.replace and confDataInsertion.replace.querystring then
for _, querystringModifier in pairs(confDataInsertion.replace.querystring) do
local parameterName, parameterValue = retrieveMetadataForConfigToken(querystringModifier)
RequestQuerystringFactory:replaceArgByKey(parameterName, parameterValue)
end
end

-- Add querystring(s)
if confDataInsertion.add and confDataInsertion.add.querystring then
for _, querystringModifier in pairs(confDataInsertion.add.querystring) do
local parameterName, parameterValue = retrieveMetadataForConfigToken(querystringModifier)
RequestQuerystringFactory:add(parameterName, parameterValue)
end
end

RequestQuerystringFactory:persist()
end

local function updateHeaders(confDataInsertion)

local RequestHeadersFactory = request_headers_factory:new()

RequestHeadersFactory:mergeArgsWithRequestArgs()

-- Remove querystring(s)
if confDataInsertion.remove and confDataInsertion.remove.headers then
for _, key in ipairs(confDataInsertion.remove.headers) do
RequestHeadersFactory:removeArgByKey(key)
end
end

-- Replace querystring(s)
if confDataInsertion.replace and confDataInsertion.replace.headers then
for _, headersModifier in pairs(confDataInsertion.replace.headers) do
local parameterName, parameterValue = retrieveMetadataForConfigToken(headersModifier)
RequestHeadersFactory:replaceArgByKey(parameterName, parameterValue)
end
end

-- Add querystring(s)
if confDataInsertion.add and confDataInsertion.add.headers then
for _, headersModifier in pairs(confDataInsertion.add.headers) do
local parameterName, parameterValue = retrieveMetadataForConfigToken(headersModifier)
RequestHeadersFactory:add(parameterName, parameterValue)
end
end

RequestHeadersFactory:persist()
end

function _M.execute(conf)

if ngx.ctx.authenticated_consumer == nil then
return responses.send_HTTP_UNAUTHORIZED("Metadata plugin can't be used without having an authenticated user.")
end

currentUserMetadata = getPersistentMetadata()
appendMetadataFromTransitoryStore(currentUserMetadata)

local _, err = pcall(function()

-----------------------------
-- Data Insertion Processing
-----------------------------
updateQuerystring(conf)
updateHeaders(conf)
end)

if err then
return responses.send_HTTP_BAD_REQUEST(err)
end
end

return _M
19 changes: 19 additions & 0 deletions kong/plugins/metadata-insertion/api.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
local crud = require "kong.api.crud_helpers"

return {
["/consumers/:username_or_id/metadata/"] = {
before = function(self, dao_factory, helpers)
crud.find_consumer_by_username_or_id(self, dao_factory, helpers)
self.params.consumer_id = self.consumer.id
end,
GET = function(self, dao_factory)
crud.paginated_set(self, dao_factory.metadata_keyvaluestore)
end,
PUT = function(self, dao_factory)
crud.put(self.params, dao_factory.metadata_keyvaluestore)
end,
POST = function(self, dao_factory)
crud.post(self.params, dao_factory.metadata_keyvaluestore)
end
}
}
16 changes: 16 additions & 0 deletions kong/plugins/metadata-insertion/daos.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
local SCHEMA = {
primary_key = { "id" },
table = "metadata_keyvaluestore",
fields = {
id = { type = "id", dao_insert_value = true },
created_at = { type = "timestamp", immutable = true, dao_insert_value = true },
consumer_id = { type = "id", required = true, foreign = "consumers:id" },
key = { type = "string", required = true },
value = { type = "string", required = true }
},
marshall_event = function(self, t)
return { id = t.id, consumer_id = t.consumer_id, key = t.key }
end
}

return { metadata_keyvaluestore = SCHEMA }
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
local req_set_header = ngx.req.set_header
local req_get_headers = ngx.req.get_headers
local HOST = "host"
local RequestHeadersFactory = {}
local args = {}

function RequestHeadersFactory:new()
return RequestHeadersFactory
end

function RequestHeadersFactory:getArgs()
return args
end

function RequestHeadersFactory:mergeArgsWithRequestArgs()
local requestArgs = req_get_headers()
for k, v in pairs(requestArgs) do
args[k] = v
end
end

function RequestHeadersFactory:persist()
for key, value in pairs(args) do
if key:lower() ~= HOST then
if value == "metadata_asked_for_removal" then
req_set_header(key, nil)
else
req_set_header(key, value)
end
end
end
end

function RequestHeadersFactory:removeArgByKey(key)
if args[key] then
args[key] = "metadata_asked_for_removal"
end
end

function RequestHeadersFactory:replaceArgByKey(key, value)
if args[key] then
args[key] = value
end
end

function RequestHeadersFactory:add(key, value)
args[key] = value
end

return RequestHeadersFactory
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
local req_get_uri_args = ngx.req.get_uri_args
local req_set_uri_args = ngx.req.set_uri_args

local RequestQuerystringFactory = {}
local args = {}

function RequestQuerystringFactory:new()
return RequestQuerystringFactory
end

function RequestQuerystringFactory:getArgs()
return args
end

function RequestQuerystringFactory:mergeArgsWithRequestArgs()
local requestArgs = req_get_uri_args()
for k, v in pairs(requestArgs) do
args[k] = v
end
end

function RequestQuerystringFactory:persist()
req_set_uri_args(args)
end

function RequestQuerystringFactory:removeArgByKey(key)
if args[key] then
args[key] = nil
end
end

function RequestQuerystringFactory:replaceArgByKey(key, value)
if args[key] then
args[key] = value
end
end

function RequestQuerystringFactory:add(key, value)
args[key] = value
end

return RequestQuerystringFactory
15 changes: 15 additions & 0 deletions kong/plugins/metadata-insertion/handler.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
local BasePlugin = require "kong.plugins.base_plugin"
local access = require "kong.plugins.metadata-insertion.access"

local MetadataInsertionHandler = BasePlugin:extend()

function MetadataInsertionHandler:new()
MetadataInsertionHandler.super.new(self, "metadata-insertion")
end

function MetadataInsertionHandler:access(conf)
MetadataInsertionHandler.super.access(self)
access.execute(conf)
end

return MetadataInsertionHandler
17 changes: 17 additions & 0 deletions kong/plugins/metadata-insertion/hooks.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
local events = require "kong.core.events"
local cache = require "kong.tools.database_cache"

local function invalidate(message_t)
if message_t.collection == "metadata_keyvaluestore" then
cache.delete(cache.metadata_keyvaluestore(message_t.old_entity and message_t.old_entity.key or message_t.entity.key))
end
end

return {
[events.TYPES.ENTITY_UPDATED] = function(message_t)
invalidate(message_t)
end,
[events.TYPES.ENTITY_DELETED] = function(message_t)
invalidate(message_t)
end
}
21 changes: 21 additions & 0 deletions kong/plugins/metadata-insertion/migrations/cassandra.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
return {
{
name = "2017-01-05-000000_init_metadata",
up = [[
CREATE TABLE IF NOT EXISTS metadata_keyvaluestore(
id uuid,
consumer_id uuid,
key text,
value text,
created_at timestamp,
PRIMARY KEY (id)
);

CREATE INDEX IF NOT EXISTS ON metadata_keyvaluestore(key);
CREATE INDEX IF NOT EXISTS metadata_keyvaluestore_consumer_id ON metadata_keyvaluestore(consumer_id);
]],
down = [[
DROP TABLE metadata_keyvaluestore;
]]
}
}
28 changes: 28 additions & 0 deletions kong/plugins/metadata-insertion/migrations/postgres.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
return {
{
name = "2017-01-05-000000_init_metadata",
up = [[
CREATE TABLE IF NOT EXISTS metadata_keyvaluestore(
id uuid,
consumer_id uuid REFERENCES consumers (id) ON DELETE CASCADE,
key text,
value text,
created_at timestamp without time zone default (CURRENT_TIMESTAMP(0) at time zone 'utc'),
PRIMARY KEY (id)
);

DO $$
BEGIN
IF (SELECT to_regclass('metadata_keyvaluestore_key_idx')) IS NULL THEN
CREATE INDEX metadata_keyvaluestore_key_idx ON metadata_keyvaluestore(key);
END IF;
IF (SELECT to_regclass('metadata_keyvaluestore_consumer_idx')) IS NULL THEN
CREATE INDEX metadata_keyvaluestore_consumer_idx ON metadata_keyvaluestore(consumer_id);
END IF;
END$$;
]],
down = [[
DROP TABLE metadata_keyvaluestore;
]]
}
}
Loading