""" 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) -> bool: """ Authenticate to Microsoft Graph using interactive browser login. Returns: bool: True if authentication succeeded, False otherwise Raises: Exception: If authentication fails """ try: # Create interactive browser credential # Using "organizations" allows login with any organizational account # additionally_allowed_tenants="*" allows acquiring tokens for any tenant (needed for Key Vault access) self.credential = InteractiveBrowserCredential( tenant_id="organizations", 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 ) # Authenticate to Graph API FIRST # This triggers the initial browser auth for Graph scope # Then Management API will use SSO (single sign-on) from this auth me = await self.client.me.get() if me: print(f"Successfully authenticated as: {me.display_name} ({me.user_principal_name})") return True return False 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