diff --git a/apps/meteor/app/api/server/v1/channels.ts b/apps/meteor/app/api/server/v1/channels.ts index 931cf4be20191..bdefd44555940 100644 --- a/apps/meteor/app/api/server/v1/channels.ts +++ b/apps/meteor/app/api/server/v1/channels.ts @@ -1,5 +1,5 @@ import { Team, Room } from '@rocket.chat/core-services'; -import type { IRoom, ISubscription, IUser, RoomType, IUpload } from '@rocket.chat/core-typings'; +import { TEAM_TYPE, type IRoom, type ISubscription, type IUser, type RoomType, type IUpload } from '@rocket.chat/core-typings'; import { Integrations, Messages, Rooms, Subscriptions, Uploads, Users } from '@rocket.chat/models'; import { isChannelsAddAllProps, @@ -285,6 +285,10 @@ API.v1.addRoute( const ourQuery = { ...query, rid: findResult._id }; + if (!(await canAccessRoomAsync(findResult, { _id: this.userId }))) { + return API.v1.unauthorized(); + } + // Special check for the permissions if ( (await hasPermissionAsync(this.userId, 'view-joined-room')) && @@ -439,6 +443,10 @@ API.v1.addRoute( const findResult = await findChannelByIdOrName({ params }); + if (!(await canAccessRoomAsync(findResult, { _id: this.userId }))) { + return API.v1.unauthorized(); + } + const moderators = ( await Subscriptions.findByRoomIdAndRoles(findResult._id, ['moderator'], { projection: { u: 1 }, @@ -874,6 +882,10 @@ API.v1.addRoute( checkedArchived: false, }); + if (!(await canAccessRoomAsync(findResult, { _id: this.userId }))) { + return API.v1.unauthorized(); + } + let includeAllPublicChannels = true; if (typeof this.queryParams.includeAllPublicChannels !== 'undefined') { includeAllPublicChannels = this.queryParams.includeAllPublicChannels === 'true'; @@ -919,12 +931,18 @@ API.v1.addRoute( { authRequired: true }, { async get() { + const findResult = await findChannelByIdOrName({ + params: this.queryParams, + checkedArchived: false, + userId: this.userId, + }); + + if (!(await canAccessRoomAsync(findResult, { _id: this.userId }))) { + return API.v1.unauthorized(); + } + return API.v1.success({ - channel: await findChannelByIdOrName({ - params: this.queryParams, - checkedArchived: false, - userId: this.userId, - }), + channel: findResult, }); }, }, @@ -1064,6 +1082,10 @@ API.v1.addRoute( checkedArchived: false, }); + if (!(await canAccessRoomAsync(findResult, { _id: this.userId }))) { + return API.v1.unauthorized(); + } + if (findResult.broadcast && !(await hasPermissionAsync(this.userId, 'view-broadcast-member-list', findResult._id))) { return API.v1.unauthorized(); } @@ -1416,7 +1438,7 @@ API.v1.addRoute( API.v1.addRoute( 'channels.anonymousread', - { authRequired: false }, + { authOrAnonRequired: true }, { async get() { const findResult = await findChannelByIdOrName({ @@ -1434,6 +1456,16 @@ API.v1.addRoute( }); } + // Public rooms of private teams should be accessible only by team members + if (findResult.teamId) { + const team = await Team.getOneById(findResult.teamId); + if (team?.type === TEAM_TYPE.PRIVATE) { + if (!this.userId || !(await canAccessRoomAsync(findResult, { _id: this.userId }))) { + return API.v1.notFound('Room not found'); + } + } + } + const { cursor, totalCount } = await Messages.findPaginated(ourQuery, { sort: sort || { ts: -1 }, skip: offset, diff --git a/apps/meteor/tests/data/chat.helper.ts b/apps/meteor/tests/data/chat.helper.ts index 46514969bd821..4e2bee007d4c3 100644 --- a/apps/meteor/tests/data/chat.helper.ts +++ b/apps/meteor/tests/data/chat.helper.ts @@ -1,3 +1,4 @@ +import type { Credentials } from '@rocket.chat/api-client'; import type { IRoom, IMessage } from '@rocket.chat/core-typings'; import { api, credentials, request } from './api-data'; @@ -29,6 +30,33 @@ export const sendSimpleMessage = ({ return request.post(api('chat.sendMessage')).set(credentials).send({ message }); }; +export const sendMessage = ({ + message, + requestCredentials, +}: { + message: { rid: IRoom['_id']; msg: string } & Partial>; + requestCredentials?: Credentials; +}) => { + return request + .post(api('chat.sendMessage')) + .set(requestCredentials ?? credentials) + .send({ message }); +}; + +export const starMessage = ({ messageId, requestCredentials }: { messageId: IMessage['_id']; requestCredentials?: Credentials }) => { + return request + .post(api('chat.starMessage')) + .set(requestCredentials ?? credentials) + .send({ messageId }); +}; + +export const pinMessage = ({ messageId, requestCredentials }: { messageId: IMessage['_id']; requestCredentials?: Credentials }) => { + return request + .post(api('chat.pinMessage')) + .set(requestCredentials ?? credentials) + .send({ messageId }); +}; + export const deleteMessage = ({ roomId, msgId }: { roomId: IRoom['_id']; msgId: IMessage['_id'] }) => { if (!roomId) { throw new Error('"roomId" is required in "deleteMessage" test helper'); diff --git a/apps/meteor/tests/end-to-end/api/channels.ts b/apps/meteor/tests/end-to-end/api/channels.ts index 59ba102fe23af..17076f4b1898b 100644 --- a/apps/meteor/tests/end-to-end/api/channels.ts +++ b/apps/meteor/tests/end-to-end/api/channels.ts @@ -1,14 +1,16 @@ import type { Credentials } from '@rocket.chat/api-client'; -import type { IIntegration, IMessage, IRoom, IUser } from '@rocket.chat/core-typings'; +import { TEAM_TYPE, type IIntegration, type IMessage, type IRoom, type ITeam, type IUser } from '@rocket.chat/core-typings'; +import { Random } from '@rocket.chat/random'; import { expect, assert } from 'chai'; import { after, before, describe, it } from 'mocha'; import { getCredentials, api, request, credentials, reservedWords } from '../../data/api-data'; +import { pinMessage, sendMessage, starMessage } from '../../data/chat.helper'; import { CI_MAX_ROOMS_PER_GUEST as maxRoomsPerGuest } from '../../data/constants'; import { createIntegration, removeIntegration } from '../../data/integration.helper'; import { updatePermission, updateSetting } from '../../data/permissions.helper'; import { createRoom, deleteRoom } from '../../data/rooms.helper'; -import { deleteTeam } from '../../data/teams.helper'; +import { createTeam, deleteTeam } from '../../data/teams.helper'; import { testFileUploads } from '../../data/uploads.helper'; import { adminUsername, password } from '../../data/user'; import type { TestUser } from '../../data/users.helper'; @@ -626,8 +628,9 @@ describe('[Channels]', () => { await Promise.all(channelIds.map((id) => deleteRoom({ type: 'c', roomId: id }))); }); }); + describe('[/channels.info]', () => { - const testChannelName = `api-channel-test-${Date.now()}`; + const testChannelName = `api-channel-test-${Date.now()}.${Random.id()}`; let testChannel: IRoom; after(async () => { @@ -807,6 +810,312 @@ describe('[Channels]', () => { }) .end(done); }); + describe('Additional Visibility Tests', () => { + let outsiderUser: IUser; + let insideUser: IUser; + let nonTeamUser: IUser; + let outsiderCredentials: { 'X-Auth-Token': string; 'X-User-Id': string }; + let insideCredentials: { 'X-Auth-Token': string; 'X-User-Id': string }; + let nonTeamCredentials: { 'X-Auth-Token': string; 'X-User-Id': string }; + + let privateChannel: IRoom; + let publicChannel: IRoom; + let publicTeam: ITeam; + let privateTeam: ITeam; + let privateChannelInPublicTeam: IRoom; + let publicChannelInPublicTeam: IRoom; + let privateChannelInPrivateTeam: IRoom; + let publicChannelInPrivateTeam: IRoom; + + before(async () => { + [outsiderUser, insideUser, nonTeamUser] = await Promise.all([ + createUser({ username: `e_${Random.id()}` }), + createUser({ username: `f_${Random.id()}` }), + createUser({ username: `g_${Random.id()}` }), + ]); + [outsiderCredentials, insideCredentials, nonTeamCredentials] = await Promise.all([ + login(outsiderUser.username, password), + login(insideUser.username, password), + login(nonTeamUser.username, password), + ]); + + // Create a public team and a private team + [publicTeam, privateTeam] = await Promise.all([ + createTeam(insideCredentials, `channels.info.team.public.${Random.id()}`, TEAM_TYPE.PUBLIC, [outsiderUser.username as string]), + createTeam(insideCredentials, `channels.info.team.private.${Random.id()}`, TEAM_TYPE.PRIVATE, [outsiderUser.username as string]), + ]); + + const [ + privateInPublicResponse, + publicInPublicResponse, + privateInPrivateResponse, + publicInPrivateResponse, + privateRoomResponse, + publicRoomResponse, + ] = await Promise.all([ + createRoom({ + type: 'p', + name: `teamPublic.privateChannel.${Date.now()}`, + credentials: insideCredentials, + extraData: { + teamId: publicTeam._id, + }, + }), + createRoom({ + type: 'c', + name: `teamPublic.publicChannel.${Date.now()}`, + credentials: insideCredentials, + extraData: { + teamId: publicTeam._id, + }, + }), + createRoom({ + type: 'p', + name: `teamPrivate.privateChannel.${Date.now()}`, + credentials: insideCredentials, + extraData: { + teamId: privateTeam._id, + }, + }), + createRoom({ + type: 'c', + name: `teamPrivate.publicChannel.${Date.now()}`, + credentials: insideCredentials, + extraData: { + teamId: privateTeam._id, + }, + }), + createRoom({ + type: 'p', + name: `channels.info.private.${Date.now()}`, + credentials: insideCredentials, + }), + createRoom({ + type: 'c', + name: `channels.info.public.${Date.now()}`, + credentials: insideCredentials, + }), + ]); + + privateChannelInPublicTeam = privateInPublicResponse.body.group; + publicChannelInPublicTeam = publicInPublicResponse.body.channel; + privateChannelInPrivateTeam = privateInPrivateResponse.body.group; + publicChannelInPrivateTeam = publicInPrivateResponse.body.channel; + privateChannel = privateRoomResponse.body.group; + publicChannel = publicRoomResponse.body.channel; + }); + + after(async () => { + await Promise.all([ + deleteRoom({ type: 'p', roomId: privateChannel._id }), + deleteRoom({ type: 'c', roomId: publicChannel._id }), + deleteRoom({ type: 'p', roomId: privateChannelInPublicTeam._id }), + deleteRoom({ type: 'c', roomId: publicChannelInPublicTeam._id }), + deleteRoom({ type: 'p', roomId: privateChannelInPrivateTeam._id }), + deleteRoom({ type: 'c', roomId: publicChannelInPrivateTeam._id }), + ]); + + await Promise.all([deleteTeam(credentials, publicTeam.name), deleteTeam(credentials, privateTeam.name)]); + + await Promise.all([deleteUser(outsiderUser), deleteUser(insideUser), deleteUser(nonTeamUser)]); + }); + + it('should not fetch private room info by user not part of room', async () => { + await request + .get(api('channels.info')) + .set(outsiderCredentials) + .query({ roomId: privateChannel._id }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + + it('should not fetch private room info by user who is part of the room', async () => { + const response = await request + .get(api('channels.info')) + .set(insideCredentials) + .query({ roomId: privateChannel._id }) + .expect('Content-Type', 'application/json') + .expect(400); + + expect(response.body.success).to.be.false; + }); + + it('should fetch public room info by user who is part of the room', async () => { + const response = await request + .get(api('channels.info')) + .set(insideCredentials) + .query({ roomId: publicChannel._id }) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(response.body.success).to.be.true; + expect(response.body).to.have.property('channel'); + }); + + it('should fetch public room info by user not part of room - because public', async () => { + const response = await request + .get(api('channels.info')) + .set(outsiderCredentials) + .query({ roomId: publicChannel._id }) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(response.body.success).to.be.true; + expect(response.body).to.have.property('channel'); + }); + + it('should not fetch a private channel info inside a public team by someone part of the room ', async () => { + await request + .get(api('channels.info')) + .set(insideCredentials) + .query({ roomId: privateChannelInPublicTeam._id }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + + it('should not fetch a private channel info inside a public team by someone not part of the room, but part of team', async () => { + await request + .get(api('channels.info')) + .set(outsiderCredentials) + .query({ roomId: privateChannelInPublicTeam._id }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + + it('should not fetch a private channel info inside a public team by someone not part of the team ', async () => { + await request + .get(api('channels.info')) + .set(nonTeamCredentials) + .query({ roomId: privateChannelInPublicTeam._id }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + + it('should fetch a public channel info inside a public team by someone part of the room ', async () => { + await request + .get(api('channels.info')) + .set(insideCredentials) + .query({ roomId: publicChannelInPublicTeam._id }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('channel'); + }); + }); + + it('should fetch a public channel info inside a public team by someone not part of the room, but part of team', async () => { + await request + .get(api('channels.info')) + .set(outsiderCredentials) + .query({ roomId: publicChannelInPublicTeam._id }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('channel'); + }); + }); + + it('should fetch a public channel info inside a public team by someone not part of the team ', async () => { + await request + .get(api('channels.info')) + .set(nonTeamCredentials) + .query({ roomId: publicChannelInPublicTeam._id }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('channel'); + }); + }); + + it('should fetch a public channel info inside a private team by someone part of the room', async () => { + await request + .get(api('channels.info')) + .set(insideCredentials) + .query({ roomId: publicChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('channel'); + }); + }); + + it('should fetch a public channel info inside a private team by someone not part of the room, but part of team', async () => { + await request + .get(api('channels.info')) + .set(outsiderCredentials) + .query({ roomId: publicChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('channel'); + }); + }); + + it('should not fetch a public channel info inside a private team by someone not part of team', async () => { + await request + .get(api('channels.info')) + .set(nonTeamCredentials) + .query({ roomId: publicChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + + it('should not fetch a private channel info inside a private team by someone part of the room', async () => { + await request + .get(api('channels.info')) + .set(insideCredentials) + .query({ roomId: privateChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + + it('should not fetch a private channel info inside a private team by someone not part of the room, but part of team', async () => { + await request + .get(api('channels.info')) + .set(outsiderCredentials) + .query({ roomId: privateChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + + it('should not fetch a private channel info inside a private team by someone not part of team', async () => { + await request + .get(api('channels.info')) + .set(nonTeamCredentials) + .query({ roomId: privateChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + }); }); describe('[/channels.online]', () => { @@ -1350,6 +1659,319 @@ describe('[Channels]', () => { }) .end(done); }); + + describe('Additional Visibility Tests', () => { + let outsiderUser: IUser; + let insideUser: IUser; + let nonTeamUser: IUser; + let outsiderCredentials: { 'X-Auth-Token': string; 'X-User-Id': string }; + let insideCredentials: { 'X-Auth-Token': string; 'X-User-Id': string }; + let nonTeamCredentials: { 'X-Auth-Token': string; 'X-User-Id': string }; + + let privateChannel: IRoom; + let publicChannel: IRoom; + let publicTeam: ITeam; + let privateTeam: ITeam; + let privateChannelInPublicTeam: IRoom; + let publicChannelInPublicTeam: IRoom; + let privateChannelInPrivateTeam: IRoom; + let publicChannelInPrivateTeam: IRoom; + + before(async () => { + [outsiderUser, insideUser, nonTeamUser] = await Promise.all([ + createUser({ username: `e_${Random.id()}` }), + createUser({ username: `f_${Random.id()}` }), + createUser({ username: `g_${Random.id()}` }), + ]); + [outsiderCredentials, insideCredentials, nonTeamCredentials] = await Promise.all([ + login(outsiderUser.username, password), + login(insideUser.username, password), + login(nonTeamUser.username, password), + ]); + + // Create a public team and a private team + [publicTeam, privateTeam] = await Promise.all([ + createTeam(insideCredentials, `channels.members.team.public.${Random.id()}`, TEAM_TYPE.PUBLIC, [outsiderUser.username as string]), + createTeam(insideCredentials, `channels.members.team.private.${Random.id()}`, TEAM_TYPE.PRIVATE, [ + outsiderUser.username as string, + ]), + ]); + + const [ + privateInPublicResponse, + publicInPublicResponse, + privateInPrivateResponse, + publicInPrivateResponse, + privateRoomResponse, + publicRoomResponse, + ] = await Promise.all([ + createRoom({ + type: 'p', + name: `teamPublic.privateChannel.${Date.now()}`, + credentials: insideCredentials, + extraData: { + teamId: publicTeam._id, + }, + }), + createRoom({ + type: 'c', + name: `teamPublic.publicChannel.${Date.now()}`, + credentials: insideCredentials, + extraData: { + teamId: publicTeam._id, + }, + }), + createRoom({ + type: 'p', + name: `teamPrivate.privateChannel.${Date.now()}`, + credentials: insideCredentials, + extraData: { + teamId: privateTeam._id, + }, + }), + createRoom({ + type: 'c', + name: `teamPrivate.publicChannel.${Date.now()}`, + credentials: insideCredentials, + extraData: { + teamId: privateTeam._id, + }, + }), + createRoom({ + type: 'p', + name: `rooms.members.private.${Date.now()}`, + credentials: insideCredentials, + }), + createRoom({ + type: 'c', + name: `rooms.members.public.${Date.now()}`, + credentials: insideCredentials, + }), + ]); + + privateChannelInPublicTeam = privateInPublicResponse.body.group; + publicChannelInPublicTeam = publicInPublicResponse.body.channel; + privateChannelInPrivateTeam = privateInPrivateResponse.body.group; + publicChannelInPrivateTeam = publicInPrivateResponse.body.channel; + privateChannel = privateRoomResponse.body.group; + publicChannel = publicRoomResponse.body.channel; + }); + + after(async () => { + await Promise.all([ + deleteRoom({ type: 'p', roomId: privateChannel._id }), + deleteRoom({ type: 'c', roomId: publicChannel._id }), + deleteRoom({ type: 'p', roomId: privateChannelInPublicTeam._id }), + deleteRoom({ type: 'c', roomId: publicChannelInPublicTeam._id }), + deleteRoom({ type: 'p', roomId: privateChannelInPrivateTeam._id }), + deleteRoom({ type: 'c', roomId: publicChannelInPrivateTeam._id }), + ]); + + await Promise.all([deleteTeam(credentials, publicTeam.name), deleteTeam(credentials, privateTeam.name)]); + + await Promise.all([deleteUser(outsiderUser), deleteUser(insideUser), deleteUser(nonTeamUser)]); + }); + + it('should not fetch private room members by user not part of room', async () => { + await request + .get(api('channels.members')) + .set(outsiderCredentials) + .query({ roomId: privateChannel._id }) + .expect('Content-Type', 'application/json') + .expect(404) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + + it('should fetch private room members by user who is part of the room', async () => { + const response = await request + .get(api('channels.members')) + .set(insideCredentials) + .query({ roomId: privateChannel._id }) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(response.body.success).to.be.true; + expect(response.body.members).to.be.an('array'); + }); + + it('should fetch public room members by user who is part of the room', async () => { + const response = await request + .get(api('channels.members')) + .set(insideCredentials) + .query({ roomId: publicChannel._id }) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(response.body.success).to.be.true; + expect(response.body.members).to.be.an('array'); + }); + + it('should fetch public room members by user not part of room - because public', async () => { + await updatePermission('view-c-room', ['admin', 'user', 'guest']); + const response = await request + .get(api('channels.members')) + .set(outsiderCredentials) + .query({ roomId: publicChannel._id }) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(response.body.success).to.be.true; + expect(response.body.members).to.be.an('array'); + }); + + it('should fetch a private channel members inside a public team by someone part of the room ', async () => { + await request + .get(api('channels.members')) + .set(insideCredentials) + .query({ roomId: privateChannelInPublicTeam._id }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body.members).to.be.an('array'); + }); + }); + + it('should not fetch a private channel members inside a public team by someone not part of the room, but part of team', async () => { + await request + .get(api('channels.members')) + .set(outsiderCredentials) + .query({ roomId: privateChannelInPublicTeam._id }) + .expect('Content-Type', 'application/json') + .expect(404) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + + it('should not fetch a private channel members inside a public team by someone not part of the team ', async () => { + await request + .get(api('channels.members')) + .set(nonTeamCredentials) + .query({ roomId: privateChannelInPublicTeam._id }) + .expect('Content-Type', 'application/json') + .expect(404) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + + it('should fetch a public channel members inside a public team by someone part of the room ', async () => { + await request + .get(api('channels.members')) + .set(insideCredentials) + .query({ roomId: publicChannelInPublicTeam._id }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body.members).to.be.an('array'); + }); + }); + + it('should fetch a public channel members inside a public team by someone not part of the room, but part of team', async () => { + await request + .get(api('channels.members')) + .set(outsiderCredentials) + .query({ roomId: publicChannelInPublicTeam._id }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body.members).to.be.an('array'); + }); + }); + + it('should fetch a public channel members inside a public team by someone not part of the team ', async () => { + await request + .get(api('channels.members')) + .set(nonTeamCredentials) + .query({ roomId: publicChannelInPublicTeam._id }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body.members).to.be.an('array'); + }); + }); + + it('should fetch a public channel members inside a private team by someone part of the room', async () => { + await request + .get(api('channels.members')) + .set(insideCredentials) + .query({ roomId: publicChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body.members).to.be.an('array'); + }); + }); + + it('should fetch a public channel members inside a private team by someone not part of the room, but part of team', async () => { + await request + .get(api('channels.members')) + .set(outsiderCredentials) + .query({ roomId: publicChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body.members).to.be.an('array'); + }); + }); + + it('should not fetch a public channel members inside a private team by someone not part of team', async () => { + await request + .get(api('channels.members')) + .set(nonTeamCredentials) + .query({ roomId: publicChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(404) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + + it('should fetch a private channel members inside a private team by someone part of the room', async () => { + await request + .get(api('channels.members')) + .set(insideCredentials) + .query({ roomId: privateChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body.members).to.be.an('array'); + }); + }); + + it('should not fetch a private channel members inside a private team by someone not part of the room, but part of team', async () => { + await request + .get(api('channels.members')) + .set(outsiderCredentials) + .query({ roomId: privateChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(404) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + + it('should not fetch a private channel members inside a private team by someone not part of team', async () => { + await request + .get(api('channels.members')) + .set(nonTeamCredentials) + .query({ roomId: privateChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(404) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + }); }); describe('/channels.getIntegrations', () => { @@ -1474,20 +2096,323 @@ describe('[Channels]', () => { }); }); }); - }); - - describe('/channels.setCustomFields:', () => { - let withCFChannel: IRoom; - let withoutCFChannel: IRoom; - - after(async () => { - await deleteRoom({ type: 'c', roomId: withCFChannel._id }); - }); - it('create channel with customFields', (done) => { - const customFields = { field0: 'value0' }; - void request - .post(api('channels.create')) + describe('Additional Visibility Tests', () => { + let outsiderUser: IUser; + let insideUser: IUser; + let nonTeamUser: IUser; + let outsiderCredentials: { 'X-Auth-Token': string; 'X-User-Id': string }; + let insideCredentials: { 'X-Auth-Token': string; 'X-User-Id': string }; + let nonTeamCredentials: { 'X-Auth-Token': string; 'X-User-Id': string }; + + let privateChannel: IRoom; + let publicChannel: IRoom; + let publicTeam: ITeam; + let privateTeam: ITeam; + let privateChannelInPublicTeam: IRoom; + let publicChannelInPublicTeam: IRoom; + let privateChannelInPrivateTeam: IRoom; + let publicChannelInPrivateTeam: IRoom; + + before(async () => { + [outsiderUser, insideUser, nonTeamUser] = await Promise.all([ + createUser({ username: `e_${Random.id()}` }), + createUser({ username: `f_${Random.id()}` }), + createUser({ username: `g_${Random.id()}` }), + ]); + [outsiderCredentials, insideCredentials, nonTeamCredentials] = await Promise.all([ + login(outsiderUser.username, password), + login(insideUser.username, password), + login(nonTeamUser.username, password), + ]); + + // Create a public team and a private team + [publicTeam, privateTeam] = await Promise.all([ + createTeam(insideCredentials, `channels.getIntegrations.team.public.${Random.id()}`, TEAM_TYPE.PUBLIC, [ + outsiderUser.username as string, + ]), + createTeam(insideCredentials, `channels.getIntegrations.team.private.${Random.id()}`, TEAM_TYPE.PRIVATE, [ + outsiderUser.username as string, + ]), + ]); + + const [ + privateInPublicResponse, + publicInPublicResponse, + privateInPrivateResponse, + publicInPrivateResponse, + privateRoomResponse, + publicRoomResponse, + ] = await Promise.all([ + createRoom({ + type: 'p', + name: `teamPublic.privateChannel.${Date.now()}`, + credentials: insideCredentials, + extraData: { + teamId: publicTeam._id, + }, + }), + createRoom({ + type: 'c', + name: `teamPublic.publicChannel.${Date.now()}`, + credentials: insideCredentials, + extraData: { + teamId: publicTeam._id, + }, + }), + createRoom({ + type: 'p', + name: `teamPrivate.privateChannel.${Date.now()}`, + credentials: insideCredentials, + extraData: { + teamId: privateTeam._id, + }, + }), + createRoom({ + type: 'c', + name: `teamPrivate.publicChannel.${Date.now()}`, + credentials: insideCredentials, + extraData: { + teamId: privateTeam._id, + }, + }), + createRoom({ + type: 'p', + name: `channels.getIntegrations.private.${Date.now()}`, + credentials: insideCredentials, + }), + createRoom({ + type: 'c', + name: `channels.getIntegrations.public.${Date.now()}`, + credentials: insideCredentials, + }), + ]); + + privateChannelInPublicTeam = privateInPublicResponse.body.group; + publicChannelInPublicTeam = publicInPublicResponse.body.channel; + privateChannelInPrivateTeam = privateInPrivateResponse.body.group; + publicChannelInPrivateTeam = publicInPrivateResponse.body.channel; + privateChannel = privateRoomResponse.body.group; + publicChannel = publicRoomResponse.body.channel; + + await updatePermission('manage-incoming-integrations', ['admin', 'user']); + }); + + after(async () => { + await Promise.all([ + deleteRoom({ type: 'p', roomId: privateChannel._id }), + deleteRoom({ type: 'c', roomId: publicChannel._id }), + deleteRoom({ type: 'p', roomId: privateChannelInPublicTeam._id }), + deleteRoom({ type: 'c', roomId: publicChannelInPublicTeam._id }), + deleteRoom({ type: 'p', roomId: privateChannelInPrivateTeam._id }), + deleteRoom({ type: 'c', roomId: publicChannelInPrivateTeam._id }), + ]); + + await Promise.all([deleteTeam(credentials, publicTeam.name), deleteTeam(credentials, privateTeam.name)]); + + await Promise.all([deleteUser(outsiderUser), deleteUser(insideUser), deleteUser(nonTeamUser)]); + + await updatePermission('manage-incoming-integrations', ['admin']); + }); + + it('should not fetch private room integrations by user who is part of the room', async () => { + const response = await request + .get(api('channels.getIntegrations')) + .set(insideCredentials) + .query({ roomId: privateChannel._id }) + .expect('Content-Type', 'application/json') + .expect(400); + + expect(response.body.success).to.be.false; + }); + + it('should fetch public room integrations by user who is part of the room', async () => { + const response = await request + .get(api('channels.getIntegrations')) + .set(insideCredentials) + .query({ roomId: publicChannel._id }) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(response.body.success).to.be.true; + expect(response.body).to.have.property('integrations'); + }); + + it('should fetch public room integrations by user not part of room - because public', async () => { + const response = await request + .get(api('channels.getIntegrations')) + .set(outsiderCredentials) + .query({ roomId: publicChannel._id }) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(response.body.success).to.be.true; + expect(response.body).to.have.property('integrations'); + }); + + it('should not fetch a private channel integrations inside a public team by someone part of the room ', async () => { + await request + .get(api('channels.getIntegrations')) + .set(insideCredentials) + .query({ roomId: privateChannelInPublicTeam._id }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + + it('should not fetch a private channel integrations inside a public team by someone not part of the room, but part of team', async () => { + await request + .get(api('channels.getIntegrations')) + .set(outsiderCredentials) + .query({ roomId: privateChannelInPublicTeam._id }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + + it('should not fetch a private channel integrations inside a public team by someone not part of the team ', async () => { + await request + .get(api('channels.getIntegrations')) + .set(nonTeamCredentials) + .query({ roomId: privateChannelInPublicTeam._id }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + + it('should fetch a public channel integrations inside a public team by someone part of the room ', async () => { + await request + .get(api('channels.getIntegrations')) + .set(insideCredentials) + .query({ roomId: publicChannelInPublicTeam._id }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('integrations'); + }); + }); + + it('should fetch a public channel integrations inside a public team by someone not part of the room, but part of team', async () => { + await request + .get(api('channels.getIntegrations')) + .set(outsiderCredentials) + .query({ roomId: publicChannelInPublicTeam._id }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('integrations'); + }); + }); + + it('should fetch a public channel integrations inside a public team by someone not part of the team ', async () => { + await request + .get(api('channels.getIntegrations')) + .set(nonTeamCredentials) + .query({ roomId: publicChannelInPublicTeam._id }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('integrations'); + }); + }); + + it('should fetch a public channel integrations inside a private team by someone part of the room', async () => { + await request + .get(api('channels.getIntegrations')) + .set(insideCredentials) + .query({ roomId: publicChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('integrations'); + }); + }); + + it('should fetch a public channel integrations inside a private team by someone not part of the room, but part of team', async () => { + await request + .get(api('channels.getIntegrations')) + .set(outsiderCredentials) + .query({ roomId: publicChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('integrations'); + }); + }); + + it('should not fetch a public channel integrations inside a private team by someone not part of team', async () => { + await request + .get(api('channels.getIntegrations')) + .set(nonTeamCredentials) + .query({ roomId: publicChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + + it('should not fetch a private channel integrations inside a private team by someone part of the room', async () => { + await request + .get(api('channels.getIntegrations')) + .set(insideCredentials) + .query({ roomId: privateChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + + it('should not fetch a private channel integrations inside a private team by someone not part of the room, but part of team', async () => { + await request + .get(api('channels.getIntegrations')) + .set(outsiderCredentials) + .query({ roomId: privateChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + + it('should not fetch a private channel integrations inside a private team by someone not part of team', async () => { + await request + .get(api('channels.getIntegrations')) + .set(nonTeamCredentials) + .query({ roomId: privateChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + }); + }); + + describe('/channels.setCustomFields:', () => { + let withCFChannel: IRoom; + let withoutCFChannel: IRoom; + + after(async () => { + await deleteRoom({ type: 'c', roomId: withCFChannel._id }); + }); + + it('create channel with customFields', (done) => { + const customFields = { field0: 'value0' }; + void request + .post(api('channels.create')) .set(credentials) .send({ name: `channel.cf.${Date.now()}`, @@ -1964,97 +2889,506 @@ describe('[Channels]', () => { }) .end(done); }); - }); - describe('/channels.anonymousread', () => { - let testChannel: IRoom; + describe('Additional Visibility Tests', () => { + let outsiderUser: IUser; + let insideUser: IUser; + let nonTeamUser: IUser; + let outsiderCredentials: { 'X-Auth-Token': string; 'X-User-Id': string }; + let insideCredentials: { 'X-Auth-Token': string; 'X-User-Id': string }; + let nonTeamCredentials: { 'X-Auth-Token': string; 'X-User-Id': string }; + + let privateChannel: IRoom; + let publicChannel: IRoom; + let publicTeam: ITeam; + let privateTeam: ITeam; + let privateChannelInPublicTeam: IRoom; + let publicChannelInPublicTeam: IRoom; + let privateChannelInPrivateTeam: IRoom; + let publicChannelInPrivateTeam: IRoom; + + before(async () => { + [outsiderUser, insideUser, nonTeamUser] = await Promise.all([ + createUser({ username: `e_${Random.id()}` }), + createUser({ username: `f_${Random.id()}` }), + createUser({ username: `g_${Random.id()}` }), + ]); + [outsiderCredentials, insideCredentials, nonTeamCredentials] = await Promise.all([ + login(outsiderUser.username, password), + login(insideUser.username, password), + login(nonTeamUser.username, password), + ]); + + // Create a public team and a private team + [publicTeam, privateTeam] = await Promise.all([ + createTeam(insideCredentials, `channels.moderators.team.public.${Random.id()}`, TEAM_TYPE.PUBLIC, [ + outsiderUser.username as string, + ]), + createTeam(insideCredentials, `channels.moderators.team.private.${Random.id()}`, TEAM_TYPE.PRIVATE, [ + outsiderUser.username as string, + ]), + ]); + + const [ + privateInPublicResponse, + publicInPublicResponse, + privateInPrivateResponse, + publicInPrivateResponse, + privateRoomResponse, + publicRoomResponse, + ] = await Promise.all([ + createRoom({ + type: 'p', + name: `teamPublic.privateChannel.${Date.now()}`, + credentials: insideCredentials, + extraData: { + teamId: publicTeam._id, + }, + }), + createRoom({ + type: 'c', + name: `teamPublic.publicChannel.${Date.now()}`, + credentials: insideCredentials, + extraData: { + teamId: publicTeam._id, + }, + }), + createRoom({ + type: 'p', + name: `teamPrivate.privateChannel.${Date.now()}`, + credentials: insideCredentials, + extraData: { + teamId: privateTeam._id, + }, + }), + createRoom({ + type: 'c', + name: `teamPrivate.publicChannel.${Date.now()}`, + credentials: insideCredentials, + extraData: { + teamId: privateTeam._id, + }, + }), + createRoom({ + type: 'p', + name: `channels.moderators.private.${Date.now()}`, + credentials: insideCredentials, + }), + createRoom({ + type: 'c', + name: `channels.moderators.public.${Date.now()}`, + credentials: insideCredentials, + }), + ]); + + privateChannelInPublicTeam = privateInPublicResponse.body.group; + publicChannelInPublicTeam = publicInPublicResponse.body.channel; + privateChannelInPrivateTeam = privateInPrivateResponse.body.group; + publicChannelInPrivateTeam = publicInPrivateResponse.body.channel; + privateChannel = privateRoomResponse.body.group; + publicChannel = publicRoomResponse.body.channel; + }); - before(async () => { - testChannel = (await createRoom({ type: 'c', name: `channel.anonymousread.test.${Date.now()}` })).body.channel; - }); + after(async () => { + await Promise.all([ + deleteRoom({ type: 'p', roomId: privateChannel._id }), + deleteRoom({ type: 'c', roomId: publicChannel._id }), + deleteRoom({ type: 'p', roomId: privateChannelInPublicTeam._id }), + deleteRoom({ type: 'c', roomId: publicChannelInPublicTeam._id }), + deleteRoom({ type: 'p', roomId: privateChannelInPrivateTeam._id }), + deleteRoom({ type: 'c', roomId: publicChannelInPrivateTeam._id }), + ]); - after(async () => { - await Promise.all([updateSetting('Accounts_AllowAnonymousRead', false), deleteRoom({ type: 'c', roomId: testChannel._id })]); - }); + await Promise.all([deleteTeam(credentials, publicTeam.name), deleteTeam(credentials, privateTeam.name)]); - it('should return an error when the setting "Accounts_AllowAnonymousRead" is disabled', (done) => { - void updateSetting('Accounts_AllowAnonymousRead', false).then(() => { - void request - .get(api('channels.anonymousread')) - .query({ - roomId: testChannel._id, - }) + await Promise.all([deleteUser(outsiderUser), deleteUser(insideUser), deleteUser(nonTeamUser)]); + }); + + it('should not fetch private room moderators by user not part of room', async () => { + await request + .get(api('channels.moderators')) + .set(outsiderCredentials) + .query({ roomId: privateChannel._id }) .expect('Content-Type', 'application/json') .expect(400) .expect((res) => { - expect(res.body).to.have.a.property('success', false); - expect(res.body).to.have.a.property('error'); - expect(res.body).to.have.a.property('errorType'); - expect(res.body.errorType).to.be.equal('error-not-allowed'); - expect(res.body.error).to.be.equal('Enable "Allow Anonymous Read" [error-not-allowed]'); - }) - .end(done); + expect(res.body).to.have.property('success', false); + }); }); - }); - it('should return the messages list when the setting "Accounts_AllowAnonymousRead" is enabled', (done) => { - void updateSetting('Accounts_AllowAnonymousRead', true).then(() => { - void request - .get(api('channels.anonymousread')) - .query({ - roomId: testChannel._id, - }) + + it('should not fetch private room moderators by user who is part of the room', async () => { + const response = await request + .get(api('channels.moderators')) + .set(insideCredentials) + .query({ roomId: privateChannel._id }) .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('messages').that.is.an('array'); - }) - .end(done); + .expect(400); + + expect(response.body.success).to.be.false; }); - }); - it('should return the messages list when the setting "Accounts_AllowAnonymousRead" is enabled even requested with count and offset params', (done) => { - void updateSetting('Accounts_AllowAnonymousRead', true).then(() => { - void request - .get(api('channels.anonymousread')) - .query({ - roomId: testChannel._id, - count: 5, - offset: 0, - }) + + it('should fetch public room moderators by user who is part of the room', async () => { + const response = await request + .get(api('channels.moderators')) + .set(insideCredentials) + .query({ roomId: publicChannel._id }) .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('messages').that.is.an('array'); - }) - .end(done); + .expect(200); + + expect(response.body.success).to.be.true; + expect(response.body).to.have.property('moderators'); }); - }); - }); - describe('/channels.convertToTeam', () => { - let testChannel: IRoom; + it('should fetch public room moderators by user not part of room - because public', async () => { + const response = await request + .get(api('channels.moderators')) + .set(outsiderCredentials) + .query({ roomId: publicChannel._id }) + .expect('Content-Type', 'application/json') + .expect(200); - before(async () => { - testChannel = (await createRoom({ type: 'c', name: `channel.convertToTeam.test.${Date.now()}` })).body.channel; - }); + expect(response.body.success).to.be.true; + expect(response.body).to.have.property('moderators'); + }); - after(async () => { - assert.isDefined(testChannel.name); - await Promise.all([ - updatePermission('create-team', ['admin', 'user']), - updatePermission('edit-room', ['admin', 'owner', 'moderator']), - deleteTeam(credentials, testChannel.name), - ]); - }); + it('should not fetch a private channel moderators inside a public team by someone part of the room ', async () => { + await request + .get(api('channels.moderators')) + .set(insideCredentials) + .query({ roomId: privateChannelInPublicTeam._id }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); - it('should fail to convert channel if lacking edit-room permission', async () => { - await updatePermission('create-team', []); - await updatePermission('edit-room', ['admin']); + it('should not fetch a private channel moderators inside a public team by someone not part of the room, but part of team', async () => { + await request + .get(api('channels.moderators')) + .set(outsiderCredentials) + .query({ roomId: privateChannelInPublicTeam._id }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); - await request - .post(api('channels.convertToTeam')) - .set(credentials) + it('should not fetch a private channel moderators inside a public team by someone not part of the team ', async () => { + await request + .get(api('channels.moderators')) + .set(nonTeamCredentials) + .query({ roomId: privateChannelInPublicTeam._id }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + + it('should fetch a public channel moderators inside a public team by someone part of the room ', async () => { + await request + .get(api('channels.moderators')) + .set(insideCredentials) + .query({ roomId: publicChannelInPublicTeam._id }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('moderators'); + }); + }); + + it('should fetch a public channel moderators inside a public team by someone not part of the room, but part of team', async () => { + await request + .get(api('channels.moderators')) + .set(outsiderCredentials) + .query({ roomId: publicChannelInPublicTeam._id }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('moderators'); + }); + }); + + it('should fetch a public channel moderators inside a public team by someone not part of the team ', async () => { + await request + .get(api('channels.moderators')) + .set(nonTeamCredentials) + .query({ roomId: publicChannelInPublicTeam._id }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('moderators'); + }); + }); + + it('should fetch a public channel moderators inside a private team by someone part of the room', async () => { + await request + .get(api('channels.moderators')) + .set(insideCredentials) + .query({ roomId: publicChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('moderators'); + }); + }); + + it('should fetch a public channel moderators inside a private team by someone not part of the room, but part of team', async () => { + await request + .get(api('channels.moderators')) + .set(outsiderCredentials) + .query({ roomId: publicChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('moderators'); + }); + }); + + it('should not fetch a public channel moderators inside a private team by someone not part of team', async () => { + await request + .get(api('channels.moderators')) + .set(nonTeamCredentials) + .query({ roomId: publicChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + + it('should not fetch a private channel moderators inside a private team by someone part of the room', async () => { + await request + .get(api('channels.moderators')) + .set(insideCredentials) + .query({ roomId: privateChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + + it('should not fetch a private channel moderators inside a private team by someone not part of the room, but part of team', async () => { + await request + .get(api('channels.moderators')) + .set(outsiderCredentials) + .query({ roomId: privateChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + + it('should not fetch a private channel moderators inside a private team by someone not part of team', async () => { + await request + .get(api('channels.moderators')) + .set(nonTeamCredentials) + .query({ roomId: privateChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + }); + }); + + describe('/channels.anonymousread', () => { + let testChannel: IRoom; + + before(async () => { + testChannel = (await createRoom({ type: 'c', name: `channel.anonymousread.test.${Date.now()}` })).body.channel; + }); + + after(async () => { + await Promise.all([updateSetting('Accounts_AllowAnonymousRead', false), deleteRoom({ type: 'c', roomId: testChannel._id })]); + }); + + it('should return an error when the setting "Accounts_AllowAnonymousRead" is disabled', (done) => { + void updateSetting('Accounts_AllowAnonymousRead', false).then(() => { + void request + .get(api('channels.anonymousread')) + .query({ + roomId: testChannel._id, + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.a.property('success', false); + expect(res.body).to.have.a.property('error', 'Enable "Allow Anonymous Read" [error-not-allowed]'); + }) + .end(done); + }); + }); + it('should return the messages list when the setting "Accounts_AllowAnonymousRead" is enabled', (done) => { + void updateSetting('Accounts_AllowAnonymousRead', true).then(() => { + void request + .get(api('channels.anonymousread')) + .query({ + roomId: testChannel._id, + }) + .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('messages').that.is.an('array'); + }) + .end(done); + }); + }); + it('should return the messages list when the setting "Accounts_AllowAnonymousRead" is enabled even requested with count and offset params', (done) => { + void updateSetting('Accounts_AllowAnonymousRead', true).then(() => { + void request + .get(api('channels.anonymousread')) + .query({ + roomId: testChannel._id, + count: 5, + offset: 0, + }) + .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('messages').that.is.an('array'); + }) + .end(done); + }); + }); + describe('Additional Visibility Tests', () => { + let outsiderUser: IUser; + let insideUser: IUser; + let nonTeamUser: IUser; + let outsiderCredentials: { 'X-Auth-Token': string; 'X-User-Id': string }; + let insideCredentials: { 'X-Auth-Token': string; 'X-User-Id': string }; + let nonTeamCredentials: { 'X-Auth-Token': string; 'X-User-Id': string }; + + let privateTeam: ITeam; + let publicChannelInPrivateTeam: IRoom; + + before(async () => { + [outsiderUser, insideUser, nonTeamUser] = await Promise.all([ + createUser({ username: `e_${Random.id()}` }), + createUser({ username: `f_${Random.id()}` }), + createUser({ username: `g_${Random.id()}` }), + ]); + [outsiderCredentials, insideCredentials, nonTeamCredentials] = await Promise.all([ + login(outsiderUser.username, password), + login(insideUser.username, password), + login(nonTeamUser.username, password), + ]); + + // Create a private team + privateTeam = await createTeam(insideCredentials, `channels.anonymousread.team.private.${Random.id()}`, TEAM_TYPE.PRIVATE, [ + outsiderUser.username as string, + ]); + + const publicInPrivateResponse = await createRoom({ + type: 'c', + name: `teamPrivate.publicChannel.${Date.now()}`, + credentials: insideCredentials, + extraData: { + teamId: privateTeam._id, + }, + }); + + publicChannelInPrivateTeam = publicInPrivateResponse.body.channel; + + await updateSetting('Accounts_AllowAnonymousRead', true); + }); + + after(async () => { + await deleteRoom({ type: 'c', roomId: publicChannelInPrivateTeam._id }); + + await Promise.all([deleteTeam(credentials, privateTeam.name)]); + + await Promise.all([deleteUser(outsiderUser), deleteUser(insideUser), deleteUser(nonTeamUser)]); + + await updateSetting('Accounts_AllowAnonymousRead', false); + }); + + it('should fetch a public channel messages inside a private team by someone part of the room', async () => { + await request + .get(api('channels.anonymousread')) + .set(insideCredentials) + .query({ roomId: publicChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('messages'); + }); + }); + + it('should fetch a public channel messages inside a private team by someone not part of the room, but part of team', async () => { + await request + .get(api('channels.anonymousread')) + .set(outsiderCredentials) + .query({ roomId: publicChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('messages'); + }); + }); + + it('should not fetch a public channel messages inside a private team by someone not part of team', async () => { + await request + .get(api('channels.anonymousread')) + .set(nonTeamCredentials) + .query({ roomId: publicChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(404) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + + it('should not fetch a public channel messages inside a private team when unauthenticated', async () => { + await request + .get(api('channels.anonymousread')) + .query({ roomId: publicChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(404) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + }); + }); + + describe('/channels.convertToTeam', () => { + let testChannel: IRoom; + + before(async () => { + testChannel = (await createRoom({ type: 'c', name: `channel.convertToTeam.test.${Date.now()}` })).body.channel; + }); + + after(async () => { + assert.isDefined(testChannel.name); + await Promise.all([ + updatePermission('create-team', ['admin', 'user']), + updatePermission('edit-room', ['admin', 'owner', 'moderator']), + deleteTeam(credentials, testChannel.name), + ]); + }); + + it('should fail to convert channel if lacking edit-room permission', async () => { + await updatePermission('create-team', []); + await updatePermission('edit-room', ['admin']); + + await request + .post(api('channels.convertToTeam')) + .set(credentials) .send({ channelId: testChannel._id }) .expect(403) .expect((res) => { @@ -2243,4 +3577,501 @@ describe('[Channels]', () => { }); }); }); + + describe('[/channels.messages]', () => { + let testChannel: IRoom; + let emptyChannel: IRoom; + let firstUser: IUser; + let secondUser: IUser; + + before(async () => { + await updatePermission('view-c-room', ['admin', 'user', 'bot', 'app', 'anonymous']); + emptyChannel = (await createRoom({ type: 'c', name: `channels.messages.empty.test.${Date.now()}` })).body.channel; + testChannel = (await createRoom({ type: 'c', name: `channels.messages.test.${Date.now()}` })).body.channel; + + firstUser = await createUser({ joinDefaultChannels: false }); + secondUser = await createUser({ joinDefaultChannels: false }); + + const messages = [ + { + rid: testChannel._id, + msg: `@${firstUser.username} youre being mentioned`, + mentions: [{ username: firstUser.username, _id: firstUser._id, name: firstUser.name }], + }, + { + rid: testChannel._id, + msg: `@${secondUser.username} youre being mentioned`, + mentions: [{ username: secondUser.username, _id: secondUser._id, name: secondUser.name }], + }, + { + rid: testChannel._id, + msg: `A simple message`, + }, + { + rid: testChannel._id, + msg: `A pinned simple message`, + }, + ]; + + const [, , starredMessage, pinnedMessage] = await Promise.all(messages.map((message) => sendMessage({ message }))); + + await Promise.all([ + starMessage({ messageId: starredMessage.body.message._id }), + pinMessage({ messageId: pinnedMessage.body.message._id }), + ]); + }); + + after(async () => { + await updatePermission('view-c-room', ['admin', 'user', 'bot', 'app', 'anonymous']); + await deleteRoom({ type: 'c', roomId: testChannel._id }); + }); + + it('should return an empty array of messages when inspecting a new room', async () => { + await request + .get(api('channels.messages')) + .set(credentials) + .query({ + roomId: emptyChannel._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('messages').and.to.be.an('array').that.is.empty; + expect(res.body).to.have.property('count', 0); + expect(res.body).to.have.property('total', 0); + }); + }); + + it('should return an array of messages when inspecting a room with messages', async () => { + await request + .get(api('channels.messages')) + .set(credentials) + .query({ + roomId: testChannel._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('messages').and.to.be.an('array').that.has.lengthOf(5); + expect(res.body).to.have.property('count', 5); + expect(res.body).to.have.property('total', 5); + + const pinnedMessage = res.body.messages.find((message: any) => message.t === 'message_pinned'); + expect(pinnedMessage).to.not.be.undefined; + }); + }); + + it('should not return message when the user does NOT have the necessary permission', async () => { + await updatePermission('view-c-room', []); + await request + .get(api('channels.messages')) + .set(credentials) + .query({ + roomId: testChannel._id, + }) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('error', 'User does not have the permissions required for this action [error-unauthorized]'); + }); + await updatePermission('view-c-room', ['admin', 'user', 'bot', 'app', 'anonymous']); + }); + + it('should return messages that mention a single user', async () => { + await request + .get(api('channels.messages')) + .set(credentials) + .query({ + roomId: testChannel._id, + mentionIds: firstUser._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body.messages).to.have.lengthOf(1); + expect(res.body.messages[0]).to.have.nested.property('mentions').that.is.an('array').and.to.have.lengthOf(1); + expect(res.body.messages[0].mentions[0]).to.have.property('_id', firstUser._id); + expect(res.body).to.have.property('count', 1); + expect(res.body).to.have.property('total', 1); + }); + }); + + it('should return messages that mention multiple users', async () => { + await request + .get(api('channels.messages')) + .set(credentials) + .query({ + roomId: testChannel._id, + mentionIds: `${firstUser._id},${secondUser._id}`, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body.messages).to.have.lengthOf(2); + expect(res.body).to.have.property('count', 2); + expect(res.body).to.have.property('total', 2); + + const mentionIds = res.body.messages.map((message: any) => message.mentions[0]._id); + expect(mentionIds).to.include.members([firstUser._id, secondUser._id]); + }); + }); + + it('should return messages that are starred by a specific user', async () => { + await request + .get(api('channels.messages')) + .set(credentials) + .query({ + roomId: testChannel._id, + starredIds: 'rocketchat.internal.admin.test', + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body.messages).to.have.lengthOf(1); + expect(res.body.messages[0]).to.have.nested.property('starred').that.is.an('array').and.to.have.lengthOf(1); + expect(res.body).to.have.property('count', 1); + expect(res.body).to.have.property('total', 1); + }); + }); + + // Return messages that are pinned + it('should return messages that are pinned', async () => { + await request + .get(api('channels.messages')) + .set(credentials) + .query({ + roomId: testChannel._id, + pinned: true, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body.messages).to.have.lengthOf(1); + expect(res.body.messages[0]).to.have.nested.property('pinned').that.is.an('boolean').and.to.be.true; + expect(res.body.messages[0]).to.have.nested.property('pinnedBy').that.is.an('object'); + expect(res.body.messages[0].pinnedBy).to.have.property('_id', 'rocketchat.internal.admin.test'); + expect(res.body).to.have.property('count', 1); + expect(res.body).to.have.property('total', 1); + }); + }); + + describe('Additional Visibility Tests', () => { + let outsiderUser: IUser; + let insideUser: IUser; + let nonTeamUser: IUser; + let outsiderCredentials: { 'X-Auth-Token': string; 'X-User-Id': string }; + let insideCredentials: { 'X-Auth-Token': string; 'X-User-Id': string }; + let nonTeamCredentials: { 'X-Auth-Token': string; 'X-User-Id': string }; + + let privateChannel: IRoom; + let publicChannel: IRoom; + let publicTeam: ITeam; + let privateTeam: ITeam; + let privateChannelInPublicTeam: IRoom; + let publicChannelInPublicTeam: IRoom; + let privateChannelInPrivateTeam: IRoom; + let publicChannelInPrivateTeam: IRoom; + + before(async () => { + [outsiderUser, insideUser, nonTeamUser] = await Promise.all([ + createUser({ username: `e_${Random.id()}` }), + createUser({ username: `f_${Random.id()}` }), + createUser({ username: `g_${Random.id()}` }), + ]); + [outsiderCredentials, insideCredentials, nonTeamCredentials] = await Promise.all([ + login(outsiderUser.username, password), + login(insideUser.username, password), + login(nonTeamUser.username, password), + ]); + + // Create a public team and a private team + [publicTeam, privateTeam] = await Promise.all([ + createTeam(insideCredentials, `channels.messages.team.public.${Random.id()}`, TEAM_TYPE.PUBLIC, [ + outsiderUser.username as string, + ]), + createTeam(insideCredentials, `channels.messages.team.private.${Random.id()}`, TEAM_TYPE.PRIVATE, [ + outsiderUser.username as string, + ]), + ]); + + const [ + privateInPublicResponse, + publicInPublicResponse, + privateInPrivateResponse, + publicInPrivateResponse, + privateRoomResponse, + publicRoomResponse, + ] = await Promise.all([ + createRoom({ + type: 'p', + name: `teamPublic.privateChannel.${Date.now()}`, + credentials: insideCredentials, + extraData: { + teamId: publicTeam._id, + }, + }), + createRoom({ + type: 'c', + name: `teamPublic.publicChannel.${Date.now()}`, + credentials: insideCredentials, + extraData: { + teamId: publicTeam._id, + }, + }), + createRoom({ + type: 'p', + name: `teamPrivate.privateChannel.${Date.now()}`, + credentials: insideCredentials, + extraData: { + teamId: privateTeam._id, + }, + }), + createRoom({ + type: 'c', + name: `teamPrivate.publicChannel.${Date.now()}`, + credentials: insideCredentials, + extraData: { + teamId: privateTeam._id, + }, + }), + createRoom({ + type: 'p', + name: `channels.messages.private.${Date.now()}`, + credentials: insideCredentials, + }), + createRoom({ + type: 'c', + name: `channels.messages.public.${Date.now()}`, + credentials: insideCredentials, + }), + ]); + + privateChannelInPublicTeam = privateInPublicResponse.body.group; + publicChannelInPublicTeam = publicInPublicResponse.body.channel; + privateChannelInPrivateTeam = privateInPrivateResponse.body.group; + publicChannelInPrivateTeam = publicInPrivateResponse.body.channel; + privateChannel = privateRoomResponse.body.group; + publicChannel = publicRoomResponse.body.channel; + }); + + after(async () => { + await Promise.all([ + deleteRoom({ type: 'p', roomId: privateChannel._id }), + deleteRoom({ type: 'c', roomId: publicChannel._id }), + deleteRoom({ type: 'p', roomId: privateChannelInPublicTeam._id }), + deleteRoom({ type: 'c', roomId: publicChannelInPublicTeam._id }), + deleteRoom({ type: 'p', roomId: privateChannelInPrivateTeam._id }), + deleteRoom({ type: 'c', roomId: publicChannelInPrivateTeam._id }), + ]); + + await Promise.all([deleteTeam(credentials, publicTeam.name), deleteTeam(credentials, privateTeam.name)]); + + await Promise.all([deleteUser(outsiderUser), deleteUser(insideUser), deleteUser(nonTeamUser)]); + }); + + it('should not fetch private room messages by user not part of room', async () => { + await request + .get(api('channels.messages')) + .set(outsiderCredentials) + .query({ roomId: privateChannel._id }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + + it('should not fetch private room messages by user who is part of the room', async () => { + const response = await request + .get(api('channels.messages')) + .set(insideCredentials) + .query({ roomId: privateChannel._id }) + .expect('Content-Type', 'application/json') + .expect(400); + + expect(response.body.success).to.be.false; + }); + + it('should fetch public room messages by user who is part of the room', async () => { + const response = await request + .get(api('channels.messages')) + .set(insideCredentials) + .query({ roomId: publicChannel._id }) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(response.body.success).to.be.true; + expect(response.body.messages).to.be.an('array'); + }); + + it('should fetch public room messages by user not part of room - because public', async () => { + await updatePermission('view-c-room', ['admin', 'user', 'guest']); + const response = await request + .get(api('channels.messages')) + .set(outsiderCredentials) + .query({ roomId: publicChannel._id }) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(response.body.success).to.be.true; + expect(response.body.messages).to.be.an('array'); + }); + + it('should not fetch a private channel messages inside a public team by someone part of the room ', async () => { + await request + .get(api('channels.messages')) + .set(insideCredentials) + .query({ roomId: privateChannelInPublicTeam._id }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + + it('should not fetch a private channel messages inside a public team by someone not part of the room, but part of team', async () => { + await request + .get(api('channels.messages')) + .set(outsiderCredentials) + .query({ roomId: privateChannelInPublicTeam._id }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + + it('should not fetch a private channel messages inside a public team by someone not part of the team ', async () => { + await request + .get(api('channels.messages')) + .set(nonTeamCredentials) + .query({ roomId: privateChannelInPublicTeam._id }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + + it('should fetch a public channel messages inside a public team by someone part of the room ', async () => { + await request + .get(api('channels.messages')) + .set(insideCredentials) + .query({ roomId: publicChannelInPublicTeam._id }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body.messages).to.be.an('array'); + }); + }); + + it('should fetch a public channel messages inside a public team by someone not part of the room, but part of team', async () => { + await request + .get(api('channels.messages')) + .set(outsiderCredentials) + .query({ roomId: publicChannelInPublicTeam._id }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body.messages).to.be.an('array'); + }); + }); + + it('should fetch a public channel messages inside a public team by someone not part of the team ', async () => { + await request + .get(api('channels.messages')) + .set(nonTeamCredentials) + .query({ roomId: publicChannelInPublicTeam._id }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body.messages).to.be.an('array'); + }); + }); + + it('should fetch a public channel messages inside a private team by someone part of the room', async () => { + await request + .get(api('channels.messages')) + .set(insideCredentials) + .query({ roomId: publicChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body.messages).to.be.an('array'); + }); + }); + + it('should fetch a public channel messages inside a private team by someone not part of the room, but part of team', async () => { + await request + .get(api('channels.messages')) + .set(outsiderCredentials) + .query({ roomId: publicChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body.messages).to.be.an('array'); + }); + }); + + it('should not fetch a public channel messages inside a private team by someone not part of team', async () => { + await request + .get(api('channels.messages')) + .set(nonTeamCredentials) + .query({ roomId: publicChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(403) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + + it('should not fetch a private channel messages inside a private team by someone part of the room', async () => { + await request + .get(api('channels.messages')) + .set(insideCredentials) + .query({ roomId: privateChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + + it('should not fetch a private channel messages inside a private team by someone not part of the room, but part of team', async () => { + await request + .get(api('channels.messages')) + .set(outsiderCredentials) + .query({ roomId: privateChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + + it('should not fetch a private channel messages inside a private team by someone not part of team', async () => { + await request + .get(api('channels.messages')) + .set(nonTeamCredentials) + .query({ roomId: privateChannelInPrivateTeam._id }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + }); + }); + }); + }); });