From 21c5eae97dd65f55d7a9becac44633d0164726cf Mon Sep 17 00:00:00 2001 From: Evgeniy Alekseev Date: Tue, 6 Apr 2021 05:34:26 +0300 Subject: [PATCH] improved ssl mode --- CONFIGURING.md | 2 +- src/ahriman/core/report/email.py | 14 ++++--- src/ahriman/models/smtp_ssl_settings.py | 49 ++++++++++++++++++++++ tests/ahriman/core/report/test_email.py | 21 ++++++++-- tests/ahriman/models/test_smtp_settings.py | 21 ++++++++++ 5 files changed, 98 insertions(+), 9 deletions(-) create mode 100644 src/ahriman/models/smtp_ssl_settings.py create mode 100644 tests/ahriman/models/test_smtp_settings.py diff --git a/CONFIGURING.md b/CONFIGURING.md index d20ef7ae..e0421ccd 100644 --- a/CONFIGURING.md +++ b/CONFIGURING.md @@ -60,8 +60,8 @@ Group name must refer to architecture, e.g. it should be `email:x86_64` for x86_ * `port` - SMTP port for sending emails, int, required. * `receivers` - SMTP receiver addresses, space separated list of strings, required. * `sender` - SMTP sender address, string, required. +* `ssl` - SSL mode for SMTP connection, one of `ssl`, `starttls`, `disabled`, optional, default `disabled`. * `template_path` - path to Jinja2 template, string, required. -* `use_tls` - use TLS for connection, boolean, optional, default False. * `user` - SMTP user to authenticate, string, optional. ### `html:*` groups diff --git a/src/ahriman/core/report/email.py b/src/ahriman/core/report/email.py index e1db381e..af42e0ad 100644 --- a/src/ahriman/core/report/email.py +++ b/src/ahriman/core/report/email.py @@ -29,6 +29,7 @@ from ahriman.core.report.jinja_template import JinjaTemplate from ahriman.core.report.report import Report from ahriman.core.util import pretty_datetime from ahriman.models.package import Package +from ahriman.models.smtp_ssl_settings import SmtpSSLSettings class Email(Report, JinjaTemplate): @@ -39,7 +40,7 @@ class Email(Report, JinjaTemplate): :ivar port: SMTP port to connect :ivar receivers: list of receivers emails :ivar sender: sender email address - :ivar use_tls: use TLS for SMTP connection + :ivar ssl: SSL mode for SMTP connection :ivar user: username to authenticate via SMTP """ @@ -58,7 +59,7 @@ class Email(Report, JinjaTemplate): self.port = configuration.getint("email", "port") self.receivers = configuration.getlist("email", "receivers") self.sender = configuration.get("email", "sender") - self.use_tls = configuration.getboolean("email", "use_tls", fallback=False) + self.ssl = SmtpSSLSettings.from_option(configuration.get("email", "ssl", fallback="disabled")) self.user = configuration.get("email", "user", fallback=None) def _send(self, text: str, attachment: Dict[str, str]) -> None: @@ -78,9 +79,12 @@ class Email(Report, JinjaTemplate): attach.add_header("Content-Disposition", "attachment", filename=filename) message.attach(attach) - session = smtplib.SMTP(self.host, self.port) - if self.use_tls: - session.starttls() + if self.ssl != SmtpSSLSettings.SSL: + session = smtplib.SMTP(self.host, self.port) + if self.ssl == SmtpSSLSettings.STARTTLS: + session.starttls() + else: + session = smtplib.SMTP_SSL(self.host, self.port) if self.user is not None and self.password is not None: session.login(self.user, self.password) session.sendmail(self.sender, self.receivers, message.as_string()) diff --git a/src/ahriman/models/smtp_ssl_settings.py b/src/ahriman/models/smtp_ssl_settings.py new file mode 100644 index 00000000..473a9297 --- /dev/null +++ b/src/ahriman/models/smtp_ssl_settings.py @@ -0,0 +1,49 @@ +# +# Copyright (c) 2021 ahriman team. +# +# This file is part of ahriman +# (see https://github.com/arcan1s/ahriman). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +from __future__ import annotations + +from enum import Enum, auto +from typing import Type + + +class SmtpSSLSettings(Enum): + """ + SMTP SSL mode enumeration + :cvar Disabled: no SSL enabled + :cvar SSL: use SMTP_SSL instead of normal SMTP client + :cvar STARTTLS: use STARTTLS in normal SMTP client + """ + + Disabled = auto() + SSL = auto() + STARTTLS = auto() + + @classmethod + def from_option(cls: Type[SmtpSSLSettings], value: str) -> SmtpSSLSettings: + """ + construct value from configuration + :param value: configuration value + :return: parsed value + """ + if value.lower() in ("ssl", "ssl/tls"): + return cls.SSL + if value.lower() in ("starttls",): + return cls.STARTTLS + return cls.Disabled diff --git a/tests/ahriman/core/report/test_email.py b/tests/ahriman/core/report/test_email.py index 29b38842..26d883c7 100644 --- a/tests/ahriman/core/report/test_email.py +++ b/tests/ahriman/core/report/test_email.py @@ -56,11 +56,26 @@ def test_send_auth_no_user(configuration: Configuration, mocker: MockerFixture) smtp_mock.return_value.login.assert_not_called() -def test_send_tls(configuration: Configuration, mocker: MockerFixture) -> None: +def test_send_ssl_tls(configuration: Configuration, mocker: MockerFixture) -> None: """ - must send an email with attachment with tls + must send an email with attachment with ssl/tls """ - configuration.set("email", "use_tls", "yes") + configuration.set("email", "ssl", "ssl") + smtp_mock = mocker.patch("smtplib.SMTP_SSL") + + report = Email("x86_64", configuration) + report._send("a text", {"attachment.html": "an attachment"}) + smtp_mock.return_value.starttls.assert_not_called() + smtp_mock.return_value.login.assert_not_called() + smtp_mock.return_value.sendmail.assert_called_once() + smtp_mock.return_value.quit.assert_called_once() + + +def test_send_starttls(configuration: Configuration, mocker: MockerFixture) -> None: + """ + must send an email with attachment with starttls + """ + configuration.set("email", "ssl", "starttls") smtp_mock = mocker.patch("smtplib.SMTP") report = Email("x86_64", configuration) diff --git a/tests/ahriman/models/test_smtp_settings.py b/tests/ahriman/models/test_smtp_settings.py new file mode 100644 index 00000000..06bd0c7d --- /dev/null +++ b/tests/ahriman/models/test_smtp_settings.py @@ -0,0 +1,21 @@ +from ahriman.models.smtp_ssl_settings import SmtpSSLSettings + + +def test_from_option_invalid() -> None: + """ + must return disabled value on invalid option + """ + assert SmtpSSLSettings.from_option("invalid") == SmtpSSLSettings.Disabled + + +def test_from_option_valid() -> None: + """ + must return value from valid options + """ + assert SmtpSSLSettings.from_option("ssl") == SmtpSSLSettings.SSL + assert SmtpSSLSettings.from_option("SSL") == SmtpSSLSettings.SSL + assert SmtpSSLSettings.from_option("ssl/tls") == SmtpSSLSettings.SSL + assert SmtpSSLSettings.from_option("SSL/TLS") == SmtpSSLSettings.SSL + + assert SmtpSSLSettings.from_option("starttls") == SmtpSSLSettings.STARTTLS + assert SmtpSSLSettings.from_option("STARTTLS") == SmtpSSLSettings.STARTTLS