From edd79dca9acc2716ba906c8ae49e1dc1af3a8e1f Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Sun, 20 Dec 2020 14:49:43 +0100 Subject: [PATCH] Added email client --- src/setup.py | 3 +- src/sh_edraft/mailing/base/__init__.py | 0 .../mailing/base/email_client_base.py | 17 ++++ src/sh_edraft/mailing/email_client.py | 71 +++++++++++++++ src/sh_edraft/mailing/model/__init__.py | 0 src/sh_edraft/mailing/model/email.py | 86 +++++++++++++++++++ .../mailing/model/email_client_settings.py | 59 +++++++++++++ .../model/email_client_settings_name.py | 9 ++ src/tests_dev/appsettings.edrafts-pc.json | 6 ++ src/tests_dev/program.py | 36 ++++---- 10 files changed, 266 insertions(+), 21 deletions(-) create mode 100644 src/sh_edraft/mailing/base/__init__.py create mode 100644 src/sh_edraft/mailing/base/email_client_base.py create mode 100644 src/sh_edraft/mailing/email_client.py create mode 100644 src/sh_edraft/mailing/model/__init__.py create mode 100644 src/sh_edraft/mailing/model/email.py create mode 100644 src/sh_edraft/mailing/model/email_client_settings.py create mode 100644 src/sh_edraft/mailing/model/email_client_settings_name.py diff --git a/src/setup.py b/src/setup.py index 748df92d..a2e40b12 100644 --- a/src/setup.py +++ b/src/setup.py @@ -18,7 +18,8 @@ setuptools.setup( 'SQLAlchemy', 'termcolor', 'pyfiglet', - 'tabulate' + 'tabulate', + 'smtplib' ], entry_points={ 'console_scripts': [ diff --git a/src/sh_edraft/mailing/base/__init__.py b/src/sh_edraft/mailing/base/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/sh_edraft/mailing/base/email_client_base.py b/src/sh_edraft/mailing/base/email_client_base.py new file mode 100644 index 00000000..bbb98879 --- /dev/null +++ b/src/sh_edraft/mailing/base/email_client_base.py @@ -0,0 +1,17 @@ +from abc import abstractmethod + +from sh_edraft.mailing.model.email import EMail +from sh_edraft.service.base.service_base import ServiceBase + + +class EMailClientBase(ServiceBase): + + @abstractmethod + def __init__(self): + ServiceBase.__init__(self) + + @abstractmethod + def connect(self): pass + + @abstractmethod + def send_mail(self, email: EMail): pass diff --git a/src/sh_edraft/mailing/email_client.py b/src/sh_edraft/mailing/email_client.py new file mode 100644 index 00000000..cc6296ee --- /dev/null +++ b/src/sh_edraft/mailing/email_client.py @@ -0,0 +1,71 @@ +import ssl +from smtplib import SMTP +from typing import Optional + +from sh_edraft.environment.base.environment_base import EnvironmentBase +from sh_edraft.logging.base.logger_base import LoggerBase +from sh_edraft.mailing.base.email_client_base import EMailClientBase +from sh_edraft.mailing.model.email import EMail +from sh_edraft.mailing.model.email_client_settings import EMailClientSettings +from sh_edraft.service.base.service_base import ServiceBase +from sh_edraft.utils.credential_manager import CredentialManager + + +class EMailClient(EMailClientBase): + + def __init__(self, environment: EnvironmentBase, logger: LoggerBase, mail_settings: EMailClientSettings): + ServiceBase.__init__(self) + + self._environment = environment + self._mail_settings = mail_settings + self._logger = logger + + self._server: Optional[SMTP] = None + + self.create() + + def create(self): + self._logger.trace(__name__, f'Started {__name__}.create') + self.connect() + self._logger.trace(__name__, f'Stopped {__name__}.create') + + def connect(self): + self._logger.trace(__name__, f'Started {__name__}.connect') + try: + self._logger.debug(__name__, f'Try to connect to {self._mail_settings.host}:{self._mail_settings.port}') + self._server = SMTP(self._mail_settings.host, self._mail_settings.port) + self._logger.info(__name__, f'Connected to {self._mail_settings.host}:{self._mail_settings.port}') + + self._logger.debug(__name__, 'Try to start tls') + self._server.starttls(context=ssl.create_default_context()) + self._logger.info(__name__, 'Started tls') + except Exception as e: + self._logger.error(__name__, 'Cannot connect to mail server', e) + + self._logger.trace(__name__, f'Stopped {__name__}.connect') + + def login(self): + self._logger.trace(__name__, f'Started {__name__}.login') + try: + self._logger.debug(__name__, f'Try to login {self._mail_settings.user_name}@{self._mail_settings.host}:{self._mail_settings.port}') + self._server.login(self._mail_settings.user_name, CredentialManager.decrypt(self._mail_settings.credentials)) + self._logger.info(__name__, f'Logged on as {self._mail_settings.user_name} to {self._mail_settings.host}:{self._mail_settings.port}') + except Exception as e: + self._logger.error(__name__, 'Cannot login to mail server', e) + + self._logger.trace(__name__, f'Stopped {__name__}.login') + + def send_mail(self, email: EMail): + self._logger.trace(__name__, f'Started {__name__}.send_mail') + try: + self.login() + email.body += f'\n\nDies ist eine automatische E-Mail.' \ + f'\nGesendet von {self._environment.application_name}-{self._environment.environment_name}@{self._environment.host_name} für ' \ + f'{self._environment.customer}.' + + self._logger.debug(__name__, f'Try to send email to {email.receiver_list}') + self._server.sendmail(self._mail_settings.user_name, email.receiver_list, email.get_content(self._mail_settings.user_name)) + self._logger.info(__name__, f'Sent email to {email.receiver_list}') + except Exception as e: + self._logger.error(__name__, f'Cannot send mail to {email.receiver_list}', e) + self._logger.trace(__name__, f'Stopped {__name__}.send_mail') diff --git a/src/sh_edraft/mailing/model/__init__.py b/src/sh_edraft/mailing/model/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/sh_edraft/mailing/model/email.py b/src/sh_edraft/mailing/model/email.py new file mode 100644 index 00000000..de49639d --- /dev/null +++ b/src/sh_edraft/mailing/model/email.py @@ -0,0 +1,86 @@ +import re + + +class EMail: + + def __init__(self, header: list[str] = None, subject: str = None, body: str = None, transceiver: str = None, receiver: list[str] = None): + self._header: list[str] = header + + self._subject: str = subject + self._body: str = body + + self._transceiver: str = transceiver + self._receiver: list[str] = receiver + + @property + def header(self) -> str: + return '\r\n'.join(self._header) + + @property + def header_list(self) -> list[str]: + return self._header + + @header.setter + def header(self, header: list[str]): + self._header = header + + @property + def subject(self) -> str: + return self._subject + + @subject.setter + def subject(self, subject: str): + self._subject = subject + + @property + def body(self) -> str: + return self._body + + @body.setter + def body(self, body: str): + self._body = body + + @property + def transceiver(self) -> str: + return self._transceiver + + @transceiver.setter + def transceiver(self, transceiver: str): + if self.check_mail(transceiver): + self._transceiver = transceiver + else: + raise Exception(f'Invalid email: {transceiver}') + + @property + def receiver(self) -> str: + return ','.join(self._receiver) + + @property + def receiver_list(self) -> list[str]: + return self._receiver + + @receiver.setter + def receiver(self, receiver: list[str]): + self._receiver = receiver + + @staticmethod + def check_mail(address: str) -> bool: + return bool(re.search('^\\w+([.-]?\\w+)*@\\w+([.-]?\\w+)*(.\\w{2,3})+$', address)) + + def add_header(self, header: str): + if self._header is None: + self._header = [] + + self._header.append(header) + + def add_receiver(self, receiver: str): + if self._receiver is None: + self._receiver = [] + + if self.check_mail(receiver): + self._receiver.append(receiver) + else: + raise Exception(f'Invalid email: {receiver}') + + def get_content(self, transceiver: str): + return str(f'From: {transceiver}\r\nTo: {self.receiver}\r\n{self.header}\r\nSubject: {self.subject}\r\n{self.body}').encode('utf-8') diff --git a/src/sh_edraft/mailing/model/email_client_settings.py b/src/sh_edraft/mailing/model/email_client_settings.py new file mode 100644 index 00000000..d9755919 --- /dev/null +++ b/src/sh_edraft/mailing/model/email_client_settings.py @@ -0,0 +1,59 @@ +import traceback + +from sh_edraft.configuration.base.configuration_model_base import ConfigurationModelBase +from sh_edraft.console.console import Console +from sh_edraft.mailing.model.email_client_settings_name import EMailClientSettingsName + + +class EMailClientSettings(ConfigurationModelBase): + + def __init__(self): + ConfigurationModelBase.__init__(self) + + self._host: str = '' + self._port: int = 0 + self._user_name: str = '' + self._credentials: str = '' + + @property + def host(self) -> str: + return self._host + + @host.setter + def host(self, host: str) -> None: + self._host = host + + @property + def port(self) -> int: + return self._port + + @port.setter + def port(self, port: int) -> None: + self._port = port + + @property + def user_name(self) -> str: + return self._user_name + + @user_name.setter + def user_name(self, user_name: str) -> None: + self._user_name = user_name + + @property + def credentials(self) -> str: + return self._credentials + + @credentials.setter + def credentials(self, credentials: str) -> None: + self._credentials = credentials + + def from_dict(self, settings: dict): + try: + self._host = settings[EMailClientSettingsName.host.value] + self._port = settings[EMailClientSettingsName.port.value] + self._user_name = settings[EMailClientSettingsName.user_name.value] + self._credentials = settings[EMailClientSettingsName.credentials.value] + except Exception as e: + Console.error(f'[ ERROR ] [ {__name__} ]: Reading error in {self.__name__} settings') + Console.error(f'[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}') + diff --git a/src/sh_edraft/mailing/model/email_client_settings_name.py b/src/sh_edraft/mailing/model/email_client_settings_name.py new file mode 100644 index 00000000..665c64b6 --- /dev/null +++ b/src/sh_edraft/mailing/model/email_client_settings_name.py @@ -0,0 +1,9 @@ +from enum import Enum + + +class EMailClientSettingsName(Enum): + + host = 'Host' + port = 'Port' + user_name = 'UserName' + credentials = 'Credentials' diff --git a/src/tests_dev/appsettings.edrafts-pc.json b/src/tests_dev/appsettings.edrafts-pc.json index 3fb75c62..0b2e194a 100644 --- a/src/tests_dev/appsettings.edrafts-pc.json +++ b/src/tests_dev/appsettings.edrafts-pc.json @@ -11,6 +11,12 @@ "ConsoleLogLevel": "TRACE", "FileLogLevel": "TRACE" }, + "EMailClientSettings": { + "Host": "mail.sh-edraft.de", + "Port": "587", + "UserName": "dev-srv@sh-edraft.de", + "Credentials": "RmBOQX1eNFYiYjgsSid3fV1nelc2WA==" + }, "PublishSettings": { "SourcePath": "../", "DistPath": "../../dist", diff --git a/src/tests_dev/program.py b/src/tests_dev/program.py index 391a44ef..d957bc49 100644 --- a/src/tests_dev/program.py +++ b/src/tests_dev/program.py @@ -1,13 +1,15 @@ from typing import Optional from sh_edraft.configuration.base import ConfigurationBase -from sh_edraft.console import Console from sh_edraft.database.context import DatabaseContext from sh_edraft.database.model import DatabaseSettings from sh_edraft.hosting import ApplicationHost from sh_edraft.hosting.base import ApplicationBase from sh_edraft.logging import Logger from sh_edraft.logging.base import LoggerBase +from sh_edraft.mailing.base.email_client_base import EMailClientBase +from sh_edraft.mailing.email_client import EMailClient +from sh_edraft.mailing.model.email import EMail from sh_edraft.service.providing.base import ServiceProviderBase from sh_edraft.utils import CredentialManager @@ -24,6 +26,7 @@ class Program(ApplicationBase): self._services: Optional[ServiceProviderBase] = None self._configuration: Optional[ConfigurationBase] = None self._logger: Optional[LoggerBase] = None + self._mailer: Optional[EMailClientBase] = None def create_application_host(self): self._app_host = ApplicationHost() @@ -51,28 +54,21 @@ class Program(ApplicationBase): self._services.add_singleton(LoggerBase, Logger) self._logger = self._services.get_service(LoggerBase) + self._services.add_singleton(EMailClientBase, EMailClient) + self._mailer = self._services.get_service(EMailClientBase) + def main(self): self._logger.header(f'{self._configuration.environment.application_name}:') self._logger.debug(__name__, f'Host: {self._configuration.environment.host_name}') self._logger.debug(__name__, f'Environment: {self._configuration.environment.environment_name}') self._logger.debug(__name__, f'Customer: {self._configuration.environment.customer}') self._services.get_service(UserRepoBase).add_test_user() - - Console.clear() - Console.write_line('Hello', 'World') - # name = Console.read_line('Name: ') - # Console.write_line('Hello', name) - Console.set_foreground_color('red') - Console.set_background_color('green') - Console.set_cursor_position(5, 5) - Console.write_line('Error') - Console.write_line_at(10, 5, 'Error') - Console.write_at(15, 5, 'Error') - Console.reset_cursor_position() - Console.set_foreground_color('green') - Console.set_background_color('default') - Console.write_line('Test') - Console.write('1') - # Console.write('x: ') - # Console.read_line('Test> ') - Console.write_line(Console.foreground_color) + mail = EMail() + mail.add_header('Mime-Version: 1.0') + mail.add_header('Content-Type: text/plain; charset=utf-8') + mail.add_header('Content-Transfer-Encoding: quoted-printable') + mail.add_receiver('edraft.sh@gmail.com') + mail.add_receiver('edraft@sh-edraft.de') + mail.subject = f'Test - {self._configuration.environment.host_name}' + mail.body = 'Dies ist ein Test :D' + self._mailer.send_mail(mail)