mirror of https://github.com/jumpserver/jumpserver
				
				
				
			perf: 邮箱支持exchange协议
							parent
							
								
									2a29cd0e70
								
							
						
					
					
						commit
						9ede3670a7
					
				| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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'),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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__)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue