This is an automated email from the ASF dual-hosted git repository.

acosentino pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new a8fedb0b079e CAMEL-22498 - Camel-Keycloak: make requiredRoles and 
requiredPermissions comma separated string, it will make YAML life easier 
(#19442)
a8fedb0b079e is described below

commit a8fedb0b079ef7c37b81e2dc43db9e10c62172dd
Author: Andrea Cosentino <[email protected]>
AuthorDate: Mon Oct 6 17:57:31 2025 +0200

    CAMEL-22498 - Camel-Keycloak: make requiredRoles and requiredPermissions 
comma separated string, it will make YAML life easier (#19442)
    
    Signed-off-by: Andrea Cosentino <[email protected]>
---
 .../src/main/docs/keycloak-component.adoc          | 91 ++++++++-------------
 .../keycloak/security/KeycloakSecurityPolicy.java  | 95 ++++++++++++++++++++--
 .../security/KeycloakSecurityProcessor.java        | 12 +--
 .../security/KeycloakSecurityPolicyTest.java       | 29 ++++++-
 4 files changed, 153 insertions(+), 74 deletions(-)

diff --git a/components/camel-keycloak/src/main/docs/keycloak-component.adoc 
b/components/camel-keycloak/src/main/docs/keycloak-component.adoc
index 463b486800ba..d0a977086b99 100644
--- a/components/camel-keycloak/src/main/docs/keycloak-component.adoc
+++ b/components/camel-keycloak/src/main/docs/keycloak-component.adoc
@@ -2189,8 +2189,8 @@ Java::
 KeycloakSecurityPolicy policy = new KeycloakSecurityPolicy(
     "http://localhost:8080";, "my-realm", "my-client", "client-secret");
 
-// Require specific roles
-policy.setRequiredRoles(Arrays.asList("admin", "user"));
+// Require specific roles (comma-separated)
+policy.setRequiredRoles("admin,user");
 policy.setAllRolesRequired(true); // User must have ALL roles
 
 from("direct:admin")
@@ -2220,9 +2220,7 @@ beans:
       realm: "my-realm"
       clientId: "my-client"
       clientSecret: "client-secret"
-      requiredRoles:
-        - "admin"
-        - "user"
+      requiredRoles: "admin,user"
       allRolesRequired: true
 ----
 ====
@@ -2238,8 +2236,8 @@ Java::
 KeycloakSecurityPolicy policy = new KeycloakSecurityPolicy(
     "http://localhost:8080";, "my-realm", "my-client", "client-secret");
 
-// Require specific permissions
-policy.setRequiredPermissions(Arrays.asList("read:documents", 
"write:documents"));
+// Require specific permissions (comma-separated)
+policy.setRequiredPermissions("read:documents,write:documents");
 policy.setAllPermissionsRequired(false); // User needs ANY permission
 
 from("direct:documents")
@@ -2269,9 +2267,7 @@ beans:
       realm: "my-realm"
       clientId: "my-client"
       clientSecret: "client-secret"
-      requiredPermissions:
-        - "read:documents"
-        - "write:documents"
+      requiredPermissions: "read:documents,write:documents"
       allPermissionsRequired: false
 ----
 ====
@@ -2411,8 +2407,8 @@ YAML::
 | clientSecret | | Keycloak client secret (for client credentials flow)
 | username | | Username (for resource owner password flow)
 | password | | Password (for resource owner password flow)
-| requiredRoles | [] | List of required roles
-| requiredPermissions | [] | List of required permissions
+| requiredRoles | "" | Comma-separated list of required roles (e.g., 
"admin,user,manager")
+| requiredPermissions | "" | Comma-separated list of required permissions 
(e.g., "read:documents,write:documents")
 | allRolesRequired | true | Whether ALL roles are required (true) or ANY role 
(false)
 | allPermissionsRequired | true | Whether ALL permissions are required (true) 
or ANY permission (false)
 | useResourceOwnerPasswordCredentials | false | Whether to use resource owner 
password flow
@@ -2482,8 +2478,8 @@ keycloakPolicy.setRealm("my-company");
 keycloakPolicy.setClientId("my-service");
 keycloakPolicy.setClientSecret("client-secret-value");
 
-// Require admin role
-keycloakPolicy.setRequiredRoles(Arrays.asList("admin"));
+// Require admin role (comma-separated string)
+keycloakPolicy.setRequiredRoles("admin");
 
 // Apply to route
 from("direct:admin-endpoint")
@@ -2516,8 +2512,7 @@ beans:
       realm: "my-company"
       clientId: "my-service"
       clientSecret: "client-secret-value"
-      requiredRoles:
-        - "admin"
+      requiredRoles: "admin"
 ----
 ====
 
@@ -2529,10 +2524,10 @@ Java::
 +
 [source,java]
 ----
-// Require either admin OR user role
+// Require either admin OR user role (comma-separated string)
 KeycloakSecurityPolicy userPolicy = new KeycloakSecurityPolicy(
     "http://localhost:8080";, "my-company", "my-service", "client-secret");
-userPolicy.setRequiredRoles(Arrays.asList("admin", "user"));
+userPolicy.setRequiredRoles("admin,user");
 userPolicy.setAllRolesRequired(false); // ANY role (OR logic)
 
 from("direct:user-endpoint")
@@ -2562,9 +2557,7 @@ beans:
       realm: "my-company"
       clientId: "my-service"
       clientSecret: "client-secret"
-      requiredRoles:
-        - "admin"
-        - "user"
+      requiredRoles: "admin,user"
       allRolesRequired: false
 ----
 ====
@@ -2581,19 +2574,19 @@ Java::
 KeycloakSecurityPolicy readPolicy = new KeycloakSecurityPolicy(
     "{{keycloak.server-url}}", "{{keycloak.realm}}",
     "{{keycloak.client-id}}", "{{keycloak.client-secret}}");
-readPolicy.setRequiredRoles(Arrays.asList("reader", "writer", "admin"));
+readPolicy.setRequiredRoles("reader,writer,admin");
 readPolicy.setAllRolesRequired(false);
 
 KeycloakSecurityPolicy writePolicy = new KeycloakSecurityPolicy(
     "{{keycloak.server-url}}", "{{keycloak.realm}}",
     "{{keycloak.client-id}}", "{{keycloak.client-secret}}");
-writePolicy.setRequiredRoles(Arrays.asList("writer", "admin"));
+writePolicy.setRequiredRoles("writer,admin");
 writePolicy.setAllRolesRequired(false);
 
 KeycloakSecurityPolicy adminPolicy = new KeycloakSecurityPolicy(
     "{{keycloak.server-url}}", "{{keycloak.realm}}",
     "{{keycloak.client-id}}", "{{keycloak.client-secret}}");
-adminPolicy.setRequiredRoles(Arrays.asList("admin"));
+adminPolicy.setRequiredRoles("admin");
 
 // Configure REST endpoints
 rest("/api")
@@ -2648,10 +2641,7 @@ beans:
       realm: "{{keycloak.realm}}"
       clientId: "{{keycloak.client-id}}"
       clientSecret: "{{keycloak.client-secret}}"
-      requiredRoles:
-        - "reader"
-        - "writer"
-        - "admin"
+      requiredRoles: "reader,writer,admin"
       allRolesRequired: false
 
   - name: writePolicy
@@ -2661,9 +2651,7 @@ beans:
       realm: "{{keycloak.realm}}"
       clientId: "{{keycloak.client-id}}"
       clientSecret: "{{keycloak.client-secret}}"
-      requiredRoles:
-        - "writer"
-        - "admin"
+      requiredRoles: "writer,admin"
       allRolesRequired: false
 
   - name: adminPolicy
@@ -2673,8 +2661,7 @@ beans:
       realm: "{{keycloak.realm}}"
       clientId: "{{keycloak.client-id}}"
       clientSecret: "{{keycloak.client-secret}}"
-      requiredRoles:
-        - "admin"
+      requiredRoles: "admin"
 ----
 ====
 
@@ -2813,7 +2800,7 @@ public class SecurityConfiguration {
     public KeycloakSecurityPolicy adminPolicy() {
         KeycloakSecurityPolicy policy = new KeycloakSecurityPolicy(
             serverUrl, realm, clientId, clientSecret);
-        policy.setRequiredRoles(Arrays.asList("admin"));
+        policy.setRequiredRoles("admin");
         return policy;
     }
 
@@ -2821,7 +2808,7 @@ public class SecurityConfiguration {
     public KeycloakSecurityPolicy userPolicy() {
         KeycloakSecurityPolicy policy = new KeycloakSecurityPolicy(
             serverUrl, realm, clientId, clientSecret);
-        policy.setRequiredRoles(Arrays.asList("user", "admin"));
+        policy.setRequiredRoles("user,admin");
         policy.setAllRolesRequired(false);
         return policy;
     }
@@ -2845,13 +2832,13 @@ public class KeycloakSecurityRoutes extends 
RouteBuilder {
         KeycloakSecurityPolicy adminPolicy = new KeycloakSecurityPolicy(
             "{{keycloak.server-url}}", "{{keycloak.realm}}",
             "{{keycloak.client-id}}", "{{keycloak.client-secret}}");
-        adminPolicy.setRequiredRoles(Arrays.asList("admin"));
+        adminPolicy.setRequiredRoles("admin");
 
         // User policy - requires user or admin role
         KeycloakSecurityPolicy userPolicy = new KeycloakSecurityPolicy(
             "{{keycloak.server-url}}", "{{keycloak.realm}}",
             "{{keycloak.client-id}}", "{{keycloak.client-secret}}");
-        userPolicy.setRequiredRoles(Arrays.asList("user", "admin"));
+        userPolicy.setRequiredRoles("user,admin");
         userPolicy.setAllRolesRequired(false); // ANY role
 
         // Error handling
@@ -2915,8 +2902,7 @@ beans:
       realm: "{{keycloak.realm}}"
       clientId: "{{keycloak.client-id}}"
       clientSecret: "{{keycloak.client-secret}}"
-      requiredRoles:
-        - "admin"
+      requiredRoles: "admin"
 
   - name: userPolicy
     type: org.apache.camel.component.keycloak.security.KeycloakSecurityPolicy
@@ -2925,9 +2911,7 @@ beans:
       realm: "{{keycloak.realm}}"
       clientId: "{{keycloak.client-id}}"
       clientSecret: "{{keycloak.client-secret}}"
-      requiredRoles:
-        - "user"
-        - "admin"
+      requiredRoles: "user,admin"
       allRolesRequired: false
 ----
 
@@ -3162,7 +3146,7 @@ For permissions-based authorization, you have several 
options to include permiss
 [source,java]
 ----
 KeycloakSecurityPolicy policy = new KeycloakSecurityPolicy();
-policy.setRequiredPermissions(Arrays.asList("documents", "users", "admin"));
+policy.setRequiredPermissions("documents,users,admin");
 policy.setAllPermissionsRequired(false); // ANY permission
 ----
 
@@ -3191,9 +3175,9 @@ strictPolicy.setRealm("{{keycloak.realm}}");
 strictPolicy.setClientId("{{keycloak.client-id}}");
 strictPolicy.setClientSecret("{{keycloak.client-secret}}");
 
-// User must have admin role AND document permissions
-strictPolicy.setRequiredRoles(Arrays.asList("admin"));
-strictPolicy.setRequiredPermissions(Arrays.asList("read:documents", 
"write:documents"));
+// User must have admin role AND document permissions (comma-separated)
+strictPolicy.setRequiredRoles("admin");
+strictPolicy.setRequiredPermissions("read:documents,write:documents");
 strictPolicy.setAllRolesRequired(true);
 strictPolicy.setAllPermissionsRequired(false); // ANY permission
 
@@ -3245,11 +3229,8 @@ beans:
       realm: "{{keycloak.realm}}"
       clientId: "{{keycloak.client-id}}"
       clientSecret: "{{keycloak.client-secret}}"
-      requiredRoles:
-        - "admin"
-      requiredPermissions:
-        - "read:documents"
-        - "write:documents"
+      requiredRoles: "admin"
+      requiredPermissions: "read:documents,write:documents"
       allRolesRequired: true
       allPermissionsRequired: false
 
@@ -3260,12 +3241,8 @@ beans:
       realm: "{{keycloak.realm}}"
       clientId: "{{keycloak.client-id}}"
       clientSecret: "{{keycloak.client-secret}}"
-      requiredRoles:
-        - "admin"
-        - "manager"
-      requiredPermissions:
-        - "read:documents"
-        - "emergency:access"
+      requiredRoles: "admin,manager"
+      requiredPermissions: "read:documents,emergency:access"
       allRolesRequired: false
       allPermissionsRequired: false
 ----
diff --git 
a/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/security/KeycloakSecurityPolicy.java
 
b/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/security/KeycloakSecurityPolicy.java
index 49086c375af0..048646e1b592 100644
--- 
a/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/security/KeycloakSecurityPolicy.java
+++ 
b/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/security/KeycloakSecurityPolicy.java
@@ -17,13 +17,16 @@
 package org.apache.camel.component.keycloak.security;
 
 import java.security.PublicKey;
-import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import org.apache.camel.NamedNode;
 import org.apache.camel.Processor;
 import org.apache.camel.Route;
 import org.apache.camel.spi.AuthorizationPolicy;
+import org.apache.camel.util.ObjectHelper;
 import org.keycloak.admin.client.Keycloak;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -37,8 +40,14 @@ public class KeycloakSecurityPolicy implements 
AuthorizationPolicy {
     private String clientSecret;
     private String username;
     private String password;
-    private List<String> requiredRoles;
-    private List<String> requiredPermissions;
+    /**
+     * Comma-separated list of required roles for authorization. Example: 
"admin,user,manager"
+     */
+    private String requiredRoles;
+    /**
+     * Comma-separated list of required permissions for authorization. 
Example: "read:documents,write:documents"
+     */
+    private String requiredPermissions;
     private boolean allRolesRequired = true;
     private boolean allPermissionsRequired = true;
     private boolean useResourceOwnerPasswordCredentials = false;
@@ -47,8 +56,8 @@ public class KeycloakSecurityPolicy implements 
AuthorizationPolicy {
     private Keycloak keycloakClient;
 
     public KeycloakSecurityPolicy() {
-        this.requiredRoles = new ArrayList<>();
-        this.requiredPermissions = new ArrayList<>();
+        this.requiredRoles = "";
+        this.requiredPermissions = "";
     }
 
     public KeycloakSecurityPolicy(String serverUrl, String realm, String 
clientId, String clientSecret) {
@@ -148,20 +157,88 @@ public class KeycloakSecurityPolicy implements 
AuthorizationPolicy {
         this.password = password;
     }
 
-    public List<String> getRequiredRoles() {
+    /**
+     * Gets the required roles as a comma-separated string.
+     *
+     * @return comma-separated roles (e.g., "admin,user,manager")
+     */
+    public String getRequiredRoles() {
         return requiredRoles;
     }
 
+    /**
+     * Sets the required roles as a comma-separated string.
+     *
+     * @param requiredRoles comma-separated roles (e.g., "admin,user,manager")
+     */
+    public void setRequiredRoles(String requiredRoles) {
+        this.requiredRoles = requiredRoles != null ? requiredRoles : "";
+    }
+
+    /**
+     * Sets the required roles from a list.
+     *
+     * @param requiredRoles list of roles
+     */
     public void setRequiredRoles(List<String> requiredRoles) {
-        this.requiredRoles = requiredRoles;
+        this.requiredRoles = requiredRoles != null ? String.join(",", 
requiredRoles) : "";
+    }
+
+    /**
+     * Gets the required roles as a list.
+     *
+     * @return list of required roles
+     */
+    public List<String> getRequiredRolesAsList() {
+        if (ObjectHelper.isEmpty(requiredRoles)) {
+            return Collections.emptyList();
+        }
+        return Arrays.stream(requiredRoles.split(","))
+                .map(String::trim)
+                .filter(s -> !s.isEmpty())
+                .collect(Collectors.toList());
     }
 
-    public List<String> getRequiredPermissions() {
+    /**
+     * Gets the required permissions as a comma-separated string.
+     *
+     * @return comma-separated permissions (e.g., 
"read:documents,write:documents")
+     */
+    public String getRequiredPermissions() {
         return requiredPermissions;
     }
 
+    /**
+     * Sets the required permissions as a comma-separated string.
+     *
+     * @param requiredPermissions comma-separated permissions (e.g., 
"read:documents,write:documents")
+     */
+    public void setRequiredPermissions(String requiredPermissions) {
+        this.requiredPermissions = requiredPermissions != null ? 
requiredPermissions : "";
+    }
+
+    /**
+     * Sets the required permissions from a list.
+     *
+     * @param requiredPermissions list of permissions
+     */
     public void setRequiredPermissions(List<String> requiredPermissions) {
-        this.requiredPermissions = requiredPermissions;
+        this.requiredPermissions = requiredPermissions != null ? 
String.join(",", requiredPermissions) : "";
+    }
+
+    /**
+     * Gets the required permissions as a list.
+     *
+     * @return list of required permissions
+     */
+    public List<String> getRequiredPermissionsAsList() {
+        if (ObjectHelper.isEmpty(requiredPermissions)) {
+            return Collections.emptyList();
+        }
+        return Arrays.stream(requiredPermissions.split(","))
+                .map(String::trim)
+                .filter(s -> !s.isEmpty())
+                .collect(Collectors.toList());
     }
 
     public boolean isAllRolesRequired() {
diff --git 
a/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/security/KeycloakSecurityProcessor.java
 
b/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/security/KeycloakSecurityProcessor.java
index a47868f00f77..d8fc46824412 100644
--- 
a/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/security/KeycloakSecurityProcessor.java
+++ 
b/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/security/KeycloakSecurityProcessor.java
@@ -51,11 +51,11 @@ public class KeycloakSecurityProcessor extends 
DelegateProcessor {
                 throw new CamelAuthorizationException("Access token not found 
in exchange", exchange);
             }
 
-            if (!policy.getRequiredRoles().isEmpty()) {
+            if (!policy.getRequiredRolesAsList().isEmpty()) {
                 validateRoles(accessToken, exchange);
             }
 
-            if (!policy.getRequiredPermissions().isEmpty()) {
+            if (!policy.getRequiredPermissionsAsList().isEmpty()) {
                 validatePermissions(accessToken, exchange);
             }
 
@@ -100,8 +100,8 @@ public class KeycloakSecurityProcessor extends 
DelegateProcessor {
             Set<String> userRoles = KeycloakSecurityHelper.extractRoles(token, 
policy.getRealm(), policy.getClientId());
 
             boolean hasRequiredRoles = policy.isAllRolesRequired()
-                    ? userRoles.containsAll(policy.getRequiredRoles())
-                    : 
policy.getRequiredRoles().stream().anyMatch(userRoles::contains);
+                    ? userRoles.containsAll(policy.getRequiredRolesAsList())
+                    : 
policy.getRequiredRolesAsList().stream().anyMatch(userRoles::contains);
 
             if (!hasRequiredRoles) {
                 String message = String.format("User does not have required 
roles. Required: %s, User has: %s",
@@ -131,8 +131,8 @@ public class KeycloakSecurityProcessor extends 
DelegateProcessor {
             Set<String> userPermissions = 
KeycloakSecurityHelper.extractPermissions(token);
 
             boolean hasRequiredPermissions = policy.isAllPermissionsRequired()
-                    ? 
userPermissions.containsAll(policy.getRequiredPermissions())
-                    : 
policy.getRequiredPermissions().stream().anyMatch(userPermissions::contains);
+                    ? 
userPermissions.containsAll(policy.getRequiredPermissionsAsList())
+                    : 
policy.getRequiredPermissionsAsList().stream().anyMatch(userPermissions::contains);
 
             if (!hasRequiredPermissions) {
                 String message = String.format("User does not have required 
permissions. Required: %s, User has: %s",
diff --git 
a/components/camel-keycloak/src/test/java/org/apache/camel/component/keycloak/security/KeycloakSecurityPolicyTest.java
 
b/components/camel-keycloak/src/test/java/org/apache/camel/component/keycloak/security/KeycloakSecurityPolicyTest.java
index 738e5c482bc4..de5d11ee9642 100644
--- 
a/components/camel-keycloak/src/test/java/org/apache/camel/component/keycloak/security/KeycloakSecurityPolicyTest.java
+++ 
b/components/camel-keycloak/src/test/java/org/apache/camel/component/keycloak/security/KeycloakSecurityPolicyTest.java
@@ -51,7 +51,19 @@ public class KeycloakSecurityPolicyTest extends 
CamelTestSupport {
         policy.setRequiredRoles(requiredRoles);
         policy.setAllRolesRequired(true);
 
-        assertEquals(requiredRoles, policy.getRequiredRoles());
+        assertEquals("admin,user", policy.getRequiredRoles());
+        assertEquals(requiredRoles, policy.getRequiredRolesAsList());
+        assertTrue(policy.isAllRolesRequired());
+    }
+
+    @Test
+    void testKeycloakSecurityPolicyWithRolesAsCommaSeparatedString() {
+        KeycloakSecurityPolicy policy = new KeycloakSecurityPolicy();
+        policy.setRequiredRoles("admin,user,manager");
+        policy.setAllRolesRequired(true);
+
+        assertEquals("admin,user,manager", policy.getRequiredRoles());
+        assertEquals(Arrays.asList("admin", "user", "manager"), 
policy.getRequiredRolesAsList());
         assertTrue(policy.isAllRolesRequired());
     }
 
@@ -62,7 +74,20 @@ public class KeycloakSecurityPolicyTest extends 
CamelTestSupport {
         policy.setRequiredPermissions(requiredPermissions);
         policy.setAllPermissionsRequired(false);
 
-        assertEquals(requiredPermissions, policy.getRequiredPermissions());
+        assertEquals("read,write", policy.getRequiredPermissions());
+        assertEquals(requiredPermissions, 
policy.getRequiredPermissionsAsList());
+        assertFalse(policy.isAllPermissionsRequired());
+    }
+
+    @Test
+    void testKeycloakSecurityPolicyWithPermissionsAsCommaSeparatedString() {
+        KeycloakSecurityPolicy policy = new KeycloakSecurityPolicy();
+        
policy.setRequiredPermissions("read:documents,write:documents,delete:documents");
+        policy.setAllPermissionsRequired(false);
+
+        assertEquals("read:documents,write:documents,delete:documents", 
policy.getRequiredPermissions());
+        assertEquals(Arrays.asList("read:documents", "write:documents", 
"delete:documents"),
+                policy.getRequiredPermissionsAsList());
         assertFalse(policy.isAllPermissionsRequired());
     }
 

Reply via email to