feat: extend email support for SSL/No Auth Email Support (#1235)

* Changes Settings to use new SMTP_AUTH_STRATEGY variable in place of SMTP_TLS with transition support

#1187

* Wires up default email client to use ssl or tls authentication if enabled in settings

* Updates the docs

* Update template file

* remove SMTP_TLS and use staticmethod for validate

* consolidate test cases with params

Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
This commit is contained in:
Nick Kringle 2022-05-21 14:15:14 -05:00 committed by GitHub
parent b2066dfe72
commit 6a88a59981
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 98 additions and 46 deletions

View File

@ -63,13 +63,14 @@ services:
# ===================================== # =====================================
# Email Configuration # Email Configuration
# - SMTP_HOST= # SMTP_HOST=
# - SMTP_PORT=587 # SMTP_PORT=587
# - SMTP_FROM_NAME=Mealie # SMTP_FROM_NAME=Mealie
# - SMTP_TLS=true # SMTP_AUTH_STRATEGY=TLS # Options: 'TLS', 'SSL', 'NONE'
# - SMTP_FROM_EMAIL= # SMTP_FROM_EMAIL=
# - SMTP_USER= # SMTP_USER=
# - SMTP_PASSWORD= # SMTP_PASSWORD=
# postgres: # postgres:
# container_name: postgres # container_name: postgres
# image: postgres # image: postgres

View File

@ -35,14 +35,14 @@
### Email ### Email
| Variables | Default | Description | | Variables | Default | Description |
| --------------- | :-----: | ------------------ | | ------------------ | :-----: | ------------------------------------------------- |
| SMTP_HOST | None | Required For email | | SMTP_HOST | None | Required For email |
| SMTP_PORT | 587 | Required For email | | SMTP_PORT | 587 | Required For email |
| SMTP_FROM_NAME | Mealie | Required For email | | SMTP_FROM_NAME | Mealie | Required For email |
| SMTP_TLS | true | Required For email | | SMTP_AUTH_STRATEGY | TLS | Required For email, Options: 'TLS', 'SSL', 'NONE' |
| SMTP_FROM_EMAIL | None | Required For email | | SMTP_FROM_EMAIL | None | Required For email |
| SMTP_USER | None | Required For email | | SMTP_USER | None | Required if SMTP_AUTH_STRATEGY is 'TLS' or 'SSL' |
| SMTP_PASSWORD | None | Required For email | | SMTP_PASSWORD | None | Required if SMTP_AUTH_STRATEGY is 'TLS' or 'SSL' |
### Webworker ### Webworker
Changing the webworker settings may cause unforeseen memory leak issues with Mealie. It's best to leave these at the defaults unless you begin to experience issues with multiple users. Exercise caution when changing these settings Changing the webworker settings may cause unforeseen memory leak issues with Mealie. It's best to leave these at the defaults unless you begin to experience issues with multiple users. Exercise caution when changing these settings

View File

@ -68,23 +68,39 @@ class AppSettings(BaseSettings):
SMTP_HOST: Optional[str] SMTP_HOST: Optional[str]
SMTP_PORT: Optional[str] = "587" SMTP_PORT: Optional[str] = "587"
SMTP_FROM_NAME: Optional[str] = "Mealie" SMTP_FROM_NAME: Optional[str] = "Mealie"
SMTP_TLS: Optional[bool] = True
SMTP_FROM_EMAIL: Optional[str] SMTP_FROM_EMAIL: Optional[str]
SMTP_USER: Optional[str] SMTP_USER: Optional[str]
SMTP_PASSWORD: Optional[str] SMTP_PASSWORD: Optional[str]
SMTP_AUTH_STRATEGY: Optional[str] = "TLS" # Options: 'TLS', 'SSL', 'NONE'
@property @property
def SMTP_ENABLE(self) -> bool: def SMTP_ENABLE(self) -> bool:
"""Validates all SMTP variables are set""" return AppSettings.validate_smtp(
required = {
self.SMTP_HOST, self.SMTP_HOST,
self.SMTP_PORT, self.SMTP_PORT,
self.SMTP_FROM_NAME, self.SMTP_FROM_NAME,
self.SMTP_TLS,
self.SMTP_FROM_EMAIL, self.SMTP_FROM_EMAIL,
self.SMTP_AUTH_STRATEGY,
self.SMTP_USER, self.SMTP_USER,
self.SMTP_PASSWORD, self.SMTP_PASSWORD,
} )
@staticmethod
def validate_smtp(
host: str,
port: str,
from_name: str,
from_email: str,
strategy: str,
user: str | None = None,
password: str | None = None,
) -> bool:
"""Validates all SMTP variables are set"""
required = {host, port, from_name, from_email, strategy}
if strategy.upper() in {"TLS", "SSL"}:
required.add(user)
required.add(password)
return "" not in required and None not in required return "" not in required and None not in required

View File

@ -24,8 +24,10 @@ class DefaultEmailSender(ABCEmailSender, BaseService):
) )
smtp_options: dict[str, str | bool] = {"host": self.settings.SMTP_HOST, "port": self.settings.SMTP_PORT} smtp_options: dict[str, str | bool] = {"host": self.settings.SMTP_HOST, "port": self.settings.SMTP_PORT}
if self.settings.SMTP_TLS: if self.settings.SMTP_AUTH_STRATEGY.upper() == "TLS":
smtp_options["tls"] = True smtp_options["tls"] = True
if self.settings.SMTP_AUTH_STRATEGY.upper() == "SSL":
smtp_options["ssl"] = True
if self.settings.SMTP_USER: if self.settings.SMTP_USER:
smtp_options["user"] = self.settings.SMTP_USER smtp_options["user"] = self.settings.SMTP_USER
if self.settings.SMTP_PASSWORD: if self.settings.SMTP_PASSWORD:

View File

@ -29,7 +29,7 @@ LANG=en-US
# SMTP_HOST="" # SMTP_HOST=""
# SMTP_PORT="" # SMTP_PORT=""
# SMTP_FROM_NAME="" # SMTP_FROM_NAME=""
# SMTP_TLS="" # SMTP_AUTH_STRATEGY="" # Options: 'TLS', 'SSL', 'NONE'
# SMTP_FROM_EMAIL="" # SMTP_FROM_EMAIL=""
# SMTP_USER="" # SMTP_USER=""
# SMTP_PASSWORD="" # SMTP_PASSWORD=""
@ -39,4 +39,3 @@ LDAP_AUTH_ENABLED=False
LDAP_SERVER_URL=None LDAP_SERVER_URL=None
LDAP_BIND_TEMPLATE=None LDAP_BIND_TEMPLATE=None
LDAP_ADMIN_FILTER=None LDAP_ADMIN_FILTER=None

View File

@ -28,7 +28,7 @@ class TestEmailSender(ABCEmailSender):
def patch_env(monkeypatch): def patch_env(monkeypatch):
monkeypatch.setenv("SMTP_HOST", "email.mealie.io") monkeypatch.setenv("SMTP_HOST", "email.mealie.io")
monkeypatch.setenv("SMTP_PORT", "587") monkeypatch.setenv("SMTP_PORT", "587")
monkeypatch.setenv("SMTP_TLS", "True") monkeypatch.setenv("SMTP_AUTH_STRATEGY", "TLS")
monkeypatch.setenv("SMTP_FROM_NAME", "Mealie") monkeypatch.setenv("SMTP_FROM_NAME", "Mealie")
monkeypatch.setenv("SMTP_FROM_EMAIL", "mealie@mealie.io") monkeypatch.setenv("SMTP_FROM_EMAIL", "mealie@mealie.io")
monkeypatch.setenv("SMTP_USER", "mealie@mealie.io") monkeypatch.setenv("SMTP_USER", "mealie@mealie.io")

View File

@ -1,6 +1,10 @@
import re import re
from dataclasses import dataclass
import pytest
from mealie.core.config import get_app_settings from mealie.core.config import get_app_settings
from mealie.core.settings.settings import AppSettings
def test_non_default_settings(monkeypatch): def test_non_default_settings(monkeypatch):
@ -36,29 +40,59 @@ def test_pg_connection_args(monkeypatch):
assert app_settings.DB_URL == "postgresql://mealie:mealie@postgres:5432/mealie" assert app_settings.DB_URL == "postgresql://mealie:mealie@postgres:5432/mealie"
def test_smtp_enable(monkeypatch): @dataclass(slots=True)
monkeypatch.setenv("SMTP_HOST", "") class SMTPValidationCase:
monkeypatch.setenv("SMTP_PORT", "") host: str
monkeypatch.setenv("SMTP_TLS", "true") port: str
monkeypatch.setenv("SMTP_FROM_NAME", "") auth_strategy: str
monkeypatch.setenv("SMTP_FROM_EMAIL", "") from_name: str
monkeypatch.setenv("SMTP_USER", "") from_email: str
monkeypatch.setenv("SMTP_PASSWORD", "") user: str
password: str
is_valid: bool
get_app_settings.cache_clear()
app_settings = get_app_settings()
assert app_settings.SMTP_ENABLE is False smtp_validation_cases = [
(
"bad_data_tls",
SMTPValidationCase("", "", "tls", "", "", "", "", False),
),
(
"bad_data_ssl",
SMTPValidationCase("", "", "ssl", "", "", "", "", False),
),
(
"no_auth",
SMTPValidationCase("email.mealie.io", "25", "none", "Mealie", "mealie@mealie.io", "", "", True),
),
(
"good_data_tls",
SMTPValidationCase(
"email.mealie.io", "587", "tls", "Mealie", "mealie@mealie.io", "mealie@mealie.io", "mealie-password", True
),
),
(
"good_data_ssl",
SMTPValidationCase(
"email.mealie.io", "465", "tls", "Mealie", "mealie@mealie.io", "mealie@mealie.io", "mealie-password", True
),
),
]
monkeypatch.setenv("SMTP_HOST", "email.mealie.io") smtp_cases = [x[1] for x in smtp_validation_cases]
monkeypatch.setenv("SMTP_PORT", "587") smtp_cases_ids = [x[0] for x in smtp_validation_cases]
monkeypatch.setenv("SMTP_TLS", "true")
monkeypatch.setenv("SMTP_FROM_NAME", "Mealie")
monkeypatch.setenv("SMTP_FROM_EMAIL", "mealie@mealie.io")
monkeypatch.setenv("SMTP_USER", "mealie@mealie.io")
monkeypatch.setenv("SMTP_PASSWORD", "mealie-password")
get_app_settings.cache_clear()
app_settings = get_app_settings()
assert app_settings.SMTP_ENABLE is True @pytest.mark.parametrize("data", smtp_cases, ids=smtp_cases_ids)
def test_smtp_enable_with_bad_data_tls(data: SMTPValidationCase):
is_valid = AppSettings.validate_smtp(
data.host,
data.port,
data.from_name,
data.from_email,
data.auth_strategy,
data.user,
data.password,
)
assert is_valid is data.is_valid