Skip to content

Commit

Permalink
feat(plugins) convert plugins to routes & services
Browse files Browse the repository at this point in the history
---

tests(statsd) make statsd plugin work with routes & services

---

feat(galileo, ldap) stop using ctx.api

Plugins still using it: oauth2, rate limiting, response rate limiting

---

use service_id & route_id in rate-limiting plugin

---

feat(response-rate-limiting) use route_id & service_id in rrl

---

feat(oauth2) use service_id in oauth2 plugin
  • Loading branch information
kikito authored and thibaultcha committed Feb 20, 2018
1 parent 5de5803 commit def201f
Show file tree
Hide file tree
Showing 31 changed files with 1,450 additions and 901 deletions.
6 changes: 5 additions & 1 deletion kong/core/plugins_iterator.lua
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,11 @@ local function load_plugin_configuration(route_id,
return responses.send_HTTP_INTERNAL_SERVER_ERROR(err)
end
if plugin ~= nil and plugin.enabled then
return plugin.config or {}
local cfg = plugin.config or {}
cfg.route_id = plugin.route_id
cfg.service_id = plugin.service_id
cfg.consumer_id = plugin.consumer_id
return cfg
end
end

Expand Down
69 changes: 46 additions & 23 deletions kong/db/strategies/cassandra/services.lua
Original file line number Diff line number Diff line change
@@ -1,49 +1,72 @@
local cassandra = require "cassandra"


local _Services = {}

local fmt = string.format

function _Services:delete(primary_key)
local ok, err_t = self.super.delete(self, primary_key)
if not ok then
return nil, err_t
end

local plugins = {}
local connector = self.connector
local cluster = connector.cluster
local _Services = {}

-- retrieve plugins associated with this Service

local query = "SELECT * FROM plugins WHERE service_id = ? ALLOW FILTERING"
local args = { cassandra.uuid(primary_key.id) }
local function select_by_service_id(cluster, table_name, service_id, errors)
local select_q = fmt("SELECT * FROM %s WHERE service_id = ?",
table_name)
local res = {}
local count = 0

for rows, err in cluster:iterate(query, args) do
for rows, err in cluster:iterate(select_q, { cassandra.uuid(service_id) }) do
if err then
return nil, self.errors:database_error("could not fetch plugins " ..
"for Service: " .. err)
return nil,
errors:database_error(
fmt("could not fetch %s for Service: %s", table_name, err))
end

for i = 1, #rows do
table.insert(plugins, rows[i])
count = count + 1
res[count] = rows[i]
end
end

-- CASCADE delete associated plugins
return res
end

local function delete_cascade(connector, table_name, service_id, errors)
local entities = select_by_service_id(connector.cluster, table_name, service_id, errors)

for i = 1, #entities do
local delete_q = fmt("DELETE from %s WHERE id = ?", table_name)

for i = 1, #plugins do
local res, err = connector:query("DELETE FROM plugins WHERE id = ?", {
cassandra.uuid(plugins[i].id)
local res, err = connector:query(delete_q, {
cassandra.uuid(entities[i].id)
}, nil, "write")

if not res then
return nil, self.errors:database_error("could not delete plugin " ..
"associated with Service: " .. err)
return nil, errors:database_error(
fmt("could not delete instance of %s associated with Service: %s",
table_name, err))
end
end

return true
end


function _Services:delete(primary_key)
local ok, err_t = self.super.delete(self, primary_key)
if not ok then
return nil, err_t
end

local connector = self.connector
local service_id = primary_key.id
local errors = self.errors

local ok1, err1 = delete_cascade(connector, "plugins", service_id, errors)
local ok2, err2 = delete_cascade(connector, "oauth2_tokens", service_id, errors)
local ok3, err3 = delete_cascade(connector, "oauth2_authorization_codes", service_id, errors)

return ok1 and ok2 and ok3,
err1 or err2 or err3
end


return _Services
8 changes: 4 additions & 4 deletions kong/plugins/galileo/handler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ local Buffer = require "kong.plugins.galileo.buffer"
local read_body = ngx.req.read_body
local get_body_data = ngx.req.get_body_data

local _alf_buffers = {} -- buffers per-api
local _alf_buffers = {} -- buffers per-route
local _server_addr

local GalileoHandler = BasePlugin:extend()
Expand Down Expand Up @@ -51,9 +51,9 @@ function GalileoHandler:log(conf)
GalileoHandler.super.log(self)

local ctx = ngx.ctx
local api_id = ctx.api.id
local route_id = ctx.route.id

local buf = _alf_buffers[api_id]
local buf = _alf_buffers[route_id]
if not buf then
local err
conf.server_addr = _server_addr
Expand All @@ -62,7 +62,7 @@ function GalileoHandler:log(conf)
ngx.log(ngx.ERR, "could not create ALF buffer: ", err)
return
end
_alf_buffers[api_id] = buf
_alf_buffers[route_id] = buf
end

local req_body, res_body
Expand Down
2 changes: 1 addition & 1 deletion kong/plugins/ldap-auth/access.lua
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ local function authenticate(conf, given_credentials)
return false
end

local cache_key = "ldap_auth_cache:" .. ngx.ctx.api.id .. ":" .. given_username
local cache_key = "ldap_auth_cache:" .. ngx.ctx.route.id .. ":" .. given_username
local credential, err = singletons.cache:get(cache_key, {
ttl = conf.cache_ttl,
neg_ttl = conf.cache_ttl,
Expand Down
48 changes: 24 additions & 24 deletions kong/plugins/oauth2/access.lua
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ local GRANT_PASSWORD = "password"
local ERROR = "error"
local AUTHENTICATED_USERID = "authenticated_userid"

local function generate_token(conf, api, credential, authenticated_userid, scope, state, expiration, disable_refresh)
local function generate_token(conf, service, credential, authenticated_userid, scope, state, expiration, disable_refresh)
local token_expiration = expiration or conf.token_expiration

local refresh_token
Expand All @@ -49,12 +49,12 @@ local function generate_token(conf, api, credential, authenticated_userid, scope
refresh_token_ttl = conf.refresh_token_ttl
end

local api_id
local service_id
if not conf.global_credentials then
api_id = api.id
service_id = service.id
end
local token, err = singletons.dao.oauth2_tokens:insert({
api_id = api_id,
service_id = service_id,
credential_id = credential.id,
authenticated_userid = authenticated_userid,
expires_in = token_expiration,
Expand Down Expand Up @@ -180,12 +180,12 @@ local function authorize(conf)
-- If there are no errors, keep processing the request
if not response_params[ERROR] then
if response_type == CODE then
local api_id
local service_id
if not conf.global_credentials then
api_id = ngx.ctx.api.id
service_id = ngx.ctx.service.id
end
local authorization_code, err = singletons.dao.oauth2_authorization_codes:insert({
api_id = api_id,
service_id = service_id,
credential_id = client.id,
authenticated_userid = parameters[AUTHENTICATED_USERID],
scope = table.concat(scopes, " ")
Expand All @@ -200,7 +200,7 @@ local function authorize(conf)
}
else
-- Implicit grant, override expiration to zero
response_params = generate_token(conf, ngx.ctx.api, client, parameters[AUTHENTICATED_USERID], table.concat(scopes, " "), state, nil, true)
response_params = generate_token(conf, ngx.ctx.service, client, parameters[AUTHENTICATED_USERID], table.concat(scopes, " "), state, nil, true)
is_implicit_grant = true
end
end
Expand Down Expand Up @@ -312,17 +312,17 @@ local function issue_token(conf)
if not response_params[ERROR] then
if grant_type == GRANT_AUTHORIZATION_CODE then
local code = parameters[CODE]
local api_id
local service_id
if not conf.global_credentials then
api_id = ngx.ctx.api.id
service_id = ngx.ctx.service.id
end
local authorization_code = code and singletons.dao.oauth2_authorization_codes:find_all({api_id = api_id, code = code})[1]
local authorization_code = code and singletons.dao.oauth2_authorization_codes:find_all({service_id = service_id, code = code})[1]
if not authorization_code then
response_params = {[ERROR] = "invalid_request", error_description = "Invalid " .. CODE}
elseif authorization_code.credential_id ~= client.id then
response_params = {[ERROR] = "invalid_request", error_description = "Invalid " .. CODE}
else
response_params = generate_token(conf, ngx.ctx.api, client, authorization_code.authenticated_userid, authorization_code.scope, state)
response_params = generate_token(conf, ngx.ctx.service, client, authorization_code.authenticated_userid, authorization_code.scope, state)
singletons.dao.oauth2_authorization_codes:delete({id=authorization_code.id}) -- Delete authorization code so it cannot be reused
end
elseif grant_type == GRANT_CLIENT_CREDENTIALS then
Expand All @@ -335,7 +335,7 @@ local function issue_token(conf)
if not ok then
response_params = scopes -- If it's not ok, then this is the error message
else
response_params = generate_token(conf, ngx.ctx.api, client, parameters.authenticated_userid, table.concat(scopes, " "), state, nil, true)
response_params = generate_token(conf, ngx.ctx.service, client, parameters.authenticated_userid, table.concat(scopes, " "), state, nil, true)
end
end
elseif grant_type == GRANT_PASSWORD then
Expand All @@ -350,24 +350,24 @@ local function issue_token(conf)
if not ok then
response_params = scopes -- If it's not ok, then this is the error message
else
response_params = generate_token(conf, ngx.ctx.api, client, parameters.authenticated_userid, table.concat(scopes, " "), state)
response_params = generate_token(conf, ngx.ctx.service, client, parameters.authenticated_userid, table.concat(scopes, " "), state)
end
end
elseif grant_type == GRANT_REFRESH_TOKEN then
local refresh_token = parameters[REFRESH_TOKEN]
local api_id
local service_id
if not conf.global_credentials then
api_id = ngx.ctx.api.id
service_id = ngx.ctx.service.id
end
local token = refresh_token and singletons.dao.oauth2_tokens:find_all({api_id = api_id, refresh_token = refresh_token})[1]
local token = refresh_token and singletons.dao.oauth2_tokens:find_all({service_id = service_id, refresh_token = refresh_token})[1]
if not token then
response_params = {[ERROR] = "invalid_request", error_description = "Invalid " .. REFRESH_TOKEN}
else
-- Check that the token belongs to the client application
if token.credential_id ~= client.id then
response_params = {[ERROR] = "invalid_client", error_description = "Invalid client authentication"}
else
response_params = generate_token(conf, ngx.ctx.api, client, token.authenticated_userid, token.scope, state)
response_params = generate_token(conf, ngx.ctx.service, client, token.authenticated_userid, token.scope, state)
singletons.dao.oauth2_tokens:delete({id=token.id}) -- Delete old token
end
end
Expand All @@ -387,12 +387,12 @@ local function issue_token(conf)
})
end

local function load_token_into_memory(conf, api, access_token)
local api_id
local function load_token_into_memory(conf, service, access_token)
local service_id
if not conf.global_credentials then
api_id = api.id
service_id = service.id
end
local credentials, err = singletons.dao.oauth2_tokens:find_all { api_id = api_id, access_token = access_token }
local credentials, err = singletons.dao.oauth2_tokens:find_all { service_id = service_id, access_token = access_token }
local result
if err then
return nil, err
Expand All @@ -407,7 +407,7 @@ local function retrieve_token(conf, access_token)
if access_token then
local token_cache_key = singletons.dao.oauth2_tokens:cache_key(access_token)
token, err = singletons.cache:get(token_cache_key, nil,
load_token_into_memory, conf, ngx.ctx.api,
load_token_into_memory, conf, ngx.ctx.service,
access_token)
if err then
return responses.send_HTTP_INTERNAL_SERVER_ERROR(err)
Expand Down Expand Up @@ -506,7 +506,7 @@ local function do_authentication(conf)
return false, {status = 401, message = {[ERROR] = "invalid_token", error_description = "The access token is invalid or has expired"}, headers = {["WWW-Authenticate"] = 'Bearer realm="service" error="invalid_token" error_description="The access token is invalid or has expired"'}}
end

if (token.api_id and ngx.ctx.api.id ~= token.api_id) or (token.api_id == nil and not conf.global_credentials) then
if (token.service_id and ngx.ctx.service.id ~= token.service_id) or (token.service_id == nil and not conf.global_credentials) then
return false, {status = 401, message = {[ERROR] = "invalid_token", error_description = "The access token is invalid or has expired"}, headers = {["WWW-Authenticate"] = 'Bearer realm="service" error="invalid_token" error_description="The access token is invalid or has expired"'}}
end

Expand Down
35 changes: 34 additions & 1 deletion kong/plugins/oauth2/daos.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
local utils = require "kong.tools.utils"
local url = require "socket.url"
local Errors = require "kong.dao.errors"
local db_errors = require "kong.db.errors"


local function validate_uris(v, t, column)
if v then
Expand All @@ -19,6 +22,28 @@ local function validate_uris(v, t, column)
return true, nil
end


local function validate_service_id(service_id, db)

if service_id ~= nil then
local service, err, err_t = db.services:select({
id = service_id,
})
if err then
if err_t.code == db_errors.codes.DATABASE_ERROR then
return false, Errors.db(err)
end

return false, Errors.schema(err_t)
end

if not service then
return false, Errors.foreign("no such Service (id=" .. service_id .. ")")
end
end
end


local OAUTH2_CREDENTIALS_SCHEMA = {
primary_key = {"id"},
table = "oauth2_credentials",
Expand All @@ -39,13 +64,17 @@ local OAUTH2_AUTHORIZATION_CODES_SCHEMA = {
table = "oauth2_authorization_codes",
fields = {
id = { type = "id", dao_insert_value = true },
service_id = { type = "id" }, --foreign = "services:id" -- manually tested in self_check
api_id = { type = "id", required = false, foreign = "apis:id" },
credential_id = { type = "id", required = true, foreign = "oauth2_credentials:id" },
code = { type = "string", required = false, unique = true, immutable = true, default = utils.random_string },
authenticated_userid = { type = "string", required = false },
scope = { type = "string" },
created_at = { type = "timestamp", immutable = true, dao_insert_value = true }
}
},
self_check = function(self, auth_t, dao, is_update)
return validate_service_id(auth_t.service_id, dao.db.new_db)
end,
}

local BEARER = "bearer"
Expand All @@ -55,6 +84,7 @@ local OAUTH2_TOKENS_SCHEMA = {
cache_key = { "access_token" },
fields = {
id = { type = "id", dao_insert_value = true },
service_id = { type = "id" }, --foreign = "services:id" -- manually tested in self_check
api_id = { type = "id", required = false, foreign = "apis:id" },
credential_id = { type = "id", required = true, foreign = "oauth2_credentials:id" },
token_type = { type = "string", required = true, enum = { BEARER }, default = BEARER },
Expand All @@ -65,6 +95,9 @@ local OAUTH2_TOKENS_SCHEMA = {
scope = { type = "string" },
created_at = { type = "timestamp", immutable = true, dao_insert_value = true }
},
self_check = function(self, token_t, dao, is_update)
return validate_service_id(token_t.service_id, dao.db.new_db)
end,
}

return {
Expand Down
16 changes: 15 additions & 1 deletion kong/plugins/oauth2/migrations/cassandra.lua
Original file line number Diff line number Diff line change
Expand Up @@ -189,5 +189,19 @@ return {
end
end,
down = function(_, _, dao) end -- not implemented
}
},
{
name = "2018-01-09-oauth2_c_add_service_id",
up = [[
ALTER TABLE oauth2_authorization_codes ADD service_id uuid;
CREATE INDEX IF NOT EXISTS ON oauth2_authorization_codes(service_id);
ALTER TABLE oauth2_tokens ADD service_id uuid;
CREATE INDEX IF NOT EXISTS ON oauth2_tokens(service_id);
]],
down = [[
ALTER TABLE oauth2_authorization_codes DROP service_id;
ALTER TABLE oauth2_tokens DROP service_id;
]],
},
}
Loading

0 comments on commit def201f

Please sign in to comment.