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
229 changes: 171 additions & 58 deletions Packs/Core/Integrations/CortexPlatformCore/CortexPlatformCore.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"core-list-scripts",
"core-run-script-agentix",
"core-list-endpoints",
"core-get-case-ai-summary",
"core-list-exception-rules",
"core-update-case",
"core-get-endpoint-update-version",
Expand Down Expand Up @@ -1073,6 +1074,22 @@ def get_custom_fields_metadata(self) -> dict[str, Any]:

return self.get_webapp_data(request_data)

def get_case_ai_summary(self, case_id: int) -> dict:
"""
Retrieves AI-generated summary for a specific case ID.

Args:
case_id (int): The ID of the case to retrieve AI summary for.

Returns:
dict: API response containing case AI summary.
"""
return self._http_request(
method="POST",
url_suffix="/cases/get_ai_case_details",
json_data={"case_id": case_id},
)


def get_appsec_suggestion(client: Client, issue: dict, issue_id: str) -> dict:
"""
Expand Down Expand Up @@ -1640,8 +1657,7 @@ def get_case_extra_data(client, args):
"""
demisto.debug(f"Calling core-get-case-extra-data, {args=}")
# Set the base URL for this API call to use the public API v1 endpoint
client._base_url = "api/webapp/public_api/v1"
case_extra_data = get_extra_data_for_case_id_command(client, args).outputs
case_extra_data = get_extra_data_for_case_id_command(init_client("public"), args).outputs
demisto.debug(f"After calling core-get-case-extra-data, {case_extra_data=}")
issue_ids = extract_ids(case_extra_data)
case_data = case_extra_data.get("case", {})
Expand Down Expand Up @@ -1748,26 +1764,7 @@ def map_case_format(case_list):
return mapped_cases


def get_cases_command(client, args):
"""
Retrieves cases from Cortex platform based on provided filtering criteria.

Args:
client: The Cortex platform client instance for making API requests.
args (dict): Dictionary containing filter parameters including page number,
limits, time ranges, status, severity, and other case attributes.

Returns:
List of mapped case objects containing case details and metadata.
"""
page = arg_to_number(args.get("page")) or 0
limit = arg_to_number(args.get("limit")) or MAX_GET_CASES_LIMIT

limit = page * MAX_GET_CASES_LIMIT + limit
page = page * MAX_GET_CASES_LIMIT

sort_by_modification_time = args.get("sort_by_modification_time")
sort_by_creation_time = args.get("sort_by_creation_time")
def build_get_cases_filter(args: dict) -> FilterBuilder:
since_creation_start_time = args.get("since_creation_time")
since_creation_end_time = datetime.now().strftime("%Y-%m-%dT%H:%M:%S") if since_creation_start_time else None
since_modification_start_time = args.get("since_modification_time")
Expand All @@ -1777,8 +1774,6 @@ def get_cases_command(client, args):
gte_modification_time = args.get("gte_modification_time")
lte_modification_time = args.get("lte_modification_time")

sort_field, sort_order = get_cases_sort_order(sort_by_creation_time, sort_by_modification_time)

status_values = [CaseManagement.STATUS[status] for status in argToList(args.get("status"))]
severity_values = [CaseManagement.SEVERITY[severity] for severity in argToList(args.get("severity"))]
tag_values = [CaseManagement.TAGS.get(tag, tag) for tag in argToList(args.get("tag"))]
Expand Down Expand Up @@ -1852,9 +1847,62 @@ def get_cases_command(client, args):
},
)

return filter_builder


def add_cases_ai_summary(client, cases_list):
"""
Enriches a list of cases with AI-generated summaries.

For each case in the provided list, fetches an AI-generated summary using the case ID
and updates the case's 'description' and 'name' fields with the AI-generated values.

Args:
client: The API client instance used to fetch AI summaries
cases_list (list): List of case dictionaries, each containing at least a 'case_id' field

Returns:
list: The enriched cases list with updated 'description' and 'name' fields where available.
Returns the original list if cases_list is None or empty.

Note:
- If fetching AI summary fails for a case, that case remains unchanged
"""
try:
for case in cases_list:
case_id = case.get("case_id")
case_summary = get_case_ai_summary_command(client, {"case_id": case_id})
if case_summary:
case["description"] = case_summary.outputs.get("case_description")
case["name"] = case_summary.outputs.get("case_name")
except Exception as e:
demisto.debug(str(e))
return cases_list


def get_cases_command(client, args):
"""
Retrieves cases from Cortex platform based on provided filtering criteria.

Args:
client: The Cortex platform client instance for making API requests.
args (dict): Dictionary containing filter parameters including page number,
limits, time ranges, status, severity, and other case attributes.

Returns:
List of mapped case objects containing case details and metadata.
"""

page = arg_to_number(args.get("page")) or 0
limit = arg_to_number(args.get("limit")) or MAX_GET_CASES_LIMIT

limit = page * MAX_GET_CASES_LIMIT + limit
page = page * MAX_GET_CASES_LIMIT

sort_field, sort_order = get_cases_sort_order(args.get("sort_by_creation_time"), args.get("sort_by_modification_time"))
request_data = build_webapp_request_data(
table_name=CASES_TABLE,
filter_dict=filter_builder.to_dict(),
filter_dict=build_get_cases_filter(args).to_dict(),
limit=limit,
sort_field=sort_field,
sort_order=sort_order,
Expand All @@ -1871,14 +1919,15 @@ def get_cases_command(client, args):
filter_count = int(reply.get("FILTER_COUNT", "0"))
returned_count = len(data)

command_results = []

command_results.append(
command_results = [
CommandResults(
outputs_prefix=f"{INTEGRATION_CONTEXT_BRAND}.CasesMetadata",
outputs={"filter_count": filter_count, "returned_count": returned_count},
)
)
]

if filter_count == 1 and returned_count == 1 and data[0].get("issue_count") > 1:
data = add_cases_ai_summary(client, data)

get_enriched_case_data = argToBoolean(args.get("get_enriched_case_data", "false"))
# In case enriched case data was requested
Expand Down Expand Up @@ -4071,6 +4120,87 @@ def xql_query_platform_command(client: Client, args: dict) -> CommandResults:
)


def get_case_ai_summary_command(client: Client, args: dict) -> CommandResults:
"""
Retrieves AI-generated summary for a specific case ID.

Args:
client (Client): The client instance used to send the request.
args (dict): Dictionary containing the arguments for the command.
Expected to include:
- case_id (str): The ID of the case to retrieve AI summary for.

Returns:
CommandResults: Object containing the formatted AI summary data,
raw response, and outputs for integration context.
"""
case_id = arg_to_number(args.get("case_id"))

response = client.get_case_ai_summary(case_id)
if not response:
raise DemistoException(f"Failed to fetch ai summary for case {case_id}. Ensure the asset ID is valid.")

reply = response.get("reply", {})

output = {
"case_id": reply.get("case_id"),
"case_name": reply.get("case_name"),
"case_description": reply.get("case_description"),
}

return CommandResults(
readable_output=tableToMarkdown("Case AI Summary", output, headerTransform=string_to_table_header),
outputs_prefix=f"{INTEGRATION_CONTEXT_BRAND}.CaseAISummary",
outputs_key_field="case_id",
outputs=output,
raw_response=response,
)


def init_client(api_type: str) -> Client:
"""
Initializes the Client for a specific API type.

Args:
api_type (str): The category of the API (e.g., 'public', 'webapp', 'data_platform', etc.)
"""
params = demisto.params()

# Connection parameters
proxy = params.get("proxy", False)
verify_cert = not params.get("insecure", False)

try:
timeout = int(params.get("timeout", 120))
except (ValueError, TypeError):
timeout = 120

# Base URL Mapping logic based on api_type
webapp_root = "/api/webapp"

url_map = {
"webapp": webapp_root,
"public": f"{webapp_root}/public_api/v1",
"data_platform": f"{webapp_root}/data-platform",
"appsec": f"{webapp_root}/public_api/appsec",
"xsoar": "/xsoar",
"agents": f"{webapp_root}/agents"
}

# Fallback to public API if the type isn't recognized
client_url = url_map.get(api_type, url_map["public"])

headers: dict = {"Authorization": params.get("api_key"), "Content-Type": "application/json"}

return Client(
base_url=client_url,
proxy=proxy,
verify=verify_cert,
headers=headers,
timeout=timeout,
)


def main(): # pragma: no cover
"""
Executes an integration command
Expand All @@ -4081,41 +4211,21 @@ def main(): # pragma: no cover
args["integration_context_brand"] = INTEGRATION_CONTEXT_BRAND
args["integration_name"] = INTEGRATION_NAME
remove_nulls_from_dictionary(args)
headers: dict = {}

webapp_api_url = "/api/webapp"
public_api_url = f"{webapp_api_url}/public_api/v1"
data_platform_api_url = f"{webapp_api_url}/data-platform"
appsec_api_url = f"{webapp_api_url}/public_api/appsec"
agents_api_url = f"{webapp_api_url}/agents"
xsoar_api_url = "/xsoar"
proxy = demisto.params().get("proxy", False)
verify_cert = not demisto.params().get("insecure", False)
try:
timeout = int(demisto.params().get("timeout", 120))
except ValueError as e:
demisto.debug(f"Failed casting timeout parameter to int, falling back to 120 - {e}")
timeout = 120

client_url = public_api_url
# Logic to determine which API type the current command belongs to
if command in WEBAPP_COMMANDS:
client_url = webapp_api_url
api_type = "webapp"
elif command in DATA_PLATFORM_COMMANDS:
client_url = data_platform_api_url
api_type = "data_platform"
elif command in APPSEC_COMMANDS:
client_url = appsec_api_url
api_type = "appsec"
elif command in ENDPOINT_COMMANDS:
client_url = agents_api_url
api_type = "agents"
elif command in XSOAR_COMMANDS:
client_url = xsoar_api_url
api_type = "xsoar"
else:
api_type = "public"

client = Client(
base_url=client_url,
proxy=proxy,
verify=verify_cert,
headers=headers,
timeout=timeout,
)
client = init_client(api_type)

try:
if command == "test-module":
Expand Down Expand Up @@ -4219,6 +4329,9 @@ def main(): # pragma: no cover

return_results(xql_query_platform_command(client, args))

elif command == "core-get-case-ai-summary":
return_results(get_case_ai_summary_command(client, args))

except Exception as err:
demisto.error(traceback.format_exc())
return_error(str(err))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1355,6 +1355,24 @@ script:
- contextPath: Core.CaseExtraData.file_artifacts.data.type
description: The type of the file artifact.
type: String
- arguments:
- description: The ID of the case to retrieve AI summary for.
name: case_id
required: true
type: number
name: core-get-case-ai-summary
description: Retrieves an AI-generated summary for a specific case.
hidden: true
outputs:
- contextPath: Core.CaseAISummary.case_id
description: The unique identifier for the case.
type: Number
- contextPath: Core.CaseAISummary.case_name
description: The name of the case.
type: String
- contextPath: Core.CaseAISummary.case_description
description: The AI-generated description of the case.
type: String
- arguments:
- description: Issue ID to update. If empty, updates the current issue ID.
name: id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4599,7 +4599,6 @@ def test_get_case_extra_data_with_all_fields_present(mocker):
args = {"case_id": "123"}
result = get_case_extra_data(mock_client, args)

assert mock_client._base_url == "api/webapp/public_api/v1"
assert result["issue_ids"] == ["issue1", "issue2"]
assert result["network_artifacts"] == [{"id": "net1", "type": "ip"}]
assert result["file_artifacts"] == [{"id": "file1", "hash": "abc123"}]
Expand All @@ -4610,32 +4609,6 @@ def test_get_case_extra_data_with_all_fields_present(mocker):
assert result["detection_time"] == "2023-01-01T00:00:00Z"


def test_get_case_extra_data_client_base_url_modification(mocker):
"""
Given:
A mock client with an original base URL.
When:
The get_case_extra_data function is called.
Then:
The client's base URL should be modified to "api/webapp/public_api/v1".
"""
from CortexPlatformCore import get_case_extra_data

mock_client = mocker.Mock()
original_url = "https://original.api.endpoint"
mock_client._base_url = original_url

mock_command_result = mocker.Mock()
mock_command_result.outputs = {}

mocker.patch("CortexPlatformCore.get_extra_data_for_case_id_command", return_value=mock_command_result)
mocker.patch("CortexPlatformCore.extract_ids", return_value=[])

args = {"case_id": "url_test"}
get_case_extra_data(mock_client, args)

assert mock_client._base_url == "api/webapp/public_api/v1"


def test_add_cases_extra_data_single_case(mocker):
"""
Expand Down
10 changes: 10 additions & 0 deletions Packs/Core/ReleaseNotes/3_4_81.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

- Documentation and metadata improvements.
<!--

#### Integrations

##### Cortex Platform - Core

- Added support for **core-get-case-ai-summary** command that retrieves an ai generated summary for a specific case.
-->
2 changes: 1 addition & 1 deletion Packs/Core/pack_metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "Core",
"description": "Automates incident response",
"support": "xsoar",
"currentVersion": "3.4.80",
"currentVersion": "3.4.81",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
Expand Down
Loading