@@ -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