Do not allow smtp to access insecure files (#104972)

This commit is contained in:
Jan Bouwhuis 2023-12-04 02:06:01 +01:00 committed by GitHub
parent c1f68c3767
commit fe2906f159
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 53 additions and 9 deletions

View File

@ -8,6 +8,7 @@ from email.mime.text import MIMEText
import email.utils
import logging
import os
from pathlib import Path
import smtplib
import voluptuous as vol
@ -193,10 +194,15 @@ class MailNotificationService(BaseNotificationService):
if data := kwargs.get(ATTR_DATA):
if ATTR_HTML in data:
msg = _build_html_msg(
message, data[ATTR_HTML], images=data.get(ATTR_IMAGES, [])
self.hass,
message,
data[ATTR_HTML],
images=data.get(ATTR_IMAGES, []),
)
else:
msg = _build_multipart_msg(message, images=data.get(ATTR_IMAGES, []))
msg = _build_multipart_msg(
self.hass, message, images=data.get(ATTR_IMAGES, [])
)
else:
msg = _build_text_msg(message)
@ -241,13 +247,21 @@ def _build_text_msg(message):
return MIMEText(message)
def _attach_file(atch_name, content_id=""):
def _attach_file(hass, atch_name, content_id=""):
"""Create a message attachment.
If MIMEImage is successful and content_id is passed (HTML), add images in-line.
Otherwise add them as attachments.
"""
try:
file_path = Path(atch_name).parent
if not hass.config.is_allowed_path(str(file_path)):
_LOGGER.warning(
"'%s' is not secure to load data from, ignoring attachment '%s'!",
file_path,
atch_name,
)
return
with open(atch_name, "rb") as attachment_file:
file_bytes = attachment_file.read()
except FileNotFoundError:
@ -277,22 +291,22 @@ def _attach_file(atch_name, content_id=""):
return attachment
def _build_multipart_msg(message, images):
def _build_multipart_msg(hass, message, images):
"""Build Multipart message with images as attachments."""
_LOGGER.debug("Building multipart email with image attachment(s)")
_LOGGER.debug("Building multipart email with image attachme_build_html_msgnt(s)")
msg = MIMEMultipart()
body_txt = MIMEText(message)
msg.attach(body_txt)
for atch_name in images:
attachment = _attach_file(atch_name)
attachment = _attach_file(hass, atch_name)
if attachment:
msg.attach(attachment)
return msg
def _build_html_msg(text, html, images):
def _build_html_msg(hass, text, html, images):
"""Build Multipart message with in-line images and rich HTML (UTF-8)."""
_LOGGER.debug("Building HTML rich email")
msg = MIMEMultipart("related")
@ -303,7 +317,7 @@ def _build_html_msg(text, html, images):
for atch_name in images:
name = os.path.basename(atch_name)
attachment = _attach_file(atch_name, name)
attachment = _attach_file(hass, atch_name, name)
if attachment:
msg.attach(attachment)
return msg

View File

@ -1,4 +1,5 @@
"""The tests for the notify smtp platform."""
from pathlib import Path
import re
from unittest.mock import patch
@ -132,15 +133,44 @@ EMAIL_DATA = [
],
)
def test_send_message(
message_data, data, content_type, hass: HomeAssistant, message
hass: HomeAssistant, message_data, data, content_type, message
) -> None:
"""Verify if we can send messages of all types correctly."""
sample_email = "<mock@mock>"
message.hass = hass
hass.config.allowlist_external_dirs.add(Path("tests/testing_config").resolve())
with patch("email.utils.make_msgid", return_value=sample_email):
result, _ = message.send_message(message_data, data=data)
assert content_type in result
@pytest.mark.parametrize(
("message_data", "data", "content_type"),
[
(
"Test msg",
{"images": ["tests/testing_config/notify/test.jpg"]},
"Content-Type: multipart/mixed",
),
],
)
def test_sending_insecure_files_fails(
hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,
message_data,
data,
content_type,
message,
) -> None:
"""Verify if we cannot send messages with insecure attachments."""
sample_email = "<mock@mock>"
message.hass = hass
with patch("email.utils.make_msgid", return_value=sample_email):
result, _ = message.send_message(message_data, data=data)
assert content_type in result
assert "test.jpg' is not secure to load data from, ignoring attachment"
def test_send_text_message(hass: HomeAssistant, message) -> None:
"""Verify if we can send simple text message."""
expected = (