Do not allow smtp to access insecure files (#104972)
This commit is contained in:
parent
c1f68c3767
commit
fe2906f159
2 changed files with 53 additions and 9 deletions
|
@ -8,6 +8,7 @@ from email.mime.text import MIMEText
|
||||||
import email.utils
|
import email.utils
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
import smtplib
|
import smtplib
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
@ -193,10 +194,15 @@ class MailNotificationService(BaseNotificationService):
|
||||||
if data := kwargs.get(ATTR_DATA):
|
if data := kwargs.get(ATTR_DATA):
|
||||||
if ATTR_HTML in data:
|
if ATTR_HTML in data:
|
||||||
msg = _build_html_msg(
|
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:
|
else:
|
||||||
msg = _build_multipart_msg(message, images=data.get(ATTR_IMAGES, []))
|
msg = _build_multipart_msg(
|
||||||
|
self.hass, message, images=data.get(ATTR_IMAGES, [])
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
msg = _build_text_msg(message)
|
msg = _build_text_msg(message)
|
||||||
|
|
||||||
|
@ -241,13 +247,21 @@ def _build_text_msg(message):
|
||||||
return MIMEText(message)
|
return MIMEText(message)
|
||||||
|
|
||||||
|
|
||||||
def _attach_file(atch_name, content_id=""):
|
def _attach_file(hass, atch_name, content_id=""):
|
||||||
"""Create a message attachment.
|
"""Create a message attachment.
|
||||||
|
|
||||||
If MIMEImage is successful and content_id is passed (HTML), add images in-line.
|
If MIMEImage is successful and content_id is passed (HTML), add images in-line.
|
||||||
Otherwise add them as attachments.
|
Otherwise add them as attachments.
|
||||||
"""
|
"""
|
||||||
try:
|
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:
|
with open(atch_name, "rb") as attachment_file:
|
||||||
file_bytes = attachment_file.read()
|
file_bytes = attachment_file.read()
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
|
@ -277,22 +291,22 @@ def _attach_file(atch_name, content_id=""):
|
||||||
return attachment
|
return attachment
|
||||||
|
|
||||||
|
|
||||||
def _build_multipart_msg(message, images):
|
def _build_multipart_msg(hass, message, images):
|
||||||
"""Build Multipart message with images as attachments."""
|
"""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()
|
msg = MIMEMultipart()
|
||||||
body_txt = MIMEText(message)
|
body_txt = MIMEText(message)
|
||||||
msg.attach(body_txt)
|
msg.attach(body_txt)
|
||||||
|
|
||||||
for atch_name in images:
|
for atch_name in images:
|
||||||
attachment = _attach_file(atch_name)
|
attachment = _attach_file(hass, atch_name)
|
||||||
if attachment:
|
if attachment:
|
||||||
msg.attach(attachment)
|
msg.attach(attachment)
|
||||||
|
|
||||||
return msg
|
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)."""
|
"""Build Multipart message with in-line images and rich HTML (UTF-8)."""
|
||||||
_LOGGER.debug("Building HTML rich email")
|
_LOGGER.debug("Building HTML rich email")
|
||||||
msg = MIMEMultipart("related")
|
msg = MIMEMultipart("related")
|
||||||
|
@ -303,7 +317,7 @@ def _build_html_msg(text, html, images):
|
||||||
|
|
||||||
for atch_name in images:
|
for atch_name in images:
|
||||||
name = os.path.basename(atch_name)
|
name = os.path.basename(atch_name)
|
||||||
attachment = _attach_file(atch_name, name)
|
attachment = _attach_file(hass, atch_name, name)
|
||||||
if attachment:
|
if attachment:
|
||||||
msg.attach(attachment)
|
msg.attach(attachment)
|
||||||
return msg
|
return msg
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
"""The tests for the notify smtp platform."""
|
"""The tests for the notify smtp platform."""
|
||||||
|
from pathlib import Path
|
||||||
import re
|
import re
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
@ -132,15 +133,44 @@ EMAIL_DATA = [
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_send_message(
|
def test_send_message(
|
||||||
message_data, data, content_type, hass: HomeAssistant, message
|
hass: HomeAssistant, message_data, data, content_type, message
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Verify if we can send messages of all types correctly."""
|
"""Verify if we can send messages of all types correctly."""
|
||||||
sample_email = "<mock@mock>"
|
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):
|
with patch("email.utils.make_msgid", return_value=sample_email):
|
||||||
result, _ = message.send_message(message_data, data=data)
|
result, _ = message.send_message(message_data, data=data)
|
||||||
assert content_type in result
|
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:
|
def test_send_text_message(hass: HomeAssistant, message) -> None:
|
||||||
"""Verify if we can send simple text message."""
|
"""Verify if we can send simple text message."""
|
||||||
expected = (
|
expected = (
|
||||||
|
|
Loading…
Add table
Reference in a new issue