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]

Reply via email to