Skip to content

Commit

Permalink
Merge pull request #33245 from RocketChat/release-6.12.1
Browse files Browse the repository at this point in the history
Release 6.12.1
  • Loading branch information
ggazzo committed Sep 13, 2024
2 parents ecff0a6 + 4aeb619 commit 2dacd9d
Show file tree
Hide file tree
Showing 28 changed files with 1,148 additions and 122 deletions.
5 changes: 5 additions & 0 deletions .changeset/bump-patch-1725994766358.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': patch
---

Bump @rocket.chat/meteor version.
5 changes: 5 additions & 0 deletions .changeset/four-cherries-kneel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rocket.chat/meteor": patch
---

Allow to use the token from `room.v` when requesting transcript instead of visitor token. Visitors may change their tokens at any time, rendering old conversations impossible to access for them (or for APIs depending on token) as the visitor token won't match the `room.v` token.
5 changes: 5 additions & 0 deletions .changeset/many-rules-shout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rocket.chat/meteor': patch
---

Security Hotfix (https://docs.rocket.chat/docs/security-fixes-and-updates)
5 changes: 5 additions & 0 deletions .changeset/pink-swans-teach.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rocket.chat/meteor": patch
---

fixed retention policy max age settings not being respected after upgrade
6 changes: 6 additions & 0 deletions .changeset/short-drinks-itch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@rocket.chat/message-parser': patch
'@rocket.chat/peggy-loader': patch
---

Improved the performance of the message parser
28 changes: 21 additions & 7 deletions apps/meteor/app/authorization/server/functions/canDeleteMessage.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { IUser } from '@rocket.chat/core-typings';
import type { IUser, IRoom } from '@rocket.chat/core-typings';
import { Rooms } from '@rocket.chat/models';

import { getValue } from '../../../settings/server/raw';
import { canAccessRoomAsync } from './canAccessRoom';
import { hasPermissionAsync } from './hasPermission';

const elapsedTime = (ts: Date): number => {
Expand All @@ -13,6 +14,25 @@ export const canDeleteMessageAsync = async (
uid: string,
{ u, rid, ts }: { u: Pick<IUser, '_id' | 'username'>; rid: string; ts: Date },
): Promise<boolean> => {
const room = await Rooms.findOneById<Pick<IRoom, '_id' | 'ro' | 'unmuted' | 't' | 'teamId' | 'prid'>>(rid, {
projection: {
_id: 1,
ro: 1,
unmuted: 1,
t: 1,
teamId: 1,
prid: 1,
},
});

if (!room) {
return false;
}

if (!(await canAccessRoomAsync(room, { _id: uid }))) {
return false;
}

const forceDelete = await hasPermissionAsync(uid, 'force-delete-message', rid);

if (forceDelete) {
Expand Down Expand Up @@ -45,12 +65,6 @@ export const canDeleteMessageAsync = async (
}
}

const room = await Rooms.findOneById(rid, { projection: { ro: 1, unmuted: 1 } });

if (!room) {
return false;
}

if (room.ro === true && !(await hasPermissionAsync(uid, 'post-readonly', rid))) {
// Unless the user was manually unmuted
if (u.username && !(room.unmuted || []).includes(u.username)) {
Expand Down
17 changes: 7 additions & 10 deletions apps/meteor/app/livechat/server/lib/sendTranscript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import {
type IUser,
type MessageTypesValues,
type IOmnichannelSystemMessage,
type ILivechatVisitor,
isFileAttachment,
isFileImageAttachment,
} from '@rocket.chat/core-typings';
import colors from '@rocket.chat/fuselage-tokens/colors';
import { Logger } from '@rocket.chat/logger';
import { LivechatRooms, LivechatVisitors, Messages, Uploads, Users } from '@rocket.chat/models';
import { LivechatRooms, Messages, Uploads, Users } from '@rocket.chat/models';
import { check } from 'meteor/check';
import moment from 'moment-timezone';

Expand Down Expand Up @@ -41,16 +42,12 @@ export async function sendTranscript({

const room = await LivechatRooms.findOneById(rid);

const visitor = await LivechatVisitors.getVisitorByToken(token, {
projection: { _id: 1, token: 1, language: 1, username: 1, name: 1 },
});

if (!visitor) {
throw new Error('error-invalid-token');
const visitor = room?.v as ILivechatVisitor;
if (token !== visitor?.token) {
throw new Error('error-invalid-visitor');
}

// @ts-expect-error - Visitor typings should include language?
const userLanguage = visitor?.language || settings.get('Language') || 'en';
const userLanguage = settings.get<string>('Language') || 'en';
const timezone = getTimezone(user);
logger.debug(`Transcript will be sent using ${timezone} as timezone`);

Expand All @@ -59,7 +56,7 @@ export async function sendTranscript({
}

// allow to only user to send transcripts from their own chats
if (room.t !== 'l' || !room.v || room.v.token !== token) {
if (room.t !== 'l') {
throw new Error('error-invalid-room');
}

Expand Down
38 changes: 36 additions & 2 deletions apps/meteor/app/otr/server/methods/updateOTRAck.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { api } from '@rocket.chat/core-services';
import type { IOTRMessage } from '@rocket.chat/core-typings';
import type { ServerMethods } from '@rocket.chat/ddp-client';
import { Rooms } from '@rocket.chat/models';
import { check } from 'meteor/check';
import { Meteor } from 'meteor/meteor';

import { canAccessRoomAsync } from '../../../authorization/server/functions/canAccessRoom';

declare module '@rocket.chat/ddp-client' {
// eslint-disable-next-line @typescript-eslint/naming-convention
interface ServerMethods {
Expand All @@ -11,10 +15,40 @@ declare module '@rocket.chat/ddp-client' {
}

Meteor.methods<ServerMethods>({
updateOTRAck({ message, ack }) {
if (!Meteor.userId()) {
async updateOTRAck({ message, ack }) {
const uid = Meteor.userId();
if (!uid) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'updateOTRAck' });
}

check(ack, String);
check(message, {
_id: String,
rid: String,
msg: String,
t: String,
ts: Date,
u: {
_id: String,
username: String,
name: String,
},
});

if (message?.t !== 'otr') {
throw new Meteor.Error('error-invalid-message', 'Invalid message type', { method: 'updateOTRAck' });
}

const room = await Rooms.findOneByIdAndType(message.rid, 'd', { projection: { t: 1, _id: 1, uids: 1 } });

if (!room) {
throw new Meteor.Error('error-invalid-room', 'Invalid room', { method: 'updateOTRAck' });
}

if (!(await canAccessRoomAsync(room, { _id: uid })) || (room.uids && (!message.u._id || !room.uids.includes(message.u._id)))) {
throw new Meteor.Error('error-invalid-user', 'Invalid user, not in room', { method: 'updateOTRAck' });
}

const acknowledgeMessage: IOTRMessage = { ...message, otrAck: ack };
void api.broadcast('otrAckUpdate', { roomId: message.rid, acknowledgeMessage });
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import { Box, Callout, Chip, Margins } from '@rocket.chat/fuselage';
import { ExternalLink } from '@rocket.chat/ui-client';
import type { TranslationKey } from '@rocket.chat/ui-contexts';
import { useTranslation } from '@rocket.chat/ui-contexts';
import DOMPurify from 'dompurify';
import React from 'react';

import ScreenshotCarouselAnchor from '../../../components/ScreenshotCarouselAnchor';
import type { AppInfo } from '../../../definitions/AppInfo';
import { purifyOptions } from '../../../lib/purifyOptions';
import AppDetailsAPIs from './AppDetailsAPIs';
import { normalizeUrl } from './normalizeUrl';

Expand Down Expand Up @@ -61,7 +63,14 @@ const AppDetails = ({ app }: AppDetailsProps) => {
<Box fontScale='h4' mbe={8} color='titles-labels'>
{t('Description')}
</Box>
<Box dangerouslySetInnerHTML={{ __html: isMarkdown ? detailedDescription.rendered : description }} withRichContent />
<Box
dangerouslySetInnerHTML={{
__html: isMarkdown
? DOMPurify.sanitize(detailedDescription.rendered, purifyOptions)
: DOMPurify.sanitize(description, purifyOptions),
}}
withRichContent
/>
</Box>

<Box is='section'>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Accordion, Box } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import DOMPurify from 'dompurify';
import type { ReactElement } from 'react';
import React from 'react';

import { useTimeAgo } from '../../../../../hooks/useTimeAgo';
import { purifyOptions } from '../../../lib/purifyOptions';

type IRelease = {
version: string;
Expand Down Expand Up @@ -36,7 +38,7 @@ const AppReleasesItem = ({ release, ...props }: ReleaseItemProps): ReactElement
return (
<Accordion.Item title={title} {...props}>
{release.detailedChangelog?.rendered ? (
<Box dangerouslySetInnerHTML={{ __html: release.detailedChangelog?.rendered }} color='default' />
<Box dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(release.detailedChangelog?.rendered, purifyOptions) }} color='default' />
) : (
<Box color='default'>{t('No_release_information_provided')}</Box>
)}
Expand Down
50 changes: 50 additions & 0 deletions apps/meteor/client/views/marketplace/lib/purifyOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
export const purifyOptions = {
ALLOWED_TAGS: [
'b',
'i',
'em',
'strong',
'br',
'p',
'ul',
'ol',
'li',
'article',
'aside',
'figure',
'section',
'summary',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'hgroup',
'div',
'hr',
'span',
'wbr',
'abbr',
'acronym',
'cite',
'code',
'dfn',
'figcaption',
'mark',
's',
'samp',
'sub',
'sup',
'var',
'time',
'q',
'del',
'ins',
'rp',
'rt',
'ruby',
'bdi',
'bdo',
],
};
4 changes: 4 additions & 0 deletions apps/meteor/server/models/raw/Rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -912,6 +912,10 @@ export class RoomsRaw extends BaseRaw<IRoom> implements IRoomsModel {
return this.findOne(query, options);
}

findOneByIdAndType(roomId: IRoom['_id'], type: IRoom['t'], options: FindOptions<IRoom> = {}): Promise<IRoom | null> {
return this.findOne({ _id: roomId, t: type }, options);
}

setCallStatus(_id: IRoom['_id'], status: IRoom['callStatus']): Promise<UpdateResult> {
const query: Filter<IRoom> = {
_id,
Expand Down
14 changes: 12 additions & 2 deletions apps/meteor/server/startup/migrations/xrun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Settings } from '@rocket.chat/models';
import type { UpdateResult } from 'mongodb';

import { upsertPermissions } from '../../../app/authorization/server/functions/upsertPermissions';
import { settings } from '../../../app/settings/server';
import { migrateDatabase, onServerVersionChange } from '../../lib/migrations';
import { ensureCloudWorkspaceRegistered } from '../cloudRegistration';

Expand All @@ -23,22 +24,31 @@ const moveRetentionSetting = async () => {
{ _id: { $in: Array.from(maxAgeSettingMap.keys()) }, value: { $ne: -1 } },
{ projection: { _id: 1, value: 1 } },
).forEach(({ _id, value }) => {
if (!maxAgeSettingMap.has(_id)) {
const newSettingId = maxAgeSettingMap.get(_id);
if (!newSettingId) {
throw new Error(`moveRetentionSetting - Setting ${_id} equivalent does not exist`);
}

const newValue = convertDaysToMs(Number(value));

promises.push(
Settings.updateOne(
{
_id: maxAgeSettingMap.get(_id),
},
{
$set: {
value: convertDaysToMs(Number(value)),
value: newValue,
},
},
),
);

const currentCache = settings.getSetting(newSettingId);
if (!currentCache) {
return;
}
settings.set({ ...currentCache, value: newValue });
});

await Promise.all(promises);
Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/tests/data/livechat/rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ export const createLivechatRoom = async (visitorToken: string, extraRoomParams?:
return response.body.room;
};

export const createVisitor = (department?: string, visitorName?: string): Promise<ILivechatVisitor> =>
export const createVisitor = (department?: string, visitorName?: string, customEmail?: string): Promise<ILivechatVisitor> =>
new Promise((resolve, reject) => {
const token = getRandomVisitorToken();
const email = `${token}@${token}.com`;
const email = customEmail || `${token}@${token}.com`;
const phone = `${Math.floor(Math.random() * 10000000000)}`;
void request.get(api(`livechat/visitor/${token}`)).end((err: Error, res: DummyResponse<ILivechatVisitor>) => {
if (!err && res && res.body && res.body.visitor) {
Expand Down
21 changes: 21 additions & 0 deletions apps/meteor/tests/end-to-end/api/livechat/11-livechat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,27 @@ describe('LIVECHAT - Utils', () => {
.send({ token: visitor.token, rid: room._id, email: '[email protected]' });
expect(body).to.have.property('success', true);
});
it('should allow a visitor to get a transcript even if token changed by using an old token that matches room.v', async () => {
const visitor = await createVisitor();
const room = await createLivechatRoom(visitor.token);
await closeOmnichannelRoom(room._id);
const visitor2 = await createVisitor(undefined, undefined, visitor.visitorEmails?.[0].address);
const room2 = await createLivechatRoom(visitor2.token);
await closeOmnichannelRoom(room2._id);

expect(visitor.token !== visitor2.token).to.be.true;
const { body } = await request
.post(api('livechat/transcript'))
.set(credentials)
.send({ token: visitor.token, rid: room._id, email: '[email protected]' });
expect(body).to.have.property('success', true);

const { body: body2 } = await request
.post(api('livechat/transcript'))
.set(credentials)
.send({ token: visitor2.token, rid: room2._id, email: '[email protected]' });
expect(body2).to.have.property('success', true);
});
});

describe('livechat/transcript/:rid', () => {
Expand Down
Loading

0 comments on commit 2dacd9d

Please sign in to comment.