From aebe4964e1cab149f93672f7d3d0b81370890cff Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Mon, 26 Dec 2022 18:33:39 -0800 Subject: [PATCH] [FIX] `*.files` endpoints returning hidden files (#27617) --- apps/meteor/server/models/raw/Uploads.ts | 18 +- apps/meteor/tests/data/uploads.helper.ts | 224 ++++++++++++++++++ .../tests/end-to-end/api/02-channels.js | 149 +----------- apps/meteor/tests/end-to-end/api/03-groups.js | 45 +--- .../tests/end-to-end/api/04-direct-message.js | 22 +- 5 files changed, 251 insertions(+), 207 deletions(-) create mode 100644 apps/meteor/tests/data/uploads.helper.ts diff --git a/apps/meteor/server/models/raw/Uploads.ts b/apps/meteor/server/models/raw/Uploads.ts index fd3bcfc9db4..eec7e6fb3bb 100644 --- a/apps/meteor/server/models/raw/Uploads.ts +++ b/apps/meteor/server/models/raw/Uploads.ts @@ -1,7 +1,18 @@ // TODO: Lib imports should not exists inside the raw models import type { IUpload, RocketChatRecordDeleted } from '@rocket.chat/core-typings'; import type { FindPaginated, IUploadsModel } from '@rocket.chat/model-typings'; -import type { Collection, FindCursor, Db, DeleteResult, IndexDescription, InsertOneResult, UpdateResult, WithId, Filter } from 'mongodb'; +import type { + Collection, + FindCursor, + Db, + DeleteResult, + IndexDescription, + InsertOneResult, + UpdateResult, + WithId, + Filter, + FindOptions, +} from 'mongodb'; import { escapeRegExp } from '@rocket.chat/string-helpers'; import { BaseRaw } from './BaseRaw'; @@ -20,7 +31,7 @@ export class UploadsRaw extends BaseRaw implements IUploadsModel { } protected modelIndexes(): IndexDescription[] { - return [{ key: { rid: 1 } }, { key: { uploadedAt: 1 } }, { key: { typeGroup: 1 } }]; + return [{ key: { uploadedAt: -1 } }, { key: { rid: 1, _hidden: 1, typeGroup: 1 } }]; } findNotHiddenFilesOfRoom(roomId: string, searchText: string, fileType: string, limit: number): FindCursor { @@ -100,10 +111,11 @@ export class UploadsRaw extends BaseRaw implements IUploadsModel { return this.deleteOne({ _id: fileId }); } - findPaginatedWithoutThumbs(query: Filter = {}, options?: any): FindPaginated>> { + findPaginatedWithoutThumbs(query: Filter = {}, options?: FindOptions): FindPaginated>> { return this.findPaginated( { ...query, + _hidden: { $ne: true }, typeGroup: { $ne: 'thumb' }, }, options, diff --git a/apps/meteor/tests/data/uploads.helper.ts b/apps/meteor/tests/data/uploads.helper.ts new file mode 100644 index 00000000000..679d99ba5fa --- /dev/null +++ b/apps/meteor/tests/data/uploads.helper.ts @@ -0,0 +1,224 @@ +import type { Response } from 'supertest'; +import { expect } from 'chai'; + +import { api, request, credentials } from './api-data.js'; +import { password } from './user.js'; +import { createUser, login } from './users.helper'; +import { imgURL } from './interactions.js'; +import { updateSetting } from './permissions.helper'; +import { createRoom } from './rooms.helper'; +import { createVisitor } from './livechat/rooms'; + +export async function testFileUploads(filesEndpoint: 'channels.files' | 'groups.files' | 'im.files', room: { _id: string; name?: string; t: string;}, invalidRoomError = 'error-room-not-found') { + before(async function () { + await updateSetting('VoIP_Enabled', true); + await updateSetting('Message_KeepHistory', true); + }); + + after(async function () { + await updateSetting('VoIP_Enabled', false); + await updateSetting('Message_KeepHistory', false); + }); + + console.log('filesEndpoint', filesEndpoint, room); + + const createVoipRoom = async function () { + const testUser = await createUser({ roles: ['user', 'livechat-agent'] }); + const testUserCredentials = await login(testUser.username, password); + const visitor = await createVisitor(); + const roomResponse = await createRoom({ + token: visitor.token, + type: 'v', + agentId: testUser._id, + credentials: testUserCredentials, + name: null, + username: null, + members: null, + }); + return roomResponse.body.room; + }; + + it('should fail if invalid channel', function (done) { + request + .get(api(filesEndpoint)) + .set(credentials) + .query({ + roomId: 'invalid', + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect(function (res: Response) { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', invalidRoomError); + }) + .end(done); + }); + + it('should fail for room type v', async function () { + const { _id } = await createVoipRoom(); + request + .get(api(filesEndpoint)) + .set(credentials) + .query({ + roomId: _id, + }) + .expect('Content-Type', 'application/json') + .expect(400) + .expect(function (res: Response) { + expect(res.body).to.have.property('success', false); + expect(res.body).to.have.property('errorType', 'error-room-not-found'); + }); + }); + + it('should succeed when searching by roomId', function (done) { + console.log('room._id ->', room._id); + request + .get(api(filesEndpoint)) + .set(credentials) + .query({ + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect(function (res: Response) { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('files').and.to.be.an('array'); + }) + .end(done); + }); + + it('should succeed when searching by roomId even requested with count and offset params', function (done) { + request + .get(api(filesEndpoint)) + .set(credentials) + .query({ + roomId: room._id, + count: 5, + offset: 0, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect(function (res: Response) { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('files').and.to.be.an('array'); + }) + .end(done); + }); + + it('should succeed when searching by roomName', function (done) { + if (!room.name) { + this.skip(); + } + request + .get(api(filesEndpoint)) + .set(credentials) + .query({ + roomName: room.name, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect(function (res: Response) { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('files').and.to.be.an('array'); + }) + .end(done); + }); + + it('should succeed when searching by roomName even requested with count and offset params', function (done) { + if (!room.name) { + this.skip(); + } + request + .get(api(filesEndpoint)) + .set(credentials) + .query({ + roomName: room.name, + count: 5, + offset: 0, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect(function (res: Response) { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('files').and.to.be.an('array'); + }) + .end(done); + }); + + it('should not return thumbnails', async function () { + await request + .post(api(`rooms.upload/${room._id}`)) + .set(credentials) + .attach('file', imgURL) + .expect('Content-Type', 'application/json') + .expect(200) + .expect(function (res: Response) { + expect(res.body).to.have.property('success', true); + }); + + await request + .get(api(filesEndpoint)) + .set(credentials) + .query({ + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect(function (res: Response) { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('files').and.to.be.an('array').with.lengthOf(1); + + const { files } = res.body; + + files.forEach(function (file: unknown) { + expect(file).to.not.have.property('originalFileId'); + }); + }); + }); + + it('should not return hidden files', async function () { + let msgId; + let fileId: string; + + await request + .post(api(`rooms.upload/${room._id}`)) + .set(credentials) + .attach('file', imgURL) + .expect('Content-Type', 'application/json') + .expect(200) + .expect(function (res: Response) { + expect(res.body).to.have.property('success', true); + + msgId = res.body.message._id; + fileId = res.body.message.file._id; + }); + + await request + .post(api('chat.delete')) + .set(credentials) + .send({ + roomId: room._id, + msgId, + }) + .expect('Content-Type', 'application/json') + .expect(200); + + await request + .get(api(filesEndpoint)) + .set(credentials) + .query({ + roomId: room._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect(function (res: Response) { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('files').and.to.be.an('array').with.lengthOf(1); + + const { files } = res.body; + files.forEach(function (file: unknown) { + expect(file).to.have.property('_id').to.not.be.equal(fileId); + }); + }); + }); +} diff --git a/apps/meteor/tests/end-to-end/api/02-channels.js b/apps/meteor/tests/end-to-end/api/02-channels.js index 975050750c6..399f0655cd9 100644 --- a/apps/meteor/tests/end-to-end/api/02-channels.js +++ b/apps/meteor/tests/end-to-end/api/02-channels.js @@ -3,11 +3,10 @@ import { expect } from 'chai'; import { getCredentials, api, request, credentials, apiPublicChannelName, channel, reservedWords } from '../../data/api-data.js'; import { adminUsername, password } from '../../data/user.js'; import { createUser, login } from '../../data/users.helper'; -import { imgURL } from '../../data/interactions.js'; import { updatePermission, updateSetting } from '../../data/permissions.helper'; import { createRoom } from '../../data/rooms.helper'; -import { createVisitor } from '../../data/livechat/rooms'; import { createIntegration, removeIntegration } from '../../data/integration.helper'; +import { testFileUploads } from '../../data/uploads.helper'; function getRoomInfo(roomId) { return new Promise((resolve /* , reject*/) => { @@ -303,150 +302,8 @@ describe('[Channels]', function () { }); }); - describe('[/channels.files]', () => { - before(() => updateSetting('VoIP_Enabled', true)); - const createVoipRoom = async () => { - const testUser = await createUser({ roles: ['user', 'livechat-agent'] }); - const testUserCredentials = await login(testUser.username, password); - const visitor = await createVisitor(); - const roomResponse = await createRoom({ - token: visitor.token, - type: 'v', - agentId: testUser._id, - credentials: testUserCredentials, - }); - return roomResponse.body.room; - }; - it('should fail if invalid channel', (done) => { - request - .get(api('channels.files')) - .set(credentials) - .query({ - roomId: 'invalid', - }) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('errorType', 'error-room-not-found'); - }) - .end(done); - }); - - it('should fail for room type v', async () => { - const { _id } = await createVoipRoom(); - request - .get(api('channels.files')) - .set(credentials) - .query({ - roomId: _id, - }) - .expect('Content-Type', 'application/json') - .expect(400) - .expect((res) => { - expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('errorType', 'error-room-not-found'); - }); - }); - - it('should succeed when searching by roomId', (done) => { - request - .get(api('channels.files')) - .set(credentials) - .query({ - roomId: channel._id, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('files').and.to.be.an('array'); - }) - .end(done); - }); - - it('should succeed when searching by roomId even requested with count and offset params', (done) => { - request - .get(api('channels.files')) - .set(credentials) - .query({ - roomId: channel._id, - count: 5, - offset: 0, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('files').and.to.be.an('array'); - }) - .end(done); - }); - - it('should succeed when searching by roomName', (done) => { - request - .get(api('channels.files')) - .set(credentials) - .query({ - roomName: channel.name, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('files').and.to.be.an('array'); - }) - .end(done); - }); - - it('should succeed when searching by roomName even requested with count and offset params', (done) => { - request - .get(api('channels.files')) - .set(credentials) - .query({ - roomName: channel.name, - count: 5, - offset: 0, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('files').and.to.be.an('array'); - }) - .end(done); - }); - - it('should not return thumbnails', async function () { - await request - .post(api(`rooms.upload/${channel._id}`)) - .set(credentials) - .attach('file', imgURL) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - }); - - await request - .get(api('channels.files')) - .set(credentials) - .query({ - roomId: channel._id, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('files').and.to.be.an('array').with.lengthOf(1); - - const { files } = res.body; - - files.forEach((file) => { - expect(file).to.not.have.property('originalFileId'); - }); - }); - }); + describe('[/channels.files]', async function () { + await testFileUploads('channels.files', channel); }); describe('[/channels.join]', () => { diff --git a/apps/meteor/tests/end-to-end/api/03-groups.js b/apps/meteor/tests/end-to-end/api/03-groups.js index d28e4e31ed5..a10eec964fb 100644 --- a/apps/meteor/tests/end-to-end/api/03-groups.js +++ b/apps/meteor/tests/end-to-end/api/03-groups.js @@ -6,6 +6,7 @@ import { createUser, login } from '../../data/users.helper'; import { updatePermission, updateSetting } from '../../data/permissions.helper'; import { createRoom } from '../../data/rooms.helper'; import { createIntegration, removeIntegration } from '../../data/integration.helper'; +import { testFileUploads } from '../../data/uploads.helper'; function getRoomInfo(roomId) { return new Promise((resolve /* , reject*/) => { @@ -42,6 +43,7 @@ describe('[Groups]', function () { expect(res.body).to.have.nested.property('group.t', 'p'); expect(res.body).to.have.nested.property('group.msgs', 0); group._id = res.body.group._id; + group.name = res.body.group.name; }) .end(done); }); @@ -743,7 +745,7 @@ describe('[Groups]', function () { }); }); }); - describe('/groups.files', () => { + describe('/groups.members', () => { it('should return group members when searching by roomId', (done) => { request .get(api('groups.members')) @@ -784,45 +786,8 @@ describe('[Groups]', function () { }); }); - describe('/groups.files', () => { - it('should return group files when searching by roomId', (done) => { - request - .get(api('groups.files')) - .set(credentials) - .query({ - roomId: group._id, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('count'); - expect(res.body).to.have.property('total'); - expect(res.body).to.have.property('offset'); - expect(res.body).to.have.property('files').and.to.be.an('array'); - }) - .end(done); - }); - it('should return group files when searching by roomId even requested with count and offset params', (done) => { - request - .get(api('groups.files')) - .set(credentials) - .query({ - roomId: group._id, - count: 5, - offset: 0, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('count'); - expect(res.body).to.have.property('total'); - expect(res.body).to.have.property('offset'); - expect(res.body).to.have.property('files').and.to.be.an('array'); - }) - .end(done); - }); + describe('[/groups.files]', async function () { + await testFileUploads('groups.files', group); }); describe('/groups.listAll', () => { diff --git a/apps/meteor/tests/end-to-end/api/04-direct-message.js b/apps/meteor/tests/end-to-end/api/04-direct-message.js index ac0923f6b1f..beb4dfa5356 100644 --- a/apps/meteor/tests/end-to-end/api/04-direct-message.js +++ b/apps/meteor/tests/end-to-end/api/04-direct-message.js @@ -5,13 +5,14 @@ import { password, adminUsername } from '../../data/user.js'; import { deleteRoom } from '../../data/rooms.helper'; import { createUser, deleteUser, login } from '../../data/users.helper'; import { updateSetting, updatePermission } from '../../data/permissions.helper'; +import { testFileUploads } from '../../data/uploads.helper'; describe('[Direct Messages]', function () { this.retries(0); before((done) => getCredentials(done)); - it('/chat.postMessage', (done) => { + before('/chat.postMessage', (done) => { request .post(api('chat.postMessage')) .set(credentials) @@ -330,23 +331,8 @@ describe('[Direct Messages]', function () { .end(done); }); - it('/im.files', (done) => { - request - .get(api('im.files')) - .set(credentials) - .query({ - roomId: directMessage._id, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('files'); - expect(res.body).to.have.property('count'); - expect(res.body).to.have.property('offset'); - expect(res.body).to.have.property('total'); - }) - .end(done); + describe('[/im.files]', async function () { + await testFileUploads('im.files', directMessage, 'invalid-channel'); }); describe('/im.messages.others', () => {