From 7aefcffe5ebda8ffaf18f618cfb222cabef61404 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento Date: Tue, 2 Jul 2024 10:44:12 -0300 Subject: [PATCH] regression(E2EEncryption): Encrypted quotes of files not working (#32699) --- .../message/hooks/useNormalizedMessage.ts | 86 ++++++++++++------- .../lib/parseMessageTextToAstMarkdown.ts | 61 +++++-------- ...spec.ts => parseMessageAttachment.spec.ts} | 23 ++--- .../MessageQuoteAttachment.ts | 2 +- 4 files changed, 89 insertions(+), 83 deletions(-) rename apps/meteor/tests/unit/client/views/room/MessageList/lib/{parseMessageQuoteAttachment.spec.ts => parseMessageAttachment.spec.ts} (79%) diff --git a/apps/meteor/client/components/message/hooks/useNormalizedMessage.ts b/apps/meteor/client/components/message/hooks/useNormalizedMessage.ts index 2b188b92f97..715a84a2359 100644 --- a/apps/meteor/client/components/message/hooks/useNormalizedMessage.ts +++ b/apps/meteor/client/components/message/hooks/useNormalizedMessage.ts @@ -1,6 +1,12 @@ import { Base64 } from '@rocket.chat/base64'; -import { isFileImageAttachment, isFileAttachment, isFileAudioAttachment, isFileVideoAttachment } from '@rocket.chat/core-typings'; -import type { IMessage } from '@rocket.chat/core-typings'; +import type { IMessage, MessageAttachment } from '@rocket.chat/core-typings'; +import { + isFileImageAttachment, + isFileAttachment, + isFileAudioAttachment, + isFileVideoAttachment, + isQuoteAttachment, +} from '@rocket.chat/core-typings'; import type { Options } from '@rocket.chat/message-parser'; import { useSetting } from '@rocket.chat/ui-contexts'; import { useMemo } from 'react'; @@ -12,6 +18,48 @@ import { useAutoTranslate } from '../../../views/room/MessageList/hooks/useAutoT import { useKatex } from '../../../views/room/MessageList/hooks/useKatex'; import { useSubscriptionFromMessageQuery } from './useSubscriptionFromMessageQuery'; +const normalizeAttachments = (attachments: MessageAttachment[], name?: string, type?: string): MessageAttachment[] => { + if (name) { + name = String.fromCharCode(...new TextEncoder().encode(name)); + } + + return attachments.map((attachment) => { + if (isQuoteAttachment(attachment) && attachment.attachments) { + attachment.attachments = normalizeAttachments(attachment.attachments); + return attachment; + } + + if (!attachment.encryption) { + return attachment; + } + + const key = Base64.encode( + JSON.stringify({ + ...attachment.encryption, + name, + type, + }), + ); + + if (isFileAttachment(attachment)) { + if (attachment.title_link && !attachment.title_link.startsWith('/file-decrypt/')) { + attachment.title_link = `/file-decrypt${attachment.title_link}?key=${key}`; + } + if (isFileImageAttachment(attachment) && !attachment.image_url.startsWith('/file-decrypt/')) { + attachment.image_url = `/file-decrypt${attachment.image_url}?key=${key}`; + } + if (isFileAudioAttachment(attachment) && !attachment.audio_url.startsWith('/file-decrypt/')) { + attachment.audio_url = `/file-decrypt${attachment.audio_url}?key=${key}`; + } + if (isFileVideoAttachment(attachment) && !attachment.video_url.startsWith('/file-decrypt/')) { + attachment.video_url = `/file-decrypt${attachment.video_url}?key=${key}`; + } + } + + return attachment; + }); +}; + export const useNormalizedMessage = (message: TMessage): MessageWithMdEnforced => { const { katexEnabled, katexDollarSyntaxEnabled, katexParenthesisSyntaxEnabled } = useKatex(); const customDomains = useAutoLinkDomains(); @@ -34,35 +82,13 @@ export const useNormalizedMessage = (message: TMessag const normalizedMessage = parseMessageTextToAstMarkdown(message, parseOptions, autoTranslateOptions); - normalizedMessage.attachments = normalizedMessage.attachments?.map((attachment) => { - if (!attachment.encryption) { - return attachment; - } - - const key = Base64.encode( - JSON.stringify({ - ...attachment.encryption, - name: String.fromCharCode(...new TextEncoder().encode(normalizedMessage.file?.name)), - type: normalizedMessage.file?.type, - }), + if (normalizedMessage.attachments) { + normalizedMessage.attachments = normalizeAttachments( + normalizedMessage.attachments, + normalizedMessage.file?.name, + normalizedMessage.file?.type, ); - - if (isFileAttachment(attachment)) { - if (attachment.title_link && !attachment.title_link.startsWith('/file-decrypt/')) { - attachment.title_link = `/file-decrypt${attachment.title_link}?key=${key}`; - } - if (isFileImageAttachment(attachment) && !attachment.image_url.startsWith('/file-decrypt/')) { - attachment.image_url = `/file-decrypt${attachment.image_url}?key=${key}`; - } - if (isFileAudioAttachment(attachment) && !attachment.audio_url.startsWith('/file-decrypt/')) { - attachment.audio_url = `/file-decrypt${attachment.audio_url}?key=${key}`; - } - if (isFileVideoAttachment(attachment) && !attachment.video_url.startsWith('/file-decrypt/')) { - attachment.video_url = `/file-decrypt${attachment.video_url}?key=${key}`; - } - } - return attachment; - }); + } return normalizedMessage; }, [showColors, customDomains, katexEnabled, katexDollarSyntaxEnabled, katexParenthesisSyntaxEnabled, message, autoTranslateOptions]); diff --git a/apps/meteor/client/lib/parseMessageTextToAstMarkdown.ts b/apps/meteor/client/lib/parseMessageTextToAstMarkdown.ts index 41cbfed9575..0a1de5049e2 100644 --- a/apps/meteor/client/lib/parseMessageTextToAstMarkdown.ts +++ b/apps/meteor/client/lib/parseMessageTextToAstMarkdown.ts @@ -1,4 +1,4 @@ -import type { IMessage, ITranslatedMessage, MessageAttachment, MessageQuoteAttachment } from '@rocket.chat/core-typings'; +import type { IMessage, ITranslatedMessage, MessageAttachment } from '@rocket.chat/core-typings'; import { isFileAttachment, isE2EEMessage, @@ -56,21 +56,35 @@ export const parseMessageTextToAstMarkdown = < }; }; -export const parseMessageQuoteAttachment = ( - quote: T, +export const parseMessageAttachment = ( + attachment: T, parseOptions: Options, autoTranslateOptions: { autoTranslateLanguage?: string; translated: boolean }, ): T => { const { translated, autoTranslateLanguage } = autoTranslateOptions; - if (quote.attachments && quote.attachments?.length > 0) { - quote.attachments = quote.attachments.map((attachment) => parseMessageQuoteAttachment(attachment, parseOptions, autoTranslateOptions)); + if (!attachment.text && !attachment.description) { + return attachment; } - const text = (isTranslatedAttachment(quote) && autoTranslateLanguage && quote?.translations?.[autoTranslateLanguage]) || quote.text || ''; + if (isQuoteAttachment(attachment) && attachment.attachments) { + attachment.attachments = parseMessageAttachments(attachment.attachments, parseOptions, autoTranslateOptions); + } + + const text = + (isTranslatedAttachment(attachment) && autoTranslateLanguage && attachment?.translations?.[autoTranslateLanguage]) || + attachment.text || + attachment.description || + ''; + + if (isFileAttachment(attachment) && attachment.description) { + attachment.descriptionMd = translated + ? textToMessageToken(text, parseOptions) + : attachment.descriptionMd ?? textToMessageToken(text, parseOptions); + } return { - ...quote, - md: translated ? textToMessageToken(text, parseOptions) : quote.md ?? textToMessageToken(text, parseOptions), + ...attachment, + md: translated ? textToMessageToken(text, parseOptions) : attachment.md ?? textToMessageToken(text, parseOptions), }; }; @@ -78,36 +92,7 @@ export const parseMessageAttachments = ( attachments: T[], parseOptions: Options, autoTranslateOptions: { autoTranslateLanguage?: string; translated: boolean }, -): T[] => - attachments.map((attachment) => { - const { translated, autoTranslateLanguage } = autoTranslateOptions; - if (!attachment.text && !attachment.description) { - return attachment; - } - - if (isQuoteAttachment(attachment) && attachment.attachments) { - attachment.attachments = attachment.attachments.map((quoteAttachment) => - parseMessageQuoteAttachment(quoteAttachment, parseOptions, autoTranslateOptions), - ); - } - - const text = - (isTranslatedAttachment(attachment) && autoTranslateLanguage && attachment?.translations?.[autoTranslateLanguage]) || - attachment.text || - attachment.description || - ''; - - if (isFileAttachment(attachment) && attachment.description) { - attachment.descriptionMd = translated - ? textToMessageToken(text, parseOptions) - : attachment.descriptionMd ?? textToMessageToken(text, parseOptions); - } - - return { - ...attachment, - md: translated ? textToMessageToken(text, parseOptions) : attachment.md ?? textToMessageToken(text, parseOptions), - }; - }); +): T[] => attachments.map((attachment) => parseMessageAttachment(attachment, parseOptions, autoTranslateOptions)); const isNotNullOrUndefined = (value: unknown): boolean => value !== null && value !== undefined; diff --git a/apps/meteor/tests/unit/client/views/room/MessageList/lib/parseMessageQuoteAttachment.spec.ts b/apps/meteor/tests/unit/client/views/room/MessageList/lib/parseMessageAttachment.spec.ts similarity index 79% rename from apps/meteor/tests/unit/client/views/room/MessageList/lib/parseMessageQuoteAttachment.spec.ts rename to apps/meteor/tests/unit/client/views/room/MessageList/lib/parseMessageAttachment.spec.ts index f867ead0dee..7b94a3cfdef 100644 --- a/apps/meteor/tests/unit/client/views/room/MessageList/lib/parseMessageQuoteAttachment.spec.ts +++ b/apps/meteor/tests/unit/client/views/room/MessageList/lib/parseMessageAttachment.spec.ts @@ -3,7 +3,7 @@ import type { MessageQuoteAttachment } from '@rocket.chat/core-typings'; import type { Options, Root } from '@rocket.chat/message-parser'; import { expect } from 'chai'; -import { parseMessageQuoteAttachment } from '../../../../../../../client/lib/parseMessageTextToAstMarkdown'; +import { parseMessageAttachment } from '../../../../../../../client/lib/parseMessageTextToAstMarkdown'; const parseOptions: Options = { colors: true, @@ -70,22 +70,20 @@ const quoteMessage = { author_name: 'authorName', author_link: 'link', author_icon: 'icon', + message_link: 'http://localhost/any_link', text: 'message **bold** _italic_ and ~strike~', md: messageParserTokenMessage, }; -describe('parseMessageQuoteAttachment', () => { +describe('parseMessageAttachment', () => { it('should return md property populated if the quote is parsed', () => { - expect(parseMessageQuoteAttachment(quoteMessage, parseOptions, autoTranslateOptions).md).to.deep.equal(messageParserTokenMessage); + expect(parseMessageAttachment(quoteMessage, parseOptions, autoTranslateOptions).md).to.deep.equal(messageParserTokenMessage); }); it('should return md property populated if the quote is not parsed', () => { expect( - parseMessageQuoteAttachment( - { ...quoteMessage, md: undefined } as unknown as MessageQuoteAttachment, - parseOptions, - autoTranslateOptions, - ).md, + parseMessageAttachment({ ...quoteMessage, md: undefined } as unknown as MessageQuoteAttachment, parseOptions, autoTranslateOptions) + .md, ).to.deep.equal(messageParserTokenMessage); }); @@ -115,15 +113,12 @@ describe('parseMessageQuoteAttachment', () => { autoTranslateLanguage: 'en', }; it('should return correct quote translated parsed md when translate is active', () => { - expect(parseMessageQuoteAttachment(translatedQuote, parseOptions, enabledAutoTranslatedOptions).md).to.deep.equal( - translatedMessageParsed, - ); + expect(parseMessageAttachment(translatedQuote, parseOptions, enabledAutoTranslatedOptions).md).to.deep.equal(translatedMessageParsed); }); it('should return text parsed md when translate is active and autoTranslateLanguage is undefined', () => { expect( - parseMessageQuoteAttachment(translatedQuote, parseOptions, { ...enabledAutoTranslatedOptions, autoTranslateLanguage: undefined }) - .md, + parseMessageAttachment(translatedQuote, parseOptions, { ...enabledAutoTranslatedOptions, autoTranslateLanguage: undefined }).md, ).to.deep.equal([ { type: 'PARAGRAPH', @@ -190,7 +185,7 @@ describe('parseMessageQuoteAttachment', () => { ], }; - expect(parseMessageQuoteAttachment(multipleQuotes, parseOptions, enabledAutoTranslatedOptions)).to.deep.equal(multipleQuotesParsed); + expect(parseMessageAttachment(multipleQuotes, parseOptions, enabledAutoTranslatedOptions)).to.deep.equal(multipleQuotesParsed); }); }); }); diff --git a/packages/core-typings/src/IMessage/MessageAttachment/MessageQuoteAttachment.ts b/packages/core-typings/src/IMessage/MessageAttachment/MessageQuoteAttachment.ts index f0dad665d28..237e8db4abb 100644 --- a/packages/core-typings/src/IMessage/MessageAttachment/MessageQuoteAttachment.ts +++ b/packages/core-typings/src/IMessage/MessageAttachment/MessageQuoteAttachment.ts @@ -10,7 +10,7 @@ export type MessageQuoteAttachment = { message_link?: string; text: string; md?: Root; - attachments?: Array; // TODO this is cauising issues to define a model, see @ts-expect-error at apps/meteor/app/api/server/v1/channels.ts:274 + attachments?: Array; // TODO this is causing issues to define a model, see @ts-expect-error at apps/meteor/app/api/server/v1/channels.ts:274 } & MessageAttachmentBase; export const isQuoteAttachment = (attachment: MessageAttachment): attachment is MessageQuoteAttachment =>