oscerd commented on code in PR #27767: URL: https://github.com/apache/camel/pull/27767#discussion_r4268194677
########## components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/vertx/VertxMcpPrincipal.java: ########## @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.mcp.server.vertx; + +import java.util.ArrayList; +import java.util.List; + +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.auth.User; + +/** + * Derives a runtime-neutral view — a caller name and role names — from a Vert.x {@link User}, so the MCP bridge can + * stamp a stable caller identity ({@code CamelMcpSecurityPrincipalName} / {@code CamelMcpSecurityPrincipalRoles}) that + * a tool route or its {@link org.apache.camel.spi.AuthorizationPolicy} can authorize on without depending on Vert.x. + */ +final class VertxMcpPrincipal { + + private VertxMcpPrincipal() { + } + + /** + * The caller name: the Vert.x {@link User#subject() subject} when set, else the {@code username} or {@code sub} + * claim of the principal, else {@code null}. + */ + static String name(User user) { + if (user == null) { + return null; + } + String subject = user.subject(); + if (isSet(subject)) { + return subject; + } + JsonObject principal = user.principal(); + if (principal != null) { + String username = principal.getString("username"); + if (isSet(username)) { + return username; + } + String sub = principal.getString("sub"); + if (isSet(sub)) { + return sub; + } + } + return null; + } + + /** + * The caller roles, read from the principal's common role claims in order: a top-level {@code roles} array, the + * Keycloak {@code realm_access.roles} array, or a {@code groups} array. Returns {@code null} when the principal + * carries none, so the bridge does not stamp an empty roles property; blank and non-string entries are skipped. + */ + static List<String> roles(User user) { + if (user == null) { + return null; + } + JsonObject principal = user.principal(); + if (principal == null) { + return null; + } + JsonArray roles = principal.getJsonArray("roles"); + if (roles == null) { + JsonObject realmAccess = principal.getJsonObject("realm_access"); + if (realmAccess != null) { + roles = realmAccess.getJsonArray("roles"); + } + } + if (roles == null) { + roles = principal.getJsonArray("groups"); + } Review Comment: Fixed in the latest push (56c20f06). `roles()` now reads each claim with `getValue()` and checks `instanceof JsonArray` (and `JsonObject` for `realm_access`) before use — exactly as you suggested. A non-array `roles`/`groups`, or a non-object `realm_access`, is now ignored instead of cast, so a token in that shape no longer throws `ClassCastException` or fails the tool call in `toToolCallContext`. Added unit tests for the string-valued `roles`/`groups`, a non-object `realm_access`, a string `realm_access.roles`, and a fall-through-to-`groups` case. Thanks for catching this. _Claude Code on behalf of oscerd_ ########## components/camel-ai/camel-ai-tool/src/main/docs/ai-tool-component.adoc: ########## @@ -444,10 +444,15 @@ Which runtimes carry the caller identity: <<The calling exchange>> above), so the identity property reaches the tool route. * Over the xref:others:mcp-server.adoc[MCP server] the authenticated transport caller is carried onto the tool exchange as the `CamelMcpSecurityPrincipal` property (the raw transport principal — for the Vert.x streamable HTTP - server, an `io.vertx.ext.auth.User`). A policy over MCP reads that property directly; Camel's shipped - identity/token policies (Keycloak, Spring Security, Shiro) do not read it yet, so MCP authorization needs a policy - that inspects `CamelMcpSecurityPrincipal`. Exposing a runtime-neutral principal name and roles for the shipped - policies is tracked as a follow-up. + server, an `io.vertx.ext.auth.User`). Alongside it the server stamps a runtime-neutral view derived from that + principal — `CamelMcpSecurityPrincipalName` (a `String`) and `CamelMcpSecurityPrincipalRoles` (a + `Collection<String>`) — so a tool route or a policy can authorize on a stable name and roles without knowing the + runtime type. For the Vert.x server the name is the user subject (or the `username`/`sub` claim) and the roles are + read from the principal's `roles`, Keycloak `realm_access.roles`, or `groups` claim; each property is set only when + the server could determine it, so treat a missing name or roles as unauthorized. Camel's shipped identity/token Review Comment: Addressed by documenting (kept to the scoped view-exposure rather than deriving from `getAuthorizations`). The doc now states roles come only from token claims (JWT/OIDC), that a camel-main basic-auth caller carries no roles in its principal — property-file roles live in the Vert.x authorizations, which `BasicAuthHandler` never reads — so `CamelMcpSecurityPrincipalRoles` is not set for it, and that only the Vert.x server populates these today. Updated the source doc and the catalog mirror, and added an integration test asserting `no-roles` for the basic-auth caller. _Claude Code on behalf of oscerd_ ########## components/camel-ai/camel-mcp-server/src/main/java/org/apache/camel/component/mcp/server/vertx/VertxMcpPrincipal.java: ########## @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.mcp.server.vertx; + +import java.util.ArrayList; +import java.util.List; + +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.auth.User; + +/** + * Derives a runtime-neutral view — a caller name and role names — from a Vert.x {@link User}, so the MCP bridge can + * stamp a stable caller identity ({@code CamelMcpSecurityPrincipalName} / {@code CamelMcpSecurityPrincipalRoles}) that + * a tool route or its {@link org.apache.camel.spi.AuthorizationPolicy} can authorize on without depending on Vert.x. + */ +final class VertxMcpPrincipal { + + private VertxMcpPrincipal() { + } + + /** + * The caller name: the Vert.x {@link User#subject() subject} when set, else the {@code username} or {@code sub} + * claim of the principal, else {@code null}. + */ Review Comment: Done — the `name()` javadoc now reads "the subject when non-null and non-blank, else the `username` or `sub` claim … when non-null and non-blank", matching `isSet()`. _Claude Code on behalf of oscerd_ -- 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]
