import demistomock as demisto
from CommonServerPython import *
from CommonServerUserPython import *
""" GLOBALS """
HEADERS = {"Content-Type": "application/json", "Accept": "application/json"}
REQ_SOAP_BODY = """
{auth_token}
{resource_id}
{entryList}
""".format
ENTRY_LIST = "{}".format
ENTRY = "{}".format
COLUMN = "{}".format
BODY = "{}".format
@logger
def int_to_ip(num):
return f"{(num >> 24) & 255}.{(num >> 16) & 255}.{(num >> 8) & 255}.{num & 255}"
@logger
def decode_ip(address_by_bytes):
"""Decodes the enigmatic ways IPs are stored in ArcSight DB into IPv4/6 format"""
str_address_by_bytes = str(address_by_bytes)
if is_ip_valid(str_address_by_bytes) or is_ipv6_valid(str_address_by_bytes):
return address_by_bytes
if isinstance(address_by_bytes, int):
return int_to_ip(address_by_bytes)
try:
# if it's not an int, it should be Base64 encoded string
decoded_string = base64.b64decode(address_by_bytes).hex()
if len(address_by_bytes) >= 20:
# split the IPv6 address into 8 chunks of 4
decoded_string = [decoded_string[i : i + 4] for i in range(0, len(decoded_string), 4)] # type: ignore
return "{}:{}:{}:{}:{}:{}:{}:{}".format(*decoded_string)
elif len(address_by_bytes) >= 6:
decoded_string = int(decoded_string, 16) # type: ignore
return int_to_ip(decoded_string)
else:
return address_by_bytes
except Exception as e:
# sometimes ArcSight would not encode IPs, this will cause the decoder to
# throw an exception, and in turn, we will return the input in its original form.
demisto.debug(str(e))
return address_by_bytes
@logger
def parse_timestamp_to_datestring(timestamp):
if timestamp and timestamp > 0:
try:
return datetime.fromtimestamp(timestamp / 1000.0).strftime("%Y-%m-%dT%H:%M:%S.000Z")
except (ValueError, TypeError) as e:
demisto.debug(str(e))
if timestamp == "31 Dec 1969 19:00:00 EST":
# Unix epoch 00:00:00 UTC
return "None"
return timestamp
return None
@logger
def decode_arcsight_output(d, depth=0, remove_nones=True):
"""Converts some of the values from ArcSight DB into a more useful & readable format"""
# ArcSight stores some None values as follows
NONE_VALUES = [-9223372036854776000, -9223372036854775808, -2147483648, 5e-324]
# ArcSight stores IP addresses as int, in the following keys
IP_FIELDS = ["address", "addressAsBytes", "Destination Address", "Source Address"]
# ArcSight stores Dates as timeStamps in the following keys, -> reformat into Date
TIMESTAMP_FIELDS = [
"createdTimestamp",
"modifiedTimestamp",
"deviceReceiptTime",
"startTime",
"endTime",
"stageUpdateTime",
"modificationTime",
"managerReceiptTime",
"createTime",
"agentReceiptTime",
]
if depth < 10:
if isinstance(d, list):
return [decode_arcsight_output(d_, depth + 1) for d_ in d]
if isinstance(d, dict):
for key, value in d.copy().items():
if isinstance(value, list):
for value_ in value:
decode_arcsight_output(value_, depth + 1)
if isinstance(value, dict):
decode_arcsight_output(value, depth + 1)
elif value in NONE_VALUES:
if remove_nones:
d.pop(key, None)
else:
d[key] = "None"
elif key in IP_FIELDS:
key = "decodedAddress" if key == "addressAsBytes" else key
d[key] = decode_ip(value)
elif key in TIMESTAMP_FIELDS:
key = key.replace("Time", "Date").replace("stamp", "")
d[key] = parse_timestamp_to_datestring(value)
elif key in ["eventId", "baseEventIds"]:
d[key] = str(value)
elif isinstance(value, int) and value > 10000000000000000:
# the platform rounds number larger than 10000000000000000
# so we cast them to string to keep as is
d[key] = str(value)
elif isinstance(value, bytes):
d[key] = value.decode()
return d
@logger
def filter_entries(entries, entry_filter):
"""Filters the entries according to the entry_filter given"""
if not entry_filter:
return entries
filtered_entries = []
filters = entry_filter.split(",")
for entry in entries:
append_flag = True
for f in filters:
k, v = f.split(":")
if k:
if entry.get(k) != v:
# if there is a key and its value is not equal to the entry_filter value
append_flag = False
elif v not in entry.values():
# if there is no key check that the value exists in one of the entry's keys
append_flag = False
if append_flag:
filtered_entries.append(entry)
return filtered_entries
def repair_malformed_json(malformed_json: str) -> str:
"""
Repairs a malformed JSON string by properly escaping quotes within dollar-sign ($) values.
This function addresses a specific issue where quotes within dollar-sign values are not
properly escaped, causing JSON parsing errors. It splits the input string into segments,
identifies dollar-sign values, and escapes inner quotes while preserving the outermost quotes.
Args:
malformed_json (str): The input string containing malformed JSON data.
Returns:
str: A repaired JSON string with properly escaped quotes within dollar-sign values.
Example:
>>> malformed_json = '{"$": "value "with" quotes"}, {"$": "another "quoted" value"}'
>>> repaired_json = repair_malformed_json(malformed_json)
>>> print(repaired_json)
'{"$": "value \\"with\\" quotes"}, {"$": "another \\"quoted\\" value"}'
"""
def find_unescaped_quotes(json_value: str) -> List[int]:
quote_positions = []
search_start = 0
while True:
quote_pos = json_value.find('"', search_start)
if quote_pos == -1: # No more occurrences found
break
# Check if the quote is already escaped
if quote_pos == 0 or json_value[quote_pos - 1] != "\\":
quote_positions.append(quote_pos)
search_start = quote_pos + 1 # Move start position to just after the found index
return quote_positions
def escape_inner_quotes(json_value, quote_indices):
# We need to escape all quotes except the first and last found quotes
json_chars = list(json_value)
for i in range(1, len(quote_indices) - 1): # Skip first and last quotes
json_chars[quote_indices[i]] = '\\"'
return "".join(json_chars)
# Split the text by "},{"
parts = malformed_json.split("},{")
modified_parts = []
for part in parts:
# Split by "$": and get the last part
dollar_key_parts = part.split('"$":')
if len(dollar_key_parts) > 1:
prefix = '"$":'.join(dollar_key_parts[:-1]) + '"$":' # Keep all parts before the last `"$":`
json_value = dollar_key_parts[-1].strip() # Get the last value
quote_positions = find_unescaped_quotes(json_value)
if len(quote_positions) > 2:
json_value = escape_inner_quotes(json_value, quote_positions)
modified_parts.append(prefix + json_value)
else:
modified_parts.append(part)
# Join the parts back together
return "},{".join(modified_parts)
def login():
query_path = "www/core-service/rest/LoginService/login"
headers = {"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}
params = {
"login": demisto.get(demisto.params(), "credentials.identifier"),
"password": demisto.get(demisto.params(), "credentials.password"),
"alt": "json",
}
res = send_request(query_path, headers=headers, body=params, is_login=True)
if not res.ok:
demisto.debug(res.text)
return_error("Failed to login, check integration parameters.")
try:
res_json = parse_json_response(res)
if "log.loginResponse" in res_json and "log.return" in res_json.get("log.loginResponse"):
auth_token = res_json.get("log.loginResponse").get("log.return")
if demisto.command() not in ["test-module", "fetch-incidents"]:
# this is done to bypass setting integration context outside of the cli
demisto.setIntegrationContext({"auth_token": auth_token})
return auth_token
return_error("Failed to login. Have not received token after login")
except ValueError:
return_error("Failed to login. Please check integration parameters")
def send_request(query_path, body=None, params=None, json=None, headers=None, method="post", is_login=False):
if headers is None:
headers = HEADERS
full_url = BASE_URL + query_path
try:
res = requests.request(method, full_url, headers=headers, verify=VERIFY_CERTIFICATE, data=body, params=params, json=json)
if not res.ok and not is_login:
if params and not body:
params["authToken"] = login()
elif "Authorization" in headers:
headers["Authorization"] = f"Bearer {login()}"
else:
body = body.replace(demisto.getIntegrationContext().get("auth_token"), login())
return requests.request(
method, full_url, headers=headers, verify=VERIFY_CERTIFICATE, data=body, params=params, json=json
)
return res
except Exception as ex:
demisto.debug(str(ex))
return_error("Connection Error. Please check integration parameters")
def test():
"""
Login (already done in global).
Test if fetch query viewers are valid.
Run query viewer if fetch defined.
"""
events_query_viewer_id = demisto.params().get("viewerId")
cases_query_viewer_id = demisto.params().get("casesQueryViewerId")
is_fetch = demisto.params().get("isFetch")
if is_fetch and not events_query_viewer_id and not cases_query_viewer_id:
return_error("If fetch is enabled, you must provide query viewer Resource ID for Cases or Events")
if events_query_viewer_id:
fields, results = get_query_viewer_results(events_query_viewer_id)
if "Event ID" not in fields or "Start Time" not in fields:
return_error(f'Query "{events_query_viewer_id}" must contain "Start Time" and "Event ID" fields')
if cases_query_viewer_id:
fields, results = get_query_viewer_results(cases_query_viewer_id)
if "ID" not in fields or "Create Time" not in fields:
return_error(f'Query "{cases_query_viewer_id}" must contain "Create Time" and "ID" fields')
@logger
def get_query_viewer_results(query_viewer_id):
query_path = "www/manager-service/rest/QueryViewerService/getMatrixData"
params = {"authToken": AUTH_TOKEN, "id": query_viewer_id, "alt": "json"}
res = send_request(query_path, params=params, method="get")
if not res.ok:
demisto.debug(res.text)
if "ResourceNotFoundException" in res.text:
return_error(f"Invalid resource ID {query_viewer_id} for Query Viewer(ResourceNotFoundException)")
else:
return_error("Failed to get query viewer results.")
return_object = None
res_json = parse_json_response(res)
if "qvs.getMatrixDataResponse" in res_json and "qvs.return" in res_json["qvs.getMatrixDataResponse"]:
# ArcSight ESM version 6.7 & 6.9 rest API supports qvs.getMatrixDataResponse
return_object = res_json.get("qvs.getMatrixDataResponse").get("qvs.return")
elif "que.getMatrixDataResponse" in res_json and "que.return" in res_json["que.getMatrixDataResponse"]:
# ArcSight ESM version 6.1 rest API supports que.getMatrixDataResponse
return_object = res_json.get("que.getMatrixDataResponse").get("que.return")
else:
return_error("Invalid response structure. Open ticket to Demisto support and attach the logs")
return None
fields = return_object.get("columnHeaders", [])
if not isinstance(fields, list):
fields = [fields]
results = return_object.get("rows", [])
if not isinstance(results, list):
results = [results]
if len(fields) == 0 or len(results) == 0:
return fields, results
"""
we parse the rows by column headers and create formatted result
"columnHeaders": [
"ID",
"Event-Event ID",
],
"rows": [
{
"@xsi.type": "listWrapper",
"value": [
{
"@xsi.type": "xs:string",
"$": ""
},
{
"@xsi.type": "xs:string",
"$": ""
}
]
},
{
"@xsi.type": "listWrapper",
"value": [
{
"@xsi.type": "xs:string",
"$": ""
},
{
"@xsi.type": "xs:string",
"$": ""
}
]
}
]
convert to ===>
query_results = [
{
Event-Event ID:
ID:
},
{
Event-Event ID:
ID:
}
]
"""
results = [{field: result.get("value")[idx].get("$") for idx, field in enumerate(fields)} for result in results]
return fields, results
@logger
def get_query_viewer_results_command():
resource_id = demisto.args().get("id")
only_columns = demisto.args().get("onlyColumns")
columns, query_results = get_query_viewer_results(query_viewer_id=resource_id)
demisto.debug("printing Query Viewer column headers")
demisto.results(
{
"Type": entryTypes["note"],
"ContentsFormat": formats["json"],
"Contents": columns,
"HumanReadable": tableToMarkdown(name="", headers="Column Headers", t=columns, removeNull=True),
}
)
if only_columns == "false":
demisto.debug("printing Query Viewer results")
contents = query_results
human_readable = tableToMarkdown(name=f"Query Viewer Results: {resource_id}", t=contents, removeNull=True)
outputs = {"ArcSightESM.QueryViewerResults": contents}
return_outputs(readable_output=human_readable, outputs=outputs, raw_response=contents)
@logger
def fetch():
"""
Query viewer should be defined in ArcSight ESM. fetch incidents fetches the results of query viewer
and converts them to Demisto incidents. We can query Cases or Events. If Cases are fetched then the
query viewer query must return fields ID and Create Time. If Events are fetched then Event ID and Start Time.
"""
events_query_viewer_id = demisto.params().get("viewerId")
cases_query_viewer_id = demisto.params().get("casesQueryViewerId")
type_of_incident = "case" if events_query_viewer_id else "event"
last_run = json.loads(demisto.getLastRun().get("value", "{}"))
already_fetched = last_run.get("already_fetched", [])
fields, query_results = get_query_viewer_results(events_query_viewer_id or cases_query_viewer_id)
# sort query_results by creation time
query_results.sort(key=lambda k: int(k.get("Start Time") or k.get("Create Time")))
incidents = []
for result in query_results:
# convert case or event to demisto incident
r_id = result.get("ID") or result.get("Event ID")
if r_id not in already_fetched:
create_time_epoch = int(result.get("Start Time") or result.get("Create Time"))
result["Create Time"] = parse_timestamp_to_datestring(create_time_epoch)
incident_name = result.get("Name") or f"New {type_of_incident} from arcsight at {datetime.now()}"
labels = [
{"type": key.encode("utf-8"), "value": value.encode("utf-8") if value else value} for key, value in result.items()
]
incident = {"name": incident_name, "occurred": result["Create Time"], "labels": labels, "rawJSON": json.dumps(result)}
incidents.append(incident)
if len(already_fetched) > MAX_UNIQUE:
already_fetched.pop(0)
already_fetched.append(r_id)
if len(incidents) >= FETCH_CHUNK_SIZE:
break
last_run = {
"already_fetched": already_fetched,
}
demisto.setLastRun({"value": json.dumps(last_run)})
decode_arcsight_output(incidents)
if demisto.command() == "as-fetch-incidents":
contents = {
"last_run": last_run,
"last_run_updated": demisto.getLastRun(),
"incidents": incidents,
"already_fetched": already_fetched,
}
return_outputs(readable_output="", outputs={}, raw_response=contents)
else:
demisto.incidents(incidents)
@logger
def get_case(resource_id, fetch_base_events=False):
query_path = "www/manager-service/rest/CaseService/getResourceById"
params = {
"authToken": AUTH_TOKEN,
"resourceId": resource_id,
}
res = send_request(query_path, params=params, method="get")
if not res.ok:
demisto.debug(res.text)
if "InvalidResourceIDException: Invalid resource ID" in res.text and "for Case" in res.text:
return_error(f"Invalid resource ID {resource_id} for Case")
else:
return_error(f"Failed to get case. StatusCode: {res.status_code}")
res_json = parse_json_response(res)
if "cas.getResourceByIdResponse" in res_json and "cas.return" in res_json.get("cas.getResourceByIdResponse"):
case = res_json.get("cas.getResourceByIdResponse").get("cas.return")
if case.get("eventIDs") and not isinstance(case["eventIDs"], list):
# if eventIDs is single id then convert to list
case["eventIDs"] = [case["eventIDs"]]
if case.get("eventIDs") and fetch_base_events:
case["events"] = decode_arcsight_output(get_security_events(case["eventIDs"], ignore_empty=True), remove_nones=False)
return case
return_error(f"Case {resource_id} not found")
return None
@logger
def get_case_command():
resource_id = demisto.args().get("resourceId")
with_base_events = demisto.args().get("withBaseEvents") == "true"
raw_case = get_case(resource_id, fetch_base_events=with_base_events)
case = {
"Name": raw_case.get("name"),
"EventIDs": raw_case.get("eventIDs"),
"Action": raw_case.get("action"),
"Stage": raw_case.get("stage"),
"CaseID": raw_case.get("resourceid"),
"Severity": raw_case.get("consequenceSeverity"),
"CreatedTime": epochToTimestamp(raw_case.get("createdTimestamp")),
}
if with_base_events:
case["events"] = raw_case.get("events")
contents = decode_arcsight_output(raw_case)
if contents.get("events"):
contents["events"] = decode_arcsight_output(contents["events"])
human_readable = tableToMarkdown(name=f"Case {resource_id}", t=case, removeNull=True)
outputs = {"ArcSightESM.Cases(val.resourceid===obj.resourceid)": contents}
return_outputs(readable_output=human_readable, outputs=outputs, raw_response=contents)
@logger
def get_all_cases_command():
query_path = "www/manager-service/rest/CaseService/findAllIds"
params = {"authToken": AUTH_TOKEN, "alt": "json"}
res = send_request(query_path, params=params, method="get")
if not res.ok:
demisto.debug(res.text)
return_error(f"Failed to get case list. StatusCode: {res.status_code}")
res_json = parse_json_response(res)
contents = res_json.get("cas.findAllIdsResponse").get("cas.return")
human_readable = tableToMarkdown(name="All cases", headers="caseID", t=contents, removeNull=True)
outputs = {"ArcSightESM.AllCaseIDs": contents}
return_outputs(readable_output=human_readable, outputs=outputs, raw_response=contents)
@logger
def get_security_events_command():
ids = demisto.args().get("ids")
last_date_range = demisto.args().get("lastDateRange")
ids = argToList(str(ids) if isinstance(ids, int) else ids)
raw_events = get_security_events(ids, last_date_range)
if raw_events:
events = []
contents = decode_arcsight_output(raw_events)
for raw_event in contents:
event = {
"Event ID": raw_event.get("eventId"),
"Time": timestamp_to_datestring(raw_event.get("endTime"), "%Y-%m-%d, %H:%M:%S"),
"Source Address": decode_ip(demisto.get(raw_event, "source.address")),
"Destination Address": decode_ip(demisto.get(raw_event, "destination.address")),
"Name": raw_event.get("name"),
"Source Port": demisto.get(raw_event, "source.port"),
"Base Event IDs": raw_event.get("baseEventIds"),
}
events.append(event)
human_readable = tableToMarkdown("Security Event: {}".format(",".join(map(str, ids))), events, removeNull=True)
outputs = {"ArcSightESM.SecurityEvents(val.eventId===obj.eventId)": contents}
return_outputs(human_readable, outputs, contents)
else:
demisto.results("No events were found")
@logger
def get_security_events(event_ids, last_date_range=None, ignore_empty=False):
start_time, end_time = -1, -1
if last_date_range:
# Must of format 'number date_range_unit'
# Examples: (2 hours, 4 minutes, 6 month, 1 day, etc.)
start_time, end_time = parse_date_range(last_date_range, to_timestamp=True)
query_path = "www/manager-service/rest/SecurityEventService/getSecurityEvents"
params = {"alt": "json"}
json_ = {
"sev.getSecurityEvents": {
"sev.authToken": AUTH_TOKEN,
"sev.ids": event_ids,
"sev.startMillis": start_time,
"sev.endMillis": end_time,
}
}
res = send_request(query_path, json=json_, params=params)
if not res.ok:
demisto.debug(res.text)
return_error(
f"Failed to get security events with ids {event_ids}.\n"
f"Full URL: {BASE_URL + query_path}\nStatus Code: {res.status_code}\nResponse Body: {res.text}"
)
res_json = parse_json_response(res)
if res_json.get("sev.getSecurityEventsResponse") and res_json.get("sev.getSecurityEventsResponse").get("sev.return"):
events = res_json.get("sev.getSecurityEventsResponse").get("sev.return")
return events if isinstance(events, list) else [events]
demisto.debug(res.text)
if not ignore_empty:
demisto.results("No events were found")
return None
return None
@logger
def update_case_command():
case_id = demisto.args().get("caseId")
stage = demisto.args().get("stage")
severity = demisto.args().get("severity")
raw_updated_case = update_case(case_id, stage, severity)
updated_case = {
"Name": raw_updated_case.get("name"),
"EventIDs": raw_updated_case.get("eventIDs"),
"Action": raw_updated_case.get("action"),
"Stage": raw_updated_case.get("stage"),
"CaseID": raw_updated_case.get("resourceid"),
"Severity": raw_updated_case.get("consequenceSeverity"),
"CreatedTime": epochToTimestamp(raw_updated_case.get("createdTimestamp")),
}
contents = decode_arcsight_output(raw_updated_case)
human_readable = tableToMarkdown(name=f"Case {case_id}", t=updated_case, removeNull=True)
outputs = {"ArcSightESM.Cases(val.resourceid===obj.resourceid)": contents}
return_outputs(readable_output=human_readable, outputs=outputs, raw_response=contents)
@logger
def update_case(case_id, stage, severity):
# get the case from arcsight
case = get_case(case_id)
case["stage"] = stage
case["consequenceSeverity"] = severity if severity else case["consequenceSeverity"]
# update its stage and send it back to arcsight
query_path = "www/manager-service/rest/CaseService/update"
params = {"alt": "json"}
json_ = {
"cas.update": {
"cas.authToken": AUTH_TOKEN,
"cas.resource": case,
}
}
res = send_request(query_path, json=json_, params=params)
if not res.ok:
demisto.debug(res.text)
return_error(
f"Failed to get security update case {case_id}. \nPlease make sure user have edit permissions,"
f" or case is unlocked. \nStatus Code: {res.status_code}\nResponse Body: {res.text}"
)
res_json = parse_json_response(res)
if "cas.updateResponse" in res_json and "cas.return" in res_json.get("cas.updateResponse"):
return case
return_error(f"Failed to update case, fail to parse response. Response Body: {res.text}")
return None
@logger
def get_correlated_events_ids(event_ids):
related_ids = set(event_ids)
correlated_events = decode_arcsight_output(get_security_events(event_ids, ignore_empty=True))
if correlated_events:
for raw_event in correlated_events:
base_event_ids = raw_event.get("baseEventIds")
if base_event_ids:
if isinstance(base_event_ids, list):
related_ids.update(base_event_ids)
else:
related_ids.add(base_event_ids)
return list(related_ids)
@logger
def get_case_event_ids_command():
case_id = demisto.args().get("caseId")
with_correlated_events = demisto.args().get("withCorrelatedEvents") == "true"
query_path = "www/manager-service/rest/CaseService/getCaseEventIDs"
params = {"authToken": AUTH_TOKEN, "caseId": case_id}
res = send_request(query_path, params=params, method="get")
if not res.ok:
demisto.debug(res.text)
return_error(f"Failed to get Event IDs with:\nStatus Code: {res.status_code}\nResponse: {res.text}")
res_json = parse_json_response(res)
if "cas.getCaseEventIDsResponse" in res_json and "cas.return" in res_json.get("cas.getCaseEventIDsResponse"):
event_ids = res_json.get("cas.getCaseEventIDsResponse").get("cas.return")
if not isinstance(event_ids, list):
event_ids = [event_ids]
if with_correlated_events:
event_ids = get_correlated_events_ids(event_ids)
contents = decode_arcsight_output(res_json)
human_readable = tableToMarkdown(name="", headers=f"Case {case_id} Event IDs", t=event_ids, removeNull=True)
outputs = {"ArcSightESM.CaseEvents": event_ids}
return_outputs(readable_output=human_readable, outputs=outputs, raw_response=contents)
else:
demisto.results("No result returned")
@logger
def delete_case_command():
case_id = demisto.args().get("caseId")
query_path = "www/manager-service/rest/CaseService/deleteByUUID"
req_body = json.dumps({"cas.deleteByUUID": {"cas.authToken": AUTH_TOKEN, "cas.id": case_id}})
params = {"alt": "json"}
res = send_request(query_path, params=params, body=req_body)
if not res.ok:
demisto.debug(res.text)
return_error(f"Failed to delete case.\nStatus Code: {res.status_code}\nResponse: {res.text}")
entry_context = {"resourceid": case_id, "deleted": "True"}
contents = f"Case {case_id} was deleted successfully"
human_readable = f"Case {case_id} successfully deleted"
outputs = {"ArcSightESM.Cases(val.resourceid===obj.resourceid)": entry_context}
return_outputs(readable_output=human_readable, outputs=outputs, raw_response=contents)
@logger
def get_entries_command(use_rest, args):
resource_id = args.get("resourceId")
entry_filter = args.get("entryFilter")
if use_rest:
query_path = "www/manager-service/rest/ActiveListService/getEntries"
params = {"alt": "json"}
body = {
"act.getEntries": {
"act.authToken": AUTH_TOKEN,
"act.resourceId": resource_id,
}
} # type: Union[str, Dict[str, Dict[str, Any]]]
res = send_request(query_path, json=body, params=params)
else:
query_path = "www/manager-service/services/ActiveListService/"
body = REQ_SOAP_BODY(function="getEntries", auth_token=AUTH_TOKEN, resource_id=resource_id, entryList=None)
res = send_request(query_path, body=body)
if not res.ok:
demisto.debug(res.text)
return_error(
f"Failed to get entries:\nResource ID: {resource_id}\n"
f"Status Code: {res.status_code}\nRequest Body: {body}\nResponse: {res.text}"
)
if use_rest:
res_json = parse_json_response(res)
raw_entries = res_json.get("act.getEntriesResponse", {}).get("act.return", {})
else:
res_json = json.loads(xml2json((res.text).encode("utf-8")))
raw_entries = demisto.get(res_json, "Envelope.Body.getEntriesResponse.return")
# retrieve columns
cols = demisto.get(raw_entries, "columns")
if cols:
hr_columns = (
tableToMarkdown(name="", headers=["Columns"], t=cols, removeNull=True) if cols else "Active list has no columns"
)
contents = cols
return_outputs(readable_output=hr_columns, outputs={}, raw_response=contents)
if "entryList" in raw_entries:
entry_list = raw_entries["entryList"] if isinstance(raw_entries["entryList"], list) else [raw_entries["entryList"]]
entry_list = [
(d["entry"] if not isinstance(d["entry"], STRING_TYPES) else (d["entry"],)) for d in entry_list if "entry" in d
]
keys = raw_entries.get("columns")
entries = [dict(zip(keys, values)) for values in entry_list]
# if the user wants only entries that contain certain 'field:value' sets (filters)
# e.g., "name:myName,eventId:0,:ValueInUnknownField"
# if the key is empty, search in every key
filtered_entries = filter_entries(entries, entry_filter)
contents = decode_arcsight_output(filtered_entries)
ActiveListContext = {
"ResourceID": resource_id,
"Entries": contents,
}
outputs = {
f"ArcSightESM.ActiveList.{resource_id}": contents,
"ArcSightESM.ActiveList(val.ResourceID===obj.ResourceID)": ActiveListContext,
}
human_readable = tableToMarkdown(name=f"Active List entries: {resource_id}", t=filtered_entries, removeNull=True)
return_outputs(readable_output=human_readable, outputs=outputs, raw_response=entries)
else:
demisto.results("Active List has no entries")
@logger
def clear_entries_command(use_rest, args):
resource_id = args.get("resourceId")
if use_rest:
query_path = "www/manager-service/rest/ActiveListService/clearEntries"
params = {"alt": "json"}
body = {
"act.clearEntries": {
"act.authToken": AUTH_TOKEN,
"act.resourceId": resource_id,
}
} # type: Union[str, Dict[str, Dict[str, Any]]]
res = send_request(query_path, json=body, params=params)
else:
query_path = "www/manager-service/services/ActiveListService/"
body = REQ_SOAP_BODY(function="clearEntries", auth_token=AUTH_TOKEN, resource_id=resource_id, entryList=None)
res = send_request(query_path, body=body)
if not res.ok:
demisto.debug(res.text)
return_error(
f"Failed to clear entries.\nResource ID: {resource_id}\n"
f"Status Code: {res.status_code}\nRequest Body: {body}\nResponse: {res.text}"
)
demisto.results("Success")
@logger
def entries_command(func):
resource_id = demisto.args().get("resourceId")
entries = demisto.args().get("entries")
query_path = "www/manager-service/services/ActiveListService/"
if not isinstance(entries, dict):
try:
entries = json.loads(entries)
except ValueError as ex:
demisto.debug(str(ex))
return_error("Entries must be in JSON format. Must be array of objects.")
if not all(entry.keys() == entries[0].keys() for entry in entries[1:]):
return_error("All entries must have the same fields")
columns = "".join(COLUMN(column) for column in entries[0]) # the fields in the entryList matrix are the columns
entry_list = BODY(columns + "".join(ENTRY_LIST("".join(ENTRY(v) for v in en.values())) for en in entries))
body = REQ_SOAP_BODY(function=func, auth_token=AUTH_TOKEN, resource_id=resource_id, entryList=entry_list)
res = send_request(query_path, body=body)
if not res.ok:
demisto.debug(res.text)
return_error(
f"Failed to {func}. Please make sure to enter Active List resource ID"
f"\nResource ID: {resource_id}\nStatus Code: {res.status_code}\nRequest Body: {body}\nResponse: {res.text}"
)
demisto.results("Success")
def add_entries_command(args):
resource_id = args.get("resourceId")
entries_arg = args.get("entries")
query_path = f"detect-api/rest/activelists/{resource_id}/entries"
fields = []
entries = []
if not isinstance(entries_arg, list):
try:
entries_arg = json.loads(entries_arg)
except ValueError as ex:
demisto.error(str(ex))
raise ValueError("entries must be in JSON format. Must be array of objects.")
if len(entries_arg) > 0:
if len(entries_arg) > 0:
fields = list(entries_arg[0].keys())
if not all(entry.keys() == entries_arg[0].keys() for entry in entries_arg[1:]):
return_error("All entries must have the same fields")
for entry_input in entries_arg:
entry = []
for k in fields:
val = entry_input[k]
entry.append(val)
entries.append({"fields": entry})
body = {"fields": fields, "entries": entries}
headers = HEADERS
headers["Authorization"] = f"Bearer {AUTH_TOKEN}"
body = json.dumps(body)
res = send_request(query_path, body=body)
if not res.ok:
raise ValueError(
"Failed to add entries. Please make sure to enter Active List resource ID"
f"\nResource ID: {resource_id}\nStatus Code: {res.status_code}\n"
f"Request Body: {body}\nResponse: {res.text}"
)
demisto.results("Success")
@logger
def get_all_query_viewers_command():
query_path = "www/manager-service/rest/QueryViewerService/findAllIds"
params = {"authToken": AUTH_TOKEN, "alt": "json"}
headers = {"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}
res = send_request(query_path, headers=headers, params=params)
if not res.ok:
demisto.debug(res.text)
return_error(f"Failed to get query viewers:\nStatus Code: {res.status_code}\nResponse: {res.text}")
res_json = parse_json_response(res)
if "qvs.findAllIdsResponse" in res_json and "qvs.return" in res_json.get("qvs.findAllIdsResponse"):
query_viewers = res_json.get("qvs.findAllIdsResponse").get("qvs.return")
contents = decode_arcsight_output(query_viewers)
outputs = {"ArcSightESM.AllQueryViewers": contents}
human_readable = tableToMarkdown(name="", t=query_viewers, headers="Query Viewers", removeNull=True)
return_outputs(readable_output=human_readable, outputs=outputs, raw_response=contents)
else:
demisto.results("No Query Viewers were found")
def parse_json_response(response: requests.Response):
"""
Parse the response to JSON.
If the parsing fails due to an invalid escape sequence, the function will attempt to fix the response data.
Args:
response: The response to parse.
Raises:
JSONDecodeError: If the response data could not be parsed to JSON.
"""
try:
return response.json()
except requests.exceptions.JSONDecodeError as e:
demisto.debug(
f"Failed to parse response to JSON.\n"
f"HTTP status code: {response.status_code}\n"
f"Headers: {response.headers}\n"
f"Response:\n{response.text}\n\n"
"Attempting to fix invalid escape sequences and parse the response again."
)
# Replace triple backslashes (where the last one doesn't escape anything) with two backslashes.
fixed_response_text = re.sub(r'(?