diff --git a/apps/meteor/server/services/video-conference/service.ts b/apps/meteor/server/services/video-conference/service.ts index cefeb16eed4cd..fc63702ff1cd9 100644 --- a/apps/meteor/server/services/video-conference/service.ts +++ b/apps/meteor/server/services/video-conference/service.ts @@ -1115,7 +1115,11 @@ export class VideoConfService extends ServiceClassInternal implements IVideoConf } private isPersistentChatEnabled(): boolean { - return settings.get('VideoConf_Enable_Persistent_Chat') && settings.get('Discussion_enabled'); + // Persistent chat discussions are always created unencrypted, so persistent chat is treated as disabled + // while the workspace enforces encryption on private rooms. + const encryptionEnforced = settings.get('E2E_Enable') && settings.get('E2E_Force_Encryption_For_Private_Rooms'); + + return settings.get('VideoConf_Enable_Persistent_Chat') && settings.get('Discussion_enabled') && !encryptionEnforced; } private async maybeCreateDiscussion(callId: VideoConference['_id'], createdBy?: IUser): Promise { diff --git a/apps/meteor/server/settings/e2e.ts b/apps/meteor/server/settings/e2e.ts index 03d1c709262f0..e55bc1d06de27 100644 --- a/apps/meteor/server/settings/e2e.ts +++ b/apps/meteor/server/settings/e2e.ts @@ -32,6 +32,7 @@ export const createE2ESettings = () => type: 'boolean', i18nLabel: 'Force_Encryption_For_Private_Rooms', i18nDescription: 'Force_Encryption_For_Private_Rooms_Description', + alert: 'Force_Encryption_For_Private_Rooms_Alert', public: true, enableQuery: { _id: 'E2E_Enable', value: true }, }); diff --git a/apps/meteor/tests/end-to-end/apps/video-conferences.ts b/apps/meteor/tests/end-to-end/apps/video-conferences.ts index 59ec27dae13f8..5b2edeb5d04ce 100644 --- a/apps/meteor/tests/end-to-end/apps/video-conferences.ts +++ b/apps/meteor/tests/end-to-end/apps/video-conferences.ts @@ -602,6 +602,119 @@ describe('Apps - Video Conferences', () => { }); }); + describe('[Persistent Chat provider with the persistent chat feature enabled and encryption forced on private rooms]', () => { + let callId: string | undefined; + + before(async () => { + if (!process.env.IS_EE) { + return; + } + + await updateSetting('VideoConf_Default_Provider', 'persistentchat'); + await updateSetting('Discussion_enabled', true); + await updateSetting('VideoConf_Enable_Persistent_Chat', true); + await Promise.all([updateSetting('E2E_Enable', true), updateSetting('E2E_Force_Encryption_For_Private_Rooms', true)]); + + const res = await request.post(api('video-conference.start')).set(credentials).send({ + roomId, + }); + + callId = res.body.data?.callId; + }); + + after(async () => { + if (!process.env.IS_EE) { + return; + } + + await Promise.all([updateSetting('E2E_Enable', false), updateSetting('E2E_Force_Encryption_For_Private_Rooms', false)]); + }); + + it('should start the call and treat persistent chat as disabled', async function () { + if (!process.env.IS_EE) { + this.skip(); + } + + expect(callId).to.be.a('string'); + + await request + .get(api('video-conference.info')) + .set(credentials) + .query({ + callId, + }) + .expect(200) + .expect((res: Response) => { + expect(res.body.success).to.be.equal(true); + expect(res.body).to.have.a.property('_id').equal(callId); + expect(res.body).to.have.a.property('rid').equal(roomId); + // the call must be fully started, since persistent chat is skipped instead of attempted + expect(res.body).to.have.a.property('url').that.is.a('string'); + expect(res.body).to.have.a.property('status').equal(1); + expect(res.body).to.have.a.property('messages').that.is.an('object'); + expect(res.body.messages).to.have.a.property('started').that.is.a('string'); + // even with VideoConf_Enable_Persistent_Chat on, enforcing encryption disables persistent chat + expect(res.body).to.not.have.a.property('discussionRid'); + }); + }); + }); + + describe('[Persistent Chat provider with the persistent chat feature enabled and encryption forced but E2EE off]', () => { + let callId: string | undefined; + let discussionRid: string | undefined; + + before(async () => { + if (!process.env.IS_EE) { + return; + } + + await updateSetting('VideoConf_Default_Provider', 'persistentchat'); + await updateSetting('Discussion_enabled', true); + await updateSetting('VideoConf_Enable_Persistent_Chat', true); + // the encryption policy only takes effect while E2EE is enabled, so persistent chat must keep working + await Promise.all([updateSetting('E2E_Enable', false), updateSetting('E2E_Force_Encryption_For_Private_Rooms', true)]); + + const res = await request.post(api('video-conference.start')).set(credentials).send({ + roomId, + }); + + callId = res.body.data?.callId; + }); + + after(async () => { + if (!process.env.IS_EE) { + return; + } + + await Promise.all([ + updateSetting('E2E_Force_Encryption_For_Private_Rooms', false), + ...(discussionRid ? [deleteRoom({ type: 'p', roomId: discussionRid })] : []), + ]); + }); + + it('should still create the persistent chat discussion', async function () { + if (!process.env.IS_EE) { + this.skip(); + } + + expect(callId).to.be.a('string'); + + await request + .get(api('video-conference.info')) + .set(credentials) + .query({ + callId, + }) + .expect(200) + .expect((res: Response) => { + discussionRid = res.body.discussionRid; + expect(res.body.success).to.be.equal(true); + expect(res.body).to.have.a.property('_id').equal(callId); + expect(res.body).to.have.a.property('discussionRid').that.is.a('string'); + }); + }); + }); + describe('[Persistent Chat provider with the persistent chat feature enabled and custom discussion names]', () => { let callId: string | undefined; let discussionRid: string | undefined; diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index 0d97b7fe396bf..acb08f157320b 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -2559,6 +2559,7 @@ "Force_Disable_OpLog_For_Cache": "Force Disable OpLog for Cache", "Force_Disable_OpLog_For_Cache_Description": "Will not use OpLog to sync cache even when it's available", "Force_Encryption_For_Private_Rooms": "Force end-to-end encryption on private rooms", + "Force_Encryption_For_Private_Rooms_Alert": "Not compatible with video conference persistent chat
Persistent chat discussions are unencrypted, so while this setting is enabled persistent chat is treated as disabled and video calls will not create a chat history discussion.", "Force_Encryption_For_Private_Rooms_Description": "When enabled, all newly created private rooms will be encrypted by default, and users will not be able to disable encryption for them.", "Force_SSL": "Force SSL", "Force_SSL_Description": "*Caution!* _Force SSL_ should never be used with reverse proxy. If you have a reverse proxy, you should do the redirect THERE. This option exists for deployments like Heroku, that does not allow the redirect configuration at the reverse proxy.", @@ -6018,7 +6019,7 @@ "VideoConf_Enable_DMs": "Enable in direct messages", "VideoConf_Enable_Groups": "Enable in private channels", "VideoConf_Enable_Persistent_Chat": "Enable Persistent Chat", - "VideoConf_Enable_Persistent_Chat_Alert": "Persistent Chat will not work if discussions are disabled on the workspace. It will also not work if the provider app being used do not explicitly support this feature.", + "VideoConf_Enable_Persistent_Chat_Alert": "Persistent Chat will not work if discussions are disabled on the workspace, or if E2E encryption is mandatory. It will also not work if the provider app being used do not explicitly support this feature.", "VideoConf_Enable_Persistent_Chat_description": "When persistent chat is enabled, Rocket.Chat will create a discussion every time a conference call is initiated. The provider app is responsible for sending the chat messages to this discussion.", "VideoConf_Enable_Teams": "Enable in teams", "VideoConf_Mobile_Ringing": "Enable mobile ringing",