This guide covers automated user lifecycle management for openDesk Edu, including account creation, role assignment, and secure removal using LDAP federation and Keycloak admin APIs.
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ HISinOne │ │ University │ │ Keycloak │ │ openDesk │
│ │ │ LDAP/AD │ │ │ │ Edu Apps │
│ │ │ │ │ │ │ │
│ SOAP API │───►│ User │───►│ LDAP Fed. │───►│ ILIAS │
│ Events │ │ Store │ │ + Admin API │ │ Moodle │
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
kcadm)# Clone the provisioning tool
cd scripts/user_import
# Install dependencies
pip install -r requirements.txt
# Configure environment
cp .env.example .env
nano .env
Create .env in scripts/user_import/:
# Keycloak Configuration
KEYCLOAK_URL=https://yourdomain.de/auth
KEYCLOAK_REALM=opendesk
KEYCLOAK_ADMIN_USERNAME=admin
KEYCLOAK_ADMIN_PASSWORD=your-admin-password
# LDAP Configuration
LDAP_SERVER=ldap://ldap.yourinstitution.de
LDAP_BASE_DN=dc=institution,dc=de
LDAP_BIND_DN=cn=admin,dc=institution,dc=de
LDAP_BIND_PASSWORD=ldap-bind-password
# User Mapping
LDAP_USER_SEARCH_BASE=ou=users,dc=institution,dc=de
LDAP_USER_OBJECT_CLASS=inetOrgPerson
LDAP_USERNAME_ATTR=uid
LDAP_EMAIL_ATTR=mail
LDAP_FIRST_NAME_ATTR=givenName
LDAP_LAST_NAME_ATTR=sn
# HISinOne Configuration (Optional)
HISINONE_URL=https://hisinone.yourinstitution.de/qisserver/services2
HISINONE_API_KEY=your-hisinone-api-key
# Provisioning Options
DRY_RUN=true
LOG_LEVEL=INFO
cd scripts/user_import
# Sync all users from LDAP
python sync_users.py --source ldap --auto-sync
# Sync only active students
python sync_users.py --source ldap --filter "(eduPersonAffiliation=student)"
# Dry run to see what would happen
python sync_users.py --source ldap --dry-run
HISinOne integration is handled via the semester provisioning API. See docs/semester-lifecycle-management.md for details on semester-based course and enrollment management.
# Create users from CSV/ODS input
python provision.py \
--input-file users.csv \
--dry-run
# Import users from LDAP/UCS
python provision.py \
--source ucs \
--csv-separator ','
Define role mappings in config/roles.json:
{
"roles": {
"student": {
"description": "Student access",
"roles": ["student"],
"groups": ["students"],
"affiliation": "student",
"services": ["ilias", "moodle", "bbb", "files"]
},
"employee": {
"description": "Employee access",
"roles": ["employee"],
"groups": ["staff"],
"affiliation": "employee",
"services": ["email", "groupware", "files", "wiki"]
},
"faculty": {
"description": "Faculty access",
"roles": ["faculty", "employee"],
"groups": ["faculty", "staff"],
"affiliation": "faculty",
"services": ["ilias", "moodle", "bbb", "email", "groupware", "wiki"]
},
"lecturer": {
"description": "Lecturer access",
"roles": ["lecturer", "employee"],
"groups": ["lecturers", "staff"],
"affiliation": "faculty",
"services": ["ilias", "moodle", "bbb", "recording", "grades"]
}
},
"mappings": {
"student": "student",
"employee": "employee",
"faculty": "faculty",
"staff": "employee",
"prof": "faculty",
"dozent": "lecturer"
}
}
Roles are assigned automatically based on LDAP attributes and configured in config/roles.json. See the Role Mapping Configuration section above for details on how roles are mapped to group memberships and service access.
For custom role assignments, modify the role mapping in config/roles.json and re-run the sync:
# Sync users with updated role mappings
python sync_users.py --source ldap --auto-sync
Phase 1 (Disable):
# Disable user access (grace period)
python deprovision_user.py \
--username john.doe \
--phase disable \
--grace-period-days 180
# Disable all students who haven't re-registered
python deprovision_user.py \
--filter "(eduPersonAffiliation=student)" \
--phase disable \
--no-ruckmeldung-since 2026-01-15
Phase 2 (Permanent Delete):
# Permanent delete after grace period
python deprovision_user.py \
--username john.doe \
--phase delete
# Batch delete users in grace period expired
python deprovision_user.py \
--phase delete \
--grace-expired-before 2025-04-06
# Deprovision a batch of students
python deprovision_user.py \
--phase disable \
--input-file students-to-archive.csv
# Delete users permanently (after confirmation)
python deprovision_user.py \
--phase delete \
--input-file users-to-delete.csv \
--confirm
When using SAML federation (DFN-AAI), SAML identity linking is handled automatically by the Keycloak admin API integration in lib/keycloak.py. The deprovision_disable.py script includes SAML identity removal when disabling users.
For manual SAML identity management, use the Keycloak admin API directly:
# Use Keycloak admin CLI
kcadm.sh get users -r opendesk
kcadm.sh get users/<user-id>/federated-identity/<identity-provider> -r opendesk
Create /etc/systemd/system/opendesk-user-sync.service:
[Unit]
Description=openDesk Edu User Sync
After=network.target
[Service]
Type=simple
User=opendesk
WorkingDirectory=/opt/opendesk-edu/scripts/user_import
ExecStart=/usr/bin/python3 sync_users.py --source ldap --auto-sync
Environment=PYTHONUNBUFFERED=1
Restart=always
Create /etc/systemd/system/opendesk-user-sync.timer:
[Unit]
Description=openDesk Edu User Sync Timer
[Timer]
OnBootSec=10min
OnUnitActiveSec=1h
[Install]
WantedBy=timers.target
# Enable and start
sudo systemctl enable opendesk-user-sync.timer
sudo systemctl start opendesk-user-sync.timer
# Check status
sudo systemctl status opendesk-user-sync.timer
# Add to crontab
crontab -e
# Run every hour
0 * * * * /opt/opendesk-edu/scripts/user_import/sync_users.py --source ldap --auto-sync >> /var/log/opendesk-user-sync.log 2>&1
/var/log/opendesk-user-sync.log - Main sync log (from sync_users.py)/var/log/opendesk-user-provisioning.log - Provisioning events (from provision.py)/var/log/opendesk-user-deprovisioning.log - Deprovisioning events (from deprovision_user.py)Monitor logs directly using standard tools:
# View recent sync activity
tail -f /var/log/opendesk-user-sync.log
# Check failed syncs
grep ERROR /var/log/opendesk-user-sync.log
# Sync statistics
grep "Synced" /var/log/opendesk-user-sync.log | wc -l
Test LDAP connectivity using standard tools:
# Test LDAP connectivity
ldapsearch -H ldap://ldap.yourinstitution.de -x -D "cn=admin,dc=institution,dc=de" -W -b "dc=institution,dc=de"
# Check if sync can connect
python sync_users.py --source ldap --dry-run
# Check for connection errors
grep "LDAP connection failed" /var/log/opendesk-user-sync.log
Test Keycloak connection using the admin CLI:
# Test Keycloak connection
kcadm.sh config credentials --server https://yourdomain.de/auth --realm master --user admin
# Check admin credentials
kcadm.sh get users -r opendesk --max 1
# Test if provision.py can connect
python provision.py --dry-run --help
# Run sync with verbose logging
python sync_users.py --source ldap --debug
# Check for errors in logs
tail -f /var/log/opendesk-user-sync.log
# Validate CSV input format before running provision.py
head -n 5 users.csv
User data is stored in Keycloak and UCS. Use their native backup/restore tools:
# Export Keycloak realm configuration (includes users)
/opt/keycloak/bin/kcadm.sh export --realm opendesk --dir /backup/keycloak
# Backup UCS/UDM
udm-cli backup --output /backup/ucs/
# Restore from Keycloak backup
/opt/keycloak/bin/kcadm.sh import --dir /backup/keycloak --realm opendesk
For provisioning-specific issues, open an issue on GitHub.