API error handling - python

So, I am using API calls to get some json data, and I am using a for loop to execute the call using multiple ids which will return different data based on the user's id. So basically we need to update the permissions for some of our users so the json data looks somewhat like this for the users without permissions:
{"meta":{"httpStatus":"401 - Unauthorized","error":{"errorMessage":"Insufficient
permissions.","errorCode":"ARH_8.5"},"requestId":null}}
and this for users with the permissions:
{"result":{"elements":[{"id":"dslkjafsadl","name":"Test","ownerId"
:"sdfadfsfsda","lastModified":"2016","isActive":true},
{"id":"dsafsad","name":"Test","ownerId":"sdfasdfa","lastModified":"2016","isActive":true}],"nextPage":null},"meta":{"httpStatus":"200 - OK","requestId":"3242343"}}
so my question is if I have a big response with many results both with and without permissions how could I only pull the ids from the users with permissions, and exclude the ones without the permissions.
Say the response is stored in the variable "data" and the ids that I am looking for will be stored in "requestId" this is the code I tried:
requestId = request_id = [element['requestId'] for element in data['result']['elements']]
from this code I receive the a TypeError, and it doesn't recognize 'result' because the first response is a user without the correct permissions.

Best way is to check the http status code from the original request. Say you use the requests library to perform the GET to whatever resource, status_code of the requests object will contain 401 for permission denied.
Just relying on the code you already have, you can include a conditional statement as:
requestId = request_id = [element['requestId'] for element in data['result']['elements'] if element.get('meta').get('httpStatus') == '200 - OK']
which will basically only take elements where 'meta'.'httpStatus' is the desired one.
For the requests library, take a look here: http://docs.python-requests.org/

If you have control over your JSON:
One potential solution would be to consturct your json such that there's a parent node which describes the permissions of the returned result. For example:
{"permissions": false,
{"meta":{"httpStatus":"401 - Unauthorized","error":
{"errorMessage":"Insufficient permissions.","errorCode":"ARH_8.5"},
"requestId":null}
}
}
And
{"permissions": true,
{"result":{"elements":[{"id":"dslkjafsadl","name":"Test","ownerId"
:"sdfadfsfsda","lastModified":"2016","isActive":true},{"
id":"dsafsad","name":"Test","ownerId":"sdfasdfa","la
stModified":"2016","isActive":true}],"nextPage":null},"meta":{"h
ttpStatus":"200 - OK","requestId":"3242343"}}}
With this setup, just read the first value, and then structure your json-parsing code accordingly.
If your don't have control over the JSON:
In this case, you have to figure out which request you're receiving before you can really process it. One way to do this would be to read the names of the top keys.
In the case of the correct response, you could do something like this:
parsed_json = json.loads(json_string)
# The key "result" is only in the sucess response, not in the error response
if "result" in parsed_json:
# Success
else:
# Error

Related

Not able to generate JWT token for python zephyr

I'm unable to generate valid JWT token for the below endpoint:
https://prod-api.zephyr4jiracloud.com/connect/public/rest/api/1.0/cycle/cycleId/export?versionId=&exportType=&projectId=
I have the cycleId passed as parameter and rest query arguments too but unable to get a response.
I'm unsure about how to set the RELATVE_PATH for the above endpoint?
Example provided for python(link) shows:
RELATIVE_PATH = '/public/rest/api/1.0/cycle'
Tried using RELATIVE_PATH = '/public/rest/api/1.0/cycle/ 79655-XXX-XXXX/export' but no luck!
I got this resolved by maintaining an order of query parameters. Strangely, the query params should be sorted alphabetically.
This was a hit and try based on the reference provided here

Sending requests - provide a dictionary or a string

I was to automate action using python requests, when encountered strange behavior.
I want to generate reports from a certain domain, to do so I have to send a POST request providing parameters in form of an XML. In devtools it looks like this:
xmlWe: <REPORTPARS><PROC_ID>11</PROC_ID>
..... and so on
when I send report data by python requests, it works perfectly whe provided a with dictionary:
data = dict(xmlWe = '<REPORTPARS><PROC_ID>11</PROC_ID>(...) '
r = s.post(URL_generate, data=data))
IMO, its kind of strange, dictionary is a type in python so why would this server handle or expect this type?
I was trying to send this XML as text or JSON, adding the corresponding header 'Content-type' but without success. The server will return 777 Java nullptr exception, which is the same as in case of sending any other nonsense datatype. So it looks like he was expecting some sort of dictionary.
My problem is, my company uses pl/sql for automation, so I will finally have to use this language. There, in http_utils data can be sent merely by write_text, so I'm restricted to sending string (can be dumped JSON).
What do You think, Is there an option for this server will accept report_pars as a string by any chance?
You need to send a xml directly:
import requests
headers = {'Content-Type': 'application/xml'}
r = requests.post(URL_generate, data=xmlWe, headers=headers)

What is a good way to parse JSON response with varied data types?

I am trying to write API calls to retrieve the last package status using UPS Tracking API. However, it seems that the JSON strings returned by the API vary in the data type returned - for example, ["TrackResponse"]["Shipment"]["Package"] returns either a JSON string or a list of JSON strings.
I am currently managing these using the below try / except statements. If a list of JSONs is returned instead of a singular JSON string, I specify that I am looking for the first JSON string in the list.
response = requests.request("POST", url, data=payload, headers=headers)
response_json=response.json()
try:
status = response_json["TrackResponse"]["Shipment"]["Package"]["Activity"][0]["Status"]["Type"]
code = response_json["TrackResponse"]["Shipment"]["Package"]["Activity"][0]["Status"]["Code"]
location = response_json["TrackResponse"]["Shipment"]["Package"]["Activity"][0]["ActivityLocation"]["Address"]["City"]
except:
status = response_json["TrackResponse"]["Shipment"]["Package"][0]["Activity"][0]["Status"]["Type"]
code = response_json["TrackResponse"]["Shipment"]["Package"][0]["Activity"][0]["Status"]["Code"]
location = response_json["TrackResponse"]["Shipment"]["Package"][0]["Activity"][0]["ActivityLocation"]["Address"]["City"]
Unfortunately, this seems like it isn't going to work well as there are multiple parts of the API response where this issue is occurring. Eg."Activity" also returns a JSON if only one activity was found, or a list of JSONs if multiple activities are returned.
Is there a more elegant way of resolving these issues, besides writing multiple nested if/else or try/except statements to test the datatype returned at each point of the JSON response?
Thanks!

Getting "INVALID_TOKEN_FORMAT The security token format does not conform to expected schema." docusign legacy auth header

I'm trying to write a request using Python Requests which sends a request to Docusign. I need to use the legacy authorization header, but unfortunately it seems most documentation for this has been removed. When I send the request I get an error as stated in the title.
From research, I found that special characters in the password can cause this issue, so I've confirmed that my password has no special characters, and that my API key is correct. I am currently sending the header as a stringified dictionary as shown below. I have tried it several other ways, and this seems to be the closest, but it still results in the error. Other ways I've tried include attempting to write out the header as a single string (not forming a dictionary first), but that didn't seem to work any better.
docusign_auth_string = {}
docusign_auth_string["Username"] = docusign_user
docusign_auth_string["Password"] = docusign_password
docusign_auth_string["IntegratorKey"] = docusign_key
docusign_auth_string = str(docusign_auth_string)
headers = {'X-DocuSign-Authentication': docusign_auth_string}
response = requests.post(docusign_url, headers=headers, data=body_data)
The above code returns a 401 with the message, INVALID_TOKEN_FORMAT "The security token format does not conform to expected schema." The header I am sending looks as follows:
{'X-DocuSign-Authentication': "{'Username': 'test#test.com', 'Password': 'xxxxxxxxxx', 'IntegratorKey': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'}"}
When I send the request via Postman, it works just fine. In Postman I enter the header name as X-Docusign-Authentication, and the value as: {"Username":"{{ds_username}}","Password":"{{ds_password}}","IntegratorKey":"{{ds_integrator_key}}"} (subbing the same variable values as in the python code).
Therefore it definitely has something to do with the way Requests is sending the header.
Does anyone know why I might be getting the above error?
I'm able to reproduce this behavior: It looks like DocuSign doesn't accept Single Quotes around the sub-parameters of the x-DocuSign-Authentication header value.
Your example fails:
{'Username': 'test#test.com', 'Password': 'xxxxxxxxxx', 'IntegratorKey': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'}
This has more success:
{"Username": "test#test.com", "Password": "xxxxxxxxxx", "IntegratorKey": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}
I'm not familiar enough with Python to advise if there's a different code structure you can follow to use double quotes instead of single. Worst case scenario, you may need to manually set the Header Value to follow that format.
I found a solution to this issue. The response that mentioned double quotes is correct, but in Python I was unable to send a string with the proper format for docusign to understand. Next I found the following Stack overflow question, which ultimately provided the solution:
How to send dict in Header as value to key 'Authorization' in python requests?
I used json.dumps and that resolved the issue. My code is as follows:
docusign_auth_string = {}
docusign_auth_string["Username"] = docusign_user
docusign_auth_string["Password"] = docusign_password
docusign_auth_string["IntegratorKey"] = docusign_key
headers = {"X-DocuSign-Authentication": json.dumps(docusign_auth_string), "Content-Type": "application/json"}
Since you are having success using Postman, it will help to get exactly what is being sent via your request. For this use:
response = requests.get(your_url, headers=your_headers)
x = response.request.headers()
print(x)
This will show you exactly what requests is preparing and sending off. If you post that response here id be happy to help more.
How can I see the entire HTTP request that's being sent by my Python application?
The 2nd answer shows all the possible parameters of your response object.

Need to get or convert data from a site to be valid JSON

I am working on a script that will get data from a Website (Cisco Patches site) and based on the data received, I need to post it to another site (ServiceNow Event Management). The POST needs to be REST/JSON with specific keys for this to work.
I have enough code to GET the data and I have the code to POST working.
I am having a hard time with converting the data I get from GET to map it into valid JSON key value pairs to POST.
I am using the following code to get a list of new patches from Cisco website. I am getting the correct data but the format if the data is not how I can use it to post to another tool in JSON format (using different keys but values from the returned information.
This works -
def getjson(ciscourl):
response = urllib.request.urlopen(ciscourl)
ciscodata = response.read().decode("utf-8")
return json.loads(ciscodata)
The data I get back looks like below (this query resulted in 2 patches):
[{"identifier":"cisco-sa-20180521-cpusidechannel","title":"CPU Side-Channel Information Disclosure Vulnerabilities: May 2018","version":"1.5","firstPublished":"2018-05-22T01:00:00.000+0000","lastPublished":"2018-05-31T20:44:16.123+0000","workflowStatus":null,"id":1,"name":"Cisco Security Advisory","url":"https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20180521-cpusidechannel","severity":"Medium","workarounds":"No","cwe":null,"cve":"CVE-2018-3639,CVE-2018-3640","ciscoBugId":"","status":"Updated","summary":"On May 21, 2018, researchers disclosed two vulnerabilities that take advantage of the implementation of speculative execution of instructions on many modern microprocessor architectures to perform side-channel information disclosure attacks. These vulnerabilities could allow an unprivileged, ","totalCount":6,"relatedResource":[]},{"identifier":"cisco-sa-20180516-firepwr-pb","title":"Cisco Firepower Threat Defense Software Policy Bypass Vulnerability","version":"1.0","firstPublished":"2018-05-16T16:00:00.000+0000","lastPublished":"2018-05-16T16:00:00.000+0000","workflowStatus":null,"id":1,"name":"Cisco Security Advisory","url":"https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20180516-firepwr-pb","severity":"Medium","workarounds":"No","cwe":"CWE-693","cve":"CVE-2018-0297","ciscoBugId":"CSCvg09316","status":"New","summary":"A vulnerability in the detection engine of Cisco Firepower Threat Defense software could allow an unauthenticated, remote attacker to bypass a configured Secure Sockets Layer (SSL) Access Control (AC) policy to block SSL traffic.The vulnerability is due to the incorrect handling ","totalCount":6,"relatedResource":[]}]
I can extract values from this, as such print(jarray.get('identifier')) but I am having a hard time being able to map these values into my own JSON map with keys I define. So the value from the key identifier I got back, needs to map to a key called "node" in my JSON map.
I have tried json.loads, json.load, json.dump, json.dumps. Each time the error is Attribute Type error.
This is the code where I am confused:
def createJson(l):
#try:
jsonarray = l
o_source = "CiscoUpdatePatchChecker"
o_node = (jsonarray.get('identifier')) #this does not work
o_metric_name = ("Critical")
o_type = ("test")
o_resource = ("test_resource")
o_description = jsonarray #this works
o_event_class = ("test event class")
o_additional_info = jsonarray
print ("-" * 50)
print (o_source, o_node, o_metric_name, o_type, o_resource, o_description, o_event_class, o_additional_info)
print ("-" * 50)
data = {"source": o_source, "node": o_node, "metric_name": o_metric_name, "type": o_type, "resource": o_resource, "event_class": o_event_class, "description": o_description, "additional_info": o_additional_info}
return json.dumps(data)
# except:
#pass
Beyond this, the rest of the code just posts the data to ITSM which is working. -
def postjson(data):
# try:
url = posturl
auth = HTTPBasicAuth(username, password)
head = {'Content-type': 'application/json',
'Accept': 'application/json'}
payld = data
ret = requests.post(url, auth=auth , data=payld, headers=head)
# sys.stdout.write(ret.text)
returned_data = ret.json()
print(returned_data)
So my issue is to map data I am getting back to my keys:value pairs in a JSON map, & I will need to loop the code for as many times as the number of patches are retrieved. I am currently planning to loop in my main function for number of JSON maps that need to POST.
For now, I am just take all the data I get and mapping all the data I get to the "description" and "additional_info" field. This works and I can post the data fine.
It will help me tremendously if someone can point me to examples of how to manipulate the data I am getting from my GET request.
json.loads(ciscodata) returns an array of dictionaries.
The statement o_node = (jsonarray.get('identifier')) fails because it is trying to get a dictionary key when jsonarray is a list.
Try o_node = jsonarray[0].get('identifier')
I am not sure why you have parens around jsonarray -- those shouldn't be there.
In general, you can do something like:
for elem in jsonarray:
identifier = elem.get('identifier')
title = elem.get('title')
... etc
do_something(identifier, title, ...)
This makes it easier to debug because you can test the values you get back from the api call.
If you are always getting the same keys in your dictionary, you can skip this assignment and just do
for elem in jsonarray:
do_something(elem.get('key1'), elem.get('key2'), ...)`
Hope that helps

Categories