diff --git a/modules/routes/routeSecurityMsft.py b/modules/routes/routeSecurityMsft.py index ac9b5af3..4b2964db 100644 --- a/modules/routes/routeSecurityMsft.py +++ b/modules/routes/routeSecurityMsft.py @@ -53,12 +53,15 @@ if not REDIRECT_URI: if CLIENT_SECRET and CLIENT_SECRET.startswith(("PROD_ENC:", "INT_ENC:", "DEV_ENC:")): logger.warning("Service_MSFT_CLIENT_SECRET appears to be encrypted - ensure decryption is working") SCOPES = [ - "Mail.ReadWrite", # Read and write mail (user's mailbox only) - "Mail.Send", # Send mail (user's mailbox only) "User.Read", # Read user profile - "Sites.ReadWrite", # Read and write user's SharePoint sites (not org-wide) - "Files.ReadWrite" # Read and write user's files (not org-wide) + "Mail.ReadWrite", # Read and write mail + "Mail.Send", # Send mail + "Files.ReadWrite.All", # Read and write files (SharePoint/OneDrive) + "Sites.ReadWrite.All" # Read and write SharePoint sites ] +# NOTE: Sites.ReadWrite.All and Files.ReadWrite.All require admin consent. +# An admin must grant consent ONCE at: /api/msft/adminconsent +# After that, all users can connect without individual admin approval. @router.get("/login") @limiter.limit("5/minute") @@ -133,6 +136,30 @@ async def login( detail=f"Failed to initiate Microsoft login: {str(e)}" ) +@router.get("/adminconsent") +@limiter.limit("5/minute") +async def adminconsent(request: Request) -> RedirectResponse: + """Initiate Microsoft Admin Consent flow. + + An Azure AD admin must visit this URL once to grant consent for the entire tenant. + After admin consent, all users can connect without individual approval. + """ + try: + adminConsentRedirectUri = REDIRECT_URI.replace("/auth/callback", "/adminconsent/callback") + adminConsentUrl = ( + f"{AUTHORITY}/adminconsent" + f"?client_id={CLIENT_ID}" + f"&redirect_uri={adminConsentRedirectUri}" + ) + logger.info(f"Redirecting to admin consent URL for tenant: {TENANT_ID}") + return RedirectResponse(adminConsentUrl) + except Exception as e: + logger.error(f"Error generating admin consent URL: {str(e)}") + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Failed to generate admin consent URL: {str(e)}" + ) + @router.get("/adminconsent/callback") async def adminconsent_callback( admin_consent: Optional[str] = Query(None),