Skip to content

Commit 2e5643e

Browse files
authored
fix: escape special characters in Salesforce SOQL queries (calcom#28700)
1 parent 82ccdc8 commit 2e5643e

1 file changed

Lines changed: 24 additions & 16 deletions

File tree

packages/app-store/salesforce/lib/CrmService.ts

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,14 @@ class SalesforceCRMService implements CRM {
133133
private credentialId: number;
134134
private describeCache = new Map<string, Set<string>>();
135135

136+
/**
137+
* Escapes a string value for safe interpolation into SOQL queries.
138+
* Prevents SOQL injection by escaping backslashes and single quotes.
139+
*/
140+
private sanitizeSoqlValue(value: string): string {
141+
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
142+
}
143+
136144
constructor(credential: CredentialPayload, appOptions: z.infer<typeof appDataSchema>, testMode = false) {
137145
this.integrationName = "salesforce_other_calendar";
138146
this.credentialId = credential.id;
@@ -289,7 +297,7 @@ class SalesforceCRMService implements CRM {
289297
private getSalesforceUserIdFromEmail = async (email: string) => {
290298
const conn = await this.conn;
291299
const query = await conn.query(
292-
`SELECT Id, Email FROM User WHERE Email = '${email}' AND IsActive = true LIMIT 1`
300+
`SELECT Id, Email FROM User WHERE Email = '${this.sanitizeSoqlValue(email)}' AND IsActive = true LIMIT 1`
293301
);
294302
if (query.records.length > 0) {
295303
return (query.records[0] as { Email: string; Id: string }).Id;
@@ -299,7 +307,7 @@ class SalesforceCRMService implements CRM {
299307
private getSalesforceUserFromUserId = async (userId: string) => {
300308
const conn = await this.conn;
301309

302-
return await conn.query(`SELECT Id, Email, Name FROM User WHERE Id = '${userId}' AND IsActive = true`);
310+
return await conn.query(`SELECT Id, Email, Name FROM User WHERE Id = '${this.sanitizeSoqlValue(userId)}' AND IsActive = true`);
303311
};
304312

305313
private getSalesforceEventBody = (event: CalendarEvent): string => {
@@ -661,10 +669,10 @@ class SalesforceCRMService implements CRM {
661669
// For an account let's assume that the first email is the one we should be querying against
662670
const attendeeEmail = emailArray[0];
663671
log.info("[recordToSearch=ACCOUNT] Searching contact for email", safeStringify({ attendeeEmail }));
664-
soql = `SELECT Id, Email, OwnerId, AccountId, Account.OwnerId, Account.Owner.Email, Account.Website${extraFields} FROM ${SalesforceRecordEnum.CONTACT} WHERE Email = '${attendeeEmail}' AND AccountId != null`;
672+
soql = `SELECT Id, Email, OwnerId, AccountId, Account.OwnerId, Account.Owner.Email, Account.Website${extraFields} FROM ${SalesforceRecordEnum.CONTACT} WHERE Email = '${this.sanitizeSoqlValue(attendeeEmail)}' AND AccountId != null`;
665673
} else {
666674
// Handle Contact/Lead record types
667-
soql = `SELECT Id, Email, OwnerId, Owner.Email${extraFields} FROM ${recordToSearch} WHERE Email IN ('${emailArray.join(
675+
soql = `SELECT Id, Email, OwnerId, Owner.Email${extraFields} FROM ${recordToSearch} WHERE Email IN ('${emailArray.map((e) => this.sanitizeSoqlValue(e)).join(
668676
"','"
669677
)}')`;
670678
}
@@ -898,7 +906,7 @@ class SalesforceCRMService implements CRM {
898906
if (!accountId && appOptions.createLeadIfAccountNull && !contactCreated) {
899907
// Check to see if the lead exists already
900908
const leadQuery = await conn.query(
901-
`SELECT Id, Email FROM Lead WHERE Email = '${attendee.email}' LIMIT 1`
909+
`SELECT Id, Email FROM Lead WHERE Email = '${this.sanitizeSoqlValue(attendee.email)}' LIMIT 1`
902910
);
903911
if (leadQuery.records.length > 0) {
904912
const contact = leadQuery.records[0] as { Id: string; Email: string };
@@ -973,17 +981,17 @@ class SalesforceCRMService implements CRM {
973981
}
974982

975983
for (const event of salesforceEvents) {
976-
const salesforceEvent = (await conn.query(`SELECT WhoId FROM Event WHERE Id = '${event.uid}'`)) as {
984+
const salesforceEvent = (await conn.query(`SELECT WhoId FROM Event WHERE Id = '${this.sanitizeSoqlValue(event.uid)}'`)) as {
977985
records: { WhoId: string }[];
978986
};
979987

980988
let salesforceAttendeeEmail: string | undefined = undefined;
981989
// Figure out if the attendee is a contact or lead
982990
const contactQuery = (await conn.query(
983-
`SELECT Email FROM Contact WHERE Id = '${salesforceEvent.records[0].WhoId}'`
991+
`SELECT Email FROM Contact WHERE Id = '${this.sanitizeSoqlValue(salesforceEvent.records[0].WhoId)}'`
984992
)) as { records: { Email: string }[] };
985993
const leadQuery = (await conn.query(
986-
`SELECT Email FROM Lead WHERE Id = '${salesforceEvent.records[0].WhoId}'`
994+
`SELECT Email FROM Lead WHERE Id = '${this.sanitizeSoqlValue(salesforceEvent.records[0].WhoId)}'`
987995
)) as { records: { Email: string }[] };
988996

989997
// Prioritize contacts over leads
@@ -1266,7 +1274,7 @@ class SalesforceCRMService implements CRM {
12661274

12671275
// Get the associated record that the event was created on
12681276
const recordQuery = (await conn.query(
1269-
`SELECT OwnerId, Owner.Name FROM ${recordType} WHERE Id = '${id}'`
1277+
`SELECT OwnerId, Owner.Name FROM ${recordType} WHERE Id = '${this.sanitizeSoqlValue(id)}'`
12701278
)) as { records: { OwnerId: string; Owner: { Name: string } }[] };
12711279

12721280
if (!recordQuery || !recordQuery.records.length) {
@@ -1300,7 +1308,7 @@ class SalesforceCRMService implements CRM {
13001308
public getAllPossibleAccountWebsiteFromEmailDomain(emailDomain: string) {
13011309
const websites = getAllPossibleWebsiteValuesFromEmailDomain(emailDomain);
13021310
// Format for SOQL query
1303-
return websites.map((website) => `'${website}'`).join(", ");
1311+
return websites.map((website) => `'${this.sanitizeSoqlValue(website)}'`).join(", ");
13041312
}
13051313

13061314
private async getAccountIdBasedOnEmailDomainOfContacts(email: string) {
@@ -1334,7 +1342,7 @@ class SalesforceCRMService implements CRM {
13341342

13351343
// Fallback to querying which account the majority of contacts are under
13361344
const response = await conn.query(
1337-
`SELECT Id, Email, AccountId FROM Contact WHERE Email LIKE '%@${emailDomain}' AND AccountId != null`
1345+
`SELECT Id, Email, AccountId FROM Contact WHERE Email LIKE '%@${this.sanitizeSoqlValue(emailDomain)}' AND AccountId != null`
13381346
);
13391347

13401348
SalesforceRoutingTraceService.searchingByContactEmailDomain({
@@ -1836,7 +1844,7 @@ class SalesforceCRMService implements CRM {
18361844
const existingFieldNames = existingFields.map((field) => field.name);
18371845

18381846
const query = await conn.query(
1839-
`SELECT Id, ${existingFieldNames.join(", ")} FROM ${personRecordType} WHERE Id = '${contactId}'`
1847+
`SELECT Id, ${existingFieldNames.join(", ")} FROM ${personRecordType} WHERE Id = '${this.sanitizeSoqlValue(contactId)}'`
18401848
);
18411849

18421850
if (!query.records.length) {
@@ -1863,7 +1871,7 @@ class SalesforceCRMService implements CRM {
18631871

18641872
// First see if the contact already exists and connect it to the account
18651873
const userQuery = await conn.query(
1866-
`SELECT Id, Email FROM Contact WHERE Email = '${attendee.email}' LIMIT 1`
1874+
`SELECT Id, Email FROM Contact WHERE Email = '${this.sanitizeSoqlValue(attendee.email)}' LIMIT 1`
18671875
);
18681876
if (userQuery.records.length > 0) {
18691877
const contact = userQuery.records[0] as { Id: string; Email: string };
@@ -1909,7 +1917,7 @@ class SalesforceCRMService implements CRM {
19091917
if (!accountId) return;
19101918

19111919
const accountQuery = (await conn.query(
1912-
`SELECT ${lookupField.name} FROM ${SalesforceRecordEnum.ACCOUNT} WHERE Id = '${accountId}'`
1920+
`SELECT ${lookupField.name} FROM ${SalesforceRecordEnum.ACCOUNT} WHERE Id = '${this.sanitizeSoqlValue(accountId)}'`
19131921
)) as {
19141922
// We do not know what fields are included in the account since it's unqiue to each instance
19151923
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -2043,7 +2051,7 @@ class SalesforceCRMService implements CRM {
20432051
private async findContactByEmail(email: string) {
20442052
const conn = await this.conn;
20452053
const contactsQuery = await conn.query(
2046-
`SELECT Id, Email FROM ${SalesforceRecordEnum.CONTACT} WHERE Email = '${email}' LIMIT 1`
2054+
`SELECT Id, Email FROM ${SalesforceRecordEnum.CONTACT} WHERE Email = '${this.sanitizeSoqlValue(email)}' LIMIT 1`
20472055
);
20482056

20492057
if (contactsQuery.records.length > 0) {
@@ -2054,7 +2062,7 @@ class SalesforceCRMService implements CRM {
20542062
private async findLeadByEmail(email: string) {
20552063
const conn = await this.conn;
20562064
const leadsQuery = await conn.query(
2057-
`SELECT Id, Email FROM ${SalesforceRecordEnum.LEAD} WHERE Email = '${email}' LIMIT 1`
2065+
`SELECT Id, Email FROM ${SalesforceRecordEnum.LEAD} WHERE Email = '${this.sanitizeSoqlValue(email)}' LIMIT 1`
20582066
);
20592067

20602068
if (leadsQuery.records.length > 0) {

0 commit comments

Comments
 (0)