mirror of
https://github.com/langgenius/dify.git
synced 2026-03-23 11:51:14 +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.
80 lines
2.6 KiB
Python
80 lines
2.6 KiB
Python
"""SMTP connection abstraction for better testability"""
|
|
|
|
import smtplib
|
|
from abc import ABC, abstractmethod
|
|
from typing import Protocol, Union
|
|
|
|
|
|
class SMTPConnectionProtocol(Protocol):
|
|
"""Protocol defining SMTP connection interface"""
|
|
|
|
def ehlo(self, name: str = "") -> tuple[int, bytes]: ...
|
|
|
|
def starttls(self) -> tuple[int, bytes]: ...
|
|
|
|
def login(self, user: str, password: str) -> tuple[int, bytes]: ...
|
|
|
|
def docmd(self, cmd: str, args: str = "") -> tuple[int, bytes]: ...
|
|
|
|
def sendmail(self, from_addr: str, to_addrs: str, msg: str) -> dict: ...
|
|
|
|
def quit(self) -> tuple[int, bytes]: ...
|
|
|
|
|
|
class SMTPConnectionFactory(ABC):
|
|
"""Abstract factory for creating SMTP connections"""
|
|
|
|
@abstractmethod
|
|
def create_connection(self, server: str, port: int, timeout: int = 10) -> SMTPConnectionProtocol:
|
|
"""Create an SMTP connection"""
|
|
pass
|
|
|
|
|
|
class SMTPConnectionWrapper:
|
|
"""Wrapper to adapt smtplib.SMTP to our protocol"""
|
|
|
|
def __init__(self, smtp_obj: Union[smtplib.SMTP, smtplib.SMTP_SSL]):
|
|
self._smtp = smtp_obj
|
|
|
|
def ehlo(self, name: str = "") -> tuple[int, bytes]:
|
|
result = self._smtp.ehlo(name)
|
|
return (result[0], result[1])
|
|
|
|
def starttls(self) -> tuple[int, bytes]:
|
|
result = self._smtp.starttls()
|
|
return (result[0], result[1])
|
|
|
|
def login(self, user: str, password: str) -> tuple[int, bytes]:
|
|
result = self._smtp.login(user, password)
|
|
return (result[0], result[1])
|
|
|
|
def docmd(self, cmd: str, args: str = "") -> tuple[int, bytes]:
|
|
result = self._smtp.docmd(cmd, args)
|
|
return (result[0], result[1])
|
|
|
|
def sendmail(self, from_addr: str, to_addrs: str, msg: str) -> dict:
|
|
result = self._smtp.sendmail(from_addr, to_addrs, msg)
|
|
return dict(result)
|
|
|
|
def quit(self) -> tuple[int, bytes]:
|
|
result = self._smtp.quit()
|
|
return (result[0], result[1])
|
|
|
|
|
|
class StandardSMTPConnectionFactory(SMTPConnectionFactory):
|
|
"""Factory for creating standard SMTP connections"""
|
|
|
|
def create_connection(self, server: str, port: int, timeout: int = 10) -> SMTPConnectionProtocol:
|
|
"""Create a standard SMTP connection"""
|
|
smtp_obj = smtplib.SMTP(server, port, timeout=timeout)
|
|
return SMTPConnectionWrapper(smtp_obj)
|
|
|
|
|
|
class SSLSMTPConnectionFactory(SMTPConnectionFactory):
|
|
"""Factory for creating SSL SMTP connections"""
|
|
|
|
def create_connection(self, server: str, port: int, timeout: int = 10) -> SMTPConnectionProtocol:
|
|
"""Create an SSL SMTP connection"""
|
|
smtp_obj = smtplib.SMTP_SSL(server, port, timeout=timeout)
|
|
return SMTPConnectionWrapper(smtp_obj)
|