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());
}