[MOV] base, mail: move mail config parameters usage to mail
RATIONALE
As multi-company tolerant alias domains will soon replace the usage of
configuration parameters, having them in base then replaced by more advanced
models in mail would be complicated to handle and not useful. Move those
ICP to 'mail' so that all mail configuration is done in that module.
SPECIFICATIONS
Move config parameter used for alias domains configuration in 'mail' module.
Base should be as simple as possible and let mail deal with mail server
complexity.
Move 'mail.{bounce/catchall}.alias' used with 'mail.alias.domain' to make
bounce and catchall emails. Move 'mail.default.from' as it will be integrated
into alias domains in some form.
Note that 'mail.default.from_filter' stays as an ICP in base as it is a
more global default parameter. It is used as default value in 'connect' when
no mail_server is used and no from_filter can be retrieved.
Some tests in 'base' are either fixed, either moved directly into 'mail'.
We now differentiate base behavior (without ICP) from configurable behavior
(with ICP in mail).
Task-3453347 (Mail: Move Mail ICP from Base to Mail)
Prepares Task-36879 (Mail: Support MultiCompany Aliases)
Part-of: odoo/odoo#130750
This commit is contained in:
@@ -86,6 +86,7 @@ For more specific needs, you may also assign custom-defined actions
|
||||
'views/mail_shortcode_views.xml',
|
||||
'views/mail_activity_views.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
'data/ir_config_parameter_data.xml',
|
||||
'data/res_partner_data.xml',
|
||||
'data/mail_message_subtype_data.xml',
|
||||
'data/mail_templates_chatter.xml',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import _, fields, models
|
||||
from odoo import _, api, fields, models
|
||||
|
||||
|
||||
class IrMailServer(models.Model):
|
||||
@@ -21,3 +21,45 @@ class IrMailServer(models.Model):
|
||||
map(lambda t: _('%s (Email Template)', t.display_name), record.mail_template_ids)
|
||||
)
|
||||
return usages_super
|
||||
|
||||
@api.model
|
||||
def _get_default_bounce_address(self):
|
||||
""" Compute the default bounce address. Try to use mail-defined config
|
||||
parameter bounce alias if set. """
|
||||
ICP = self.env['ir.config_parameter'].sudo()
|
||||
bounce_alias = ICP.get_param('mail.bounce.alias')
|
||||
domain = ICP.get_param('mail.catchall.domain')
|
||||
if bounce_alias and domain:
|
||||
return f'{bounce_alias}@{domain}'
|
||||
return super()._get_default_from_address()
|
||||
|
||||
@api.model
|
||||
def _get_default_from_address(self):
|
||||
""" Default from: try to use ``mail.default.from`` ICP either as a
|
||||
full email address, either completed with ``mail.catchall.domain``
|
||||
to form a alias-based default from. """
|
||||
get_param = self.env['ir.config_parameter'].sudo().get_param
|
||||
email_from = get_param("mail.default.from")
|
||||
if email_from and "@" in email_from:
|
||||
return email_from
|
||||
if email_from:
|
||||
if domain := get_param("mail.catchall.domain"):
|
||||
return f"{email_from}@{domain}"
|
||||
return super()._get_default_from_address()
|
||||
|
||||
def _get_test_email_from(self):
|
||||
self.ensure_one()
|
||||
if from_filter_parts := [part.strip() for part in (self.from_filter or '').split(",") if part.strip()]:
|
||||
# find first found complete email in filter parts
|
||||
if mail_from := next((email for email in from_filter_parts if "@" in email), None):
|
||||
return mail_from
|
||||
# try to complete with default.from in same domain of first found filter parts
|
||||
default_from = self.env["ir.config_parameter"].sudo().get_param("mail.default.from", "odoo")
|
||||
if "@" not in default_from:
|
||||
return f"{default_from}@{from_filter_parts[0]}"
|
||||
# the mail server is configured for a domain that matches the default email address
|
||||
if self._match_from_filter(default_from, self.from_filter):
|
||||
return default_from
|
||||
# no from_filter or from_filter is configured for a domain different that
|
||||
# the one of the full email configured in mail.default.from -> fallback
|
||||
return super()._get_test_email_from()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_ir_mail_server
|
||||
from . import test_link_preview
|
||||
from . import test_mail_activity
|
||||
from . import test_mail_composer
|
||||
|
||||
@@ -0,0 +1,338 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo.addons.mail.tests.common import MailCommon
|
||||
from odoo.tests import tagged, users
|
||||
from odoo.tools import config, mute_logger
|
||||
|
||||
|
||||
@tagged('mail_server')
|
||||
class TestIrMailServer(MailCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.default_bounce_address = f'{cls.alias_bounce}@{cls.alias_domain}'
|
||||
cls.default_from_address = f'{cls.default_from}@{cls.alias_domain}'
|
||||
|
||||
def test_assert_base_values(self):
|
||||
self.assertEqual(
|
||||
self.env['ir.mail_server']._get_default_bounce_address(),
|
||||
self.default_bounce_address)
|
||||
self.assertEqual(
|
||||
self.env['ir.mail_server']._get_default_from_address(),
|
||||
self.default_from_address)
|
||||
|
||||
@patch.dict(config.options, {"email_from": "settings@example.com"})
|
||||
def test_default_email_from(self, *args):
|
||||
""" Check that 'mail.default.from' setting is respected. """
|
||||
ICP = self.env["ir.config_parameter"].sudo()
|
||||
for (icp_from, icp_domain), expected_from in zip(
|
||||
[
|
||||
('icp', 'test.mycompany.com'),
|
||||
('icp', False),
|
||||
('icp@another.company.com', 'test.mycompany.com'),
|
||||
('icp@another.company.com', False),
|
||||
(False, 'test.mycompany.com'),
|
||||
], [
|
||||
"icp@test.mycompany.com",
|
||||
"settings@example.com",
|
||||
"icp@another.company.com",
|
||||
"icp@another.company.com",
|
||||
"settings@example.com",
|
||||
],
|
||||
):
|
||||
with self.subTest(icp_from=icp_from, icp_domain=icp_domain):
|
||||
ICP.set_param("mail.default.from", icp_from)
|
||||
ICP.set_param("mail.catchall.domain", icp_domain)
|
||||
message = self.env["ir.mail_server"].build_email(
|
||||
False, "recipient@example.com", "Subject",
|
||||
"The body of an email",
|
||||
)
|
||||
self.assertEqual(message["From"], expected_from)
|
||||
|
||||
@mute_logger('odoo.models.unlink')
|
||||
@patch.dict(config.options, {
|
||||
"from_filter": "dummy@example.com, test.mycompany.com, dummy2@example.com",
|
||||
"smtp_server": "example.com",
|
||||
})
|
||||
def test_mail_server_config_bin(self):
|
||||
""" Test the configuration provided in the odoo-bin arguments. This config
|
||||
is used when no mail server exists. Test with and without giving a
|
||||
pre-configured SMTP session, should not impact results.
|
||||
|
||||
Also check "mail.default.from_filter" parameter usage that should overwrite
|
||||
odoo-bin argument "--from-filter".
|
||||
"""
|
||||
IrMailServer = self.env['ir.mail_server']
|
||||
|
||||
# Remove all mail server so we will use the odoo-bin arguments
|
||||
IrMailServer.search([]).unlink()
|
||||
self.assertFalse(IrMailServer.search([]))
|
||||
|
||||
for mail_from, (expected_smtp_from, expected_msg_from) in zip(
|
||||
[
|
||||
# inside "from_filter" domain
|
||||
'specific_user@test.mycompany.com',
|
||||
'"Formatted Name" <specific_user@test.mycompany.com>',
|
||||
'"Formatted Name" <specific_user@test.MYCOMPANY.com>',
|
||||
'"Formatted Name" <SPECIFIC_USER@test.mycompany.com>',
|
||||
# outside "from_filter" domain
|
||||
'test@unknown_domain.com',
|
||||
'"Formatted Name" <test@unknown_domain.com>',
|
||||
], [
|
||||
# inside "from_filter" domain: no rewriting
|
||||
(self.default_bounce_address, 'specific_user@test.mycompany.com'),
|
||||
(self.default_bounce_address, '"Formatted Name" <specific_user@test.mycompany.com>'),
|
||||
(self.default_bounce_address, '"Formatted Name" <specific_user@test.MYCOMPANY.com>'),
|
||||
(self.default_bounce_address, '"Formatted Name" <SPECIFIC_USER@test.mycompany.com>'),
|
||||
# outside "from_filter" domain: we will use notifications emails in the
|
||||
# headers, and bounce address in the envelope because the "from_filter"
|
||||
# allows to use the entire domain
|
||||
(self.default_bounce_address, '"test" <notifications.test@test.mycompany.com>'),
|
||||
(self.default_bounce_address, '"Formatted Name" <notifications.test@test.mycompany.com>'),
|
||||
]
|
||||
):
|
||||
for provide_smtp in [False, True]: # providing smtp session should ont impact test
|
||||
with self.subTest(mail_from=mail_from, provide_smtp=provide_smtp):
|
||||
with self.mock_smtplib_connection():
|
||||
if provide_smtp:
|
||||
smtp_session = IrMailServer.connect(smtp_from=mail_from)
|
||||
message = self._build_email(mail_from=mail_from)
|
||||
IrMailServer.send_email(message, smtp_session=smtp_session)
|
||||
else:
|
||||
message = self._build_email(mail_from=mail_from)
|
||||
IrMailServer.send_email(message)
|
||||
|
||||
self.connect_mocked.assert_called_once()
|
||||
self.assertEqual(len(self.emails), 1)
|
||||
self.assertSMTPEmailsSent(
|
||||
smtp_from=expected_smtp_from,
|
||||
message_from=expected_msg_from,
|
||||
from_filter="dummy@example.com, test.mycompany.com, dummy2@example.com",
|
||||
)
|
||||
|
||||
# for from_filter in ICP, overwrite the one from odoo-bin
|
||||
self.env['ir.config_parameter'].sudo().set_param('mail.default.from_filter', 'icp.example.com')
|
||||
|
||||
# Use an email in the domain of the config parameter "mail.default.from_filter"
|
||||
with self.mock_smtplib_connection():
|
||||
message = self._build_email(mail_from='specific_user@icp.example.com')
|
||||
IrMailServer.send_email(message)
|
||||
|
||||
self.assertSMTPEmailsSent(
|
||||
smtp_from='specific_user@icp.example.com',
|
||||
message_from='specific_user@icp.example.com',
|
||||
from_filter='icp.example.com',
|
||||
)
|
||||
|
||||
@users('admin')
|
||||
def test_mail_server_get_test_email_from(self):
|
||||
""" Test the email used to test the mail server connection. Check
|
||||
from_filter parsing / mail.default.from support. """
|
||||
test_server = self.env['ir.mail_server'].create({
|
||||
'from_filter': 'example_2.com, example_3.com',
|
||||
'name': 'Test Server',
|
||||
'smtp_host': 'smtp_host',
|
||||
'smtp_encryption': 'none',
|
||||
})
|
||||
|
||||
# check default.from / filter matching
|
||||
for (default_from, from_filter), expected_test_email in zip(
|
||||
[
|
||||
('notifications@example.com', 'example_2.com, example_3.com'),
|
||||
('notifications', 'example_2.com, example_3.com'),
|
||||
('notifications@example.com', 'dummy.com, full_email@example_2.com, dummy2.com'),
|
||||
('notifications', 'dummy.com, full_email@example_2.com, dummy2.com'),
|
||||
('notifications@example.com', 'example.com'),
|
||||
# default relies on "odoo"
|
||||
(False, 'example.com'),
|
||||
# fallback on user email if no from_filter
|
||||
('example_2.com', ' '),
|
||||
('example_2.com', ','),
|
||||
('example_2.com', False),
|
||||
(False, False),
|
||||
], [
|
||||
'noreply@example_2.com',
|
||||
'notifications@example_2.com',
|
||||
'full_email@example_2.com',
|
||||
'full_email@example_2.com',
|
||||
'notifications@example.com',
|
||||
'odoo@example.com',
|
||||
self.env.user.email,
|
||||
self.env.user.email,
|
||||
self.env.user.email,
|
||||
self.env.user.email,
|
||||
],
|
||||
):
|
||||
with self.subTest(default_from=default_from, from_filter=from_filter):
|
||||
self.env['ir.config_parameter'].set_param('mail.default.from', default_from)
|
||||
test_server.from_filter = from_filter
|
||||
email_from = test_server._get_test_email_from()
|
||||
self.assertEqual(email_from, expected_test_email)
|
||||
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_mail_server_priorities(self):
|
||||
""" Test if we choose the right mail server to send an email.
|
||||
Priorities are
|
||||
1. Forced mail server (e.g.: in mass mailing)
|
||||
- If the "from_filter" of the mail server match the notification email
|
||||
use the notifications email in the "From header"
|
||||
- Otherwise spoof the "From" (because we force the mail server but we don't
|
||||
know which email use to send it)
|
||||
2. A mail server for which the "from_filter" match the "From" header
|
||||
3. A mail server for which the "from_filter" match the domain of the "From" header
|
||||
4. The mail server used for notifications
|
||||
5. A mail server without "from_filter" (and so spoof the "From" header because we
|
||||
do not know for which email address it can be used)
|
||||
"""
|
||||
# this mail server can now be used for a specific email address and 2 domain names
|
||||
self.mail_server_user.from_filter = "domain1.com, specific_user@test.mycompany.com, domain2.com"
|
||||
|
||||
for email_from, (expected_mail_server, expected_email_from) in zip(
|
||||
[
|
||||
# matches user-specific server
|
||||
'specific_user@test.mycompany.com',
|
||||
# matches user-specific server (with formatting') -> should extract
|
||||
# email from full name, must keep the given email_from
|
||||
'"Name name@strange.name" <specific_user@test.mycompany.com>',
|
||||
# case check
|
||||
'SPECIFIC_USER@test.mycompany.com',
|
||||
'specific_user@test.MYCOMPANY.com',
|
||||
# matches domain-based server: domain is case insensitive
|
||||
'unknown_email@test.mycompany.com',
|
||||
'unknown_email@TEST.MYCOMPANY.COM',
|
||||
'"Unknown" <unknown_email@test.mycompany.com>',
|
||||
# fallback on notification email
|
||||
'"Test" <test@unknown_domain.com>',
|
||||
# mail_server_user multiple from_filter check: can be used for a
|
||||
# specific email and 2 domain names -> check other domains in filter
|
||||
'"Example" <test@domain2.com>',
|
||||
'"Example" <test@domain1.com>',
|
||||
], [
|
||||
(self.mail_server_user, 'specific_user@test.mycompany.com'),
|
||||
(self.mail_server_user, '"Name name@strange.name" <specific_user@test.mycompany.com>'),
|
||||
(self.mail_server_user, 'SPECIFIC_USER@test.mycompany.com'),
|
||||
(self.mail_server_user, 'specific_user@test.MYCOMPANY.com'),
|
||||
(self.mail_server_domain, 'unknown_email@test.mycompany.com'),
|
||||
(self.mail_server_domain, 'unknown_email@TEST.MYCOMPANY.COM'),
|
||||
(self.mail_server_domain, '"Unknown" <unknown_email@test.mycompany.com>'),
|
||||
(self.mail_server_notification, 'notifications.test@test.mycompany.com'),
|
||||
# mail_server_user multiple from_filter check
|
||||
(self.mail_server_user, '"Example" <test@domain2.com>'),
|
||||
(self.mail_server_user, '"Example" <test@domain1.com>'),
|
||||
],
|
||||
):
|
||||
with self.subTest(email_from=email_from):
|
||||
mail_server, mail_from = self.env['ir.mail_server']._find_mail_server(email_from=email_from)
|
||||
self.assertEqual(mail_server, expected_mail_server)
|
||||
self.assertEqual(mail_from, expected_email_from)
|
||||
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_mail_server_send_email(self):
|
||||
""" Test main 'send_email' usage: check mail_server choice based on from
|
||||
filters, encapsulation, spoofing. """
|
||||
IrMailServer = self.env['ir.mail_server']
|
||||
|
||||
for mail_from, (expected_smtp_from, expected_msg_from, expected_mail_server) in zip(
|
||||
[
|
||||
'specific_user@test.mycompany.com',
|
||||
'"Name" <test@unknown_domain.com>',
|
||||
'test@unknown_domain.com',
|
||||
'"Name" <unknown_name@test.mycompany.com>'
|
||||
], [
|
||||
# A mail server is configured for the email
|
||||
('specific_user@test.mycompany.com', 'specific_user@test.mycompany.com', self.mail_server_user),
|
||||
# No mail server are configured for the email address, so it will use the
|
||||
# notifications email instead and encapsulate the old email
|
||||
('notifications.test@test.mycompany.com', '"Name" <notifications.test@test.mycompany.com>', self.mail_server_notification),
|
||||
# same situation, but the original email has no name part
|
||||
('notifications.test@test.mycompany.com', '"test" <notifications.test@test.mycompany.com>', self.mail_server_notification),
|
||||
# A mail server is configured for the entire domain name, so we can use the bounce
|
||||
# email address because the mail server supports it
|
||||
(self.default_bounce_address, '"Name" <unknown_name@test.mycompany.com>', self.mail_server_domain),
|
||||
],
|
||||
):
|
||||
# test with and without providing an SMTP session, which should not impact test
|
||||
for provide_smtp in [True, False]:
|
||||
with self.subTest(mail_from=mail_from):
|
||||
with self.mock_smtplib_connection():
|
||||
if provide_smtp:
|
||||
smtp_session = IrMailServer.connect(smtp_from=mail_from)
|
||||
message = self._build_email(mail_from=mail_from)
|
||||
IrMailServer.send_email(message, smtp_session=smtp_session)
|
||||
else:
|
||||
message = self._build_email(mail_from=mail_from)
|
||||
IrMailServer.send_email(message)
|
||||
self.connect_mocked.assert_called_once()
|
||||
self.assertEqual(len(self.emails), 1)
|
||||
self.assertSMTPEmailsSent(
|
||||
smtp_from=expected_smtp_from,
|
||||
message_from=expected_msg_from,
|
||||
mail_server=expected_mail_server,
|
||||
)
|
||||
|
||||
# remove the notification server
|
||||
# so <notifications.test@test.mycompany.com> will use the <test.mycompany.com> mail server
|
||||
# The mail server configured for the notifications email has been removed
|
||||
# but we can still use the mail server configured for test.mycompany.com
|
||||
# and so we will be able to use the bounce address
|
||||
# because we use the mail server for "test.mycompany.com"
|
||||
self.mail_server_notification.unlink()
|
||||
for provide_smtp in [False, True]:
|
||||
with self.mock_smtplib_connection():
|
||||
if provide_smtp:
|
||||
smtp_session = IrMailServer.connect(smtp_from='"Name" <test@unknown_domain.com>')
|
||||
message = self._build_email(mail_from='"Name" <test@unknown_domain.com>')
|
||||
IrMailServer.send_email(message, smtp_session=smtp_session)
|
||||
else:
|
||||
message = self._build_email(mail_from='"Name" <test@unknown_domain.com>')
|
||||
IrMailServer.send_email(message)
|
||||
self.connect_mocked.assert_called_once()
|
||||
self.assertEqual(len(self.emails), 1)
|
||||
self.assertSMTPEmailsSent(
|
||||
smtp_from=self.default_bounce_address,
|
||||
message_from='"Name" <notifications.test@test.mycompany.com>',
|
||||
mail_server=self.mail_server_domain,
|
||||
)
|
||||
|
||||
def test_mail_server_send_email_default_from(self):
|
||||
""" Test the case when the "mail.default.from" contains a full email address
|
||||
and not just the local part the domain of this default email address can be
|
||||
different than the catchall domain """
|
||||
other_default_from = 'notifications.other.test@custom_domain.com'
|
||||
self.env['ir.config_parameter'].sudo().set_param('mail.default.from', other_default_from)
|
||||
custom_server = self.env['ir.mail_server'].create({
|
||||
'from_filter': 'custom_domain.com',
|
||||
'name': 'Custom Domain Server',
|
||||
'smtp_host': 'smtp_host',
|
||||
'smtp_encryption': 'none',
|
||||
})
|
||||
|
||||
for mail_from, (expected_smtp_from, expected_msg_from, expected_mail_server) in zip(
|
||||
[
|
||||
# custom server domain
|
||||
'"Custom Server Name" <customer.test@custom_domain.com>',
|
||||
# unknown domain
|
||||
'"Name" <customer.test@unknown_domain.com>',
|
||||
# notification server domain
|
||||
'"Name" <unknown_name@test.mycompany.com>'
|
||||
], [
|
||||
('customer.test@custom_domain.com', '"Custom Server Name" <customer.test@custom_domain.com>', custom_server),
|
||||
(other_default_from, '"Name" <notifications.other.test@custom_domain.com>', custom_server),
|
||||
(self.default_bounce_address, '"Name" <unknown_name@test.mycompany.com>', self.mail_server_domain),
|
||||
],
|
||||
):
|
||||
with self.subTest(mail_from=mail_from):
|
||||
with self.mock_smtplib_connection():
|
||||
message = self._build_email(mail_from=mail_from)
|
||||
self.env['ir.mail_server'].send_email(message)
|
||||
|
||||
self.assertSMTPEmailsSent(
|
||||
smtp_from=expected_smtp_from,
|
||||
smtp_to_list=['dest@xn--example--i1a.com'],
|
||||
message_from=expected_msg_from,
|
||||
mail_server=expected_mail_server,
|
||||
)
|
||||
@@ -21,7 +21,6 @@ The kernel of Odoo, needed for all installation.
|
||||
'data/report_paperformat_data.xml',
|
||||
'data/res_country_data.xml',
|
||||
'data/ir_demo_data.xml',
|
||||
'data/ir_config_parameter_data.xml',
|
||||
'security/base_groups.xml',
|
||||
'security/base_security.xml',
|
||||
'views/base_menus.xml',
|
||||
|
||||
@@ -202,24 +202,17 @@ class IrMailServer(models.Model):
|
||||
|
||||
def _get_test_email_from(self):
|
||||
self.ensure_one()
|
||||
email_from = False
|
||||
if from_filter_parts := [part.strip() for part in (self.from_filter or '').split(",") if part.strip()]:
|
||||
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 mail_from
|
||||
# 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}@{from_filter_parts[0]}"
|
||||
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
|
||||
# the from_filter is configured for a domain different that the one
|
||||
# of the full email configured in mail.default.from
|
||||
return f"noreply@{from_filter_parts[0]}"
|
||||
# Fallback to current user email if there's no from filter
|
||||
email_from = self.env.user.email
|
||||
# find first found complete email in filter parts
|
||||
email_from = next((email for email in from_filter_parts if "@" in email), False)
|
||||
# no complete email -> consider noreply
|
||||
if not email_from:
|
||||
email_from = f"noreply@{from_filter_parts[0]}"
|
||||
if not email_from:
|
||||
# Fallback to current user email if there's no from filter
|
||||
email_from = self.env.user.email
|
||||
if not email_from or "@" not in email_from:
|
||||
raise UserError(_('Please configure an email on the current user to simulate '
|
||||
'sending an email message via this outgoing server'))
|
||||
return email_from
|
||||
@@ -531,47 +524,20 @@ class IrMailServer(models.Model):
|
||||
|
||||
@api.model
|
||||
def _get_default_bounce_address(self):
|
||||
'''Compute the default bounce address.
|
||||
""" Computes the default bounce address. It is used to set the envelop
|
||||
address if no envelop address is provided in the message.
|
||||
|
||||
The default bounce address is used to set the envelop address if no
|
||||
envelop address is provided in the message. It is formed by properly
|
||||
joining the parameters "mail.bounce.alias" and
|
||||
"mail.catchall.domain".
|
||||
|
||||
If "mail.bounce.alias" is not set it defaults to "postmaster-odoo".
|
||||
|
||||
If "mail.catchall.domain" is not set, return None.
|
||||
|
||||
'''
|
||||
ICP = self.env['ir.config_parameter'].sudo()
|
||||
bounce_alias = ICP.get_param('mail.bounce.alias')
|
||||
domain = ICP.get_param('mail.catchall.domain')
|
||||
if bounce_alias and domain:
|
||||
return '%s@%s' % (bounce_alias, domain)
|
||||
return
|
||||
:return str/None: defaults to the ``--email-from`` CLI/config parameter.
|
||||
"""
|
||||
return tools.config.get("email_from")
|
||||
|
||||
@api.model
|
||||
def _get_default_from_address(self):
|
||||
"""Compute the default from address.
|
||||
""" Computes the default from address. It is used for the "header from"
|
||||
address when no other has been received.
|
||||
|
||||
Used for the "header from" address when no other has been received.
|
||||
|
||||
:return str/None:
|
||||
If the config parameter ``mail.default.from`` contains
|
||||
a full email address, return it.
|
||||
Otherwise, combines config parameters ``mail.default.from`` and
|
||||
``mail.catchall.domain`` to generate a default sender address.
|
||||
|
||||
If some of those parameters is not defined, it will default to the
|
||||
``--email-from`` CLI/config parameter.
|
||||
:return str/None: defaults to the ``--email-from`` CLI/config parameter.
|
||||
"""
|
||||
get_param = self.env['ir.config_parameter'].sudo().get_param
|
||||
email_from = get_param("mail.default.from")
|
||||
if email_from and "@" in email_from:
|
||||
return email_from
|
||||
domain = get_param("mail.catchall.domain")
|
||||
if email_from and domain:
|
||||
return "%s@%s" % (email_from, domain)
|
||||
return tools.config.get("email_from")
|
||||
|
||||
def _prepare_email_message(self, message, smtp_session):
|
||||
|
||||
@@ -399,13 +399,7 @@ class MockSmtplibCase:
|
||||
|
||||
@classmethod
|
||||
def _init_mail_gateway(cls):
|
||||
cls.alias_bounce = 'bounce.test'
|
||||
cls.alias_domain = 'test.mycompany.com'
|
||||
cls.default_from = 'notifications.test'
|
||||
cls.default_from_filter = False
|
||||
cls.env['ir.config_parameter'].sudo().set_param('mail.catchall.domain', cls.alias_domain)
|
||||
cls.env['ir.config_parameter'].sudo().set_param('mail.default.from', cls.default_from)
|
||||
cls.env['ir.config_parameter'].sudo().set_param('mail.bounce.alias', cls.alias_bounce)
|
||||
cls.env['ir.config_parameter'].sudo().set_param('mail.default.from_filter', cls.default_from_filter)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -36,32 +36,13 @@ class _FakeSMTP:
|
||||
class EmailConfigCase(TransactionCase):
|
||||
|
||||
@patch.dict(config.options, {"email_from": "settings@example.com"})
|
||||
def test_default_email_from(self, *args):
|
||||
"""Email from setting is respected."""
|
||||
ICP = self.env["ir.config_parameter"].sudo()
|
||||
for (icp_from, icp_domain), expected_from in zip(
|
||||
[
|
||||
('icp', 'test.mycompany.com'),
|
||||
('icp', False),
|
||||
('icp@another.company.com', 'test.mycompany.com'),
|
||||
('icp@another.company.com', False),
|
||||
(False, 'test.mycompany.com'),
|
||||
], [
|
||||
"icp@test.mycompany.com",
|
||||
"settings@example.com",
|
||||
"icp@another.company.com",
|
||||
"icp@another.company.com",
|
||||
"settings@example.com",
|
||||
],
|
||||
):
|
||||
with self.subTest(icp_from=icp_from, icp_domain=icp_domain):
|
||||
ICP.set_param("mail.default.from", icp_from)
|
||||
ICP.set_param("mail.catchall.domain", icp_domain)
|
||||
message = self.env["ir.mail_server"].build_email(
|
||||
False, "recipient@example.com", "Subject",
|
||||
"The body of an email",
|
||||
)
|
||||
self.assertEqual(message["From"], expected_from)
|
||||
def test_default_email_from(self):
|
||||
""" Email from setting is respected and comes from configuration. """
|
||||
message = self.env["ir.mail_server"].build_email(
|
||||
False, "recipient@example.com", "Subject",
|
||||
"The body of an email",
|
||||
)
|
||||
self.assertEqual(message["From"], "settings@example.com")
|
||||
|
||||
|
||||
@tagged('mail_server')
|
||||
@@ -70,19 +51,16 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls._init_mail_gateway()
|
||||
# TEMPORARY
|
||||
for icp in {'mail.default.from', 'mail.catchall.alias', 'mail.bounce.alias', 'mail.catchall.domain', 'mail.default.from_filter'}:
|
||||
cls.env['ir.config_parameter'].sudo().set_param(icp, False)
|
||||
cls._init_mail_servers()
|
||||
|
||||
cls.default_bounce_address = f'{cls.alias_bounce}@{cls.alias_domain}'
|
||||
cls.default_from_address = f'{cls.default_from}@{cls.alias_domain}'
|
||||
cls.default_bounce_address = 'CACA'
|
||||
cls.default_from_address = 'PROUT'
|
||||
|
||||
def test_assert_base_values(self):
|
||||
self.assertEqual(
|
||||
self.env['ir.mail_server']._get_default_bounce_address(),
|
||||
self.default_bounce_address)
|
||||
self.assertEqual(
|
||||
self.env['ir.mail_server']._get_default_from_address(),
|
||||
self.default_from_address)
|
||||
self.assertFalse(self.env['ir.mail_server']._get_default_bounce_address())
|
||||
self.assertFalse(self.env['ir.mail_server']._get_default_from_address())
|
||||
|
||||
def test_bpo_34424_35805(self):
|
||||
"""Ensure all email sent are bpo-34424 and bpo-35805 free"""
|
||||
@@ -164,44 +142,31 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase):
|
||||
|
||||
@users('admin')
|
||||
def test_mail_server_get_test_email_from(self):
|
||||
""" Test the email used to test the mail server connection. """
|
||||
""" Test the email used to test the mail server connection. Check
|
||||
from_filter parsing / default fallback value. """
|
||||
test_server = self.env['ir.mail_server'].create({
|
||||
'from_filter': 'example_2.com, example_3.com',
|
||||
'name': 'Test Server',
|
||||
'smtp_host': 'smtp_host',
|
||||
'smtp_encryption': 'none',
|
||||
})
|
||||
|
||||
# check default.from / filter matching
|
||||
for (default_from, from_filter), expected_test_email in zip(
|
||||
for from_filter, expected_test_email in zip(
|
||||
[
|
||||
('notifications@example.com', 'example_2.com, example_3.com'),
|
||||
('notifications', 'example_2.com, example_3.com'),
|
||||
('notifications@example.com', 'dummy.com, full_email@example_2.com, dummy2.com'),
|
||||
('notifications', 'dummy.com, full_email@example_2.com, dummy2.com'),
|
||||
('notifications@example.com', 'example.com'),
|
||||
# default relies on "odoo"
|
||||
(False, 'example.com'),
|
||||
# fallback on user email if no from_filter
|
||||
('example_2.com', ' '),
|
||||
('example_2.com', ','),
|
||||
('example_2.com', False),
|
||||
(False, False),
|
||||
'example_2.com, example_3.com',
|
||||
'dummy.com, full_email@example_2.com, dummy2.com',
|
||||
# fallback on user's email
|
||||
' ',
|
||||
',',
|
||||
False,
|
||||
], [
|
||||
'noreply@example_2.com',
|
||||
'notifications@example_2.com',
|
||||
'full_email@example_2.com',
|
||||
'full_email@example_2.com',
|
||||
'notifications@example.com',
|
||||
'odoo@example.com',
|
||||
self.env.user.email,
|
||||
self.env.user.email,
|
||||
self.env.user.email,
|
||||
self.env.user.email,
|
||||
],
|
||||
):
|
||||
with self.subTest(default_from=default_from, from_filter=from_filter):
|
||||
self.env['ir.config_parameter'].set_param('mail.default.from', default_from)
|
||||
with self.subTest(from_filter=from_filter):
|
||||
test_server.from_filter = from_filter
|
||||
email_from = test_server._get_test_email_from()
|
||||
self.assertEqual(email_from, expected_test_email)
|
||||
@@ -236,73 +201,9 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase):
|
||||
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_mail_server_priorities(self):
|
||||
""" Test if we choose the right mail server to send an email.
|
||||
|
||||
Priorities are
|
||||
1. Forced mail server (e.g.: in mass mailing)
|
||||
- If the "from_filter" of the mail server match the notification email
|
||||
use the notifications email in the "From header"
|
||||
- Otherwise spoof the "From" (because we force the mail server but we don't
|
||||
know which email use to send it)
|
||||
2. A mail server for which the "from_filter" match the "From" header
|
||||
3. A mail server for which the "from_filter" match the domain of the "From" header
|
||||
4. The mail server used for notifications
|
||||
5. A mail server without "from_filter" (and so spoof the "From" header because we
|
||||
do not know for which email address it can be used)
|
||||
"""
|
||||
# sanity checks
|
||||
self.assertTrue(self.env['ir.mail_server']._get_default_from_address(), 'Notifications email must be set for testing')
|
||||
self.assertTrue(self.env['ir.mail_server']._get_default_bounce_address(), 'Bounce email must be set for testing')
|
||||
# this mail server can not be used for a specific email address and 2 domain names
|
||||
self.mail_server_user.from_filter = "domain1.com, specific_user@test.mycompany.com, domain2.com"
|
||||
|
||||
for email_from, (expected_mail_server, expected_email_from) in zip(
|
||||
[
|
||||
# matches user-specific server
|
||||
'specific_user@test.mycompany.com',
|
||||
# matches user-specific server (with formatting') -> should extract
|
||||
# email from full name, must keep the given email_from
|
||||
'"Name name@strange.name" <specific_user@test.mycompany.com>',
|
||||
# case check
|
||||
'SPECIFIC_USER@test.mycompany.com',
|
||||
'specific_user@test.MYCOMPANY.com',
|
||||
# matches domain-based server: domain is case insensitive
|
||||
'unknown_email@test.mycompany.com',
|
||||
'unknown_email@TEST.MYCOMPANY.COM',
|
||||
'"Unknown" <unknown_email@test.mycompany.com>',
|
||||
# fallback on notification email
|
||||
'"Test" <test@unknown_domain.com>',
|
||||
# mail_server_user multiple from_filter check: can be used for a
|
||||
# specific email and 2 domain names -> check other domains in filter
|
||||
'"Example" <test@domain2.com>',
|
||||
'"Example" <test@domain1.com>',
|
||||
], [
|
||||
(self.mail_server_user, 'specific_user@test.mycompany.com'),
|
||||
(self.mail_server_user, '"Name name@strange.name" <specific_user@test.mycompany.com>'),
|
||||
(self.mail_server_user, 'SPECIFIC_USER@test.mycompany.com'),
|
||||
(self.mail_server_user, 'specific_user@test.MYCOMPANY.com'),
|
||||
(self.mail_server_domain, 'unknown_email@test.mycompany.com'),
|
||||
(self.mail_server_domain, 'unknown_email@TEST.MYCOMPANY.COM'),
|
||||
(self.mail_server_domain, '"Unknown" <unknown_email@test.mycompany.com>'),
|
||||
(self.mail_server_notification, 'notifications.test@test.mycompany.com'),
|
||||
# mail_server_user multiple from_filter check
|
||||
(self.mail_server_user, '"Example" <test@domain2.com>'),
|
||||
(self.mail_server_user, '"Example" <test@domain1.com>'),
|
||||
],
|
||||
):
|
||||
with self.subTest(email_from=email_from):
|
||||
mail_server, mail_from = self.env['ir.mail_server']._find_mail_server(email_from=email_from)
|
||||
self.assertEqual(mail_server, expected_mail_server)
|
||||
self.assertEqual(mail_from, expected_email_from)
|
||||
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_mail_server_priorities_base_config(self):
|
||||
""" Test if we choose the right mail server to send an email, without
|
||||
ICP configuration. Simulates unconfigured Odoo DB so we have to spoof
|
||||
the FROM otherwise we cannot send any email. """
|
||||
self.env['ir.config_parameter'].sudo().set_param('mail.catchall.domain', False)
|
||||
self.env['ir.config_parameter'].sudo().set_param('mail.default.from', False)
|
||||
|
||||
""" Test if we choose the right mail server to send an email. Simulates
|
||||
simple Odoo DB so we have to spoof the FROM otherwise we cannot send
|
||||
any email. """
|
||||
for email_from, (expected_mail_server, expected_email_from) in zip(
|
||||
[
|
||||
'specific_user@test.mycompany.com',
|
||||
@@ -337,12 +238,12 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase):
|
||||
('specific_user@test.mycompany.com', 'specific_user@test.mycompany.com', self.mail_server_user),
|
||||
# No mail server are configured for the email address, so it will use the
|
||||
# notifications email instead and encapsulate the old email
|
||||
('notifications.test@test.mycompany.com', '"Name" <notifications.test@test.mycompany.com>', self.mail_server_notification),
|
||||
('test@unknown_domain.com', '"Name" <test@unknown_domain.com>', self.mail_server_default),
|
||||
# same situation, but the original email has no name part
|
||||
('notifications.test@test.mycompany.com', '"test" <notifications.test@test.mycompany.com>', self.mail_server_notification),
|
||||
('test@unknown_domain.com', 'test@unknown_domain.com', self.mail_server_default),
|
||||
# A mail server is configured for the entire domain name, so we can use the bounce
|
||||
# email address because the mail server supports it
|
||||
(self.default_bounce_address, '"Name" <unknown_name@test.mycompany.com>', self.mail_server_domain),
|
||||
('unknown_name@test.mycompany.com', '"Name" <unknown_name@test.mycompany.com>', self.mail_server_domain),
|
||||
]
|
||||
):
|
||||
# test with and without providing an SMTP session, which should not impact test
|
||||
@@ -385,9 +286,9 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase):
|
||||
self.connect_mocked.assert_called_once()
|
||||
self.assertEqual(len(self.emails), 1)
|
||||
self.assertSMTPEmailsSent(
|
||||
smtp_from=self.default_bounce_address,
|
||||
message_from='"Name" <notifications.test@test.mycompany.com>',
|
||||
mail_server=self.mail_server_domain,
|
||||
smtp_from='test@unknown_domain.com',
|
||||
message_from='"Name" <test@unknown_domain.com>',
|
||||
from_filter=False,
|
||||
)
|
||||
|
||||
@mute_logger('odoo.models.unlink')
|
||||
@@ -400,52 +301,12 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase):
|
||||
|
||||
self.assertEqual(len(self.emails), 1)
|
||||
self.assertSMTPEmailsSent(
|
||||
smtp_from=f'{self.alias_bounce}@xn--9caaaaaaa.com',
|
||||
smtp_from='test@xn--9caaaaaaa.com',
|
||||
smtp_to_list=['dest@xn--example--i1a.com'],
|
||||
message_from='test@=?utf-8?b?w6nDqcOpw6nDqcOpw6k=?=.com',
|
||||
from_filter=False,
|
||||
)
|
||||
|
||||
@mute_logger('odoo.models.unlink')
|
||||
def test_mail_server_send_email_default_from(self):
|
||||
""" Test the case when the "mail.default.from" contains a full email address
|
||||
and not just the local part the domain of this default email address can be
|
||||
different than the catchall domain """
|
||||
other_default_from = 'notifications.other.test@custom_domain.com'
|
||||
self.env['ir.config_parameter'].sudo().set_param('mail.default.from', other_default_from)
|
||||
custom_server = self.env['ir.mail_server'].create({
|
||||
'from_filter': 'custom_domain.com',
|
||||
'name': 'Custom Domain Server',
|
||||
'smtp_host': 'smtp_host',
|
||||
'smtp_encryption': 'none',
|
||||
})
|
||||
|
||||
for mail_from, (expected_smtp_from, expected_msg_from, expected_mail_server) in zip(
|
||||
[
|
||||
# custom server domain
|
||||
'"Custom Server Name" <customer.test@custom_domain.com>',
|
||||
# unknown domain
|
||||
'"Name" <customer.test@unknown_domain.com>',
|
||||
# notification server domain
|
||||
'"Name" <unknown_name@test.mycompany.com>'
|
||||
], [
|
||||
('customer.test@custom_domain.com', '"Custom Server Name" <customer.test@custom_domain.com>', custom_server),
|
||||
(other_default_from, '"Name" <notifications.other.test@custom_domain.com>', custom_server),
|
||||
(self.default_bounce_address, '"Name" <unknown_name@test.mycompany.com>', self.mail_server_domain),
|
||||
],
|
||||
):
|
||||
with self.subTest(mail_from=mail_from):
|
||||
with self.mock_smtplib_connection():
|
||||
message = self._build_email(mail_from=mail_from)
|
||||
self.env['ir.mail_server'].send_email(message)
|
||||
|
||||
self.assertSMTPEmailsSent(
|
||||
smtp_from=expected_smtp_from,
|
||||
smtp_to_list=['dest@xn--example--i1a.com'],
|
||||
message_from=expected_msg_from,
|
||||
mail_server=expected_mail_server,
|
||||
)
|
||||
|
||||
@mute_logger('odoo.models.unlink')
|
||||
@patch.dict(config.options, {
|
||||
"from_filter": "dummy@example.com, test.mycompany.com, dummy2@example.com",
|
||||
@@ -477,15 +338,13 @@ class TestIrMailServer(TransactionCase, MockSmtplibCase):
|
||||
'"Formatted Name" <test@unknown_domain.com>',
|
||||
], [
|
||||
# inside "from_filter" domain: no rewriting
|
||||
(self.default_bounce_address, 'specific_user@test.mycompany.com'),
|
||||
(self.default_bounce_address, '"Formatted Name" <specific_user@test.mycompany.com>'),
|
||||
(self.default_bounce_address, '"Formatted Name" <specific_user@test.MYCOMPANY.com>'),
|
||||
(self.default_bounce_address, '"Formatted Name" <SPECIFIC_USER@test.mycompany.com>'),
|
||||
# outside "from_filter" domain: we will use notifications emails in the
|
||||
# headers, and bounce address in the envelope because the "from_filter"
|
||||
# allows to use the entire domain
|
||||
(self.default_bounce_address, '"test" <notifications.test@test.mycompany.com>'),
|
||||
(self.default_bounce_address, '"Formatted Name" <notifications.test@test.mycompany.com>'),
|
||||
('specific_user@test.mycompany.com', 'specific_user@test.mycompany.com'),
|
||||
('specific_user@test.mycompany.com', '"Formatted Name" <specific_user@test.mycompany.com>'),
|
||||
('specific_user@test.MYCOMPANY.com', '"Formatted Name" <specific_user@test.MYCOMPANY.com>'),
|
||||
('SPECIFIC_USER@test.mycompany.com', '"Formatted Name" <SPECIFIC_USER@test.mycompany.com>'),
|
||||
# outside "from_filter" domain: spoofing, as fallback email can be found
|
||||
('test@unknown_domain.com', 'test@unknown_domain.com'),
|
||||
('test@unknown_domain.com', '"Formatted Name" <test@unknown_domain.com>'),
|
||||
]
|
||||
):
|
||||
for provide_smtp in [False, True]: # providing smtp session should ont impact test
|
||||
|
||||
Reference in New Issue
Block a user