From 1ca67905b4e456b57541d80188bc53b25e1282d9 Mon Sep 17 00:00:00 2001 From: Florent de Labarre Date: Thu, 26 Aug 2021 08:12:35 +0000 Subject: [PATCH 1/5] [FIX] mail: handle wrong Final-Recipient header in incoming emails Some emails are wrongly formatted mainly due to old servers. If Final-Recipient header is void or wrongly encoded it currently crashes. This fix ensure there is no crash, even if bounce detection could be incomplete. Task-2641572 PR odoo/odoo#76159 Closes odoo/odoo#75618 X-original-commit: f437967a1fa4fca56c88e1cf79586805101ab712 Part-of: odoo/odoo#77005 Co-authored-by: Thibault Delavallee --- addons/mail/models/mail_thread.py | 4 +- addons/test_mail/data/test_mail_data.py | 50 +++++++++++++++++++++ addons/test_mail/tests/test_mail_gateway.py | 5 +++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index 5c9dee9ae8e..1ed1e71e575 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -1321,7 +1321,9 @@ class MailThread(models.AbstractModel): if dsn_part and len(dsn_part.get_payload()) > 1: dsn = dsn_part.get_payload()[1] final_recipient_data = tools.decode_message_header(dsn, 'Final-Recipient') - bounced_email = tools.email_normalize(final_recipient_data.split(';', 1)[1].strip()) + # old servers may hold void or invalid Final-Recipient header + if final_recipient_data and ";" in final_recipient_data: + bounced_email = tools.email_normalize(final_recipient_data.split(';', 1)[1].strip()) if bounced_email: bounced_partner = self.env['res.partner'].sudo().search([('email_normalized', '=', bounced_email)]) diff --git a/addons/test_mail/data/test_mail_data.py b/addons/test_mail/data/test_mail_data.py index d6e38846930..9b7856732bf 100644 --- a/addons/test_mail/data/test_mail_data.py +++ b/addons/test_mail/data/test_mail_data.py @@ -883,3 +883,53 @@ OyI+T2RvbzwvYT4uCjwvcD4KPC9kaXY+CiAgICAgICAg --92726A5F09.1555335666/mail2.test.ironsky-- """ + +MAIL_NO_FINAL_RECIPIENT = """\ +Return-Path: +Delivered-To: catchall@xxxx.xxxx +Received: from in58.mail.ovh.net (unknown [10.101.4.58]) + by vr46.mail.ovh.net (Postfix) with ESMTP id 4GvFsq2QLYz1t0N7r + for ; Tue, 24 Aug 2021 17:07:43 +0000 (UTC) +Received-SPF: Softfail (mailfrom) identity=mailfrom; client-ip=46.105.72.169; helo=40.mo36.mail-out.ovh.net; envelope-from=bounce-md_9656353.6125275c.v1-f28f7746389e45f0bfbf9faefe9e0dc8@mandrillapp.com; receiver=catchall@xxxx.xxxx +Authentication-Results: in58.mail.ovh.net; + dkim=pass (1024-bit key; unprotected) header.d=mandrillapp.com header.i=bounces-noreply@mandrillapp.com header.b="TDzUcdJs"; + dkim=pass (1024-bit key) header.d=mandrillapp.com header.i=@mandrillapp.com header.b="MyjddTY5"; + dkim-atps=neutral +Delivered-To: xxxx.xxxx-{email_to} +Authentication-Results: in62.mail.ovh.net; + dkim=pass (1024-bit key; unprotected) header.d=mandrillapp.com header.i=bounces-noreply@mandrillapp.com header.b="TDzUcdJs"; + dkim=pass (1024-bit key) header.d=mandrillapp.com header.i=@mandrillapp.com header.b="MyjddTY5"; + dkim-atps=neutral +From: MAILER-DAEMON +Subject: Undelivered Mail Returned to Sender +To: {email_to} +X-Report-Abuse: Please forward a copy of this message, including all headers, to abuse@mandrill.com +X-Report-Abuse: You can also report abuse here: http://mandrillapp.com/contact/abuse?id=9656353.f28f7746389e45f0bfbf9faefe9e0dc8 +X-Mandrill-User: md_9656353 +Feedback-ID: 9656353:9656353.20210824:md +Message-Id: <9656353.20210824170740.6125275cf21879.17950539@mail9.us4.mandrillapp.com> +Date: Tue, 24 Aug 2021 17:07:40 +0000 +MIME-Version: 1.0 +Content-Type: multipart/report; boundary="_av-UfLe6y6qxNo54-urtAxbJQ" + +--_av-UfLe6y6qxNo54-urtAxbJQ +Content-Type: text/plain; charset=utf-8 +Content-Transfer-Encoding: 7bit + + --- The following addresses had delivery problems --- + +<{email_from}> (5.7.1 <{email_from}>: Recipient address rejected: Access denied) + + +--_av-UfLe6y6qxNo54-urtAxbJQ +Content-Type: message/delivery-status +Content-Transfer-Encoding: 7bit + +Original-Recipient: <{email_from}> +Action: failed +Diagnostic-Code: smtp; 554 5.7.1 <{email_from}>: Recipient address rejected: Access denied +Remote-MTA: 10.245.192.40 + + + +--_av-UfLe6y6qxNo54-urtAxbJQ--""" diff --git a/addons/test_mail/tests/test_mail_gateway.py b/addons/test_mail/tests/test_mail_gateway.py index 0720a9c9eb7..58b490886ac 100644 --- a/addons/test_mail/tests/test_mail_gateway.py +++ b/addons/test_mail/tests/test_mail_gateway.py @@ -55,6 +55,11 @@ class TestEmailParsing(TestMailCommon): res = self.env['mail.thread'].message_parse(self.from_string(test_mail_data.MAIL_MULTIPART_WEIRD_FILENAME)) self.assertEqual(res['attachments'][0][0], '62_@;,][)=.(ÇÀÉ.txt') + def test_message_parse_bugs(self): + """ Various corner cases or message parsing """ + # message without Final-Recipient + self.env['mail.thread'].message_parse(self.from_string(test_mail_data.MAIL_NO_FINAL_RECIPIENT)) + def test_message_parse_eml(self): # Test that the parsing of mail with embedded emails as eml(msg) which generates empty attachments, can be processed. mail = self.format(test_mail_data.MAIL_EML_ATTACHMENT, email_from='"Sylvie Lelitre" ', to='generic@test.com') From a8518ae75d987aa4ad9d232376ccc81c39d84cc2 Mon Sep 17 00:00:00 2001 From: Florent de Labarre Date: Thu, 26 Aug 2021 09:24:17 +0000 Subject: [PATCH 2/5] [FIX] mail: handle parsing of void body in incoming emails When body does not contain any tag or content parsing currently fails with an ``lxml.etree.ParserError``. To avoid that we can improve condition about void body: stripping void characters allows to avoid that traceback. Task-2641572 PR odoo#76159 Closes odoo#75625 X-original-commit: 708fe3e74991c144a05c6bdcafa1931672e36ce9 Part-of: odoo/odoo#77005 Co-authored-by: Thibault Delavallee --- addons/mail/models/mail_thread.py | 4 +-- addons/test_mail/data/test_mail_data.py | 36 +++++++++++++++++++++ addons/test_mail/tests/test_mail_gateway.py | 4 +++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index 1ed1e71e575..462ec514378 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -1199,8 +1199,8 @@ class MailThread(models.AbstractModel): Indeed those aspects should be covered by the html_sanitize method located in tools. """ body, attachments = payload_dict['body'], payload_dict['attachments'] - if not body: - return payload_dict + if not body.strip(): + return {'body': body, 'attachments': attachments} try: root = lxml.html.fromstring(body) except ValueError: diff --git a/addons/test_mail/data/test_mail_data.py b/addons/test_mail/data/test_mail_data.py index 9b7856732bf..c7fd5cb853e 100644 --- a/addons/test_mail/data/test_mail_data.py +++ b/addons/test_mail/data/test_mail_data.py @@ -884,6 +884,42 @@ OyI+T2RvbzwvYT4uCjwvcD4KPC9kaXY+CiAgICAgICAg --92726A5F09.1555335666/mail2.test.ironsky-- """ +MAIL_NO_BODY = '''\ +Return-Path: <{email_from}> +Delivered-To: catchall@xxxx.xxxx +Received: from in66.mail.ovh.net (unknown [10.101.4.66]) + by vr38.mail.ovh.net (Postfix) with ESMTP id 4GLCGr70Kyz1myr75 + for ; Thu, 8 Jul 2021 10:30:12 +0000 (UTC) +X-Comment: SPF check N/A for local connections - client-ip=213.186.33.59; helo=mail663.ha.ovh.net; envelope-from={email_from}; receiver=catchall@xxxx.xxxx +Authentication-Results: in66.mail.ovh.net; dkim=none; dkim-atps=neutral +Delivered-To: xxxx.xxxx-{email_to} +X-ME-Helo: opme11oxm23aub.bagnolet.francetelecom.fr +X-ME-Auth: ZnJlZGVyaWMuYmxhY2hvbjA3QG9yYW5nZS5mcg== +X-ME-Date: Thu, 08 Jul 2021 12:30:11 +0200 +X-ME-IP: 86.221.151.111 +Date: Thu, 8 Jul 2021 12:30:11 +0200 (CEST) +From: =?UTF-8?Q?Fr=C3=A9d=C3=A9ric_BLACHON?= <{email_from}> +Reply-To: + =?UTF-8?Q?Fr=C3=A9d=C3=A9ric_BLACHON?= <{email_from}> +To: {email_to} +Message-ID: <1024471522.82574.1625740211606.JavaMail.open-xchange@opme11oxm23aub.bagnolet.francetelecom.fr> +Subject: transport autorisation 19T +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="----=_Part_82573_178179506.1625740211587" + +------=_Part_82573_178179506.1625740211587 +MIME-Version: 1.0 +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: 7bit + + + + + + +''' + MAIL_NO_FINAL_RECIPIENT = """\ Return-Path: Delivered-To: catchall@xxxx.xxxx diff --git a/addons/test_mail/tests/test_mail_gateway.py b/addons/test_mail/tests/test_mail_gateway.py index 58b490886ac..d58e57e10d1 100644 --- a/addons/test_mail/tests/test_mail_gateway.py +++ b/addons/test_mail/tests/test_mail_gateway.py @@ -60,6 +60,10 @@ class TestEmailParsing(TestMailCommon): # message without Final-Recipient self.env['mail.thread'].message_parse(self.from_string(test_mail_data.MAIL_NO_FINAL_RECIPIENT)) + # message with empty body (including only void characters) + res = self.env['mail.thread'].message_parse(self.from_string(test_mail_data.MAIL_NO_BODY)) + self.assertEqual(res['body'], '\n \n', 'Gateway should not crash with void content') + def test_message_parse_eml(self): # Test that the parsing of mail with embedded emails as eml(msg) which generates empty attachments, can be processed. mail = self.format(test_mail_data.MAIL_EML_ATTACHMENT, email_from='"Sylvie Lelitre" ', to='generic@test.com') From 223b51c462ec96f45d618b867a6f3d35ae5b779e Mon Sep 17 00:00:00 2001 From: Jinal Patel Date: Fri, 20 Aug 2021 07:14:09 +0000 Subject: [PATCH 3/5] [FIX] mail: fix ACLs issue with mail composer in new mode As create_uid has no value on mail.compose.message model when being in onchange or new mode, 'Mail Compose Message Rule' record rule may crash. In this commit we fix that issue by adding a value for create_uid. An unit test is added to ensure it effectively fixes the use case. Steps to reproduce this warning: 1. Create automated action for the 'mail.compose.message' model 2. Try to open 'Email compose Wizard' Warning: "Due to security restrictions, you are not allowed to modify 'Email composition wizard' (mail.compose.message) records. Records: mail.compose.message,NewId_0x7f8e99762310 (id=NewId_0x7f8e99762310) User: USERNAME (id=2) This restriction is due to the following rules: Contact your administrator to request access if necessary." Task-2641572 opw-2628005 PR odoo#76159 Closes#75369 X-original-commit: 26d64c0b84b76bb9164a8895bcc21b66754478ae Part-of: odoo/odoo#77005 Co-authored-by: Thibault Delavallee --- addons/base_automation/tests/__init__.py | 2 + .../tests/test_mail_composer.py | 39 +++++++++++++++++++ addons/mail/tests/test_mail_full_composer.py | 2 +- addons/mail/wizard/mail_compose_message.py | 4 ++ 4 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 addons/base_automation/tests/test_mail_composer.py diff --git a/addons/base_automation/tests/__init__.py b/addons/base_automation/tests/__init__.py index 92a8a4959d9..a6533bd6153 100644 --- a/addons/base_automation/tests/__init__.py +++ b/addons/base_automation/tests/__init__.py @@ -1,3 +1,5 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. + from . import test_automation +from . import test_mail_composer diff --git a/addons/base_automation/tests/test_mail_composer.py b/addons/base_automation/tests/test_mail_composer.py new file mode 100644 index 00000000000..2609466facd --- /dev/null +++ b/addons/base_automation/tests/test_mail_composer.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.tests.common import tagged, HttpCase + + +@tagged('-at_install', 'post_install', 'mail_composer') +class TestMailFullComposer(HttpCase): + + def test_full_composer_tour(self): + self.env['mail.template'].create({ + 'name': 'Test template', # name hardcoded for test + 'partner_to': '${object.id}', + 'lang': '${object.lang}', + 'auto_delete': True, + 'model_id': self.ref('base.model_res_partner'), + }) + test_user = self.env['res.users'].create({ + 'email': 'testuser@testuser.com', + 'groups_id': [ + (6, 0, [self.ref('base.group_user'), self.ref('base.group_partner_manager')]), + ], + 'name': 'Test User', + 'login': 'testuser', + 'password': 'testuser', + }) + + automated_action = self.env['base.automation'].create({ + 'name': 'Test', + 'active': True, + 'trigger': 'on_change', + 'on_change_field_ids': (4, self.ref('mail.field_mail_compose_message__template_id'),), + 'state': 'code', + 'model_id': self.env.ref('mail.model_mail_compose_message').id, + }) + + self.start_tour("/web#id=%d&model=res.partner" % test_user.partner_id, 'mail/static/tests/tours/mail_full_composer_test_tour.js', login='testuser') + + automated_action.unlink() diff --git a/addons/mail/tests/test_mail_full_composer.py b/addons/mail/tests/test_mail_full_composer.py index 1456dba505a..0921a698f8f 100644 --- a/addons/mail/tests/test_mail_full_composer.py +++ b/addons/mail/tests/test_mail_full_composer.py @@ -5,7 +5,7 @@ from odoo.tests.common import tagged, HttpCase from odoo import Command -@tagged('-at_install', 'post_install') +@tagged('-at_install', 'post_install', 'mail_composer') class TestMailFullComposer(HttpCase): def test_full_composer_tour(self): diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index f9f032e04f7..9415fa2c543 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -75,6 +75,10 @@ class MailComposer(models.TransientModel): if result.get('composition_mode') == 'comment' and (set(fields) & set(['model', 'res_id', 'partner_ids', 'record_name', 'subject'])): result.update(self.get_record_data(result)) + # when being in new mode, create_uid is not granted -> ACLs issue may arise + if 'create_uid' in fields and 'create_uid' not in result: + result['create_uid'] = self.env.uid + filtered_result = dict((fname, result[fname]) for fname in result if fname in fields) return filtered_result From 1efeffd2977885aff22ec9c2564c0e8d53de855a Mon Sep 17 00:00:00 2001 From: Jairo Llopis Date: Thu, 8 Jul 2021 10:12:41 +0000 Subject: [PATCH 4/5] [FW][IMP] test_mail: test models with type do not mess with attachment types Purpose of this commit is to highlight an issue that may happens easily with `crm` that is made generic here within `test_mail`. `crm` alters the context when creating a new record adding in this case `default_type` to it][1]. The returned record contains that altered context. his results in other records created from it trying to assign that same default value for `type`. This is a very common name for fields, and happens to exist in `ir.attachment` too. If you create an alias for incoming leads in your DB with default values `{"type": "lead"}` (something very common) and then an email comes to that alias that contains an inlined base64 image, the attachment creation process would simply fail. Obtained error is ``ValueError: Wrong value for ir.attachment.type: 'lead'`` . [1]: https://github.com/odoo/odoo/blob/272602193f5647f7f2270ed6ec68777625a139dd/addons/crm/models/crm_lead.py#L310-L311 X-original-commit: 99434b2e8528c10fcc9cb6860765e0ddcaa364c8 Part-of: odoo/odoo#77005 Co-authored-by: Thibault Delavallee --- addons/mail/tests/common.py | 14 ++++-- addons/test_mail/data/test_mail_data.py | 44 +++++++++++++++++ addons/test_mail/models/__init__.py | 3 +- .../models/test_mail_corner_case_models.py | 22 +++++++++ addons/test_mail/security/ir.model.access.csv | 2 + addons/test_mail/tests/test_mail_gateway.py | 49 +++++++++++++++++++ 6 files changed, 129 insertions(+), 5 deletions(-) diff --git a/addons/mail/tests/common.py b/addons/mail/tests/common.py index a2bd6db544a..67883fdc223 100644 --- a/addons/mail/tests/common.py +++ b/addons/mail/tests/common.py @@ -109,20 +109,26 @@ class MockEmail(common.BaseCase): def format(self, template, to='groups@example.com, other@gmail.com', subject='Frogs', email_from='Sylvie Lelitre ', return_path='', cc='', - extra='', msg_id='<1198923581.41972151344608186760.JavaMail@agrolait.com>'): + extra='', msg_id='<1198923581.41972151344608186760.JavaMail@agrolait.com>', + **kwargs): return template.format( subject=subject, to=to, cc=cc, email_from=email_from, return_path=return_path, - extra=extra, msg_id=msg_id) + extra=extra, msg_id=msg_id, + **kwargs) def format_and_process(self, template, email_from, to, subject='Frogs', cc='', return_path='', extra='', msg_id=False, - model=None, target_model='mail.test.gateway', target_field='name'): + model=None, target_model='mail.test.gateway', target_field='name', + **kwargs): self.assertFalse(self.env[target_model].search([(target_field, '=', subject)])) if not msg_id: msg_id = "<%.7f-test@iron.sky>" % (time.time()) - mail = self.format(template, to=to, subject=subject, cc=cc, return_path=return_path, extra=extra, email_from=email_from, msg_id=msg_id) + mail = self.format(template, to=to, subject=subject, cc=cc, + return_path=return_path, extra=extra, + email_from=email_from, msg_id=msg_id, + **kwargs) self.env['mail.thread'].message_process(model, mail) return self.env[target_model].search([(target_field, '=', subject)]) diff --git a/addons/test_mail/data/test_mail_data.py b/addons/test_mail/data/test_mail_data.py index c7fd5cb853e..81a8f456d7a 100644 --- a/addons/test_mail/data/test_mail_data.py +++ b/addons/test_mail/data/test_mail_data.py @@ -43,6 +43,50 @@ Content-Transfer-Encoding: quoted-printable ------=_Part_4200734_24778174.1344608186754-- """ +MAIL_TEMPLATE_EXTRA_HTML = """Return-Path: +To: {to} +cc: {cc} +Received: by mail1.openerp.com (Postfix, from userid 10002) + id 5DF9ABFB2A; Fri, 10 Aug 2012 16:16:39 +0200 (CEST) +From: {email_from} +Subject: {subject} +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="----=_Part_4200734_24778174.1344608186754" +Date: Fri, 10 Aug 2012 14:16:26 +0000 +Message-ID: {msg_id} +{extra} +------=_Part_4200734_24778174.1344608186754 +Content-Type: text/plain; charset=utf-8 +Content-Transfer-Encoding: quoted-printable + +Please call me as soon as possible this afternoon! + +-- +Sylvie +------=_Part_4200734_24778174.1344608186754 +Content-Type: text/html; charset=utf-8 +Content-Transfer-Encoding: quoted-printable + + + + =20 + + =20 + =20 + +

Please call me as soon as possible this afternoon!

+ {extra_html} + +

--
+ Sylvie +

+ + +------=_Part_4200734_24778174.1344608186754-- +""" + + MAIL_TEMPLATE_PLAINTEXT = """Return-Path: To: {to} Received: by mail1.openerp.com (Postfix, from userid 10002) diff --git a/addons/test_mail/models/__init__.py b/addons/test_mail/models/__init__.py index 0f5cea0a294..64cc5bb37b9 100644 --- a/addons/test_mail/models/__init__.py +++ b/addons/test_mail/models/__init__.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. -from . import test_mail_models from . import test_mail_corner_case_models +from . import test_mail_models from . import test_mail_thread_models diff --git a/addons/test_mail/models/test_mail_corner_case_models.py b/addons/test_mail/models/test_mail_corner_case_models.py index c540676d497..8272a559d17 100644 --- a/addons/test_mail/models/test_mail_corner_case_models.py +++ b/addons/test_mail/models/test_mail_corner_case_models.py @@ -32,6 +32,28 @@ class MailPerformanceTracking(models.Model): field_2 = fields.Char(tracking=True) +class MailTestFieldType(models.Model): + """ Test default values, notably type, messing through models during gateway + processing (i.e. lead.type versus attachment.type). """ + _description = 'Test Field Type' + _name = 'mail.test.field.type' + _inherit = ['mail.thread'] + + name = fields.Char() + email_from = fields.Char() + datetime = fields.Datetime(default=fields.Datetime.now) + customer_id = fields.Many2one('res.partner', 'Customer') + type = fields.Selection([('first', 'First'), ('second', 'Second')]) + user_id = fields.Many2one('res.users', 'Responsible', tracking=True) + + @api.model_create_multi + def create(self, vals_list): + # Emulate an addon that alters the creation context, such as `crm` + if not self._context.get('default_type'): + self = self.with_context(default_type='first') + return super(MailTestFieldType, self).create(vals_list) + + class MailTestLang(models.Model): """ A simple chatter model with lang-based capabilities, allowing to test translations. """ diff --git a/addons/test_mail/security/ir.model.access.csv b/addons/test_mail/security/ir.model.access.csv index e5feacb7a54..9c4a96ae8ba 100644 --- a/addons/test_mail/security/ir.model.access.csv +++ b/addons/test_mail/security/ir.model.access.csv @@ -9,6 +9,8 @@ access_mail_test_track_portal,mail.test.track.portal,model_mail_test_track,base. access_mail_test_track_user,mail.test.track.user.employee,model_mail_test_track,base.group_user,1,1,1,1 access_mail_test_activity_portal,mail.test.activity.portal,model_mail_test_activity,base.group_portal,1,0,0,0 access_mail_test_activity_user,mail.test.activity.user,model_mail_test_activity,base.group_user,1,1,1,1 +access_mail_test_field_type_portal,mail.test.field.type.portal,model_mail_test_field_type,base.group_portal,0,0,0,0 +access_mail_test_field_type_user,mail.test.field.type.user,model_mail_test_field_type,base.group_user,1,1,1,1 access_mail_test_ticket_portal,mail.test.ticket.portal,model_mail_test_ticket,base.group_portal,0,0,0,0 access_mail_test_ticket_user,mail.test.ticket.user,model_mail_test_ticket,base.group_user,1,1,1,1 access_mail_test_composer_mixin_all,mail.test.composer.mixin.all,model_mail_test_composer_mixin,,0,0,0,0 diff --git a/addons/test_mail/tests/test_mail_gateway.py b/addons/test_mail/tests/test_mail_gateway.py index d58e57e10d1..aff9f63bf71 100644 --- a/addons/test_mail/tests/test_mail_gateway.py +++ b/addons/test_mail/tests/test_mail_gateway.py @@ -1140,6 +1140,55 @@ class TestMailgateway(TestMailCommon): self.assertFalse(incoming_msg.parent_id) self.assertTrue(incoming_msg.res_id == res_test.id) + # -------------------------------------------------- + # Gateway / Record synchronization + # -------------------------------------------------- + + @mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models') + def test_gateway_values_base64_image(self): + """New record with mail that contains base64 inline image.""" + target_model = "mail.test.field.type" + alias = self.env["mail.alias"].create({ + "alias_name": "base64-lover", + "alias_model_id": self.env["ir.model"]._get(target_model).id, + "alias_defaults": "{}", + "alias_contact": "everyone", + }) + record = self.format_and_process( + test_mail_data.MAIL_TEMPLATE_EXTRA_HTML, self.email_from, + '%s@%s' % (alias.alias_name, self.alias_catchall), + subject='base64 image to alias', + target_model=target_model, + extra_html='', + ) + self.assertEqual(record.type, "first") + self.assertEqual(len(record.message_ids[0].attachment_ids), 1) + self.assertEqual(record.message_ids[0].attachment_ids[0].name, "image0") + self.assertEqual(record.message_ids[0].attachment_ids[0].type, "binary") + + @mute_logger('odoo.addons.mail.models.mail_thread', 'odoo.models') + def test_gateway_values_base64_image_walias(self): + """New record with mail that contains base64 inline image + default values + coming from alias.""" + target_model = "mail.test.field.type" + alias = self.env["mail.alias"].create({ + "alias_name": "base64-lover", + "alias_model_id": self.env["ir.model"]._get(target_model).id, + "alias_defaults": "{'type': 'second'}", + "alias_contact": "everyone", + }) + record = self.format_and_process( + test_mail_data.MAIL_TEMPLATE_EXTRA_HTML, self.email_from, + '%s@%s' % (alias.alias_name, self.alias_catchall), + subject='base64 image to alias', + target_model=target_model, + extra_html='', + ) + self.assertEqual(record.type, "second") + self.assertEqual(len(record.message_ids[0].attachment_ids), 1) + self.assertEqual(record.message_ids[0].attachment_ids[0].name, "image0") + self.assertEqual(record.message_ids[0].attachment_ids[0].type, "binary") + # -------------------------------------------------- # Thread formation: mail gateway corner cases # -------------------------------------------------- From be8aa967f012c217fa15ffa35460a180af404a4c Mon Sep 17 00:00:00 2001 From: Raf Ven Date: Thu, 3 Jun 2021 09:04:16 +0000 Subject: [PATCH 5/5] [IMP] base: merge activities of merged partners When merging partners through the ``base.partner.merge.automatic.wizard`` wizard, messages and followers are merged. However activities are not probably because its underlying model is "newer" then messages and followers. This commits fixes that behavior. Task-2641572 PR odoo#76159 Closes #71654 X-original-commit: 7e17678ec6ebbbe37b807a5d264cc5afd46bae1a Part-of: odoo/odoo#77005 Co-authored-by: Thibault Delavallee --- addons/mail/tests/test_res_partner.py | 46 +++++++++++++++++++ odoo/addons/base/wizard/base_partner_merge.py | 1 + 2 files changed, 47 insertions(+) diff --git a/addons/mail/tests/test_res_partner.py b/addons/mail/tests/test_res_partner.py index 8032b9d9714..be9784d0717 100644 --- a/addons/mail/tests/test_res_partner.py +++ b/addons/mail/tests/test_res_partner.py @@ -2,6 +2,8 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo.addons.mail.tests.common import MailCommon +from odoo.tests.common import Form, users + # samples use effective TLDs from the Mozilla public suffix # list at http://publicsuffix.org @@ -123,3 +125,47 @@ class TestPartner(MailCommon): new_msg = new_user.message_ids[0] self.assertIn('Portal Access Granted', new_msg.body) self.assertEqual(new_msg.subtype_id, subtype_note) + + @users('admin') + def test_res_partner_merge_wizards(self): + Partner = self.env['res.partner'] + + p1 = Partner.create({'name': 'Customer1', 'email': 'test1@test.example.com'}) + p1_msg_ids_init = p1.message_ids + p2 = Partner.create({'name': 'Customer2', 'email': 'test2@test.example.com'}) + p2_msg_ids_init = p2.message_ids + p3 = Partner.create({'name': 'Other (dup email)', 'email': 'test1@test.example.com'}) + + # add some mail related documents + p1.message_subscribe(partner_ids=p3.ids) + p1_act1 = p1.activity_schedule(act_type_xmlid='mail.mail_activity_data_todo') + p1_msg1 = p1.message_post( + body='

Log on P1

', + subtype_id=self.env.ref('mail.mt_comment').id + ) + self.assertEqual(p1.activity_ids, p1_act1) + self.assertEqual(p1.message_follower_ids.partner_id, self.partner_admin + p3) + self.assertEqual(p1.message_ids, p1_msg_ids_init + p1_msg1) + self.assertEqual(p2.activity_ids, self.env['mail.activity']) + self.assertEqual(p2.message_follower_ids.partner_id, self.partner_admin) + self.assertEqual(p2.message_ids, p2_msg_ids_init) + + MergeForm = Form(self.env['base.partner.merge.automatic.wizard'].with_context( + active_model='res.partner', + active_ids=(p1 + p2).ids + )) + self.assertEqual(MergeForm.partner_ids[:], p1 + p2) + self.assertEqual(MergeForm.dst_partner_id, p2) + merge_form = MergeForm.save() + merge_form.action_merge() + + # check destination and removal + self.assertFalse(p1.exists()) + self.assertTrue(p2.exists()) + # check mail documents have been moved + self.assertEqual(p2.activity_ids, p1_act1) + # TDE note: currently not working as soon as there is a single partner duplicated -> should be improved + # self.assertEqual(p2.message_follower_ids.partner_id, self.partner_admin + p3) + all_msg = p2_msg_ids_init + p1_msg_ids_init + p1_msg1 + self.assertEqual(len(p2.message_ids), len(all_msg) + 1, 'Should have original messages + a log') + self.assertTrue(all(msg in p2.message_ids for msg in all_msg)) diff --git a/odoo/addons/base/wizard/base_partner_merge.py b/odoo/addons/base/wizard/base_partner_merge.py index d862d35b532..cc55e20a164 100644 --- a/odoo/addons/base/wizard/base_partner_merge.py +++ b/odoo/addons/base/wizard/base_partner_merge.py @@ -204,6 +204,7 @@ class MergePartnerAutomatic(models.TransientModel): update_records('calendar', src=partner, field_model='model_id.model') update_records('ir.attachment', src=partner, field_model='res_model') update_records('mail.followers', src=partner, field_model='res_model') + update_records('mail.activity', src=partner, field_model='res_model') update_records('mail.message', src=partner) update_records('ir.model.data', src=partner)