Details
| ID | SalesforceV2 |
|---|---|
| Provider | Salesforce |
| Category | Case Management |
| From Version | 6.2.0 |
| Docker Image | demisto/python3:3.12.8.3296088 |
| Supported Modules | Agentix XSIAM |
README
CRM Services
Configure Salesforce V2 in Cortex
| Parameter | Description | Required |
|---|---|---|
| Instance URL | True | |
| Credentials | True | |
| Password | True | |
| Consumer Key | True | |
| Consumer Secret | True | |
| Trust any certificate (not secure) | False | |
| Use system proxy settings | False | |
| Fetch type: cases/comments | Only fetch comments when using the SalesforceAskUser automation. | False |
| Define a query to determine which objects to fetch. | E.g.: OwnerId=’0056s000000wGoWAAX’ | False |
| Fields to Fetch (only for cases/comments) | Additional fields to fetch | False |
| Fetch incidents | False | |
| Incident type | False | |
| First Fetch Time | The First Fetch Time, e.g., 1 hour, 3 days | False |
| Incident Mirroring Direction | Choose the direction to mirror the incident: Incoming (from Salesforce to Cortex XSOAR), Outgoing (from Cortex XSOAR to ServiceNow), or Incoming and Outgoing (from/to Cortex XSOAR and Salesforce). | False |
| Comment Entry Tag | Choose the tag to add to an entry to mirror it as a comment in Salesforce. | False |
| Close Mirrored XSOAR Incident | When selected, closing the Salesforce ticket is mirrored in Cortex XSOAR. | False |
| Close Mirrored Salesforce case | When selected, closing the Cortex XSOAR incident is mirrored in Salesforce. | False |
| Incidents Fetch Interval | False |
Commands
You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
salesforce-search
Search records that contain values with the defined pattern.
Base Command
salesforce-search
Input
| Argument Name | Description | Required |
|---|---|---|
| pattern | The string or number to search. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SalesForceV2.Case.ID | string | The ID of the case object. |
| SalesForceV2.Case.CaseNumber | string | The case number. |
| SalesForceV2.Case.Subject | string | The subject of the case. |
| SalesForceV2.Case.Description | string | The description of the case. |
| SalesForceV2.Case.CreateDate | date | The creation date the case. |
| SalesForceV2.Case.ClosedDate | date | The closure date of the case. |
| SalesForceV2.Case.Owner | string | The owner of the case. |
| SalesForceV2.Case.Priority | string | The priority of the case. Can be: “Low”, “Medium”, or “High”. |
| SalesForceV2.Case.Origin | string | The origin of the case. Can be: “Web”, “Phone”, or “Email”. |
| SalesForceV2.Case.Status | string | The status of the case. Can be: “New”, “Escalated”, “On Hold”, or “Closed”. |
| SalesForceV2.Case.Reason | string | The reason for the case creation. |
| SalesForceV2.Contact.ID | string | ID of the contact. |
| SalesForceV2.Contact.Name | string | The name of the contact. |
| SalesForceV2.Contact.Account | string | The account associated with the Contact information. |
| SalesForceV2.Contact.Title | string | The title of the contact. |
| SalesForceV2.Contact.Phone | string | The phone number of the contact. |
| SalesForceV2.Contact.MobilePhone | string | The mobile number of the contact. |
| SalesForceV2.Contact.Email | string | The email address of the contact. |
| SalesForceV2.Contact.Owner | string | The owner of the contact. |
| SalesForceV2.Lead.ID | string | The lead ID. |
| SalesForceV2.Lead.Name | string | The lead name. |
| SalesForceV2.Lead.Title | string | The title of the lead. |
| SalesForceV2.Lead.Company | string | The lead company. |
| SalesForceV2.Lead.Phone | string | The lead phone number. |
| SalesForceV2.Lead.Mobile | string | The lead mobile number. |
| SalesForceV2.Lead.Email | string | The lead email address. |
| SalesForceV2.Lead.Owner | string | The lead owner. |
| SalesForceV2.Lead.Status | string | The lead status. Can be: “New”, “Nurturing”, “Working”, “Qualified”, or “Unqualified”. |
| SalesForceV2.Task.ID | string | The ID of the task. |
| SalesForceV2.Task.Subject | string | The subject of the task. |
| SalesForceV2.Task.Lead | string | The leader of the task. |
| SalesForceV2.Task.RelatedTo | string | The relevant account. |
| SalesForceV2.Task.DueDate | date | The due date of the task. |
| SalesForceV2.User.ID | string | The ID of the user. |
| SalesForceV2.User.Name | string | The name of the user. |
| SalesForceV2.User.Title | string | The title of the user. |
| SalesForceV2.User.Phone | string | The phone number of the user. |
| SalesForceV2.User.Email | string | The email address of the user. |
| SalesForceV2.Case.IsEscalated | boolean | Whether the case is escalated. |
| SalesForceV2.Case.SuppliedPhone | string | Case supplied phone number. |
| SalesForceV2.Case.SuppliedCompany | string | Case supplied company. |
| SalesForceV2.Case.ContactEmail | string | Case contact email address. |
| SalesForceV2.Case.ContactId | string | Case contact ID. |
| SalesForceV2.Case.AccountId | string | Case account ID. |
salesforce-query
Queries Salesforce in SOQL format.
Base Command
salesforce-query
Input
| Argument Name | Description | Required |
|---|---|---|
| query | Query in SOQL format: “SELECT name from Account”. | Required |
Context Output
There is no context output for this command.
salesforce-get-object
Returns an object by its path.
Base Command
salesforce-get-object
Input
| Argument Name | Description | Required |
|---|---|---|
| path | The object path. For example, “Case/5000Y000001EjzRQAS” for Object “Case” with ID “5000Y000001EjzRQAS”. | Optional |
| oid | Object ID (in case no path is given). For example, 5000Y000001EjzRQAS. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SalesForceV2.Case.ID | string | The object ID of the case. |
| SalesForceV2.Case.CaseNumber | string | The case number. |
| SalesForceV2.Case.Subject | string | The subject of the case. |
| SalesForceV2.Case.Description | string | The description of the case. |
| SalesForceV2.Case.CreateDate | date | The creation date of the case. |
| SalesForceV2.Case.ClosedDate | date | The closure date of the case. |
| SalesForceV2.Case.Owner | string | The owner of the case. |
| SalesForceV2.Case.Priority | string | The priority of the case. Can be: “Low”, “Medium”, or “High”. |
| SalesForceV2.Case.Origin | string | Origin of the case. Can be: “Web”, “Phone”, or “Email”. |
| SalesForceV2.Case.Status | string | The status of the case. Can be: “New”, “Escalated”, “On Hold”, or “Closed”. |
| SalesForceV2.Case.Reason | string | The reason for the case creation. |
| SalesForceV2.Contact.ID | string | The ID of the contact. |
| SalesForceV2.Contact.Name | string | The name of the contact. |
| SalesForceV2.Contact.Account | string | The account associated with the contact information. |
| SalesForceV2.Contact.Title | string | The title of the contact. |
| SalesForceV2.Contact.Phone | string | The phone number of the contact. |
| SalesForceV2.Contact.MobilePhone | string | The mobile number of the contact. |
| SalesForceV2.Contact.Email | string | The email address of the contact. |
| SalesForceV2.Contact.Owner | string | The owner of the contact. |
| SalesForceV2.Lead.ID | string | The lead ID. |
| SalesForceV2.Lead.Name | string | The lead name. |
| SalesForceV2.Lead.Title | string | The title of the lead. |
| SalesForceV2.Lead.Company | string | The lead company. |
| SalesForceV2.Lead.Phone | string | The lead phone number. |
| SalesForceV2.Lead.Mobile | string | The lead mobile number. |
| SalesForceV2.Lead.Email | string | The lead email address. |
| SalesForceV2.Lead.Owner | string | The lead owner. |
| SalesForceV2.Lead.Status | string | The lead status. Can be: “New”, “Nurturing”, “Working”, “Qualified”, or “Unqualified”. |
| SalesForceV2.Task.ID | string | The ID of the task. |
| SalesForceV2.Task.Subject | string | The subject of the task. |
| SalesForceV2.Task.Lead | string | The leader of the task. |
| SalesForceV2.Task.RelatedTo | string | The relevant account of the task. |
| SalesForceV2.Task.DueDate | date | The due date of the task. |
| SalesForceV2.User.ID | string | The ID of the user. |
| SalesForceV2.User.Name | string | The name of the user. |
| SalesForceV2.User.Title | string | The title of the user. |
| SalesForceV2.User.Phone | string | The phone number of the user. |
| SalesForceV2.User.Email | string | The email address of the user. |
salesforce-update-object
Updates object fields.
Base Command
salesforce-update-object
Input
| Argument Name | Description | Required |
|---|---|---|
| path | The object path. For example, “Case/5000Y000001EjzRQAS” for Object “Case” with ID “5000Y000001EjzRQAS”. | Required |
| json | The JSON file with fields and values of the object to be updated. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SalesForceV2.Case.ID | string | The object ID of the case. |
| SalesForceV2.Case.CaseNumber | string | The case number. |
| SalesForceV2.Case.Subject | string | The subject of the case. |
| SalesForceV2.Case.Description | string | The description of the case. |
| SalesForceV2.Case.CreateDate | date | The creation date of the case. |
| SalesForceV2.Case.ClosedDate | date | The closure time of the case. |
| SalesForceV2.Case.Owner | string | The owner of the case. |
| SalesForceV2.Case.Priority | string | The priority of the case. Can be: “Low”, “Medium”, or “High”. |
| SalesForceV2.Case.Origin | string | The origin of the case. Can be: “Web”, “Phone”, or “Email”. |
| SalesForceV2.Case.Status | string | The status of the case. Can be: “New”, “Escalated”, “On Hold”, or “Closed”. |
| SalesForceV2.Case.Reason | string | The reason for the case creation. |
| SalesForceV2.Contact.ID | string | The ID of the contact. |
| SalesForceV2.Contact.Name | string | The name of the contact. |
| SalesForceV2.Contact.Account | string | The account associated with the contact information. |
| SalesForceV2.Contact.Title | string | The title of the contact. |
| SalesForceV2.Contact.Phone | string | The phone number of the contact. |
| SalesForceV2.Contact.MobilePhone | string | The mobile number of the contact. |
| SalesForceV2.Contact.Email | string | The email address of the contact. |
| SalesForceV2.Contact.Owner | string | The owner of the contact. |
| SalesForceV2.Lead.ID | string | The lead ID. |
| SalesForceV2.Lead.Name | string | The lead name. |
| SalesForceV2.Lead.Title | string | The title of the lead. |
| SalesForceV2.Lead.Company | string | The lead company. |
| SalesForceV2.Lead.Phone | string | The lead phone number. |
| SalesForceV2.Lead.Mobile | string | The lead mobile number. |
| SalesForceV2.Lead.Email | string | The lead email address. |
| SalesForceV2.Lead.Owner | string | The lead owner. |
| SalesForceV2.Lead.Status | string | The lead status. Can be: “New”, “Nurturing”, “Working”, “Qualified”, or “Unqualified”. |
| SalesForceV2.Task.ID | string | The ID of the task. |
| SalesForceV2.Task.Subject | string | The subject of the task. |
| SalesForceV2.Task.Lead | string | The leader of the task. |
| SalesForceV2.Task.RelatedTo | string | The relevant account. |
| SalesForceV2.Task.DueDate | date | The due date of the task. |
| SalesForceV2.User.ID | string | The ID of the user. |
| SalesForceV2.User.Name | string | The name of the user. |
| SalesForceV2.User.Title | string | The title of the user. |
| SalesForceV2.User.Phone | string | The phone number of the user. |
| SalesForceV2.User.Email | string | The email address of the user. |
salesforce-create-object
Creates a new object.
Base Command
salesforce-create-object
Input
| Argument Name | Description | Required |
|---|---|---|
| path | The object path. For example, “Case” for Object “Case”. | Required |
| json | The JSON file with fields and values of the object to be created. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SalesForceV2.Case.ID | string | The object ID of the case. |
| SalesForceV2.Case.CaseNumber | string | The case number. |
| SalesForceV2.Case.Subject | string | The subject of the case. |
| SalesForceV2.Case.Description | string | The description of the case. |
| SalesForceV2.Case.CreateDate | date | The creation date of the case. |
| SalesForceV2.Case.ClosedDate | date | The closure date of the case. |
| SalesForceV2.Case.Owner | string | The owner of the case. |
| SalesForceV2.Case.Priority | string | The priority of the case. Can be: “Low”, “Medium”, “High”. |
| SalesForceV2.Case.Origin | string | Origin of the case. Can be: “Web”, “Phone”, or “Email”. |
| SalesForceV2.Case.Status | string | The status of the case. Can be: “New”, “Escalated”, “On Hold”, or “Closed”. |
| SalesForceV2.Case.Reason | string | The reason for the case creation. |
| SalesForceV2.Contact.ID | string | The ID of the contact. |
| SalesForceV2.Contact.Name | string | The name of the contact. |
| SalesForceV2.Contact.Account | string | The account associated with the contact information. |
| SalesForceV2.Contact.Title | string | The title of the contact. |
| SalesForceV2.Contact.Phone | string | The phone number of the contact. |
| SalesForceV2.Contact.MobilePhone | string | The mobile number of the contact. |
| SalesForceV2.Contact.Email | string | The email address of the contact. |
| SalesForceV2.Contact.Owner | string | The owner of the contact. |
| SalesForceV2.Lead.ID | string | The lead ID. |
| SalesForceV2.Lead.Name | string | The lead name. |
| SalesForceV2.Lead.Title | string | The title of the lead. |
| SalesForceV2.Lead.Company | string | The lead company. |
| SalesForceV2.Lead.Phone | string | The lead phone number. |
| SalesForceV2.Lead.Mobile | string | The lead mobile number. |
| SalesForceV2.Lead.Email | string | The lead email address. |
| SalesForceV2.Lead.Owner | string | The lead owner. |
| SalesForceV2.Lead.Status | string | The lead status. Can be: “New”, “Nurturing”, “Working”, “Qualified”, or “Unqualified”. |
| SalesForceV2.Task.ID | string | The ID of the task. |
| SalesForceV2.Task.Subject | string | The subject of the task. |
| SalesForceV2.Task.Lead | string | The leader of the task. |
| SalesForceV2.Task.RelatedTo | string | The relevant account of the task. |
| SalesForceV2.Task.DueDate | date | The due date of the task. |
| SalesForceV2.User.ID | string | The ID of the user. |
| SalesForceV2.User.Name | string | The name of the user. |
| SalesForceV2.User.Title | string | The title of the user. |
| SalesForceV2.User.Phone | string | The phone number of the user. |
| SalesForceV2.User.Email | string | The email address of the user. |
salesforce-push-comment
Adds a comment to Chatter.
Base Command
salesforce-push-comment
Input
| Argument Name | Description | Required |
|---|---|---|
| oid | The object ID of the subject. | Required |
| text | Chat text. | Required |
| link | Adds a link to the message. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SalesForceV2.Comment.Body | string | The body of the comment. |
| SalesForceV2.Comment.CreatedDate | date | The date the comment was created. |
| SalesForceV2.Comment.Title | string | The title of the comment. |
| SalesForceV2.Comment.ParentType | string | The parent type of the comment. |
| SalesForceV2.Comment.ParentName | string | The parent name of the comment. |
| SalesForceV2.Comment.URL | string | The URL link of the comment. |
| SalesForceV2.Comment.Visibility | string | The visibility of the comment. |
salesforce-get-case
Returns information on a case. All arguments are optional, but you must specify at least one argument for the command to execute successfully.
Base Command
salesforce-get-case
Input
| Argument Name | Description | Required |
|---|---|---|
| oid | The object ID of the case. | Optional |
| caseNumber | Case number of the case. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SalesForceV2.Case.ID | string | The object ID of the case. |
| SalesForceV2.Case.CaseNumber | string | The case number. |
| SalesForceV2.Case.Subject | string | The subject of the case. |
| SalesForceV2.Case.Description | string | The description of the case. |
| SalesForceV2.Case.CreateDate | date | The creation date of the case. |
| SalesForceV2.Case.ClosedDate | date | The closure date of the case. |
| SalesForceV2.Case.Owner | string | The owner of the case. |
| SalesForceV2.Case.Priority | string | The priority of the case. Can be: “Low”, “Medium”, or “High”. |
| SalesForceV2.Case.Origin | string | The origin of the case. Can be: “Web”, “Phone”, or “Email”. |
| SalesForceV2.Case.Status | string | The status of the case. Can be: “New”, “Escalated”, “On Hold”, or “Closed”. |
| SalesForceV2.Case.Reason | string | The reason for the case creation. |
salesforce-create-case
Creates a new case.
Base Command
salesforce-create-case
Input
| Argument Name | Description | Required |
|---|---|---|
| subject | The case subject. | Required |
| description | The case description. | Optional |
| status | The case status. Possible values are: New, On Hold, Closed, Escalated. Default is New. | Required |
| origin | The case origin. Possible values are: Email, Phone, Web. | Optional |
| priority | The case priority. Possible values are: Low, Medium, High. Default is Low. | Optional |
| type | The case type. Possible values are: Question, Problem, Feature Request. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SalesForceV2.Case.ID | string | The object ID of the case. |
| SalesForceV2.Case.CaseNumber | string | The case number. |
| SalesForceV2.Case.Subject | string | The subject of the case. |
| SalesForceV2.Case.Description | string | The description of the case. |
| SalesForceV2.Case.CreateDate | date | The creation date of the case. |
| SalesForceV2.Case.ClosedDate | date | The closure date of the case. |
| SalesForceV2.Case.Owner | string | The owner of the case. |
| SalesForceV2.Case.Priority | string | The priority of the case. Can be: “Low”, “Medium”, or “High”. |
| SalesForceV2.Case.Origin | string | The origin of the case. Can be: “Web”, “Phone”, or “Email”. |
| SalesForceV2.Case.Status | string | The status of the case. Can be: “New”, “Escalated”, “On Hold”, or “Closed”. |
| SalesForceV2.Case.Reason | string | The reason for the case creation. |
salesforce-update-case
Updates case fields.
Base Command
salesforce-update-case
Input
| Argument Name | Description | Required |
|---|---|---|
| oid | The object ID of the case. | Optional |
| caseNumber | The case number. | Optional |
| subject | The case subject. | Optional |
| description | The case description. | Optional |
| status | The case status. Possible values are: New, On Hold, Closed, Escalated. | Optional |
| origin | The case origin. Possible values are: Email, Phone, Web. | Optional |
| priority | The case priority. Possible values are: Low, Medium, High. | Optional |
| type | The case type. Possible values are: Question, Problem, Feature Request. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SalesForceV2.Case.ID | string | The object ID of the case. |
| SalesForceV2.Case.CaseNumber | string | The number of the case. |
| SalesForceV2.Case.Subject | string | The subject of the case. |
| SalesForceV2.Case.Description | string | The description of the case. |
| SalesForceV2.Case.CreateDate | date | The creation date of the case. |
| SalesForceV2.Case.ClosedDate | date | The closure date of the case. |
| SalesForceV2.Case.Owner | string | The owner of the case. |
| SalesForceV2.Case.Priority | string | The priority of the case. Can be: “Low”, “Medium”, or “High”. |
| SalesForceV2.Case.Origin | string | Origin of the case. Can be: “Web”, “Phone”, or “Email”. |
| SalesForceV2.Case.Status | string | The status of the case. Can be: “New, “Escalated”, “On Hold”, or “Closed”. |
| SalesForceV2.Case.Reason | string | Reason for the case creation. |
salesforce-get-cases
Returns all cases.
Base Command
salesforce-get-cases
Input
| Argument Name | Description | Required |
| — | — | — |
Context Output
There is no context output for this command.
salesforce-close-case
Close a case
Base Command
salesforce-close-case
Input
| Argument Name | Description | Required |
|---|---|---|
| oid | The case object ID. | Optional |
| caseNumber | The case number. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SalesForceV2.Case.ID | string | The object ID of the case. |
| SalesForceV2.Case.CaseNumber | string | The case number. |
| SalesForceV2.Case.Subject | string | The subject of the case. |
| SalesForceV2.Case.Description | string | Case description. |
| SalesForceV2.Case.CreateDate | date | Creation time of the case. |
| SalesForceV2.Case.ClosedDate | date | Closure time of the case. |
| SalesForceV2.Case.Owner | string | Case owner. |
| SalesForceV2.Case.Priority | string | Priority of the case. Can be one of the following: “Low”, “Medium”, “High”. |
| SalesForceV2.Case.Origin | string | Origin of the case. Can be one of the following: “Web”, “Phone”, “Email”. |
| SalesForceV2.Case.Status | string | Case status. Can be one of the following: “New”, “Escalated”,” On Hold” or “Closed”. |
| SalesForceV2.Case.Reason | string | Reason the case was created. |
salesforce-push-comment-threads
Add the comment to the Chatter thread. Use this command only after salesforce-push-comment.
Base Command
salesforce-push-comment-threads
Input
| Argument Name | Description | Required |
|---|---|---|
| id | The Chatter comment thread ID. | Required |
| text | The comment text. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SalesForceV2.Comment.Reply.Body | string | Reply body. |
| SalesForceV2.Comment.Reply.CreatedDate | date | Reply created date. |
| SalesForceV2.Comment.Reply.URL | string | Reply URL link. |
salesforce-delete-case
Deletes a case.
Base Command
salesforce-delete-case
Input
| Argument Name | Description | Required |
|---|---|---|
| oid | The object ID of the case. | Optional |
| caseNumber | The case number. | Optional |
Context Output
There is no context output for this command.
salesforce-get-casecomment
Returns a comment through the case number.
Base Command
salesforce-get-casecomment
Input
| Argument Name | Description | Required |
|---|---|---|
| oid | The object ID of the case. | Optional |
| caseNumber | The case number of the case. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ID | string | The ID of the case. |
| ParentId | string | The ID of the parent case of the case comment. |
| IsPublished | boolean | Whether the case comment is visible to customers in the Self-Service portal (true). This is the only CaseComment field that can be updated through the API. |
| CommentBody | string | The text of the case body. Maximum size is 4,000 bytes. |
| CreatedById | unknown | The created date by ID. |
| CreatedDate | string | The created date. |
| SystemModstamp | string | The SystemMod stamp. |
| LastModifiedDate | string | The last modified date. |
| LastModifiedById | string | The last modified date by ID. |
| IsDeleted | boolean | Whether the object has been moved to the Recycle Bin (true). |
salesforce-post-casecomment
The post comment through the case number.
Base Command
salesforce-post-casecomment
Input
| Argument Name | Description | Required |
|---|---|---|
| oid | The object ID of the case. | Optional |
| caseNumber | The case number of the case. | Optional |
| text | The text to add to the context. | Optional |
| public | Whether to make the comment public (true or false). Default value is false. Possible values are: true, false. Default is false. | Required |
Context Output
There is no context output for this command.
salesforce-get-user
Returns the user name through the case number.
Base Command
salesforce-get-user
Input
| Argument Name | Description | Required |
|---|---|---|
| oid | The object ID of the case. | Optional |
| caseNumber | The case number of the case. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ID | string | The ID of the case. |
| Alias | string | The user’s alias. For example, jsmith. |
| CommunityNickname | string | The name used to identify the user in the Community application, which includes the ideas and answers features. |
| CreatedById | string | Created by the ID. |
| string | The user’s email address. Required. | |
| LastLoginDate | string | The time and date when the user last successfully logged in. This value is updated if 60 seconds have elapsed since the user’s last login. |
| LastModifiedDate | string | The last modified date. |
| LastName | string | The user’s last name. |
| Name | string | Concatenation of FirstName and LastName. |
| Username | string | Contains the name that a user enters to log in to the API or the user interface. |
| UserRoleId | string | The ID of the user’s UserRole. |
salesforce-get-org
Returns organization details from the case number.
Base Command
salesforce-get-org
Input
| Argument Name | Description | Required |
|---|---|---|
| caseNumber | The case number of the case. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ID | string | The unique ID of the case. |
| Name | string | Name of the account. If the account has a record type of Person Account, this value is the concatenation of the FirstName, MiddleName, LastName, and Suffix of the associated person contact. |
get-remote-data
Gets remote data from a remote incident. This method is only used for debugging purposes and will not update the current incident.
Base Command
get-remote-data
Input
| Argument Name | Description | Required |
|---|---|---|
| id | The remote incident ID. | Required |
| lastUpdate | UTC timestamp in seconds. The incident is only updated if it was modified after the last update time. Default is 0. | Optional |
Context Output
There is no context output for this command.
get-modified-remote-data
Available from Cortex XSOAR version 6.1.0. This command queries for incidents that were modified since the last update. This method is only used for debugging purposes.
Base Command
get-modified-remote-data
Input
| Argument Name | Description | Required |
|---|---|---|
| lastUpdate | . | Required |
Context Output
There is no context output for this command.
update-remote-system
Available from Cortex XSOAR version 6.1.0. This command pushes local changes to the remote system.
Base Command
update-remote-system
Input
| Argument Name | Description | Required |
|---|---|---|
| data | The data to send to the remote system. | Required |
| entries | The entries to send to the remote system. | Optional |
| incident_changed | Boolean that is telling us if the local incident indeed changed or not. | Optional |
| remote_incident_id | the remote incident id. | Optional |
Context Output
There is no context output for this command.
get-mapping-fields
Returns the list of fields for an incident type.
Base Command
get-mapping-fields
Input
| Argument Name | Description | Required |
| — | — | — |
Context Output
There is no context output for this command.
salesforce-describe-sobject-field
Describe Salesforce object field.
Base Command
salesforce-describe-sobject-field
Input
| Argument Name | Description | Required |
|---|---|---|
| sobject | Salesforce object name. For example, Case (default Case). Default is Case. | Required |
| field | Field definition to return. | Required |
Context Output
There is no context output for this command.
salesforce-list-case-files
Return the list of files attached to the case.
Base Command
salesforce-list-case-files
Input
| Argument Name | Description | Required |
|---|---|---|
| caseoId | Case object ID. | Optional |
| caseNumber | Case number. | Optional |
Context Output
There is no context output for this command.
salesforce-get-case-file-by-id
Retrieve a case file by file ID.
Base Command
salesforce-get-case-file-by-id
Input
| Argument Name | Description | Required |
|---|---|---|
| caseFileId | Case file ID. | Optional |
| caseNumber | Case number. | Optional |
Context Output
There is no context output for this command.
Incident Mirroring
You can enable incident mirroring between Cortex XSOAR incidents and Salesforce V2 corresponding events (available from Cortex XSOAR version 6.0.0).
To set up the mirroring:
- Enable Fetching incidents in your instance configuration.
-
In the Mirroring Direction integration parameter, select in which direction the incidents should be mirrored:
Option Description None Turns off incident mirroring. Incoming Any changes in Salesforce V2 events (mirroring incoming fields) will be reflected in Cortex XSOAR incidents. Outgoing Any changes in Cortex XSOAR incidents will be reflected in Salesforce V2 events (outgoing mirrored fields). Incoming And Outgoing Changes in Cortex XSOAR incidents and Salesforce V2 events will be reflected in both directions. - Optional: You can go to the mirroring tags parameter and select the tags used to mark incident entries to be mirrored. Available tags are: Comment Entry Tag.
- Optional: Check the Close Mirrored XSOAR Incident integration parameter to close the Cortex XSOAR incident when the corresponding event is closed in Salesforce V2.
Newly fetched incidents will be mirrored in the chosen direction. However, this selection does not affect existing incidents.
Important Note: To ensure the mirroring works as expected, mappers are required, both for incoming and outgoing, to map the expected fields in Cortex XSOAR and Salesforce V2.
Configuration parameters
InstanceURL— Instance URL (required)credentials— Credentials (required)clientID— Consumer Key (required)clientSecret— Consumer Secret (required)insecure— Trust any certificate (not secure)useproxy— Use system proxy settingsfetchType— Fetch type: cases/commentscondition— Define a query to determine which objects to fetch.fetchFields— Fields to Fetch (only for cases/comments)isFetch— Fetch incidentsincidentType— Incident typefirstFetchTime— First Fetch Timemirror_direction— Incident Mirroring Directioncomment_tag— Comment Entry Tagclose_incident— Close Mirrored XSOAR Incidentclose_case— Close Mirrored Salesforce caseincidentFetchInterval— Incidents Fetch Interval
Commands (24)
-
get-mapping-fieldsReturns the list of fields for an incident type.
-
get-modified-remote-dataAvailable from Cortex XSOAR version 6.1.0. This command queries for incidents that were modified since the last update. This method is only used for debugging purposes.
-
get-remote-dataGets remote data from a remote incident. This method is only used for debugging purposes and will not update the current incident.
-
salesforce-close-caseClose a case.
-
salesforce-create-caseCreates a new case.
-
salesforce-create-objectCreates a new object.
-
salesforce-delete-caseDeletes a case.
-
salesforce-describe-sobject-fieldDescribe Salesforce object field.
-
salesforce-get-caseReturns information on a case. All arguments are optional, but you must specify at least one argument for the command to execute successfully.
-
salesforce-get-case-file-by-idRetrieve a case file by file ID.
-
salesforce-get-casecommentReturns a comment through the case number.
-
salesforce-get-casesReturns all cases.
-
salesforce-get-objectReturns an object by its path.
-
salesforce-get-orgReturns organization details from the case number.
-
salesforce-get-userReturns the user name through the case number.
-
salesforce-list-case-filesReturn the list of files attached to the case.
-
salesforce-post-casecommentThe post comment through the case number.
-
salesforce-push-commentAdds a comment to Chatter.
-
salesforce-push-comment-threadsAdd the comment to the Chatter thread. Use this command only after salesforce-push-comment.
-
salesforce-queryQueries Salesforce in SOQL format.
-
salesforce-searchSearch records that contain values with the defined pattern.
-
salesforce-update-caseUpdates case fields.
-
salesforce-update-objectUpdates object fields.
-
update-remote-systemAvailable from Cortex XSOAR version 6.1.0. This command pushes local changes to the remote system.
import json from typing import Any import demistomock as demisto # noqa: F401 import urllib3 from CommonServerPython import * # noqa: F401 from dateparser import parse # Disable insecure warnings urllib3.disable_warnings() """ CONSTANTS """ DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ" MAX_INCIDENTS_TO_FETCH = 50 MIRROR_DIRECTION = {"None": None, "Incoming": "In", "Outgoing": "Out", "Incoming And Outgoing": "Both"} """ CLIENT CLASS """ class Client(BaseClient): SESSION_DATA = "" URI_PREFIX = "/services/data/v39.0/" def getNewToken(self): response = self._http_request( method="POST", url_suffix="/services/oauth2/token", headers={"Content-Type": "application/x-www-form-urlencoded"}, params={ "grant_type": "password", "client_id": demisto.params().get("clientID"), "client_secret": demisto.params().get("clientSecret"), "username": demisto.params()["credentials"].get("identifier"), "password": demisto.params()["credentials"].get("password"), }, ) if response.get("access_token"): self.SESSION_DATA = response self._base_url = self.SESSION_DATA.get("instance_url") # type: ignore self._headers = { "Authorization": f'Bearer {self.SESSION_DATA.get("access_token")}', # type: ignore "Content-Type": "application/json", } return response def sendRequestInSession(self, method, uri="", body={}, params={}, full_url=None): if not full_url: response = self._http_request( method=method, url_suffix=f"{self.URI_PREFIX}{uri}", headers=self._headers, json_data=body, params=params, resp_type="response", ) if response.status_code == 401: # get a new token and re-run the request self.SESSION_DATA = self.getNewToken() response = self._http_request( method=method, url_suffix=f"{self.URI_PREFIX}{uri}", headers=self._headers, json_data=body, params=params, resp_type="response", ) else: response = self._http_request( method=method, full_url=full_url, headers=self._headers, json_data=body, params=params, resp_type="response" ) if response.status_code == 401: # get a new token and re-run the request self.SESSION_DATA = self.getNewToken() response = self._http_request( method=method, full_url=full_url, headers=self._headers, json_data=body, params=params, resp_type="response" ) try: return response.json() except ValueError: return response.text def getCaseFiles(self, oid, caseNumber): if oid: p = "cid={oid}" else: p = f"cno={caseNumber}" response = self.sendRequestInSession(method="GET", full_url=f"{self._base_url}/services/apexrest/TACCaseFileAPI?{p}") return response def getCaseFileById(self, caseNumber, caseFileId): case_files = self.getCaseFiles(None, caseNumber).get("fileInfo") for cf in case_files: if cf.get("id") == caseFileId: response = requests.get(cf.get("url")).content return fileResult(cf.get("fileName"), response) raise Exception(f"Cannot find {caseFileId} in {caseNumber}") def getObjectTypes(self): sobjects = {} response = self.sendRequestInSession("GET", "sobjects").get("sobjects", None) if len(response) > 0: for item in response: if key_prefix := item.get("keyPrefix"): sobjects[key_prefix] = item.get("label") # set the integration object with object prefix demisto.setIntegrationContext({"sobjects": sobjects}) def identifyObjectType(self, oid): # identify object type based on the first 3 chars of oid return demisto.getIntegrationContext()["sobjects"].get(str(oid[0:3]), "Unknown") def queryRaw(self, query): url = "query/" params = {"q": query} records = [] response = self.sendRequestInSession("GET", url, "", params) records += response.get("records") while response.get("done") is False: response = self.sendRequestInSession("GET", url + response.get("nextRecordsUrl").split("/")[5], "", {}) records += response.get("records") response["records"] = records return response def queryObjects(self, fields, table, condition=None): query = f"SELECT {','.join(fields)} FROM {table}" if condition: query += f" WHERE {condition}" return self.queryRaw(query) def getObject(self, path): if "/" in path: # if full path provided response = self.sendRequestInSession("GET", "sobjects/" + path, "") else: # otherwise only oid provided obj_type = self.identifyObjectType(path).replace(" ", "") response = self.sendRequestInSession("GET", f"sobjects/{obj_type}/{path}", "") return response def createObject(self, path, json_obj): return self.sendRequestInSession("POST", "sobjects/" + path, json_obj) def updateObject(self, path, json_obj): return self.sendRequestInSession("PATCH", "sobjects/" + path, json_obj) def deleteObject(self, path): return self.sendRequestInSession("DELETE", "sobjects/" + path) # Add the capability to get all comments in a specific case def getCaseComment(self, oid, caseNumber): if caseNumber: condition = f"CaseNumber='{caseNumber}'" cases = self.queryObjects(["Id", "CaseNumber"], "Case", condition).get("records") comments = self.sendRequestInSession("GET", f"sobjects/Case/{cases[0]['Id']}/CaseComments") elif oid: comments = self.sendRequestInSession("GET", f"sobjects/Case/{oid}/CaseComments") else: comments = "" return comments def getUser(self, oid, caseNumber): if caseNumber: condition = f"CaseNumber='{caseNumber}'" cases = self.queryObjects(["Id", "CaseNumber", "OwnerId"], "Case", condition).get("records", []) if len(cases) == 1: # retrieve object type based on OwnerId obj_type = self.identifyObjectType(cases[0].get("OwnerId")) users = self.getObject(f"{obj_type}/{cases[0].get('OwnerId')}") else: users = "" demisto.debug(f"{len(cases)=} != 1. {users=}") elif oid: users = self.getObject(f"User/{oid}") else: users = "" demisto.debug(f"not caseNumber and not oid. {users=}") return users # Add the capability to post comment in specific case def postCaseComment(self, public, oid, caseNumber, text): # retrieve oid based on case number if caseNumber: condition = f"CaseNumber='{caseNumber}'" cases = self.queryObjects(["Id", "CaseNumber", "OwnerId"], "Case", condition).get("records", []) if len(cases) == 1: oid = cases[0].get("Id") else: return_error("Invalid Case Number Provided.") data = {"CommentBody": text, "ParentId": oid, "IsPublished": public} response = self.sendRequestInSession("POST", "sobjects/CaseComment", data) return response def pushComment(self, oid, text, linkUrl): data = { "body": { "messageSegments": [{"type": "Text", "text": text}], }, "feedElementType": "FeedItem", "subjectId": oid, } if linkUrl: data["body"]["messageSegments"].append({"type": "Link", "url": linkUrl}) response = self.sendRequestInSession("POST", "chatter/feed-elements", data) return response def pushCommentThread(self, id, text): data = {"body": {"messageSegments": [{"type": "Text", "text": text}]}} return self.sendRequestInSession("POST", f"chatter/feed-elements/{id}/capabilities/comments/items", data) """ HELPER FUNCTIONS """ def getUserNames(client): res = client.queryObjects(["Id", "Name"], "User", None) users = {} for item in res.get("records"): users[item.get("Id")] = item.get("Name") return users def searchToEntry(client, searchRecords): if len(searchRecords) == 0: raise Exception("No records matched the search.") case_ids = [] contact_ids = [] lead_ids = [] task_ids = [] user_ids = [] general = [] case_comment = [] get_org = [] for record in searchRecords: record_type = record.get("attributes", {}).get("type") if record_type == "CaseComment": case_comment.append(record.get("Id")) break elif record_type == "getOrgName": # noqa: RET508 get_org.append(record.get("Id")) break elif record_type == "Case": case_ids.append(record.get("Id")) break elif record_type == "Contact": contact_ids.append(record.get("id")) break elif record_type == "Lead": lead_ids.append(record.get("Id")) break elif record_type == "Task": task_ids.append(record.get("Id")) break elif record_type == "User": user_ids.append(record.get("Id")) break else: # in case we don't know how to parse the object general.append(record) break condition = None properties = None entries = [] # var userMapping = getUserNames(); if len(get_org) > 0: condition = "ID IN ('" + "','".join(get_org) + "')" properties = ["ID", "Name"] # cases = client.queryObjects(properties, "Account", condition).get('records') # entries.append(client.orgToEntry(cases, 'Account:', userMapping)) if len(case_ids) > 0: condition = "ID IN ('" + "','".join(case_ids) + "')" properties = [ "ID", "CaseNumber", "Subject", "Description", "CreatedDate", "ClosedDate", "OwnerID", "Priority", "Origin", "Status", "Reason", "IsEscalated", "SuppliedPhone", "SuppliedCompany", "SuppliedEmail", "ContactEmail", "ContactId", "AccountId", ] cases = client.queryObjects(properties, "Case", condition).get("records") entries.append(objectToEntry(client, cases)) if len(case_comment) > 0: condition = "ID IN ('" + "','".join(case_comment) + "')" properties = [ "ID", "CommentBody", "CreatedDate", "CreatedById", "IsPublished", "SystemModstamp", "LastModifiedById", "LastModifiedDate", ] cases_comment = client.queryObjects(properties, "CaseComment", condition).get("records") entries.append(objectToEntry(client, cases_comment)) if len(contact_ids) > 0: condition = "ID IN ('" + "','".join(contact_ids) + "')" properties = ["ID", "Name", "Title", "AccountId", "Phone", "MobilePhone", "Email", "OwnerId"] contacts = client.queryObjects(properties, "Contact", condition).get("records") entries.append(objectToEntry(client, contacts)) if len(lead_ids) > 0: condition = "ID IN ('" + "','".join(lead_ids) + "')" properties = ["ID", "Name", "Title", "Company", "Phone", "MobilePhone", "Email", "Status", "OwnerId"] leads = client.queryObjects(properties, "Lead", condition).get("records") entries.append(objectToEntry(client, leads)) if len(task_ids) > 0: condition = "ID IN ('" + "','".join(task_ids) + "')" properties = ["ID", "Subject", "WhoId", "ActivityDate"] tasks = client.queryObjects(properties, "Task", condition).get("records") entries.append(objectToEntry(client, tasks)) if len(user_ids) > 0: condition = "ID IN ('" + "','".join(user_ids) + "')" properties = ["ID", "Name", "Title", "Phone", "Email"] users = client.queryObjects(properties, "User", condition).get("records") entries.append(objectToEntry(client, users)) if len(general) > 0: entries.append({"unparsed": general}) return entries def queryToEntry(client, args): query = client.queryRaw(args.get("query")) results = CommandResults( outputs_prefix="SalesForce.QueryResults", outputs_key_field="", readable_output=tableToMarkdown("Query Results", query.get("records")), outputs=query.get("records"), ) return results def commentToEntry(raw_info, title, userMapping): # fix owner field if userMapping: for i in range(raw_info): # use OwnerId if no user was found raw_info[i].OwnerId = ( userMapping[raw_info[i].get("OwnerId")] if userMapping[raw_info[i].get("OwnerId")] else raw_info[i].get("OwnerId") ) results = CommandResults( outputs_prefix="SalesForceV2.CaseComment", outputs_key_field="ID", readable_output=tableToMarkdown(title, raw_info), outputs=raw_info, ) return results def objectToEntry(client, raw_info): title = "" if isinstance(raw_info, dict): obj_id = raw_info.get("Id") obj_type = client.identifyObjectType(obj_id) title = f"{obj_type} #{obj_id}:" elif isinstance(raw_info, list): obj_type = client.identifyObjectType(raw_info[0].get("Id")) title = f"{obj_type}" else: return_error("Cannot Identify Object Type") if obj_type == "CaseComment": headers = [ "ID", "ParentId", "IsPublished", "CommentBody", "CreatedById", "CreatedDate", "SystemModstamp", "LastModifiedDate", "LastModifiedById", "IsDeleted", ] outputs_prefix = "SalesForceV2.CaseComment" outputs_key_field = "ID" elif obj_type == "Case": headers = [ "ID", "CaseNumber", "Subject", "Description", "CreatedDate", "ClosedDate", "OwnerId", "Priority", "Origin", "Status", "Reason", "IsEscalated", "SuppliedPhone", "SuppliedCompany", "SuppliedEmail", "ContactEmail", "ContactId", "AccountId", ] outputs_prefix = "SalesForceV2.Case" outputs_key_field = "ID" elif obj_type == "Contact": headers = ["ID", "Name", "Account", "Title", "Phone", "Mobile", "Email", "Owner"] outputs_prefix = "SalesForceV2.Contact" outputs_key_field = "ID" elif obj_type == "Lead": headers = ["ID", "Name", "Title", "Company", "Phone", "MobilePhone", "Email", "OwnerId", "Status"] outputs_prefix = "SalesForceV2.Lead" outputs_key_field = "ID" elif obj_type == "Task": headers = ["ID", "Subject", "WhoId", "RelatedTo", "ActivityDate"] outputs_prefix = "SalesForceV2.Task" outputs_key_field = "ID" elif obj_type == "User": headers = ["ID", "Name", "Alias", "CommunityNickname", "Title", "Phone", "Email", "FirstName", "Username"] outputs_prefix = "SalesForceV2.GetUsers" outputs_key_field = "ID" else: headers = [] outputs_prefix = "SalesForceV2.Result" outputs_key_field = "" # backward compatibility --> capitalize ID if isinstance(raw_info, dict): raw_info["ID"] = raw_info.pop("Id") elif isinstance(raw_info, list): for index, _item in enumerate(raw_info): raw_info[index]["ID"] = raw_info[index]["Id"] del raw_info[index]["Id"] results = CommandResults( outputs_prefix=outputs_prefix, outputs_key_field=outputs_key_field, readable_output=tableToMarkdown(title, raw_info, headers=headers), outputs=raw_info, ) return results def get_object_command(client: Client, args: dict[str, Any]) -> CommandResults: if path := args.get("path"): result = client.getObject(path) elif oid := args.get("oid"): result = client.getObject(oid) else: result = "" demisto.debug(f"no path or oid. {result=}") return objectToEntry(client, result) def update_object_command(client: Client, args: dict[str, Any]) -> CommandResults: client.updateObject(args.get("path"), args.get("json")) # return updated object return get_object_command(client, args) def create_object_command(client: Client) -> CommandResults: result = client.createObject(demisto.args().get("path"), demisto.args().get("json")) return result def get_case_command(client: Client, args: dict[str, Any]) -> CommandResults: if args.get("caseNumber"): condition = f"CaseNumber='{args.get('caseNumber')}" properties = [ "ID", "CaseNumber", "Subject", "Description", "CreatedDate", "ClosedDate", "OwnerID", "Priority", "Origin", "Status", "Reason", "IsEscalated", "SuppliedPhone", "SuppliedCompany", "SuppliedEmail", "ContactEmail", "ContactId", "AccountId", ] cases = client.queryObjects(properties, "Case", condition).get("records") return objectToEntry(client, cases) elif args.get("oid"): cases = client.getObject(f"Case/{args.get('oid')}") return objectToEntry(client, cases) else: raise Exception("You must specify object ID or a Case Number") def get_user_command(client: Client, args: dict[str, Any]) -> CommandResults: response = client.getUser(args.get("oid"), args.get("caseNumber")) return objectToEntry(client, response) def get_case_comment_command(client: Client, args: dict[str, Any]) -> CommandResults: result = client.getCaseComment(args.get("oid"), args.get("caseNumber")) return objectToEntry(client, result.get("records", [])) def get_org_name_command(client: Client, args: dict[str, Any]) -> CommandResults: if case_number := args.get("caseNumber"): condition = f"CaseNumber='{case_number}'" properties = ["ID", "CaseNumber", "AccountId"] cases = client.queryObjects(properties, "Case", condition).get("records") condition_a = f"Id='{cases[0].get('AccountId')}'" properties_a = ["Id", "Name"] users_a = client.queryObjects(properties_a, "Account", condition_a).get("records") else: raise Exception("You must specify a Case Number") return objectToEntry(client, users_a) def post_case_comment_command(client: Client, args: dict[str, Any]) -> CommandResults: results = client.postCaseComment(args.get("public"), args.get("oid"), args.get("caseNumber"), args.get("text")) if results.get("success"): case_comment = client.getObject(f"{results.get('id')}") else: raise Exception(f"Unable to post case comment. Error Encountered was:{json.dumps(results.get('errors'))}") return objectToEntry(client, case_comment) def create_case_command(client: Client, args: dict[str, Any]) -> CommandResults: data = { "Subject": args.get("subject"), "Description": args.get("description"), "Status": args.get("status"), "Origin": args.get("origin"), "Priority": args.get("priority"), "Type": args.get("caseType"), } results = client.createObject("Case", data) if results.get("success"): case = client.getObject(f"{results.get('id')}") else: raise Exception(f"Unable to create case. Error Encountered was:{json.dumps(results.get('errors'))}") return objectToEntry(client, case) def update_case_command(client: Client, args: dict[str, Any]) -> CommandResults: case_number = args.get("caseNumber") oid = args.get("oid") if oid and case_number: raise Exception("You must specify an object ID or a Case Number (not both)") if case_number: condition = f"CaseNumber='{case_number}'" cases = client.queryObjects(["Id"], "Case", condition).get("records") if len(cases) > 0: oid = cases[0].get("Id") else: raise Exception("Unable to update case -> Invalid Case Number provided") data = {} if subject := args.get("subject"): data["Subject"] = subject if description := args.get("description"): data["Description"] = description if status := args.get("status"): data["Status"] = status if origin := args.get("origin"): data["Origin"] = origin if priority := args.get("priority"): data["Priority"] = priority if case_type := args.get("caseType"): data["Type"] = case_type client.updateObject(f"Case/{oid}", data) return get_case_command(client, {"oid": oid}) def get_cases_command(client: Client) -> CommandResults: properties = [ "ID", "CaseNumber", "Subject", "Description", "CreatedDate", "ClosedDate", "OwnerID", "Priority", "Origin", "Status", "Reason", ] cases = client.queryObjects(properties, "Case", "").get("records") return objectToEntry(client, cases) def close_case_command(client: Client, args: dict[str, Any]) -> CommandResults: args["status"] = "Closed" return update_case_command(client, args) def delete_case_command(client: Client, args: dict[str, Any]) -> CommandResults: case_number = args.get("caseNumber") if args.get("oid") and case_number: raise Exception("You must specify object ID or a Case Number") if case_number: condition = f"CaseNumber='{case_number}'" cases = client.queryObjects(["Id"], "Case", condition).get("records") if len(cases) > 0: oid = cases[0].get("Id") else: raise Exception("Unable to update case -> Invalid Case Number provided") else: oid = args.get("oid") return client.deleteObject(f"Case/{oid}") def push_comment_command(client: Client, args: dict[str, Any]) -> CommandResults: return client.pushComment(args.get("oid"), args.get("text"), args.get("linkUrl")) def push_comment_thread_command(client: Client, args: dict[str, Any]) -> CommandResults: results = client.pushCommentThread(args.get("id"), args.get("text")) return results def search_command(client: Client, args: dict[str, Any]) -> CommandResults: search_records = client.sendRequestInSession("GET", f"search/?q=FIND+%7B{args.get('pattern')}%7D", "").get("searchRecords") return searchToEntry(client, search_records) def list_case_files_command(client: Client, args: dict[str, Any]) -> CommandResults: case_oid = args.get("caseoId") case_number = args.get("caseNumber") file_info = client.getCaseFiles(case_oid, case_number).get("fileInfo", []) results = CommandResults( outputs_prefix="SalesforceV2.Files", outputs_key_field="id", readable_output=tableToMarkdown(f"Case Files {case_oid or case_number}", file_info), outputs=file_info, ignore_auto_extract=True, ) return results def get_case_file_by_id_command(client: Client, args: dict[str, Any]) -> CommandResults: return client.getCaseFileById(args.get("caseNumber"), args.get("caseFileId")) def describe_sobject_field_command(client: Client, args: dict[str, Any]): response = client.sendRequestInSession("GET", "sobjects/Case/describe/") if field_to_search := args.get("field"): fields = response.get("fields") for field in fields: if field.get("name") == field_to_search: return field raise Exception(f"The field: {field_to_search} cannot be found in the sobject. Perhaps wrong field name or object name.") return None def get_mapping_fields_command(client): case_incident_type_scheme = SchemeTypeMapping(type_name="Salesforce Case") fields = client.sendRequestInSession("GET", "sobjects/Case/describe/").get("fields") for field in fields: case_incident_type_scheme.add_field(name=field.get("name"), description="N/A") mapping_response = GetMappingFieldsResponse() mapping_response.add_scheme_type(case_incident_type_scheme) return mapping_response def update_remote_system_command(client: Client, args: dict[str, Any], params: dict[str, Any]) -> str: """ This command pushes local changes to the remote system. Args: client: XSOAR Client to use. args: args['data']: the data to send to the remote system args['entries']: the entries to send to the remote system args['incident_changed']: boolean telling us if the local incident indeed changed or not args['remote_incident_id']: the remote incident id params: entry_tags: the tags to pass to the entries (to separate between comments and work_notes) Returns: The remote incident id - ticket_id """ parsed_args = UpdateRemoteSystemArgs(args) object_id = parsed_args.remote_incident_id if parsed_args.delta: demisto.debug(f"Got the following delta keys {list(parsed_args.delta.keys())!s}") if parsed_args.incident_changed: demisto.debug(f"Incident changed: {parsed_args.incident_changed}") # close case in Salesforce when incident is closed in XSOAR if parsed_args.inc_status == IncidentStatus.DONE and params.get("close_case"): parsed_args.data["status"] = "Closed" demisto.debug(f"Sending update request to server {parsed_args.data}") updated_fields = parsed_args.data if "Id" in updated_fields: del updated_fields["Id"] result = client.updateObject(f"Case/{object_id}", parsed_args.data) demisto.info(f"Case Update result {result}") entries = parsed_args.entries if entries: demisto.debug(f"New entries {entries}") for entry in entries: demisto.debug(f'Sending entry {entry.get("id")}, type: {entry.get("type")}') # Mirroring files as entries if entry.get("type", 0) != 3: # Mirroring comment and work notes as entries tags = entry.get("tags", []) user = entry.get("user", "dbot") text = f"({user}): {entry.get('contents', '')!s}\n\n Mirrored from Cortex XSOAR" public = "true" if "public" in tags else "false" client.postCaseComment(public, object_id, None, text) return object_id def get_data(client, remote_incident_id, last_update, params): case = {} if len(params.get("fetchFields", [])) > 1: properties = params.get("fetchFields", []).split(",") else: properties = [] properties += [ "Id", "CaseNumber", "Subject", "Description", "CreatedDate", "ClosedDate", "OwnerId", "Priority", "Origin", "Status", "Reason", "LastModifiedDate", "MilestoneStatus", "isEscalated", ] cases = client.queryObjects( list(set(properties)), "Case", f"LastModifiedDate >= {last_update} AND Id='{remote_incident_id}'" ).get("records") comments = [] if len(cases) == 1: case = cases[0] case["OwnerDetails"] = client.getObject(case.get("OwnerId")) del case["attributes"] # remove attributes condition = f"LastModifiedDate >= {last_update} AND ParentId='{case.get('Id')}' ORDER BY LastModifiedDate DESC" properties = [ "ID", "CommentBody", "CreatedDate", "CreatedById", "IsPublished", "SystemModstamp", "LastModifiedById", "LastModifiedDate", ] comments = client.queryObjects(properties, "CaseComment", condition).get("records") if isinstance(comments, dict): comments = [comments] for index, comment in enumerate(comments): comments[index]["Owner"] = client.getObject(comment.get("CreatedById")) else: # we have more that 1 case or none raise Exception("Invalid remote incident ID provided.") return case, comments def get_modified_remote_data_command(client, args, params): modified_records_ids = [] remote_args = GetModifiedRemoteDataArgs(args) last_upadte_parsed = parse(remote_args.last_update, settings={"TIMEZONE": "UTC"}) if last_upadte_parsed: last_update = last_upadte_parsed.isoformat().split(".")[0] + "Z" demisto.debug(f"SalesforcePy : * START * Performing get-modified-remote-data command. Last update is: {last_update}") cases = client.queryObjects(["Id"], "Case", f"{params.get('mirroring_condition')}".replace("AND ", "")).get("records") for item in cases: modified_records_ids.append(item["Id"]) demisto.debug( f"SalesforcePy : * END * Performing get-modified-remote-data command. Results: {','.join(modified_records_ids)}" ) return GetModifiedRemoteDataResponse(modified_records_ids) def get_remote_data_command(client, args, params): parsed_args = GetRemoteDataArgs(args) new_incident_data = {} entries = [] lastcomment_date = [] last_update_sfdc_parsed = parse(parsed_args.last_update, settings={"TIMEZONE": "UTC"}) last_update_sfdc = last_update_sfdc_parsed.isoformat().split(".")[0] + "Z" if last_update_sfdc_parsed else "" try: new_incident_data, case_comments = get_data(client, parsed_args.remote_incident_id, last_update_sfdc, params) new_incident_data["id"] = parsed_args.remote_incident_id new_incident_data["in_mirror_error"] = "" if len(case_comments) > 0: for cc in case_comments: owner = cc.get("Owner", "") lastcomment_date.append(cc.get("LastModifiedDate", "").split(".")[0] + "Z") entries.append( { "Type": EntryType.NOTE, "Contents": f"# Case Comment From Salesforce on {cc.get('LastModifiedDate', '')}\n*Created By*:" f" {owner.get('FirstName', '')} {owner.get('LastName', '')}" f" ({owner.get('Username', '')})\n\n{cc.get('CommentBody', '')}", "ContentsFormat": EntryFormat.MARKDOWN, "Tags": ["Salesforce Case Comment"], # the list of tags to add to the entry "Note": True, "IgnoreAutoExtract": True, } ) # setup last comment date to track tickets if len(lastcomment_date) > 0: new_incident_data["lastcomment_date"] = max(lastcomment_date) # end # close xsoar incident when SFDC case is closed if new_incident_data.get("Status") == "Closed" and demisto.params().get("close_incident"): demisto.debug(f"case is closed: {new_incident_data}") entries.append( { "Type": EntryType.NOTE, "Contents": { "dbotIncidentClose": True, "closeReason": f'Case closed in Salesforce on {new_incident_data.get("LastModifiedDate")}', }, "ContentsFormat": EntryFormat.JSON, } ) return GetRemoteDataResponse(mirrored_object=new_incident_data, entries=entries) except Exception as e: if new_incident_data: new_incident_data["in_mirror_error"] = str(e) else: new_incident_data = {"id": parsed_args.remote_incident_id, "in_mirror_error": str(e)} return GetRemoteDataResponse(mirrored_object=new_incident_data, entries=[]) def fetchIncident(client, params): fetchType = params.get("fetchType") lastRun = demisto.getLastRun() if not lastRun.get("last_case_time"): lastRun = {} first_fetch_time = params.get("firstFetchTime", "3 days").strip() first_fetch_time_parsed = parse(f"{first_fetch_time} UTC") if first_fetch_time_parsed: lastRun["last_case_time"] = first_fetch_time_parsed.isoformat().split("+")[0].split(".")[0] + "Z" demisto.setLastRun(lastRun) incidents = [] if len(params.get("fetchFields", [])) > 1: properties = params.get("fetchFields", []).split(",") else: properties = [] if fetchType == "cases": # query cases from last time condition = f"CreatedDate>{lastRun.get('last_case_time')} {params.get('condition','')} ORDER BY CreatedDate DESC" properties += [ "Id", "CaseNumber", "Subject", "Description", "CreatedDate", "ClosedDate", "OwnerId", "Priority", "Origin", "Status", "Reason", "LastModifiedDate", "MilestoneStatus", "isEscalated", ] cases = client.queryObjects(list(set(properties)), "Case", condition).get("records") if len(cases) > 0: last_case_time_parsed = parse(cases[0].get("CreatedDate")) if last_case_time_parsed: lastRun["last_case_time"] = last_case_time_parsed.isoformat().split("+")[0].split(".")[0] + "Z" demisto.setLastRun(lastRun) for index, item in enumerate(cases): del item["attributes"] # remove attributes # retrieve owner info cases[index]["OwnerDetails"] = client.getObject(item.get("OwnerId")) last_modified_date_parsed = parse(item.get("LastModifiedDate")) if last_modified_date_parsed: cases[index]["LastModifiedDate"] = last_modified_date_parsed.isoformat().split("+")[0].split(".")[0] + "Z" created_dated_parsed = parse(item.get("CreatedDate")) if created_dated_parsed: cases[index]["CreatedDate"] = created_dated_parsed.isoformat().split("+")[0].split(".")[0] + "Z" incidents.append( { "name": f"{item.get('Id')} {item.get('Subject')}", "details": item.get("Description"), "owner": cases[index]["OwnerDetails"].get("Email"), "occurred": created_dated_parsed.isoformat().split("+")[0].split(".")[0] + "Z" if created_dated_parsed else "", "rawJSON": json.dumps(item), "mirror_direction": MIRROR_DIRECTION.get(params.get("mirror_direction")), "mirror_tags": [params.get("comment_tag"), params.get("file_tag")], "mirror_instance": demisto.integrationInstance(), } ) else: # Fetch comment replies properties += ["Id", "CommentBody", "CreatedDate"] condition = ( f"CreatedDate> {lastRun.get('last_case_time')} {params.get('condition','')} ORDER BY CreatedDate DESC LIMIT 10" ) replies = client.queryObjects(list(set(properties)), "FeedComment", condition).get("records") if len(replies) > 0: last_case_time_parsed = parse(replies[0].get("CreatedDate")) if last_case_time_parsed: lastRun["last_case_time"] = last_case_time_parsed.isoformat().split("+")[0].split(".")[0] + "Z" demisto.setLastRun(lastRun) for reply in replies: # Get reply details replyDetails = client.sendRequestInSession("GET", f'chatter/comments/{reply.get("Id")}') feedElement = replyDetails.get("feedElement") parentID = feedElement.get("id") # get parent details parentDetails = client.getObject(f"CaseFeed/{parentID}") parentText = parentDetails # if (parentText.indexOf('DemistoID') !== -1) { messageSegments = replyDetails.get("body").get("messageSegments") for ms in messageSegments: if ms.get("type") == "Text": created_date_parsed = parse(parentText.get("CreatedDate")) # Found the relevant comment (there's only one), so we return it incidents.append( { "name": f"{parentText['attributes']['type']} {parentText['Id']}", "details": ms.get("text"), "occurred": created_date_parsed.isoformat().split("+")[0].split(".")[0] + "Z" if created_date_parsed else "", "rawJSON": json.dumps(parentText), } ) return incidents def test_module(client): token = client.getNewToken() if token.get("access_token"): return "ok" return None """ MAIN FUNCTION """ def main() -> None: """ main function, parses params and runs command functions """ # get the service API url params = demisto.params() args = demisto.args() base_url = params.get("InstanceURL") verify_certificate = params.get("insecure", False) proxy = params.get("proxy", False) command = demisto.command() demisto.debug(f"Command being called is {command}") try: client = Client(base_url=base_url, verify=verify_certificate, headers={}, proxy=proxy) # obtain the token client.getNewToken() # set the integration context if not already if "sobjects" not in demisto.getIntegrationContext(): client.getObjectTypes() if command == "test-module": # This is the call made when pressing the integration Test button. return_results(test_module(client)) elif command == "fetch-incidents": incidents = fetchIncident(client, params) demisto.incidents(incidents) elif command == "salesforce-search": return_results(search_command(client, args)) elif command == "salesforce-query": return_results(queryToEntry(client, args)) elif command == "salesforce-get-object": return_results(get_object_command(client, args)) elif command == "salesforce-update-object": return_results(update_object_command(client, args)) elif command == "salesforce-create-object": return_results(create_object_command(client)) elif command == "salesforce-get-case": return_results(get_case_command(client, args)) elif command == "salesforce-get-user": return_results(get_user_command(client, args)) elif command == "salesforce-get-casecomment": return_results(get_case_comment_command(client, args)) elif command == "salesforce-get-org": return_results(get_org_name_command(client, args)) elif command == "salesforce-post-casecomment": return_results(post_case_comment_command(client, args)) elif command == "salesforce-create-case": return_results(create_case_command(client, args)) elif command == "salesforce-update-case": return_results(update_case_command(client, args)) elif command == "salesforce-get-cases": return_results(get_cases_command(client)) elif command == "salesforce-close-case": return_results(close_case_command(client, args)) elif command == "salesforce-delete-case": return_results(delete_case_command(client, args)) elif command == "salesforce-push-comment": return_results(push_comment_command(client, args)) elif command == "salesforce-push-comment-threads": return_results(push_comment_thread_command(client, args)) elif command == "salesforce-describe-sobject-field": return_results(describe_sobject_field_command(client, args)) elif command == "salesforce-list-case-files": return_results(list_case_files_command(client, args)) elif command == "salesforce-get-case-file-by-id": return_results(get_case_file_by_id_command(client, args)) elif command == "get-remote-data": return_results(get_remote_data_command(client, args, params)) elif command == "get-modified-remote-data": return_results(get_modified_remote_data_command(client, args, params)) elif command == "update-remote-system": return_results(update_remote_system_command(client, args, params)) elif command == "get-mapping-fields": return_results(get_mapping_fields_command(client)) else: raise NotImplementedError(f"Command {command} is not implemented") # Log exceptions and return errors except Exception as e: return_error(f"Failed to execute {command} command.\nError:\n{e!s}") """ ENTRY POINT """ if __name__ in ("__main__", "__builtin__", "builtins"): main()