Refactored SSL/Auth handling of Emails (#774)

This commit is contained in:
Chris Caron
2022-11-23 07:13:38 -05:00
committed by GitHub
parent eb85dca076
commit 6fb8fbab19
2 changed files with 305 additions and 35 deletions

View File

@ -105,6 +105,12 @@ TEST_URLS = (
('mailtos://user:pass@nuxref.com:567', {
'instance': NotifyEmail,
}),
('mailto://user:pass@nuxref.com?mode=ssl', {
# mailto:// with mode=ssl causes us to convert to ssl
'instance': NotifyEmail,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'mailtos://user:****@nuxref.com',
}),
('mailto://user:pass@nuxref.com:567?format=html', {
'instance': NotifyEmail,
}),
@ -913,6 +919,7 @@ def test_plugin_email_url_parsing(mock_smtp, mock_smtp_ssl):
assert 'smtp=smtp-mail.outlook.com' in obj.url()
mock_smtp.reset_mock()
mock_smtp_ssl.reset_mock()
response.reset_mock()
# The below switches the `name` with the `to` to verify the results
@ -952,6 +959,11 @@ def test_plugin_email_url_parsing(mock_smtp, mock_smtp_ssl):
assert _to[0] == 'user2@yahoo.com'
assert _msg.split('\n')[-3] == 'test'
user, pw = response.login.call_args[0]
# the SMTP Server was ovr
assert pw == 'pass123'
assert user == 'user'
assert obj.url().startswith(
'mailtos://user:pass123@hotmail.com/user2%40yahoo.com')
# Test that our template over-ride worked
@ -961,11 +973,38 @@ def test_plugin_email_url_parsing(mock_smtp, mock_smtp_ssl):
assert 'reply=' not in obj.url()
mock_smtp.reset_mock()
mock_smtp_ssl.reset_mock()
response.reset_mock()
#
# Test outlook/hotmail lookups
#
results = NotifyEmail.parse_url(
'mailtos://user:pass123@hotmail.com')
obj = Apprise.instantiate(results, suppress_exceptions=False)
assert isinstance(obj, NotifyEmail) is True
assert obj.smtp_host == 'smtp-mail.outlook.com'
# No entries in the reply_to
assert not obj.reply_to
assert mock_smtp.call_count == 0
assert mock_smtp_ssl.call_count == 0
assert response.starttls.call_count == 0
assert obj.notify("test") is True
assert mock_smtp.call_count == 1
assert mock_smtp_ssl.call_count == 0
assert response.starttls.call_count == 1
assert response.login.call_count == 1
assert response.sendmail.call_count == 1
user, pw = response.login.call_args[0]
assert pw == 'pass123'
assert user == 'user@hotmail.com'
mock_smtp.reset_mock()
mock_smtp_ssl.reset_mock()
response.reset_mock()
results = NotifyEmail.parse_url(
'mailtos://user:pass123@outlook.com')
obj = Apprise.instantiate(results, suppress_exceptions=False)
@ -974,6 +1013,24 @@ def test_plugin_email_url_parsing(mock_smtp, mock_smtp_ssl):
# No entries in the reply_to
assert not obj.reply_to
assert mock_smtp.call_count == 0
assert mock_smtp_ssl.call_count == 0
assert response.starttls.call_count == 0
assert obj.notify("test") is True
assert mock_smtp.call_count == 1
assert mock_smtp_ssl.call_count == 0
assert response.starttls.call_count == 1
assert response.login.call_count == 1
assert response.sendmail.call_count == 1
user, pw = response.login.call_args[0]
assert pw == 'pass123'
assert user == 'user@outlook.com'
mock_smtp.reset_mock()
mock_smtp_ssl.reset_mock()
response.reset_mock()
results = NotifyEmail.parse_url(
'mailtos://user:pass123@outlook.com.au')
obj = Apprise.instantiate(results, suppress_exceptions=False)
@ -982,6 +1039,24 @@ def test_plugin_email_url_parsing(mock_smtp, mock_smtp_ssl):
# No entries in the reply_to
assert not obj.reply_to
assert mock_smtp.call_count == 0
assert mock_smtp_ssl.call_count == 0
assert response.starttls.call_count == 0
assert obj.notify("test") is True
assert mock_smtp.call_count == 1
assert mock_smtp_ssl.call_count == 0
assert response.starttls.call_count == 1
assert response.login.call_count == 1
assert response.sendmail.call_count == 1
user, pw = response.login.call_args[0]
assert pw == 'pass123'
assert user == 'user@outlook.com.au'
mock_smtp.reset_mock()
mock_smtp_ssl.reset_mock()
response.reset_mock()
# Consisitency Checks
results = NotifyEmail.parse_url(
'mailtos://outlook.com?smtp=smtp.outlook.com'
@ -994,6 +1069,24 @@ def test_plugin_email_url_parsing(mock_smtp, mock_smtp_ssl):
assert obj1.secure_mode == 'starttls'
assert obj1.port == 587
assert mock_smtp.call_count == 0
assert mock_smtp_ssl.call_count == 0
assert response.starttls.call_count == 0
assert obj1.notify("test") is True
assert mock_smtp.call_count == 1
assert mock_smtp_ssl.call_count == 0
assert response.starttls.call_count == 1
assert response.login.call_count == 1
assert response.sendmail.call_count == 1
user, pw = response.login.call_args[0]
assert pw == 'app.pw'
assert user == 'user@outlook.com'
mock_smtp.reset_mock()
mock_smtp_ssl.reset_mock()
response.reset_mock()
results = NotifyEmail.parse_url(
'mailtos://user:app.pw@outlook.com')
obj2 = Apprise.instantiate(results, suppress_exceptions=False)
@ -1004,6 +1097,24 @@ def test_plugin_email_url_parsing(mock_smtp, mock_smtp_ssl):
assert obj2.secure_mode == obj1.secure_mode
assert obj2.port == obj1.port
assert mock_smtp.call_count == 0
assert mock_smtp_ssl.call_count == 0
assert response.starttls.call_count == 0
assert obj2.notify("test") is True
assert mock_smtp.call_count == 1
assert mock_smtp_ssl.call_count == 0
assert response.starttls.call_count == 1
assert response.login.call_count == 1
assert response.sendmail.call_count == 1
user, pw = response.login.call_args[0]
assert pw == 'app.pw'
assert user == 'user@outlook.com'
mock_smtp.reset_mock()
mock_smtp_ssl.reset_mock()
response.reset_mock()
results = NotifyEmail.parse_url(
'mailtos://user:pass123@live.com')
obj = Apprise.instantiate(results, suppress_exceptions=False)
@ -1011,6 +1122,24 @@ def test_plugin_email_url_parsing(mock_smtp, mock_smtp_ssl):
# No entries in the reply_to
assert not obj.reply_to
assert mock_smtp.call_count == 0
assert mock_smtp_ssl.call_count == 0
assert response.starttls.call_count == 0
assert obj.notify("test") is True
assert mock_smtp.call_count == 1
assert mock_smtp_ssl.call_count == 0
assert response.starttls.call_count == 1
assert response.login.call_count == 1
assert response.sendmail.call_count == 1
user, pw = response.login.call_args[0]
assert pw == 'pass123'
assert user == 'user@live.com'
mock_smtp.reset_mock()
mock_smtp_ssl.reset_mock()
response.reset_mock()
results = NotifyEmail.parse_url(
'mailtos://user:pass123@hotmail.com')
obj = Apprise.instantiate(results, suppress_exceptions=False)
@ -1018,6 +1147,24 @@ def test_plugin_email_url_parsing(mock_smtp, mock_smtp_ssl):
# No entries in the reply_to
assert not obj.reply_to
assert mock_smtp.call_count == 0
assert mock_smtp_ssl.call_count == 0
assert response.starttls.call_count == 0
assert obj.notify("test") is True
assert mock_smtp.call_count == 1
assert mock_smtp_ssl.call_count == 0
assert response.starttls.call_count == 1
assert response.login.call_count == 1
assert response.sendmail.call_count == 1
user, pw = response.login.call_args[0]
assert pw == 'pass123'
assert user == 'user@hotmail.com'
mock_smtp.reset_mock()
mock_smtp_ssl.reset_mock()
response.reset_mock()
#
# Test Port Over-Riding
#
@ -1042,9 +1189,28 @@ def test_plugin_email_url_parsing(mock_smtp, mock_smtp_ssl):
assert re.match(r'.*mode=ssl.*', obj.url()) is not None
# No smtp= as the SMTP server is the same as the hostname in this case
assert re.match(r'.*smtp=smtp.exmail.qq.com.*', obj.url()) is not None
# URL is assembled based on provided user
# URL is assembled based on provided user (:465 is dropped because it
# is a default port when using xyz.cn)
assert re.match(
r'^mailtos://abc:password@xyz.cn:465/.*', obj.url()) is not None
r'^mailtos://abc:password@xyz.cn/.*', obj.url()) is not None
assert mock_smtp.call_count == 0
assert mock_smtp_ssl.call_count == 0
assert response.starttls.call_count == 0
assert obj.notify("test") is True
assert mock_smtp.call_count == 0
assert mock_smtp_ssl.call_count == 1
assert response.starttls.call_count == 0
assert response.login.call_count == 1
assert response.sendmail.call_count == 1
user, pw = response.login.call_args[0]
assert pw == 'password'
assert user == 'abc'
mock_smtp.reset_mock()
mock_smtp_ssl.reset_mock()
response.reset_mock()
results = NotifyEmail.parse_url(
"mailtos://abc:password@xyz.cn?"
@ -1061,6 +1227,24 @@ def test_plugin_email_url_parsing(mock_smtp, mock_smtp_ssl):
# No entries in the reply_to
assert not obj.reply_to
assert mock_smtp.call_count == 0
assert mock_smtp_ssl.call_count == 0
assert response.starttls.call_count == 0
assert obj.notify("test") is True
assert mock_smtp.call_count == 0
assert mock_smtp_ssl.call_count == 1
assert response.starttls.call_count == 0
assert response.login.call_count == 1
assert response.sendmail.call_count == 1
user, pw = response.login.call_args[0]
assert pw == 'password'
assert user == 'abc'
mock_smtp.reset_mock()
mock_smtp_ssl.reset_mock()
response.reset_mock()
#
# Test Reply-To Email
#
@ -1078,6 +1262,24 @@ def test_plugin_email_url_parsing(mock_smtp, mock_smtp_ssl):
# Test that our template over-ride worked
assert 'reply=noreply%40example.com' in obj.url()
assert mock_smtp.call_count == 0
assert mock_smtp_ssl.call_count == 0
assert response.starttls.call_count == 0
assert obj.notify("test") is True
assert mock_smtp.call_count == 1
assert mock_smtp_ssl.call_count == 0
assert response.starttls.call_count == 1
assert response.login.call_count == 1
assert response.sendmail.call_count == 1
user, pw = response.login.call_args[0]
assert pw == 'pass'
assert user == 'user'
mock_smtp.reset_mock()
mock_smtp_ssl.reset_mock()
response.reset_mock()
#
# Test Reply-To Email with Name Inline
#
@ -1094,3 +1296,67 @@ def test_plugin_email_url_parsing(mock_smtp, mock_smtp_ssl):
'mailtos://user:pass@example.com')
# Test that our template over-ride worked
assert 'reply=Chris+%3Cnoreply%40example.ca%3E' in obj.url()
assert mock_smtp.call_count == 0
assert mock_smtp_ssl.call_count == 0
assert response.starttls.call_count == 0
assert obj.notify("test") is True
assert mock_smtp.call_count == 1
assert mock_smtp_ssl.call_count == 0
assert response.starttls.call_count == 1
assert response.login.call_count == 1
assert response.sendmail.call_count == 1
user, pw = response.login.call_args[0]
assert pw == 'pass'
assert user == 'user'
mock_smtp.reset_mock()
mock_smtp_ssl.reset_mock()
response.reset_mock()
# Fast Mail Handling
# Test variations of username required to be an email address
# user@example.com; we also test an over-ride port on a template driven
# mailto:// entry
results = NotifyEmail.parse_url(
'mailto://fastmail.com/?to=hello@concordium-explorer.nl'
'&user=joe@mydomain.nl&pass=abc123'
'&from=Concordium Explorer Bot<bot@concordium-explorer.nl>')
assert isinstance(results, dict)
assert 'Concordium Explorer Bot<bot@concordium-explorer.nl>' == \
results['from_addr']
assert 'joe@mydomain.nl' == results['user']
assert results['port'] is None
assert 'fastmail.com' == results['host']
assert 'abc123' == results['password']
assert 'hello@concordium-explorer.nl' in results['targets']
obj = Apprise.instantiate(results, suppress_exceptions=False)
assert isinstance(obj, NotifyEmail) is True
assert mock_smtp.call_count == 0
assert mock_smtp_ssl.call_count == 0
assert obj.notify("test") is True
assert mock_smtp.call_count == 0
assert mock_smtp_ssl.call_count == 1
assert response.starttls.call_count == 0
assert response.login.call_count == 1
assert response.sendmail.call_count == 1
# Store our Sent Arguments
# Syntax is:
# sendmail(from_addr, to_addrs, msg, mail_options=(), rcpt_options=())
# [0] [1] [2]
_from = response.sendmail.call_args[0][0]
_to = response.sendmail.call_args[0][1]
_msg = response.sendmail.call_args[0][2]
assert _from == 'bot@concordium-explorer.nl'
assert isinstance(_to, list)
assert len(_to) == 1
assert _to[0] == 'hello@concordium-explorer.nl'
assert _msg.split('\n')[-3] == 'test'
user, pw = response.login.call_args[0]
assert pw == 'abc123'
assert user == 'joe@mydomain.nl'