Files
I-SecretUpdate/auth/graph_authenticator.py
T
2025-12-22 09:57:49 +01:00

118 lines
3.9 KiB
Python

"""
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