From 95bfe6a75cc3d49780c13e9bd54f481f8e54a427 Mon Sep 17 00:00:00 2001 From: Aapo Talvensaari Date: Thu, 14 Feb 2019 17:42:38 -0200 Subject: [PATCH] feat(api) restrict data manipulation operations on `off` db with errors --- kong/api/endpoints.lua | 8 +- kong/api/init.lua | 5 + .../04-admin_api/14-off_spec.lua | 182 ++++++++++++++++++ 3 files changed, 193 insertions(+), 2 deletions(-) create mode 100644 spec/02-integration/04-admin_api/14-off_spec.lua diff --git a/kong/api/endpoints.lua b/kong/api/endpoints.lua index c40cbd6eecfd..a5d223f7f6e8 100644 --- a/kong/api/endpoints.lua +++ b/kong/api/endpoints.lua @@ -31,6 +31,7 @@ local ERRORS_HTTP_CODES = { [Errors.codes.INVALID_SIZE] = 400, [Errors.codes.INVALID_UNIQUE] = 400, [Errors.codes.INVALID_OPTIONS] = 400, + [Errors.codes.OPERATION_UNSUPPORTED] = 405, } @@ -113,8 +114,11 @@ local function handle_error(err_t) return app_helpers.yield_error(err_t) end - local body = utils.get_default_exit_body(status, err_t) - return kong.response.exit(status, body) + if err_t.code == Errors.codes.OPERATION_UNSUPPORTED then + return kong.response.exit(status, err_t) + end + + return kong.response.exit(status, utils.get_default_exit_body(status, err_t)) end diff --git a/kong/api/init.lua b/kong/api/init.lua index 72527ad964ef..ab0724c3c0f9 100644 --- a/kong/api/init.lua +++ b/kong/api/init.lua @@ -83,6 +83,11 @@ local function new_db_on_error(self) return kong.response.exit(404, err) end + if err.code == Errors.codes.OPERATION_UNSUPPORTED then + kong.log.err(err) + return kong.response.exit(405, err) + end + if err.code == Errors.codes.PRIMARY_KEY_VIOLATION or err.code == Errors.codes.UNIQUE_VIOLATION then diff --git a/spec/02-integration/04-admin_api/14-off_spec.lua b/spec/02-integration/04-admin_api/14-off_spec.lua new file mode 100644 index 000000000000..d2747c7c3f03 --- /dev/null +++ b/spec/02-integration/04-admin_api/14-off_spec.lua @@ -0,0 +1,182 @@ +local cjson = require "cjson" +local utils = require "kong.tools.utils" +local helpers = require "spec.helpers" +local Errors = require "kong.db.errors" + + +local function it_content_types(title, fn) + local test_form_encoded = fn("application/x-www-form-urlencoded") + local test_multipart = fn("multipart/form-data") + local test_json = fn("application/json") + + it(title .. " with application/www-form-urlencoded", test_form_encoded) + it(title .. " with multipart/form-data", test_multipart) + it(title .. " with application/json", test_json) +end + +describe("Admin API #off", function() + local client + + lazy_setup(function() + assert(helpers.start_kong({ + database = "off", + })) + end) + + lazy_teardown(function() + helpers.stop_kong(nil, true) + end) + + before_each(function() + client = assert(helpers.admin_client()) + end) + + after_each(function() + if client then + client:close() + end + end) + + describe("/routes", function() + describe("POST", function() + it_content_types("doesn't allow to creates a route", function(content_type) + return function() + if content_type == "multipart/form-data" then + -- the client doesn't play well with this + return + end + + local res = client:post("/routes", { + body = { + protocols = { "http" }, + hosts = { "my.route.com" }, + service = { id = utils.uuid() }, + }, + headers = { ["Content-Type"] = content_type } + }) + local body = assert.res_status(405, res) + local json = cjson.decode(body) + assert.same({ + code = Errors.codes.OPERATION_UNSUPPORTED, + name = Errors.names[Errors.codes.OPERATION_UNSUPPORTED], + message = "cannot create 'routes' entities when not using a database", + }, json) + end + end) + + it_content_types("doesn't allow to creates a complex route", function(content_type) + return function() + if content_type == "multipart/form-data" then + -- the client doesn't play well with this + return + end + + local res = client:post("/routes", { + body = { + protocols = { "http" }, + methods = { "GET", "POST", "PATCH" }, + hosts = { "foo.api.com", "bar.api.com" }, + paths = { "/foo", "/bar" }, + service = { id = utils.uuid() }, + }, + headers = { ["Content-Type"] = content_type } + }) + + local body = assert.res_status(405, res) + local json = cjson.decode(body) + assert.same({ + code = Errors.codes.OPERATION_UNSUPPORTED, + name = Errors.names[Errors.codes.OPERATION_UNSUPPORTED], + message = "cannot create 'routes' entities when not using a database", + }, json) + end + end) + end) + + describe("GET", function() + describe("errors", function() + it("handles invalid offsets", function() + local res = client:get("/routes", { query = { offset = "x" } }) + local body = assert.res_status(400, res) + assert.same({ + code = Errors.codes.INVALID_OFFSET, + name = "invalid offset", + message = "'x' is not a valid offset: bad base64 encoding" + }, cjson.decode(body)) + + res = client:get("/routes", { query = { offset = "|potato|" } }) + body = assert.res_status(400, res) + + local json = cjson.decode(body) + json.message = nil + + assert.same({ + code = Errors.codes.INVALID_OFFSET, + name = "invalid offset", + }, json) + end) + end) + end) + + it("returns HTTP 405 on invalid method", function() + local methods = { "DELETE", "PUT", "PATCH", "POST" } + for i = 1, #methods do + local res = assert(client:send { + method = methods[i], + path = "/routes", + body = { + paths = { "/" }, + service = { id = utils.uuid() } + }, + headers = { + ["Content-Type"] = "application/json" + } + }) + local body = assert.response(res).has.status(405) + local json = cjson.decode(body) + if methods[i] == "POST" then + assert.same({ + code = Errors.codes.OPERATION_UNSUPPORTED, + name = Errors.names[Errors.codes.OPERATION_UNSUPPORTED], + message = "cannot create 'routes' entities when not using a database", + }, json) + + else + assert.same({ message = "Method not allowed" }, json) + end + end + end) + end) + + describe("/routes/{route}", function() + it("returns HTTP 405 on invalid method", function() + local methods = { "PUT", "POST" } + for i = 1, #methods do + local res = assert(client:send { + method = methods[i], + path = "/routes/" .. utils.uuid(), + body = { + paths = { "/" }, + service = { id = utils.uuid() } + }, + headers = { + ["Content-Type"] = "application/json" + } + }) + + local body = assert.response(res).has.status(405) + local json = cjson.decode(body) + if methods[i] ~= "POST" then + assert.same({ + code = Errors.codes.OPERATION_UNSUPPORTED, + name = Errors.names[Errors.codes.OPERATION_UNSUPPORTED], + message = "cannot create or update 'routes' entities when not using a database", + }, json) + + else + assert.same({ message = "Method not allowed" }, json) + end + end + end) + end) +end)