import demistomock as demisto # noqa: F401
from CommonServerPython import * # noqa: F401
from typing import Any
import traceback
import re
from json import dumps
""" STANDALONE FUNCTION """
def perform_rest_call(method: str, endpoint: str, body=None):
"""
Perform a REST Call
:param endpoint:
:param body: `dict` representing the HTTP body
:param method: `get` or `post`
:return: The contents from the API Call
"""
args = {
"uri": endpoint,
}
if body:
args["body"] = dumps(body)
result = demisto.executeCommand(f"core-api-{method}", args)
if len(result) < 1 or "Contents" not in result[0] or "response" not in result[0].get("Contents"):
raise Exception(f"Error with REST call to endpoint {endpoint}")
return result[0]["Contents"]["response"]
def get_tasks_list(tasks):
# {'1':{'id': '1'}, ...} -> [{'id':'1'}, ...]
tasks_list = []
for task_number in tasks:
tasks_list.append(tasks[task_number])
return tasks_list
def append_commands(commands, subplaybook_name, subplaybook_json):
for t in get_tasks_list(subplaybook_json):
# commands not using-brand
task = t.get("task", {})
if task.get("isCommand") and task.get("scriptId", "").startswith("|"):
key = task.get("scriptId", "").replace("|||", "")
# These are base commands and should be excluded from brandless list
if key not in ["domain", "file", "ip", "url"]:
if key in commands:
if subplaybook_name not in commands[key]:
commands[key].append(subplaybook_name)
else:
commands.update({key: [subplaybook_name]})
return commands
def append_to_playbooks_and_integrations(playbooks, integrations, script_ids, commands, playbook_json):
playbook_name = playbook_json.get("name")
if playbook_name and playbook_name not in playbooks:
playbooks.append(playbook_name)
for b in argToList(playbook_json.get("brands")):
if b and b != "Builtin" and b not in integrations:
integrations.append(b)
for script_id in playbook_json.get("scriptIds"):
script_ids.append(script_id)
commands = append_commands(commands, playbook_name, playbook_json.get("tasks"))
return playbooks, integrations, script_ids, commands
def get_subplaybook_tasks(playbooks, integrations, script_ids, commands, lists, task):
# recursively go through all subplaybook tasks and append to playbooks and integrations
_task = task.get("task", {})
try:
subplaybook_json = perform_rest_call("get", f"playbook/{_task.get('playbookId')}")
except Exception:
raise Exception(f"Playbook {_task.get('name')} not found")
playbooks, integrations, script_ids, commands = append_to_playbooks_and_integrations(
playbooks, integrations, script_ids, commands, subplaybook_json
)
for t in get_tasks_list(subplaybook_json.get("tasks")):
if t.get("type") == "regular":
lists = get_xsoar_list_name(t, lists)
elif t.get("type") == "playbook" and t.get("task", {}).get("playbookId"):
# playbookId does not exist if the playbook the task references is missing
playbooks, integrations, script_ids, commands, lists = get_subplaybook_tasks(
playbooks, integrations, script_ids, commands, lists, t
)
return playbooks, integrations, script_ids, commands, lists
def create_markdown_list(
incident_types,
layouts,
incident_fields,
indicator_fields,
jobs,
lists,
mappers,
pre_process_rules,
scripts,
integrations,
playbooks,
parent_playbook,
):
markdown_string = "## XSOAR Objects\n"
markdown_string += (
"* Incident Types\n * None\n"
if len(incident_types) == 0
else "* Incident Types\n * " + "\n * ".join(incident_types) + "\n"
)
markdown_string += "* Layouts\n * None\n" if len(layouts) == 0 else "* Layouts\n * " + "\n * ".join(layouts) + "\n"
markdown_string += (
"* Incident Fields\n * None\n"
if len(incident_fields) == 0
else "* Incident Fields\n * " + "\n * ".join(incident_fields) + "\n"
)
markdown_string += (
"* Indicator Fields\n * None\n"
if len(indicator_fields) == 0
else "* Indicator Fields\n * " + "\n * ".join(indicator_fields) + "\n"
)
markdown_string += "* Jobs\n * None\n" if len(jobs) == 0 else "* Jobs\n * " + "\n * ".join(jobs) + "\n"
markdown_string += "* Lists\n * None\n" if len(lists) == 0 else "* Lists\n * " + "\n * ".join(lists) + "\n"
markdown_string += "* Mappers\n * None\n" if len(mappers) == 0 else "* Mappers\n * " + "\n * ".join(mappers) + "\n"
markdown_string += (
"* Pre-Process Rules\n * None\n"
if len(pre_process_rules) == 0
else "* Pre-Process Rules\n * " + "\n * ".join(pre_process_rules) + "\n"
)
markdown_string += "* Parent Playbook: " + parent_playbook + "\n"
markdown_string += (
"* Custom Automations\n * None\n" if len(scripts) == 0 else "* Custom Automations\n * " + "\n * ".join(scripts) + "\n"
)
markdown_string += (
"* Integrations\n * None\n" if len(integrations) == 0 else "* Integrations\n * " + "\n * ".join(integrations) + "\n"
)
markdown_string += "* Playbooks\n * None\n" if len(playbooks) == 0 else "* Playbooks\n * " + "\n * ".join(playbooks) + "\n"
return markdown_string
def create_html_list(
incident_types,
layouts,
incident_fields,
indicator_fields,
jobs,
lists,
mappers,
pre_process_rules,
scripts,
integrations,
playbooks,
parent_playbook,
):
html_string = "
XSOAR Objects
"
html_string += (
"- Incident Types
"
if len(incident_types) == 0
else "- Incident Types
- " + "
- ".join(incident_types) + "
"
)
html_string += (
"- Layouts
"
if len(layouts) == 0
else "- Layouts
"
)
html_string += (
"- Incident Fields
"
if len(incident_fields) == 0
else "- Incident Fields
- " + "
- ".join(incident_fields) + "
"
)
html_string += (
"- Indicator Fields
"
if len(indicator_fields) == 0
else "- Indicator Fields
- " + "
- ".join(indicator_fields) + "
"
)
html_string += (
"- Jobs
"
if len(jobs) == 0
else "- Jobs
"
)
html_string += (
"- Lists
"
if len(lists) == 0
else "- Lists
"
)
html_string += (
"- Mappers
"
if len(mappers) == 0
else "- Mappers
"
)
html_string += (
"- Pre-Process Rules
"
if len(pre_process_rules) == 0
else "- Pre-Process Rules
- " + "
- ".join(pre_process_rules) + "
"
)
html_string += "- Parent Playbook: " + parent_playbook + "
"
html_string += (
"- Custom Automations
"
if len(scripts) == 0
else "- Custom Automations
"
)
html_string += (
"- Integrations
"
if len(integrations) == 0
else "- Integrations
- " + "
- ".join(integrations) + "
"
)
html_string += (
"- Playbooks
"
if len(playbooks) == 0
else "- Playbooks
- " + "
- ".join(playbooks) + "
"
)
return html_string
def get_xsoar_list_name(task, lists):
# Search for lists in tasks
if "scriptArguments" in task:
script_arguments = task.get("scriptArguments")
# Check if the complex argument is a list
try:
if (
script_arguments["value"]["complex"]["accessor"] not in lists
and script_arguments["value"]["complex"]["root"] == "lists"
):
lists.append(script_arguments["value"]["complex"]["accessor"])
except KeyError:
pass
r = re.findall(r"['\{]lists\.(.*?)?[.'\}]", str(script_arguments))
if r:
for list_name in r:
if list_name not in lists:
lists.append(list_name)
return lists
""" COMMAND FUNCTION """
def retrieve_playbook_dependencies(args: dict[str, Any]) -> CommandResults:
playbooks: list[str] = []
integrations: list[str] = []
script_ids: list[str] = []
commands: dict[str, Any] = {} # commands not using brand
lists: list[str] = [] # XSOAR List names
parent_playbook = args.get("playbook_name") or ""
# Call parent playbook's data, then recursivley call all subplaybooks' data
playbooks_json = perform_rest_call("post", "playbook/search", {"query": f'''name:"{parent_playbook}"'''})
match_found = False
if playbooks_json.get("playbooks"):
for playbook_json in playbooks_json.get("playbooks"):
if playbook_json.get("name") == parent_playbook:
match_found = True
break
if not match_found:
raise Exception(f"""Playbook '{parent_playbook}' not found""")
playbooks, integrations, script_ids, commands = append_to_playbooks_and_integrations(
playbooks, integrations, script_ids, commands, playbook_json
)
for task in get_tasks_list(playbook_json.get("tasks")):
if task.get("type") == "regular":
lists = get_xsoar_list_name(task, lists)
elif task.get("type") == "playbook":
playbooks, integrations, script_ids, commands, lists = get_subplaybook_tasks(
playbooks, integrations, script_ids, commands, lists, task
)
# Sort scripts into base scripts and custom scripts, and get the displayname for custom scripts
script_ids = list(set(script_ids))
custom_scripts: list[str] = []
base_scripts: list[str] = []
for script_id in script_ids:
if "-" in script_id:
custom_scripts.append(perform_rest_call("post", f"automation/load/{script_id}").get("name"))
else:
base_scripts.append(perform_rest_call("post", f"automation/load/{script_id}").get("name"))
# Dedup
integrations = list(set(integrations))
playbooks = list(set(playbooks))
# Format results for output
base_scripts.sort()
custom_scripts.sort()
integrations.sort()
lists.sort()
playbooks.sort()
# Sort, format and display brandless commands' possible integrations and the playbooks they were located in
if len(commands) > 0:
integration_result = perform_rest_call("get", "settings/integration-commands")
integration_commands: dict[str, list[str]] = {}
# Find the integrations connected to brand-less commands
for integration in integration_result:
for command in integration.get("commands", []):
if command.get("name") and command.get("name") in commands:
integration_commands.setdefault(integration.get("display"), []).append(command.get("name"))
if len(integration_commands) > 0:
# Format into markdown table displaying integration, command, and playbook it was found in
integration_commands_str = "## Warning\n\n"
integration_commands_str += "Commands found with no clear integration connected to them. "
integration_commands_str += "These integrations have been included in the main markdown list uncritically, "
integration_commands_str += "but are listed below in case revisions are needed:\n"
integration_commands_str += "Recommended Action: Locate command calls in playbooks and confirm tasks are using "
integration_commands_str += "branded commands. EXAMPLE:\n\n"
integration_commands_str += " UNBRANDED: 'command_name'\n BRANDED: 'command_name (Integration Name)'\n"
integration_commands_str += "| Integration | Brandless Command | Located in Playbook(s) |\n"
integration_commands_str += "|---|---|---|"
for key in integration_commands:
integrations.append(key)
for command in integration_commands.get(key, []):
command_playbooks = (", ").join(commands.get(command))
integration_commands_str += f"\n| {key} | {command} | {command_playbooks} |"
return_results(CommandResults(readable_output=integration_commands_str))
# Create final markdown and html for documentation
# Possible opportunities for automating these as well
incident_types = argToList(args.get("incident_types")) if args.get("incident_types") else []
layouts = argToList(args.get("layouts")) if args.get("layouts") else []
incident_fields = argToList(args.get("incident_fields")) if args.get("incident_fields") else []
indicator_fields = argToList(args.get("indicator_fields")) if args.get("indicator_fields") else []
jobs = argToList(args.get("jobs")) if args.get("jobs") else []
mappers = argToList(args.get("mappers")) if args.get("mappers") else []
pre_process_rules = argToList(args.get("pre_process_rules")) if args.get("pre_process_rules") else []
markdown_string = create_markdown_list(
incident_types,
layouts,
incident_fields,
indicator_fields,
jobs,
lists,
mappers,
pre_process_rules,
custom_scripts,
integrations,
playbooks,
parent_playbook,
)
if args.get("Markdown") == "true":
demisto.results(markdown_string)
html_string = create_html_list(
incident_types,
layouts,
incident_fields,
indicator_fields,
jobs,
lists,
mappers,
pre_process_rules,
custom_scripts,
integrations,
playbooks,
parent_playbook,
)
if args.get("HTML") == "true":
demisto.results(html_string)
dependencies = {
"Parent Playbook": args.get("playbook_name"),
"Playbooks": playbooks,
"Integrations": integrations,
"Automations": {"CustomScripts": custom_scripts, "BaseScripts": base_scripts},
"Commands": commands,
"Lists": lists,
"MarkdownString": markdown_string,
"HTMLString": html_string,
}
parent_playbook = parent_playbook.replace(" ", "_")
outputs = {f"{parent_playbook}": dependencies}
return CommandResults(
readable_output=f'''Retrieved Dependencies for Playbook "{parent_playbook}"''',
outputs_prefix="RetrievePlaybookDependencies",
outputs_key_field="",
outputs=outputs,
)
""" MAIN FUNCTION """
def main():
try:
return_results(retrieve_playbook_dependencies(demisto.args()))
except Exception as ex:
demisto.error(traceback.format_exc())
return_error(f"Failed to execute RetrievePlaybookDependencies. Error: {str(ex)}")
""" ENTRY POINT """
if __name__ in ("__main__", "__builtin__", "builtins"):
main()