Netsuite REST Web Services API GET Request, Outside of Postman - python

Hi Stack Overflow community, thanks in advance for your help:
Question for Community:
Instead of reinventing the oauth wheel, are there any tips / best practices for creating my own oauth signature/nonce within my python GET requests for Netsuite's new API (REST Web Services; see below for context of question)? It seems that other folks who have been successful at this have done it through trial and error which is my plan as well, but ideally I'd like to have fewer errors and again, not reinvent the wheel. Any tips, tricks, ideas are greatly welcome. See context below
What:
Attempting to make a GET request using Netsuite's brand new REST API (REST Web Services). This is a different API than their SOAP/ RESTlets.
How:
Through writing Python script in Visual Studio Code. I am successful at making the request in Postman. I copied the code into Visual Studio Code that Postman used to make the successful GET request and received a 401 response (see below).
Problem Encountered:
I receive a 401 response, invalid login. There is no official Netsuite documentation on how make a successful interaction with this new REST API outside of Postman, so after reading through StackOverflow and other blogs/publications it seems that I need to create my own oauth_signature, oauth_timestamp, and oauth_nonce.
Postman GET Request Code:
import requests
url = "https://123456-sb1.suitetalk.api.netsuite.com/services/rest/query/v1/workbook/custworkbook12345/result"
payload = {}
headers = {
'Authorization': 'OAuth realm="123456_SB1",oauth_consumer_key="123456789101112131415",oauth_token="123456789101112131415",oauth_signature_method="HMAC-SHA256",oauth_timestamp="123456789",oauth_nonce="123456789",oauth_version="1.0",oauth_signature="123456789101112131415"',
'Cookie': 'NS_ROUTING_VERSION=LAGGING'
}
response = requests.request("GET", url, headers=headers, data = payload)
print(response.text.encode('utf8'))
Thanks in advance!

Thanks to Josh's recommendation (see comments to my original question) I successfully used oauthlib's oauth1 client to send a request in Visual Studio Code. The nonce and signature look a little different than what Postman shows in their code snippet, but it did work. For anyone attempting the same thing with Netsuite's REST Web Services I suggest going this route.
My code that sent a successful GET request:
import requests
import oauthlib.oauth1
import json
url = "https://12345-sb1.suitetalk.api.netsuite.com/services/rest/query/v1/dataset/custdataset1/result"
payload = {}
client = oauthlib.oauth1.Client('consumer key', client_secret='12345',
resource_owner_key='12345', resource_owner_secret='12345', realm='12345_SB1',signature_method="HMAC-SHA256")
url, headers, body = client.sign('https://4635201-sb4.suitetalk.api.netsuite.com/services/rest/query/v1/dataset/custdataset1/result')
response = requests.request("GET", url, headers=headers, data = payload)
print(response.text.encode('utf8'))
A Few Additional Helpful Notes -
I'm testing this in Netsuite Sandbox, hence the realm "12345_SB1". If
you aren't in sandbox you shouldn't need the underscore SB. Just use your account ID.
I'm pulling Netsuite Analytics Report, which at this time is still in
beta for the new API (REST Web Services).
I used the Python oauthlib that Josh recommended and I recommend you do the same, link here

Related

Python Requests / HTTPX - Authenticate All Redirected URL - How To

I wonder if there is a way to authenticate each redirected URL when working with Python modules such as httpx or requests?
Problem Statement
I am trying to connect to an API endpoint under the company network. Due to the company's cyber security measures, the API endpoint will be randomly masked with a company proxy, which causes the 307 Redirect status code.
my current code snippet looks like the below:
import httpx
api_url = 'https://demo.vizionapi.com/carriers'
head = {
'X-API-Key':'API KEY'
}
response = httpx.get(url=api_url, verify='supporting_files/cacert.pem',
headers=head, auth=('my username', 'my password'),
follow_redirects=True)
With above code, I received the 401 authentication needed error (But auth has been passed). This error will only happen when redirection occurs due to the company proxy.
Question:
My assumption is the authentication is only being passed into the first URL not the redirected URL. Therefore, I wonder if anyone know how I can use the same auth parameter for all URLs (direct & redirect)?
Any suggestion will be deeply appracaited.
I don't know what requests behavior with regards to auth during redirect is, but the first solution to come to mind is to manually follow the redirects yourself. Put your request in a loop that checks for the 3xx response codes, and handle auth however you want to.

Streamlabs API 405 response code

I'm trying to use Streamlabs API. Streamlabs API uses Oauth2 for creating apps. So first I send whoever's using my app to an authorization link containing my app's client id and the scopes I want to use.
(Something like this: streamlabs.com/api/v1.0/authorize?client_id=CLIENT-ID-HERE&redirect_uri=REDIRECT-URI&response_type=code&scope=SOME+SCOPES+HERE)
Once I've done that I receive a code at the redirect uri specified. I then use that code to get the access token for permanent access to the connected user's account. I then receive the access token from a POST request that works perfectly... Now I run into the problem. When getting the temporary code before the access token I specified the scopes: "donations.read +donations.create+alerts.write+alerts.create".
When authorizing, the app asks for permission to the different scopes. The scope in focus is "alerts.write" so that I can send test alerts using POST requests. But this doesn't work for some reason. To send a test alert I have to send a POST request to this url: "https://streamlabs.com/api/alerts/send_test_alert"
I've tried doing that in two different ways.
1:
import requests
url = "https://streamlabs.com/api/alerts/send_test_alert"
data = {
"access_token":"UserAccessTokenHere",
"type":"donation"
}
response = requests.post(url=url, data=data)
print(response.text)
2:
import requests
url = "https://streamlabs.com/api/alerts/send_test_alert?access_token=UserAccessTokenHere&type=donation"
response = requests.post(url=url)
print(response.text)
If I do print(response) it prints "Response [405]".
But if I do print(response.text) I get a long HTML document for this page: Error response page
Any ideas what's going wrong with my Python requests? send_test_alert documentation here: Link
I've contacted support and looks like you've made the same error as me.
You're not actually sending a request to the right URL.
You are a sending a request to: "https://streamlabs.com/api/alerts/send_test_alert"
You should be using the URL: "https://streamlabs.com/api/v1.0/alerts/send_test_alert"

Access Sonarqube Webapi with python requests

i quite new to pyhton. I just try a simple way to get an HTTP response with python to a simple get from the sonar Web API
i use the request library and try a simple use :
project = requests.get(url=Sonar_Api_Projects_Search, params=param_Projects, verify=False, headers={'Authorization': 'token {}'.format(token)})
the request is well formatted and work fine when i use it in e web browser.
but as a response i get this strange output :
{"err_code":500,"err_msg":"undefined method empty?' for
nil:NilClass\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/lib/authenticated_system.rb:132:in
login_from_basic_auth'\n\torg/jruby/RubyProc.java:290:in
call'\n\torg/jruby/RubyProc.java:224:in
call'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/actionpack-2.3.15/lib/action_controller/http_authentication.rb:126:in
authenticate'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/actionpack-2.3.15/lib/action_controller/http_authentication.rb:116:in
authenticate_with_http_basic'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/lib/authenticated_system.rb:129:in
login_from_basic_auth'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/lib/authenticated_system.rb:11:in
current_user'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/app/controllers/application_controller.rb:102:in set_user_session'\n\torg/jruby/RubyKernel.java:2223:in
send'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/activesupport-2.3.15/lib/active_support/callbacks.rb:178:in
evaluate_method'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/activesupport-2.3.15/lib/active_support/callbacks.rb:166:in
call'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/actionpack-2.3.15/lib/action_controller/filters.rb:225:in
call'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/actionpack-2.3.15/lib/action_controller/filters.rb:629:in
run_before_filters'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/actionpack-2.3.15/lib/action_controller/filters.rb:615:in
call_filters'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/actionpack-2.3.15/lib/action_controller/filters.rb:610:in
perform_action_with_filters'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/actionpack-2.3.15/lib/action_controller/benchmarking.rb:68:in
perform_action_with_benchmark'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/activesupport-2.3.15/lib/active_support/core_ext/benchmark.rb:17:in
ms'\n\tjar:file:/D:/sonarqube-5.6.6_20170214/lib/server/jruby-complete-1.7.9.jar!/META-INF/jruby.home/lib/ruby/1.8/benchmark.rb:308:in
realtime'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/activesupport-2.3.15/lib/active_support/core_ext/benchmark.rb:17:in
ms'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/actionpack-2.3.15/lib/action_controller/benchmarking.rb:68:in
perform_action_with_benchmark'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/actionpack-2.3.15/lib/action_controller/rescue.rb:160:in
perform_action_with_rescue'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/actionpack-2.3.15/lib/action_controller/flash.rb:151:in perform_action_with_flash'\n\torg/jruby/RubyKernel.java:2223:in
send'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/actionpack-2.3.15/lib/action_controller/base.rb:532:in
process'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/actionpack-2.3.15/lib/action_controller/filters.rb:606:in
process_with_filters'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/actionpack-2.3.15/lib/action_controller/base.rb:391:in
process'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/actionpack-2.3.15/lib/action_controller/base.rb:386:in
call'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/actionpack-2.3.15/lib/action_controller/routing/route_set.rb:450:in
call'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/actionpack-2.3.15/lib/action_controller/dispatcher.rb:87:in
dispatch'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/actionpack-2.3.15/lib/action_controller/dispatcher.rb:85:in
dispatch'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/actionpack-2.3.15/lib/action_controller/dispatcher.rb:121:in
_call'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/actionpack-2.3.15/lib/action_controller/dispatcher.rb:130:in
build_middleware_stack'\n\torg/jruby/RubyProc.java:290:in
call'\n\torg/jruby/RubyProc.java:224:in
call'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/activerecord-2.3.15/lib/active_record/query_cache.rb:29:in
call'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/activerecord-2.3.15/lib/active_record/connection_adapters/abstract/query_cache.rb:34:in
cache'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/activerecord-2.3.15/lib/active_record/query_cache.rb:9:in
cache'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/activerecord-2.3.15/lib/active_record/query_cache.rb:28:in
call'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/activerecord-2.3.15/lib/active_record/connection_adapters/abstract/connection_pool.rb:361:in
call'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/config/environment.rb:67:in
call'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/actionpack-2.3.15/lib/action_controller/string_coercion.rb:25:in
call'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/rack-1.1.6/lib/rack/head.rb:9:in call'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/rack-1.1.6/lib/rack/methodoverride.rb:24:in
call'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/actionpack-2.3.15/lib/action_controller/params_parser.rb:15:in
call'\n\tfile:/D:/sonarqube-5.6.6_20170214/lib/server/jruby-rack-1.1.13.2.jar!/jruby/rack/session_store.rb:70:in
context'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/rack-1.1.6/lib/rack/session/abstract/id.rb:58:in
call'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/actionpack-2.3.15/lib/action_controller/failsafe.rb:26:in
call'\n\tD:/sonarqube-5.6.6_20170214/web/WEB-INF/gems/gems/actionpack-2.3.15/lib/action_controller/dispatcher.rb:106:in
call'\n\tfile:/D:/sonarqube-5.6.6_20170214/lib/server/jruby-rack-1.1.13.2.jar!/rack/adapter/rails.rb:34:in
serve_rails'\n\tfile:/D:/sonarqube-5.6.6_20170214/lib/server/jruby-rack-1.1.13.2.jar!/rack/adapter/rails.rb:39:in
call'\n\tfile:/D:/sonarqube-5.6.6_20170214/lib/server/jruby-rack-1.1.13.2.jar!/rack/handler/servlet.rb:22:in
call'\n"}
Can someone help me ?
Thanks a lot
Best regards
Arnaud
Direct use of requests never worked for me.
I do the following and it is working fine:
(below code is to list projects in Sonar)
import json , requests, pprint
url = 'http://sonar_url:9000/api/projects/search'
myToken = 'fa2377941a95125443f4efade615512jjkd221211a48'
session = requests.Session()
session.auth = myToken, ''
call = getattr(session, 'get')
res = call(url)
print(res.status_code)
binary = res.content
output = json.loads(binary)
pprint.pprint(output)
...
#Parse json result
In Sonarqube 8.9, requests is working for me.
First, you should should create an API token. Per the docs:
This is the recommended way. Benefits are described in the page User Token. The token is sent via the login field of HTTP basic authentication, without any password.
The docs go on to provide a weird curl usage example:
# note that the colon after the token is required in curl to set an empty password
curl -u THIS_IS_MY_TOKEN: https://sonarqube.com/api/user_tokens/search
In requests, this looks something like this:
response = requests.get(
"http://your-sonar-instance.com/api/blah",
auth=HTTPBasicAuth("Some Sonarqube API token", "")
)
return json.loads(response.text)
See https://docs.sonarqube.org/latest/extend/web-api/ for API details.
Also note that auth=HTTPBasicAuth("token", "") seems to behave differently from auth=HTTPBasicAuth("token", None).
I know its an old question. Thankfully there is a wrapper library available now - https://github.com/shijl0925/python-sonarqube-api. It works quite well and is easy to setup.
If possible people from Sonarsource could make it the official one so that more people start using it and it gets maintained in the future too.

Python 2 Zoho Post Error?

I am trying to add a lead to a Zoho CRM module with Python. I keep getting:
< response>< error>< code>4600< /code>< message>Unable to process your request. Please verify if the name and value is appropriate for the "xmlData" parameter.< /message>< /error>< /response>
from the server. I have no idea if I am posting correctly or if it is a problem with our Xml Data. I am using urllib and urllib2 to format the post request.
The post request looks like this.
url = ("https://crm.zoho.com/crm/private/xml/Leads/insertRecords?authtoken="
""+str(self.authToken)+"&scope=crmapi")
params = {"xmlData":self.xml}
data = urllib.urlencode(params)
request = urllib2.Request(url = url, data =data)
request.add_header("Content-Type",'application/xml')
response = urllib2.urlopen(request)
You cannot combine HTTP GET query parameters (ones in URL) and HTTP POST parameters.
This is limitation on the HTTP protocol level, not in Python or Zoho.
Most likely you are doing it wrong. Revisit Zoho documentation how it should be done.
Here is another old library doing Zoho + CRM, written in Python. You might want to check it for inspiration: https://github.com/miohtama/mfabrik.zoho

Bad Request (400) error while trying to access REST API using requests in python

This error is so common that I found several hits on this error. However, couldn't find a solution to my problem.
I am trying to consume a REST API (not one of the publicly available ones) using requests module in Python. However, the specifications for the API consumption indicates that I call a POST method with URI, Content-type = 'application/xml' and body "<authCommand><userID> </userID><password> </password></authCommand>".
I tried this in the Python using requests:
r1 = requests.post('https://abc.360.net/rest/auth/1/login.xml',data= None, auth=('sdm#company.com','*********'), headers = {'content-type':'application/xml'})
When I run this, I receive a bad request error.
Is this the correct approach? Why am I receiving this error?
auth argument is to set Authorization header (basic http authentication by default). The API expects credentials in the body:
r = request.post(url, data=xml, headers={'Content-Type': 'application/xml'})
where xml=b"<authCommand><userID>sdm#company.com</authCommand>...".

Categories