import demistomock as demisto from CommonServerPython import * from CommonServerUserPython import * """ IMPORTS """ import json import requests import traceback from datetime import datetime, timedelta import os import urllib3 # Disable insecure warnings urllib3.disable_warnings() """ GLOBALS/PARAMS """ FETCHES_INCIDENTS = "" FETCH_TIME = "" FETCH_ATTACHMENTS = "" OBJECTS_TO_FETCH = "" MAX_RESULT = "" USERNAME = "" PASSWORD = "" SERVER = "" SECURED = False CLIENT_ID = "" QUERY_STRING = "" DATE_FORMAT = "" BASE_URL = "" HTTP_CODES = {"unauthorized": 401, "internal_server_error": 500, "success": 200} HEADERS = {"Content-Type": "application/json", "Accept": "application/json"} QUERY_OPERATORS = ["eq", "gt", "lt", "contains", "startswith"] ONE_STEP_ACTION_HEADERS = ["name", "displayName", "description", "id", "association", "standInKey"] ####################################################################################################################### """ HELPER FUNCTIONS """ def parse_response(response, error_operation, file_content=False, is_fetch=False): try: response.raise_for_status() if not response.content: return None if file_content: return response.content else: return response.json() except requests.exceptions.HTTPError: try: res_json = response.json() err_msg = res_json.get("errorMessage") or res_json.get("error_description") or res_json.get("Message") except Exception: err_msg = response.content.decode("utf-8") raise_or_return_error(error_operation + ": " + str(err_msg), is_fetch) except Exception as error: raise_or_return_error(f"Could not parse response {error}", is_fetch) def cherwell_dict_parser(key, value, item_list): new_dict = {} for item in item_list: field_key = item.get(key) new_dict[field_key] = item.get(value) return new_dict def parse_fields_from_business_object(field_list): new_business_obj = cherwell_dict_parser("name", "value", field_list) return new_business_obj def parse_fields_from_business_object_list(response): object_list = [] if not response.get("businessObjects"): return [] for business_obj in response.get("businessObjects"): new_business_obj = parse_fields_from_business_object(business_obj.get("fields")) new_business_obj["BusinessObjectId"] = business_obj.get("busObId") new_business_obj["PublicId"] = business_obj.get("busObPublicId") new_business_obj["RecordId"] = business_obj.get("busObRecId") object_list.append(new_business_obj) return object_list def build_fields_for_business_object(data_dict, ids_dict): fields = [] for key, value in data_dict.items(): new_field = {"dirty": "true", "fieldId": ids_dict.get(key), "name": key, "value": value} fields.append(new_field) return fields def http_request(method, url, payload, token=None, custom_headers=None, is_fetch=False): headers = build_headers(token, custom_headers) try: response = requests.request(method, url, data=payload, headers=headers, verify=SECURED) except requests.exceptions.ConnectionError as e: err_message = f"Error connecting to server. Check your URL/Proxy/Certificate settings: {e}" raise_or_return_error(err_message, is_fetch) return response def request_new_access_token(using_refresh): url = BASE_URL + "token" refresh_token = demisto.getIntegrationContext().get("refresh_token") if using_refresh: payload = f"client_id={CLIENT_ID}&grant_type=refresh_token&refresh_token={refresh_token}" else: payload = f"client_id={CLIENT_ID}&grant_type=password&username={USERNAME}&password={PASSWORD}" headers = { "Accept": "application/json", "Content-Type": "application/x-www-form-urlencoded", } response = http_request("POST", url, payload, custom_headers=headers) return response def get_new_access_token(is_fetch=False): response = request_new_access_token(True) if response.status_code != HTTP_CODES["success"]: response = request_new_access_token(False) res_json = parse_response( response, "Could not get token. Check your credentials (user/password/client id) and try again", is_fetch=is_fetch ) token_expiration_time = int(date_to_timestamp(res_json.get(".expires"), "%a, %d %b %Y %H:%M:%S GMT")) demisto.setIntegrationContext( { "refresh_token": res_json.get("refresh_token"), "token_expiration_time": token_expiration_time, "access_token": res_json.get("access_token"), } ) return res_json.get("access_token") def get_access_token(new_token, is_fetch=False): integration_context = demisto.getIntegrationContext() token_expiration_time = integration_context.get("token_expiration_time") current_time = date_to_timestamp(datetime.utcnow()) if new_token or not token_expiration_time or token_expiration_time < current_time: token = get_new_access_token(is_fetch=is_fetch) return token else: return integration_context.get("access_token") def build_headers(token, headers=None): headers = headers if headers else HEADERS headers["Authorization"] = f"Bearer {token}" return headers def make_request(method, url, payload=None, headers=None, is_fetch=False): token = get_access_token(False, is_fetch=is_fetch) response = http_request(method, url, payload, token, custom_headers=headers, is_fetch=is_fetch) if response.status_code == HTTP_CODES["unauthorized"]: token = get_access_token(True, is_fetch=is_fetch) response = http_request(method, url, payload, token, custom_headers=headers, is_fetch=is_fetch) return response def get_business_object_summary_by_name(name, is_fetch=False): url = BASE_URL + f"api/V1/getbusinessobjectsummary/busobname/{name}" response = make_request("GET", url, is_fetch=is_fetch) return parse_response(response, "Could not get business object summary", is_fetch=is_fetch) def get_business_object_summary_by_id(_id, is_fetch=False): url = BASE_URL + f"api/V1/getbusinessobjectsummary/busobid/{_id}" response = make_request("GET", url, is_fetch=is_fetch) return parse_response(response, "Could not get business object summary", is_fetch=is_fetch) def resolve_business_object_id_by_name(name, is_fetch=False): res = get_business_object_summary_by_name(name, is_fetch) if not res: err_message = f'Could not retrieve "{name}" business object id. Make sure "{name}" is a valid business object.' raise_or_return_error(err_message, is_fetch) return res[0].get("busObId") def save_business_object(payload): url = BASE_URL + "api/V1/savebusinessobject" response = make_request("POST", url, json.dumps(payload)) return parse_response(response, "Could not save business object") def get_business_object_record(business_object_id, object_id, id_type): id_type_str = "publicid" if id_type == "public_id" else "busobrecid" url = BASE_URL + f"api/V1/getbusinessobject/busobid/{business_object_id}/{id_type_str}/{object_id}" response = make_request("GET", url) return parse_response(response, "Could not get business objects") def delete_business_object_record(business_object_id, object_id, id_type): id_type_str = "publicid" if id_type == "public_id" else "busobrecid" url = BASE_URL + f"api/V1/deletebusinessobject/busobid/{business_object_id}/{id_type_str}/{object_id}" response = make_request("DELETE", url) return parse_response(response, "Could not delete business object") def get_search_results(payload, is_fetch=False): url = BASE_URL + "api/V1/getsearchresults" response = make_request("POST", url, json.dumps(payload)) return parse_response(response, "Could not search for business objects", is_fetch=is_fetch) def get_business_object_template(business_object_id, include_all=True, field_names=None, fields_ids=None, is_fetch=False): url = BASE_URL + "api/V1/getbusinessobjecttemplate" payload = {"busObId": business_object_id, "includeAll": include_all} if field_names: payload["fieldNames"] = field_names if fields_ids: payload["fieldIds"] = fields_ids response = make_request("POST", url, json.dumps(payload), is_fetch=is_fetch) return parse_response(response, "Could not get business object template", is_fetch=is_fetch) def build_business_object_json(simple_json, business_object_id, object_id=None, id_type=None): business_object_ids_dict = get_key_value_dict_from_template("name", "fieldId", business_object_id) fields_for_business_object = build_fields_for_business_object(simple_json, business_object_ids_dict) business_object_json = {"busObId": business_object_id, "fields": fields_for_business_object} if object_id: id_key = "busObPublicId" if id_type == "public_id" else "busObRecId" business_object_json[id_key] = object_id return business_object_json def create_business_object(name, data_json): business_object_id = resolve_business_object_id_by_name(name) business_object_json = build_business_object_json(data_json, business_object_id) return save_business_object(business_object_json) def update_business_object(name, data_json, object_id, id_type): business_object_id = resolve_business_object_id_by_name(name) business_object_json = build_business_object_json(data_json, business_object_id, object_id, id_type) return save_business_object(business_object_json) def get_business_object(name, object_id, id_type): business_object_id = resolve_business_object_id_by_name(name) results = get_business_object_record(business_object_id, object_id, id_type) parsed_business_object = parse_fields_from_business_object(results.get("fields")) parsed_business_object["PublicId"] = results.get("busObPublicId") parsed_business_object["RecordId"] = results.get("busObRecId") return parsed_business_object, results def delete_business_object(name, object_id, id_type): business_object_id = resolve_business_object_id_by_name(name) return delete_business_object_record(business_object_id, object_id, id_type) def download_attachment_from_business_object(attachment, is_fetch): attachment_id = attachment.get("attachmentId") business_object_id = attachment.get("busObId") business_record_id = attachment.get("busObRecId") url = ( BASE_URL + f"api/V1/getbusinessobjectattachment" f"/attachmentid/{attachment_id}/busobid/{business_object_id}/busobrecid/{business_record_id}" ) response = make_request("GET", url, is_fetch=is_fetch) return parse_response(response, f"Unable to get content of attachment {attachment_id}", file_content=True, is_fetch=is_fetch) def get_attachments_content(attachments_to_download, is_fetch): attachments = [] for attachment in attachments_to_download: new_attachment = { "FileName": attachment.get("displayText"), "CreatedAt": attachment.get("created"), "Content": download_attachment_from_business_object(attachment, is_fetch=is_fetch), } attachments.append(new_attachment) return attachments def get_attachments_details(id_type, object_id, object_type_name, object_type_id, type, attachment_type, is_fetch=False): id_type_str = "publicid" if id_type == "public_id" else "busobrecid" business_object_type_str = "busobid" if object_type_id else "busobname" object_type = object_type_id if object_type_id else object_type_name url = ( BASE_URL + f"api/V1/getbusinessobjectattachments/" f"{business_object_type_str}/{object_type}/" f"{id_type_str}/{object_id}" f"/type/{type}" f"/attachmenttype/{attachment_type}" ) response = make_request("GET", url, is_fetch=is_fetch) return parse_response(response, f"Unable to get attachments for {object_type} {object_id}", is_fetch=is_fetch) def download_attachments(id_type, object_id, business_object_type_name=None, business_object_type_id=None, is_fetch=False): type = "File" attachment_type = "Imported" result = get_attachments_details( id_type, object_id, business_object_type_name, business_object_type_id, type, attachment_type, is_fetch=is_fetch ) attachments_to_download = result.get("attachments") if not attachments_to_download: return None return get_attachments_content(attachments_to_download, is_fetch=is_fetch) def get_attachments_info(id_type, object_id, attachment_type, business_object_type_name=None, business_object_type_id=None): type = "File" result = get_attachments_details( id_type, object_id, business_object_type_name, business_object_type_id, type, attachment_type ) attachments = result.get("attachments") attachments_info = [ { "AttachmentFiledId": attachment.get("attachmentFileId"), "FileName": attachment.get("displayText"), "AttachmentId": attachment.get("attachmentId"), "BusinessObjectType": business_object_type_name, f"BusinessObject{string_to_context_key(id_type)}": object_id, } for attachment in attachments ] return attachments_info, result def attachment_results(attachments): attachments_file_results = [] for attachment in attachments: attachment_content = attachment.get("Content") attachment_name = attachment.get("FileName") attachments_file_results.append(fileResult(attachment_name, attachment_content)) return attachments_file_results def run_query_on_business_objects(bus_id, filter_query, max_results, is_fetch): payload = {"busObId": bus_id, "includeAllFields": True, "filters": filter_query} if max_results: payload["pageSize"] = max_results return get_search_results(payload, is_fetch=is_fetch) def get_key_value_dict_from_template(key, val, business_object_id, is_fetch=False): template_dict = get_business_object_template(business_object_id, is_fetch=is_fetch) return cherwell_dict_parser(key, val, template_dict.get("fields")) def get_all_incidents(objects_names, last_created_time, max_results, query_string, real_fetch): all_incidents: list = [] for business_object_name in objects_names: business_object_id = resolve_business_object_id_by_name(business_object_name, is_fetch=real_fetch) query_list = [["CreatedDateTime", "gt", last_created_time]] if query_string: additional_query_list = validate_query_for_fetch_incidents(objects_names, query_string, real_fetch) query_list += additional_query_list incidents, _ = query_business_object(query_list, business_object_id, max_results, is_fetch=real_fetch) all_incidents += incidents sorted_incidents = sorted(all_incidents, key=lambda incident: incident.get("CreatedDateTime")) return sorted_incidents[:max_results] def object_to_incident(obj): attachments_list = [] attachments = obj.get("Attachments") if attachments: obj.pop("Attachments") for attachment in attachments: file_name = attachment.get("FileName") attachment_file = fileResult(file_name, attachment.get("Content")) attachments_list.append({"path": attachment_file.get("FileID"), "name": file_name}) item = {"name": f'Record ID:{obj.get("RecID")}', "attachment": attachments_list, "rawJSON": json.dumps(obj)} return createContext(item, removeNull=True) def save_incidents(objects_to_save): final_incidents = [] for obj in objects_to_save: final_incidents.append(object_to_incident(obj)) demisto.incidents(final_incidents) def fetch_incidents_attachments(incidents, is_fetch): for incident in incidents: rec_id = incident.get("RecID") business_object_id = incident.get("BusinessObjectId") incident["Attachments"] = [] attachments = download_attachments("record_id", rec_id, business_object_type_id=business_object_id, is_fetch=is_fetch) if attachments: for attachment in attachments: new_attachment_obj = {"Content": attachment.get("Content"), "FileName": attachment.get("FileName")} incident["Attachments"].append(new_attachment_obj) return incidents def validate_params_for_fetch(max_result, objects_to_fetch, real_fetch): # Check that max result is positive integer try: max_result = int(max_result) if max_result < 0: raise ValueError except ValueError: max_result_err_message = "Max results to fetch must be a number grater than 0" raise_or_return_error(max_result_err_message, real_fetch) # Make sure that there are objects to fetch if len(objects_to_fetch) == 0: objects_to_fetch_err_message = "No objects to fetch were given" raise_or_return_error(objects_to_fetch_err_message, real_fetch) def fetch_incidents(objects_names, fetch_time, max_results, query_string, fetch_attachments, real_fetch=False): validate_params_for_fetch(max_results, objects_names, real_fetch) max_results = int(max_results) last_run = demisto.getLastRun() last_objects_fetched = last_run.get("objects_names_to_fetch") if "last_created_time" in last_run and last_objects_fetched == objects_names: last_created_time = last_run.get("last_created_time") else: try: last_created_time, _ = parse_date_range(fetch_time, date_format=DATE_FORMAT, to_timestamp=False) except ValueError: error_message = ( f"First fetch time stamp should be of the form: