No description
Find a file
2026-08-21 11:56:58 +02:00
.claude Organisation und Organisation-Unit gemappt 2026-08-21 11:22:20 +02:00
.env.example Kontakte-Sync von JSON-Dateien auf LDAP (TelDAP) umgestellt 2026-08-19 19:07:09 +02:00
.gitignore Claude-Ordner hinzugefügt 2026-08-19 19:08:47 +02:00
config.py BASE_URL für Crontab gesteuerte Prozesse implementiert 2026-08-20 13:56:37 +02:00
graph_client.py Kontakte-Sync von JSON-Dateien auf LDAP (TelDAP) umgestellt 2026-08-19 19:07:09 +02:00
graph_mapper.py JobTitle ist nun auch in der Suche mit drin 2026-08-21 11:56:58 +02:00
ldap_client.py Kontakte-Sync von JSON-Dateien auf LDAP (TelDAP) umgestellt 2026-08-19 19:07:09 +02:00
ldap_contact_sync.py BASE_URL für Crontab gesteuerte Prozesse implementiert 2026-08-20 13:56:37 +02:00
ldap_mapper.py JobTitle ist nun auch in der Suche mit drin 2026-08-21 11:56:58 +02:00
ldap_settings.py BASE_URL für Crontab gesteuerte Prozesse implementiert 2026-08-20 13:56:37 +02:00
phone_normalizer.py Kontakte-Sync von JSON-Dateien auf LDAP (TelDAP) umgestellt 2026-08-19 19:07:09 +02:00
README.md Kontakte-Sync von JSON-Dateien auf LDAP (TelDAP) umgestellt 2026-08-19 19:07:09 +02:00
requirements.txt Kontakte-Sync von JSON-Dateien auf LDAP (TelDAP) umgestellt 2026-08-19 19:07:09 +02:00
search_index.py JobTitle ist nun auch in der Suche mit drin 2026-08-21 11:56:58 +02:00
settings.yaml Kontakte-Sync von JSON-Dateien auf LDAP (TelDAP) umgestellt 2026-08-19 19:07:09 +02:00
stable_id.py Kontakte-Sync von JSON-Dateien auf LDAP (TelDAP) umgestellt 2026-08-19 19:07:09 +02:00
sync.py Kontakte-Sync von JSON-Dateien auf LDAP (TelDAP) umgestellt 2026-08-19 19:07:09 +02:00
sync_user_data.py Kontakte-Sync von JSON-Dateien auf LDAP (TelDAP) umgestellt 2026-08-19 19:07:09 +02:00
usermapping.py Kontakte-Sync von JSON-Dateien auf LDAP (TelDAP) umgestellt 2026-08-19 19:07:09 +02:00

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-run fü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

  1. Azure PortalAzure Active DirectoryApp registrationsNew registration
  2. Name vergeben, Account type: Accounts in this organizational directory only
  3. Certificates & secretsNew client secret → Secret kopieren
  4. API permissionsAdd a permissionMicrosoft GraphApplication permissions
    → Die vier Permissions oben hinzufügen
  5. 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.csv gelisteten User gesynct, nicht für alle Tenant-User.
  • Alte State-Dateien im v1-Format ({"delta_link": "..."}) werden automatisch migriert.