Merge pull request #1305 from NousResearch/hermes/hermes-2ba57c8a
fix: email adapter IMAP UID tracking and SMTP TLS verification
This commit is contained in:
@@ -22,6 +22,7 @@ import logging
|
||||
import os
|
||||
import re
|
||||
import smtplib
|
||||
import ssl
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from email.header import decode_header
|
||||
@@ -212,7 +213,7 @@ class EmailAdapter(BasePlatformAdapter):
|
||||
imap.login(self._address, self._password)
|
||||
# Mark all existing messages as seen so we only process new ones
|
||||
imap.select("INBOX")
|
||||
status, data = imap.search(None, "ALL")
|
||||
status, data = imap.uid("search", None, "ALL")
|
||||
if status == "OK" and data[0]:
|
||||
for uid in data[0].split():
|
||||
self._seen_uids.add(uid)
|
||||
@@ -225,7 +226,7 @@ class EmailAdapter(BasePlatformAdapter):
|
||||
try:
|
||||
# Test SMTP connection
|
||||
smtp = smtplib.SMTP(self._smtp_host, self._smtp_port)
|
||||
smtp.starttls()
|
||||
smtp.starttls(context=ssl.create_default_context())
|
||||
smtp.login(self._address, self._password)
|
||||
smtp.quit()
|
||||
logger.info("[Email] SMTP connection test passed.")
|
||||
@@ -277,7 +278,7 @@ class EmailAdapter(BasePlatformAdapter):
|
||||
imap.login(self._address, self._password)
|
||||
imap.select("INBOX")
|
||||
|
||||
status, data = imap.search(None, "UNSEEN")
|
||||
status, data = imap.uid("search", None, "UNSEEN")
|
||||
if status != "OK" or not data[0]:
|
||||
imap.logout()
|
||||
return results
|
||||
@@ -287,7 +288,7 @@ class EmailAdapter(BasePlatformAdapter):
|
||||
continue
|
||||
self._seen_uids.add(uid)
|
||||
|
||||
status, msg_data = imap.fetch(uid, "(RFC822)")
|
||||
status, msg_data = imap.uid("fetch", uid, "(RFC822)")
|
||||
if status != "OK":
|
||||
continue
|
||||
|
||||
@@ -427,7 +428,7 @@ class EmailAdapter(BasePlatformAdapter):
|
||||
msg.attach(MIMEText(body, "plain", "utf-8"))
|
||||
|
||||
smtp = smtplib.SMTP(self._smtp_host, self._smtp_port)
|
||||
smtp.starttls()
|
||||
smtp.starttls(context=ssl.create_default_context())
|
||||
smtp.login(self._address, self._password)
|
||||
smtp.send_message(msg)
|
||||
smtp.quit()
|
||||
@@ -515,7 +516,7 @@ class EmailAdapter(BasePlatformAdapter):
|
||||
msg.attach(part)
|
||||
|
||||
smtp = smtplib.SMTP(self._smtp_host, self._smtp_port)
|
||||
smtp.starttls()
|
||||
smtp.starttls(context=ssl.create_default_context())
|
||||
smtp.login(self._address, self._password)
|
||||
smtp.send_message(msg)
|
||||
smtp.quit()
|
||||
|
||||
@@ -797,7 +797,7 @@ class TestConnectDisconnect(unittest.TestCase):
|
||||
adapter = self._make_adapter()
|
||||
|
||||
mock_imap = MagicMock()
|
||||
mock_imap.search.return_value = ("OK", [b"1 2 3"])
|
||||
mock_imap.uid.return_value = ("OK", [b"1 2 3"])
|
||||
|
||||
with patch("imaplib.IMAP4_SSL", return_value=mock_imap), \
|
||||
patch("smtplib.SMTP") as mock_smtp:
|
||||
@@ -831,7 +831,7 @@ class TestConnectDisconnect(unittest.TestCase):
|
||||
adapter = self._make_adapter()
|
||||
|
||||
mock_imap = MagicMock()
|
||||
mock_imap.search.return_value = ("OK", [b""])
|
||||
mock_imap.uid.return_value = ("OK", [b""])
|
||||
|
||||
with patch("imaplib.IMAP4_SSL", return_value=mock_imap), \
|
||||
patch("smtplib.SMTP", side_effect=Exception("SMTP down")):
|
||||
@@ -880,8 +880,15 @@ class TestFetchNewMessages(unittest.TestCase):
|
||||
raw_email["Message-ID"] = "<msg@test.com>"
|
||||
|
||||
mock_imap = MagicMock()
|
||||
mock_imap.search.return_value = ("OK", [b"1 2 3"])
|
||||
mock_imap.fetch.return_value = ("OK", [(b"3", raw_email.as_bytes())])
|
||||
|
||||
def uid_handler(command, *args):
|
||||
if command == "search":
|
||||
return ("OK", [b"1 2 3"])
|
||||
if command == "fetch":
|
||||
return ("OK", [(b"3", raw_email.as_bytes())])
|
||||
return ("NO", [])
|
||||
|
||||
mock_imap.uid.side_effect = uid_handler
|
||||
|
||||
with patch("imaplib.IMAP4_SSL", return_value=mock_imap):
|
||||
results = adapter._fetch_new_messages()
|
||||
@@ -896,7 +903,7 @@ class TestFetchNewMessages(unittest.TestCase):
|
||||
adapter = self._make_adapter()
|
||||
|
||||
mock_imap = MagicMock()
|
||||
mock_imap.search.return_value = ("OK", [b""])
|
||||
mock_imap.uid.return_value = ("OK", [b""])
|
||||
|
||||
with patch("imaplib.IMAP4_SSL", return_value=mock_imap):
|
||||
results = adapter._fetch_new_messages()
|
||||
@@ -922,8 +929,15 @@ class TestFetchNewMessages(unittest.TestCase):
|
||||
raw_email["Message-ID"] = "<msg@test.com>"
|
||||
|
||||
mock_imap = MagicMock()
|
||||
mock_imap.search.return_value = ("OK", [b"1"])
|
||||
mock_imap.fetch.return_value = ("OK", [(b"1", raw_email.as_bytes())])
|
||||
|
||||
def uid_handler(command, *args):
|
||||
if command == "search":
|
||||
return ("OK", [b"1"])
|
||||
if command == "fetch":
|
||||
return ("OK", [(b"1", raw_email.as_bytes())])
|
||||
return ("NO", [])
|
||||
|
||||
mock_imap.uid.side_effect = uid_handler
|
||||
|
||||
with patch("imaplib.IMAP4_SSL", return_value=mock_imap):
|
||||
results = adapter._fetch_new_messages()
|
||||
@@ -966,8 +980,15 @@ class TestPollLoop(unittest.TestCase):
|
||||
raw_email["Message-ID"] = "<inbox@test.com>"
|
||||
|
||||
mock_imap = MagicMock()
|
||||
mock_imap.search.return_value = ("OK", [b"1"])
|
||||
mock_imap.fetch.return_value = ("OK", [(b"1", raw_email.as_bytes())])
|
||||
|
||||
def uid_handler(command, *args):
|
||||
if command == "search":
|
||||
return ("OK", [b"1"])
|
||||
if command == "fetch":
|
||||
return ("OK", [(b"1", raw_email.as_bytes())])
|
||||
return ("NO", [])
|
||||
|
||||
mock_imap.uid.side_effect = uid_handler
|
||||
|
||||
with patch("imaplib.IMAP4_SSL", return_value=mock_imap):
|
||||
asyncio.run(adapter._check_inbox())
|
||||
@@ -986,8 +1007,9 @@ class TestSendEmailStandalone(unittest.TestCase):
|
||||
"EMAIL_SMTP_PORT": "587",
|
||||
})
|
||||
def test_send_email_tool_success(self):
|
||||
"""_send_email should use SMTP to send."""
|
||||
"""_send_email should use verified STARTTLS when sending."""
|
||||
import asyncio
|
||||
import ssl
|
||||
from tools.send_message_tool import _send_email
|
||||
|
||||
with patch("smtplib.SMTP") as mock_smtp:
|
||||
@@ -1000,6 +1022,8 @@ class TestSendEmailStandalone(unittest.TestCase):
|
||||
|
||||
self.assertTrue(result["success"])
|
||||
self.assertEqual(result["platform"], "email")
|
||||
_, kwargs = mock_server.starttls.call_args
|
||||
self.assertIsInstance(kwargs["context"], ssl.SSLContext)
|
||||
|
||||
@patch.dict(os.environ, {
|
||||
"EMAIL_ADDRESS": "hermes@test.com",
|
||||
|
||||
@@ -9,6 +9,7 @@ import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import ssl
|
||||
import time
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -432,7 +433,7 @@ async def _send_email(extra, chat_id, message):
|
||||
msg["Subject"] = "Hermes Agent"
|
||||
|
||||
server = smtplib.SMTP(smtp_host, smtp_port)
|
||||
server.starttls()
|
||||
server.starttls(context=ssl.create_default_context())
|
||||
server.login(address, password)
|
||||
server.send_message(msg)
|
||||
server.quit()
|
||||
|
||||
Reference in New Issue
Block a user