From 7a2650760d91bc460e3c8ee649b46ed1e52b395e Mon Sep 17 00:00:00 2001 From: Vincent Larcin Date: Mon, 13 Jun 2022 15:48:50 +0000 Subject: [PATCH] [FIX] portal: use correct author for comments on shared records Steps to reproduce ------------------ 1. Create two partners A and B. 2. Create a shareable record, e.g. a sales order, and set A as the customer. 3. Share the record with B. 4. Use the share link sent to B and comment on the record. 5. Your comments will be sent with A as the author. Expected behavior ----------------- Share links sent to a customer should make you comment as that customer. Task-2883044 X-original-commit: df162acc589b74b0e3c1e9687ce6a1980233f5f1 Part-of: odoo/odoo#94695 --- addons/portal/controllers/mail.py | 17 ++--- addons/test_mail_full/__manifest__.py | 2 +- .../models/test_mail_models_mail.py | 19 ++++++ .../security/ir.model.access.csv | 2 + addons/test_mail_full/tests/__init__.py | 1 + addons/test_mail_full/tests/test_portal.py | 63 +++++++++++++++++++ 6 files changed, 95 insertions(+), 9 deletions(-) create mode 100644 addons/test_mail_full/tests/test_portal.py diff --git a/addons/portal/controllers/mail.py b/addons/portal/controllers/mail.py index b3a6eb49ccd..1b53274e084 100644 --- a/addons/portal/controllers/mail.py +++ b/addons/portal/controllers/mail.py @@ -15,11 +15,11 @@ from odoo.exceptions import AccessError, MissingError, UserError def _check_special_access(res_model, res_id, token='', _hash='', pid=False): record = request.env[res_model].browse(res_id).sudo() - if token: # Token Case: token is the global one of the document + if _hash and pid: # Signed Token Case: hash implies token is signed by partner pid + return consteq(_hash, record._sign_token(pid)) + elif token: # Token Case: token is the global one of the document token_field = request.env[res_model]._mail_post_token_field return (token and record and consteq(record[token_field], token)) - elif _hash and pid: # Signed Token Case: hash implies token is signed by partner pid - return consteq(_hash, record._sign_token(pid)) else: raise Forbidden() @@ -64,8 +64,11 @@ def _message_post_helper(res_model, res_id, message, token='', _hash=False, pid= # deduce author of message author_id = request.env.user.partner_id.id if request.env.user.partner_id else False + # Signed Token Case: author_id is forced + if _hash and pid: + author_id = pid # Token Case: author is document customer (if not logged) or itself even if user has not the access - if token: + elif token: if request.env.user._is_public(): # TODO : After adding the pid and sign_token in access_url when send invoice by email, remove this line # TODO : Author must be Public User (to rename to 'Anonymous') @@ -73,9 +76,6 @@ def _message_post_helper(res_model, res_id, message, token='', _hash=False, pid= else: if not author_id: raise NotFound() - # Signed Token Case: author_id is forced - elif _hash and pid: - author_id = pid email_from = None if author_id and 'email_from' not in kw: @@ -101,7 +101,7 @@ def _message_post_helper(res_model, res_id, message, token='', _hash=False, pid= class PortalChatter(http.Controller): def _portal_post_filter_params(self): - return ['token', 'hash', 'pid'] + return ['token', 'pid'] def _portal_post_check_attachments(self, attachment_ids, attachment_tokens): if len(attachment_tokens) != len(attachment_ids): @@ -145,6 +145,7 @@ class PortalChatter(http.Controller): 'attachment_ids': False, # will be added afterward } post_values.update((fname, kw.get(fname)) for fname in self._portal_post_filter_params()) + post_values['_hash'] = kw.get('hash') message = _message_post_helper(**post_values) result.update({'default_message_id': message.id}) diff --git a/addons/test_mail_full/__manifest__.py b/addons/test_mail_full/__manifest__.py index 856aeea12a3..94579145735 100644 --- a/addons/test_mail_full/__manifest__.py +++ b/addons/test_mail_full/__manifest__.py @@ -19,7 +19,7 @@ real applications. """, 'rating', # 'snailmail', 'mass_mailing', - 'mass_mailing_sms', + 'mass_mailing_sms', # adds portal 'phone_validation', 'sms', ], diff --git a/addons/test_mail_full/models/test_mail_models_mail.py b/addons/test_mail_full/models/test_mail_models_mail.py index b6cdd20dd5d..926b6808af2 100644 --- a/addons/test_mail_full/models/test_mail_models_mail.py +++ b/addons/test_mail_full/models/test_mail_models_mail.py @@ -4,6 +4,25 @@ from odoo import api, fields, models +class MailTestPortal(models.Model): + """ A model intheriting from mail.thread with some fields used for portal + sharing, like a partner, ...""" + _description = 'Chatter Model for Portal' + _name = 'mail.test.portal' + _inherit = [ + 'mail.thread', + 'portal.mixin', + ] + + name = fields.Char() + partner_id = fields.Many2one('res.partner', 'Customer') + + def _compute_access_url(self): + self.access_url = False + for record in self.filtered('id'): + record.access_url = '/my/test_portal/%s' % self.id + + class MailTestRating(models.Model): """ A model inheriting from mail.thread with some fields used for SMS gateway, like a partner, a specific mobile phone, ... """ diff --git a/addons/test_mail_full/security/ir.model.access.csv b/addons/test_mail_full/security/ir.model.access.csv index 4181b40d397..ce3ec00b9b2 100644 --- a/addons/test_mail_full/security/ir.model.access.csv +++ b/addons/test_mail_full/security/ir.model.access.csv @@ -1,4 +1,6 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_mail_test_portal_all,mail.test.portal.all,model_mail_test_portal,,1,0,0,0 +access_mail_test_portal_user,mail.test.portal.user,model_mail_test_portal,base.group_user,1,1,1,1 access_mail_test_rating_all,mail.test.rating.all,model_mail_test_rating,,0,0,0,0 access_mail_test_rating_portal,mail.test.rating.portal,model_mail_test_rating,base.group_portal,1,0,0,0 access_mail_test_rating_user,mail.test.rating.user,model_mail_test_rating,base.group_user,1,1,1,1 diff --git a/addons/test_mail_full/tests/__init__.py b/addons/test_mail_full/tests/__init__.py index 3921c4e1343..e640bbabe03 100644 --- a/addons/test_mail_full/tests/__init__.py +++ b/addons/test_mail_full/tests/__init__.py @@ -7,6 +7,7 @@ from . import test_phone_blacklist from . import test_mass_mailing from . import test_mass_mailing_statistics from . import test_mass_sms +from . import test_portal from . import test_rating from . import test_sms_composer from . import test_sms_management diff --git a/addons/test_mail_full/tests/test_portal.py b/addons/test_mail_full/tests/test_portal.py new file mode 100644 index 00000000000..33821308859 --- /dev/null +++ b/addons/test_mail_full/tests/test_portal.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import http +from odoo.addons.test_mail_full.tests.common import TestMailFullCommon, TestMailFullRecipients +from odoo.tests import tagged, users +from odoo.tests.common import HttpCase + + +@tagged('portal') +class TestPortal(HttpCase, TestMailFullCommon, TestMailFullRecipients): + + def setUp(self): + super(TestPortal, self).setUp() + + self.record_portal = self.env['mail.test.portal'].create({ + 'partner_id': self.partner_1.id, + 'name': 'Test Portal Record', + }) + + self.record_portal._portal_ensure_token() + + @users('employee') + def test_portal_mixin(self): + """ Test internals of portal mixin """ + customer = self.partner_1.with_env(self.env) + record_portal = self.env['mail.test.portal'].create({ + 'partner_id': customer.id, + 'name': 'Test Portal Record', + }) + + self.assertFalse(record_portal.access_token) + self.assertEqual(record_portal.access_url, '/my/test_portal/%s' % record_portal.id) + + record_portal._portal_ensure_token() + self.assertTrue(record_portal.access_token) + + def test_portal_share_comment(self): + """ Test posting through portal controller allowing to use a hash to + post wihtout access rights. """ + self.authenticate(None, None) + post_url = f"{self.record_portal.get_base_url()}/mail/chatter_post" + + # test as not logged + self.opener.post( + url=post_url, + json={ + 'params': { + 'csrf_token': http.Request.csrf_token(self), + 'hash': self.record_portal._sign_token(self.partner_2.id), + 'message': 'Test', + 'pid': self.partner_2.id, + 'redirect': '/', + 'res_model': self.record_portal._name, + 'res_id': self.record_portal.id, + 'token': self.record_portal.access_token, + }, + }, + ) + message = self.record_portal.message_ids[0] + + self.assertIn('Test', message.body) + self.assertEqual(message.author_id, self.partner_2)