Source
import json import demistomock as demisto from CommonServerPython import * from CommonServerUserPython import * CONTENT_TYPE_MAPPER = { "json": "application/json", "xml": "text/xml", "form": "application/x-www-form-urlencoded", "data": "multipart/form-data", } RAW_RESPONSE = "raw_response" class Client(BaseClient): def __init__(self, base_url: str, auth: tuple, verify: bool, proxy: bool): super().__init__(base_url=base_url, auth=auth, verify=verify, proxy=proxy) def http_request( self, method: str, full_url: str = "", headers: dict = None, resp_type: str = RAW_RESPONSE, params: dict = None, data: str = None, timeout: int = 10, retries: int = 0, status_list_to_retry: list = None, raise_on_status: bool = False, allow_redirects: bool = True, backoff_factor: int = 5, ): try: res = self._http_request( method=method, full_url=full_url, headers=headers, params=params, timeout=timeout, resp_type=resp_type, status_list_to_retry=status_list_to_retry, raise_on_status=raise_on_status, retries=retries, data=data, error_handler=self._generic_error_handler, allow_redirects=allow_redirects, backoff_factor=backoff_factor, ) except requests.exceptions.ConnectTimeout as exception: err_msg = ( "Connection Timeout Error - potential reasons might be that the Server URL parameter" " is incorrect or that the Server is not accessible from your host." ) raise DemistoException(err_msg, exception) return res @staticmethod def _generic_error_handler(res: requests.Response): status_code = res.status_code if status_code == 400: raise DemistoException(f"Bad request. Status code: {status_code}. Origin response from server: {res.text}") if status_code == 401: raise DemistoException(f"Unauthorized. Status code: {status_code}. Origin response from server: {res.text}") if status_code == 403: raise DemistoException(f"Invalid permissions. Status code: {status_code}. Origin response from server: {res.text}") if status_code == 404: raise DemistoException( f"The server has not found anything matching the request URI. Status code:" f" {status_code}. Origin response from server: {res.text}" ) if status_code == 500: raise DemistoException(f"Internal server error. Status code: {status_code}. Origin response from server: {res.text}") if status_code == 502: raise DemistoException(f"Bad gateway. Status code: {status_code}. Origin response from server: {res.text}") def create_headers(headers: dict, request_content_type_header: str, response_content_type_header: str) -> dict[str, str]: """ Create a dictionary of headers. It will map the header if it exists in the CONTENT_TYPE_MAPPER. Args: headers: The headers the user insert. request_content_type_header: The content type header. response_content_type_header: The response type header. Returns: A dictionary of headers to send in the request. """ if request_content_type_header in CONTENT_TYPE_MAPPER: request_content_type_header = CONTENT_TYPE_MAPPER[request_content_type_header] if response_content_type_header in CONTENT_TYPE_MAPPER: response_content_type_header = CONTENT_TYPE_MAPPER[response_content_type_header] if request_content_type_header and not headers.get("Content-Type"): headers["Content-Type"] = request_content_type_header if response_content_type_header and not headers.get("Accept"): headers["Accept"] = response_content_type_header return headers def get_parsed_response(res, resp_type: str) -> Any: try: resp_type = resp_type.lower() if resp_type == "json": res = res.json() elif resp_type == "xml": res = json.loads(xml2json(res.content)) else: res = res.text return res except ValueError as exception: raise DemistoException(f"Failed to parse json object from response: {res.content}", exception) def format_status_list(status_list: list) -> list[int]: """ Get a status list and format it to a range of status numbers. Example: given: ['400-404',500,501] return: [400,401,402,403,500,501] Args: status_list: The given status list. Returns: A list of statuses to retry. """ final_status_list = [] for status in status_list: # Checks if the status is a range of statuses if "-" in status: range_numbers = status.split("-") status_range = list(range(int(range_numbers[0]), int(range_numbers[1]) + 1)) final_status_list.extend(status_range) elif status.isdigit(): final_status_list.append(int(status)) return final_status_list def build_outputs(parsed_res, res: requests.Response) -> dict: return { "ParsedBody": parsed_res, "Body": res.text, "StatusCode": res.status_code, "StatusText": res.reason, "URL": res.url, "Headers": dict(res.headers), } def parse_headers(headers: str) -> dict: """ Parsing headers from str type to dict. The allowed format are: 1. {"key": "value"} 2. "key": "value" """ if not headers.startswith("{") and not headers.endswith("}"): headers = "{" + headers + "}" try: headers_dict = json.loads(headers) except json.decoder.JSONDecodeError: raise DemistoException("Make sure the headers are in one of the allowed formats.") return headers_dict """ MAIN FUNCTION """ def main(): try: args = demisto.args() method = args.get("method", "") full_url = args.get("url", "") body = args.get("body", "") request_content_type = args.get("request_content_type", "") response_content_type = args.get("response_content_type", "") parse_response_as = args.get("parse_response_as", RAW_RESPONSE) params = args.get("params", {}) headers = args.get("headers", {}) if isinstance(headers, str): headers = parse_headers(headers) headers = create_headers(headers, request_content_type, response_content_type) auth = tuple(argToList(args.get("auth_credentials", None))) save_as_file = args.get("save_as_file", "no") file_name = args.get("filename", "http-file") timeout = arg_to_number(args.get("timeout", 10)) timeout_between_retries = arg_to_number(args.get("timeout_between_retries", 5)) retry_count = arg_to_number(args.get("retry_count", 3)) proxy = argToBoolean(args.get("proxy", False)) verify = argToBoolean(not args.get("unsecure", False)) client = Client(base_url=full_url, auth=auth, verify=verify, proxy=proxy) kwargs = { "method": method, "full_url": full_url, "headers": headers, "data": body, "timeout": timeout, "params": params, "backoff_factor": timeout_between_retries, } retry_on_status = args.get("retry_on_status", None) raise_on_status = bool(retry_on_status) retry_status_list = format_status_list(argToList(retry_on_status)) if raise_on_status: kwargs.update({"retries": retry_count, "status_list_to_retry": retry_status_list, "raise_on_status": raise_on_status}) enable_redirect = argToBoolean(args.get("enable_redirect", True)) if not enable_redirect: kwargs.update({"allow_redirects": enable_redirect}) res = client.http_request(**kwargs) parsed_res = get_parsed_response(res, parse_response_as) if save_as_file == "yes": return fileResult(file_name, res.content) outputs = build_outputs(parsed_res, res) return CommandResults( readable_output=f"Sent a {method} request to {full_url}", outputs_prefix="HttpRequest.Response", outputs=outputs, raw_response={"data": parsed_res}, ) except Exception as e: return_error(f"Failed to execute HttpV2 script. Error: {e!s}") if __name__ in ("__main__", "__builtin__", "builtins"): return_results(main())
README
Sends a HTTP request with advanced capabilities
Script Data
| Name | Description |
|---|---|
| Script Type | python3 |
| Tags | basescript |
| Cortex XSOAR Version | 6.5.0 |
Inputs
| Argument Name | Description |
|---|---|
| url | Specify where the request should be sent. Include the URI scheme (‘http’ or ‘https’). |
| method | Specify the HTTP method to use. |
| headers | Specify a hash of headers to send with the request. Headers can be of string type but need to be formatted in the following ways: `{“key1”: “value1”, “key2”: “value2”}` or `“key1”: “value1”, “key2”: “value2”` |
| body | Specify the body of the request. |
| request_content_type | Specify the Content-Type header for the request. Shorthands are provided for the following common content types: json (application/json) xml (text/xml) form (application/x-www-form-urlencoded) data (multipart/form-data) If you choose to define a different type, please include the full type name, e.g: application/pdf |
| response_content_type | Specify the Accept header for the request - the response content type. Shorthands are provided for the following common content types: json (application/json) xml (text/xml) form (application/x-www-form-urlencoded) data (multipart/form-data) If you choose to define a different type, please include the full type name, e.g: application/pdf |
| parse_response_as | Specify how you would like to parse the response. |
| auth_credentials | Basic authorization. Please set values in the format: username,password. For Bearer token please use the headers. |
| params | URL parameters to specify the query. |
| timeout | Specify the timeout of the HTTP request in seconds. Defaults to 10 seconds. |
| enable_redirect | The request will be called again with the new URL. |
| retry_on_status | Specify the array of status codes that should cause a retry. For example: 301-303,400,402. |
| retry_count | Specify the number or retries to be made in case of a failure. Defaults to 3. |
| timeout_between_retries | Specify the timeout between each retry in seconds. Defaults to 5. |
| save_as_file | Save the response in a file. |
| filename | filename |
| unsecure | Trust any certificate (not secure) |
| proxy | Use system proxy settings |
Outputs
| Path | Description | Type |
|---|---|---|
| HttpRequest.Response.StatusCode | The number that indicates the status of the request. | String |
| HttpRequest.Response.StatusText | The text corresponding to the status code | String |
| HttpRequest.Response.URL | The URL of the response | String |
| HttpRequest.Response.ParsedBody | The parsed response, according to `parse_response_as` argument. | String |
| HttpRequest.Response.Headers | The response headers. | String |
| HttpRequest.Response.Body | The response data. | Unknown |
Script Examples
Example command
!HttpV2 method=GET url="https://test.jamfcloud.com/JSSResource/computers/id/1/subset/General" response_content_type=json request_content_type=json auth_credentials=myUser,myPass parse_response_as=json
Context Example
{
"HttpRequest": {
"Response": {
"Body": "{\"computer\":{\"general\":{\"id\":1,\"name\":\"Computer 1\",\"network_adapter_type\":\"\",\"mac_address\":\"11:5B:35:CA:12:12\",\"alt_network_adapter_type\":\"\",\"alt_mac_address\":\"A1:34:95:EC:97:C1\",\"ip_address\":\"123.243.192.11\",\"last_reported_ip\":\"192.168.1.11\",\"serial_number\":\"AA40F81C60A3\",\"udid\":\"AA40F812-60A3-11E4-90B8-12DF261F2C7E\",\"jamf_version\":\"9.6.29507.c\",\"platform\":\"Mac\",\"barcode_1\":\"\",\"barcode_2\":\"\",\"asset_tag\":\"\",\"remote_management\":{\"managed\":false,\"management_username\":\"\",\"management_password_sha256\":\"1230c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b811\"},\"supervised\":false,\"mdm_capable\":false,\"mdm_capable_users\":{},\"report_date\":\"2021-03-29 12:44:12\",\"report_date_epoch\":1617021852595,\"report_date_utc\":\"2021-03-29T12:44:12.595+0000\",\"last_contact_time\":\"2014-10-24 10:26:55\",\"last_contact_time_epoch\":1414146415335,\"last_contact_time_utc\":\"2014-10-24T10:26:55.335+0000\",\"initial_entry_date\":\"2021-03-29\",\"initial_entry_date_epoch\":1617021852322,\"initial_entry_date_utc\":\"2021-03-29T12:44:12.322+0000\",\"last_cloud_backup_date_epoch\":0,\"last_cloud_backup_date_utc\":\"\",\"last_enrolled_date_epoch\":1414146339607,\"last_enrolled_date_utc\":\"2014-10-24T10:25:39.607+0000\",\"mdm_profile_expiration_epoch\":0,\"mdm_profile_expiration_utc\":\"\",\"distribution_point\":\"\",\"sus\":\"\",\"site\":{\"id\":-1,\"name\":\"None\"},\"itunes_store_account_is_active\":false}}}",
"Headers": {
"Accept-Ranges": "bytes",
"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0",
"Connection": "keep-alive",
"Content-Encoding": "gzip",
"Content-Length": "641",
"Content-Type": "text/plain;charset=UTF-8",
"Date": "Wed, 19 Jan 2022 12:12:16 GMT",
"Server": "Jamf Cloud Node",
"Set-Cookie": "AASALB=rbRVEXaRgAKa6+YhXAr2E4JbbP5PSS3eZpm9G1AeWQBNVG/vQ3SOmsNV4tYA5N0v7sEUQd+QYMkuUTOmF/7USLyVnQVz/yW7wQh4dWrbGOY/gAU7rL30IvQwfdut; Expires=Wed, 26 Jan 2022 12:12:16 GMT; Path=/, AWSALBCORS=rbRVEXaRgAKa6+YhXAr2E4JbbP5PSS3eZpm9G1AeWQBNVG/vQ3SOmsNV4tYA5N0v7sEUQd+QYMkuUTOmF/7USLyVnQVz/yW7wQh4dWrbGOY/gAU7rL30IvQwfdut; Expires=Wed, 26 Jan 2022 12:12:16 GMT; Path=/; SameSite=None; Secure, APBALANCEID=aws.usw2-std-ellison2-tc-4; path=/;HttpOnly;Secure;",
"Strict-Transport-Security": "max-age=31536000; includeSubdomains;, max-age=31536000 ; includeSubDomains",
"Vary": "Accept-Charset,Accept-Encoding,Accept-Language,Accept",
"X-FRAME-OPTIONS": "DENY",
"X-XSS-Protection": "1; mode=block"
},
"ParsedBody": {
"computer": {
"general": {
"alt_mac_address": "A1:34:95:EC:97:C1",
"alt_network_adapter_type": "",
"asset_tag": "",
"barcode_1": "",
"barcode_2": "",
"distribution_point": "",
"id": 1,
"initial_entry_date": "2021-03-29",
"initial_entry_date_epoch": 1617021852322,
"initial_entry_date_utc": "2021-03-29T12:44:12.322+0000",
"ip_address": "200.200.200.200",
"itunes_store_account_is_active": false,
"jamf_version": "9.6.29507.c",
"last_cloud_backup_date_epoch": 0,
"last_cloud_backup_date_utc": "",
"last_contact_time": "2014-10-24 10:26:55",
"last_contact_time_epoch": 1414146415335,
"last_contact_time_utc": "2014-10-24T10:26:55.335+0000",
"last_enrolled_date_epoch": 1414146339607,
"last_enrolled_date_utc": "2014-10-24T10:25:39.607+0000",
"last_reported_ip": "192.168.1.10",
"mac_address": "11:5B:35:CA:12:12",
"mdm_capable": false,
"mdm_capable_users": {},
"mdm_profile_expiration_epoch": 0,
"mdm_profile_expiration_utc": "",
"name": "Computer 1",
"network_adapter_type": "",
"platform": "Mac",
"remote_management": {
"managed": false,
"management_password_sha256": "abc0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"management_username": ""
},
"report_date": "2021-03-29 12:44:12",
"report_date_epoch": 1617021852595,
"report_date_utc": "2021-03-29T12:44:12.595+0000",
"serial_number": "AA40F81C60A3",
"site": {
"id": -1,
"name": "None"
},
"supervised": false,
"sus": "",
"udid": "AA40F812-60A3-11E4-90B8-12DF261F2C7E"
}
}
},
"StatusCode": 200,
"StatusText": "",
"URL": "https://test.jamfcloud.com/JSSResource/computers/id/1/subset/General"
}
}
}
Human Readable Output
Sent a GET request to https://test.jamfcloud.com/JSSResource/computers/id/1/subset/General