perf: 邮箱支持exchange协议

pull/12508/head
jiangweidong 2024-01-02 17:00:15 +08:00 committed by Bryan
parent 2a29cd0e70
commit 9ede3670a7
6 changed files with 135 additions and 10 deletions

View File

@ -2,7 +2,7 @@ import os
from celery import shared_task
from django.conf import settings
from django.core.mail import send_mail, EmailMultiAlternatives
from django.core.mail import send_mail, EmailMultiAlternatives, get_connection
from django.utils.translation import gettext_lazy as _
import jms_storage
@ -11,6 +11,16 @@ from .utils import get_logger
logger = get_logger(__file__)
def get_email_connection(**kwargs):
email_backend_map = {
'smtp': 'django.core.mail.backends.smtp.EmailBackend',
'exchange': 'jumpserver.rewriting.exchange.EmailBackend'
}
return get_connection(
backend=email_backend_map.get(settings.EMAIL_PROTOCOL), **kwargs
)
def task_activity_callback(self, subject, message, recipient_list, *args, **kwargs):
from users.models import User
email_list = recipient_list
@ -40,7 +50,7 @@ def send_mail_async(*args, **kwargs):
args = tuple(args)
try:
return send_mail(*args, **kwargs)
return send_mail(connection=get_email_connection(), *args, **kwargs)
except Exception as e:
logger.error("Sending mail error: {}".format(e))
@ -55,7 +65,8 @@ def send_mail_attachment_async(subject, message, recipient_list, attachment_list
subject=subject,
body=message,
from_email=from_email,
to=recipient_list
to=recipient_list,
connection=get_email_connection(),
)
for attachment in attachment_list:
email.attach_file(attachment)

View File

@ -453,6 +453,7 @@ class Config(dict):
'CUSTOM_SMS_REQUEST_METHOD': 'get',
# Email
'EMAIL_PROTOCOL': 'smtp',
'EMAIL_CUSTOM_USER_CREATED_SUBJECT': _('Create account successfully'),
'EMAIL_CUSTOM_USER_CREATED_HONORIFIC': _('Hello'),
'EMAIL_CUSTOM_USER_CREATED_BODY': _('Your account has been created successfully'),

View File

@ -0,0 +1,104 @@
import urllib3
from urllib3.exceptions import InsecureRequestWarning
from django.core.mail.backends.base import BaseEmailBackend
from django.core.mail.message import sanitize_address
from django.conf import settings
from exchangelib import Account, Credentials, Configuration, DELEGATE
from exchangelib import Mailbox, Message, HTMLBody, FileAttachment
from exchangelib import BaseProtocol, NoVerifyHTTPAdapter
from exchangelib.errors import TransportError
urllib3.disable_warnings(InsecureRequestWarning)
BaseProtocol.HTTP_ADAPTER_CLS = NoVerifyHTTPAdapter
class EmailBackend(BaseEmailBackend):
def __init__(
self,
service_endpoint=None,
username=None,
password=None,
fail_silently=False,
**kwargs,
):
super().__init__(fail_silently=fail_silently)
self.service_endpoint = service_endpoint or settings.EMAIL_HOST
self.username = settings.EMAIL_HOST_USER if username is None else username
self.password = settings.EMAIL_HOST_PASSWORD if password is None else password
self._connection = None
def open(self):
if self._connection:
return False
try:
config = Configuration(
service_endpoint=self.service_endpoint, credentials=Credentials(
username=self.username, password=self.password
)
)
self._connection = Account(self.username, config=config, access_type=DELEGATE)
return True
except TransportError:
if not self.fail_silently:
raise
def close(self):
self._connection = None
def send_messages(self, email_messages):
if not email_messages:
return 0
new_conn_created = self.open()
if not self._connection or new_conn_created is None:
return 0
num_sent = 0
for message in email_messages:
sent = self._send(message)
if sent:
num_sent += 1
if new_conn_created:
self.close()
return num_sent
def _send(self, email_message):
if not email_message.recipients():
return False
encoding = settings.DEFAULT_CHARSET
from_email = sanitize_address(email_message.from_email, encoding)
recipients = [
Mailbox(email_address=sanitize_address(addr, encoding)) for addr in email_message.recipients()
]
try:
message_body = email_message.body
alternatives = email_message.alternatives or []
attachments = []
for attachment in email_message.attachments or []:
name, content, mimetype = attachment
if isinstance(content, str):
content = content.encode(encoding)
attachments.append(
FileAttachment(name=name, content=content, content_type=mimetype)
)
for alternative in alternatives:
if alternative[1] == 'text/html':
message_body = HTMLBody(alternative[0])
break
email_message = Message(
account=self._connection, subject=email_message.subject,
body=message_body, to_recipients=recipients, sender=from_email,
attachments=[]
)
email_message.attach(attachments)
email_message.send_and_save()
except Exception as error:
if not self.fail_silently:
raise error
return False
return True

View File

@ -331,6 +331,7 @@ if DEBUG_DEV:
FIXTURE_DIRS = [os.path.join(BASE_DIR, 'fixtures'), ]
# Email config
EMAIL_PROTOCOL = CONFIG.EMAIL_PROTOCOL
EMAIL_HOST = CONFIG.EMAIL_HOST
EMAIL_PORT = CONFIG.EMAIL_PORT
EMAIL_HOST_USER = CONFIG.EMAIL_HOST_USER

View File

@ -4,11 +4,12 @@
from smtplib import SMTPSenderRefused
from django.conf import settings
from django.core.mail import send_mail, get_connection
from django.core.mail import send_mail
from django.utils.translation import gettext_lazy as _
from rest_framework.views import Response, APIView
from common.utils import get_logger
from common.tasks import get_email_connection as get_connection
from .. import serializers
logger = get_logger(__file__)

View File

@ -1,11 +1,12 @@
# coding: utf-8
#
from django.db import models
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from common.serializers.fields import EncryptedField
__all__ = [
'MailTestSerializer', 'EmailSettingSerializer',
'EmailContentSettingSerializer', 'SMSBackendSerializer',
@ -18,14 +19,20 @@ class MailTestSerializer(serializers.Serializer):
class EmailSettingSerializer(serializers.Serializer):
# encrypt_fields 现在使用 write_only 来判断了
PREFIX_TITLE = _('Email')
EMAIL_HOST = serializers.CharField(max_length=1024, required=True, label=_("SMTP host"))
EMAIL_PORT = serializers.CharField(max_length=5, required=True, label=_("SMTP port"))
EMAIL_HOST_USER = serializers.CharField(max_length=128, required=True, label=_("SMTP account"))
class EmailProtocol(models.TextChoices):
smtp = 'smtp', _('SMTP')
exchange = 'exchange', _('EXCHANGE')
EMAIL_PROTOCOL = serializers.ChoiceField(
choices=EmailProtocol.choices, label=_("Protocol"), default=EmailProtocol.smtp
)
EMAIL_HOST = serializers.CharField(max_length=1024, required=True, label=_("Host"))
EMAIL_PORT = serializers.CharField(max_length=5, required=True, label=_("Port"))
EMAIL_HOST_USER = serializers.CharField(max_length=128, required=True, label=_("Account"))
EMAIL_HOST_PASSWORD = EncryptedField(
max_length=1024, required=False, label=_("SMTP password"),
max_length=1024, required=False, label=_("Password"),
help_text=_("Tips: Some provider use token except password")
)
EMAIL_FROM = serializers.CharField(