""" Microsoft Graph Authentication Module Handles authentication to Microsoft Graph API for app registration management. """ from azure.identity import InteractiveBrowserCredential from msgraph import GraphServiceClient from typing import Optional import config class GraphAuthenticator: """Handles Microsoft Graph authentication and client creation.""" def __init__(self, client_id: str = None): """ Initialize the Graph authenticator. Args: client_id: Application Client ID (defaults to config.CLIENT_ID) """ self.client_id = client_id or config.CLIENT_ID self.credential: Optional[InteractiveBrowserCredential] = None self.client: Optional[GraphServiceClient] = None async def authenticate(self, tenant_id: str = None, credential: InteractiveBrowserCredential = None, skip_validation: bool = False) -> bool: """ Authenticate to Microsoft Graph using interactive browser login. Args: tenant_id: Optional specific tenant ID (recommended to avoid double auth) credential: Optional credential to reuse (avoids creating new credential) skip_validation: Skip the me.get() validation call (use when reusing credential to avoid extra auth) Returns: bool: True if authentication succeeded, False otherwise Raises: Exception: If authentication fails """ try: if credential: # Reuse provided credential (recommended to avoid multiple auth prompts) self.credential = credential else: # Use specific tenant if provided, otherwise fall back to "organizations" auth_tenant = tenant_id if tenant_id else "organizations" # Create interactive browser credential self.credential = InteractiveBrowserCredential( tenant_id=auth_tenant, client_id=self.client_id, additionally_allowed_tenants=["*"] ) # Define scopes for Microsoft Graph scopes = ['https://graph.microsoft.com/.default'] # Create Graph service client self.client = GraphServiceClient( credentials=self.credential, scopes=scopes ) # Validate authentication (unless skip_validation is True) if not skip_validation: me = await self.client.me.get() if me: print(f"Successfully authenticated as: {me.display_name} ({me.user_principal_name})") return True return False else: print("Skipping Graph validation (using cached authentication)") return True except Exception as e: raise Exception(f"Graph authentication failed: {str(e)}") def get_client(self) -> GraphServiceClient: """ Get the authenticated Graph service client. Returns: GraphServiceClient: The authenticated client Raises: Exception: If not authenticated """ if not self.client: raise Exception("Not authenticated. Call authenticate() first.") return self.client def get_credential(self) -> InteractiveBrowserCredential: """ Get the credential object for reuse in other Azure services. Returns: InteractiveBrowserCredential: The credential object Raises: Exception: If not authenticated """ if not self.credential: raise Exception("Not authenticated. Call authenticate() first.") return self.credential def is_authenticated(self) -> bool: """ Check if currently authenticated. Returns: bool: True if authenticated, False otherwise """ return self.client is not None and self.credential is not None