""" Secret Service Handles creation and removal of app registration secrets. """ from msgraph import GraphServiceClient from msgraph.generated.models.password_credential import PasswordCredential from msgraph.generated.applications.item.add_password.add_password_post_request_body import AddPasswordPostRequestBody from msgraph.generated.applications.item.remove_password.remove_password_post_request_body import RemovePasswordPostRequestBody from datetime import datetime, timedelta from typing import Dict, List, Any import config class SecretService: """Service for managing app registration secrets.""" def __init__(self, graph_client: GraphServiceClient): """ Initialize the secret service. Args: graph_client: Authenticated Graph service client """ self.graph_client = graph_client async def create_secret( self, app_object_id: str, description: str, years: int = None ) -> Dict[str, Any]: """ Create a new secret for an app registration. Args: app_object_id: The object ID of the app registration description: Display name/description for the secret years: Number of years until expiration (defaults to config.APP_SECRET_EXPIRATION_YEARS) Returns: Dict: Contains secret_text, key_id, and end_datetime Raises: Exception: If the API call fails """ try: if years is None: years = config.APP_SECRET_EXPIRATION_YEARS # Calculate expiration date end_date = datetime.now() + timedelta(days=365 * years) # Create password credential password_cred = PasswordCredential() password_cred.display_name = description password_cred.end_date_time = end_date # Create request body request_body = AddPasswordPostRequestBody() request_body.password_credential = password_cred # Call Graph API to add password result = await self.graph_client.applications.by_application_id( app_object_id ).add_password.post(request_body) if result: return { 'secret_text': result.secret_text, 'key_id': str(result.key_id), 'end_datetime': result.end_date_time, 'display_name': result.display_name } raise Exception("No result returned from add_password") except Exception as e: raise Exception(f"Failed to create secret: {str(e)}") async def remove_old_secrets( self, app_object_id: str, keep_key_id: str ) -> int: """ Remove all secrets except the specified one. Args: app_object_id: The object ID of the app registration keep_key_id: The key ID to keep (newly created secret) Returns: int: Number of secrets removed Raises: Exception: If the API call fails """ try: removed_count = 0 # Get the app with its password credentials app = await self.graph_client.applications.by_application_id(app_object_id).get() if app and app.password_credentials: for cred in app.password_credentials: # Remove if it's not the one we want to keep if str(cred.key_id) != str(keep_key_id): # Create request body request_body = RemovePasswordPostRequestBody() request_body.key_id = cred.key_id # Call Graph API to remove password await self.graph_client.applications.by_application_id( app_object_id ).remove_password.post(request_body) removed_count += 1 return removed_count except Exception as e: raise Exception(f"Failed to remove old secrets: {str(e)}") async def list_secrets(self, app_object_id: str) -> List[Dict[str, Any]]: """ List all secrets for an app registration (metadata only, not the secret values). Args: app_object_id: The object ID of the app registration Returns: List[Dict]: List of secret metadata Raises: Exception: If the API call fails """ try: app = await self.graph_client.applications.by_application_id(app_object_id).get() secrets = [] if app and app.password_credentials: for cred in app.password_credentials: secrets.append({ 'key_id': str(cred.key_id), 'display_name': cred.display_name, 'start_datetime': cred.start_date_time, 'end_datetime': cred.end_date_time }) return secrets except Exception as e: raise Exception(f"Failed to list secrets: {str(e)}")