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-jbang-examples.git


The following commit(s) were added to refs/heads/main by this push:
     new 11270ca  LDAP Migration to Keycloak with camel-keycloak bulk (#50)
11270ca is described below

commit 11270cafea65ef357f386debfb4a533824c61207
Author: Andrea Cosentino <[email protected]>
AuthorDate: Wed Nov 12 12:06:11 2025 +0100

    LDAP Migration to Keycloak with camel-keycloak bulk (#50)
    
    Signed-off-by: Andrea Cosentino <[email protected]>
---
 keycloak-ldap-migration/README.adoc               | 764 ++++++++++++++++++++++
 keycloak-ldap-migration/application.properties    |  77 +++
 keycloak-ldap-migration/ldap-migration.camel.yaml | 235 +++++++
 keycloak-ldap-migration/users.ldif                |  63 ++
 4 files changed, 1139 insertions(+)

diff --git a/keycloak-ldap-migration/README.adoc 
b/keycloak-ldap-migration/README.adoc
new file mode 100644
index 0000000..a025b6b
--- /dev/null
+++ b/keycloak-ldap-migration/README.adoc
@@ -0,0 +1,764 @@
+= LDAP to Keycloak User Migration
+
+This example demonstrates how to migrate users from an LDAP directory to 
Keycloak using Apache Camel.
+It leverages the `camel-ldap` component to read users from LDAP and the 
`camel-keycloak` component's bulk operations to efficiently import users into 
Keycloak.
+
+== Features
+
+* Reads users from LDAP using customizable search filters
+* Transforms LDAP user attributes to Keycloak UserRepresentation
+* Uses Keycloak bulk operations for efficient mass user creation
+* Supports LDAP authentication (anonymous, simple, DIGEST-MD5)
+* Maps common LDAP attributes (uid, cn, mail, givenName, sn, ou, 
telephoneNumber)
+* Preserves LDAP metadata as Keycloak user attributes
+* Configurable password update requirements
+* Detailed migration results with success/failure reporting
+* Error handling with continue-on-error support
+
+== Use Cases
+
+This example is useful for:
+
+* **Initial Migration**: Moving users from legacy LDAP to Keycloak
+* **Consolidation**: Merging users from multiple LDAP directories into Keycloak
+* **Modernization**: Replacing LDAP authentication with Keycloak's modern 
identity management
+* **Hybrid Environments**: Syncing LDAP users to Keycloak while maintaining 
LDAP for other systems
+
+== Prerequisites
+
+* JBang installed (https://www.jbang.dev)
+* Access to an LDAP server with users to migrate
+* Keycloak server (can be started with Camel JBang infra)
+* Basic understanding of LDAP and Keycloak concepts
+
+== Dependencies
+
+This example requires:
+
+* `camel-ldap` - For LDAP integration
+* `camel-keycloak` - For Keycloak integration
+
+== Install JBang
+
+First install JBang according to https://www.jbang.dev
+
+When JBang is installed then you should be able to run from a shell:
+
+[source,sh]
+----
+$ jbang --version
+----
+
+This will output the version of JBang.
+
+To run this example you can either install Camel on JBang via:
+
+[source,sh]
+----
+$ jbang app install camel@apache/camel
+----
+
+Which allows to run Camel JBang with `camel` as shown below.
+
+== Setting Up LDAP Server
+
+For this example, we'll use OpenLDAP running in Docker:
+
+[source,sh]
+----
+$ docker run -d \
+  --name openldap \
+  -p 389:389 \
+  -p 636:636 \
+  -e LDAP_ORGANISATION="Example Inc" \
+  -e LDAP_DOMAIN="example.org" \
+  -e LDAP_ADMIN_PASSWORD="admin" \
+  osixia/openldap:latest
+----
+
+This will start OpenLDAP with:
+* Port 389: LDAP
+* Port 636: LDAPS (secure LDAP)
+* Admin DN: `cn=admin,dc=example,dc=org`
+* Admin password: `admin`
+* Base DN: `dc=example,dc=org` (automatically created)
+
+=== Adding Test Users to LDAP
+
+You can use LDAP tools like `ldapadd` or Apache Directory Studio to add test 
users.
+
+IMPORTANT: Before adding users, you need to create the organizational unit 
structure in LDAP. The default configuration expects 
`ou=users,dc=example,dc=org` to exist.
+
+Create a file named `users.ldif` with the following content to set up the 
structure and add test users:
+
+[source,ldif]
+----
+dn: ou=users,dc=example,dc=org
+objectClass: organizationalUnit
+ou: users
+
+dn: uid=jdoe,ou=users,dc=example,dc=org
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+uid: jdoe
+cn: John Doe
+sn: Doe
+givenName: John
+mail: [email protected]
+telephoneNumber: +1-555-1234
+userPassword: password123
+
+dn: uid=jsmith,ou=users,dc=example,dc=org
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+uid: jsmith
+cn: Jane Smith
+sn: Smith
+givenName: Jane
+mail: [email protected]
+telephoneNumber: +1-555-5678
+userPassword: password456
+
+dn: uid=admin,ou=users,dc=example,dc=org
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+uid: admin
+cn: Admin User
+sn: User
+givenName: Admin
+mail: [email protected]
+ou: IT
+description: System Administrator
+userPassword: adminpass
+----
+
+To add the organizational unit and users to your LDAP server:
+
+[source,sh]
+----
+$ ldapadd -x -H ldap://localhost:389 -D "cn=admin,dc=example,dc=org" -w admin 
-f users.ldif
+----
+
+Expected output:
+[source]
+----
+adding new entry "ou=users,dc=example,dc=org"
+adding new entry "uid=jdoe,ou=users,dc=example,dc=org"
+adding new entry "uid=jsmith,ou=users,dc=example,dc=org"
+adding new entry "uid=admin,ou=users,dc=example,dc=org"
+----
+
+NOTE: If you're using the `osixia/openldap` Docker container, the base DN 
`dc=example,dc=org` is automatically created. You only need to add the 
`ou=users` organizational unit and the user entries.
+
+== Setting Up Keycloak
+
+Use Camel JBang Infra to run Keycloak:
+
+[source,sh]
+----
+$ jbang -Dcamel.jbang.version=4.16.0 camel@apache/camel infra run keycloak
+----
+
+This will start Keycloak configured with:
+* Admin username: `admin`
+* Admin password: `admin`
+* Port: `8080`
+
+Wait a few seconds for Keycloak to fully start before proceeding.
+
+To stop Keycloak later:
+
+[source,sh]
+----
+$ jbang -Dcamel.jbang.version=4.16.0 camel@apache/camel infra stop keycloak
+----
+
+== Configuring the Migration
+
+Edit the `application.properties` file to configure your LDAP and Keycloak 
settings:
+
+=== LDAP Configuration
+
+[source,properties]
+----
+# LDAP server URL
+ldap.server.url=ldap://localhost:389
+
+# Authentication (none, simple, or DIGEST-MD5)
+ldap.security.authentication=simple
+ldap.security.principal=cn=admin,dc=example,dc=org
+ldap.security.credentials=admin
+
+# Search configuration
+ldap.search.base=ou=users,dc=example,dc=org
+ldap.search.filter=(objectClass=person)
+----
+
+=== Keycloak Configuration
+
+[source,properties]
+----
+# Keycloak server URL
+keycloak.server.url=http://localhost:8080
+
+# Authentication
+keycloak.realm=master
+keycloak.username=admin
+keycloak.password=admin
+
+# Target realm for migrated users
+keycloak.target.realm=master
+----
+
+=== Migration Options
+
+[source,properties]
+----
+# Require password update on first login
+keycloak.user.requirePasswordUpdate=true
+----
+
+== Running the Migration
+
+After configuring LDAP and Keycloak, and adding test users to LDAP, run the 
migration:
+
+[source,sh]
+----
+$ jbang -Dcamel.jbang.version=4.16.0 camel@apache/camel run *
+----
+
+IMPORTANT: Make sure you have created the LDAP organizational unit structure 
and added test users (see "Adding Test Users to LDAP" section above). If you 
see an error like `NameNotFoundException: No Such Object`, it means the LDAP 
base DN doesn't exist yet.
+
+The migration will:
+
+1. Initialize LDAP connection
+2. Connect to the LDAP server
+3. Search for users based on the configured filter
+4. Transform LDAP attributes to Keycloak user format
+5. Bulk create users in Keycloak
+6. Display detailed migration results
+
+== Expected Output
+
+[source]
+----
+[INFO] Starting LDAP to Keycloak user migration...
+[INFO] Found 3 users in LDAP
+
+================================================================================
+LDAP to Keycloak Migration Results
+================================================================================
+Total users processed: 3
+Successfully created:  3
+Failed:                0
+================================================================================
+
+Detailed Results:
+--------------------------------------------------------------------------------
+✓ jdoe                           - success
+✓ jsmith                         - success
+✓ admin                          - success
+--------------------------------------------------------------------------------
+
+Migration completed!
+----
+
+== LDAP Attribute Mapping
+
+The migration maps LDAP attributes to Keycloak user fields:
+
+[cols="1,1,3",options="header"]
+|===
+|LDAP Attribute
+|Keycloak Field
+|Notes
+
+|`uid`, `cn`, `sAMAccountName`
+|`username`
+|First available attribute is used (required)
+
+|`mail`
+|`email`
+|User's email address
+
+|`givenName`
+|`firstName`
+|User's first name
+
+|`sn`
+|`lastName`
+|User's surname/last name
+
+|`ou`
+|`attributes.ldap_ou`
+|Organizational unit (custom attribute)
+
+|`dn`
+|`attributes.ldap_dn`
+|Distinguished name (custom attribute, for reference)
+
+|`description`
+|`attributes.description`
+|User description (custom attribute)
+
+|`telephoneNumber`
+|`attributes.phoneNumber`
+|Phone number (custom attribute)
+|===
+
+NOTE: All users are enabled by default (`enabled: true`).
+
+== Advanced Configuration
+
+=== Custom LDAP Search Filters
+
+You can customize the LDAP search filter to target specific users:
+
+[source,properties]
+----
+# Search only for active persons with email
+ldap.search.filter=(&(objectClass=person)(mail=*)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))
+
+# Search for users in specific organizational unit
+ldap.search.filter=(&(objectClass=inetOrgPerson)(ou=Engineering))
+
+# Search for users created after a specific date (if supported)
+ldap.search.filter=(&(objectClass=person)(createTimestamp>=20240101000000Z))
+----
+
+=== Anonymous LDAP Authentication
+
+If your LDAP server allows anonymous access:
+
+[source,properties]
+----
+ldap.security.authentication=none
+# No need to specify principal and credentials
+----
+
+=== Multiple LDAP Servers
+
+To migrate from multiple LDAP servers, you can:
+
+1. Run the migration multiple times with different configurations
+2. Modify the route to iterate over multiple LDAP servers
+3. Create separate routes for each LDAP server
+
+=== Migrating to Different Realms
+
+To migrate users to a specific realm (not master):
+
+1. Create the realm in Keycloak first:
+   - Go to Keycloak Admin Console
+   - Click "Create Realm"
+   - Enter realm name (e.g., "company")
+   - Click "Create"
+
+2. Update `application.properties`:
++
+[source,properties]
+----
+keycloak.target.realm=company
+----
+
+=== Handling Large User Bases
+
+For large LDAP directories (thousands of users):
+
+1. **Batch Processing**: Modify the route to process users in batches
+2. **Pagination**: Use LDAP pagination for very large result sets
+3. **Scheduling**: Run the migration during off-peak hours
+4. **Incremental Sync**: Add logic to skip already migrated users
+
+Example for batch processing:
+
+[source,yaml]
+----
+- split:
+    simple: "${body}"
+    streaming: true
+    parallelProcessing: true
+    steps:
+      - aggregate:
+          simple: "batch"
+          completionSize: 100
+          steps:
+            - to: "keycloak:admin?operation=bulkCreateUsers"
+----
+
+== Error Handling
+
+The migration is configured with `continueOnError: true`, which means:
+
+* If a user fails to create, the migration continues with the next user
+* All errors are captured in the results
+* The final report shows which users succeeded and which failed
+
+Common errors and solutions:
+
+[cols="1,2",options="header"]
+|===
+|Error
+|Solution
+
+|"User already exists"
+|User with same username already in Keycloak. Either delete existing user or 
modify LDAP filter to exclude already migrated users.
+
+|"Invalid email format"
+|LDAP `mail` attribute contains invalid email. Clean up LDAP data or add email 
validation in the transformer.
+
+|"Missing required field"
+|Username is required but not found in LDAP attributes. Ensure users have 
`uid`, `cn`, or `sAMAccountName` attribute.
+
+|"Connection refused"
+|Cannot connect to LDAP or Keycloak. Verify server URLs and network 
connectivity.
+
+|"Authentication failed"
+|LDAP credentials are incorrect. Verify `ldap.security.principal` and 
`ldap.security.credentials`.
+|===
+
+== Security Considerations
+
+IMPORTANT: This migration does NOT copy user passwords from LDAP to Keycloak.
+
+=== Password Handling
+
+LDAP passwords are typically hashed and cannot be directly transferred. 
Options:
+
+1. **Require Password Update** (Default):
+   - Set `keycloak.user.requirePasswordUpdate=true`
+   - Users must set a new password on first login
+   - Most secure option
+
+2. **Set Temporary Passwords**:
+   - Modify the transformer to set a temporary password for each user
+   - Send password reset emails to users
+   - Example code:
++
+[source,groovy]
+----
+user.credentials = [[
+  type: "password",
+  value: UUID.randomUUID().toString(),
+  temporary: true
+]]
+----
+
+3. **LDAP Federation in Keycloak**:
+   - Instead of migration, configure LDAP as a user federation in Keycloak
+   - Passwords remain in LDAP
+   - Keycloak authenticates against LDAP
+   - See: https://www.keycloak.org/docs/latest/server_admin/#_ldap[Keycloak 
LDAP Documentation]
+
+=== Sensitive Data
+
+* Store LDAP credentials securely (use environment variables or secrets 
management)
+* Use LDAPS (LDAP over SSL/TLS) for production environments
+* Review custom attributes to ensure no sensitive data is migrated 
inappropriately
+* Consider GDPR and data protection requirements
+
+== Customizing the Migration
+
+=== Adding Custom Attributes
+
+To map additional LDAP attributes, modify the transformer bean in 
`ldap-migration.camel.yaml`:
+
+[source,groovy]
+----
+// Employee ID
+def employeeId = attrs.get("employeeNumber")?.get()
+if (employeeId) {
+  customAttrs.put("employeeId", [employeeId.toString()])
+}
+
+// Department
+def department = attrs.get("departmentNumber")?.get()
+if (department) {
+  customAttrs.put("department", [department.toString()])
+}
+----
+
+=== Assigning Roles During Migration
+
+To assign default roles to migrated users, add after user creation:
+
+[source,yaml]
+----
+# After bulk create users
+- split:
+    simple: "${body.results}"
+    steps:
+      - filter:
+          simple: "${body.status} == 'success'"
+      - setHeader:
+          name: CamelKeycloakRealmName
+          constant: "{{keycloak.target.realm}}"
+      - setHeader:
+          name: CamelKeycloakUsername
+          simple: "${body.username}"
+      - setHeader:
+          name: CamelKeycloakRoleName
+          constant: "default-user-role"
+      - to:
+          uri: "keycloak:admin?operation=assignRoleToUser"
+----
+
+=== Group Assignment
+
+To add users to groups based on LDAP organizational unit:
+
+[source,groovy]
+----
+// In the transformer
+def ou = attrs.get("ou")?.get()
+if (ou) {
+  // Store OU for later group assignment
+  user.groups = [ou.toString()]
+}
+----
+
+Then add a separate route to handle group assignment.
+
+== Validating the Migration
+
+After migration, verify users in Keycloak:
+
+1. **Via Keycloak Admin Console**:
+   - Navigate to http://localhost:8080
+   - Login with admin/admin
+   - Go to Users
+   - Verify migrated users appear
+
+2. **Via REST API**:
++
+[source,sh]
+----
+# Get admin access token
+TOKEN=$(curl -X POST 
http://localhost:8080/realms/master/protocol/openid-connect/token \
+  -H "Content-Type: application/x-www-form-urlencoded" \
+  -d "username=admin" \
+  -d "password=admin" \
+  -d "grant_type=password" \
+  -d "client_id=admin-cli" \
+  | jq -r '.access_token')
+
+# List all users
+curl http://localhost:8080/admin/realms/master/users \
+  -H "Authorization: Bearer $TOKEN" | jq
+----
+
+3. **Test User Login**:
++
+Users should be able to login to Keycloak with their credentials (after 
setting password if required).
+
+== Troubleshooting
+
+=== LDAP Connection Issues
+
+[source,sh]
+----
+# Test LDAP connectivity
+ldapsearch -x -H ldap://localhost:389 \
+  -D "cn=admin,dc=example,dc=org" \
+  -w admin \
+  -b "ou=users,dc=example,dc=org" \
+  "(objectClass=person)"
+----
+
+=== Enable Debug Logging
+
+Uncomment in `application.properties`:
+
+[source,properties]
+----
+logging.level.org.apache.camel=DEBUG
+logging.level.org.apache.camel.component.keycloak=DEBUG
+logging.level.org.apache.camel.component.ldap=DEBUG
+----
+
+=== Common Issues
+
+**"No users found in LDAP"** or **"NameNotFoundException: No Such Object"**:
+- This error means the LDAP base DN doesn't exist in your LDAP server
+- Verify `ldap.search.base` points to correct organizational unit
+- Create the organizational unit structure (see "Adding Test Users to LDAP" 
section)
+- Use `ldapsearch` to verify the base DN exists:
++
+[source,sh]
+----
+ldapsearch -x -H ldap://localhost:389 -D "cn=admin,dc=example,dc=org" -w admin 
-b "dc=example,dc=org" "(objectClass=*)"
+----
+- Check `ldap.search.filter` matches your LDAP schema
+- Ensure LDAP connection is successful
+
+**"LDAP authentication failed"**:
+- Verify bind DN in `ldap.security.principal`
+- Check password in `ldap.security.credentials`
+- Try with `ldap.security.authentication=none` if server allows
+
+**"Keycloak bulk operation failed"**:
+- Verify Keycloak is running and accessible
+- Check admin credentials
+- Ensure target realm exists
+
+**"Groovy script errors"**:
+- Ensure JBang can access Groovy runtime
+- Check for syntax errors in transformer script
+- Review logs for detailed error messages
+
+== Developer Console
+
+You can enable the developer console via `--console` flag:
+
+[source,sh]
+----
+$ camel run * --console
+----
+
+Then browse: http://localhost:8080/q/dev to introspect the running Camel 
application.
+
+== Stopping
+
+To stop the Camel application, press `Ctrl+C`.
+
+To stop Keycloak:
+
+[source,sh]
+----
+$ jbang -Dcamel.jbang.version=4.16.0 camel@apache/camel infra stop keycloak
+----
+
+To stop OpenLDAP:
+
+[source,sh]
+----
+$ docker stop openldap
+$ docker rm openldap
+----
+
+== Architecture
+
+This example demonstrates:
+
+1. **LDAP Integration**: Using `camel-ldap` to query LDAP directories
+2. **Dynamic Bean Creation**: LDAP DirContext is created at runtime with 
resolved properties
+3. **Keycloak Bulk Operations**: Using `bulkCreateUsers` for efficient mass 
user creation
+4. **Data Transformation**: Converting LDAP SearchResults to Keycloak 
UserRepresentation
+5. **Error Handling**: Continue-on-error pattern for resilient migration
+6. **Result Reporting**: Detailed success/failure tracking
+
+== How It Works
+
+The migration process works in two phases:
+
+=== Phase 1: LDAP Context Initialization
+
+A startup route (`ldap-context-initializer`) runs immediately and:
+1. Resolves the LDAP configuration properties
+2. Creates a JNDI `InitialDirContext` with the LDAP server connection
+3. Binds the context to the Camel registry as `ldapserver`
+
+This ensures the LDAP connection is established before migration starts.
+
+=== Phase 2: User Migration
+
+The main migration route (`ldap-to-keycloak-migration`) then:
+1. Waits 1 second to ensure LDAP context is initialized
+2. Searches LDAP using the configured filter and base DN
+3. Transforms each LDAP SearchResult to Keycloak UserRepresentation
+4. Bulk creates all users in Keycloak
+5. Reports detailed results
+
+== Migration Flow
+
+[source]
+----
+        ┌──────────────────────────────────────────┐
+        │ Phase 1: LDAP Context Initialization    │
+        └──────────────────┬───────────────────────┘
+                           │
+                           ▼
+                  ┌─────────────────┐
+                  │ Resolve Props   │
+                  │ Create Context  │
+                  │ Bind to Registry│
+                  └────────┬────────┘
+                           │
+        ┌──────────────────┴───────────────────────┐
+        │ Phase 2: User Migration                  │
+        └──────────────────┬───────────────────────┘
+                           │
+                           ▼
+                  ┌─────────────┐
+                  │ LDAP Server │
+                  └──────┬──────┘
+                         │ 1. Search Users
+                         │ (ldap:ldapserver)
+                         ▼
+                  ┌──────────────────────┐
+                  │ Camel Route          │
+                  │ - Search LDAP        │
+                  │ - Transform Users    │
+                  │ - Bulk Create        │
+                  └──────┬───────────────┘
+                         │ 2. Bulk Create
+                         │ (keycloak:admin?operation=bulkCreateUsers)
+                         ▼
+                  ┌─────────────────┐
+                  │ Keycloak Server │
+                  └─────────────────┘
+                         │
+                         │ 3. Migration Results
+                         ▼
+                  ┌──────────────────┐
+                  │ Console Output   │
+                  │ - Success Count  │
+                  │ - Failure Count  │
+                  │ - User Details   │
+                  └──────────────────┘
+----
+
+== Next Steps
+
+After successful migration:
+
+* Configure user federation for ongoing LDAP synchronization
+* Set up identity brokering for social logins
+* Configure multi-factor authentication (MFA)
+* Implement role-based access control (RBAC)
+* Set up client applications to use Keycloak
+* Configure email server for password reset notifications
+* Implement custom themes for login pages
+* Set up backup and disaster recovery
+
+== Best Practices
+
+1. **Test First**: Run migration on a test Keycloak instance before production
+2. **Backup**: Backup both LDAP and Keycloak before migration
+3. **Dry Run**: Create a "dry run" mode that doesn't create users, just logs 
what would happen
+4. **Incremental**: For large directories, migrate in batches or phases
+5. **Validation**: Verify a sample of users after migration
+6. **Communication**: Notify users about the migration and password reset 
requirements
+7. **Documentation**: Document custom attribute mappings for future reference
+8. **Monitoring**: Monitor Keycloak performance during and after migration
+
+== Learn More
+
+* https://camel.apache.org/components/latest/ldap-component.html[Camel LDAP 
Component]
+* https://camel.apache.org/components/latest/keycloak-component.html[Camel 
Keycloak Component]
+* https://www.keycloak.org/docs/latest/server_admin/[Keycloak Server 
Administration Guide]
+* https://ldap.com/[LDAP.com - LDAP Reference]
+* https://directory.apache.org/[Apache Directory Project]
+
+== Help and Contributions
+
+If you hit any problem using Camel or have some feedback, then please
+https://camel.apache.org/community/support/[let us know].
+
+We also love contributors, so
+https://camel.apache.org/community/contributing/[get involved] :-)
+
+The Camel riders!
diff --git a/keycloak-ldap-migration/application.properties 
b/keycloak-ldap-migration/application.properties
new file mode 100644
index 0000000..b20ae8c
--- /dev/null
+++ b/keycloak-ldap-migration/application.properties
@@ -0,0 +1,77 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# ===================================================================
+# LDAP Server Configuration
+# ===================================================================
+# LDAP server URL
+ldap.server.url=ldap://localhost:389
+
+# LDAP authentication type: none, simple, or DIGEST-MD5
+ldap.security.authentication=simple
+
+# LDAP bind DN (required if authentication is not 'none')
+ldap.security.principal=cn=admin,dc=example,dc=org
+
+# LDAP bind password (required if authentication is not 'none')
+ldap.security.credentials=admin
+
+# LDAP search base - where to search for users
+ldap.search.base=ou=users,dc=example,dc=org
+
+# LDAP search filter - filter to find users
+# Common filters:
+#   (objectClass=person)           - All persons
+#   (objectClass=inetOrgPerson)    - All inetOrgPersons
+#   (uid=*)                        - All entries with uid attribute
+#   (&(objectClass=person)(mail=*)) - Persons with email
+ldap.search.filter=(objectClass=person)
+
+# ===================================================================
+# Keycloak Server Configuration
+# ===================================================================
+# Keycloak server URL (running via Camel JBang Infra)
+keycloak.server.url=http://localhost:8080
+
+# Keycloak authentication realm
+keycloak.realm=master
+
+# Keycloak admin username
+keycloak.username=admin
+
+# Keycloak admin password
+keycloak.password=admin
+
+# Target realm for user migration
+keycloak.target.realm=master
+
+# ===================================================================
+# Migration Options
+# ===================================================================
+# Require users to update password on first login
+keycloak.user.requirePasswordUpdate=true
+
+# ===================================================================
+# Camel Configuration
+# ===================================================================
+camel.main.name=LdapToKeycloakMigration
+
+# Log level for debugging
+# Uncomment to enable detailed logging
+# logging.level.org.apache.camel=DEBUG
+# logging.level.org.apache.camel.component.keycloak=DEBUG
+# logging.level.org.apache.camel.component.ldap=DEBUG
diff --git a/keycloak-ldap-migration/ldap-migration.camel.yaml 
b/keycloak-ldap-migration/ldap-migration.camel.yaml
new file mode 100644
index 0000000..5481fb3
--- /dev/null
+++ b/keycloak-ldap-migration/ldap-migration.camel.yaml
@@ -0,0 +1,235 @@
+## ---------------------------------------------------------------------------
+## Licensed to the Apache Software Foundation (ASF) under one or more
+## contributor license agreements.  See the NOTICE file distributed with
+## this work for additional information regarding copyright ownership.
+## The ASF licenses this file to You under the Apache License, Version 2.0
+## (the "License"); you may not use this file except in compliance with
+## the License.  You may obtain a copy of the License at
+##
+##      http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS,
+## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+## ---------------------------------------------------------------------------
+
+# camel-k: dependency=camel:keycloak
+# camel-k: dependency=camel:ldap
+
+# Configure Keycloak component
+- beans:
+  - name: keycloak
+    type: org.apache.camel.component.keycloak.KeycloakComponent
+    properties:
+      serverUrl: "{{keycloak.server.url}}"
+      realm: "{{keycloak.realm}}"
+      username: "{{keycloak.username}}"
+      password: "{{keycloak.password}}"
+
+# Startup route to initialize LDAP DirContext
+- route:
+    id: ldap-context-initializer
+    from:
+      uri: "timer:init?repeatCount=1&delay=100"
+      steps:
+        - log:
+            message: "Initializing LDAP context..."
+        - script:
+            groovy: |
+              import javax.naming.*
+              import javax.naming.directory.*
+
+              try {
+                def env = new Hashtable()
+                env.put(Context.INITIAL_CONTEXT_FACTORY, 
"com.sun.jndi.ldap.LdapCtxFactory")
+                env.put(Context.PROVIDER_URL, 
camelContext.resolvePropertyPlaceholders("{{ldap.server.url}}"))
+                env.put(Context.SECURITY_AUTHENTICATION, 
camelContext.resolvePropertyPlaceholders("{{ldap.security.authentication}}"))
+
+                // Add credentials if not using anonymous authentication
+                def authType = 
camelContext.resolvePropertyPlaceholders("{{ldap.security.authentication}}")
+                if (!"none".equals(authType)) {
+                  env.put(Context.SECURITY_PRINCIPAL, 
camelContext.resolvePropertyPlaceholders("{{ldap.security.principal}}"))
+                  env.put(Context.SECURITY_CREDENTIALS, 
camelContext.resolvePropertyPlaceholders("{{ldap.security.credentials}}"))
+                }
+
+                log.info("Creating LDAP context with URL: 
${env.get(Context.PROVIDER_URL)}")
+                def ldapContext = new InitialDirContext(env)
+
+                // Bind to registry
+                camelContext.registry.bind("ldapserver", ldapContext)
+                log.info("LDAP context successfully bound to registry as 
'ldapserver'")
+
+                // Verify it was bound
+                def boundContext = 
camelContext.registry.lookupByName("ldapserver")
+                if (boundContext != null) {
+                  log.info("Verified: LDAP context is available in registry")
+                } else {
+                  log.error("Error: LDAP context not found in registry after 
binding!")
+                }
+              } catch (Exception e) {
+                log.error("Failed to initialize LDAP context: ${e.message}", e)
+                throw e
+              }
+
+# Main route for LDAP to Keycloak migration
+- route:
+    id: ldap-to-keycloak-migration
+    from:
+      uri: "timer:migrate?repeatCount=1&delay=2000"
+      steps:
+        - log:
+            message: "Checking if LDAP context is available..."
+        - script:
+            groovy: |
+              def ldapCtx = camelContext.registry.lookupByName("ldapserver")
+              if (ldapCtx == null) {
+                throw new IllegalStateException("LDAP context not initialized. 
Please check ldap-context-initializer route.")
+              }
+              log.info("LDAP context found in registry, proceeding with 
migration...")
+        - log:
+            message: "Starting LDAP to Keycloak user migration..."
+        - log:
+            message: "Searching LDAP with filter: {{ldap.search.filter}}, 
base: {{ldap.search.base}}"
+
+        # Search LDAP for users
+        - setBody:
+            constant: "{{ldap.search.filter}}"
+        - to:
+            uri: "ldap:ldapserver?base={{ldap.search.base}}"
+        - log:
+            message: "Found ${body.size()} users in LDAP"
+
+        # Transform LDAP SearchResults to Keycloak UserRepresentation
+        - script:
+            groovy: |
+              import javax.naming.directory.SearchResult
+              import javax.naming.directory.Attributes
+              import org.keycloak.representations.idm.UserRepresentation
+
+              // Get LDAP search results from exchange body
+              def searchResults = request.body
+              def users = []
+
+              searchResults.each { SearchResult result ->
+                Attributes attrs = result.attributes
+
+                // Create Keycloak user representation
+                def user = new UserRepresentation()
+
+                // Map LDAP attributes to Keycloak user fields
+                // Username (required) - from uid, cn, or sAMAccountName
+                def username = attrs.get("uid")?.get() ?:
+                               attrs.get("cn")?.get() ?:
+                               attrs.get("sAMAccountName")?.get()
+                if (username) {
+                  user.username = username.toString()
+                }
+
+                // Email
+                def mail = attrs.get("mail")?.get()
+                if (mail) {
+                  user.email = mail.toString()
+                }
+
+                // First name - from givenName
+                def givenName = attrs.get("givenName")?.get()
+                if (givenName) {
+                  user.firstName = givenName.toString()
+                }
+
+                // Last name - from sn (surname)
+                def sn = attrs.get("sn")?.get()
+                if (sn) {
+                  user.lastName = sn.toString()
+                }
+
+                // Enable user by default
+                user.enabled = true
+
+                // Add custom attributes if configured
+                def customAttrs = [:]
+
+                // Organizational Unit
+                def ou = attrs.get("ou")?.get()
+                if (ou) {
+                  customAttrs.put("ldap_ou", [ou.toString()])
+                }
+
+                // Distinguished Name (for reference)
+                def dn = result.nameInNamespace
+                if (dn) {
+                  customAttrs.put("ldap_dn", [dn])
+                }
+
+                // Description
+                def description = attrs.get("description")?.get()
+                if (description) {
+                  customAttrs.put("description", [description.toString()])
+                }
+
+                // Telephone number
+                def telephoneNumber = attrs.get("telephoneNumber")?.get()
+                if (telephoneNumber) {
+                  customAttrs.put("phoneNumber", [telephoneNumber.toString()])
+                }
+
+                if (!customAttrs.isEmpty()) {
+                  user.attributes = customAttrs
+                }
+
+                // Add required actions if configured
+                def requirePasswordUpdate = 
camelContext.resolvePropertyPlaceholders("{{keycloak.user.requirePasswordUpdate}}")
+                if ("true".equals(requirePasswordUpdate)) {
+                  user.requiredActions = ["UPDATE_PASSWORD"]
+                }
+
+                users.add(user)
+              }
+
+              // Set transformed users as message body
+              request.body = users
+
+        # Set headers for bulk user creation
+        - setHeader:
+            name: CamelKeycloakRealmName
+            constant: "{{keycloak.target.realm}}"
+        - setHeader:
+            name: CamelKeycloakContinueOnError
+            constant: true
+
+        # Bulk create users in Keycloak
+        - to:
+            uri: "keycloak:admin?operation=bulkCreateUsers"
+
+        # Log migration results
+        - script:
+            groovy: |
+              def result = request.body
+
+              println ""
+              println "=" * 80
+              println "LDAP to Keycloak Migration Results"
+              println "=" * 80
+              println "Total users processed: ${result.total}"
+              println "Successfully created:  ${result.success}"
+              println "Failed:                ${result.failed}"
+              println "=" * 80
+
+              if (result.results) {
+                println "\nDetailed Results:"
+                println "-" * 80
+                result.results.each { userResult ->
+                  def status = userResult.status == "success" ? "✓" : "✗"
+                  println "${status} ${userResult.username?.padRight(30)} - 
${userResult.status}"
+                  if (userResult.error) {
+                    println "  Error: ${userResult.error}"
+                  }
+                }
+                println "-" * 80
+              }
+
+              println ""
+              println "Migration completed!"
+              println ""
diff --git a/keycloak-ldap-migration/users.ldif 
b/keycloak-ldap-migration/users.ldif
new file mode 100644
index 0000000..de05a23
--- /dev/null
+++ b/keycloak-ldap-migration/users.ldif
@@ -0,0 +1,63 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Create organizational unit for users
+dn: ou=users,dc=example,dc=org
+objectClass: top
+objectClass: organizationalUnit
+ou: users
+
+# User 1: John Doe
+dn: uid=jdoe,ou=users,dc=example,dc=org
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+uid: jdoe
+cn: John Doe
+sn: Doe
+givenName: John
+mail: [email protected]
+telephoneNumber: +1-555-1234
+userPassword: password123
+
+# User 2: Jane Smith
+dn: uid=jsmith,ou=users,dc=example,dc=org
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+uid: jsmith
+cn: Jane Smith
+sn: Smith
+givenName: Jane
+mail: [email protected]
+telephoneNumber: +1-555-5678
+userPassword: password456
+
+# User 3: Admin User
+dn: uid=admin,ou=users,dc=example,dc=org
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: person
+uid: admin
+cn: Admin User
+sn: User
+givenName: Admin
+mail: [email protected]
+ou: IT
+description: System Administrator
+telephoneNumber: +1-555-9999
+userPassword: adminpass


Reply via email to