No description
| .claude | ||
| .env.example | ||
| .gitignore | ||
| config.py | ||
| graph_client.py | ||
| graph_mapper.py | ||
| ldap_client.py | ||
| ldap_contact_sync.py | ||
| ldap_mapper.py | ||
| ldap_settings.py | ||
| phone_normalizer.py | ||
| README.md | ||
| requirements.txt | ||
| search_index.py | ||
| settings.yaml | ||
| stable_id.py | ||
| sync.py | ||
| sync_user_data.py | ||
| usermapping.py | ||
Microsoft Graph API – M365 Sync
Synchronisiert M365-Daten. Source of Truth ist Microsoft 365. Kontakte werden nach
LDAP (TelDAP) importiert, Kalendereinträge weiterhin als JSON-Dateien abgelegt.
Siehe .claude/LDAP.md für die vollständige LDAP-Spezifikation und settings.yaml
für die LDAP-Verbindungseinstellungen.
| Ressource | Graph-Quelle | Ziel |
|---|---|---|
| Mitarbeitertelefonbuch | /users/{employees_mailbox_id}/contacts/delta |
LDAP ou=employees,ou=addressbook,... |
| Globale/öffentliche Kontakte | /users/{global_mailbox_id}/contacts/delta |
LDAP ou=global,ou=addressbook,... |
Persönliche Kontakte (User aus state/usermapping.csv) |
/users/{upn}/contacts/delta |
LDAP ou=personal,ou=addressbook,... |
| Kalendereinträge (alle Tenant-User) | /users/{uid}/events/delta |
objects/user/{uid}/calendar/{id}.json |
- Erster Lauf: vollständiger Sync
- Folgeläufe: inkrementell via Graph Delta API
- Gelöschte Einträge werden in LDAP bzw. lokal entfernt
- State wird laufend gespeichert (resilient bei Abbruch)
--dry-runführt keine LDAP-Änderungen durch, loggt nur die geplante Aktion
Voraussetzungen
- Python 3.10+
- Erreichbarer LDAP-Server (TelDAP) mit
cn=graph-sync-Service-Account, siehe.claude/LDAP.md - Azure App Registration mit folgenden Application Permissions (Admin Consent erforderlich):
| Permission | Zweck |
|---|---|
User.Read.All |
Alle Tenant-User enumerieren (für Kalender) |
Contacts.Read |
Kontakte aller relevanten Postfächer (employees/global/personal) |
Calendars.Read |
Kalender aller User |
Einrichtung
1. Azure App Registration
- Azure Portal → Azure Active Directory → App registrations → New registration
- Name vergeben, Account type: Accounts in this organizational directory only
- Certificates & secrets → New client secret → Secret kopieren
- API permissions → Add a permission → Microsoft Graph → Application permissions
→ Die vier Permissions oben hinzufügen - Grant admin consent for [Tenant] klicken
2. Projekt einrichten
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
cp .env.example .env
.env ausfüllen:
TENANT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
CLIENT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
CLIENT_SECRET=your-client-secret-here
TELDAP_LDAP_PASSWORD=your-ldap-password-here
settings.yaml prüfen/anpassen (LDAP-Host/Port, Basis-DNs, employees/global Mailbox-IDs, Pfad zu state/usermapping.csv).
Verwendung
.venv/bin/python sync.py # echter Lauf
.venv/bin/python sync.py --dry-run # keine LDAP-Änderungen, nur Logging
Der erste Lauf führt einen vollständigen Sync durch. Jeder weitere Lauf synchronisiert nur Änderungen seit dem letzten Lauf.
Für automatische Ausführung per Cron:
# Täglich um 03:00 Uhr
0 3 * * * /pfad/zum/projekt/.venv/bin/python /pfad/zum/projekt/sync.py
Dateistruktur
.
├── sync.py # Einstiegspunkt und Orchestrierung
├── ldap_contact_sync.py # Sync: Kontakte (employees/global/personal) -> LDAP
├── sync_user_data.py # Sync: Kalender (JSON)
├── graph_mapper.py # Graph-Contact -> kanonisches Contact-Modell
├── ldap_mapper.py # Kanonisches Contact-Modell -> LDAP-Attribute/DN
├── phone_normalizer.py # Telefonnummern-Normalisierung (E.164 + Such-Varianten)
├── search_index.py # rsmSearch-Werte
├── stable_id.py # Stabile LDAP-uid (UUIDv5)
├── ldap_client.py # LDAP-Verbindung + ADD/MODIFY/DELETE (ldap3)
├── ldap_settings.py # Lädt settings.yaml + LDAP-Passwort
├── usermapping.py # Lädt state/usermapping.csv
├── graph_client.py # Graph API Auth + Pagination
├── config.py # Konfiguration (lädt .env)
├── settings.yaml # LDAP-Verbindung, Basis-DNs, Postfach-IDs
├── requirements.txt
├── .env # Credentials (nicht committen!)
├── .env.example
├── objects/
│ └── user/
│ └── {uid}/
│ └── calendar/
│ └── {id}.json
└── state/
├── sync_state.json # Delta-Links aller Ressourcen
└── usermapping.csv # Graph-User -> LDAP-uid (personal contacts scope)
State-Datei
{
"ldap_contacts": {
"employees": { "delta_link": "https://..." },
"global": { "delta_link": "https://..." },
"personal": {
"4986244073217": { "delta_link": "https://..." }
}
},
"users": {
"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx": {
"calendar_delta_link": "https://..."
}
}
}
Hinweise
- User ohne Exchange-Mailbox (keine Lizenz, Service Accounts) werden automatisch übersprungen (HTTP 403/404).
- Die State-Datei wird laufend gespeichert. Ein Abbruch führt beim nächsten Lauf nur zu einem Teilsync.
- Persönliche Kontakte werden nur für die in
state/usermapping.csvgelisteten User gesynct, nicht für alle Tenant-User. - Alte State-Dateien im v1-Format (
{"delta_link": "..."}) werden automatisch migriert.