[IMP] base: allow to set a list of domain / email in the from_filter

Purpose
=======
Some SMTP configuration can be used for many domains names. E.G.,
our own SMTP configuration can be used for both "mail.odoo.com" and
"odoo.com". So we want to be able to set a list of allowed domains
name, separated by a comma, instead of a single domain.

Task-3332488

closes odoo/odoo#122234

Signed-off-by: Warnon Aurélien (awa) <awa@odoo.com>
This commit is contained in:
std-odoo
2023-06-29 15:27:27 +02:00
parent d0710323b9
commit 2ccce7ff1e
3 changed files with 62 additions and 31 deletions
+32 -23
View File
@@ -96,7 +96,7 @@ class IrMailServer(models.Model):
name = fields.Char(string='Name', required=True, index=True)
from_filter = fields.Char(
"FROM Filtering",
help='Define for which email address or domain this server can be used.\n'
help='Comma-separated list of addresses or domains for which this server can be used.\n'
'e.g.: "notification@odoo.com" or "odoo.com"')
smtp_host = fields.Char(string='SMTP Server', help="Hostname or IP of SMTP server")
smtp_port = fields.Integer(string='SMTP Port', default=25, help="SMTP Port. Usually 465 for SSL, and 25 or 587 for other cases.")
@@ -204,20 +204,21 @@ class IrMailServer(models.Model):
self.ensure_one()
email_to = "noreply@odoo.com"
if self.from_filter:
if "@" in self.from_filter:
from_filter_parts = [part.strip() for part in self.from_filter.split(",")]
if mail_from := next((email for email in from_filter_parts if "@" in email), None):
# All emails will be sent from the same address
return self.from_filter, email_to
return mail_from, email_to
# All emails will be sent from any address in the same domain
default_from = self.env["ir.config_parameter"].sudo().get_param("mail.default.from", "odoo")
if "@" not in default_from:
return f"{default_from}@{self.from_filter}", email_to
return f"{default_from}@{from_filter_parts[0]}", email_to
elif self._match_from_filter(default_from, self.from_filter):
# the mail server is configured for a domain
# that match the default email address
return default_from, email_to
# the from_filter is configured for a domain different that the one
# of the full email configured in mail.default.from
return f"noreply@{self.from_filter}", email_to
return f"noreply@{from_filter_parts[0]}", email_to
# Fallback to current user email if there's no from filter
email_from = self.env.user.email
if not email_from:
@@ -733,30 +734,33 @@ class IrMailServer(models.Model):
if mail_servers is None:
mail_servers = self.sudo().search([], order='sequence')
# 1. Try to find a mail server for the right mail from
mail_server = mail_servers.filtered(lambda m: email_normalize(m.from_filter) == email_from_normalized)
if mail_server:
return mail_server[0], email_from
def first_match(target, normalize_method):
for mail_server in mail_servers:
if mail_server.from_filter and any(
normalize_method(email.strip()) == target
for email in mail_server.from_filter.split(',')
):
return mail_server
mail_server = mail_servers.filtered(lambda m: email_domain_normalize(m.from_filter) == email_from_domain)
if mail_server:
return mail_server[0], email_from
# 1. Try to find a mail server for the right mail from
if mail_server := first_match(email_from_normalized, email_normalize):
return mail_server, email_from
if mail_server := first_match(email_from_domain, email_domain_normalize):
return mail_server, email_from
# 2. Try to find a mail server for <notifications@domain.com>
if notifications_email:
mail_server = mail_servers.filtered(lambda m: email_normalize(m.from_filter) == notifications_email)
if mail_server:
return mail_server[0], notifications_email
if mail_server := first_match(notifications_email, email_normalize):
return mail_server, notifications_email
mail_server = mail_servers.filtered(lambda m: email_domain_normalize(m.from_filter) == notifications_domain)
if mail_server:
return mail_server[0], notifications_email
if mail_server := first_match(notifications_domain, email_domain_normalize):
return mail_server, notifications_email
# 3. Take the first mail server without "from_filter" because
# nothing else has been found... Will spoof the FROM because
# we have no other choices
mail_server = mail_servers.filtered(lambda m: not m.from_filter)
if mail_server:
if mail_server := mail_servers.filtered(lambda m: not m.from_filter):
return mail_server[0], email_from
# 4. Return the first mail server even if it was configured for another domain
@@ -786,10 +790,15 @@ class IrMailServer(models.Model):
return True
normalized_mail_from = email_normalize(email_from)
if '@' in from_filter:
return email_normalize(from_filter) == normalized_mail_from
normalized_domain = email_domain_extract(normalized_mail_from)
return email_domain_extract(normalized_mail_from) == email_domain_normalize(from_filter)
for email_filter in from_filter.split(','):
email_filter = email_filter.strip()
if '@' in email_filter and email_normalize(email_filter) == normalized_mail_from:
return True
if '@' not in email_filter and email_domain_normalize(email_filter) == normalized_domain:
return True
return False
@api.onchange('smtp_encryption')
def _onchange_encryption(self):
+29 -7
View File
@@ -41,6 +41,7 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase):
('admin@mail.example.com', False),
('"fake@test.com" <admin@mail.example.com>', 'mail.example.com'),
('"fake@test.com" <ADMIN@mail.example.com>', 'mail.example.com'),
('"fake@test.com" <ADMIN@mail.example.com>', 'test.com, mail.example.com, test2.com'),
]
for email, from_filter in tests:
self.assertTrue(match_from_filter(email, from_filter))
@@ -53,6 +54,7 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase):
('admin@mmail.example.com', 'mail.example.com'),
('admin@mail.example.com', 'mmail.example.com'),
('"admin@mail.example.com" <fake@test.com>', 'mail.example.com'),
('"fake@test.com" <ADMIN@mail.example.com>', 'test.com, wrong.mail.example.com, test3.com'),
]
for email, from_filter in tests:
self.assertFalse(match_from_filter(email, from_filter))
@@ -138,6 +140,23 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase):
self.assertEqual(mail_server, self.server_notification, 'Should take the notification email')
self.assertEqual(mail_from, 'notifications@test.com')
# this mail server can not be used for a specific email address and 2 domain names
self.server_user.from_filter = "domain1.com, specific_user@test.com, domain2.com"
mail_server, mail_from = self.env['ir.mail_server']._find_mail_server(email_from='"Test" <specific_user@test.com>')
self.assertEqual(mail_server, self.server_user, "The entire email matches, the server should be used")
self.assertEqual(mail_from, '"Test" <specific_user@test.com>')
mail_server, mail_from = self.env['ir.mail_server']._find_mail_server(email_from='"Example" <test@domain2.com>')
self.assertEqual(mail_server, self.server_user, "The domain matches, the server should be used")
self.assertEqual(mail_from, '"Example" <test@domain2.com>')
mail_server, mail_from = self.env['ir.mail_server']._find_mail_server(email_from='"Example" <test@domain1.com>')
self.assertEqual(mail_server, self.server_user, "The domain matches, the server should be used")
self.assertEqual(mail_from, '"Example" <test@domain1.com>')
# check that we take the domain server and not the "user specific" server
mail_server, mail_from = self.env['ir.mail_server']._find_mail_server(email_from='"Test" <not_specific_user@test.com>')
self.assertEqual(mail_server, self.server_domain)
self.assertEqual(mail_from, '"Test" <not_specific_user@test.com>')
# remove the notifications email to simulate a mis-configured Odoo database
# so we do not have the choice, we have to spoof the FROM
# (otherwise we can not send the email)
@@ -391,7 +410,10 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase):
)
@mute_logger('odoo.models.unlink')
@patch.dict(config.options, {"from_filter": "test.com", "smtp_server": "example.com"})
@patch.dict(config.options, {
"from_filter": "dummy@example.com, test.com, dummy2@example.com",
"smtp_server": "example.com",
})
def test_mail_server_binary_arguments_domain_smtp_session(self):
"""Test the configuration provided in the odoo-bin arguments.
@@ -415,7 +437,7 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase):
self.assert_email_sent_smtp(
smtp_from=default_bounce_adress,
message_from='specific_user@test.com',
from_filter='test.com',
from_filter='dummy@example.com, test.com, dummy2@example.com',
)
# Use an email outside of the domain of the "from_filter"
@@ -430,12 +452,12 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase):
self.assert_email_sent_smtp(
smtp_from=default_bounce_adress,
message_from='"test" <notifications@test.com>',
from_filter='test.com',
from_filter='dummy@example.com, test.com, dummy2@example.com',
)
def test_mail_server_get_email_addresses(self):
"""Test the email used to test the mail server connection."""
self.server_notification.from_filter = 'example_2.com'
self.server_notification.from_filter = 'example_2.com, example_3.com'
self.env['ir.config_parameter'].set_param('mail.default.from', 'notifications@example.com')
email_from = self.server_notification._get_test_email_addresses()[0]
@@ -445,7 +467,7 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase):
email_from = self.server_notification._get_test_email_addresses()[0]
self.assertEqual(email_from, 'notifications@example_2.com')
self.server_notification.from_filter = 'full_email@example_2.com'
self.server_notification.from_filter = 'dummy.com, full_email@example_2.com, dummy2.com'
self.env['ir.config_parameter'].set_param('mail.default.from', 'notifications')
email_from = self.server_notification._get_test_email_addresses()[0]
@@ -477,7 +499,7 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase):
'smtp_host': 'smtp_host',
'smtp_encryption': 'none',
'smtp_authentication': 'cli',
'from_filter': 'cli_example.com',
'from_filter': 'dummy@example.com, cli_example.com, dummy2@example.com',
}])
IrMailServer = self.env['ir.mail_server']
@@ -491,7 +513,7 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase):
self.assert_email_sent_smtp(
smtp_from='test@cli_example.com',
message_from='test@cli_example.com',
from_filter='cli_example.com',
from_filter='dummy@example.com, cli_example.com, dummy2@example.com',
)
# other mail server still work
@@ -15,7 +15,7 @@
<group>
<group>
<field name="name" placeholder="e.g. My Outgoing Server"/>
<field name="from_filter"/>
<field name="from_filter" placeholder="e.g. email@domain.com, domain.com"/>
</group>
<group>
<field name="sequence"/>