XSOAR-Web-Server
This is a simple web-server that as of now, supports handling configurable user responses (like Yes/No/Maybe) and data collection tasks that can be used to fetch key value pairs. What makes it different from Data collection tasks is that, the URL to perform a certain action is predictable and written to the incident context when an action is setup.This URL can be inserted to for eg: an HTML email. User clicks are are recorded in the integration context and can be polled by Scheduled Commands/ Generic Polling.
Utilities · Xsoar-web-server
Details
| ID | XSOAR-Web-Server |
|---|---|
| Provider | Palo Alto Networks |
| Category | Utilities |
| From Version | 6.5.0 |
| Docker Image | demisto/bottle:1.0.0.10133006 |
README
This is a simple web-server that as of now, supports handling configurable user responses (like Yes/No/Maybe) and data collection tasks that can be used to fetch key value pairs. What makes it different from Data collection tasks is that the URL to perform a certain action is predictable and written to the incident context when an action is setup. This URL can be inserted to for example: an HTML email. User clicks are recorded in the integration context and can be polled by Scheduled Commands/ Generic Polling.
This integration was integrated and tested with version 1.0 of XSOAR-Web-Server
Configure XSOAR-Web-Server in Cortex
| Parameter | Description | Required |
|---|---|---|
| Incident type | False | |
| Long running instance | False | |
| Server Listening Port | Runs the service on this port from within Cortex XSOAR. Requires a unique port for each long-running integration instance. Do not use the same port for multiple instances. Note: If you click the test button more than once, a failure may occur mistakenly indicating that the port is already in use. (For Cortex XSOAR 8 and Cortex XSIAM) If using an engine, you must enter a Listen Port. If not using an engine, do not enter a Listen Port and an unused port will automatically be generated when the instance is saved. | True |
| XSOAR external URL | The URL on which the user should send the response to. | True |
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.
xsoar-ws-setup-simple-action
setup the web server to handle URL clicks for each action specified from single or multiple recipients
Base Command
xsoar-ws-setup-simple-action
Input
| Argument Name | Description | Required | |
|---|---|---|---|
| actions | Comma-separated possible values for each action. Mandatory for get requests. | Required | |
| emailaddresses | Comma-separated email addresses of the recipients. | Required | |
| userstring | User defined string that has to be set from the playbook. This is used to differentiate between multiple jobs running on the same incident. | Required | |
| htmltemplate | Template to construct the HTML mail. | Required | |
| xsoarproxy | Used to specify what endpoint to submit the responses. If set to false, the HTML template will have the endpoint containing the custom port. Possible values are: true, false. Default is true. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| WS-ActionDetails | unknown | The current status of the action’s configuration details. |
| WS-ActionDetails.job_uuid | unknown | Subset of action details, added for ease of configuration in playbooks. |
xsoar-ws-clear-cache
Clear the backend storage containing all session information.
Base Command
xsoar-ws-clear-cache
Input
| Argument Name | Description | Required |
| — | — | — |
Context Output
There is no context output for this command.
xsoar-ws-show-cache
Show the details of all the setup actions from the backend.
Base Command
xsoar-ws-show-cache
Input
| Argument Name | Description | Required |
| — | — | — |
Context Output
There is no context output for this command.
xsoar-ws-remove-action
Remove a certain job from the backend.
Base Command
xsoar-ws-remove-action
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | Job’s UUID. | Required |
Context Output
There is no context output for this command.
xsoar-ws-get-action-status
Gets the current status of an action that was setup. Used to track if the user responded to the action.
Base Command
xsoar-ws-get-action-status
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | Job’s UUID. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| WS-ActionStatus | unknown | The current status of the action with some configuration data. Is used for polling the status. |
| WS-ActionStatus.link_tracker | unknown | Subset of action status. Tracked here to make it easier for configuring playbooks. |
xsoar-ws-set-job-complete
Set a job to complete. Usually called from the automation that is polling the result.
Base Command
xsoar-ws-set-job-complete
Input
| Argument Name | Description | Required |
|---|---|---|
| uuid | Job’s uuid. | Required |
Context Output
There is no context output for this command.
xsoar-ws-setup-form-submission
Setup a form submission job that can take multiple values from multiple users.
Base Command
xsoar-ws-setup-form-submission
Input
| Argument Name | Description | Required |
|---|---|---|
| emailaddresses | Comma-separated email addresses of the recipients. | Required |
| userstring | Optional user string that can be set from the playbook. Can be used to differentiate between multiple jobs running on the same incident. | Optional |
| htmltemplate | The template to build the email content. | Required |
| xsoarproxy | Used to specify what endpoint to submit the responses. If set to false, the HTML template will have the endpoint containing the custom port. Default is true. | Optional |
Context Output
There is no context output for this command.
Configuration parameters
incidentType— Incident typelongRunning— Long running instancelongRunningPort— Server Listening Port (required)xsoar-external-url— XSOAR external URL (required)
Commands (7)
-
xsoar-ws-clear-cacheClear the backend storage containing all session information.
-
xsoar-ws-get-action-statusGets the current status of an action that was setup; Used to track if the user responded to the action.
-
xsoar-ws-remove-actionRemove a certain job from the backend.
-
xsoar-ws-set-job-completeSet a job to complete. Usually called from the automation that is polling the result.
-
xsoar-ws-setup-form-submissionsetup a form submission job that can take multiple values from multiple users.
-
xsoar-ws-setup-simple-actionsetup the web server to handle URL clicks for each action specified from single or multiple recipients.
-
xsoar-ws-show-cacheShow the details of all the setup actions from the backend.
import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 import uuid from string import Template from bottle import request, route, run @route("/hello") def hello(): return "Hello World!" @route("/process/<uuid>/<linkuuid>/<action>", method="GET") def process_get(uuid, linkuuid, action): integration_context = get_integration_context() try: data = integration_context[uuid] if action not in data["input_list"]: raise KeyError data["link_tracker"][linkuuid]["response"] = action data["link_tracker"][linkuuid]["response_received"] = True integration_context[uuid] = data set_integration_context(integration_context) response_html = """<html><body> <b>Thank you for responding, Your security team is grateful !</b> </body> </html>""" return response_html except KeyError: return "Cannot process action,server error" @route("/processform/<uuid>/<linkuuid>/", method="POST") def process_form(uuid, linkuuid): integration_context = get_integration_context() try: data = integration_context[uuid] data["link_tracker"][linkuuid]["response_received"] = True data["link_tracker"][linkuuid]["response"] = dict(request.forms.decode()) # pylint: disable=E1101 integration_context[uuid] = data set_integration_context(integration_context) response_html = """<html><body> <b>Thank you for responding, Your security team is grateful !</b> </body> </html>""" return response_html except KeyError: return "Cannot process action,server error" @route("/processform/<uuid>/<linkuuid>/", method="GET") def process_form_get(uuid, linkuuid): integration_context = get_integration_context() try: data = integration_context[uuid] email_html = data["link_tracker"][linkuuid]["htmlemail"] return email_html except KeyError: return "Cannot process action,server error" def run_long_running(listen_port): run(host="0.0.0.0", port=listen_port, debug=True) def return_common_params(): port = demisto.params().get("longRunningPort") inc = demisto.incident() ic = get_integration_context() uuid_ret = str(uuid.uuid4()) ext_url = demisto.params().get("xsoar-external-url").rstrip("/") integration_name = demisto.integrationInstance() return port, inc, ic, uuid_ret, ext_url, integration_name def run_setup_simple_action(): port, inc, integration_context, entry_uuid, xsoar_external_url, integration_instance_name = return_common_params() user_emails = demisto.args().get("emailaddresses").split(",") user_string = demisto.args().get("userstring", "") input_list = demisto.args().get("actions").split(",") htmltemplate = demisto.args().get("htmltemplate") xsoar_proxy = demisto.args().get("xsoarproxy") link_tracker = {} for email in user_emails: html_dict = {} link_uuid = str(uuid.uuid4()) partial_link = f"{xsoar_external_url}/instance/execute/{integration_instance_name}/process/{entry_uuid}/{link_uuid}/" partial_link_port = ( f"{xsoar_external_url}:{port}/instance/execute/{integration_instance_name}/process/{entry_uuid}/{link_uuid}/" # noqa: E501 ) temp_link_tracker = {"response": "", "response_received": False, "emailaddress": email} for ind, action in enumerate(input_list): marker = ind + 1 if xsoar_proxy == "true": temp_link_tracker[f"action_{action}_url"] = partial_link + action html_dict[f"action{marker}"] = partial_link + action else: temp_link_tracker[f"action_{action}_url"] = partial_link_port + action html_dict[f"action{marker}"] = partial_link_port + action temp_link_tracker["htmlemail"] = Template(htmltemplate).substitute(**html_dict) link_tracker[link_uuid] = temp_link_tracker integration_context[entry_uuid] = { "type": "simple_action", "inc_id": inc["id"], "input_list": input_list, "link_tracker": link_tracker, "job_uuid": entry_uuid, "completed": False, "user_string": user_string, } set_integration_context(integration_context) to_return = CommandResults( outputs_prefix="WS-ActionDetails(val.job_uuid==obj.job_uuid)", outputs=integration_context[entry_uuid] ) return_results(to_return) def run_setup_post_action(): port, inc, integration_context, entry_uuid, xsoar_external_url, integration_instance_name = return_common_params() user_emails = demisto.args().get("emailaddresses").split(",") user_string = demisto.args().get("userstring", "") htmltemplate = demisto.args().get("htmltemplate") xsoar_proxy = demisto.args().get("xsoarproxy") link_tracker = {} for _ind, email in enumerate(user_emails): html_dict = {} link_uuid = str(uuid.uuid4()) partial_link = f"{xsoar_external_url}/instance/execute/{integration_instance_name}/processform/{entry_uuid}/{link_uuid}/" partial_link_port = ( f"{xsoar_external_url}:{port}/instance/execute/{integration_instance_name}/processform/{entry_uuid}/{link_uuid}/" # noqa: E501 ) if xsoar_proxy == "true": action_url = partial_link else: action_url = partial_link_port html_dict["action1"] = action_url temp_link_tracker = { "response": "", "response_received": False, "emailaddress": email, "action_url": action_url, "htmlemail": Template(htmltemplate).substitute(**html_dict), } link_tracker[link_uuid] = temp_link_tracker integration_context[entry_uuid] = { "type": "post_action", "inc_id": inc["id"], "link_tracker": link_tracker, "job_uuid": entry_uuid, "completed": False, "user_string": user_string, } set_integration_context(integration_context) to_return = CommandResults( outputs_prefix="WS-ActionDetails(val.job_uuid==obj.job_uuid)", outputs=integration_context[entry_uuid] ) return_results(to_return) def run_clear_integration_cache(): set_integration_context({}) return_results("cleared backend cache") def run_show_integration_cache(): ic = get_integration_context() return_results(json.dumps(ic)) def remove_action_internal(uuid): integration_context = get_integration_context() integration_context.pop(uuid) set_integration_context(integration_context) def run_remove_action(): uuid = demisto.args().get("uuid") lookup, data = validate_uuid(uuid) if lookup: remove_action_internal(uuid) else: return_results(data) def validate_uuid(uuid): integration_context = get_integration_context() try: data = integration_context[uuid] return True, data except KeyError: return False, "Action lookup failed; please check the UUID" def run_get_action_status(): uuid = demisto.args().get("uuid") lookup, data = validate_uuid(uuid) if lookup: input_get_req = data.get("input_list", "") output_to_ret = { "job_uuid": data["job_uuid"], "incident_id": data["inc_id"], "user_string": data["user_string"], "completed": data["completed"], "input_list": input_get_req, } link_tracker = data["link_tracker"] link_tracker_list = [] for key, value in link_tracker.items(): link_tracker_list.append( { "link_uuid": key, "email": value["emailaddress"], "response": value["response"], "response_received": value["response_received"], "emailhtml": value["htmlemail"], } ) output_to_ret["link_tracker"] = link_tracker_list to_return = CommandResults( outputs_prefix="WS-ActionStatus(val.job_uuid==obj.job_uuid)", readable_output="fetching current status", outputs=output_to_ret, ) return_results(to_return) else: return_results(data) def run_set_job_complete(uuid): lookup, data = validate_uuid(uuid) if lookup: ic = get_integration_context() data["completed"] = True ic[uuid] = data set_integration_context(ic) return_results("Job successfully marked as Complete") else: return_results(data) def main() -> None: """main function, parses params and runs command functions :return: :rtype: """ listen_port = demisto.params().get("longRunningPort") command = demisto.command() try: if command == "long-running-execution": run_long_running(listen_port) elif command == "test-module": pass elif command == "xsoar-ws-setup-simple-action": run_setup_simple_action() elif command == "xsoar-ws-clear-cache": run_clear_integration_cache() elif command == "xsoar-ws-show-cache": run_show_integration_cache() elif command == "xsoar-ws-get-action-status": run_get_action_status() elif command == "xsoar-ws-remove-action": run_remove_action() elif command == "xsoar-ws-set-job-complete": job_uuid = demisto.args().get("uuid") run_set_job_complete(job_uuid) elif command == "xsoar-ws-setup-form-submission": run_setup_post_action() else: return_error("Command not found") except Exception as e: return_error(f"Failed to execute {demisto.command()} command.\nError:\n{str(e)}") if __name__ in ("__main__", "__builtin__", "builtins"): main()