Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 192 additions & 0 deletions Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR.py
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,39 @@ def update_asset_group(self, group_id: str, request_data: dict):
json_data=request_data,
)

def search_cases(self, request_data: dict):
"""
API Endpoint: POST /public_api/v1/case/search
"""
res = self._http_request(
method="POST",
url_suffix="/case/search",
json_data=request_data,
)
return res.get("reply", {}).get("DATA", [])

def update_case(self, case_id: str, request_data: dict):
"""
API Endpoint: POST /public_api/v1/case/update/{case-id}
In case of success the API returns 204
"""
return self._http_request(
method="POST",
url_suffix=f"/case/update/{case_id}",
json_data=request_data,
resp_type="response"
)

def get_case_artifacts(self, case_id: str):
"""
API Endpoint: GET /public_api/v1/case/artifacts
"""
res = self._http_request(
method="GET",
url_suffix=f"/case/artifacts/{case_id}",
)
return res


def extract_paths_and_names(paths: list) -> tuple:
"""
Expand Down Expand Up @@ -1878,6 +1911,156 @@ def update_asset_group_command(client: Client, args: Dict) -> CommandResults:
return CommandResults(readable_output="Asset group updated successfully")


def case_list_command(client: Client, args: Dict[str, Any]) -> CommandResults:
"""
API Docs: https://docs-cortex.paloaltonetworks.com/r/Cortex-XDR-Platform-APIs/Retrieve-cases-based-on-filters
Returns a list of cases.

Args:
- client (Client): The client to use for the request.
- args (dict): The command arguments.

Returns:
- CommandResults: A CommandResults object.
"""
case_ids = argToList(args.get("case_id"))
case_domains = argToList(args.get("case_domain"))
severities = argToList(args.get("severity"))
statuses = argToList(args.get("status"))
created_before = arg_to_timestamp(args.get("created_before"), "created_before") if args.get("created_before") else None
created_after = arg_to_timestamp(args.get("created_after"), "created_after") if args.get("created_after") else None
sort_field = args.get("sort_field")
sort_order = args.get("sort_order")
limit = arg_to_number(args.get("limit")) if args.get("limit") else 50
page_size = arg_to_number(args.get("page_size")) if args.get("page_size") else limit
page = arg_to_number(args.get("page")) if args.get("page_size") else 0

filters = []
if case_ids:
converted_ids = list(map(int, case_ids))
filters.append({"field": "case_id", "operator": "in", "value": converted_ids})
if case_domains:
filters.append({"field": "case_domain", "operator": "in", "value": case_domains})
if severities:
filters.append({"field": "severity", "operator": "in", "value": severities})
if statuses:
filters.append({"field": "status_progress", "operator": "in", "value": statuses})
if created_before:
filters.append({"field": "creation_time", "operator": "lte", "value": created_before})
if created_after:
filters.append({"field": "creation_time", "operator": "gte", "value": created_after})

request_data: Dict[str, Any] = {
"search_from": page * page_size,
"search_to": min((page + 1) * page_size, (page * page_size) + limit),
"filters": filters,
}
if sort_field:
request_data["sort"] = {"field": sort_field, "keyword": sort_order or "desc"}
else:
request_data["sort"] = {}

cases = client.search_cases({"request_data": request_data})

readable_output = tableToMarkdown(
name="Cortex XDR Cases",
t=cases,
date_fields=["creation_time", "modification_time"],
headerTransform=string_to_table_header,
removeNull=True,
)

return CommandResults(
readable_output=readable_output,
outputs_prefix=f"{INTEGRATION_CONTEXT_BRAND}.Case",
outputs_key_field="case_id",
outputs=cases,
raw_response=cases,
)


def case_update_command(client: Client, args: Dict[str, Any]) -> CommandResults:
"""
API Docs: https://docs-cortex.paloaltonetworks.com/r/Cortex-XDR-Platform-APIs/Update-existing-case
Updates an existing case.

Args:
- client (Client): The client to use for the request.
- args (dict): The command arguments.

Returns:
- CommandResults: A CommandResults object.
"""
case_id = args.get("case_id") # required
status = args.get("status").upper() if args.get("status") else None
resolve_reason = args.get("resolve_reason").upper() if args.get("resolve_reason") else None
resolve_comment = args.get("resolve_comment").upper() if args.get("resolve_comment") else None

update_data = assign_params(
status_progress=status,
resolve_reason=resolve_reason,
resolve_comment=resolve_comment,
)

client.update_case(case_id, request_data={"request_data": {"update_data": update_data}})

return CommandResults(readable_output=f"Case {case_id} updated successfully")


def case_artifact_list_command(client: Client, args: Dict[str, Any]) -> CommandResults:
"""
API Docs: https://docs-cortex.paloaltonetworks.com/r/Cortex-XDR-Platform-APIs/Retrieve-Case-Artifacts-by-Case-Id
Retrieves artifacts for a specific case.
Args:
- client (Client): The client to use for the request.
- args (dict): The command arguments.

Returns:
- CommandResults: A CommandResults object.
"""
case_id = args.get("case_id")
artifacts = client.get_case_artifacts(case_id)
if isinstance(artifacts, list):
artifacts = artifacts[0]

network_artifacts = artifacts.get("network_artifacts", {}).get("DATA", [])
file_artifacts = artifacts.get("file_artifacts", {}).get("DATA", [])

for artifact in network_artifacts:
artifact["case_id"] = case_id
for artifact in file_artifacts:
artifact["case_id"] = case_id

readable_output = ""
if network_artifacts:
readable_output += tableToMarkdown(
name=f"Network Artifacts for Case {case_id}",
t=network_artifacts,
headerTransform=string_to_table_header,
removeNull=True,
)
if file_artifacts:
readable_output += tableToMarkdown(
name=f"File Artifacts for Case {case_id}",
t=file_artifacts,
headerTransform=string_to_table_header,
removeNull=True,
)

if not readable_output:
readable_output = f"No artifacts found for case {case_id}"

outputs = {}
if network_artifacts:
outputs[f"{INTEGRATION_CONTEXT_BRAND}.CaseNetworkArtifact"] = network_artifacts
if file_artifacts:
outputs[f"{INTEGRATION_CONTEXT_BRAND}.CaseFileArtifact"] = file_artifacts

return CommandResults(readable_output=readable_output,
outputs=outputs,
raw_response=artifacts)


def main(): # pragma: no cover
"""
Executes an integration command
Expand Down Expand Up @@ -2375,6 +2558,15 @@ def main(): # pragma: no cover
elif command == "xdr-api-key-delete":
return_results(api_key_delete_command(client, args))

elif command == "xdr-case-list":
return_results(case_list_command(client, args))

elif command == "xdr-case-update":
return_results(case_update_command(client, args))

elif command == "xdr-case-artifact-list":
return_results(case_artifact_list_command(client, args))

except Exception as err:
return_error(str(err))

Expand Down
127 changes: 127 additions & 0 deletions Packs/CortexXDR/Integrations/CortexXDRIR/CortexXDRIR.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4132,6 +4132,133 @@ script:
name: membership_predicate_json
description: Updates an asset group.
name: xdr-asset-group-update
- arguments:
- description: A comma-separated list of case IDs.
isArray: true
name: case_id
- description: A comma-separated list of case domains.
isArray: true
name: case_domain
- description: A comma-separated list of severities.
isArray: true
name: severity
- description: Filters cases that were created before this date. Supports English expressions like "in a year".
name: created_before
- description: Filters cases that were created after this date. Supports English expressions like "in a year".
name: created_after
- description: A comma-separated list of statuses.
isArray: true
name: status
- auto: PREDEFINED
description: The field by which to sort the results.
name: sort_field
predefined:
- case_id
- severity
- creation_time
- auto: PREDEFINED
description: The order in which to sort the results.
name: sort_order
predefined:
- asc
- desc
- description: Maximum number of cases to return.
name: limit
- description: Page size for pagination.
name: page_size
- description: Page number for pagination.
name: page
description: Returns a list of cases based on filters.
name: xdr-case-list
outputs:
- contextPath: PaloAltoNetworksXDR.Case.case_id
description: The unique identifier of the case.
type: String
- contextPath: PaloAltoNetworksXDR.Case.case_name
description: The name of the case.
type: String
- contextPath: PaloAltoNetworksXDR.Case.severity
description: The severity of the case.
type: String
- contextPath: PaloAltoNetworksXDR.Case.status_progress
description: The progress status of the case (e.g., New, Under Investigation).
type: String
- contextPath: PaloAltoNetworksXDR.Case.description
description: A detailed description of the case and involved entities.
type: String
- contextPath: PaloAltoNetworksXDR.Case.low_severity_issue_count
description: The number of low severity issues associated with the case.
type: Number
- contextPath: PaloAltoNetworksXDR.Case.med_severity_issue_count
description: The number of medium severity issues associated with the case.
type: Number
- contextPath: PaloAltoNetworksXDR.Case.case_domain
description: The security domain of the case.
type: String
- contextPath: PaloAltoNetworksXDR.Case.xdr_url
description: The direct URL to the incident view in the XDR console.
type: String
- contextPath: PaloAltoNetworksXDR.Case.is_blocked
description: Indicates if the threat was blocked.
type: Boolean
- contextPath: PaloAltoNetworksXDR.Case.aggregated_score
description: The overall risk score calculated for the case.
type: Number
- contextPath: PaloAltoNetworksXDR.Case.host_count
description: The number of hosts involved in the case.
type: Number
- contextPath: PaloAltoNetworksXDR.Case.user_count
description: The number of users involved in the case.
type: Number
- contextPath: PaloAltoNetworksXDR.Case.wildfire_hits
description: The number of WildFire malware hits associated with the case.
type: Number
- contextPath: PaloAltoNetworksXDR.Case.tags
description: A list of tags associated with the case.
type: String
- contextPath: PaloAltoNetworksXDR.Case.starred
description: Whether the case has been starred/flagged.
type: Boolean
- arguments:
- description: The ID of the case to update.
name: case_id
required: true
- auto: PREDEFINED
description: The status to set for the case. if the status is updated to "resolved", resolution_reason must be provided.
name: status
predefined:
- new
- under_investigation
- resolved
- auto: PREDEFINED
description: The reason for resolving the case.
name: resolve_reason
predefined:
- resolved_known_issue
- resolved_duplicate
- resolved_false_positive
- "Resolved - Other"
- resolved_true_positive
- resolved_security_testing
- resolved_fixed
- resolved_dismissed
- description: A comment explaining the resolution.
name: resolve_comment
description: Updates an existing case.
name: xdr-case-update
- arguments:
- description: The ID of the case for which to retrieve artifacts.
name: case_id
required: true
description: Retrieves artifacts for a specific case.
name: xdr-case-artifact-list
outputs:
- contextPath: PaloAltoNetworksXDR.CaseNetworkArtifact.case_id
description: The ID of the case associated with the artifact.
type: String
- contextPath: PaloAltoNetworksXDR.CaseFileArtifact.case_id
description: The ID of the case associated with the artifact.
type: String
dockerimage: demisto/python3:3.12.12.5490952
isfetch: true
isfetch:xpanse: false
Expand Down
Loading