The communications platform that puts data protection first.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
Rocket.Chat/app/integrations/server/lib/validation.js

169 lines
6.4 KiB

import { Meteor } from 'meteor/meteor';
import { Match } from 'meteor/check';
import { Babel } from 'meteor/babel-compiler';
import _ from 'underscore';
import s from 'underscore.string';
import { Rooms, Users, Subscriptions } from '../../../models';
import { hasPermission, hasAllPermission } from '../../../authorization';
import { integrations } from '../../lib/rocketchat';
const scopedChannels = ['all_public_channels', 'all_private_groups', 'all_direct_messages'];
const validChannelChars = ['@', '#'];
function _verifyRequiredFields(integration) {
if (!integration.event || !Match.test(integration.event, String) || integration.event.trim() === '' || !integrations.outgoingEvents[integration.event]) {
throw new Meteor.Error('error-invalid-event-type', 'Invalid event type', { function: 'validateOutgoing._verifyRequiredFields' });
}
if (!integration.username || !Match.test(integration.username, String) || integration.username.trim() === '') {
throw new Meteor.Error('error-invalid-username', 'Invalid username', { function: 'validateOutgoing._verifyRequiredFields' });
}
if (integrations.outgoingEvents[integration.event].use.targetRoom && !integration.targetRoom) {
throw new Meteor.Error('error-invalid-targetRoom', 'Invalid Target Room', { function: 'validateOutgoing._verifyRequiredFields' });
}
if (!Match.test(integration.urls, [String])) {
throw new Meteor.Error('error-invalid-urls', 'Invalid URLs', { function: 'validateOutgoing._verifyRequiredFields' });
}
for (const [index, url] of integration.urls.entries()) {
if (url.trim() === '') {
delete integration.urls[index];
}
}
integration.urls = _.without(integration.urls, [undefined]);
if (integration.urls.length === 0) {
throw new Meteor.Error('error-invalid-urls', 'Invalid URLs', { function: 'validateOutgoing._verifyRequiredFields' });
}
}
function _verifyUserHasPermissionForChannels(integration, userId, channels) {
for (let channel of channels) {
if (scopedChannels.includes(channel)) {
if (channel === 'all_public_channels') {
// No special permissions needed to add integration to public channels
} else if (!hasPermission(userId, 'manage-outgoing-integrations')) {
throw new Meteor.Error('error-invalid-channel', 'Invalid Channel', { function: 'validateOutgoing._verifyUserHasPermissionForChannels' });
}
} else {
let record;
const channelType = channel[0];
channel = channel.substr(1);
switch (channelType) {
case '#':
record = Rooms.findOne({
$or: [
{ _id: channel },
{ name: channel },
],
});
break;
case '@':
record = Users.findOne({
$or: [
{ _id: channel },
{ username: channel },
],
});
break;
}
if (!record) {
throw new Meteor.Error('error-invalid-room', 'Invalid room', { function: 'validateOutgoing._verifyUserHasPermissionForChannels' });
}
if (!hasAllPermission(userId, ['manage-outgoing-integrations', 'manage-own-outgoing-integrations']) && !Subscriptions.findOneByRoomIdAndUserId(record._id, userId, { fields: { _id: 1 } })) {
throw new Meteor.Error('error-invalid-channel', 'Invalid Channel', { function: 'validateOutgoing._verifyUserHasPermissionForChannels' });
}
}
}
}
function _verifyRetryInformation(integration) {
if (!integration.retryFailedCalls) {
return;
}
// Don't allow negative retry counts
integration.retryCount = integration.retryCount && parseInt(integration.retryCount) > 0 ? parseInt(integration.retryCount) : 4;
integration.retryDelay = !integration.retryDelay || !integration.retryDelay.trim() ? 'powers-of-ten' : integration.retryDelay.toLowerCase();
}
integrations.validateOutgoing = function _validateOutgoing(integration, userId) {
if (integration.channel && Match.test(integration.channel, String) && integration.channel.trim() === '') {
delete integration.channel;
}
// Moved to it's own function to statisfy the complexity rule
_verifyRequiredFields(integration);
let channels = [];
if (integrations.outgoingEvents[integration.event].use.channel) {
if (!Match.test(integration.channel, String)) {
throw new Meteor.Error('error-invalid-channel', 'Invalid Channel', { function: 'validateOutgoing' });
} else {
channels = _.map(integration.channel.split(','), (channel) => s.trim(channel));
for (const channel of channels) {
if (!validChannelChars.includes(channel[0]) && !scopedChannels.includes(channel.toLowerCase())) {
throw new Meteor.Error('error-invalid-channel-start-with-chars', 'Invalid channel. Start with @ or #', { function: 'validateOutgoing' });
}
}
}
} else if (!hasPermission(userId, 'manage-outgoing-integrations')) {
throw new Meteor.Error('error-invalid-permissions', 'Invalid permission for required Integration creation.', { function: 'validateOutgoing' });
}
if (integrations.outgoingEvents[integration.event].use.triggerWords && integration.triggerWords) {
if (!Match.test(integration.triggerWords, [String])) {
throw new Meteor.Error('error-invalid-triggerWords', 'Invalid triggerWords', { function: 'validateOutgoing' });
}
integration.triggerWords.forEach((word, index) => {
if (!word || word.trim() === '') {
delete integration.triggerWords[index];
}
});
integration.triggerWords = _.without(integration.triggerWords, [undefined]);
} else {
delete integration.triggerWords;
}
if (integration.scriptEnabled === true && integration.script && integration.script.trim() !== '') {
try {
const babelOptions = Object.assign(Babel.getDefaultOptions({ runtime: false }), { compact: true, minified: true, comments: false });
integration.scriptCompiled = Babel.compile(integration.script, babelOptions).code;
integration.scriptError = undefined;
} catch (e) {
integration.scriptCompiled = undefined;
integration.scriptError = _.pick(e, 'name', 'message', 'stack');
}
}
if (typeof integration.runOnEdits !== 'undefined') {
// Verify this value is only true/false
integration.runOnEdits = integration.runOnEdits === true;
}
_verifyUserHasPermissionForChannels(integration, userId, channels);
_verifyRetryInformation(integration);
const user = Users.findOne({ username: integration.username });
if (!user) {
throw new Meteor.Error('error-invalid-user', 'Invalid user (did you delete the `rocket.cat` user?)', { function: 'validateOutgoing' });
}
integration.type = 'webhook-outgoing';
integration.userId = user._id;
integration.channel = channels;
return integration;
};