Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/gold-cougars-stare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rocket.chat/meteor": patch
---

Fixes an issue that allowed agents without the `preview-c-room` permission to join a closed livechat conversation, creating a livechat room that could not be closed or removed from the sidebar.
2 changes: 1 addition & 1 deletion apps/meteor/app/lib/server/methods/joinRoom.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Room } from '@rocket.chat/core-services';
import type { IRoom } from '@rocket.chat/core-typings';
import { type IRoom } from '@rocket.chat/core-typings';
import type { ServerMethods } from '@rocket.chat/ddp-client';
import { Rooms } from '@rocket.chat/models';
import { check } from 'meteor/check';
Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/client/views/room/hooks/useOpenRoom.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { IRoom, RoomType } from '@rocket.chat/core-typings';
import { isOmnichannelRoom, type IRoom, type RoomType } from '@rocket.chat/core-typings';
import { useMethod, usePermission, useRoute, useSetting, useUser } from '@rocket.chat/ui-contexts';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useEffect } from 'react';
Expand Down Expand Up @@ -90,7 +90,7 @@ export function useOpenRoom({ type, reference }: { type: RoomType; reference: st
const sub = Subscriptions.findOne({ rid: room._id });

// if user doesn't exist at this point, anonymous read is enabled, otherwise an error would have been thrown
if (user && !sub && !hasPreviewPermission) {
if (user && !sub && !hasPreviewPermission && !isOmnichannelRoom(room)) {
throw new NotSubscribedToRoomError(undefined, { rid: room._id });
}

Expand Down
6 changes: 5 additions & 1 deletion apps/meteor/server/services/room/service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ServiceClassInternal, Authorization, MeteorError } from '@rocket.chat/core-services';
import type { ICreateRoomParams, IRoomService } from '@rocket.chat/core-services';
import { type AtLeast, type IRoom, type IUser, isRoomWithJoinCode } from '@rocket.chat/core-typings';
import { type AtLeast, type IRoom, type IUser, isOmnichannelRoom, isRoomWithJoinCode } from '@rocket.chat/core-typings';
import { Rooms, Users } from '@rocket.chat/models';

import { FederationActions } from './hooks/BeforeFederationActions';
Expand Down Expand Up @@ -102,6 +102,10 @@ export class RoomService extends ServiceClassInternal implements IRoomService {
throw new MeteorError('error-not-allowed', 'Not allowed', { method: 'joinRoom' });
}

if (isOmnichannelRoom(room) && !room.open) {
throw new MeteorError('room-closed', 'Room is closed', { method: 'joinRoom' });
}

if (!(await Authorization.canAccessRoom(room, user))) {
throw new MeteorError('error-not-allowed', 'Not allowed', { method: 'joinRoom' });
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ test.describe('Omnichannel chat history', () => {
await api.delete('/livechat/users/agent/user1');
await api.delete('/livechat/users/manager/user1');
await agent.page.close();

await api.post('/permissions.update', { permissions: [{ _id: 'preview-c-room', roles: ['admin', 'owner', 'moderator', 'user'] }] });
});

test('Receiving a message from visitor', async ({ page }) => {
test('Receiving a message from visitor', async ({ page, api }) => {
await test.step('Expect send a message as a visitor', async () => {
await page.goto('/livechat');
await poLiveChat.openLiveChat();
Expand Down Expand Up @@ -72,5 +74,19 @@ test.describe('Omnichannel chat history', () => {
await agent.poHomeOmnichannel.contacts.contactInfo.historyItem.click();
await expect(agent.poHomeOmnichannel.contacts.contactInfo.historyMessage).toBeVisible();
});

await api.post('/permissions.update', { permissions: [{ _id: 'preview-c-room', roles: [] }] });

await test.step('Expect agent to see conversation history, but not join room', async () => {
await agent.page.reload();

await agent.poHomeOmnichannel.contacts.contactInfo.historyItem.click();
await agent.poHomeOmnichannel.contacts.contactInfo.historyMessage.click();
await agent.poHomeOmnichannel.contacts.contactInfo.btnOpenChat.click();

// Should not show the NoSubscribedRoom.tsx component on livechat rooms
await expect(agent.page.locator('div >> text=This conversation is already closed.')).toBeVisible();
await expect(agent.page.locator('div >> text="this_a_test_message_from_visitor"')).toBeVisible();
});
});
});
4 changes: 4 additions & 0 deletions apps/meteor/tests/e2e/page-objects/omnichannel-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,8 @@ export class OmnichannelContactInfo extends OmnichannelManageContact {
get historyMessage(): Locator {
return this.dialogContactInfo.getByRole('listitem').first();
}

get btnOpenChat(): Locator {
return this.dialogContactInfo.getByRole('button', { name: 'Open chat' });
}
}
45 changes: 44 additions & 1 deletion apps/meteor/tests/end-to-end/api/methods.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import type { Credentials } from '@rocket.chat/api-client';
import type { IMessage, IRoom, IThreadMessage, IUser } from '@rocket.chat/core-typings';
import type { IMessage, IOmnichannelRoom, IRoom, IThreadMessage, IUser } from '@rocket.chat/core-typings';
import { Random } from '@rocket.chat/random';
import { expect } from 'chai';
import { after, before, describe, it } from 'mocha';

import { api, credentials, getCredentials, methodCall, request } from '../../data/api-data';
import { sendSimpleMessage } from '../../data/chat.helper';
import { CI_MAX_ROOMS_PER_GUEST as maxRoomsPerGuest } from '../../data/constants';
import { closeOmnichannelRoom, createAgent, createLivechatRoom, createVisitor } from '../../data/livechat/rooms';
import { updatePermission, updateSetting } from '../../data/permissions.helper';
import { createRoom, deleteRoom } from '../../data/rooms.helper';
import { password } from '../../data/user';
Expand Down Expand Up @@ -3927,4 +3928,46 @@ describe('Meteor.methods', () => {
});
});
});

describe('[@joinRoom]', async () => {
let room: IOmnichannelRoom;
let user: TestUser<IUser>;
let userCredentials: Credentials;

before(async () => {
const visitor = await createVisitor();
room = await createLivechatRoom(visitor.token);
await closeOmnichannelRoom(room._id);

user = await createUser();
await createAgent(user.username);
userCredentials = await login(user.username, password);
});

after(() => Promise.all([deleteUser(user)]));

it('should not allow an agent to join a closed livechat room', async () => {
await request
.post(methodCall('joinRoom'))
.set(userCredentials)
.send({
message: JSON.stringify({
method: 'joinRoom',
params: [room._id],
id: 'id',
msg: 'method',
}),
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.a.property('success', true);
expect(res.body).to.have.a.property('message').that.is.a('string');

const data = JSON.parse(res.body.message);
expect(data).to.have.a.property('error').that.is.an('object');
expect(data.error).to.have.a.property('error', 'room-closed');
});
});
});
});
Loading