From a29a7183a4a265fdbdac111719f4c3ed382dc667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Theys?= Date: Fri, 8 Apr 2022 12:09:08 +0000 Subject: [PATCH] [IMP] mail, im_livechat: fix performance of channel access rule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Current access rule requires fetching all members of each channel, which does not scale. Use is_member field instead, and make sure itself does not fetch all members. task-2818759 closes odoo/odoo#88221 Related: odoo/upgrade#3422 Signed-off-by: Sébastien Theys (seb) --- .../tests/test_get_mail_channel.py | 4 +-- addons/mail/models/mail_channel.py | 34 +++++++++++++++++-- addons/mail/models/mail_guest.py | 7 ++++ addons/mail/security/mail_security.xml | 4 +-- addons/mail/tests/test_mail_channel.py | 4 +-- 5 files changed, 44 insertions(+), 9 deletions(-) diff --git a/addons/im_livechat/tests/test_get_mail_channel.py b/addons/im_livechat/tests/test_get_mail_channel.py index 9d3571b478d..87f23bc1ba7 100644 --- a/addons/im_livechat/tests/test_get_mail_channel.py +++ b/addons/im_livechat/tests/test_get_mail_channel.py @@ -129,10 +129,10 @@ class TestGetMailChannel(TransactionCase): 'email': 'michel@example.com', 'livechat_username': 'Michel at your service', }) - channel_info = self.livechat_channel.with_user(public_user)._open_livechat_mail_channel(anonymous_name='whatever') + channel_info = self.livechat_channel.with_user(public_user).sudo()._open_livechat_mail_channel(anonymous_name='whatever') channel = self.env['mail.channel'].browse(channel_info['id']) channel.with_user(operator).message_post(body='Hello', message_type='comment', subtype_xmlid='mail.mt_comment') - message_formats = channel.with_user(public_user)._channel_fetch_message() + message_formats = channel.with_user(public_user).sudo()._channel_fetch_message() self.assertEqual(len(message_formats), 1) self.assertEqual(message_formats[0]['author_id'][0], operator.partner_id.id) self.assertEqual(message_formats[0]['author_id'][1], operator.livechat_username) diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py index 581c1ebb084..c119460cb7c 100644 --- a/addons/mail/models/mail_channel.py +++ b/addons/mail/models/mail_channel.py @@ -73,7 +73,7 @@ class Channel(models.Model): 'mail.channel.partner', 'channel_id', string='Last Seen', groups='base.group_user') rtc_session_ids = fields.One2many('mail.channel.rtc.session', 'channel_id', groups="base.group_system") - is_member = fields.Boolean('Is Member', compute='_compute_is_member', compute_sudo=True) + is_member = fields.Boolean('Is Member', compute='_compute_is_member', search='_search_is_member') member_count = fields.Integer(string="Member Count", compute='_compute_member_count', compute_sudo=True, help="Excluding guests from count.") group_ids = fields.Many2many( 'res.groups', string='Auto Subscription', @@ -166,10 +166,38 @@ class Channel(models.Model): ]) )] - @api.depends('channel_partner_ids') + @api.depends_context('uid', 'guest') + @api.depends('channel_last_seen_partner_ids') def _compute_is_member(self): + if not self: + return + if self.env.user._is_public(): + guest = self.env['mail.guest']._get_guest_from_context() + if not guest: + self.is_member = False + return + user_domain = [('guest_id', '=', guest.id)] + else: + user_domain = [('partner_id', '=', self.env.user.partner_id.id)] + members = self.env['mail.channel.partner'].sudo().search(expression.AND([[('channel_id', 'in', self.ids)], user_domain])) + is_member_channels = {member.channel_id for member in members} for channel in self: - channel.is_member = self.env.user.partner_id in channel.channel_partner_ids + channel.is_member = channel in is_member_channels + + def _search_is_member(self, operator, operand): + is_in = (operator == '=' and operand) or (operator == '!=' and not operand) + if self.env.user._is_public(): + guest = self.env['mail.guest']._get_guest_from_context() + if not guest: + return expression.FALSE_DOMAIN if is_in else expression.TRUE_DOMAIN + user_domain = [('guest_id', '=', guest.id)] + else: + user_domain = [('partner_id', '=', self.env.user.partner_id.id)] + return [( + 'channel_last_seen_partner_ids', + 'in' if is_in else 'not in', + self.env['mail.channel.partner'].sudo()._search(user_domain) + )] @api.depends('channel_partner_ids') def _compute_member_count(self): diff --git a/addons/mail/models/mail_guest.py b/addons/mail/models/mail_guest.py index c30420a19ef..c1ba4b95779 100644 --- a/addons/mail/models/mail_guest.py +++ b/addons/mail/models/mail_guest.py @@ -29,6 +29,13 @@ class MailGuest(models.Model): timezone = fields.Selection(string="Timezone", selection=_tz_get) channel_ids = fields.Many2many(string="Channels", comodel_name='mail.channel', relation='mail_channel_partner', column1='guest_id', column2='channel_id', copy=False) + def _get_guest_from_context(self): + """Returns the current guest record from the context, if applicable.""" + guest = self.env.context.get('guest') + if isinstance(guest, self.pool['mail.guest']): + return guest + return self.env['mail.guest'] + def _get_guest_from_request(self, request): parts = request.httprequest.cookies.get(self._cookie_name, '').split(self._cookie_separator) if len(parts) != 2: diff --git a/addons/mail/security/mail_security.xml b/addons/mail/security/mail_security.xml index 5741befbd94..31a7b11a9db 100644 --- a/addons/mail/security/mail_security.xml +++ b/addons/mail/security/mail_security.xml @@ -8,7 +8,7 @@ ['|', '|', ('public', '=', 'public'), -'&', ('public', '=', 'private'), ('channel_partner_ids', 'in', [user.partner_id.id]), +'&', ('public', '=', 'private'), ('is_member', '=', True), '&', ('public', '=', 'groups'), ('group_public_id', 'in', [g.id for g in user.groups_id])] @@ -19,7 +19,7 @@ ['|', '|', ('channel_id.public', '=', 'public'), -'&', ('channel_id.public', '=', 'private'), ('channel_id.channel_partner_ids', 'in', [user.partner_id.id]), +'&', ('channel_id.public', '=', 'private'), ('channel_id.is_member', '=', True), '&', ('channel_id.public', '=', 'groups'), ('channel_id.group_public_id', 'in', [g.id for g in user.groups_id])] diff --git a/addons/mail/tests/test_mail_channel.py b/addons/mail/tests/test_mail_channel.py index 2b6338f38a7..22021b5e36a 100644 --- a/addons/mail/tests/test_mail_channel.py +++ b/addons/mail/tests/test_mail_channel.py @@ -43,7 +43,7 @@ class TestChannelAccessRights(MailCommon): 'public': 'private'}) @mute_logger('odoo.addons.base.models.ir_rule', 'odoo.addons.base.models.ir_model', 'odoo.models') - @users('user_public') + @users('user_portal') def test_access_public(self): # Read public group -> ok self.env['mail.channel'].browse(self.group_public.id).read() @@ -56,7 +56,7 @@ class TestChannelAccessRights(MailCommon): self.env['mail.channel'].browse(self.group_private.id).read() # Read a private group when being a member: ok - self.group_private.write({'channel_partner_ids': [(4, self.user_public.partner_id.id)]}) + self.group_private.write({'channel_partner_ids': [(4, self.user_portal.partner_id.id)]}) self.env['mail.channel'].browse(self.group_private.id).read() # Create group: ko, no access rights