This is an automated email from the ASF dual-hosted git repository.
oscerd 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 873ea8eb482c CAMEL-23452: camel-keycloak - Add Organizations API
operations (Keycloak 26+)
873ea8eb482c is described below
commit 873ea8eb482c7b46ee625d8bede0ca63b821f210
Author: Andrea Cosentino <[email protected]>
AuthorDate: Mon May 11 13:54:33 2026 +0200
CAMEL-23452: camel-keycloak - Add Organizations API operations (Keycloak
26+)
Wires the Keycloak 26 Organizations primitive (OrganizationsResource /
OrganizationResource) into the existing camel-keycloak component:
* 12 new operations on KeycloakOperations covering organization CRUD,
organization member management, and identity-provider linking.
* New header constants on KeycloakConstants for organization id, name,
alias, description, redirect URL, domain and search query.
* Producer logic in KeycloakProducer follows the existing
switch-on-operation pattern, with header-driven and pojoRequest
variants for create/update, paging support for list/search, and
consistent missing-input validation messages.
Unit tests in KeycloakProducerTest cover happy-path mocking for all 12
operations plus missing-name / missing-id validation. The
testcontainers-based KeycloakTestInfraIT exercises the full
create / list / get / search / member-add / list / remove /
link-idp / list-idps / unlink-idp / delete lifecycle against a real
Keycloak 26 server, with an Order(49) pre-step that enables
organizations on the test realm (per-realm opt-in feature in Keycloak
26). keycloak-component.adoc is updated with the new operations in the
Supported Operations summary and a new "Organization Operations"
section with end-to-end Java examples. camel-catalog and
camel-endpointdsl regen artifacts are included.
Closes #23113
---
.../apache/camel/catalog/components/keycloak.json | 15 +-
.../apache/camel/component/keycloak/keycloak.json | 15 +-
.../src/main/docs/keycloak-component.adoc | 84 ++++++
.../component/keycloak/KeycloakConstants.java | 22 ++
.../component/keycloak/KeycloakOperations.java | 15 +-
.../camel/component/keycloak/KeycloakProducer.java | 316 +++++++++++++++++++++
.../component/keycloak/KeycloakProducerTest.java | 277 ++++++++++++++++++
.../component/keycloak/KeycloakTestInfraIT.java | 299 +++++++++++++++++++
.../dsl/KeycloakEndpointBuilderFactory.java | 86 ++++++
9 files changed, 1120 insertions(+), 9 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/keycloak.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/keycloak.json
index 91c05b6be946..692f286998c7 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/keycloak.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/keycloak.json
@@ -43,7 +43,7 @@
"ipAddress": { "index": 16, "kind": "property", "displayName": "Ip
Address", "group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Filter events by IP
address" },
"keycloakClient": { "index": 17, "kind": "property", "displayName":
"Keycloak Client", "group": "common", "label": "", "required": false, "type":
"object", "javaType": "org.keycloak.admin.client.Keycloak", "deprecated":
false, "deprecationNote": "", "autowired": true, "secret": false,
"configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "To use an existing
configured Keycloak admin client" },
"maxResults": { "index": 18, "kind": "property", "displayName": "Max
Results", "group": "common", "label": "", "required": false, "type": "integer",
"javaType": "int", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": 100, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Maximum number of events
to retrieve per poll" },
- "operation": { "index": 19, "kind": "property", "displayName":
"Operation", "group": "common", "label": "", "required": false, "type": "enum",
"javaType": "org.apache.camel.component.keycloak.KeycloakOperations", "enum": [
"createRealm", "deleteRealm", "getRealm", "updateRealm", "createUser",
"deleteUser", "getUser", "updateUser", "listUsers", "searchUsers",
"createRole", "deleteRole", "getRole", "updateRole", "listRoles",
"assignRoleToUser", "removeRoleFromUser", "getUserRoles", "cr [...]
+ "operation": { "index": 19, "kind": "property", "displayName":
"Operation", "group": "common", "label": "", "required": false, "type": "enum",
"javaType": "org.apache.camel.component.keycloak.KeycloakOperations", "enum": [
"createRealm", "deleteRealm", "getRealm", "updateRealm", "createUser",
"deleteUser", "getUser", "updateUser", "listUsers", "searchUsers",
"createRole", "deleteRole", "getRole", "updateRole", "listRoles",
"assignRoleToUser", "removeRoleFromUser", "getUserRoles", "cr [...]
"operationTypes": { "index": 20, "kind": "property", "displayName":
"Operation Types", "group": "common", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Filter admin events by
operation types (comma-separated list, e.g., CREATE,UPDATE,DELETE)" },
"password": { "index": 21, "kind": "property", "displayName": "Password",
"group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Keycloak password" },
"pojoRequest": { "index": 22, "kind": "property", "displayName": "Pojo
Request", "group": "common", "label": "", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "If we want to use a POJO
request as body or not" },
@@ -59,7 +59,7 @@
"autowiredEnabled": { "index": 32, "kind": "property", "displayName":
"Autowired Enabled", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether autowiring is enabled. This is used for automatic autowiring options
(the option must be marked as autowired) by looking up in the registry to find
if there is a single instance of matching [...]
},
"headers": {
- "CamelKeycloakOperation": { "index": 0, "kind": "header", "displayName":
"", "group": "common", "label": "", "required": false, "javaType":
"org.apache.camel.component.keycloak.KeycloakOperations", "enum": [
"createRealm", "deleteRealm", "getRealm", "updateRealm", "createUser",
"deleteUser", "getUser", "updateUser", "listUsers", "searchUsers",
"createRole", "deleteRole", "getRole", "updateRole", "listRoles",
"assignRoleToUser", "removeRoleFromUser", "getUserRoles", "createGroup", "de
[...]
+ "CamelKeycloakOperation": { "index": 0, "kind": "header", "displayName":
"", "group": "common", "label": "", "required": false, "javaType":
"org.apache.camel.component.keycloak.KeycloakOperations", "enum": [
"createRealm", "deleteRealm", "getRealm", "updateRealm", "createUser",
"deleteUser", "getUser", "updateUser", "listUsers", "searchUsers",
"createRole", "deleteRole", "getRole", "updateRole", "listRoles",
"assignRoleToUser", "removeRoleFromUser", "getUserRoles", "createGroup", "de
[...]
"CamelKeycloakRealmName": { "index": 1, "kind": "header", "displayName":
"", "group": "common", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The realm name", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#REALM_NAME" },
"CamelKeycloakUserId": { "index": 2, "kind": "header", "displayName": "",
"group": "common", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The user ID", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#USER_ID" },
"CamelKeycloakUsername": { "index": 3, "kind": "header", "displayName":
"", "group": "common", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The username", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#USERNAME" },
@@ -113,7 +113,14 @@
"CamelKeycloakPermissionScopes": { "index": 51, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Comma-separated list of scopes to
evaluate permissions for", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#PERMISSION_SCOPES" },
"CamelKeycloakSubjectToken": { "index": 52, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Subject token for permission evaluation
on behalf of a user", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#SUBJECT_TOKEN" },
"CamelKeycloakPermissionAudience": { "index": 53, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Audience for permission evaluation",
"constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#PERMISSION_AUDIENCE" },
- "CamelKeycloakPermissionsOnly": { "index": 54, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Whether to only return the list of
permissions without obtaining an RPT", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#PERMISSIONS_ONLY" }
+ "CamelKeycloakPermissionsOnly": { "index": 54, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Whether to only return the list of
permissions without obtaining an RPT", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#PERMISSIONS_ONLY" },
+ "CamelKeycloakOrganizationId": { "index": 55, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The organization ID", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#ORGANIZATION_ID" },
+ "CamelKeycloakOrganizationName": { "index": 56, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The organization name", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#ORGANIZATION_NAME" },
+ "CamelKeycloakOrganizationAlias": { "index": 57, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The organization alias",
"constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#ORGANIZATION_ALIAS" },
+ "CamelKeycloakOrganizationDescription": { "index": 58, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The organization description",
"constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#ORGANIZATION_DESCRIPTION"
},
+ "CamelKeycloakOrganizationRedirectUrl": { "index": 59, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The organization redirect URL",
"constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#ORGANIZATION_REDIRECT_URL"
},
+ "CamelKeycloakOrganizationDomain": { "index": 60, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The organization domain name",
"constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#ORGANIZATION_DOMAIN" },
+ "CamelKeycloakOrganizationSearch": { "index": 61, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Search query for organizations",
"constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#ORGANIZATION_SEARCH" }
},
"properties": {
"label": { "index": 0, "kind": "path", "displayName": "Label", "group":
"common", "label": "", "required": true, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Logical name" },
@@ -135,7 +142,7 @@
"ipAddress": { "index": 16, "kind": "parameter", "displayName": "Ip
Address", "group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Filter events by IP
address" },
"keycloakClient": { "index": 17, "kind": "parameter", "displayName":
"Keycloak Client", "group": "common", "label": "", "required": false, "type":
"object", "javaType": "org.keycloak.admin.client.Keycloak", "deprecated":
false, "deprecationNote": "", "autowired": true, "secret": false,
"configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "To use an existing
configured Keycloak admin client" },
"maxResults": { "index": 18, "kind": "parameter", "displayName": "Max
Results", "group": "common", "label": "", "required": false, "type": "integer",
"javaType": "int", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": 100, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Maximum number of events
to retrieve per poll" },
- "operation": { "index": 19, "kind": "parameter", "displayName":
"Operation", "group": "common", "label": "", "required": false, "type": "enum",
"javaType": "org.apache.camel.component.keycloak.KeycloakOperations", "enum": [
"createRealm", "deleteRealm", "getRealm", "updateRealm", "createUser",
"deleteUser", "getUser", "updateUser", "listUsers", "searchUsers",
"createRole", "deleteRole", "getRole", "updateRole", "listRoles",
"assignRoleToUser", "removeRoleFromUser", "getUserRoles", "c [...]
+ "operation": { "index": 19, "kind": "parameter", "displayName":
"Operation", "group": "common", "label": "", "required": false, "type": "enum",
"javaType": "org.apache.camel.component.keycloak.KeycloakOperations", "enum": [
"createRealm", "deleteRealm", "getRealm", "updateRealm", "createUser",
"deleteUser", "getUser", "updateUser", "listUsers", "searchUsers",
"createRole", "deleteRole", "getRole", "updateRole", "listRoles",
"assignRoleToUser", "removeRoleFromUser", "getUserRoles", "c [...]
"operationTypes": { "index": 20, "kind": "parameter", "displayName":
"Operation Types", "group": "common", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Filter admin events by
operation types (comma-separated list, e.g., CREATE,UPDATE,DELETE)" },
"password": { "index": 21, "kind": "parameter", "displayName": "Password",
"group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Keycloak password" },
"pojoRequest": { "index": 22, "kind": "parameter", "displayName": "Pojo
Request", "group": "common", "label": "", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "If we want to use a POJO
request as body or not" },
diff --git
a/components/camel-keycloak/src/generated/resources/META-INF/org/apache/camel/component/keycloak/keycloak.json
b/components/camel-keycloak/src/generated/resources/META-INF/org/apache/camel/component/keycloak/keycloak.json
index 91c05b6be946..692f286998c7 100644
---
a/components/camel-keycloak/src/generated/resources/META-INF/org/apache/camel/component/keycloak/keycloak.json
+++
b/components/camel-keycloak/src/generated/resources/META-INF/org/apache/camel/component/keycloak/keycloak.json
@@ -43,7 +43,7 @@
"ipAddress": { "index": 16, "kind": "property", "displayName": "Ip
Address", "group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Filter events by IP
address" },
"keycloakClient": { "index": 17, "kind": "property", "displayName":
"Keycloak Client", "group": "common", "label": "", "required": false, "type":
"object", "javaType": "org.keycloak.admin.client.Keycloak", "deprecated":
false, "deprecationNote": "", "autowired": true, "secret": false,
"configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "To use an existing
configured Keycloak admin client" },
"maxResults": { "index": 18, "kind": "property", "displayName": "Max
Results", "group": "common", "label": "", "required": false, "type": "integer",
"javaType": "int", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": 100, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Maximum number of events
to retrieve per poll" },
- "operation": { "index": 19, "kind": "property", "displayName":
"Operation", "group": "common", "label": "", "required": false, "type": "enum",
"javaType": "org.apache.camel.component.keycloak.KeycloakOperations", "enum": [
"createRealm", "deleteRealm", "getRealm", "updateRealm", "createUser",
"deleteUser", "getUser", "updateUser", "listUsers", "searchUsers",
"createRole", "deleteRole", "getRole", "updateRole", "listRoles",
"assignRoleToUser", "removeRoleFromUser", "getUserRoles", "cr [...]
+ "operation": { "index": 19, "kind": "property", "displayName":
"Operation", "group": "common", "label": "", "required": false, "type": "enum",
"javaType": "org.apache.camel.component.keycloak.KeycloakOperations", "enum": [
"createRealm", "deleteRealm", "getRealm", "updateRealm", "createUser",
"deleteUser", "getUser", "updateUser", "listUsers", "searchUsers",
"createRole", "deleteRole", "getRole", "updateRole", "listRoles",
"assignRoleToUser", "removeRoleFromUser", "getUserRoles", "cr [...]
"operationTypes": { "index": 20, "kind": "property", "displayName":
"Operation Types", "group": "common", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Filter admin events by
operation types (comma-separated list, e.g., CREATE,UPDATE,DELETE)" },
"password": { "index": 21, "kind": "property", "displayName": "Password",
"group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Keycloak password" },
"pojoRequest": { "index": 22, "kind": "property", "displayName": "Pojo
Request", "group": "common", "label": "", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "If we want to use a POJO
request as body or not" },
@@ -59,7 +59,7 @@
"autowiredEnabled": { "index": 32, "kind": "property", "displayName":
"Autowired Enabled", "group": "advanced", "label": "advanced", "required":
false, "type": "boolean", "javaType": "boolean", "deprecated": false,
"autowired": false, "secret": false, "defaultValue": true, "description":
"Whether autowiring is enabled. This is used for automatic autowiring options
(the option must be marked as autowired) by looking up in the registry to find
if there is a single instance of matching [...]
},
"headers": {
- "CamelKeycloakOperation": { "index": 0, "kind": "header", "displayName":
"", "group": "common", "label": "", "required": false, "javaType":
"org.apache.camel.component.keycloak.KeycloakOperations", "enum": [
"createRealm", "deleteRealm", "getRealm", "updateRealm", "createUser",
"deleteUser", "getUser", "updateUser", "listUsers", "searchUsers",
"createRole", "deleteRole", "getRole", "updateRole", "listRoles",
"assignRoleToUser", "removeRoleFromUser", "getUserRoles", "createGroup", "de
[...]
+ "CamelKeycloakOperation": { "index": 0, "kind": "header", "displayName":
"", "group": "common", "label": "", "required": false, "javaType":
"org.apache.camel.component.keycloak.KeycloakOperations", "enum": [
"createRealm", "deleteRealm", "getRealm", "updateRealm", "createUser",
"deleteUser", "getUser", "updateUser", "listUsers", "searchUsers",
"createRole", "deleteRole", "getRole", "updateRole", "listRoles",
"assignRoleToUser", "removeRoleFromUser", "getUserRoles", "createGroup", "de
[...]
"CamelKeycloakRealmName": { "index": 1, "kind": "header", "displayName":
"", "group": "common", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The realm name", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#REALM_NAME" },
"CamelKeycloakUserId": { "index": 2, "kind": "header", "displayName": "",
"group": "common", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The user ID", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#USER_ID" },
"CamelKeycloakUsername": { "index": 3, "kind": "header", "displayName":
"", "group": "common", "label": "", "required": false, "javaType": "String",
"deprecated": false, "deprecationNote": "", "autowired": false, "secret":
false, "description": "The username", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#USERNAME" },
@@ -113,7 +113,14 @@
"CamelKeycloakPermissionScopes": { "index": 51, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Comma-separated list of scopes to
evaluate permissions for", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#PERMISSION_SCOPES" },
"CamelKeycloakSubjectToken": { "index": 52, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Subject token for permission evaluation
on behalf of a user", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#SUBJECT_TOKEN" },
"CamelKeycloakPermissionAudience": { "index": 53, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Audience for permission evaluation",
"constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#PERMISSION_AUDIENCE" },
- "CamelKeycloakPermissionsOnly": { "index": 54, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Whether to only return the list of
permissions without obtaining an RPT", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#PERMISSIONS_ONLY" }
+ "CamelKeycloakPermissionsOnly": { "index": 54, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Whether to only return the list of
permissions without obtaining an RPT", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#PERMISSIONS_ONLY" },
+ "CamelKeycloakOrganizationId": { "index": 55, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The organization ID", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#ORGANIZATION_ID" },
+ "CamelKeycloakOrganizationName": { "index": 56, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The organization name", "constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#ORGANIZATION_NAME" },
+ "CamelKeycloakOrganizationAlias": { "index": 57, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The organization alias",
"constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#ORGANIZATION_ALIAS" },
+ "CamelKeycloakOrganizationDescription": { "index": 58, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The organization description",
"constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#ORGANIZATION_DESCRIPTION"
},
+ "CamelKeycloakOrganizationRedirectUrl": { "index": 59, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The organization redirect URL",
"constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#ORGANIZATION_REDIRECT_URL"
},
+ "CamelKeycloakOrganizationDomain": { "index": 60, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "The organization domain name",
"constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#ORGANIZATION_DOMAIN" },
+ "CamelKeycloakOrganizationSearch": { "index": 61, "kind": "header",
"displayName": "", "group": "common", "label": "", "required": false,
"javaType": "String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "description": "Search query for organizations",
"constantName":
"org.apache.camel.component.keycloak.KeycloakConstants#ORGANIZATION_SEARCH" }
},
"properties": {
"label": { "index": 0, "kind": "path", "displayName": "Label", "group":
"common", "label": "", "required": true, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Logical name" },
@@ -135,7 +142,7 @@
"ipAddress": { "index": 16, "kind": "parameter", "displayName": "Ip
Address", "group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Filter events by IP
address" },
"keycloakClient": { "index": 17, "kind": "parameter", "displayName":
"Keycloak Client", "group": "common", "label": "", "required": false, "type":
"object", "javaType": "org.keycloak.admin.client.Keycloak", "deprecated":
false, "deprecationNote": "", "autowired": true, "secret": false,
"configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "To use an existing
configured Keycloak admin client" },
"maxResults": { "index": 18, "kind": "parameter", "displayName": "Max
Results", "group": "common", "label": "", "required": false, "type": "integer",
"javaType": "int", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": 100, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Maximum number of events
to retrieve per poll" },
- "operation": { "index": 19, "kind": "parameter", "displayName":
"Operation", "group": "common", "label": "", "required": false, "type": "enum",
"javaType": "org.apache.camel.component.keycloak.KeycloakOperations", "enum": [
"createRealm", "deleteRealm", "getRealm", "updateRealm", "createUser",
"deleteUser", "getUser", "updateUser", "listUsers", "searchUsers",
"createRole", "deleteRole", "getRole", "updateRole", "listRoles",
"assignRoleToUser", "removeRoleFromUser", "getUserRoles", "c [...]
+ "operation": { "index": 19, "kind": "parameter", "displayName":
"Operation", "group": "common", "label": "", "required": false, "type": "enum",
"javaType": "org.apache.camel.component.keycloak.KeycloakOperations", "enum": [
"createRealm", "deleteRealm", "getRealm", "updateRealm", "createUser",
"deleteUser", "getUser", "updateUser", "listUsers", "searchUsers",
"createRole", "deleteRole", "getRole", "updateRole", "listRoles",
"assignRoleToUser", "removeRoleFromUser", "getUserRoles", "c [...]
"operationTypes": { "index": 20, "kind": "parameter", "displayName":
"Operation Types", "group": "common", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Filter admin events by
operation types (comma-separated list, e.g., CREATE,UPDATE,DELETE)" },
"password": { "index": 21, "kind": "parameter", "displayName": "Password",
"group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": true, "security": "secret", "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "Keycloak password" },
"pojoRequest": { "index": 22, "kind": "parameter", "displayName": "Pojo
Request", "group": "common", "label": "", "required": false, "type": "boolean",
"javaType": "boolean", "deprecated": false, "autowired": false, "secret":
false, "defaultValue": false, "configurationClass":
"org.apache.camel.component.keycloak.KeycloakConfiguration",
"configurationField": "configuration", "description": "If we want to use a POJO
request as body or not" },
diff --git a/components/camel-keycloak/src/main/docs/keycloak-component.adoc
b/components/camel-keycloak/src/main/docs/keycloak-component.adoc
index d5eb6288a9e2..7a08c9899f7e 100644
--- a/components/camel-keycloak/src/main/docs/keycloak-component.adoc
+++ b/components/camel-keycloak/src/main/docs/keycloak-component.adoc
@@ -160,6 +160,7 @@ The component supports the following operations:
* **Client Scope Management**: `createClientScope`, `getClientScope`,
`updateClientScope`, `listClientScopes`, `deleteClientScope`
* **Identity Provider Management**: `createIdentityProvider`,
`getIdentityProvider`, `updateIdentityProvider`, `listIdentityProviders`,
`deleteIdentityProvider`
* **Authorization Services**: `createResource`, `getResource`,
`updateResource`, `listResources`, `deleteResource`, `createResourcePolicy`,
`getResourcePolicy`, `updateResourcePolicy`, `listResourcePolicies`,
`deleteResourcePolicy`, `createResourcePermission`, `getResourcePermission`,
`updateResourcePermission`, `listResourcePermissions`,
`deleteResourcePermission`, `evaluatePermission`
+* **Organization Management** (Keycloak 26+): `createOrganization`,
`getOrganization`, `updateOrganization`, `listOrganizations`,
`searchOrganizations`, `deleteOrganization`, `addOrganizationMember`,
`removeOrganizationMember`, `listOrganizationMembers`,
`linkOrganizationIdentityProvider`, `unlinkOrganizationIdentityProvider`,
`listOrganizationIdentityProviders`
=== Realm Operations
@@ -1009,6 +1010,89 @@ IdentityProviderRepresentation provider =
template.requestBodyAndHeaders(
template.sendBodyAndHeaders("keycloak:admin?operation=deleteIdentityProvider",
null, getIdpHeaders);
----
+=== Organization Operations
+
+Organizations (introduced in Keycloak 26) allow you to model multi-tenant
scenarios within a realm. Each organization can have members and can be linked
to one or more identity providers.
+
+[source,java]
+----
+// Create a new organization
+Map<String, Object> orgHeaders = new HashMap<>();
+orgHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
+orgHeaders.put(KeycloakConstants.ORGANIZATION_NAME, "acme-corp");
+orgHeaders.put(KeycloakConstants.ORGANIZATION_ALIAS, "acme");
+orgHeaders.put(KeycloakConstants.ORGANIZATION_DESCRIPTION, "Acme Corporation");
+orgHeaders.put(KeycloakConstants.ORGANIZATION_DOMAIN, "acme.com");
+
+template.sendBodyAndHeaders("keycloak:admin?operation=createOrganization",
null, orgHeaders);
+
+// Alternatively, pass a full OrganizationRepresentation via pojoRequest=true
+OrganizationRepresentation org = new OrganizationRepresentation();
+org.setName("acme-corp");
+org.setAlias("acme");
+org.setEnabled(true);
+OrganizationDomainRepresentation domain = new
OrganizationDomainRepresentation();
+domain.setName("acme.com");
+org.addDomain(domain);
+
+template.sendBodyAndHeader("keycloak:admin?operation=createOrganization&pojoRequest=true",
+ org, KeycloakConstants.REALM_NAME, "my-realm");
+
+// List all organizations in a realm
+template.sendBodyAndHeader("keycloak:admin?operation=listOrganizations", null,
+ KeycloakConstants.REALM_NAME, "my-realm");
+
+// Search organizations by name/alias/domain
+Map<String, Object> searchHeaders = new HashMap<>();
+searchHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
+searchHeaders.put(KeycloakConstants.ORGANIZATION_SEARCH, "acme");
+
+template.sendBodyAndHeaders("keycloak:admin?operation=searchOrganizations",
null, searchHeaders);
+
+// Get a specific organization by ID
+Map<String, Object> getOrgHeaders = new HashMap<>();
+getOrgHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
+getOrgHeaders.put(KeycloakConstants.ORGANIZATION_ID, "<organization-id>");
+
+OrganizationRepresentation organization = template.requestBodyAndHeaders(
+ "keycloak:admin?operation=getOrganization", null, getOrgHeaders,
+ OrganizationRepresentation.class);
+
+// Add an existing user as a member of an organization
+Map<String, Object> addMemberHeaders = new HashMap<>();
+addMemberHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
+addMemberHeaders.put(KeycloakConstants.ORGANIZATION_ID, "<organization-id>");
+addMemberHeaders.put(KeycloakConstants.USER_ID, "<user-id>");
+
+template.sendBodyAndHeaders("keycloak:admin?operation=addOrganizationMember",
null, addMemberHeaders);
+
+// List members of an organization
+template.sendBodyAndHeaders("keycloak:admin?operation=listOrganizationMembers",
null, getOrgHeaders);
+
+// Remove a member from an organization
+template.sendBodyAndHeaders("keycloak:admin?operation=removeOrganizationMember",
null, addMemberHeaders);
+
+// Link an identity provider to an organization
+Map<String, Object> linkIdpHeaders = new HashMap<>();
+linkIdpHeaders.put(KeycloakConstants.REALM_NAME, "my-realm");
+linkIdpHeaders.put(KeycloakConstants.ORGANIZATION_ID, "<organization-id>");
+linkIdpHeaders.put(KeycloakConstants.IDP_ALIAS, "acme-saml");
+
+template.sendBodyAndHeaders("keycloak:admin?operation=linkOrganizationIdentityProvider",
+ null, linkIdpHeaders);
+
+// List identity providers linked to an organization
+template.sendBodyAndHeaders("keycloak:admin?operation=listOrganizationIdentityProviders",
+ null, getOrgHeaders);
+
+// Unlink an identity provider from an organization
+template.sendBodyAndHeaders("keycloak:admin?operation=unlinkOrganizationIdentityProvider",
+ null, linkIdpHeaders);
+
+// Delete an organization
+template.sendBodyAndHeaders("keycloak:admin?operation=deleteOrganization",
null, getOrgHeaders);
+----
+
=== User Attribute Operations
User attributes allow you to store custom key-value pairs on user accounts for
additional metadata or application-specific data.
diff --git
a/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakConstants.java
b/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakConstants.java
index c3a7d397ac38..1f5399780fa8 100644
---
a/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakConstants.java
+++
b/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakConstants.java
@@ -193,6 +193,28 @@ public final class KeycloakConstants {
@Metadata(description = "Whether to only return the list of permissions
without obtaining an RPT", javaType = "Boolean")
public static final String PERMISSIONS_ONLY =
"CamelKeycloakPermissionsOnly";
+ // Organization constants (Keycloak 26+)
+ @Metadata(description = "The organization ID", javaType = "String")
+ public static final String ORGANIZATION_ID = "CamelKeycloakOrganizationId";
+
+ @Metadata(description = "The organization name", javaType = "String")
+ public static final String ORGANIZATION_NAME =
"CamelKeycloakOrganizationName";
+
+ @Metadata(description = "The organization alias", javaType = "String")
+ public static final String ORGANIZATION_ALIAS =
"CamelKeycloakOrganizationAlias";
+
+ @Metadata(description = "The organization description", javaType =
"String")
+ public static final String ORGANIZATION_DESCRIPTION =
"CamelKeycloakOrganizationDescription";
+
+ @Metadata(description = "The organization redirect URL", javaType =
"String")
+ public static final String ORGANIZATION_REDIRECT_URL =
"CamelKeycloakOrganizationRedirectUrl";
+
+ @Metadata(description = "The organization domain name", javaType =
"String")
+ public static final String ORGANIZATION_DOMAIN =
"CamelKeycloakOrganizationDomain";
+
+ @Metadata(description = "Search query for organizations", javaType =
"String")
+ public static final String ORGANIZATION_SEARCH =
"CamelKeycloakOrganizationSearch";
+
private KeycloakConstants() {
// Utility class
}
diff --git
a/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakOperations.java
b/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakOperations.java
index 5743a5a00718..7bddf4a57569 100644
---
a/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakOperations.java
+++
b/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakOperations.java
@@ -114,5 +114,18 @@ public enum KeycloakOperations {
bulkDeleteUsers,
bulkAssignRolesToUser,
bulkAssignRoleToUsers,
- bulkUpdateUsers
+ bulkUpdateUsers,
+ // Organization operations (Keycloak 26+)
+ createOrganization,
+ updateOrganization,
+ deleteOrganization,
+ getOrganization,
+ listOrganizations,
+ searchOrganizations,
+ addOrganizationMember,
+ removeOrganizationMember,
+ listOrganizationMembers,
+ linkOrganizationIdentityProvider,
+ unlinkOrganizationIdentityProvider,
+ listOrganizationIdentityProviders
}
diff --git
a/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakProducer.java
b/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakProducer.java
index 0d3d605353ef..50ab83d3617b 100644
---
a/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakProducer.java
+++
b/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakProducer.java
@@ -41,6 +41,9 @@ import
org.keycloak.representations.idm.ClientScopeRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
+import org.keycloak.representations.idm.MemberRepresentation;
+import org.keycloak.representations.idm.OrganizationDomainRepresentation;
+import org.keycloak.representations.idm.OrganizationRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
@@ -67,6 +70,10 @@ public class KeycloakProducer extends DefaultProducer {
public static final String MISSING_CLIENT_ID = "Client ID must be
specified";
public static final String MISSING_CLIENT_UUID = "Client UUID must be
specified";
public static final String MISSING_PASSWORD = "Password must be specified";
+ public static final String MISSING_ORGANIZATION_ID = "Organization ID must
be specified";
+ public static final String MISSING_ORGANIZATION_NAME = "Organization name
must be specified";
+ public static final String MISSING_ORGANIZATION_MEMBER_ID = "Organization
member (user) ID must be specified";
+ public static final String MISSING_ORGANIZATION_IDP_ALIAS = "Organization
identity provider alias must be specified";
private transient String keycloakProducerToString;
@@ -334,6 +341,42 @@ public class KeycloakProducer extends DefaultProducer {
case bulkUpdateUsers:
bulkUpdateUsers(getEndpoint().getKeycloakClient(), exchange);
break;
+ case createOrganization:
+ createOrganization(getEndpoint().getKeycloakClient(),
exchange);
+ break;
+ case updateOrganization:
+ updateOrganization(getEndpoint().getKeycloakClient(),
exchange);
+ break;
+ case deleteOrganization:
+ deleteOrganization(getEndpoint().getKeycloakClient(),
exchange);
+ break;
+ case getOrganization:
+ getOrganization(getEndpoint().getKeycloakClient(), exchange);
+ break;
+ case listOrganizations:
+ listOrganizations(getEndpoint().getKeycloakClient(), exchange);
+ break;
+ case searchOrganizations:
+ searchOrganizations(getEndpoint().getKeycloakClient(),
exchange);
+ break;
+ case addOrganizationMember:
+ addOrganizationMember(getEndpoint().getKeycloakClient(),
exchange);
+ break;
+ case removeOrganizationMember:
+ removeOrganizationMember(getEndpoint().getKeycloakClient(),
exchange);
+ break;
+ case listOrganizationMembers:
+ listOrganizationMembers(getEndpoint().getKeycloakClient(),
exchange);
+ break;
+ case linkOrganizationIdentityProvider:
+
linkOrganizationIdentityProvider(getEndpoint().getKeycloakClient(), exchange);
+ break;
+ case unlinkOrganizationIdentityProvider:
+
unlinkOrganizationIdentityProvider(getEndpoint().getKeycloakClient(), exchange);
+ break;
+ case listOrganizationIdentityProviders:
+
listOrganizationIdentityProviders(getEndpoint().getKeycloakClient(), exchange);
+ break;
default:
throw new IllegalArgumentException("Unsupported operation: " +
operation);
}
@@ -2450,6 +2493,279 @@ public class KeycloakProducer extends DefaultProducer {
message.setBody(summary);
}
+ // Organization operations (Keycloak 26+)
+ private void createOrganization(Keycloak keycloakClient, Exchange
exchange) throws InvalidPayloadException {
+ String realmName =
exchange.getIn().getHeader(KeycloakConstants.REALM_NAME, String.class);
+ if (ObjectHelper.isEmpty(realmName)) {
+ throw new IllegalArgumentException(MISSING_REALM_NAME);
+ }
+
+ if (getConfiguration().isPojoRequest()) {
+ Object payload = exchange.getIn().getMandatoryBody();
+ if (payload instanceof OrganizationRepresentation
orgRepresentation) {
+ Response response =
keycloakClient.realm(realmName).organizations().create(orgRepresentation);
+ Message message = getMessageForResponse(exchange);
+ message.setBody(response);
+ }
+ } else {
+ String orgName =
exchange.getIn().getHeader(KeycloakConstants.ORGANIZATION_NAME, String.class);
+ if (ObjectHelper.isEmpty(orgName)) {
+ throw new IllegalArgumentException(MISSING_ORGANIZATION_NAME);
+ }
+ OrganizationRepresentation org = new OrganizationRepresentation();
+ org.setName(orgName);
+ org.setEnabled(true);
+
+ String alias =
exchange.getIn().getHeader(KeycloakConstants.ORGANIZATION_ALIAS, String.class);
+ if (ObjectHelper.isNotEmpty(alias)) {
+ org.setAlias(alias);
+ }
+ String description =
exchange.getIn().getHeader(KeycloakConstants.ORGANIZATION_DESCRIPTION,
String.class);
+ if (ObjectHelper.isNotEmpty(description)) {
+ org.setDescription(description);
+ }
+ String redirectUrl =
exchange.getIn().getHeader(KeycloakConstants.ORGANIZATION_REDIRECT_URL,
String.class);
+ if (ObjectHelper.isNotEmpty(redirectUrl)) {
+ org.setRedirectUrl(redirectUrl);
+ }
+ String domainName =
exchange.getIn().getHeader(KeycloakConstants.ORGANIZATION_DOMAIN, String.class);
+ if (ObjectHelper.isNotEmpty(domainName)) {
+ OrganizationDomainRepresentation domain = new
OrganizationDomainRepresentation();
+ domain.setName(domainName);
+ org.addDomain(domain);
+ }
+
+ Response response =
keycloakClient.realm(realmName).organizations().create(org);
+ Message message = getMessageForResponse(exchange);
+ message.setBody(response);
+ }
+ }
+
+ private void updateOrganization(Keycloak keycloakClient, Exchange
exchange) throws InvalidPayloadException {
+ String realmName =
exchange.getIn().getHeader(KeycloakConstants.REALM_NAME, String.class);
+ if (ObjectHelper.isEmpty(realmName)) {
+ throw new IllegalArgumentException(MISSING_REALM_NAME);
+ }
+
+ String orgId =
exchange.getIn().getHeader(KeycloakConstants.ORGANIZATION_ID, String.class);
+ if (ObjectHelper.isEmpty(orgId)) {
+ throw new IllegalArgumentException(MISSING_ORGANIZATION_ID);
+ }
+
+ if (getConfiguration().isPojoRequest()) {
+ Object payload = exchange.getIn().getMandatoryBody();
+ if (payload instanceof OrganizationRepresentation
orgRepresentation) {
+ Response response =
keycloakClient.realm(realmName).organizations().get(orgId).update(orgRepresentation);
+ Message message = getMessageForResponse(exchange);
+ message.setBody(response);
+ }
+ } else {
+ throw new IllegalArgumentException("Update organization requires
POJO request with OrganizationRepresentation");
+ }
+ }
+
+ private void deleteOrganization(Keycloak keycloakClient, Exchange
exchange) {
+ String realmName =
exchange.getIn().getHeader(KeycloakConstants.REALM_NAME, String.class);
+ if (ObjectHelper.isEmpty(realmName)) {
+ throw new IllegalArgumentException(MISSING_REALM_NAME);
+ }
+
+ String orgId =
exchange.getIn().getHeader(KeycloakConstants.ORGANIZATION_ID, String.class);
+ if (ObjectHelper.isEmpty(orgId)) {
+ throw new IllegalArgumentException(MISSING_ORGANIZATION_ID);
+ }
+
+ keycloakClient.realm(realmName).organizations().get(orgId).delete();
+ Message message = getMessageForResponse(exchange);
+ message.setBody("Organization deleted successfully");
+ }
+
+ private void getOrganization(Keycloak keycloakClient, Exchange exchange) {
+ String realmName =
exchange.getIn().getHeader(KeycloakConstants.REALM_NAME, String.class);
+ if (ObjectHelper.isEmpty(realmName)) {
+ throw new IllegalArgumentException(MISSING_REALM_NAME);
+ }
+
+ String orgId =
exchange.getIn().getHeader(KeycloakConstants.ORGANIZATION_ID, String.class);
+ if (ObjectHelper.isEmpty(orgId)) {
+ throw new IllegalArgumentException(MISSING_ORGANIZATION_ID);
+ }
+
+ OrganizationRepresentation org =
keycloakClient.realm(realmName).organizations().get(orgId).toRepresentation();
+ Message message = getMessageForResponse(exchange);
+ message.setBody(org);
+ }
+
+ private void listOrganizations(Keycloak keycloakClient, Exchange exchange)
{
+ String realmName =
exchange.getIn().getHeader(KeycloakConstants.REALM_NAME, String.class);
+ if (ObjectHelper.isEmpty(realmName)) {
+ throw new IllegalArgumentException(MISSING_REALM_NAME);
+ }
+
+ Integer firstResult =
exchange.getIn().getHeader(KeycloakConstants.FIRST_RESULT, Integer.class);
+ Integer maxResults =
exchange.getIn().getHeader(KeycloakConstants.MAX_RESULTS, Integer.class);
+
+ List<OrganizationRepresentation> organizations;
+ if (firstResult != null || maxResults != null) {
+ organizations =
keycloakClient.realm(realmName).organizations().list(firstResult, maxResults);
+ } else {
+ organizations =
keycloakClient.realm(realmName).organizations().getAll();
+ }
+ Message message = getMessageForResponse(exchange);
+ message.setBody(organizations);
+ }
+
+ private void searchOrganizations(Keycloak keycloakClient, Exchange
exchange) {
+ String realmName =
exchange.getIn().getHeader(KeycloakConstants.REALM_NAME, String.class);
+ if (ObjectHelper.isEmpty(realmName)) {
+ throw new IllegalArgumentException(MISSING_REALM_NAME);
+ }
+
+ String search =
exchange.getIn().getHeader(KeycloakConstants.ORGANIZATION_SEARCH, String.class);
+ if (ObjectHelper.isEmpty(search)) {
+ throw new IllegalArgumentException("Organization search query must
be specified");
+ }
+
+ Integer firstResult =
exchange.getIn().getHeader(KeycloakConstants.FIRST_RESULT, Integer.class);
+ Integer maxResults =
exchange.getIn().getHeader(KeycloakConstants.MAX_RESULTS, Integer.class);
+
+ List<OrganizationRepresentation> organizations;
+ if (firstResult != null || maxResults != null) {
+ organizations =
keycloakClient.realm(realmName).organizations().search(search, Boolean.FALSE,
firstResult,
+ maxResults);
+ } else {
+ organizations =
keycloakClient.realm(realmName).organizations().search(search);
+ }
+ Message message = getMessageForResponse(exchange);
+ message.setBody(organizations);
+ }
+
+ private void addOrganizationMember(Keycloak keycloakClient, Exchange
exchange) {
+ String realmName =
exchange.getIn().getHeader(KeycloakConstants.REALM_NAME, String.class);
+ if (ObjectHelper.isEmpty(realmName)) {
+ throw new IllegalArgumentException(MISSING_REALM_NAME);
+ }
+
+ String orgId =
exchange.getIn().getHeader(KeycloakConstants.ORGANIZATION_ID, String.class);
+ if (ObjectHelper.isEmpty(orgId)) {
+ throw new IllegalArgumentException(MISSING_ORGANIZATION_ID);
+ }
+
+ String userId = exchange.getIn().getHeader(KeycloakConstants.USER_ID,
String.class);
+ if (ObjectHelper.isEmpty(userId)) {
+ throw new IllegalArgumentException(MISSING_ORGANIZATION_MEMBER_ID);
+ }
+
+ Response response =
keycloakClient.realm(realmName).organizations().get(orgId).members().addMember(userId);
+ Message message = getMessageForResponse(exchange);
+ message.setBody(response);
+ }
+
+ private void removeOrganizationMember(Keycloak keycloakClient, Exchange
exchange) {
+ String realmName =
exchange.getIn().getHeader(KeycloakConstants.REALM_NAME, String.class);
+ if (ObjectHelper.isEmpty(realmName)) {
+ throw new IllegalArgumentException(MISSING_REALM_NAME);
+ }
+
+ String orgId =
exchange.getIn().getHeader(KeycloakConstants.ORGANIZATION_ID, String.class);
+ if (ObjectHelper.isEmpty(orgId)) {
+ throw new IllegalArgumentException(MISSING_ORGANIZATION_ID);
+ }
+
+ String userId = exchange.getIn().getHeader(KeycloakConstants.USER_ID,
String.class);
+ if (ObjectHelper.isEmpty(userId)) {
+ throw new IllegalArgumentException(MISSING_ORGANIZATION_MEMBER_ID);
+ }
+
+
keycloakClient.realm(realmName).organizations().get(orgId).members().member(userId).delete();
+ Message message = getMessageForResponse(exchange);
+ message.setBody("Organization member removed successfully");
+ }
+
+ private void listOrganizationMembers(Keycloak keycloakClient, Exchange
exchange) {
+ String realmName =
exchange.getIn().getHeader(KeycloakConstants.REALM_NAME, String.class);
+ if (ObjectHelper.isEmpty(realmName)) {
+ throw new IllegalArgumentException(MISSING_REALM_NAME);
+ }
+
+ String orgId =
exchange.getIn().getHeader(KeycloakConstants.ORGANIZATION_ID, String.class);
+ if (ObjectHelper.isEmpty(orgId)) {
+ throw new IllegalArgumentException(MISSING_ORGANIZATION_ID);
+ }
+
+ Integer firstResult =
exchange.getIn().getHeader(KeycloakConstants.FIRST_RESULT, Integer.class);
+ Integer maxResults =
exchange.getIn().getHeader(KeycloakConstants.MAX_RESULTS, Integer.class);
+
+ List<MemberRepresentation> members;
+ if (firstResult != null || maxResults != null) {
+ members =
keycloakClient.realm(realmName).organizations().get(orgId).members().list(firstResult,
maxResults);
+ } else {
+ members =
keycloakClient.realm(realmName).organizations().get(orgId).members().getAll();
+ }
+ Message message = getMessageForResponse(exchange);
+ message.setBody(members);
+ }
+
+ private void linkOrganizationIdentityProvider(Keycloak keycloakClient,
Exchange exchange) {
+ String realmName =
exchange.getIn().getHeader(KeycloakConstants.REALM_NAME, String.class);
+ if (ObjectHelper.isEmpty(realmName)) {
+ throw new IllegalArgumentException(MISSING_REALM_NAME);
+ }
+
+ String orgId =
exchange.getIn().getHeader(KeycloakConstants.ORGANIZATION_ID, String.class);
+ if (ObjectHelper.isEmpty(orgId)) {
+ throw new IllegalArgumentException(MISSING_ORGANIZATION_ID);
+ }
+
+ String idpAlias =
exchange.getIn().getHeader(KeycloakConstants.IDP_ALIAS, String.class);
+ if (ObjectHelper.isEmpty(idpAlias)) {
+ throw new IllegalArgumentException(MISSING_ORGANIZATION_IDP_ALIAS);
+ }
+
+ Response response
+ =
keycloakClient.realm(realmName).organizations().get(orgId).identityProviders().addIdentityProvider(idpAlias);
+ Message message = getMessageForResponse(exchange);
+ message.setBody(response);
+ }
+
+ private void unlinkOrganizationIdentityProvider(Keycloak keycloakClient,
Exchange exchange) {
+ String realmName =
exchange.getIn().getHeader(KeycloakConstants.REALM_NAME, String.class);
+ if (ObjectHelper.isEmpty(realmName)) {
+ throw new IllegalArgumentException(MISSING_REALM_NAME);
+ }
+
+ String orgId =
exchange.getIn().getHeader(KeycloakConstants.ORGANIZATION_ID, String.class);
+ if (ObjectHelper.isEmpty(orgId)) {
+ throw new IllegalArgumentException(MISSING_ORGANIZATION_ID);
+ }
+
+ String idpAlias =
exchange.getIn().getHeader(KeycloakConstants.IDP_ALIAS, String.class);
+ if (ObjectHelper.isEmpty(idpAlias)) {
+ throw new IllegalArgumentException(MISSING_ORGANIZATION_IDP_ALIAS);
+ }
+
+
keycloakClient.realm(realmName).organizations().get(orgId).identityProviders().get(idpAlias).delete();
+ Message message = getMessageForResponse(exchange);
+ message.setBody("Organization identity provider unlinked
successfully");
+ }
+
+ private void listOrganizationIdentityProviders(Keycloak keycloakClient,
Exchange exchange) {
+ String realmName =
exchange.getIn().getHeader(KeycloakConstants.REALM_NAME, String.class);
+ if (ObjectHelper.isEmpty(realmName)) {
+ throw new IllegalArgumentException(MISSING_REALM_NAME);
+ }
+
+ String orgId =
exchange.getIn().getHeader(KeycloakConstants.ORGANIZATION_ID, String.class);
+ if (ObjectHelper.isEmpty(orgId)) {
+ throw new IllegalArgumentException(MISSING_ORGANIZATION_ID);
+ }
+
+ List<IdentityProviderRepresentation> idps
+ =
keycloakClient.realm(realmName).organizations().get(orgId).identityProviders().getIdentityProviders();
+ Message message = getMessageForResponse(exchange);
+ message.setBody(idps);
+ }
+
public static Message getMessageForResponse(final Exchange exchange) {
return exchange.getMessage();
}
diff --git
a/components/camel-keycloak/src/test/java/org/apache/camel/component/keycloak/KeycloakProducerTest.java
b/components/camel-keycloak/src/test/java/org/apache/camel/component/keycloak/KeycloakProducerTest.java
index 71331c02844b..f149a7e7fd94 100644
---
a/components/camel-keycloak/src/test/java/org/apache/camel/component/keycloak/KeycloakProducerTest.java
+++
b/components/camel-keycloak/src/test/java/org/apache/camel/component/keycloak/KeycloakProducerTest.java
@@ -29,6 +29,12 @@ import org.junit.jupiter.api.Test;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.ClientsResource;
import org.keycloak.admin.client.resource.GroupsResource;
+import org.keycloak.admin.client.resource.OrganizationIdentityProviderResource;
+import
org.keycloak.admin.client.resource.OrganizationIdentityProvidersResource;
+import org.keycloak.admin.client.resource.OrganizationMemberResource;
+import org.keycloak.admin.client.resource.OrganizationMembersResource;
+import org.keycloak.admin.client.resource.OrganizationResource;
+import org.keycloak.admin.client.resource.OrganizationsResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.RealmsResource;
import org.keycloak.admin.client.resource.RoleMappingResource;
@@ -38,6 +44,8 @@ import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.GroupRepresentation;
+import org.keycloak.representations.idm.MemberRepresentation;
+import org.keycloak.representations.idm.OrganizationRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.mockito.Mockito;
@@ -61,6 +69,14 @@ public class KeycloakProducerTest extends CamelTestSupport {
private ClientsResource clientsResource =
Mockito.mock(ClientsResource.class);
private RoleMappingResource roleMappingResource =
Mockito.mock(RoleMappingResource.class);
private RoleScopeResource roleScopeResource =
Mockito.mock(RoleScopeResource.class);
+ private OrganizationsResource organizationsResource =
Mockito.mock(OrganizationsResource.class);
+ private OrganizationResource organizationResource =
Mockito.mock(OrganizationResource.class);
+ private OrganizationMembersResource organizationMembersResource =
Mockito.mock(OrganizationMembersResource.class);
+ private OrganizationMemberResource organizationMemberResource =
Mockito.mock(OrganizationMemberResource.class);
+ private OrganizationIdentityProvidersResource organizationIdpsResource
+ = Mockito.mock(OrganizationIdentityProvidersResource.class);
+ private OrganizationIdentityProviderResource organizationIdpResource
+ = Mockito.mock(OrganizationIdentityProviderResource.class);
private Response response = Mockito.mock(Response.class);
@Override
@@ -119,6 +135,50 @@ public class KeycloakProducerTest extends CamelTestSupport
{
from("direct:evaluatePermission")
.to("keycloak:test?keycloakClient=#keycloakClient&operation=evaluatePermission")
.to("mock:result");
+
+ from("direct:createOrganization")
+
.to("keycloak:test?keycloakClient=#keycloakClient&operation=createOrganization")
+ .to("mock:result");
+
+ from("direct:listOrganizations")
+
.to("keycloak:test?keycloakClient=#keycloakClient&operation=listOrganizations")
+ .to("mock:result");
+
+ from("direct:getOrganization")
+
.to("keycloak:test?keycloakClient=#keycloakClient&operation=getOrganization")
+ .to("mock:result");
+
+ from("direct:deleteOrganization")
+
.to("keycloak:test?keycloakClient=#keycloakClient&operation=deleteOrganization")
+ .to("mock:result");
+
+ from("direct:searchOrganizations")
+
.to("keycloak:test?keycloakClient=#keycloakClient&operation=searchOrganizations")
+ .to("mock:result");
+
+ from("direct:addOrganizationMember")
+
.to("keycloak:test?keycloakClient=#keycloakClient&operation=addOrganizationMember")
+ .to("mock:result");
+
+ from("direct:removeOrganizationMember")
+
.to("keycloak:test?keycloakClient=#keycloakClient&operation=removeOrganizationMember")
+ .to("mock:result");
+
+ from("direct:listOrganizationMembers")
+
.to("keycloak:test?keycloakClient=#keycloakClient&operation=listOrganizationMembers")
+ .to("mock:result");
+
+ from("direct:linkOrganizationIdentityProvider")
+
.to("keycloak:test?keycloakClient=#keycloakClient&operation=linkOrganizationIdentityProvider")
+ .to("mock:result");
+
+ from("direct:unlinkOrganizationIdentityProvider")
+
.to("keycloak:test?keycloakClient=#keycloakClient&operation=unlinkOrganizationIdentityProvider")
+ .to("mock:result");
+
+ from("direct:listOrganizationIdentityProviders")
+
.to("keycloak:test?keycloakClient=#keycloakClient&operation=listOrganizationIdentityProviders")
+ .to("mock:result");
}
};
}
@@ -347,4 +407,221 @@ public class KeycloakProducerTest extends
CamelTestSupport {
assertTrue(e.getCause().getMessage().contains("Server URL must be
specified"));
}
}
+
+ @Test
+ public void testCreateOrganization() throws Exception {
+ when(keycloakClient.realm(anyString())).thenReturn(realmResource);
+ when(realmResource.organizations()).thenReturn(organizationsResource);
+
when(organizationsResource.create(any(OrganizationRepresentation.class))).thenReturn(response);
+
+ MockEndpoint mock = getMockEndpoint("mock:result");
+ mock.expectedMessageCount(1);
+
+ template.sendBodyAndHeaders("direct:createOrganization", null, Map.of(
+ KeycloakConstants.REALM_NAME, "testRealm",
+ KeycloakConstants.ORGANIZATION_NAME, "testOrg",
+ KeycloakConstants.ORGANIZATION_ALIAS, "test-org",
+ KeycloakConstants.ORGANIZATION_DOMAIN, "test.example.com"));
+
+ MockEndpoint.assertIsSatisfied(context);
+ }
+
+ @Test
+ public void testListOrganizations() throws Exception {
+ when(keycloakClient.realm(anyString())).thenReturn(realmResource);
+ when(realmResource.organizations()).thenReturn(organizationsResource);
+ when(organizationsResource.getAll()).thenReturn(List.of(new
OrganizationRepresentation()));
+
+ MockEndpoint mock = getMockEndpoint("mock:result");
+ mock.expectedMessageCount(1);
+
+ template.sendBodyAndHeader("direct:listOrganizations", null,
KeycloakConstants.REALM_NAME, "testRealm");
+
+ MockEndpoint.assertIsSatisfied(context);
+ }
+
+ @Test
+ public void testGetOrganization() throws Exception {
+ when(keycloakClient.realm(anyString())).thenReturn(realmResource);
+ when(realmResource.organizations()).thenReturn(organizationsResource);
+
when(organizationsResource.get(anyString())).thenReturn(organizationResource);
+ when(organizationResource.toRepresentation()).thenReturn(new
OrganizationRepresentation());
+
+ MockEndpoint mock = getMockEndpoint("mock:result");
+ mock.expectedMessageCount(1);
+
+ template.sendBodyAndHeaders("direct:getOrganization", null, Map.of(
+ KeycloakConstants.REALM_NAME, "testRealm",
+ KeycloakConstants.ORGANIZATION_ID, "orgId123"));
+
+ MockEndpoint.assertIsSatisfied(context);
+ }
+
+ @Test
+ public void testDeleteOrganization() throws Exception {
+ when(keycloakClient.realm(anyString())).thenReturn(realmResource);
+ when(realmResource.organizations()).thenReturn(organizationsResource);
+
when(organizationsResource.get(anyString())).thenReturn(organizationResource);
+
+ MockEndpoint mock = getMockEndpoint("mock:result");
+ mock.expectedMessageCount(1);
+
+ template.sendBodyAndHeaders("direct:deleteOrganization", null, Map.of(
+ KeycloakConstants.REALM_NAME, "testRealm",
+ KeycloakConstants.ORGANIZATION_ID, "orgId123"));
+
+ MockEndpoint.assertIsSatisfied(context);
+ }
+
+ @Test
+ public void testSearchOrganizations() throws Exception {
+ when(keycloakClient.realm(anyString())).thenReturn(realmResource);
+ when(realmResource.organizations()).thenReturn(organizationsResource);
+ when(organizationsResource.search(anyString())).thenReturn(List.of(new
OrganizationRepresentation()));
+
+ MockEndpoint mock = getMockEndpoint("mock:result");
+ mock.expectedMessageCount(1);
+
+ template.sendBodyAndHeaders("direct:searchOrganizations", null, Map.of(
+ KeycloakConstants.REALM_NAME, "testRealm",
+ KeycloakConstants.ORGANIZATION_SEARCH, "test"));
+
+ MockEndpoint.assertIsSatisfied(context);
+ }
+
+ @Test
+ public void testAddOrganizationMember() throws Exception {
+ when(keycloakClient.realm(anyString())).thenReturn(realmResource);
+ when(realmResource.organizations()).thenReturn(organizationsResource);
+
when(organizationsResource.get(anyString())).thenReturn(organizationResource);
+
when(organizationResource.members()).thenReturn(organizationMembersResource);
+
when(organizationMembersResource.addMember(anyString())).thenReturn(response);
+
+ MockEndpoint mock = getMockEndpoint("mock:result");
+ mock.expectedMessageCount(1);
+
+ template.sendBodyAndHeaders("direct:addOrganizationMember", null,
Map.of(
+ KeycloakConstants.REALM_NAME, "testRealm",
+ KeycloakConstants.ORGANIZATION_ID, "orgId123",
+ KeycloakConstants.USER_ID, "userId123"));
+
+ MockEndpoint.assertIsSatisfied(context);
+ }
+
+ @Test
+ public void testRemoveOrganizationMember() throws Exception {
+ when(keycloakClient.realm(anyString())).thenReturn(realmResource);
+ when(realmResource.organizations()).thenReturn(organizationsResource);
+
when(organizationsResource.get(anyString())).thenReturn(organizationResource);
+
when(organizationResource.members()).thenReturn(organizationMembersResource);
+
when(organizationMembersResource.member(anyString())).thenReturn(organizationMemberResource);
+
+ MockEndpoint mock = getMockEndpoint("mock:result");
+ mock.expectedMessageCount(1);
+
+ template.sendBodyAndHeaders("direct:removeOrganizationMember", null,
Map.of(
+ KeycloakConstants.REALM_NAME, "testRealm",
+ KeycloakConstants.ORGANIZATION_ID, "orgId123",
+ KeycloakConstants.USER_ID, "userId123"));
+
+ MockEndpoint.assertIsSatisfied(context);
+ }
+
+ @Test
+ public void testListOrganizationMembers() throws Exception {
+ when(keycloakClient.realm(anyString())).thenReturn(realmResource);
+ when(realmResource.organizations()).thenReturn(organizationsResource);
+
when(organizationsResource.get(anyString())).thenReturn(organizationResource);
+
when(organizationResource.members()).thenReturn(organizationMembersResource);
+ when(organizationMembersResource.getAll()).thenReturn(List.of(new
MemberRepresentation()));
+
+ MockEndpoint mock = getMockEndpoint("mock:result");
+ mock.expectedMessageCount(1);
+
+ template.sendBodyAndHeaders("direct:listOrganizationMembers", null,
Map.of(
+ KeycloakConstants.REALM_NAME, "testRealm",
+ KeycloakConstants.ORGANIZATION_ID, "orgId123"));
+
+ MockEndpoint.assertIsSatisfied(context);
+ }
+
+ @Test
+ public void testLinkOrganizationIdentityProvider() throws Exception {
+ when(keycloakClient.realm(anyString())).thenReturn(realmResource);
+ when(realmResource.organizations()).thenReturn(organizationsResource);
+
when(organizationsResource.get(anyString())).thenReturn(organizationResource);
+
when(organizationResource.identityProviders()).thenReturn(organizationIdpsResource);
+
when(organizationIdpsResource.addIdentityProvider(anyString())).thenReturn(response);
+
+ MockEndpoint mock = getMockEndpoint("mock:result");
+ mock.expectedMessageCount(1);
+
+ template.sendBodyAndHeaders("direct:linkOrganizationIdentityProvider",
null, Map.of(
+ KeycloakConstants.REALM_NAME, "testRealm",
+ KeycloakConstants.ORGANIZATION_ID, "orgId123",
+ KeycloakConstants.IDP_ALIAS, "my-idp"));
+
+ MockEndpoint.assertIsSatisfied(context);
+ }
+
+ @Test
+ public void testUnlinkOrganizationIdentityProvider() throws Exception {
+ when(keycloakClient.realm(anyString())).thenReturn(realmResource);
+ when(realmResource.organizations()).thenReturn(organizationsResource);
+
when(organizationsResource.get(anyString())).thenReturn(organizationResource);
+
when(organizationResource.identityProviders()).thenReturn(organizationIdpsResource);
+
when(organizationIdpsResource.get(anyString())).thenReturn(organizationIdpResource);
+
+ MockEndpoint mock = getMockEndpoint("mock:result");
+ mock.expectedMessageCount(1);
+
+
template.sendBodyAndHeaders("direct:unlinkOrganizationIdentityProvider", null,
Map.of(
+ KeycloakConstants.REALM_NAME, "testRealm",
+ KeycloakConstants.ORGANIZATION_ID, "orgId123",
+ KeycloakConstants.IDP_ALIAS, "my-idp"));
+
+ MockEndpoint.assertIsSatisfied(context);
+ }
+
+ @Test
+ public void testListOrganizationIdentityProviders() throws Exception {
+ when(keycloakClient.realm(anyString())).thenReturn(realmResource);
+ when(realmResource.organizations()).thenReturn(organizationsResource);
+
when(organizationsResource.get(anyString())).thenReturn(organizationResource);
+
when(organizationResource.identityProviders()).thenReturn(organizationIdpsResource);
+
when(organizationIdpsResource.getIdentityProviders()).thenReturn(List.of());
+
+ MockEndpoint mock = getMockEndpoint("mock:result");
+ mock.expectedMessageCount(1);
+
+
template.sendBodyAndHeaders("direct:listOrganizationIdentityProviders", null,
Map.of(
+ KeycloakConstants.REALM_NAME, "testRealm",
+ KeycloakConstants.ORGANIZATION_ID, "orgId123"));
+
+ MockEndpoint.assertIsSatisfied(context);
+ }
+
+ @Test
+ public void testMissingOrganizationName() throws Exception {
+ MockEndpoint mock = getMockEndpoint("mock:result");
+ mock.expectedMessageCount(0);
+
+ try {
+ template.sendBodyAndHeader("direct:createOrganization", null,
KeycloakConstants.REALM_NAME, "testRealm");
+ } catch (Exception e) {
+ assertTrue(e.getCause().getMessage().contains("Organization name
must be specified"));
+ }
+ }
+
+ @Test
+ public void testMissingOrganizationId() throws Exception {
+ MockEndpoint mock = getMockEndpoint("mock:result");
+ mock.expectedMessageCount(0);
+
+ try {
+ template.sendBodyAndHeader("direct:getOrganization", null,
KeycloakConstants.REALM_NAME, "testRealm");
+ } catch (Exception e) {
+ assertTrue(e.getCause().getMessage().contains("Organization ID
must be specified"));
+ }
+ }
}
diff --git
a/components/camel-keycloak/src/test/java/org/apache/camel/component/keycloak/KeycloakTestInfraIT.java
b/components/camel-keycloak/src/test/java/org/apache/camel/component/keycloak/KeycloakTestInfraIT.java
index 2af6bc84d3cc..062c062790a7 100644
---
a/components/camel-keycloak/src/test/java/org/apache/camel/component/keycloak/KeycloakTestInfraIT.java
+++
b/components/camel-keycloak/src/test/java/org/apache/camel/component/keycloak/KeycloakTestInfraIT.java
@@ -38,6 +38,9 @@ import org.junit.jupiter.api.extension.RegisterExtension;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
+import org.keycloak.representations.idm.MemberRepresentation;
+import org.keycloak.representations.idm.OrganizationRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.authorization.PolicyRepresentation;
@@ -71,6 +74,9 @@ public class KeycloakTestInfraIT extends CamelTestSupport {
private static final String TEST_IDP_ALIAS = "testinfra-idp-" +
UUID.randomUUID().toString().substring(0, 8);
private static final String TEST_RESOURCE_NAME = "testinfra-resource-" +
UUID.randomUUID().toString().substring(0, 8);
private static final String TEST_POLICY_NAME = "testinfra-policy-" +
UUID.randomUUID().toString().substring(0, 8);
+ private static final String TEST_ORG_NAME = "testinfra-org-" +
UUID.randomUUID().toString().substring(0, 8);
+ private static final String TEST_ORG_ALIAS = "testinfra-org-alias-" +
UUID.randomUUID().toString().substring(0, 8);
+ private static final String TEST_ORG_DOMAIN = TEST_ORG_ALIAS +
".example.com";
// NOTE: not yet used
// private static final String TEST_AUTHZ_CLIENT_ID =
"testinfra-authz-client-" + UUID.randomUUID().toString().substring(0, 8);
@@ -79,6 +85,7 @@ public class KeycloakTestInfraIT extends CamelTestSupport {
private static String testClientUuid;
private static String testResourceId;
private static String testPolicyId;
+ private static String testOrgId;
// NOTE: not yet used
// private static String testAuthzClientUuid;
// private static String testAuthzClientSecret;
@@ -256,6 +263,45 @@ public class KeycloakTestInfraIT extends CamelTestSupport {
// Permission evaluation operation
from("direct:evaluatePermission")
.to(keycloakEndpoint +
"?operation=evaluatePermission");
+
+ // Organization operations (Keycloak 26+)
+ // Organizations is a per-realm feature in Keycloak 26 and
must be enabled
+ // on the realm before any organization API call can succeed.
+ from("direct:updateRealm")
+ .to(keycloakEndpoint +
"?operation=updateRealm&pojoRequest=true");
+
+ from("direct:createOrganization")
+ .to(keycloakEndpoint +
"?operation=createOrganization");
+
+ from("direct:getOrganization")
+ .to(keycloakEndpoint + "?operation=getOrganization");
+
+ from("direct:listOrganizations")
+ .to(keycloakEndpoint + "?operation=listOrganizations");
+
+ from("direct:searchOrganizations")
+ .to(keycloakEndpoint +
"?operation=searchOrganizations");
+
+ from("direct:addOrganizationMember")
+ .to(keycloakEndpoint +
"?operation=addOrganizationMember");
+
+ from("direct:listOrganizationMembers")
+ .to(keycloakEndpoint +
"?operation=listOrganizationMembers");
+
+ from("direct:removeOrganizationMember")
+ .to(keycloakEndpoint +
"?operation=removeOrganizationMember");
+
+ from("direct:linkOrganizationIdentityProvider")
+ .to(keycloakEndpoint +
"?operation=linkOrganizationIdentityProvider");
+
+ from("direct:listOrganizationIdentityProviders")
+ .to(keycloakEndpoint +
"?operation=listOrganizationIdentityProviders");
+
+ from("direct:unlinkOrganizationIdentityProvider")
+ .to(keycloakEndpoint +
"?operation=unlinkOrganizationIdentityProvider");
+
+ from("direct:deleteOrganization")
+ .to(keycloakEndpoint +
"?operation=deleteOrganization");
}
};
}
@@ -1189,6 +1235,238 @@ public class KeycloakTestInfraIT extends
CamelTestSupport {
}
}
+ // Organization operation tests (Keycloak 26+)
+ // The Organizations API is a per-realm feature in Keycloak 26 — it must
be enabled
+ // on the realm via organizationsEnabled=true, otherwise the
/organizations endpoint
+ // returns 404. Run this update step before any organization API call.
+ @Test
+ @Order(49)
+ void testEnableOrganizationsOnRealm() {
+ RealmRepresentation realm = new RealmRepresentation();
+ realm.setRealm(TEST_REALM_NAME);
+ realm.setEnabled(true);
+ realm.setOrganizationsEnabled(Boolean.TRUE);
+
+ Exchange exchange = TestSupport.createExchangeWithBody(this.context,
realm);
+ exchange.getIn().setHeader(KeycloakConstants.REALM_NAME,
TEST_REALM_NAME);
+
+ Exchange result = template.send("direct:updateRealm", exchange);
+ assertNotNull(result);
+ assertNull(result.getException());
+
+ log.info("Enabled Organizations feature on realm: {}",
TEST_REALM_NAME);
+ }
+
+ @Test
+ @Order(50)
+ void testCreateOrganization() {
+ Exchange exchange = TestSupport.createExchangeWithBody(this.context,
null);
+ exchange.getIn().setHeader(KeycloakConstants.REALM_NAME,
TEST_REALM_NAME);
+ exchange.getIn().setHeader(KeycloakConstants.ORGANIZATION_NAME,
TEST_ORG_NAME);
+ exchange.getIn().setHeader(KeycloakConstants.ORGANIZATION_ALIAS,
TEST_ORG_ALIAS);
+ exchange.getIn().setHeader(KeycloakConstants.ORGANIZATION_DESCRIPTION,
+ "Test organization for test-infra demonstration");
+ exchange.getIn().setHeader(KeycloakConstants.ORGANIZATION_DOMAIN,
TEST_ORG_DOMAIN);
+
+ Exchange result = template.send("direct:createOrganization", exchange);
+ assertNotNull(result);
+ assertNull(result.getException());
+
+ Response response = result.getIn().getBody(Response.class);
+ assertNotNull(response);
+
+ String location = response.getHeaderString("Location");
+ if (location != null) {
+ testOrgId = location.substring(location.lastIndexOf('/') + 1);
+ log.info("Created organization: {} with ID: {}", TEST_ORG_NAME,
testOrgId);
+ }
+ }
+
+ @Test
+ @Order(51)
+ void testListOrganizations() {
+ Exchange exchange = TestSupport.createExchangeWithBody(this.context,
null);
+ exchange.getIn().setHeader(KeycloakConstants.REALM_NAME,
TEST_REALM_NAME);
+
+ Exchange result = template.send("direct:listOrganizations", exchange);
+ assertNotNull(result);
+ assertNull(result.getException());
+
+ @SuppressWarnings("unchecked")
+ List<OrganizationRepresentation> organizations =
result.getIn().getBody(List.class);
+ assertNotNull(organizations);
+ assertTrue(organizations.size() >= 1);
+
+ log.info("Found {} organizations in realm: {}", organizations.size(),
TEST_REALM_NAME);
+ }
+
+ @Test
+ @Order(52)
+ void testGetOrganization() {
+ assertNotNull(testOrgId, "testOrgId should be set");
+
+ Exchange exchange = TestSupport.createExchangeWithBody(this.context,
null);
+ exchange.getIn().setHeader(KeycloakConstants.REALM_NAME,
TEST_REALM_NAME);
+ exchange.getIn().setHeader(KeycloakConstants.ORGANIZATION_ID,
testOrgId);
+
+ Exchange result = template.send("direct:getOrganization", exchange);
+ assertNotNull(result);
+ assertNull(result.getException());
+
+ OrganizationRepresentation org =
result.getIn().getBody(OrganizationRepresentation.class);
+ assertNotNull(org);
+ assertEquals(TEST_ORG_NAME, org.getName());
+
+ log.info("Retrieved organization: {}", TEST_ORG_NAME);
+ }
+
+ @Test
+ @Order(53)
+ void testSearchOrganizations() {
+ Exchange exchange = TestSupport.createExchangeWithBody(this.context,
null);
+ exchange.getIn().setHeader(KeycloakConstants.REALM_NAME,
TEST_REALM_NAME);
+ exchange.getIn().setHeader(KeycloakConstants.ORGANIZATION_SEARCH,
TEST_ORG_NAME);
+
+ Exchange result = template.send("direct:searchOrganizations",
exchange);
+ assertNotNull(result);
+ assertNull(result.getException());
+
+ @SuppressWarnings("unchecked")
+ List<OrganizationRepresentation> organizations =
result.getIn().getBody(List.class);
+ assertNotNull(organizations);
+ assertTrue(organizations.size() >= 1);
+
+ log.info("Search for '{}' found {} organizations", TEST_ORG_NAME,
organizations.size());
+ }
+
+ @Test
+ @Order(54)
+ void testAddOrganizationMember() {
+ assertNotNull(testOrgId, "testOrgId should be set");
+ assertNotNull(testUserId, "testUserId should be set");
+
+ Exchange exchange = TestSupport.createExchangeWithBody(this.context,
null);
+ exchange.getIn().setHeader(KeycloakConstants.REALM_NAME,
TEST_REALM_NAME);
+ exchange.getIn().setHeader(KeycloakConstants.ORGANIZATION_ID,
testOrgId);
+ exchange.getIn().setHeader(KeycloakConstants.USER_ID, testUserId);
+
+ Exchange result = template.send("direct:addOrganizationMember",
exchange);
+ assertNotNull(result);
+ assertNull(result.getException());
+
+ log.info("Added user {} to organization {}", testUserId, testOrgId);
+ }
+
+ @Test
+ @Order(55)
+ void testListOrganizationMembers() {
+ assertNotNull(testOrgId, "testOrgId should be set");
+
+ Exchange exchange = TestSupport.createExchangeWithBody(this.context,
null);
+ exchange.getIn().setHeader(KeycloakConstants.REALM_NAME,
TEST_REALM_NAME);
+ exchange.getIn().setHeader(KeycloakConstants.ORGANIZATION_ID,
testOrgId);
+
+ Exchange result = template.send("direct:listOrganizationMembers",
exchange);
+ assertNotNull(result);
+ assertNull(result.getException());
+
+ @SuppressWarnings("unchecked")
+ List<MemberRepresentation> members =
result.getIn().getBody(List.class);
+ assertNotNull(members);
+
+ log.info("Found {} members in organization: {}", members.size(),
testOrgId);
+ }
+
+ @Test
+ @Order(56)
+ void testRemoveOrganizationMember() {
+ assertNotNull(testOrgId, "testOrgId should be set");
+ assertNotNull(testUserId, "testUserId should be set");
+
+ Exchange exchange = TestSupport.createExchangeWithBody(this.context,
null);
+ exchange.getIn().setHeader(KeycloakConstants.REALM_NAME,
TEST_REALM_NAME);
+ exchange.getIn().setHeader(KeycloakConstants.ORGANIZATION_ID,
testOrgId);
+ exchange.getIn().setHeader(KeycloakConstants.USER_ID, testUserId);
+
+ Exchange result = template.send("direct:removeOrganizationMember",
exchange);
+ assertNotNull(result);
+ assertNull(result.getException());
+
+ String body = result.getIn().getBody(String.class);
+ assertEquals("Organization member removed successfully", body);
+
+ log.info("Removed user {} from organization {}", testUserId,
testOrgId);
+ }
+
+ @Test
+ @Order(57)
+ void testLinkOrganizationIdentityProvider() {
+ assertNotNull(testOrgId, "testOrgId should be set");
+
+ Exchange exchange = TestSupport.createExchangeWithBody(this.context,
null);
+ exchange.getIn().setHeader(KeycloakConstants.REALM_NAME,
TEST_REALM_NAME);
+ exchange.getIn().setHeader(KeycloakConstants.ORGANIZATION_ID,
testOrgId);
+ exchange.getIn().setHeader(KeycloakConstants.IDP_ALIAS,
TEST_IDP_ALIAS);
+
+ try {
+ Exchange result =
template.send("direct:linkOrganizationIdentityProvider", exchange);
+ if (result.getException() == null) {
+ log.info("Linked identity provider {} to organization {}",
TEST_IDP_ALIAS, testOrgId);
+ } else {
+ log.warn("Link organization identity provider failed: {}",
result.getException().getMessage());
+ }
+ } catch (Exception e) {
+ log.warn("Skipping link organization identity provider test: {}",
e.getMessage());
+ }
+ }
+
+ @Test
+ @Order(58)
+ void testListOrganizationIdentityProviders() {
+ assertNotNull(testOrgId, "testOrgId should be set");
+
+ Exchange exchange = TestSupport.createExchangeWithBody(this.context,
null);
+ exchange.getIn().setHeader(KeycloakConstants.REALM_NAME,
TEST_REALM_NAME);
+ exchange.getIn().setHeader(KeycloakConstants.ORGANIZATION_ID,
testOrgId);
+
+ Exchange result =
template.send("direct:listOrganizationIdentityProviders", exchange);
+ assertNotNull(result);
+ assertNull(result.getException());
+
+ @SuppressWarnings("unchecked")
+ List<IdentityProviderRepresentation> idps =
result.getIn().getBody(List.class);
+ assertNotNull(idps);
+
+ log.info("Found {} identity providers linked to organization: {}",
idps.size(), testOrgId);
+ }
+
+ @Test
+ @Order(59)
+ void testUnlinkOrganizationIdentityProvider() {
+ if (testOrgId == null) {
+ log.info("Skipping testUnlinkOrganizationIdentityProvider -
testOrgId not set");
+ return;
+ }
+
+ Exchange exchange = TestSupport.createExchangeWithBody(this.context,
null);
+ exchange.getIn().setHeader(KeycloakConstants.REALM_NAME,
TEST_REALM_NAME);
+ exchange.getIn().setHeader(KeycloakConstants.ORGANIZATION_ID,
testOrgId);
+ exchange.getIn().setHeader(KeycloakConstants.IDP_ALIAS,
TEST_IDP_ALIAS);
+
+ try {
+ Exchange result =
template.send("direct:unlinkOrganizationIdentityProvider", exchange);
+ if (result.getException() == null) {
+ String body = result.getIn().getBody(String.class);
+ assertEquals("Organization identity provider unlinked
successfully", body);
+ log.info("Unlinked identity provider {} from organization {}",
TEST_IDP_ALIAS, testOrgId);
+ } else {
+ log.warn("Unlink organization identity provider failed: {}",
result.getException().getMessage());
+ }
+ } catch (Exception e) {
+ log.warn("Skipping unlink organization identity provider test:
{}", e.getMessage());
+ }
+ }
+
@Test
@Order(90)
void testCleanupAuthorizationResources() {
@@ -1222,6 +1500,27 @@ public class KeycloakTestInfraIT extends
CamelTestSupport {
}
}
+ @Test
+ @Order(94)
+ void testCleanupOrganization() {
+ if (testOrgId != null) {
+ try {
+ Exchange exchange =
TestSupport.createExchangeWithBody(this.context, null);
+ exchange.getIn().setHeader(KeycloakConstants.REALM_NAME,
TEST_REALM_NAME);
+ exchange.getIn().setHeader(KeycloakConstants.ORGANIZATION_ID,
testOrgId);
+
+ Exchange result = template.send("direct:deleteOrganization",
exchange);
+ if (result.getException() == null) {
+ String body = result.getIn().getBody(String.class);
+ assertEquals("Organization deleted successfully", body);
+ log.info("Deleted organization: {}", TEST_ORG_NAME);
+ }
+ } catch (Exception e) {
+ log.warn("Failed to delete organization {}: {}",
TEST_ORG_NAME, e.getMessage());
+ }
+ }
+ }
+
@Test
@Order(95)
void testCleanupIdentityProvider() {
diff --git
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/KeycloakEndpointBuilderFactory.java
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/KeycloakEndpointBuilderFactory.java
index 9e2de9e824aa..025c71d27aaf 100644
---
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/KeycloakEndpointBuilderFactory.java
+++
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/KeycloakEndpointBuilderFactory.java
@@ -3189,6 +3189,92 @@ public interface KeycloakEndpointBuilderFactory {
public String keycloakPermissionsOnly() {
return "CamelKeycloakPermissionsOnly";
}
+ /**
+ * The organization ID.
+ *
+ * The option is a: {@code String} type.
+ *
+ * Group: common
+ *
+ * @return the name of the header {@code KeycloakOrganizationId}.
+ */
+ public String keycloakOrganizationId() {
+ return "CamelKeycloakOrganizationId";
+ }
+ /**
+ * The organization name.
+ *
+ * The option is a: {@code String} type.
+ *
+ * Group: common
+ *
+ * @return the name of the header {@code KeycloakOrganizationName}.
+ */
+ public String keycloakOrganizationName() {
+ return "CamelKeycloakOrganizationName";
+ }
+ /**
+ * The organization alias.
+ *
+ * The option is a: {@code String} type.
+ *
+ * Group: common
+ *
+ * @return the name of the header {@code KeycloakOrganizationAlias}.
+ */
+ public String keycloakOrganizationAlias() {
+ return "CamelKeycloakOrganizationAlias";
+ }
+ /**
+ * The organization description.
+ *
+ * The option is a: {@code String} type.
+ *
+ * Group: common
+ *
+ * @return the name of the header {@code
+ * KeycloakOrganizationDescription}.
+ */
+ public String keycloakOrganizationDescription() {
+ return "CamelKeycloakOrganizationDescription";
+ }
+ /**
+ * The organization redirect URL.
+ *
+ * The option is a: {@code String} type.
+ *
+ * Group: common
+ *
+ * @return the name of the header {@code
+ * KeycloakOrganizationRedirectUrl}.
+ */
+ public String keycloakOrganizationRedirectUrl() {
+ return "CamelKeycloakOrganizationRedirectUrl";
+ }
+ /**
+ * The organization domain name.
+ *
+ * The option is a: {@code String} type.
+ *
+ * Group: common
+ *
+ * @return the name of the header {@code KeycloakOrganizationDomain}.
+ */
+ public String keycloakOrganizationDomain() {
+ return "CamelKeycloakOrganizationDomain";
+ }
+ /**
+ * Search query for organizations.
+ *
+ * The option is a: {@code String} type.
+ *
+ * Group: common
+ *
+ * @return the name of the header {@code KeycloakOrganizationSearch}.
+ */
+ public String keycloakOrganizationSearch() {
+ return "CamelKeycloakOrganizationSearch";
+ }
}
static KeycloakEndpointBuilder endpointBuilder(String componentName,
String path) {
class KeycloakEndpointBuilderImpl extends AbstractEndpointBuilder
implements KeycloakEndpointBuilder, AdvancedKeycloakEndpointBuilder {