change(ua-restriction): allowlist and denylist can't be enabled at the same time (#9841)
diff --git a/apisix/plugins/ua-restriction.lua b/apisix/plugins/ua-restriction.lua
index ec74e75..577dc2b 100644
--- a/apisix/plugins/ua-restriction.lua
+++ b/apisix/plugins/ua-restriction.lua
@@ -22,11 +22,8 @@
local str_strip = stringx.strip
local re_find = ngx.re.find
-local MATCH_NONE = 0
-local MATCH_ALLOW = 1
-local MATCH_DENY = 2
-
-local lrucache_useragent = core.lrucache.new({ ttl = 300, count = 4096 })
+local lrucache_allow = core.lrucache.new({ ttl = 300, count = 4096 })
+local lrucache_deny = core.lrucache.new({ ttl = 300, count = 4096 })
local schema = {
type = "object",
@@ -58,6 +55,10 @@
default = "Not allowed"
},
},
+ oneOf = {
+ {required = {"allowlist"}},
+ {required = {"denylist"}}
+ }
}
local plugin_name = "ua-restriction"
@@ -69,27 +70,56 @@
schema = schema,
}
-local function match_user_agent(user_agent, conf)
- user_agent = str_strip(user_agent)
- if conf.allowlist then
- for _, rule in ipairs(conf.allowlist) do
+local function check_with_allow_list(user_agents, allowlist)
+ local check = function (user_agent)
+ user_agent = str_strip(user_agent)
+
+ for _, rule in ipairs(allowlist) do
if re_find(user_agent, rule, "jo") then
- return MATCH_ALLOW
+ return true
end
end
+ return false
end
- if conf.denylist then
- for _, rule in ipairs(conf.denylist) do
- if re_find(user_agent, rule, "jo") then
- return MATCH_DENY
+ if type(user_agents) == "table" then
+ for _, v in ipairs(user_agents) do
+ if lrucache_allow(v, allowlist, check, v) then
+ return true
end
end
+ return false
+ else
+ return lrucache_allow(user_agents, allowlist, check, user_agents)
end
-
- return MATCH_NONE
end
+
+local function check_with_deny_list(user_agents, denylist)
+ local check = function (user_agent)
+ user_agent = str_strip(user_agent)
+
+ for _, rule in ipairs(denylist) do
+ if re_find(user_agent, rule, "jo") then
+ return false
+ end
+ end
+ return true
+ end
+
+ if type(user_agents) == "table" then
+ for _, v in ipairs(user_agents) do
+ if lrucache_deny(v, denylist, check, v) then
+ return false
+ end
+ end
+ return true
+ else
+ return lrucache_deny(user_agents, denylist, check, user_agents)
+ end
+end
+
+
function _M.check_schema(conf)
local ok, err = core.schema.check(schema, conf)
@@ -118,6 +148,7 @@
return true
end
+
function _M.access(conf, ctx)
local user_agent = core.request.header(ctx, "User-Agent")
@@ -128,21 +159,16 @@
return 403, { message = conf.message }
end
end
- local match = MATCH_NONE
- if type(user_agent) == "table" then
- for _, v in ipairs(user_agent) do
- if type(v) == "string" then
- match = lrucache_useragent(v, conf, match_user_agent, v, conf)
- if match > MATCH_ALLOW then
- break
- end
- end
- end
+
+ local is_passed
+
+ if conf.allowlist then
+ is_passed = check_with_allow_list(user_agent, conf.allowlist)
else
- match = lrucache_useragent(user_agent, conf, match_user_agent, user_agent, conf)
+ is_passed = check_with_deny_list(user_agent, conf.denylist)
end
- if match > MATCH_ALLOW then
+ if not is_passed then
return 403, { message = conf.message }
end
end
diff --git a/docs/en/latest/plugins/ua-restriction.md b/docs/en/latest/plugins/ua-restriction.md
index d9dce2b..538bf99 100644
--- a/docs/en/latest/plugins/ua-restriction.md
+++ b/docs/en/latest/plugins/ua-restriction.md
@@ -43,7 +43,7 @@
:::note
-Both `allowlist` and `denylist` can be used on their own. If they are used together, the `allowlist` matches before the `denylist`.
+Both `allowlist` and `denylist` can't be used at the same time.
:::
diff --git a/docs/zh/latest/plugins/ua-restriction.md b/docs/zh/latest/plugins/ua-restriction.md
index 3f31e09..d8e21b6 100644
--- a/docs/zh/latest/plugins/ua-restriction.md
+++ b/docs/zh/latest/plugins/ua-restriction.md
@@ -43,7 +43,7 @@
:::note
-`allowlist` 和 `denylist` 可以同时启用。同时启用时,插件会根据 `User-Agent` 先检查 `allowlist`,再检查 `denylist`。
+`allowlist` 和 `denylist` 不可以同时启用。
:::
diff --git a/t/plugin/ua-restriction.t b/t/plugin/ua-restriction.t
index 0e8a954..56f07b3 100644
--- a/t/plugin/ua-restriction.t
+++ b/t/plugin/ua-restriction.t
@@ -38,13 +38,12 @@
__DATA__
-=== TEST 1: set allowlist, denylist, bypass_missing and user-defined message
+=== TEST 1: set both allowlist and denylist
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.ua-restriction")
local conf = {
- bypass_missing = true,
allowlist = {
"my-bot1",
"my-bot2"
@@ -53,18 +52,18 @@
"my-bot1",
"my-bot2"
},
- message = "User-Agent Forbidden",
}
local ok, err = plugin.check_schema(conf)
if not ok then
ngx.say(err)
+ return
end
ngx.say(require("toolkit.json").encode(conf))
}
}
--- response_body
-{"allowlist":["my-bot1","my-bot2"],"bypass_missing":true,"denylist":["my-bot1","my-bot2"],"message":"User-Agent Forbidden"}
+value should match only one schema, but matches both schemas 1 and 2
@@ -216,7 +215,19 @@
-=== TEST 9: hit route and user-agent match denylist regex
+=== TEST 9: hit route and user-agent in denylist with reverse order multiple user-agent
+--- request
+GET /hello
+--- more_headers
+User-Agent:my-bot2
+User-Agent:my-bot1
+--- error_code: 403
+--- response_body
+{"message":"Not allowed"}
+
+
+
+=== TEST 10: hit route and user-agent match denylist regex
--- request
GET /hello
--- more_headers
@@ -227,7 +238,7 @@
-=== TEST 10: hit route and user-agent not in denylist
+=== TEST 11: hit route and user-agent not in denylist
--- request
GET /hello
--- more_headers
@@ -238,7 +249,7 @@
-=== TEST 11: set allowlist
+=== TEST 12: set allowlist
--- config
location /t {
content_by_lua_block {
@@ -275,7 +286,7 @@
-=== TEST 12: hit route and user-agent in allowlist
+=== TEST 13: hit route and user-agent in allowlist
--- request
GET /hello
--- more_headers
@@ -286,7 +297,7 @@
-=== TEST 13: hit route and user-agent match allowlist regex
+=== TEST 14: hit route and user-agent match allowlist regex
--- request
GET /hello
--- more_headers
@@ -297,209 +308,40 @@
-=== TEST 14: hit route and user-agent not in allowlist
+=== TEST 15: hit route and user-agent not in allowlist
--- request
GET /hello
--- more_headers
User-Agent:foo/bar
---- error_code: 200
---- response_body
-hello world
-
-
-
-=== TEST 15: set config: user-agent in both allowlist and denylist
---- 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,
- [[{
- "uri": "/hello",
- "upstream": {
- "type": "roundrobin",
- "nodes": {
- "127.0.0.1:1980": 1
- }
- },
- "plugins": {
- "ua-restriction": {
- "allowlist": [
- "foo/bar",
- "(Baiduspider)/(\\d+)\\.(\\d+)"
- ],
- "denylist": [
- "foo/bar",
- "(Baiduspider)/(\\d+)\\.(\\d+)"
- ]
- }
- }
- }]]
- )
-
- if code >= 300 then
- ngx.status = code
- end
- ngx.say(body)
- }
- }
---- response_body
-passed
-
-
-
-=== TEST 16: hit route and user-agent in both allowlist and denylist, pass(part 1)
---- request
-GET /hello
---- more_headers
-User-Agent:foo/bar
---- error_code: 200
---- response_body
-hello world
-
-
-
-=== TEST 17: hit route and user-agent in both allowlist and denylist, pass(part 2)
---- request
-GET /hello
---- more_headers
-User-Agent:Baiduspider/1.0
---- error_code: 200
---- response_body
-hello world
-
-
-
-=== TEST 18: bypass_missing test, using default, reset conf(part1)
---- 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,
- [[{
- "uri": "/hello",
- "upstream": {
- "type": "roundrobin",
- "nodes": {
- "127.0.0.1:1980": 1
- }
- },
- "plugins": {
- "ua-restriction": {
- }
- }
- }]]
- )
-
- if code >= 300 then
- ngx.status = code
- end
- ngx.say(body)
- }
- }
---- response_body
-passed
-
-
-
-=== TEST 19: bypass_missing test, using default, send request without User-Agent(part2)
---- request
-GET /hello
--- error_code: 403
--- response_body
{"message":"Not allowed"}
-=== TEST 20: bypass_missing test, set to true(part1)
---- 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,
- [[{
- "uri": "/hello",
- "upstream": {
- "type": "roundrobin",
- "nodes": {
- "127.0.0.1:1980": 1
- }
- },
- "plugins": {
- "ua-restriction": {
- "bypass_missing": true
- }
- }
- }]]
- )
-
- if code >= 300 then
- ngx.status = code
- end
- ngx.say(body)
- }
- }
---- response_body
-passed
-
-
-
-=== TEST 21: bypass_missing test, set to true, send request without User-Agent(part2)
+=== TEST 16: hit route and user-agent in allowlist with multiple user-agent
--- request
GET /hello
---- error_code: 200
+--- more_headers
+User-Agent:foo/bar
+User-Agent:my-bot1
--- response_body
hello world
-=== TEST 22: bypass_missing test, set to false(part1)
---- 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,
- [[{
- "uri": "/hello",
- "upstream": {
- "type": "roundrobin",
- "nodes": {
- "127.0.0.1:1980": 1
- }
- },
- "plugins": {
- "ua-restriction": {
- "bypass_missing": false
- }
- }
- }]]
- )
-
- if code >= 300 then
- ngx.status = code
- end
- ngx.say(body)
- }
- }
---- response_body
-passed
-
-
-
-=== TEST 23: bypass_missing test, set to false, send request without User-Agent(part2)
+=== TEST 17: hit route and user-agent in allowlist with reverse order multiple user-agent
--- request
GET /hello
---- error_code: 403
+--- more_headers
+User-Agent:my-bot1
+User-Agent:foo/bar
--- response_body
-{"message":"Not allowed"}
+hello world
-=== TEST 24: message that do not reach the minimum range
+=== TEST 18: message that do not reach the minimum range
--- config
location /t {
content_by_lua_block {
@@ -530,7 +372,7 @@
-=== TEST 25: exceeds the maximum limit of message
+=== TEST 19: exceeds the maximum limit of message
--- config
location /t {
content_by_lua_block {
@@ -568,7 +410,7 @@
-=== TEST 26: set custom message
+=== TEST 20: set custom message
--- config
location /t {
content_by_lua_block {
@@ -606,7 +448,7 @@
-=== TEST 27: test custom message
+=== TEST 21: test custom message
--- request
GET /hello
--- more_headers
@@ -617,7 +459,7 @@
-=== TEST 28: test remove ua-restriction, add denylist(part 1)
+=== TEST 22: test remove ua-restriction, add denylist(part 1)
--- config
location /enable {
content_by_lua_block {
@@ -656,7 +498,7 @@
-=== TEST 29: test remove ua-restriction, fail(part 2)
+=== TEST 23: test remove ua-restriction, fail(part 2)
--- request
GET /hello
--- more_headers
@@ -667,7 +509,7 @@
-=== TEST 30: test remove ua-restriction, remove plugin(part 3)
+=== TEST 24: test remove ua-restriction, remove plugin(part 3)
--- config
location /disable {
content_by_lua_block {
@@ -701,7 +543,7 @@
-=== TEST 31: test remove ua-restriction, check spider User-Agent(part 4)
+=== TEST 25: test remove ua-restriction, check spider User-Agent(part 4)
--- request
GET /hello
--- more_headers
@@ -711,7 +553,7 @@
-=== TEST 32: set disable=true
+=== TEST 26: set disable=true
--- config
location /t {
content_by_lua_block {
@@ -744,7 +586,7 @@
-=== TEST 33: the element in allowlist is null
+=== TEST 27: the element in allowlist is null
--- config
location /t {
content_by_lua_block {
@@ -771,7 +613,7 @@
-=== TEST 34: the element in denylist is null
+=== TEST 28: the element in denylist is null
--- config
location /t {
content_by_lua_block {
@@ -795,3 +637,125 @@
--- response_body
property "denylist" validation failed: wrong type: expected array, got table
done
+
+
+
+=== TEST 29: test both allowlist and denylist are not exist
+--- 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,
+ [[{
+ "uri": "/hello",
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:1980": 1
+ }
+ },
+ "plugins": {
+ "ua-restriction": {
+ }
+ }
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.print(body)
+ }
+ }
+--- error_code: 400
+--- response_body
+{"error_msg":"failed to check the configuration of plugin ua-restriction err: value should match only one schema, but matches none"}
+
+
+
+=== TEST 30: test bypass_missing
+--- 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,
+ [[{
+ "uri": "/hello",
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:1980": 1
+ }
+ },
+ "plugins": {
+ "ua-restriction": {
+ "allowlist": [
+ "my-bot1"
+ ]
+ }
+ }
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 31: hit
+--- request
+GET /hello
+--- error_code: 403
+--- response_body
+{"message":"Not allowed"}
+
+
+
+=== TEST 32: test bypass_missing with true
+--- 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,
+ [[{
+ "uri": "/hello",
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:1980": 1
+ }
+ },
+ "plugins": {
+ "ua-restriction": {
+ "bypass_missing": true,
+ "denylist": [
+ "my-bot1"
+ ]
+ }
+ }
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 33: hit
+--- request
+GET /hello
+--- response_body
+hello world