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
# - SMTP_HOST=
# - SMTP_PORT=587
# - SMTP_FROM_NAME=Mealie
# - SMTP_TLS=true
# - SMTP_FROM_EMAIL=
# - SMTP_USER=
# - SMTP_PASSWORD=
# SMTP_HOST=
# SMTP_PORT=587
# SMTP_FROM_NAME=Mealie
# SMTP_AUTH_STRATEGY=TLS # Options: 'TLS', 'SSL', 'NONE'
# SMTP_FROM_EMAIL=
# SMTP_USER=
# SMTP_PASSWORD=
# postgres:
# container_name: postgres
# image: postgres

View File

@ -34,15 +34,15 @@
### Email
| Variables | Default | Description |
| --------------- | :-----: | ------------------ |
| SMTP_HOST | None | Required For email |
| SMTP_PORT | 587 | Required For email |
| SMTP_FROM_NAME | Mealie | Required For email |
| SMTP_TLS | true | Required For email |
| SMTP_FROM_EMAIL | None | Required For email |
| SMTP_USER | None | Required For email |
| SMTP_PASSWORD | None | Required For email |
| Variables | Default | Description |
| ------------------ | :-----: | ------------------------------------------------- |
| SMTP_HOST | None | Required For email |
| SMTP_PORT | 587 | Required For email |
| SMTP_FROM_NAME | Mealie | Required For email |
| SMTP_AUTH_STRATEGY | TLS | Required For email, Options: 'TLS', 'SSL', 'NONE' |
| SMTP_FROM_EMAIL | None | Required For email |
| SMTP_USER | None | Required if SMTP_AUTH_STRATEGY is 'TLS' or 'SSL' |
| SMTP_PASSWORD | None | Required if SMTP_AUTH_STRATEGY is 'TLS' or 'SSL' |
### 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

View File

@ -68,23 +68,39 @@ class AppSettings(BaseSettings):
SMTP_HOST: Optional[str]
SMTP_PORT: Optional[str] = "587"
SMTP_FROM_NAME: Optional[str] = "Mealie"
SMTP_TLS: Optional[bool] = True
SMTP_FROM_EMAIL: Optional[str]
SMTP_USER: Optional[str]
SMTP_PASSWORD: Optional[str]
SMTP_AUTH_STRATEGY: Optional[str] = "TLS" # Options: 'TLS', 'SSL', 'NONE'
@property
def SMTP_ENABLE(self) -> bool:
"""Validates all SMTP variables are set"""
required = {
return AppSettings.validate_smtp(
self.SMTP_HOST,
self.SMTP_PORT,
self.SMTP_FROM_NAME,
self.SMTP_TLS,
self.SMTP_FROM_EMAIL,
self.SMTP_AUTH_STRATEGY,
self.SMTP_USER,
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

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}
if self.settings.SMTP_TLS:
if self.settings.SMTP_AUTH_STRATEGY.upper() == "TLS":
smtp_options["tls"] = True
if self.settings.SMTP_AUTH_STRATEGY.upper() == "SSL":
smtp_options["ssl"] = True
if self.settings.SMTP_USER:
smtp_options["user"] = self.settings.SMTP_USER
if self.settings.SMTP_PASSWORD:

View File

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

View File

@ -28,7 +28,7 @@ class TestEmailSender(ABCEmailSender):
def patch_env(monkeypatch):
monkeypatch.setenv("SMTP_HOST", "email.mealie.io")
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_EMAIL", "mealie@mealie.io")
monkeypatch.setenv("SMTP_USER", "mealie@mealie.io")

View File

@ -1,6 +1,10 @@
import re
from dataclasses import dataclass
import pytest
from mealie.core.config import get_app_settings
from mealie.core.settings.settings import AppSettings
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"
def test_smtp_enable(monkeypatch):
monkeypatch.setenv("SMTP_HOST", "")
monkeypatch.setenv("SMTP_PORT", "")
monkeypatch.setenv("SMTP_TLS", "true")
monkeypatch.setenv("SMTP_FROM_NAME", "")
monkeypatch.setenv("SMTP_FROM_EMAIL", "")
monkeypatch.setenv("SMTP_USER", "")
monkeypatch.setenv("SMTP_PASSWORD", "")
@dataclass(slots=True)
class SMTPValidationCase:
host: str
port: str
auth_strategy: str
from_name: str
from_email: str
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")
monkeypatch.setenv("SMTP_PORT", "587")
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")
smtp_cases = [x[1] for x in smtp_validation_cases]
smtp_cases_ids = [x[0] for x in smtp_validation_cases]
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