diff --git a/kong-1.0.0rc2-0.rockspec b/kong-1.0.0rc2-0.rockspec index 048ce3f91d9f..9147b74acaf7 100644 --- a/kong-1.0.0rc2-0.rockspec +++ b/kong-1.0.0rc2-0.rockspec @@ -179,6 +179,7 @@ build = { ["kong.pdk.service"] = "kong/pdk/service.lua", ["kong.pdk.service.request"] = "kong/pdk/service/request.lua", ["kong.pdk.service.response"] = "kong/pdk/service/response.lua", + ["kong.pdk.router"] = "kong/pdk/router.lua", ["kong.pdk.request"] = "kong/pdk/request.lua", ["kong.pdk.response"] = "kong/pdk/response.lua", ["kong.pdk.table"] = "kong/pdk/table.lua", diff --git a/kong/pdk/client.lua b/kong/pdk/client.lua index e649948d319b..566f1ebecce9 100644 --- a/kong/pdk/client.lua +++ b/kong/pdk/client.lua @@ -12,10 +12,16 @@ local phase_checker = require "kong.pdk.private.phases" local ngx = ngx local tonumber = tonumber +local check_phase = phase_checker.check local check_not_phase = phase_checker.check_not local PHASES = phase_checker.phases +local AUTH_AND_LATER = phase_checker.new(PHASES.access, + PHASES.header_filter, + PHASES.body_filter, + PHASES.log) +local TABLE_OR_NIL = { ["table"] = true, ["nil"] = true } local function new(self) @@ -121,6 +127,73 @@ local function new(self) end + --- + -- Returns the credentials of the currently authenticated consumer. + -- If not set yet, it returns `nil`. + -- @function kong.client.get_credential + -- @phases access, header_filter, body_filter, log + -- @return the authenticated credential + -- @usage + -- local credential = kong.client.get_credential() + -- if credential then + -- consumer_id = credential.consumer_id + -- else + -- -- request not authenticated yet + -- end + function _CLIENT.get_credential() + check_phase(AUTH_AND_LATER) + + return ngx.ctx.authenticated_credential + end + + + --- + -- Returns the `consumer` entity of the currently authenticated consumer. + -- If not set yet, it returns `nil`. + -- @function kong.client.get_consumer + -- @phases access, header_filter, body_filter, log + -- @treturn table the authenticated consumer entity + -- @usage + -- local consumer = kong.client.get_consumer() + -- if consumer then + -- consumer_id = consumer.id + -- else + -- -- request not authenticated yet, or a credential + -- -- without a consumer (external auth) + -- end + function _CLIENT.get_consumer() + check_phase(AUTH_AND_LATER) + + return ngx.ctx.authenticated_consumer + end + + + --- + -- Sets the authenticated consumer (and credential) for the current request. + -- @function kong.client.authenticate + -- @phases access + -- @tparam table consumer The consumer to set, this can be `nil`. Note: if no + -- value is provided, then any existing value will be cleared! + -- @tparam table credential The credential to set, this can be `nil`. Note: if + -- no value is provided, then any existing value will be cleared! + -- @usage + -- -- assuming `credential` and `consumer` have been set by some authentication code + -- kong.client.authenticate(consumer, credentials) + function _CLIENT.authenticate(consumer, credential) + check_phase(PHASES.access) + + if not TABLE_OR_NIL[type(consumer)] then + error("consumer must be a table or nil", 2) + elseif not TABLE_OR_NIL[type(credential)] then + error("credential must be a table or nil", 2) + end + + local ctx = ngx.ctx + ctx.authenticated_consumer = consumer + ctx.authenticated_credential = credential + end + + return _CLIENT end diff --git a/kong/pdk/init.lua b/kong/pdk/init.lua index 3f0a8b22ac17..f84e56b93f49 100644 --- a/kong/pdk/init.lua +++ b/kong/pdk/init.lua @@ -116,6 +116,11 @@ -- @redirect kong.response +--- Router module +-- @field kong.router +-- @redirect kong.router + + --- Singletons -- @section singletons @@ -226,6 +231,7 @@ local MAJOR_VERSIONS = { "service.request", "service.response", "response", + "router", }, }, diff --git a/kong/pdk/router.lua b/kong/pdk/router.lua new file mode 100644 index 000000000000..f5456fe59595 --- /dev/null +++ b/kong/pdk/router.lua @@ -0,0 +1,70 @@ +--- Router module +-- A set of functions to access the routing properties of the request. +-- +-- @module kong.router + + +local phase_checker = require "kong.pdk.private.phases" + + +local ngx = ngx +local check_phase = phase_checker.check + + +local PHASES = phase_checker.phases +local ROUTER_PHASES = phase_checker.new(PHASES.access, + PHASES.header_filter, + PHASES.body_filter, + PHASES.log) + +local function new(self) + local _ROUTER = {} + + + --- + -- Returns the current `route` entity. The request was matched against this + -- route. + -- + -- @function kong.router.get_route + -- @phases access, header_filter, body_filter, log + -- @treturn table the `route` entity. + -- @usage + -- if kong.router.get_route() then + -- -- routed by route & service entities + -- else + -- -- routed by a legacy API entity + -- end + function _ROUTER.get_route() + check_phase(ROUTER_PHASES) + + return ngx.ctx.route + end + + + --- + -- Returns the current `service` entity. The request will be targetted to this + -- upstream service. + -- + -- @function kong.router.get_service + -- @phases access, header_filter, body_filter, log + -- @treturn table the `service` entity. + -- @usage + -- if kong.router.get_service() then + -- -- routed by route & service entities + -- else + -- -- routed by a legacy API entity + -- end + function _ROUTER.get_service() + check_phase(ROUTER_PHASES) + + return ngx.ctx.service + end + + + return _ROUTER +end + + +return { + new = new, +} diff --git a/t/01-pdk/05-client/00-phase_checks.t b/t/01-pdk/05-client/00-phase_checks.t index 161907288e3b..19cf3092e5aa 100644 --- a/t/01-pdk/05-client/00-phase_checks.t +++ b/t/01-pdk/05-client/00-phase_checks.t @@ -74,6 +74,39 @@ qq{ body_filter = true, log = true, admin_api = true, + }, { + method = "get_credential", + args = {}, + init_worker = "forced false", + certificate = "pending", + rewrite = "forced false", + access = true, + header_filter = true, + body_filter = true, + log = true, + admin_api = "forced false", + }, { + method = "get_consumer", + args = {}, + init_worker = "forced false", + certificate = "pending", + rewrite = "forced false", + access = true, + header_filter = true, + body_filter = true, + log = true, + admin_api = "forced false", + }, { + method = "set_consumer", + args = {}, + init_worker = "forced false", + certificate = "pending", + rewrite = "forced false", + access = true, + header_filter = "forced false", + body_filter = "forced false", + log = "forced false", + admin_api = "forced false", }, } diff --git a/t/01-pdk/05-client/05-get_credential.t b/t/01-pdk/05-client/05-get_credential.t new file mode 100644 index 000000000000..2889b9f379f1 --- /dev/null +++ b/t/01-pdk/05-client/05-get_credential.t @@ -0,0 +1,57 @@ +use strict; +use warnings FATAL => 'all'; +use Test::Nginx::Socket::Lua; +use t::Util; + +$ENV{TEST_NGINX_NXSOCK} ||= html_dir(); + +plan tests => repeat_each() * (blocks() * 3); + +run_tests(); + +__DATA__ + +=== TEST 1: client.get_credential() returns selected credential +--- http_config eval: $t::Util::HttpConfig +--- config + location = /t { + content_by_lua_block { + ngx.ctx.authenticated_credential = setmetatable({},{ + __tostring = function() return "this credential" end, + }) + + local PDK = require "kong.pdk" + local pdk = PDK.new() + + ngx.say("credential: ", tostring(pdk.client.get_credential())) + } + } +--- request +GET /t +--- response_body +credential: this credential +--- no_error_log +[error] + + + +=== TEST 2: client.get_service() returns nil if not set +--- http_config eval: $t::Util::HttpConfig +--- config + location = /t { + content_by_lua_block { + ngx.ctx.authenticated_credential = nil + + local PDK = require "kong.pdk" + local pdk = PDK.new() + + ngx.say("credential: ", tostring(pdk.client.get_credential())) + } + } +--- request +GET /t +--- response_body +credential: nil +--- no_error_log +[error] + diff --git a/t/01-pdk/05-client/06-get_consumer.t b/t/01-pdk/05-client/06-get_consumer.t new file mode 100644 index 000000000000..c8a42e2cb63f --- /dev/null +++ b/t/01-pdk/05-client/06-get_consumer.t @@ -0,0 +1,57 @@ +use strict; +use warnings FATAL => 'all'; +use Test::Nginx::Socket::Lua; +use t::Util; + +$ENV{TEST_NGINX_NXSOCK} ||= html_dir(); + +plan tests => repeat_each() * (blocks() * 3); + +run_tests(); + +__DATA__ + +=== TEST 1: client.get_consumer() returns selected consumer +--- http_config eval: $t::Util::HttpConfig +--- config + location = /t { + content_by_lua_block { + ngx.ctx.authenticated_consumer = setmetatable({},{ + __tostring = function() return "this consumer" end, + }) + + local PDK = require "kong.pdk" + local pdk = PDK.new() + + ngx.say("consumer: ", tostring(pdk.client.get_consumer())) + } + } +--- request +GET /t +--- response_body +consumer: this consumer +--- no_error_log +[error] + + + +=== TEST 2: client.get_service() returns nil if not set +--- http_config eval: $t::Util::HttpConfig +--- config + location = /t { + content_by_lua_block { + ngx.ctx.authenticated_consumer = nil + + local PDK = require "kong.pdk" + local pdk = PDK.new() + + ngx.say("consumer: ", tostring(pdk.client.get_consumer())) + } + } +--- request +GET /t +--- response_body +consumer: nil +--- no_error_log +[error] + diff --git a/t/01-pdk/05-client/07-set_consumer.t b/t/01-pdk/05-client/07-set_consumer.t new file mode 100644 index 000000000000..cf7b77faec30 --- /dev/null +++ b/t/01-pdk/05-client/07-set_consumer.t @@ -0,0 +1,112 @@ +use strict; +use warnings FATAL => 'all'; +use Test::Nginx::Socket::Lua; +use t::Util; + +$ENV{TEST_NGINX_NXSOCK} ||= html_dir(); + +plan tests => repeat_each() * (blocks() * 3); + +run_tests(); + +__DATA__ + +=== TEST 1: client.authenticate() sets the consumer +--- http_config eval: $t::Util::HttpConfig +--- config + location = /t { + content_by_lua_block { + local PDK = require "kong.pdk" + local pdk = PDK.new() + + pdk.client.authenticate(setmetatable({},{ + __tostring = function() return "this consumer" end, + }), + setmetatable({},{ + __tostring = function() return "this credential" end, + })) + + + ngx.say("consumer: ", tostring(pdk.client.get_consumer()), ", credential: ", tostring(pdk.client.get_credential())) + } + } +--- request +GET /t +--- response_body +consumer: this consumer, credential: this credential +--- no_error_log +[error] + + + +=== TEST 2: client.authenticate() allows setting nils +--- http_config eval: $t::Util::HttpConfig +--- config + location = /t { + content_by_lua_block { + local PDK = require "kong.pdk" + local pdk = PDK.new() + + pdk.client.authenticate(setmetatable({},{ + __tostring = function() return "this consumer" end, + }), + setmetatable({},{ + __tostring = function() return "this credential" end, + })) + + -- now set the actual nils to overwrite the above values + pdk.client.authenticate(nil, nil) + + ngx.say("consumer: ", tostring(pdk.client.get_consumer()), ", credential: ", tostring(pdk.client.get_credential())) + } + } +--- request +GET /t +--- response_body +consumer: nil, credential: nil +--- no_error_log +[error] + + + +=== TEST 3: client.authenticate() only accepts table as consumer +--- http_config eval: $t::Util::HttpConfig +--- config + location = /t { + content_by_lua_block { + local PDK = require "kong.pdk" + local pdk = PDK.new() + + local pok, err = pcall(pdk.client.authenticate, "not_a_proper_consumer") + + ngx.say(tostring(err)) + } + } +--- request +GET /t +--- response_body +consumer must be a table or nil +--- no_error_log +[error] + + + +=== TEST 4: client.authenticate() only accepts table as credential +--- http_config eval: $t::Util::HttpConfig +--- config + location = /t { + content_by_lua_block { + local PDK = require "kong.pdk" + local pdk = PDK.new() + + local pok, err = pcall(pdk.client.authenticate, nil, "not_a_proper_credential") + + ngx.say(tostring(err)) + } + } +--- request +GET /t +--- response_body +credential must be a table or nil +--- no_error_log +[error] diff --git a/t/01-pdk/13-router/00-phase_checks.t b/t/01-pdk/13-router/00-phase_checks.t new file mode 100644 index 000000000000..166643e41ec6 --- /dev/null +++ b/t/01-pdk/13-router/00-phase_checks.t @@ -0,0 +1,105 @@ +use strict; +use warnings FATAL => 'all'; +use Test::Nginx::Socket::Lua; +use t::Util; + +$ENV{TEST_NGINX_NXSOCK} ||= html_dir(); + +plan tests => repeat_each() * (blocks() * 2); + +run_tests(); + +__DATA__ + +=== TEST 1: verify phase checking in kong.router +--- http_config eval +qq{ + $t::Util::HttpConfig + + server { + listen unix:$ENV{TEST_NGINX_NXSOCK}/nginx.sock; + + location / { + return 200; + } + } + + init_worker_by_lua_block { + + -- mock kong.runloop.balancer + package.loaded["kong.runloop.balancer"] = { + get_upstream_by_name = function(name) + if name == "my_upstream" then + return {} + end + end + } + + phases = require("kong.pdk.private.phases").phases + + phase_check_module = "router" + phase_check_data = { + { + method = "get_route", + args = { }, + init_worker = "forced false", + certificate = "pending", + rewrite = "forced false", + access = true, + header_filter = true, + body_filter = true, + log = true, + admin_api = "forced false", + }, { + method = "get_service", + args = { }, + init_worker = "forced false", + certificate = "pending", + rewrite = "forced false", + access = true, + header_filter = true, + body_filter = true, + log = true, + admin_api = "forced false", + }, + } + + phase_check_functions(phases.init_worker) + } + + #ssl_certificate_by_lua_block { + # phase_check_functions(phases.certificate) + #} +} +--- config + location /t { + proxy_pass http://unix:$TEST_NGINX_NXSOCK/nginx.sock; + set $upstream_host 'example.com'; + set $upstream_uri '/t'; + set $upstream_scheme 'http'; + + rewrite_by_lua_block { + phase_check_functions(phases.rewrite) + } + + access_by_lua_block { + phase_check_functions(phases.access) + phase_check_functions(phases.admin_api) + } + + header_filter_by_lua_block { + phase_check_functions(phases.header_filter) + } + + body_filter_by_lua_block { + phase_check_functions(phases.body_filter) + } + + log_by_lua_block { + phase_check_functions(phases.log) + } + } +--- request +GET /t +--- no_error_log +[error] diff --git a/t/01-pdk/13-router/01-get_route.t b/t/01-pdk/13-router/01-get_route.t new file mode 100644 index 000000000000..82ad7a8bc308 --- /dev/null +++ b/t/01-pdk/13-router/01-get_route.t @@ -0,0 +1,57 @@ +use strict; +use warnings FATAL => 'all'; +use Test::Nginx::Socket::Lua; +use t::Util; + +$ENV{TEST_NGINX_NXSOCK} ||= html_dir(); + +plan tests => repeat_each() * (blocks() * 3); + +run_tests(); + +__DATA__ + +=== TEST 1: router.get_route() returns selected route +--- http_config eval: $t::Util::HttpConfig +--- config + location = /t { + content_by_lua_block { + ngx.ctx.route = setmetatable({},{ + __tostring = function() return "this route" end, + }) + + local PDK = require "kong.pdk" + local pdk = PDK.new() + + ngx.say("route: ", tostring(pdk.router.get_route())) + } + } +--- request +GET /t +--- response_body +route: this route +--- no_error_log +[error] + + + +=== TEST 2: router.get_route() returns nil if not set +--- http_config eval: $t::Util::HttpConfig +--- config + location = /t { + content_by_lua_block { + ngx.ctx.route = nil + + local PDK = require "kong.pdk" + local pdk = PDK.new() + + ngx.say("route: ", tostring(pdk.router.get_route())) + } + } +--- request +GET /t +--- response_body +route: nil +--- no_error_log +[error] + diff --git a/t/01-pdk/13-router/02-get_service.t b/t/01-pdk/13-router/02-get_service.t new file mode 100644 index 000000000000..9020329edc63 --- /dev/null +++ b/t/01-pdk/13-router/02-get_service.t @@ -0,0 +1,57 @@ +use strict; +use warnings FATAL => 'all'; +use Test::Nginx::Socket::Lua; +use t::Util; + +$ENV{TEST_NGINX_NXSOCK} ||= html_dir(); + +plan tests => repeat_each() * (blocks() * 3); + +run_tests(); + +__DATA__ + +=== TEST 1: router.get_service() returns selected service +--- http_config eval: $t::Util::HttpConfig +--- config + location = /t { + content_by_lua_block { + ngx.ctx.service = setmetatable({},{ + __tostring = function() return "this service" end, + }) + + local PDK = require "kong.pdk" + local pdk = PDK.new() + + ngx.say("service: ", tostring(pdk.router.get_service())) + } + } +--- request +GET /t +--- response_body +service: this service +--- no_error_log +[error] + + + +=== TEST 2: router.get_service() returns nil if not set +--- http_config eval: $t::Util::HttpConfig +--- config + location = /t { + content_by_lua_block { + ngx.ctx.service = nil + + local PDK = require "kong.pdk" + local pdk = PDK.new() + + ngx.say("service: ", tostring(pdk.router.get_service())) + } + } +--- request +GET /t +--- response_body +service: nil +--- no_error_log +[error] +