From c24f4765380fe4c0c755b43b3e0dca7de668c82e Mon Sep 17 00:00:00 2001 From: Thanh Dodeur Date: Wed, 6 Oct 2021 14:30:01 +0000 Subject: [PATCH] [FIX] mail: prevent the server from sending outdated session state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before this commit, the server could return outdated information to the client about the state of their own rtc session. The server should not return information about the client's own rtc session as the source of truth is the client-side state. A case in which this was causing an issue was when a user toggled their mute state right before their client pinged the server and received outdated session information before their most recent state could be sent to the server. This commit fixes this issue. closes odoo/odoo#78028 X-original-commit: fe991edd2bca98b58fc5a49494871475c79bc22f Signed-off-by: Sébastien Theys (seb) --- addons/mail/controllers/discuss.py | 6 ++- addons/mail/models/mail_channel_partner.py | 14 ++++--- .../mail/models/mail_channel_rtc_session.py | 13 +++--- addons/mail/tests/test_rtc.py | 42 +++++++++++++++++++ 4 files changed, 63 insertions(+), 12 deletions(-) diff --git a/addons/mail/controllers/discuss.py b/addons/mail/controllers/discuss.py index 7ae3c99bad0..3ef41e6901d 100644 --- a/addons/mail/controllers/discuss.py +++ b/addons/mail/controllers/discuss.py @@ -377,7 +377,11 @@ class DiscussController(http.Controller): ('id', '=', int(rtc_session_id)), ('channel_partner_id', '=', channel_partner_sudo.id), ]).write({}) # update write_date - return {'rtcSessions': channel_partner_sudo._rtc_sync_sessions(check_rtc_session_ids=check_rtc_session_ids)} + current_rtc_sessions, outdated_rtc_sessions = channel_partner_sudo._rtc_sync_sessions(check_rtc_session_ids=check_rtc_session_ids) + return {'rtcSessions': [ + ('insert', [rtc_session_sudo._mail_rtc_session_format(complete_info=False) for rtc_session_sudo in current_rtc_sessions]), + ('insert-and-unlink', [{'id': missing_rtc_session_sudo.id} for missing_rtc_session_sudo in outdated_rtc_sessions]), + ]} # -------------------------------------------------------------------------- # Chatter API diff --git a/addons/mail/models/mail_channel_partner.py b/addons/mail/models/mail_channel_partner.py index cf4ce567867..8abca5a1c36 100644 --- a/addons/mail/models/mail_channel_partner.py +++ b/addons/mail/models/mail_channel_partner.py @@ -114,9 +114,13 @@ class ChannelPartner(models.Model): self.channel_id._rtc_cancel_invitations(partner_ids=self.partner_id.ids, guest_ids=self.guest_id.ids) self.rtc_session_ids.unlink() rtc_session = self.env['mail.channel.rtc.session'].create({'channel_partner_id': self.id}) + current_rtc_sessions, outdated_rtc_sessions = self._rtc_sync_sessions(check_rtc_session_ids=check_rtc_session_ids) res = { 'iceServers': self.env['mail.ice.server']._get_ice_servers() or False, - 'rtcSessions': self._rtc_sync_sessions(check_rtc_session_ids=check_rtc_session_ids), + 'rtcSessions': [ + ('insert', [rtc_session_sudo._mail_rtc_session_format() for rtc_session_sudo in current_rtc_sessions]), + ('insert-and-unlink', [{'id': missing_rtc_session_sudo.id} for missing_rtc_session_sudo in outdated_rtc_sessions]), + ], 'sessionId': rtc_session.id, } if len(self.channel_id.rtc_session_ids) == 1 and self.channel_id.channel_type in {'chat', 'group'}: @@ -141,15 +145,13 @@ class ChannelPartner(models.Model): - Current sessions are returned. - Sessions given in check_rtc_session_ids that no longer exists are returned as non-existing. - Returns a list of JS command for updating rtcSessions field of channel. + :param list check_rtc_session_ids: list of the ids of the sessions to check + :returns tuple: (current_rtc_sessions, outdated_rtc_sessions) """ self.ensure_one() self.channel_id.rtc_session_ids._delete_inactive_rtc_sessions() check_rtc_sessions = self.env['mail.channel.rtc.session'].browse([int(check_rtc_session_id) for check_rtc_session_id in (check_rtc_session_ids or [])]) - return [ - ('insert', [rtc_session_sudo._mail_rtc_session_format() for rtc_session_sudo in self.channel_id.rtc_session_ids]), - ('insert-and-unlink', [{'id': missing_rtc_session_sudo.id} for missing_rtc_session_sudo in check_rtc_sessions - self.channel_id.rtc_session_ids]), - ] + return self.channel_id.rtc_session_ids, check_rtc_sessions - self.channel_id.rtc_session_ids def _rtc_invite_members(self, partner_ids=None, guest_ids=None): """ Sends invitations to join the RTC call to all connected members of the thread who are not already invited. diff --git a/addons/mail/models/mail_channel_rtc_session.py b/addons/mail/models/mail_channel_rtc_session.py index d6ba1b15fde..ca1d22796c3 100644 --- a/addons/mail/models/mail_channel_rtc_session.py +++ b/addons/mail/models/mail_channel_rtc_session.py @@ -115,15 +115,18 @@ class MailRtcSession(models.Model): 'payload': payload, }) for target, payload in payload_by_target.items()]) - def _mail_rtc_session_format(self): + def _mail_rtc_session_format(self, complete_info=True): self.ensure_one() vals = { 'id': self.id, - 'isCameraOn': self.is_camera_on, - 'isDeaf': self.is_deaf, - 'isMuted': self.is_muted, - 'isScreenSharingOn': self.is_screen_sharing_on, } + if complete_info: + vals.update({ + 'isCameraOn': self.is_camera_on, + 'isDeaf': self.is_deaf, + 'isMuted': self.is_muted, + 'isScreenSharingOn': self.is_screen_sharing_on, + }) if self.guest_id: vals['guest'] = [('insert', { 'id': self.guest_id.id, diff --git a/addons/mail/tests/test_rtc.py b/addons/mail/tests/test_rtc.py index 65eed27b376..0ede07d94ac 100644 --- a/addons/mail/tests/test_rtc.py +++ b/addons/mail/tests/test_rtc.py @@ -592,3 +592,45 @@ class TestChannelInternals(MailCommon): ): channel_partner.rtc_session_ids.action_disconnect() self.assertFalse(channel_partner.rtc_session_ids) + + @users('employee') + @mute_logger('odoo.models.unlink') + def test_60_rtc_sync_sessions_should_gc_and_return_outdated_and_active_sessions(self): + channel = self.env['mail.channel'].browse(self.env['mail.channel'].create_group(partners_to=self.user_employee.partner_id.ids)['id']) + channel_partner = channel.sudo().channel_last_seen_partner_ids.filtered(lambda channel_partner: channel_partner.partner_id == self.user_employee.partner_id) + join_call_values = channel_partner._rtc_join_call() + test_guest = self.env['mail.guest'].sudo().create({'name': "Test Guest"}) + test_channel_partner = self.env['mail.channel.partner'].create({ + 'guest_id': test_guest.id, + 'channel_id': channel.id, + }) + test_session = self.env['mail.channel.rtc.session'].sudo().create({'channel_partner_id': test_channel_partner.id}) + test_session.flush() + test_session._write({'write_date': fields.Datetime.now() - relativedelta(days=2)}) + unused_ids = [9998, 9999] + self.env['bus.bus'].sudo().search([]).unlink() + with self.assertBus( + [ + (self.cr.dbname, 'mail.guest', test_guest.id), # session ended + (self.cr.dbname, 'mail.channel', channel.id), # update list of sessions + ], + [ + { + 'type': 'rtc_session_ended', + 'payload': { + 'sessionId': test_session.id, + }, + }, + { + 'type': 'rtc_sessions_update', + 'payload': { + 'id': channel.id, + 'rtcSessions': [('insert-and-unlink', [{'id': test_session.id}])], + }, + }, + ], + ): + current_rtc_sessions, outdated_rtc_sessions = channel_partner._rtc_sync_sessions(check_rtc_session_ids=[join_call_values['sessionId']] + unused_ids) + self.assertEqual(channel_partner.rtc_session_ids, current_rtc_sessions) + self.assertEqual(unused_ids, outdated_rtc_sessions.ids) + self.assertFalse(outdated_rtc_sessions.exists())