[FIX] User Impersonation through sendMessage API (#20391)

Co-authored-by: Diego Sampaio <chinello@gmail.com>
pull/21215/head^2
Lucas Sartor Chauvin 4 years ago committed by GitHub
parent 592097593f
commit ee19c6b0fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      app/authorization/server/startup.js
  2. 11
      app/lib/server/functions/sendMessage.js
  3. 2
      packages/rocketchat-i18n/i18n/en.i18n.json
  4. 2
      packages/rocketchat-i18n/i18n/pt-BR.i18n.json
  5. 1
      server/startup/migrations/index.js
  6. 11
      server/startup/migrations/v223.js
  7. 85
      tests/end-to-end/api/05-chat.js

@ -125,6 +125,7 @@ Meteor.startup(function() {
{ _id: 'send-omnichannel-chat-transcript', roles: ['livechat-manager', 'admin'] },
{ _id: 'mail-messages', roles: ['admin'] },
{ _id: 'toggle-room-e2e-encryption', roles: ['owner'] },
{ _id: 'message-impersonate', roles: ['bot'] },
{ _id: 'create-team', roles: ['admin', 'user'] },
{ _id: 'delete-team', roles: ['admin', 'owner'] },
{ _id: 'edit-team', roles: ['admin', 'owner'] },

@ -6,6 +6,7 @@ import { Messages } from '../../../models';
import { Apps } from '../../../apps/server';
import { isURL, isRelativeURL } from '../../../utils/lib/isURL';
import { FileUpload } from '../../../file-upload/server';
import { hasPermission } from '../../../authorization/server';
import { parseUrlsInMessage } from './parseUrlsInMessage';
/**
@ -126,7 +127,7 @@ const validateAttachment = (attachment) => {
const validateBodyAttachments = (attachments) => attachments.map(validateAttachment);
const validateMessage = (message) => {
const validateMessage = (message, userId) => {
check(message, objectMaybeIncluding({
_id: String,
msg: String,
@ -140,6 +141,10 @@ const validateMessage = (message) => {
blocks: [Match.Any],
}));
if ((message.alias || message.avatar) && !hasPermission(userId, 'message-impersonate', message.rid)) {
throw new Error('Not enough permission');
}
if (Array.isArray(message.attachments) && message.attachments.length) {
validateBodyAttachments(message.attachments);
}
@ -150,7 +155,7 @@ export const sendMessage = function(user, message, room, upsert = false) {
return false;
}
validateMessage(message);
validateMessage(message, user._id);
if (!message.ts) {
message.ts = new Date();
@ -199,7 +204,7 @@ export const sendMessage = function(user, message, room, upsert = false) {
message = Object.assign(message, result);
// Some app may have inserted malicious/invalid values in the message, let's check it again
validateMessage(message);
validateMessage(message, user._id);
}
}

@ -2752,6 +2752,8 @@
"Message_HideType_wm": "Hide \"Welcome\" messages",
"Message_Id": "Message Id",
"Message_Ignored": "This message was ignored",
"message-impersonate": "Impersonate Other Users",
"message-impersonate_description": "Permission to impersonate other users using message alias",
"Message_info": "Message info",
"Message_KeepHistory": "Keep Per Message Editing History",
"Message_MaxAll": "Maximum Channel Size for ALL Message",

@ -2297,6 +2297,8 @@
"Message_HideType_ul": "Ocultar mensagens \"Deixar usuário\"",
"Message_Id": "ID da mensagem",
"Message_Ignored": "Esta mensagem foi ignorada",
"message-impersonate": "Impersonar Outros Usuários",
"message-impersonate_description": "Permissão para impersonar outros usuários usando a propriedade alias ou avatar no envio de uma mensagem",
"Message_info": "Informação da mensagem",
"Message_KeepHistory": "Manter Histórico de Mensagens",
"Message_MaxAll": "Tamanho máximo de canais para a todas mensagens",

@ -219,4 +219,5 @@ import './v219';
import './v220';
import './v221';
import './v222';
import './v223';
import './xrun';

@ -0,0 +1,11 @@
import { Migrations } from '../../../app/migrations/server';
import { Permissions } from '../../../app/models/server';
const roleName = 'user';
Migrations.add({
version: 223,
up() {
Permissions.update({ _id: 'message-impersonate' }, { $addToSet: { roles: roleName } });
},
});

@ -423,9 +423,7 @@ describe('[Chat]', function() {
.send({
channel: 'general',
text: 'Sample message',
alias: 'Gruggy',
emoji: ':smirk:',
avatar: 'http://res.guggy.com/logo_128.png',
attachments: [{
color: '#ff0000',
text: 'Yay for gruggy!',
@ -659,9 +657,7 @@ describe('[Chat]', function() {
_id: message._id,
rid: 'GENERAL',
msg: 'Sample message',
alias: 'Gruggy',
emoji: ':smirk:',
avatar: 'http://res.guggy.com/logo_128.png',
attachments: [{
color: '#ff0000',
text: 'Yay for gruggy!',
@ -708,18 +704,14 @@ describe('[Chat]', function() {
_id: `id-${ Date.now() }`,
rid: 'GENERAL',
msg: 'https://www.youtube.com/watch?v=T2v29gK8fP4',
alias: 'Gruggy',
emoji: ':smirk:',
avatar: 'http://res.guggy.com/logo_128.png',
};
const imgUrlMsgPayload = {
_id: `id-${ Date.now() }1`,
rid: 'GENERAL',
msg: 'https://i.picsum.photos/id/671/200/200.jpg?hmac=F8KUqkSzkLxagDZW5rOEHLjzFVxRZWnkrFPvq2BlnhE',
alias: 'Gruggy',
emoji: ':smirk:',
avatar: 'http://res.guggy.com/logo_128.png',
};
const ytPostResponse = await request.post(api('chat.sendMessage'))
@ -758,7 +750,7 @@ describe('[Chat]', function() {
.to.have.string('<iframe style="max-width: 100%"');
})
.end(done);
}, 200);
}, 500);
});
it('should embed an image preview if message has an image url', (done) => {
@ -881,26 +873,65 @@ describe('[Chat]', function() {
.end(done);
});
it('should send a message when the user has permission to send messages on readonly channels', (done) => {
updatePermission('post-readonly', ['user']).then(() => {
request.post(api('chat.sendMessage'))
.set(userCredentials)
.send({
message: {
rid: readOnlyChannel._id,
msg: 'Sample message overwriting readonly status',
},
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('message').and.to.be.an('object');
})
.end(done);
});
it('should send a message when the user has permission to send messages on readonly channels', async () => {
await updatePermission('post-readonly', ['user']);
await request.post(api('chat.sendMessage'))
.set(userCredentials)
.send({
message: {
rid: readOnlyChannel._id,
msg: 'Sample message overwriting readonly status',
},
})
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res) => {
expect(res.body).to.have.property('success', true);
expect(res.body).to.have.property('message').and.to.be.an('object');
});
await updatePermission('post-readonly', ['admin', 'owner', 'moderator']);
});
});
it('should fail if user does not have the message-impersonate permission and tries to send message with alias param', (done) => {
request.post(api('chat.sendMessage'))
.set(credentials)
.send({
message: {
rid: 'GENERAL',
msg: 'Sample message',
alias: 'Gruggy',
},
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'Not enough permission');
})
.end(done);
});
it('should fail if user does not have the message-impersonate permission and tries to send message with avatar param', (done) => {
request.post(api('chat.sendMessage'))
.set(credentials)
.send({
message: {
rid: 'GENERAL',
msg: 'Sample message',
avatar: 'http://site.com/logo.png',
},
})
.expect('Content-Type', 'application/json')
.expect(400)
.expect((res) => {
expect(res.body).to.have.property('success', false);
expect(res.body).to.have.property('error', 'Not enough permission');
})
.end(done);
});
});
describe('/chat.update', () => {

Loading…
Cancel
Save