Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FIX] Integrations avatar attribute misuse #25283

Merged
merged 25 commits into from
May 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5cdf3df
Rename files
pierre-lehnen-rc Apr 22, 2022
6ca677f
Started converting integrations to typescript
pierre-lehnen-rc Apr 22, 2022
a77f5ce
Babel compiler module placeholder
pierre-lehnen-rc Apr 22, 2022
80bed47
Data validation
pierre-lehnen-rc Apr 25, 2022
1e7856c
Partial endpoint definition
pierre-lehnen-rc Apr 25, 2022
9dec06f
Merge branch 'develop' into ts/integrations
pierre-lehnen-rc Apr 26, 2022
ebae2d3
eslint
pierre-lehnen-rc Apr 29, 2022
0a011a8
Merge branch 'develop' into ts/integrations
pierre-lehnen-rc Apr 29, 2022
99b601b
Merge branch 'develop' into ts/integrations
pierre-lehnen-rc Apr 29, 2022
09c6f39
imports
pierre-lehnen-rc May 2, 2022
2a93c5d
Merge branch 'develop' into ts/integrations
pierre-lehnen-rc May 3, 2022
08b0581
Fixing Integration model
pierre-lehnen-rc May 3, 2022
1757ff2
integrations.new endpoint
pierre-lehnen-rc May 3, 2022
1473c4f
more endpoints
pierre-lehnen-rc May 3, 2022
0b2bed5
Merge branch 'develop' into ts/integrations
pierre-lehnen-rc May 6, 2022
0832b29
typings
pierre-lehnen-rc May 9, 2022
eed7ec7
rename
pierre-lehnen-rc May 9, 2022
305c336
Converted endpoints to TS
pierre-lehnen-rc May 9, 2022
9a7cb78
Merge branch 'develop' into ts/integrations
pierre-lehnen-rc May 13, 2022
166ddb4
Merge branch 'develop' into ts/integrations
pierre-lehnen-rc May 13, 2022
4aba68a
Migrate avatarUrl to avatar
pierre-lehnen-rc May 13, 2022
e8e7ba7
Adjust avatar attribute and finished typings
pierre-lehnen-rc May 15, 2022
35ed21b
comment
pierre-lehnen-rc May 15, 2022
2df082f
optional script
pierre-lehnen-rc May 17, 2022
a04a1f5
optional script and updated error messages on api tests
pierre-lehnen-rc May 17, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/meteor/app/api/server/lib/integrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const findOneIntegration = async ({
}: {
userId: string;
integrationId: string;
createdBy: IUser;
createdBy?: IUser['_id'];
}): Promise<IIntegration> => {
const integration = await Integrations.findOneByIdAndCreatedByIfExists({
_id: integrationId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { Meteor } from 'meteor/meteor';
import { Match, check } from 'meteor/check';
import { IIntegration } from '@rocket.chat/core-typings';
import {
isIntegrationsCreateProps,
isIntegrationsHistoryProps,
isIntegrationsRemoveProps,
isIntegrationsGetProps,
isIntegrationsUpdateProps,
} from '@rocket.chat/rest-typings';

import { hasAtLeastOnePermission } from '../../../authorization/server';
import { Integrations, IntegrationHistory } from '../../../models/server/raw';
Expand All @@ -12,45 +20,32 @@ import { findOneIntegration } from '../lib/integrations';

API.v1.addRoute(
'integrations.create',
{ authRequired: true },
{ authRequired: true, validateParams: isIntegrationsCreateProps },
{
post() {
check(
this.bodyParams,
Match.ObjectIncluding({
type: String,
name: String,
enabled: Boolean,
username: String,
urls: Match.Maybe([String]),
channel: String,
event: Match.Maybe(String),
triggerWords: Match.Maybe([String]),
alias: Match.Maybe(String),
avatar: Match.Maybe(String),
emoji: Match.Maybe(String),
token: Match.Maybe(String),
scriptEnabled: Boolean,
script: Match.Maybe(String),
targetChannel: Match.Maybe(String),
}),
);

let integration;

switch (this.bodyParams.type) {
case 'webhook-outgoing':
Meteor.runAsUser(this.userId, () => {
integration = Meteor.call('addOutgoingIntegration', this.bodyParams);
});
break;
case 'webhook-incoming':
Meteor.runAsUser(this.userId, () => {
integration = Meteor.call('addIncomingIntegration', this.bodyParams);
});
break;
default:
return API.v1.failure('Invalid integration type.');
const { userId, bodyParams } = this;

const integration = ((): IIntegration | undefined => {
let integration: IIntegration | undefined;

switch (bodyParams.type) {
case 'webhook-outgoing':
Meteor.runAsUser(userId, () => {
integration = Meteor.call('addOutgoingIntegration', bodyParams);
});
break;
case 'webhook-incoming':
Meteor.runAsUser(userId, () => {
integration = Meteor.call('addIncomingIntegration', bodyParams);
});
break;
}

return integration;
})();

if (!integration) {
return API.v1.failure('Invalid integration type.');
}

return API.v1.success({ integration });
Expand All @@ -60,21 +55,23 @@ API.v1.addRoute(

API.v1.addRoute(
'integrations.history',
{ authRequired: true },
{ authRequired: true, validateParams: isIntegrationsHistoryProps },
{
get() {
if (!hasAtLeastOnePermission(this.userId, ['manage-outgoing-integrations', 'manage-own-outgoing-integrations'])) {
const { userId, queryParams } = this;

if (!hasAtLeastOnePermission(userId, ['manage-outgoing-integrations', 'manage-own-outgoing-integrations'])) {
return API.v1.unauthorized();
}

if (!this.queryParams.id || this.queryParams.id.trim() === '') {
if (!queryParams.id || queryParams.id.trim() === '') {
return API.v1.failure('Invalid integration id.');
}

const { id } = this.queryParams;
const { id } = queryParams;
const { offset, count } = this.getPaginationItems();
const { sort, fields: projection, query } = this.parseJsonQuery();
const ourQuery = Object.assign(mountIntegrationHistoryQueryBasedOnPermissions(this.userId, id), query);
const ourQuery = Object.assign(mountIntegrationHistoryQueryBasedOnPermissions(userId, id), query);

const cursor = IntegrationHistory.find(ourQuery, {
sort: sort || { _updatedAt: -1 },
Expand All @@ -90,6 +87,7 @@ API.v1.addRoute(
history,
offset,
items: history.length,
count: history.length,
total,
});
},
Expand Down Expand Up @@ -131,6 +129,7 @@ API.v1.addRoute(
integrations,
offset,
items: integrations.length,
count: integrations.length,
total,
});
},
Expand All @@ -139,7 +138,7 @@ API.v1.addRoute(

API.v1.addRoute(
'integrations.remove',
{ authRequired: true },
{ authRequired: true, validateParams: isIntegrationsRemoveProps },
{
post() {
if (
Expand All @@ -153,48 +152,51 @@ API.v1.addRoute(
return API.v1.unauthorized();
}

check(
this.bodyParams,
Match.ObjectIncluding({
type: String,
target_url: Match.Maybe(String),
integrationId: Match.Maybe(String),
}),
);

if (!this.bodyParams.target_url && !this.bodyParams.integrationId) {
return API.v1.failure('An integrationId or target_url needs to be provided.');
}
const { bodyParams } = this;

let integration;
switch (this.bodyParams.type) {
let integration: IIntegration | null = null;
switch (bodyParams.type) {
case 'webhook-outgoing':
if (this.bodyParams.target_url) {
integration = Promise.await(Integrations.findOne({ urls: this.bodyParams.target_url }));
} else if (this.bodyParams.integrationId) {
integration = Promise.await(Integrations.findOne({ _id: this.bodyParams.integrationId }));
if (!bodyParams.target_url && !bodyParams.integrationId) {
return API.v1.failure('An integrationId or target_url needs to be provided.');
}

if (bodyParams.target_url) {
integration = Promise.await(Integrations.findOne({ urls: bodyParams.target_url }));
} else if (bodyParams.integrationId) {
integration = Promise.await(Integrations.findOne({ _id: bodyParams.integrationId }));
}

if (!integration) {
return API.v1.failure('No integration found.');
}

const outgoingId = integration._id;

Meteor.runAsUser(this.userId, () => {
Meteor.call('deleteOutgoingIntegration', integration._id);
Meteor.call('deleteOutgoingIntegration', outgoingId);
});

return API.v1.success({
integration,
});
case 'webhook-incoming':
integration = Promise.await(Integrations.findOne({ _id: this.bodyParams.integrationId }));
check(
bodyParams,
Match.ObjectIncluding({
integrationId: String,
}),
);

integration = Promise.await(Integrations.findOne({ _id: bodyParams.integrationId }));

if (!integration) {
return API.v1.failure('No integration found.');
}

const incomingId = integration._id;
Meteor.runAsUser(this.userId, () => {
Meteor.call('deleteIncomingIntegration', integration._id);
Meteor.call('deleteIncomingIntegration', incomingId);
});

return API.v1.success({
Expand All @@ -209,7 +211,7 @@ API.v1.addRoute(

API.v1.addRoute(
'integrations.get',
{ authRequired: true },
{ authRequired: true, validateParams: isIntegrationsGetProps },
{
get() {
const { integrationId, createdBy } = this.queryParams;
Expand All @@ -232,58 +234,37 @@ API.v1.addRoute(

API.v1.addRoute(
'integrations.update',
{ authRequired: true },
{ authRequired: true, validateParams: isIntegrationsUpdateProps },
{
put() {
check(
this.bodyParams,
Match.ObjectIncluding({
type: String,
name: String,
enabled: Boolean,
username: String,
urls: Match.Maybe([String]),
channel: String,
event: Match.Maybe(String),
triggerWords: Match.Maybe([String]),
alias: Match.Maybe(String),
avatar: Match.Maybe(String),
emoji: Match.Maybe(String),
token: Match.Maybe(String),
scriptEnabled: Boolean,
script: Match.Maybe(String),
targetChannel: Match.Maybe(String),
integrationId: Match.Maybe(String),
target_url: Match.Maybe(String),
}),
);
const { bodyParams } = this;

let integration;
switch (this.bodyParams.type) {
switch (bodyParams.type) {
case 'webhook-outgoing':
if (this.bodyParams.target_url) {
integration = Promise.await(Integrations.findOne({ urls: this.bodyParams.target_url }));
} else if (this.bodyParams.integrationId) {
integration = Promise.await(Integrations.findOne({ _id: this.bodyParams.integrationId }));
if (bodyParams.target_url) {
integration = Promise.await(Integrations.findOne({ urls: bodyParams.target_url }));
} else if (bodyParams.integrationId) {
integration = Promise.await(Integrations.findOne({ _id: bodyParams.integrationId }));
}

if (!integration) {
return API.v1.failure('No integration found.');
}

Meteor.call('updateOutgoingIntegration', integration._id, this.bodyParams);
Meteor.call('updateOutgoingIntegration', integration._id, bodyParams);

return API.v1.success({
integration: Promise.await(Integrations.findOne({ _id: integration._id })),
});
case 'webhook-incoming':
integration = Promise.await(Integrations.findOne({ _id: this.bodyParams.integrationId }));
integration = Promise.await(Integrations.findOne({ _id: bodyParams.integrationId }));

if (!integration) {
return API.v1.failure('No integration found.');
}

Meteor.call('updateIncomingIntegration', integration._id, this.bodyParams);
Meteor.call('updateIncomingIntegration', integration._id, bodyParams);

return API.v1.success({
integration: Promise.await(Integrations.findOne({ _id: integration._id })),
Expand Down
70 changes: 70 additions & 0 deletions apps/meteor/app/integrations/lib/outgoingEvents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import type { OutgoingIntegrationEvent } from '@rocket.chat/core-typings';

export const outgoingEvents: Record<
OutgoingIntegrationEvent,
{ label: string; value: OutgoingIntegrationEvent; use: { channel: boolean; triggerWords: boolean; targetRoom: boolean } }
> = {
sendMessage: {
label: 'Integrations_Outgoing_Type_SendMessage',
value: 'sendMessage',
use: {
channel: true,
triggerWords: true,
targetRoom: false,
},
},
fileUploaded: {
label: 'Integrations_Outgoing_Type_FileUploaded',
value: 'fileUploaded',
use: {
channel: true,
triggerWords: false,
targetRoom: false,
},
},
roomArchived: {
label: 'Integrations_Outgoing_Type_RoomArchived',
value: 'roomArchived',
use: {
channel: false,
triggerWords: false,
targetRoom: false,
},
},
roomCreated: {
label: 'Integrations_Outgoing_Type_RoomCreated',
value: 'roomCreated',
use: {
channel: false,
triggerWords: false,
targetRoom: false,
},
},
roomJoined: {
label: 'Integrations_Outgoing_Type_RoomJoined',
value: 'roomJoined',
use: {
channel: true,
triggerWords: false,
targetRoom: false,
},
},
roomLeft: {
label: 'Integrations_Outgoing_Type_RoomLeft',
value: 'roomLeft',
use: {
channel: true,
triggerWords: false,
targetRoom: false,
},
},
userCreated: {
label: 'Integrations_Outgoing_Type_UserCreated',
value: 'userCreated',
use: {
channel: false,
triggerWords: false,
targetRoom: true,
},
},
} as const;
Loading