HttpV2
Sends a HTTP request with advanced capabilities.
- Type
- python
- Pack
- CommonScripts
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