From df6ec15ce9e5bb4c50ad0d84a9897803be3c4c51 Mon Sep 17 00:00:00 2001 From: spacewander Date: Wed, 25 May 2022 17:52:46 +0800 Subject: [PATCH] feat: allow customizing response in the plugin Signed-off-by: spacewander --- apisix/plugin.lua | 20 ++++++- apisix/schema_def.lua | 11 ++++ docs/en/latest/terminology/plugin.md | 24 ++++++++ docs/zh/latest/terminology/plugin.md | 24 ++++++++ t/admin/plugins.t | 4 +- t/plugin/plugin.t | 87 ++++++++++++++++++++++++++++ 6 files changed, 167 insertions(+), 3 deletions(-) diff --git a/apisix/plugin.lua b/apisix/plugin.lua index da3b848f33a2..fc76b2546ba2 100644 --- a/apisix/plugin.lua +++ b/apisix/plugin.lua @@ -142,6 +142,16 @@ local function load_plugin(name, plugins_list, plugin_type) end properties.disable = plugin_injected_schema.disable + + if properties._meta then + core.log.error("invalid plugin [", name, + "]: found forbidden '_meta' field in the schema") + return + end + + properties._meta = plugin_injected_schema._meta + -- new injected fields should be added under `_meta` + plugin.schema['$comment'] = plugin_injected_schema['$comment'] end @@ -743,11 +753,19 @@ function _M.run_plugin(phase, plugins, api_ctx) local phase_func = plugins[i][phase] if phase_func then plugin_run = true - local code, body = phase_func(plugins[i + 1], api_ctx) + local conf = plugins[i + 1] + local code, body = phase_func(conf, api_ctx) if code or body then if is_http then if code >= 400 then core.log.warn(plugins[i].name, " exits with http status code ", code) + + if conf._meta and conf._meta.error_response then + -- Whether or not the original error message is output, + -- always return the configured message + -- so the caller can't guess the real error + body = conf._meta.error_response + end end core.response.exit(code, body) diff --git a/apisix/schema_def.lua b/apisix/schema_def.lua index 091785d37944..767c2fa63503 100644 --- a/apisix/schema_def.lua +++ b/apisix/schema_def.lua @@ -914,6 +914,17 @@ _M.plugin_injected_schema = { ["$comment"] = "this is a mark for our injected plugin schema", disable = { type = "boolean", + }, + _meta = { + type = "object", + properties = { + error_response = { + oneOf = { + { type = "string" }, + { type = "object" }, + } + }, + } } } diff --git a/docs/en/latest/terminology/plugin.md b/docs/en/latest/terminology/plugin.md index 7e94bf0b1555..4bb12a4e3146 100644 --- a/docs/en/latest/terminology/plugin.md +++ b/docs/en/latest/terminology/plugin.md @@ -72,6 +72,30 @@ A warning level log as shown below indicates that the request was rejected by th ip-restriction exits with http status code 403 ``` +## Plugin Common Configuration + +Some common configurations can be applied to the plugin configuration. For example, + +```json +{ + "jwt-auth": { + "_meta": { + "error_response": { + "message": "Missing credential in request" + } + } + } +} +``` + +the configuration above means customizing the error response from the jwt-auth plugin to '{"message": "Missing credential in request"}'. + +### Plugin Common Configuration Under `_meta` + +| Name | Type | Description | +|--------------|------|-------------| +| error_response | string/object | Custom error response | + ## Hot Reload APISIX Plugins are hot-loaded. This means that there is no need to restart the service if you add, delete, modify plugins, or even if you update the plugin code. To hot-reload, you can send an HTTP request through the [Admin API](../admin-api.md): diff --git a/docs/zh/latest/terminology/plugin.md b/docs/zh/latest/terminology/plugin.md index daddd168e787..c57767565d4f 100644 --- a/docs/zh/latest/terminology/plugin.md +++ b/docs/zh/latest/terminology/plugin.md @@ -66,6 +66,30 @@ local _M = { 如果一个请求因为某个插件而被拒绝,会有类似这样的 warn 日志:`ip-restriction exits with http status code 403`。 +## 插件通用配置 + +一些通用的配置可以应用于插件配置。比如说。 + +````json +{ + "jwt-auth": { + "_meta": { + "error_response": { + "message": "Missing credential in request" + } + } + } +} +``` + +上面的配置意味着将 jwt-auth 插件的错误响应自定义为 '{"message": "Missing credential in request"}'。 + +### 在 `_meta` 下的插件通用配置 + +| 名称 | 类型 | 描述 | +|--------------|------|----------------| +| error_response | string/object | 自定义错误响应 | + ## 热加载 APISIX 的插件是热加载的,不管你是新增、删除还是修改插件,都不需要重启服务。 diff --git a/t/admin/plugins.t b/t/admin/plugins.t index 9581c5389caf..2bfb5fee30ce 100644 --- a/t/admin/plugins.t +++ b/t/admin/plugins.t @@ -265,7 +265,7 @@ plugins: } } --- response_body eval -qr/\{"metadata_schema":\{"properties":\{"ikey":\{"minimum":0,"type":"number"\},"skey":\{"type":"string"\}\},"required":\["ikey","skey"\],"type":"object"\},"priority":0,"schema":\{"\$comment":"this is a mark for our injected plugin schema","properties":\{"disable":\{"type":"boolean"\},"i":\{"minimum":0,"type":"number"\},"ip":\{"type":"string"\},"port":\{"type":"integer"\},"s":\{"type":"string"\},"t":\{"minItems":1,"type":"array"\}\},"required":\["i"\],"type":"object"\},"version":0.1\}/ +qr/\{"metadata_schema":\{"properties":\{"ikey":\{"minimum":0,"type":"number"\},"skey":\{"type":"string"\}\},"required":\["ikey","skey"\],"type":"object"\},"priority":0,"schema":\{"\$comment":"this is a mark for our injected plugin schema","properties":\{"_meta":\{"properties":\{"error_response":\{"oneOf":\[\{"type":"string"\},\{"type":"object"\}\]\}\},"type":"object"\},"disable":\{"type":"boolean"\},"i":\{"minimum":0,"type":"number"\},"ip":\{"type":"string"\},"port":\{"type":"integer"\},"s":\{"type":"string"\},"t":\{"minItems":1,"type":"array"\}\},"required":\["i"\],"type":"object"\},"version":0.1\}/ @@ -366,7 +366,7 @@ qr/\{"properties":\{"password":\{"type":"string"\},"username":\{"type":"string"\ } } --- response_body -{"priority":1003,"schema":{"$comment":"this is a mark for our injected plugin schema","properties":{"burst":{"minimum":0,"type":"integer"},"conn":{"exclusiveMinimum":0,"type":"integer"},"default_conn_delay":{"exclusiveMinimum":0,"type":"number"},"disable":{"type":"boolean"},"key":{"type":"string"},"key_type":{"default":"var","enum":["var","var_combination"],"type":"string"},"only_use_default_delay":{"default":false,"type":"boolean"}},"required":["conn","burst","default_conn_delay","key"],"type":"object"},"version":0.1} +{"priority":1003,"schema":{"$comment":"this is a mark for our injected plugin schema","properties":{"_meta":{"properties":{"error_response":{"oneOf":[{"type":"string"},{"type":"object"}]}},"type":"object"},"burst":{"minimum":0,"type":"integer"},"conn":{"exclusiveMinimum":0,"type":"integer"},"default_conn_delay":{"exclusiveMinimum":0,"type":"number"},"disable":{"type":"boolean"},"key":{"type":"string"},"key_type":{"default":"var","enum":["var","var_combination"],"type":"string"},"only_use_default_delay":{"default":false,"type":"boolean"}},"required":["conn","burst","default_conn_delay","key"],"type":"object"},"version":0.1} diff --git a/t/plugin/plugin.t b/t/plugin/plugin.t index 009a51187949..e45f5d5f7814 100644 --- a/t/plugin/plugin.t +++ b/t/plugin/plugin.t @@ -219,3 +219,90 @@ GET /apisix/plugin/blah } --- response_body ok + + + +=== TEST 7: plugin with custom error message +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "jwt-auth": { + "_meta": { + "error_response": { + "message":"Missing credential in request" + } + } + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- response_body +passed + + + +=== TEST 8: verify, missing token +--- request +GET /hello +--- error_code: 401 +--- response_body +{"message":"Missing credential in request"} + + + +=== TEST 9: validate custom error message configuration +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + + for _, case in ipairs({ + {input = true}, + {input = { + error_response = true + }}, + {input = { + error_response = "OK" + }}, + }) do + local code, body = t('/apisix/admin/global_rules/1', + ngx.HTTP_PUT, + { + plugins = { + ["jwt-auth"] = { + _meta = case.input + } + } + } + ) + if code >= 300 then + ngx.print(body) + else + ngx.say(body) + end + end + } + } +--- response_body +{"error_msg":"failed to check the configuration of plugin jwt-auth err: property \"_meta\" validation failed: wrong type: expected object, got boolean"} +{"error_msg":"failed to check the configuration of plugin jwt-auth err: property \"_meta\" validation failed: property \"error_response\" validation failed: value should match only one schema, but matches none"} +passed