feat: E2EE messages mentions (#32510)

pull/33297/head^2
Hugo Costa 1 year ago committed by GitHub
parent 7faba775f0
commit 274f4f5881
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 7
      .changeset/late-planes-sniff.md
  2. 2
      apps/meteor/app/lib/server/methods/updateMessage.ts
  3. 20
      apps/meteor/app/mentions/server/Mentions.ts
  4. 22
      apps/meteor/client/startup/e2e.ts
  5. 6
      apps/meteor/server/settings/e2e.ts
  6. 71
      apps/meteor/tests/e2e/e2e-encryption.spec.ts
  7. 2
      apps/meteor/tests/e2e/page-objects/fragments/home-content.ts
  8. 1
      packages/core-typings/src/IMessage/IMessage.ts
  9. 2
      packages/i18n/src/locales/en.i18n.json

@ -0,0 +1,7 @@
---
"@rocket.chat/meteor": minor
"@rocket.chat/core-typings": patch
"@rocket.chat/i18n": patch
---
Added a new setting to enable mentions in end to end encrypted channels

@ -10,7 +10,7 @@ import { hasPermissionAsync } from '../../../authorization/server/functions/hasP
import { settings } from '../../../settings/server';
import { updateMessage } from '../functions/updateMessage';
const allowedEditedFields = ['tshow', 'alias', 'attachments', 'avatar', 'emoji', 'msg', 'customFields', 'content'];
const allowedEditedFields = ['tshow', 'alias', 'attachments', 'avatar', 'emoji', 'msg', 'customFields', 'content', 'e2eMentions'];
export async function executeUpdateMessage(
uid: IUser['_id'],

@ -2,7 +2,7 @@
* Mentions is a named function that will process Mentions
* @param {Object} message - The message object
*/
import type { IMessage, IRoom, IUser } from '@rocket.chat/core-typings';
import { isE2EEMessage, type IMessage, type IRoom, type IUser } from '@rocket.chat/core-typings';
import { type MentionsParserArgs, MentionsParser } from '../lib/MentionsParser';
@ -43,8 +43,13 @@ export class MentionsServer extends MentionsParser {
});
}
async getUsersByMentions({ msg, rid, u: sender }: Pick<IMessage, 'msg' | 'rid' | 'u'>): Promise<IMessage['mentions']> {
const mentions = this.getUserMentions(msg);
async getUsersByMentions(message: IMessage): Promise<IMessage['mentions']> {
const { msg, rid, u: sender, e2eMentions }: Pick<IMessage, 'msg' | 'rid' | 'u' | 't' | 'e2eMentions'> = message;
const mentions =
isE2EEMessage(message) && e2eMentions?.e2eUserMentions && e2eMentions?.e2eUserMentions.length > 0
? e2eMentions?.e2eUserMentions
: this.getUserMentions(msg);
const mentionsAll: { _id: string; username: string }[] = [];
const userMentions = [];
@ -67,8 +72,13 @@ export class MentionsServer extends MentionsParser {
return [...mentionsAll, ...(userMentions.length ? await this.getUsers(userMentions) : [])];
}
async getChannelbyMentions({ msg }: Pick<IMessage, 'msg'>) {
const channels = this.getChannelMentions(msg);
async getChannelbyMentions(message: IMessage) {
const { msg, e2eMentions }: Pick<IMessage, 'msg' | 't' | 'e2eMentions'> = message;
const channels =
isE2EEMessage(message) && e2eMentions?.e2eChannelMentions && e2eMentions?.e2eChannelMentions.length > 0
? e2eMentions?.e2eChannelMentions
: this.getChannelMentions(msg);
return this.getChannels(channels.map((c) => c.trim().substr(1)));
}

@ -5,6 +5,7 @@ import { Tracker } from 'meteor/tracker';
import { E2EEState } from '../../app/e2e/client/E2EEState';
import { e2e } from '../../app/e2e/client/rocketchat.e2e';
import { MentionsParser } from '../../app/mentions/lib/MentionsParser';
import { ChatRoom } from '../../app/models/client';
import { settings } from '../../app/settings/client';
import { onClientBeforeSendMessage } from '../lib/onClientBeforeSendMessage';
@ -88,6 +89,27 @@ Meteor.startup(() => {
return message;
}
const mentionsEnabled = settings.get<boolean>('E2E_Enabled_Mentions');
if (mentionsEnabled) {
const me = Meteor.user()?.username || '';
const pattern = settings.get('UTF8_User_Names_Validation');
const useRealName = settings.get('UI_Use_Real_Name');
const mentions = new MentionsParser({
pattern: () => pattern,
useRealName: () => useRealName,
me: () => me,
});
const e2eMentions: IMessage['e2eMentions'] = {
e2eUserMentions: mentions.getUserMentions(message.msg),
e2eChannelMentions: mentions.getChannelMentions(message.msg),
};
message.e2eMentions = e2eMentions;
}
// Should encrypt this message.
return e2eRoom.encryptMessage(message);
});

@ -35,4 +35,10 @@ export const createE2ESettings = () =>
public: true,
enableQuery: { _id: 'E2E_Enable', value: true },
});
await this.add('E2E_Enabled_Mentions', false, {
type: 'boolean',
public: true,
enableQuery: { _id: 'E2E_Enable', value: true },
});
});

@ -133,11 +133,13 @@ test.describe.serial('e2e-encryption', () => {
test.beforeAll(async ({ api }) => {
expect((await api.post('/settings/E2E_Allow_Unencrypted_Messages', { value: true })).status()).toBe(200);
expect((await api.post('/settings/E2E_Enabled_Mentions', { value: true })).status()).toBe(200);
});
test.afterAll(async ({ api }) => {
expect((await api.post('/settings/E2E_Enable', { value: false })).status()).toBe(200);
expect((await api.post('/settings/E2E_Allow_Unencrypted_Messages', { value: false })).status()).toBe(200);
expect((await api.post('/settings/E2E_Enabled_Mentions', { value: false })).status()).toBe(200);
});
test('expect create a private channel encrypted and send an encrypted message', async ({ page }) => {
@ -265,6 +267,75 @@ test.describe.serial('e2e-encryption', () => {
await expect(poHomeChannel.content.lastUserMessage.locator('.rcx-icon--name-key')).toBeVisible();
});
test('expect create a encrypted private channel and mention user', async ({ page }) => {
const channelName = faker.string.uuid();
await poHomeChannel.sidenav.createEncryptedChannel(channelName);
await expect(page).toHaveURL(`/group/${channelName}`);
await poHomeChannel.dismissToast();
await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible();
await poHomeChannel.content.sendMessage('hello @user1');
const userMention = await page.getByRole('button', {
name: 'user1',
});
await expect(userMention).toBeVisible();
});
test('expect create a encrypted private channel, mention a channel and navigate to it', async ({ page }) => {
const channelName = faker.string.uuid();
await poHomeChannel.sidenav.createEncryptedChannel(channelName);
await expect(page).toHaveURL(`/group/${channelName}`);
await poHomeChannel.dismissToast();
await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible();
await poHomeChannel.content.sendMessage('Are you in the #general channel?');
const channelMention = await page.getByRole('button', {
name: 'general',
});
await expect(channelMention).toBeVisible();
await channelMention.click();
await expect(page).toHaveURL(`/channel/general`);
});
test('expect create a encrypted private channel, mention a channel and user', async ({ page }) => {
const channelName = faker.string.uuid();
await poHomeChannel.sidenav.createEncryptedChannel(channelName);
await expect(page).toHaveURL(`/group/${channelName}`);
await poHomeChannel.dismissToast();
await expect(poHomeChannel.content.encryptedRoomHeaderIcon).toBeVisible();
await poHomeChannel.content.sendMessage('Are you in the #general channel, @user1 ?');
const channelMention = await page.getByRole('button', {
name: 'general',
});
const userMention = await page.getByRole('button', {
name: 'user1',
});
await expect(userMention).toBeVisible();
await expect(channelMention).toBeVisible();
});
test('should encrypted field be available on edit room', async ({ page }) => {
const channelName = faker.string.uuid();

@ -85,7 +85,7 @@ export class HomeContent {
await this.joinRoomIfNeeded();
await this.page.waitForSelector('[name="msg"]:not([disabled])');
await this.page.locator('[name="msg"]').fill(text);
await this.page.keyboard.press('Enter');
await this.page.getByLabel('Send').click();
}
async dispatchSlashCommand(text: string): Promise<void> {

@ -170,6 +170,7 @@ export interface IMessage extends IRocketChatRecord {
tcount?: number;
t?: MessageTypesValues;
e2e?: 'pending' | 'done';
e2eMentions?: { e2eUserMentions?: string[]; e2eChannelMentions?: string[] };
otrAck?: string;
urls?: MessageUrl[];

@ -1805,6 +1805,8 @@
"E2E_Enabled": "E2E Enabled",
"E2E_Enabled_Default_DirectRooms": "Enable encryption for Direct Rooms by default",
"E2E_Enabled_Default_PrivateRooms": "Enable encryption for Private Rooms by default",
"E2E_Enabled_Mentions": "Mentions",
"E2E_Enabled_Mentions_Description": "Notify people, and highlight user, channel, and team mentions in encrypted content.",
"E2E_Enable_Encrypt_Files": "Encrypt files",
"E2E_Enable_Encrypt_Files_Description": "Encrypt files sent inside encrypted rooms. Check for possible conflicts in [file upload settings.](admin/settings/FileUpload)",
"E2E_Encryption_Password_Change": "Change Encryption Password",

Loading…
Cancel
Save