From 2ccce7ff1e4f280ad859c85aedf61261f52a846a Mon Sep 17 00:00:00 2001 From: std-odoo Date: Wed, 24 May 2023 07:58:35 +0000 Subject: [PATCH] [IMP] base: allow to set a list of domain / email in the from_filter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- odoo/addons/base/models/ir_mail_server.py | 55 +++++++++++-------- odoo/addons/base/tests/test_ir_mail_server.py | 36 +++++++++--- .../base/views/ir_mail_server_views.xml | 2 +- 3 files changed, 62 insertions(+), 31 deletions(-) diff --git a/odoo/addons/base/models/ir_mail_server.py b/odoo/addons/base/models/ir_mail_server.py index ca06f8795e0..c6546290f8d 100644 --- a/odoo/addons/base/models/ir_mail_server.py +++ b/odoo/addons/base/models/ir_mail_server.py @@ -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 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): diff --git a/odoo/addons/base/tests/test_ir_mail_server.py b/odoo/addons/base/tests/test_ir_mail_server.py index 4d6329f1549..47cdcc6cf2d 100644 --- a/odoo/addons/base/tests/test_ir_mail_server.py +++ b/odoo/addons/base/tests/test_ir_mail_server.py @@ -41,6 +41,7 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase): ('admin@mail.example.com', False), ('"fake@test.com" ', 'mail.example.com'), ('"fake@test.com" ', 'mail.example.com'), + ('"fake@test.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" ', 'mail.example.com'), + ('"fake@test.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" ') + self.assertEqual(mail_server, self.server_user, "The entire email matches, the server should be used") + self.assertEqual(mail_from, '"Test" ') + mail_server, mail_from = self.env['ir.mail_server']._find_mail_server(email_from='"Example" ') + self.assertEqual(mail_server, self.server_user, "The domain matches, the server should be used") + self.assertEqual(mail_from, '"Example" ') + mail_server, mail_from = self.env['ir.mail_server']._find_mail_server(email_from='"Example" ') + self.assertEqual(mail_server, self.server_user, "The domain matches, the server should be used") + self.assertEqual(mail_from, '"Example" ') + + # 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" ') + self.assertEqual(mail_server, self.server_domain) + self.assertEqual(mail_from, '"Test" ') + # 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" ', - 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 diff --git a/odoo/addons/base/views/ir_mail_server_views.xml b/odoo/addons/base/views/ir_mail_server_views.xml index 26c53f10e3b..2478e32a4c8 100644 --- a/odoo/addons/base/views/ir_mail_server_views.xml +++ b/odoo/addons/base/views/ir_mail_server_views.xml @@ -15,7 +15,7 @@ - +