mirror of
https://github.com/langgenius/dify.git
synced 2026-03-07 16:45:58 +08:00
Add comprehensive OAuth 2.0 authentication support for SMTP to address Microsoft's Basic Authentication retirement in September 2025. Key features: - OAuth 2.0 SASL XOAUTH2 authentication mechanism - Microsoft Azure AD integration with client credentials flow - Backward compatible with existing basic authentication - Comprehensive configuration options in .env.example files - Enhanced SMTP client with dependency injection for better testability - Complete test coverage with proper mocking Configuration: - SMTP_AUTH_TYPE: Choose between 'basic' and 'oauth2' authentication - Microsoft OAuth 2.0 settings for Azure AD integration - Automatic token acquisition using client credentials flow Files changed: - Enhanced SMTP client with OAuth 2.0 support - New mail module structure under libs/mail/ - Updated configuration system with OAuth settings - Comprehensive documentation and setup instructions - Complete test suite for OAuth functionality This change ensures compatibility with Microsoft Exchange Online after Basic Authentication retirement.
46 lines
1.6 KiB
Python
46 lines
1.6 KiB
Python
"""HTTP client abstraction for OAuth requests"""
|
|
|
|
from abc import ABC, abstractmethod
|
|
from typing import Optional, Union
|
|
|
|
import requests
|
|
|
|
|
|
class OAuthHTTPClientProtocol(ABC):
|
|
"""Abstract interface for OAuth HTTP operations"""
|
|
|
|
@abstractmethod
|
|
def post(
|
|
self, url: str, data: dict[str, Union[str, int]], headers: Optional[dict[str, str]] = None
|
|
) -> dict[str, Union[str, int, dict, list]]:
|
|
"""Make a POST request"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def get(self, url: str, headers: Optional[dict[str, str]] = None) -> dict[str, Union[str, int, dict, list]]:
|
|
"""Make a GET request"""
|
|
pass
|
|
|
|
|
|
class OAuthHTTPClient(OAuthHTTPClientProtocol):
|
|
"""Default implementation using requests library"""
|
|
|
|
def post(
|
|
self, url: str, data: dict[str, Union[str, int]], headers: Optional[dict[str, str]] = None
|
|
) -> dict[str, Union[str, int, dict, list]]:
|
|
"""Make a POST request"""
|
|
response = requests.post(url, data=data, headers=headers or {})
|
|
return {
|
|
"status_code": response.status_code,
|
|
"json": response.json() if response.headers.get("content-type", "").startswith("application/json") else {},
|
|
"text": response.text,
|
|
"headers": dict(response.headers),
|
|
}
|
|
|
|
def get(self, url: str, headers: Optional[dict[str, str]] = None) -> dict[str, Union[str, int, dict, list]]:
|
|
"""Make a GET request"""
|
|
response = requests.get(url, headers=headers or {})
|
|
response.raise_for_status()
|
|
json_data = response.json()
|
|
return dict(json_data)
|