import logging from azure.identity import ( DeviceCodeCredential, TokenCachePersistenceOptions, AuthenticationRecord, ) from msgraph.core import GraphClient from database import get_setting, set_setting logger = logging.getLogger(__name__) CACHE_OPTIONS = TokenCachePersistenceOptions( name="busylight", allow_unencrypted_storage=True ) class GraphAPI: def __init__(self): self.client = None self.credential = None self._init_client() def _init_client(self): client_id = get_setting("azure_client_id") tenant_id = get_setting("azure_auth_tenant", "common") scopes = get_setting("azure_scopes").split() auth_record_json = get_setting("azure_auth_record") if not client_id or not auth_record_json: logger.warning("Azure nicht konfiguriert oder nicht authentifiziert") return try: record = AuthenticationRecord.deserialize(auth_record_json) self.credential = DeviceCodeCredential( client_id=client_id, tenant_id=tenant_id, cache_persistence_options=CACHE_OPTIONS, authentication_record=record, ) self.credential.authenticate(tenant_id=tenant_id, scopes=scopes) self.client = GraphClient(credential=self.credential, scopes=scopes) logger.info("Graph-Client erfolgreich initialisiert") except Exception as e: logger.error("Graph-Client Fehler: %s", e) self.client = None def authenticate(self): """Trigger device code flow. Must run in terminal.""" client_id = get_setting("azure_client_id") tenant_id = get_setting("azure_auth_tenant", "common") scopes = get_setting("azure_scopes").split() if not client_id: raise ValueError("Azure Client ID nicht konfiguriert") self.credential = DeviceCodeCredential( client_id=client_id, tenant_id=tenant_id, cache_persistence_options=CACHE_OPTIONS, ) record = self.credential.authenticate(tenant_id=tenant_id, scopes=scopes) set_setting("azure_auth_record", record.serialize()) self.client = GraphClient(credential=self.credential, scopes=scopes) logger.info("Azure Authentifizierung erfolgreich") return record def is_authenticated(self): return self.client is not None def get_users(self): if not self.client: return [] try: resp = self.client.get( "/users?$select=displayName,id,mail&$orderBy=displayName&$top=999" ) data = resp.json() users = data.get("value", []) # Handle pagination while "@odata.nextLink" in data: resp = self.client.get(data["@odata.nextLink"]) data = resp.json() users.extend(data.get("value", [])) return users except Exception as e: logger.error("Fehler beim Abrufen der User: %s", e) return [] def get_presence(self, user_id): if not self.client: return None try: resp = self.client.get(f"/users/{user_id}/presence") return resp.json() except Exception as e: logger.error("Fehler beim Abrufen der Presence für %s: %s", user_id, e) return None