Skip to content

Commit e2671c4

Browse files
fix(dingtalk-auth): clear client-supplied X-Userinfo before authentication (#13491)
1 parent bf91696 commit e2671c4

2 files changed

Lines changed: 142 additions & 0 deletions

File tree

apisix/plugins/dingtalk-auth.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,9 @@ end
215215
function _M.rewrite(conf, ctx)
216216
local userinfo, err
217217

218+
-- clear any client-supplied X-Userinfo before authentication
219+
core.request.set_header(ctx, "X-Userinfo", nil)
220+
218221
local sess, sess_err = session.open(
219222
{
220223
secret = conf.secret,

t/plugin/dingtalk-auth.t

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@ add_block_preprocessor(sub {
6767
}))
6868
}
6969
}
70+
71+
location /dt-echo {
72+
content_by_lua_block {
73+
-- echo back the received X-Userinfo header so tests can assert it
74+
ngx.say(ngx.req.get_headers()["x-userinfo"] or "none")
75+
}
76+
}
7077
}
7178
_EOC_
7279

@@ -371,3 +378,135 @@ passed
371378
]
372379
--- error_code eval
373380
[302, 200]
381+
382+
383+
384+
=== TEST 14: client-supplied X-Userinfo is not forwarded to upstream
385+
--- config
386+
location /t {
387+
content_by_lua_block {
388+
local http = require("resty.http")
389+
local httpc = http.new()
390+
local t = require("lib.test_admin").test
391+
local forged = ngx.encode_base64('{"userid":"admin","name":"forged"}')
392+
393+
-- restore route 1 to a clean config and obtain a legitimate cookie
394+
local code = t('/apisix/admin/routes/1',
395+
ngx.HTTP_PUT,
396+
[[{
397+
"methods": ["GET"],
398+
"upstream": {
399+
"nodes": {"127.0.0.1:1980": 1},
400+
"type": "roundrobin"
401+
},
402+
"plugins": {
403+
"dingtalk-auth": {
404+
"app_key": "testappkey",
405+
"app_secret": "testappsecret",
406+
"secret": "my-session-secret",
407+
"access_token_url": "http://127.0.0.1:10421/v1.0/oauth2/accessToken",
408+
"userinfo_url": "http://127.0.0.1:10421/topapi/v2/user/getuserinfo",
409+
"redirect_uri": "/login"
410+
}
411+
},
412+
"uri": "/hello"
413+
}]]
414+
)
415+
assert(code <= 201, "setup route 1 failed: " .. tostring(code))
416+
417+
local base = "http://127.0.0.1:" .. ngx.var.server_port
418+
local res, err = httpc:request_uri(base .. "/hello", {
419+
method = "GET",
420+
query = {code = "valid_code"},
421+
})
422+
assert(res, err)
423+
assert(res.status == 200, "expected 200 on auth, got " .. res.status)
424+
local cookie = res.headers["Set-Cookie"]
425+
assert(cookie, "expected Set-Cookie after auth")
426+
427+
-- forged X-Userinfo without a cookie must not bypass authentication
428+
local res1, err1 = httpc:request_uri(base .. "/hello", {
429+
method = "GET",
430+
headers = {["X-Userinfo"] = forged},
431+
})
432+
assert(res1, err1)
433+
assert(res1.status == 302,
434+
"forged X-Userinfo without cookie should redirect, got " .. res1.status)
435+
436+
-- route with set_userinfo_header=false: upstream must receive no X-Userinfo,
437+
-- even when the client supplies a forged one alongside a valid cookie
438+
local code2 = t('/apisix/admin/routes/2',
439+
ngx.HTTP_PUT,
440+
[[{
441+
"methods": ["GET"],
442+
"upstream": {
443+
"nodes": {"127.0.0.1:10421": 1},
444+
"type": "roundrobin"
445+
},
446+
"plugins": {
447+
"dingtalk-auth": {
448+
"app_key": "testappkey",
449+
"app_secret": "testappsecret",
450+
"secret": "my-session-secret",
451+
"access_token_url": "http://127.0.0.1:10421/v1.0/oauth2/accessToken",
452+
"userinfo_url": "http://127.0.0.1:10421/topapi/v2/user/getuserinfo",
453+
"set_userinfo_header": false,
454+
"redirect_uri": "/login"
455+
}
456+
},
457+
"uri": "/dt-echo-off"
458+
}]]
459+
)
460+
assert(code2 <= 201, "setup route 2 failed: " .. tostring(code2))
461+
462+
local res2, err2 = httpc:request_uri(base .. "/dt-echo-off", {
463+
method = "GET",
464+
headers = {["Cookie"] = cookie, ["X-Userinfo"] = forged},
465+
})
466+
assert(res2, err2)
467+
assert(res2.status == 200, "expected 200 on echo route, got " .. res2.status)
468+
assert(res2.body == "none\n",
469+
"forged X-Userinfo must not reach upstream, got: " .. (res2.body or "nil"))
470+
471+
-- route with set_userinfo_header=true: the forged value is overwritten
472+
-- with the verified user info, never forwarded as-is
473+
local code3 = t('/apisix/admin/routes/3',
474+
ngx.HTTP_PUT,
475+
[[{
476+
"methods": ["GET"],
477+
"upstream": {
478+
"nodes": {"127.0.0.1:10421": 1},
479+
"type": "roundrobin"
480+
},
481+
"plugins": {
482+
"dingtalk-auth": {
483+
"app_key": "testappkey",
484+
"app_secret": "testappsecret",
485+
"secret": "my-session-secret",
486+
"access_token_url": "http://127.0.0.1:10421/v1.0/oauth2/accessToken",
487+
"userinfo_url": "http://127.0.0.1:10421/topapi/v2/user/getuserinfo",
488+
"set_userinfo_header": true,
489+
"redirect_uri": "/login"
490+
}
491+
},
492+
"uri": "/dt-echo-on"
493+
}]]
494+
)
495+
assert(code3 <= 201, "setup route 3 failed: " .. tostring(code3))
496+
497+
local res3, err3 = httpc:request_uri(base .. "/dt-echo-on", {
498+
method = "GET",
499+
headers = {["Cookie"] = cookie, ["X-Userinfo"] = forged},
500+
})
501+
assert(res3, err3)
502+
assert(res3.status == 200, "expected 200 on echo route, got " .. res3.status)
503+
assert(res3.body ~= forged .. "\n",
504+
"forged X-Userinfo must be overwritten, got: " .. (res3.body or "nil"))
505+
assert(res3.body ~= "none\n",
506+
"verified X-Userinfo should be set, got: " .. (res3.body or "nil"))
507+
508+
ngx.say("passed")
509+
}
510+
}
511+
--- response_body
512+
passed

0 commit comments

Comments
 (0)