CardSync/backend/group_sync.py

122 lines
4.8 KiB
Python

"""Synchronisation von Azure AD Gruppenmitgliedern → CardSync User-Tabelle.
Holt alle Mitglieder einer Azure AD Gruppe via Microsoft Graph (Application Permissions)
und legt sie als User in CardSync an. Bestehende User werden aktualisiert,
nicht mehr in der Gruppe vorhandene User werden NICHT automatisch gelöscht
(nur deren Sync deaktiviert, damit es kein Datenverlust gibt).
"""
import logging
from datetime import datetime, timezone
from sqlalchemy.orm import Session
from models import User, AzureGroupConfig
from ms_graph import get_group_info, get_group_members
logger = logging.getLogger(__name__)
async def sync_group_members(db: Session) -> dict:
"""Holt alle Gruppenmitglieder und legt User in der DB an / aktualisiert sie."""
config = db.query(AzureGroupConfig).first()
if not config:
return {"status": "error", "message": "Keine Azure-Gruppen-Konfiguration vorhanden"}
started = datetime.now(timezone.utc)
try:
# Gruppen-Info aktualisieren
group_info = await get_group_info(config.group_id)
if group_info:
config.group_name = group_info.get("displayName", config.group_name)
members = await get_group_members(config.group_id)
logger.info(f"Azure AD: {len(members)} Mitglieder in Gruppe '{config.group_name}' gefunden")
created = 0
updated = 0
deactivated = 0
# Set für die Erkennung entfernter Mitglieder
current_member_ids = set()
for m in members:
ms_user_id = m.get("id")
email = (m.get("mail") or m.get("userPrincipalName") or "").lower()
display_name = m.get("displayName", "")
if not ms_user_id or not email:
continue
current_member_ids.add(ms_user_id)
user = db.query(User).filter(User.ms_user_id == ms_user_id).first()
if not user:
# Mit E-Mail als Fallback suchen
user = db.query(User).filter(User.email == email).first()
if user:
# Existierender User → Stammdaten aktualisieren
if user.ms_user_id != ms_user_id:
user.ms_user_id = ms_user_id
if user.email != email:
user.email = email
if user.display_name != display_name and display_name:
user.display_name = display_name
# Auto-Aktivierung NUR für reine Group-Import-User
# OAuth-User behalten ihre individuellen Einstellungen
if user.source == "group_import":
if not user.sync_enabled and config.default_sync_enabled:
user.sync_enabled = True
user.updated_at = datetime.now(timezone.utc)
updated += 1
else:
# Neuer User
new_user = User(
ms_user_id=ms_user_id,
email=email,
display_name=display_name,
source="group_import",
sync_enabled=config.default_sync_enabled,
carddav_folder=config.default_carddav_folder,
sync_interval_minutes=config.default_sync_interval_minutes,
)
db.add(new_user)
created += 1
logger.info(f"Neuer User aus Gruppe importiert: {email}")
# User die nicht mehr in der Gruppe sind → Sync deaktivieren
# (nur für group_import User, OAuth-User nicht anfassen)
all_imported = db.query(User).filter(User.source == "group_import").all()
for u in all_imported:
if u.ms_user_id not in current_member_ids and u.sync_enabled:
u.sync_enabled = False
u.last_sync_message = "Aus Azure AD Gruppe entfernt — Sync automatisch deaktiviert"
u.updated_at = datetime.now(timezone.utc)
deactivated += 1
logger.info(f"Sync deaktiviert für {u.email} (nicht mehr in Gruppe)")
config.last_member_sync_at = started
config.last_member_sync_status = "success"
config.last_member_sync_message = (
f"{created} neu, {updated} aktualisiert, {deactivated} deaktiviert"
)
db.commit()
return {
"status": "success",
"message": config.last_member_sync_message,
"created": created,
"updated": updated,
"deactivated": deactivated,
"total_members": len(members),
}
except Exception as e:
logger.error(f"Group member sync failed: {e}", exc_info=True)
config.last_member_sync_at = started
config.last_member_sync_status = "error"
config.last_member_sync_message = str(e)
db.commit()
return {"status": "error", "message": str(e)}