This is an automated email from the ASF dual-hosted git repository.
nic-6443 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new 72b247542 fix(admin): duplicate consumer authentication keys are
silently accepted (#13529)
72b247542 is described below
commit 72b247542da6f73ff123b48562e2b84e10592c90
Author: Nic <[email protected]>
AuthorDate: Fri Jul 17 10:18:40 2026 +0800
fix(admin): duplicate consumer authentication keys are silently accepted
(#13529)
---
apisix/admin/consumers.lua | 6 +
apisix/admin/credentials.lua | 14 +-
apisix/admin/resource.lua | 7 +-
apisix/consumer.lua | 109 +++++++++++++
t/admin/consumers.t | 361 +++++++++++++++++++++++++++++++++++++++++++
t/admin/credentials.t | 316 ++++++++++++++++++++++++++++++++++++-
6 files changed, 807 insertions(+), 6 deletions(-)
diff --git a/apisix/admin/consumers.lua b/apisix/admin/consumers.lua
index 32b1a9440..79fbca2fc 100644
--- a/apisix/admin/consumers.lua
+++ b/apisix/admin/consumers.lua
@@ -18,6 +18,7 @@ local core = require("apisix.core")
local plugins = require("apisix.admin.plugins")
local plugins_encrypt_conf = require("apisix.admin.plugins").encrypt_conf
local resource = require("apisix.admin.resource")
+local apisix_consumer = require("apisix.consumer")
local function check_conf(username, conf, need_username, schema, opts)
@@ -36,6 +37,11 @@ local function check_conf(username, conf, need_username,
schema, opts)
if not ok then
return nil, {error_msg = "invalid plugins configuration: " .. err}
end
+
+ ok, err = apisix_consumer.check_duplicate_key(conf.plugins,
conf.username)
+ if not ok then
+ return nil, {error_msg = err}
+ end
end
if conf.group_id and not opts.skip_references_check then
diff --git a/apisix/admin/credentials.lua b/apisix/admin/credentials.lua
index d15b78eca..fcd10b51c 100644
--- a/apisix/admin/credentials.lua
+++ b/apisix/admin/credentials.lua
@@ -18,10 +18,12 @@ local core = require("apisix.core")
local plugins = require("apisix.admin.plugins")
local plugin = require("apisix.plugin")
local resource = require("apisix.admin.resource")
+local apisix_consumer = require("apisix.consumer")
local plugins_encrypt_conf = require("apisix.admin.plugins").encrypt_conf
local pairs = pairs
-local function check_conf(_id, conf, _need_id, schema)
+local function check_conf(id, conf, _need_id, schema, opts)
+ opts = opts or {}
local ok, err = core.schema.check(schema, conf)
if not ok then
return nil, {error_msg = "invalid configuration: " .. err}
@@ -42,6 +44,16 @@ local function check_conf(_id, conf, _need_id, schema)
return nil, {error_msg = "only supports auth type plugins in
consumer credential"}
end
end
+
+ -- opts.sub_path is in the form of {consumer_name}/credentials or
+ -- {consumer_name}/credentials/{credential_id}
+ if opts.sub_path then
+ local consumer_name = core.utils.split_uri(opts.sub_path)[1]
+ ok, err = apisix_consumer.check_duplicate_key(conf.plugins,
consumer_name, id)
+ if not ok then
+ return nil, {error_msg = err}
+ end
+ end
end
return true, nil
diff --git a/apisix/admin/resource.lua b/apisix/admin/resource.lua
index ceef868b4..5de0a9beb 100644
--- a/apisix/admin/resource.lua
+++ b/apisix/admin/resource.lua
@@ -82,7 +82,7 @@ local function check_forbidden_properties(conf,
forbidden_properties)
end
-function _M:check_conf(id, conf, need_id, typ, allow_time)
+function _M:check_conf(id, conf, need_id, typ, allow_time, sub_path)
if self.name == "secrets" then
id = typ .. "/" .. id
end
@@ -124,7 +124,8 @@ function _M:check_conf(id, conf, need_id, typ, allow_time)
end
local conf_for_check = tbl_deepcopy(conf)
- local ok, err = self.checker(id, conf_for_check, need_id, self.schema,
{secret_type = typ})
+ local ok, err = self.checker(id, conf_for_check, need_id, self.schema,
+ {secret_type = typ, sub_path = sub_path})
if self.encrypt_conf then
self.encrypt_conf(id, conf)
@@ -254,7 +255,7 @@ function _M:put(id, conf, sub_path, args)
end
local need_id = not no_id_res[self.name]
- local ok, err = self:check_conf(id, conf, need_id, typ)
+ local ok, err = self:check_conf(id, conf, need_id, typ, nil, sub_path)
if not ok then
return 400, err
end
diff --git a/apisix/consumer.lua b/apisix/consumer.lua
index 9e3c2495c..e8877495f 100644
--- a/apisix/consumer.lua
+++ b/apisix/consumer.lua
@@ -24,6 +24,7 @@ local error = error
local ipairs = ipairs
local pairs = pairs
local type = type
+local tostring = tostring
local string_sub = string.sub
local consumers
@@ -292,6 +293,114 @@ function _M.find_consumer(plugin_name, key, key_value)
end
+-- The auth plugins below match the consumer by a unique key attribute, which
is
+-- the attribute that find_consumer() is called with at runtime. Duplicating
such
+-- keys across consumers/credentials makes the runtime matching ambiguous: the
+-- last loaded consumer silently wins.
+local plugin_unique_key_attrs = {
+ ["key-auth"] = "key",
+ ["basic-auth"] = "username",
+ ["jwt-auth"] = "key",
+ ["hmac-auth"] = "key_id",
+ ["ldap-auth"] = "user_dn",
+}
+
+
+local function get_auth_unique_keys(plugins_conf)
+ local keys
+ for plugin_name, plugin_conf in pairs(plugins_conf) do
+ local key_attr = plugin_unique_key_attrs[plugin_name]
+ if key_attr and type(plugin_conf) == "table" then
+ local key_value = plugin_conf[key_attr]
+ if type(key_value) == "string" then
+ if secret.is_secret_ref(key_value) then
+ core.log.info("skip duplicate check for the ", key_attr,
+ " of plugin ", plugin_name,
+ ": secret reference cannot be resolved at
write time")
+ else
+ keys = keys or {}
+ keys[plugin_name] = key_value
+ end
+ end
+ end
+ end
+
+ return keys
+end
+
+
+-- Reject the write when an auth plugin key in plugins_conf is already used by
+-- another consumer or credential, since the runtime consumer matching can not
+-- distinguish them.
+--
+-- The lookup goes through find_consumer(), i.e. the consumer data that this
+-- process has already watched and synced from etcd -- the same view the
+-- runtime uses for auth matching -- instead of pulling the full /consumers
+-- range from etcd on every admin write. The tradeoff is that the local view
+-- may lag the latest writes by one sync cycle, so a duplicate written moments
+-- earlier can slip through under rapid successive or concurrent writes. This
+-- is accepted: the check is best-effort protection against misconfiguration,
+-- and the authoritative behavior at runtime remains last-loaded-wins.
+--
+-- When writing a consumer, consumer_name is its username and credential_id is
+-- nil: a key owned by the consumer itself (inline or by its credentials) is
+-- not treated as a duplicate. When writing a credential, both consumer_name
+-- and credential_id are set: only the credential itself is skipped.
+function _M.check_duplicate_key(plugins_conf, consumer_name, credential_id)
+ if not plugins_conf then
+ return true
+ end
+
+ -- the /consumers watcher may not be initialized yet, e.g. the write
+ -- arrives right after the worker starts: degrade to allow, consistent
+ -- with the best-effort nature of this check
+ if not consumers then
+ return true
+ end
+
+ local in_keys = get_auth_unique_keys(plugins_conf)
+ if not in_keys then
+ return true
+ end
+
+ -- the credential id may come from the request payload as a number, while
+ -- the cached credential_id is taken from the etcd key as a string
+ if credential_id then
+ credential_id = tostring(credential_id)
+ end
+
+ for plugin_name, key_value in pairs(in_keys) do
+ local key_attr = plugin_unique_key_attrs[plugin_name]
+ local owner = _M.find_consumer(plugin_name, key_attr, key_value)
+ if owner then
+ local is_self
+ if credential_id then
+ -- a credential does not conflict with itself
+ is_self = owner.consumer_name == consumer_name
+ and owner.credential_id == credential_id
+ else
+ -- a consumer does not conflict with itself or its own
credentials
+ is_self = owner.consumer_name == consumer_name
+ end
+
+ if not is_self then
+ local owner_desc
+ if owner.credential_id then
+ owner_desc = "credential: " .. owner.credential_id ..
+ " of consumer: " .. owner.consumer_name
+ else
+ owner_desc = "consumer: " .. owner.consumer_name
+ end
+ return nil, "duplicate " .. key_attr .. " of plugin " ..
plugin_name
+ .. " found with " .. owner_desc
+ end
+ end
+ end
+
+ return true
+end
+
+
local function check_consumer(consumer, key)
local data_valid
local err
diff --git a/t/admin/consumers.t b/t/admin/consumers.t
index 2e9c5e206..a30a77cc5 100644
--- a/t/admin/consumers.t
+++ b/t/admin/consumers.t
@@ -360,3 +360,364 @@ GET /t
--- error_code: 400
--- response_body eval
qr/\{"error_msg":"the property is forbidden:.*"\}/
+
+
+
+=== TEST 12: add consumer case_a with key-auth key
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/consumers',
+ ngx.HTTP_PUT,
+ [[{
+ "username": "case_a",
+ "plugins": {
+ "key-auth": {
+ "key": "duplicate-check-key-1"
+ }
+ }
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- request
+GET /t
+--- response_body
+passed
+
+
+
+=== TEST 13: add consumer case_b with the same key-auth key, should fail
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ -- the duplicate check runs against the locally synced consumer
+ -- data, wait until the watcher catches up with the previous write
+ local find_consumer = require("apisix.consumer").find_consumer
+ for _ = 1, 100 do
+ if find_consumer("key-auth", "key", "duplicate-check-key-1")
then
+ break
+ end
+ ngx.sleep(0.05)
+ end
+
+ local code, body = t('/apisix/admin/consumers',
+ ngx.HTTP_PUT,
+ [[{
+ "username": "case_b",
+ "plugins": {
+ "key-auth": {
+ "key": "duplicate-check-key-1"
+ }
+ }
+ }]]
+ )
+
+ ngx.status = code
+ ngx.print(body)
+ }
+ }
+--- request
+GET /t
+--- error_code: 400
+--- response_body
+{"error_msg":"duplicate key of plugin key-auth found with consumer: case_a"}
+
+
+
+=== TEST 14: add consumer case_b with a different key-auth key, should success
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/consumers',
+ ngx.HTTP_PUT,
+ [[{
+ "username": "case_b",
+ "plugins": {
+ "key-auth": {
+ "key": "duplicate-check-key-2"
+ }
+ }
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- request
+GET /t
+--- response_body
+passed
+
+
+
+=== TEST 15: update consumer case_a with its own key unchanged, should success
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/consumers',
+ ngx.HTTP_PUT,
+ [[{
+ "username": "case_a",
+ "desc": "updated description",
+ "plugins": {
+ "key-auth": {
+ "key": "duplicate-check-key-1"
+ }
+ }
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- request
+GET /t
+--- response_body
+passed
+
+
+
+=== TEST 16: duplicate basic-auth username between consumers, should fail
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/consumers',
+ ngx.HTTP_PUT,
+ [[{
+ "username": "case_c",
+ "plugins": {
+ "basic-auth": {
+ "username": "case-user",
+ "password": "the-password"
+ }
+ }
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ return
+ end
+
+ -- the duplicate check runs against the locally synced consumer
+ -- data, wait until the watcher catches up with the previous write
+ local find_consumer = require("apisix.consumer").find_consumer
+ for _ = 1, 100 do
+ if find_consumer("basic-auth", "username", "case-user") then
+ break
+ end
+ ngx.sleep(0.05)
+ end
+
+ local code, body = t('/apisix/admin/consumers',
+ ngx.HTTP_PUT,
+ [[{
+ "username": "case_d",
+ "plugins": {
+ "basic-auth": {
+ "username": "case-user",
+ "password": "another-password"
+ }
+ }
+ }]]
+ )
+
+ ngx.status = code
+ ngx.print(body)
+ }
+ }
+--- request
+GET /t
+--- error_code: 400
+--- response_body
+{"error_msg":"duplicate username of plugin basic-auth found with consumer:
case_c"}
+
+
+
+=== TEST 17: store consumer with data_encryption explicitly enabled
+--- extra_yaml_config
+apisix:
+ data_encryption:
+ enable_encrypt_fields: true
+ keyring:
+ - qeddd145sfvddff3
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/consumers',
+ ngx.HTTP_PUT,
+ [[{
+ "username": "enc_case_a",
+ "plugins": {
+ "key-auth": {
+ "key": "duplicate-check-enc-key"
+ }
+ }
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- request
+GET /t
+--- response_body
+passed
+
+
+
+=== TEST 18: duplicate key against the stored encrypted consumer, should fail
+--- extra_yaml_config
+apisix:
+ data_encryption:
+ enable_encrypt_fields: true
+ keyring:
+ - qeddd145sfvddff3
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ -- the duplicate check runs against the locally synced consumer
+ -- data, wait until the watcher catches up with the previous write
+ local find_consumer = require("apisix.consumer").find_consumer
+ for _ = 1, 100 do
+ if find_consumer("key-auth", "key", "duplicate-check-enc-key")
then
+ break
+ end
+ ngx.sleep(0.05)
+ end
+
+ local code, body = t('/apisix/admin/consumers',
+ ngx.HTTP_PUT,
+ [[{
+ "username": "enc_case_b",
+ "plugins": {
+ "key-auth": {
+ "key": "duplicate-check-enc-key"
+ }
+ }
+ }]]
+ )
+
+ ngx.status = code
+ ngx.print(body)
+ }
+ }
+--- request
+GET /t
+--- error_code: 400
+--- response_body
+{"error_msg":"duplicate key of plugin key-auth found with consumer:
enc_case_a"}
+
+
+
+=== TEST 19: add consumer ldap_case_a with ldap-auth user_dn
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/consumers',
+ ngx.HTTP_PUT,
+ [[{
+ "username": "ldap_case_a",
+ "plugins": {
+ "ldap-auth": {
+ "user_dn":
"cn=duplicate-check,ou=users,dc=example,dc=org"
+ }
+ }
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- request
+GET /t
+--- response_body
+passed
+
+
+
+=== TEST 20: add consumer ldap_case_b with the same ldap-auth user_dn, should
fail
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ -- the duplicate check runs against the locally synced consumer
+ -- data, wait until the watcher catches up with the previous write
+ local find_consumer = require("apisix.consumer").find_consumer
+ for _ = 1, 100 do
+ if find_consumer("ldap-auth", "user_dn",
+
"cn=duplicate-check,ou=users,dc=example,dc=org") then
+ break
+ end
+ ngx.sleep(0.05)
+ end
+
+ local code, body = t('/apisix/admin/consumers',
+ ngx.HTTP_PUT,
+ [[{
+ "username": "ldap_case_b",
+ "plugins": {
+ "ldap-auth": {
+ "user_dn":
"cn=duplicate-check,ou=users,dc=example,dc=org"
+ }
+ }
+ }]]
+ )
+
+ ngx.status = code
+ ngx.print(body)
+ }
+ }
+--- request
+GET /t
+--- error_code: 400
+--- response_body
+{"error_msg":"duplicate user_dn of plugin ldap-auth found with consumer:
ldap_case_a"}
+
+
+
+=== TEST 21: clean up consumers
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ for _, name in ipairs({"case_a", "case_b", "case_c", "enc_case_a",
"ldap_case_a"}) do
+ local code, body = t('/apisix/admin/consumers/' .. name,
ngx.HTTP_DELETE)
+ if code >= 300 then
+ ngx.say("failed to delete consumer ", name, ": ", body)
+ return
+ end
+ end
+ ngx.say("passed")
+ }
+ }
+--- request
+GET /t
+--- response_body
+passed
diff --git a/t/admin/credentials.t b/t/admin/credentials.t
index 15119829c..f3730cb44 100644
--- a/t/admin/credentials.t
+++ b/t/admin/credentials.t
@@ -110,7 +110,7 @@ passed
"desc": "basic-auth for jack",
"plugins": {
"basic-auth": {
- "username": "the-user",
+ "username": "the-new-user",
"password": "the-password"
}
}
@@ -119,7 +119,7 @@ passed
"value":{
"desc":"basic-auth for jack",
"id":"credential_a",
-
"plugins":{"basic-auth":{"username":"the-user","password":"WvF5kpaLvIzjuk4GNIMTJg=="}}
+
"plugins":{"basic-auth":{"username":"the-new-user","password":"WvF5kpaLvIzjuk4GNIMTJg=="}}
},
"key":"/apisix/consumers/jack/credentials/credential_a"
}]]
@@ -492,3 +492,315 @@ GET /t
--- error_code: 400
--- response_body
{"error_msg":"missing credential id"}
+
+
+
+=== TEST 17: prepare consumers for duplicate key checks
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/consumers',
+ ngx.HTTP_PUT,
+ [[{ "username": "alex" }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ return
+ end
+
+ local code, body = t('/apisix/admin/consumers',
+ ngx.HTTP_PUT,
+ [[{
+ "username": "bob",
+ "plugins": {
+ "key-auth": {
+ "key": "the-key-of-bob"
+ }
+ }
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- request
+GET /t
+--- response_body
+passed
+
+
+
+=== TEST 18: create a credential with key-auth for consumer alex
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body =
t('/apisix/admin/consumers/alex/credentials/cred-alex-a',
+ ngx.HTTP_PUT,
+ [[{
+ "plugins": {
+ "key-auth": {
+ "key": "the-key-of-alex"
+ }
+ }
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- request
+GET /t
+--- response_body
+passed
+
+
+
+=== TEST 19: credential of another consumer duplicates the credential key,
should fail
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ -- the duplicate check runs against the locally synced consumer
+ -- data, wait until the watcher catches up with the previous write
+ local find_consumer = require("apisix.consumer").find_consumer
+ for _ = 1, 100 do
+ if find_consumer("key-auth", "key", "the-key-of-alex") then
+ break
+ end
+ ngx.sleep(0.05)
+ end
+
+ local code, body =
t('/apisix/admin/consumers/bob/credentials/cred-bob-a',
+ ngx.HTTP_PUT,
+ [[{
+ "plugins": {
+ "key-auth": {
+ "key": "the-key-of-alex"
+ }
+ }
+ }]]
+ )
+
+ ngx.status = code
+ ngx.print(body)
+ }
+ }
+--- request
+GET /t
+--- error_code: 400
+--- response_body
+{"error_msg":"duplicate key of plugin key-auth found with credential:
cred-alex-a of consumer: alex"}
+
+
+
+=== TEST 20: credential duplicates the key of another consumer, should fail
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ -- the duplicate check runs against the locally synced consumer
+ -- data, wait until the watcher catches up with the previous write
+ local find_consumer = require("apisix.consumer").find_consumer
+ for _ = 1, 100 do
+ if find_consumer("key-auth", "key", "the-key-of-bob") then
+ break
+ end
+ ngx.sleep(0.05)
+ end
+
+ local code, body =
t('/apisix/admin/consumers/alex/credentials/cred-alex-b',
+ ngx.HTTP_PUT,
+ [[{
+ "plugins": {
+ "key-auth": {
+ "key": "the-key-of-bob"
+ }
+ }
+ }]]
+ )
+
+ ngx.status = code
+ ngx.print(body)
+ }
+ }
+--- request
+GET /t
+--- error_code: 400
+--- response_body
+{"error_msg":"duplicate key of plugin key-auth found with consumer: bob"}
+
+
+
+=== TEST 21: consumer duplicates the key of an existing credential, should fail
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ -- the duplicate check runs against the locally synced consumer
+ -- data, wait until the watcher catches up with the previous write
+ local find_consumer = require("apisix.consumer").find_consumer
+ for _ = 1, 100 do
+ if find_consumer("key-auth", "key", "the-key-of-alex") then
+ break
+ end
+ ngx.sleep(0.05)
+ end
+
+ local code, body = t('/apisix/admin/consumers',
+ ngx.HTTP_PUT,
+ [[{
+ "username": "carol",
+ "plugins": {
+ "key-auth": {
+ "key": "the-key-of-alex"
+ }
+ }
+ }]]
+ )
+
+ ngx.status = code
+ ngx.print(body)
+ }
+ }
+--- request
+GET /t
+--- error_code: 400
+--- response_body
+{"error_msg":"duplicate key of plugin key-auth found with credential:
cred-alex-a of consumer: alex"}
+
+
+
+=== TEST 22: update the credential with its own key unchanged, should success
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body =
t('/apisix/admin/consumers/alex/credentials/cred-alex-a',
+ ngx.HTTP_PUT,
+ [[{
+ "desc": "new description",
+ "plugins": {
+ "key-auth": {
+ "key": "the-key-of-alex"
+ }
+ }
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- request
+GET /t
+--- response_body
+passed
+
+
+
+=== TEST 23: sibling credential of the same consumer duplicates the key,
should fail
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ -- the duplicate check runs against the locally synced consumer
+ -- data, wait until the watcher catches up with the previous write
+ local find_consumer = require("apisix.consumer").find_consumer
+ for _ = 1, 100 do
+ if find_consumer("key-auth", "key", "the-key-of-alex") then
+ break
+ end
+ ngx.sleep(0.05)
+ end
+
+ local code, body =
t('/apisix/admin/consumers/alex/credentials/cred-alex-c',
+ ngx.HTTP_PUT,
+ [[{
+ "plugins": {
+ "key-auth": {
+ "key": "the-key-of-alex"
+ }
+ }
+ }]]
+ )
+
+ ngx.status = code
+ ngx.print(body)
+ }
+ }
+--- request
+GET /t
+--- error_code: 400
+--- response_body
+{"error_msg":"duplicate key of plugin key-auth found with credential:
cred-alex-a of consumer: alex"}
+
+
+
+=== TEST 24: credential duplicates the key of its own consumer, should fail
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ -- the duplicate check runs against the locally synced consumer
+ -- data, wait until the watcher catches up with the previous write
+ local find_consumer = require("apisix.consumer").find_consumer
+ for _ = 1, 100 do
+ if find_consumer("key-auth", "key", "the-key-of-bob") then
+ break
+ end
+ ngx.sleep(0.05)
+ end
+
+ local code, body =
t('/apisix/admin/consumers/bob/credentials/cred-bob-b',
+ ngx.HTTP_PUT,
+ [[{
+ "plugins": {
+ "key-auth": {
+ "key": "the-key-of-bob"
+ }
+ }
+ }]]
+ )
+
+ ngx.status = code
+ ngx.print(body)
+ }
+ }
+--- request
+GET /t
+--- error_code: 400
+--- response_body
+{"error_msg":"duplicate key of plugin key-auth found with consumer: bob"}
+
+
+
+=== TEST 25: clean up consumers and credentials
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ for _, name in ipairs({"alex", "bob", "jack"}) do
+ local code, body = t('/apisix/admin/consumers/' .. name,
ngx.HTTP_DELETE)
+ if code >= 300 then
+ ngx.say("failed to delete consumer ", name, ": ", body)
+ return
+ end
+ end
+ ngx.say("passed")
+ }
+ }
+--- request
+GET /t
+--- response_body
+passed