Skip to content

Commit

Permalink
perf: 邮箱支持exchange协议
Browse files Browse the repository at this point in the history
  • Loading branch information
O-Jiangweidong authored and BaiJiangJie committed Jan 8, 2024
1 parent 2a29cd0 commit 9ede367
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 10 deletions.
17 changes: 14 additions & 3 deletions apps/common/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

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

Expand All @@ -11,6 +11,16 @@
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
Expand Down Expand Up @@ -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))

Expand All @@ -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)
Expand Down
1 change: 1 addition & 0 deletions apps/jumpserver/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
104 changes: 104 additions & 0 deletions apps/jumpserver/rewriting/exchange.py
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions apps/jumpserver/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ def parse_sentinels_host(sentinels_host):
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
Expand Down
3 changes: 2 additions & 1 deletion apps/settings/api/email.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
Expand Down
19 changes: 13 additions & 6 deletions apps/settings/serializers/msg.py
Original file line number Diff line number Diff line change
@@ -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',
Expand All @@ -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(
Expand Down

0 comments on commit 9ede367

Please sign in to comment.