AlinsRan commented on code in PR #13649:
URL: https://github.com/apache/apisix/pull/13649#discussion_r3671970343


##########
apisix-master-0.rockspec:
##########
@@ -51,7 +51,7 @@ dependencies = {
     "opentracing-openresty = 0.1-0",
     "lua-resty-radixtree = 2.9.2-0",
     "lua-protobuf = 0.5.3-1",
-    "lua-resty-openidc = 1.8.0-1",
+    "lua-resty-openidc = 1.9.0-1",

Review Comment:
   The 1.8.0 -> 1.9.0 bump also changes behavior for existing configurations, 
beyond the opt-in PAR/DPoP options.
   
   1.8.0 `openidc.introspect()` unconditionally put `client_id`/`client_secret` 
in the introspection POST body. 1.9.0 only does that when 
`introspection_endpoint_auth_method` is nil (`openidc.lua:2337`). APISIX 
defaults that field to `client_secret_basic` 
(`apisix/plugins/openid-connect.lua:78-81`), and the default reaches the conf 
that is handed to the library on both paths: Admin API 
(`apisix/admin/resource.lua:258` validates and `:314` stores the same table) 
and config load (`apisix/http/route.lua:122` -> `plugin_checker` -> 
`check_schema`), since the schema validator injects defaults in place. So after 
this bump every route that validates tokens through introspection stops sending 
credentials in the POST body; only the Basic header remains.
   
   For an OP that authenticates the introspection call from the body, this 
breaks with a completely unchanged APISIX configuration. The workaround is 
`introspection_endpoint_auth_method: "client_secret_post"`, but users have no 
way to know that from this PR - worth an upgrade note in the docs or at least 
in the PR description.
   
   I checked the other 1.9.0 changes as well: negative introspection caching is 
opt-in via `introspection_enable_negative_cache` (default false), so that one 
is not a behavior change.



##########
t/plugin/openid-connect.t:
##########
@@ -1845,7 +1849,278 @@ done
 
 
 
-=== TEST 51: Configure plugin with a custom session.cookie_name.
+=== TEST 51a: Accept PAR, DPoP, and client assertion algorithm options.
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.openid-connect")
+            local ok, err = plugin.check_schema({
+                client_id = "a",
+                discovery = 
"https://example.com/.well-known/openid-configuration";,
+                bearer_only = false,
+                use_pkce = true,
+                par = {
+                    enabled = true,
+                    endpoint = "https://example.com/par";,
+                    endpoint_auth_method = "private_key_jwt",
+                },
+                dpop = {
+                    enabled = true,
+                    signing_alg = "PS256",
+                    private_key = "-----BEGIN PRIVATE 
KEY-----\nMIIEowIBAAK\n-----END PRIVATE KEY-----",
+                    public_jwk = {
+                        kty = "RSA",
+                        e = "AQAB",
+                        n = "abc",
+                    },
+                },
+                token_endpoint_auth_method = "private_key_jwt",
+                client_rsa_private_key = "-----BEGIN RSA PRIVATE 
KEY-----\nMIIEowIBAAK\n-----END RSA PRIVATE KEY-----",
+                client_jwt_assertion_alg = "PS256",
+                client_jwt_assertion_audience = 
"https://issuer.example.com/token";,
+                session = { secret = "jwcE5v3pM9VhqLxmxFOH9uZaLo8u7KQK" },
+            })
+            if not ok then
+                ngx.say(err)
+            end
+            ngx.say("done")
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 52b: Reject unsupported DPoP signing algorithm in schema.
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.openid-connect")
+            local ok, err = plugin.check_schema({
+                client_id = "a",
+                client_secret = "b",
+                discovery = 
"https://example.com/.well-known/openid-configuration";,
+                dpop = {
+                    signing_alg = "HS256",
+                },
+                session = { secret = "jwcE5v3pM9VhqLxmxFOH9uZaLo8u7KQK" },
+            })
+            if not ok then
+                ngx.say(err)
+            end
+            ngx.say("done")
+        }
+    }
+--- response_body
+property "dpop" validation failed: property "signing_alg" validation failed: 
matches none of the enum values
+done
+
+
+
+=== TEST 53c: Accept PAR enabled without endpoint in schema.
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.openid-connect")
+            local ok, err = plugin.check_schema({
+                client_id = "a",
+                client_secret = "b",
+                discovery = 
"https://example.com/.well-known/openid-configuration";,
+                par = {
+                    enabled = true,
+                },
+                session = { secret = "jwcE5v3pM9VhqLxmxFOH9uZaLo8u7KQK" },
+            })
+            if not ok then
+                ngx.say(err)
+            end
+            ngx.say("done")
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 54d: Reject DPoP enabled without key material in schema.
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.openid-connect")
+            local ok, err = plugin.check_schema({
+                client_id = "a",
+                client_secret = "b",
+                discovery = 
"https://example.com/.well-known/openid-configuration";,
+                dpop = {
+                    enabled = true,
+                },
+                session = { secret = "jwcE5v3pM9VhqLxmxFOH9uZaLo8u7KQK" },
+            })
+            if not ok then
+                ngx.say(err)
+            end
+            ngx.say("done")
+        }
+    }
+--- response_body
+property "dpop" validation failed: then clause did not match
+done
+
+
+
+=== TEST 55e: Reject private key material in DPoP public JWK.
+--- config
+    location /t {
+        content_by_lua_block {
+            local plugin = require("apisix.plugins.openid-connect")
+            local ok, err = plugin.check_schema({
+                client_id = "a",
+                client_secret = "b",
+                discovery = 
"https://example.com/.well-known/openid-configuration";,
+                dpop = {
+                    enabled = true,
+                    private_key = "-----BEGIN PRIVATE 
KEY-----\nMIIEowIBAAK\n-----END PRIVATE KEY-----",
+                    public_jwk = {
+                        kty = "RSA",
+                        e = "AQAB",
+                        n = "abc",
+                        d = "private-exponent",
+                    },
+                },
+                session = { secret = "jwcE5v3pM9VhqLxmxFOH9uZaLo8u7KQK" },
+            })
+            if not ok then
+                ngx.say(err)
+            end
+            ngx.say("done")
+        }
+    }
+--- response_body_like
+property "dpop" validation failed: property "public_jwk" validation failed:.*
+done
+
+
+
+=== TEST 56: PAR runtime mapping sends authorization parameters through PAR.
+--- http_config
+    server {
+        listen 16969;
+        server_name localhost;
+
+        location /.well-known/openid-configuration {
+            content_by_lua_block {
+                ngx.header.content_type = "application/json"
+                ngx.say([[{
+                    "issuer": "http://127.0.0.1:16969";,
+                    "authorization_endpoint": 
"http://127.0.0.1:16969/authorize";,
+                    "token_endpoint": "http://127.0.0.1:16969/token";,
+                    "userinfo_endpoint": "http://127.0.0.1:16969/userinfo";,
+                    "jwks_uri": "http://127.0.0.1:16969/jwks";
+                }]])
+            }
+        }
+
+        location /par {

Review Comment:
   This runtime block covers the PAR mapping, but the four `dpop_*` assignments 
in `flatten_openidc_options` are still only covered by schema tests - a typo 
there, or an option rename upstream, would leave DPoP silently disabled with CI 
green.
   
   This mock can cover most of it at no extra cost: when `use_dpop` is set, 
lua-resty-openidc adds `params.dpop_jkt` before pushing to the PAR endpoint 
(`openidc.lua:651-657`), so enabling `dpop` in the route config and asserting 
`args.dpop_jkt` here exercises the `use_dpop` and `dpop_public_jwk` mappings. 
No token endpoint mock is needed: `dpop_private_key` is only read in 
`openidc_dpop_proof()` (`openidc.lua:232`), so a placeholder value is enough to 
satisfy the schema, and the thumbprint only requires `e`/`n` to be present on 
the RSA JWK - it does not decode them.



##########
apisix/plugins/openid-connect.lua:
##########
@@ -371,6 +454,14 @@ local schema = {
             type = "integer",
             default = 60
         },
+        client_jwt_assertion_alg = {

Review Comment:
   No enum here, while `dpop.signing_alg` added in this PR has one. An invalid 
value is only caught per request by the library (`openidc.lua:836-844`: 
symmetric alg rejected for `private_key_jwt`, asymmetric for 
`client_secret_jwt`, plus the 
`token_endpoint_auth_signing_alg_values_supported` check), so a typo becomes a 
runtime authentication failure instead of a config-time rejection. Constraining 
it in the schema would match the new `dpop.signing_alg`.
   
   For balance: `token_endpoint_auth_method` and 
`introspection_endpoint_auth_method` in this schema are plain strings too, so 
this is not an established convention in this file - take it as a suggestion.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to