118 lines
3.9 KiB
Python
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
|