Netsuite: OAuth1 / Token-Based Authentication where to put the state parameter - python

I want to connect to Netsuite via Token-Based Authentication (i.e. OAuth1) as documented here.
In the section Step One Obtain An Unauthorized Request Token it is written that an optional state parameter can be added to the Authorization Header. There they also refer to RFC 6749, Section 4.1.1 for further information. However, what is explained there has nothing to do with OAuth1.0 but Oauth2.0.
The reason why I depend on the state parameter is that I have the url to which the callback server shall forward the request after the authorization is done encoded in it (using JWT).
Now when I create the OAuth1 authorization header with oauthlib using the sign method from oauthlib.oauth1.Client in Python
from oauthlib.oauth1 import SIGNATURE_HMAC_SHA256
from oauthlib.oauth1 import Client
client = Client(client_key=CONSUMER_KEY,
client_secret=CONSUMER_SECRET,
callback_uri=CALLBACK_URL,
signature_method=SIGNATURE_HMAC_SHA256)
uri, headers, body = client.sign(uri="https://123456.restlets.api.netsuite.com/rest/requesttoken", http_method='POST')
I get this for headers:
headers = {
'Authorization': 'OAuth oauth_nonce="123..", oauth_timestamp="163...", oauth_version="1.0", oauth_signature_method="HMAC-SHA256", oauth_consumer_key="f18...", oauth_callback="...", oauth_signature="9kae..."'
}
I can acquire the temporary credentials when sending the request with this headers to https://123456.restlets.api.netsuite.com/rest/requesttoken.
Still as I need the state parameter later on on my side I somehow need to add the state parameter to the authorization header (at least that is what Netsuite says in their documentation).
When I add my state parameter to the authorization header (the one created before by the sign method from oauthlib.oauth1.Client) like this
from oauthlib.common import to_unicode
headers["Authorization"] = f'{headers["Authorization"]}, state="{to_unicode(data=state, encoding="UTF-8")}"'
which results in this for headers (I will refer to it as new_headers):
# headers with state appended to Authorization
headers = {'Authorization': 'OAuth oauth_nonce="123...", oauth_timestamp="163...", oauth_version="1.0", oauth_signature_method="HMAC-SHA256", oauth_consumer_key="f18...", oauth_callback="...", oauth_signature="9kae...", state="eyJ0..."'}
I get this response when trying to send a request to the request token url with this header:
{"error" : {"code" : "USER_ERROR", "message" : "Invalid login attempt."}}
When I do it differently (not as specified in the doc) and add the state parameter to the request token url like this "https://123456.restlets.api.netsuite.com/rest/requesttoken?state=eyJ0..." and send the previous header with authorization not including the state (i.e. headers) I again get to the login page. So I can assume this could work.
My problem is that I cannot test it at the moment with a Netsuite account so I just need to implement it according to the documentation and hope that I send the state parameter in the right way and it is forwarded to the callback server after a user logs in.
Now my question is:
Is the documentation correct and the state parameter needs to be added to the authorization header like in "new_headers" above and I just do sth wrong here. If so what am I doing wrong here?
Or is the documentation misleading as simply adding the state parameter as a normal query parameter to the request token url like this "https://123456.restlets.api.netsuite.com/rest/requesttoken?state=eyJ0..." is correct?
I would really appreciate some help here!
Best regards,
JayKay

Related

FastAPI's RedirectResponse doesn't work as expected in Swagger UI

I have a FastAPI app with a download endpoint. What this download endpoint does is to use a BlobServiceClient (for Azure Blob Storage) to generate a token and a Blob URL to a file specified in the request. What I want to do is to redirect the user to that URL. Here is a code snippet of the download enpoint (I commented some things out because I'm not allowed to show the code).
#router.get("..path", tags=["some tags"], summary=..., responses={404: {"model": ...}, 403: {"model": ...}, 307: {"model": ...}}, response_model_exclude_none=True)
async def download_file(
# there's a depends on an API key
blob_path: str = Query(
...
)):
credential = ClientSecretCredential(...) //secrets
blob_service_client = BlobServiceClient(f"https://{storage_account}.blob.core.windows.net", credential=credential)
user_delegation_key = blob_service_client.get_user_delegation_key(key_start_time=datetime.utcnow(),key_expiry_time=datetime.utcnow() + timedelta(minutes=30))
token = generate_blob_sas(account_name=...,
container_name=...,
blob_name=blob_path,
user_delegation_key=user_delegation_key,
permission=BlobSasPermissions(read=True),
expiry=datetime.utcnow() + timedelta(minutes=30))
blob_url = f'https://{storage_account}.blob.core.windows.net/{container_name}/{blob_path}?{token}'
print(blob_url)
response = RedirectResponse(blob_url)
return response
What I expected is the query to be executed, and after the response is returned, the download to start in the background or in a separate tab. What I've got instead is a different response as you can see in the Swagger:
I also had a look in the Network tab to see what is happening with that request:
Looks like there is an OPTIONS request and I assume that I'm getting the response to that request. Not sure if this is how Swagger handles the request. Any idea how/why this is happening and how to fix it? Thank you!
To start with, the HTTP OPTIONS, in CORS, is a preflight request that is automatically issued by the browser, before the actual request—is not the one that returns the File response. It requests the permitted communication options for a given server, and the server responds with an Access-Control-Allow-Methods header including a set of permitted methods (e.g., Access-Control-Allow-Methods: OPTIONS, GET, HEAD, POST, DELETE). The preflight response can be optionally cached for the requests created in the same URL using Access-Control-Max-Age header, thus allowing the server to limit the number of preflight requests. The value of this header is expressed in seconds; hence, allowing caching for 10 minutes, for example, would look as Access-Control-Max-Age: 600.
As for the RedirectResponse, Swagger UI always follows redirect responses. In a fetch request, for instance, the redirect parameter would be set to follow, indicating that the redirect should be followed. This means that Swagger UI follows the redirect and waits for the response to be completely received before providing you with a Download file link (as shown in the screenshot you provided above) that would allow you to download the file. That is also why you can't see the download starting either in the background or in a new tab. As mentioned in the linked github post above, it is not possible to change that behaviour, which could allow you to handle it differently, similar to the approach demonstrated in this answer.
Instead of using Swagger UI to test that specific endpoint, you can either test it directly through typing the URL to your API endpoint in the address bar of your browser (since it is a GET endpoint, you can do that, as when you type a URL in the address bar of your browser, it performs a GET request), or create your own custom Template (or use HTMLResponse) and submit an HTML <form>, as shown in this answer.

Automatically getting Mojang bearer token

I am writing a program that can change your Mojang username at whatever time you specify (i.e. the name "tom" becomes available at 09:05:39 so you would set the program to change your name to "tom" at that time). To do this you need a bearer token. I can log into minecraft.net and use EditThisCookie to access that, but I want to be able to have the program wait until it is within a minute of the name dropping to gather proxies and the bearer token automatically for situations where a name is becoming available in the middle of the night or I'm just lazy. I'm not sure why my function for getting the bearer token won't work. I referenced this to write this code.
def getAT():
jsonForAT = json.dumps({"agent":{"name":"Minecraft","version":1},"username":email,"password":password,"clientToken":""})
headersForAT = {'Content-Type': 'application/json'}
requestForAT = requests.post('https://authserver.mojang.com/authenticate', data=jsonForAT, headers=headersForAT)
pullATRequestData = requestForAT.json()
AT = pullATRequestData["accessToken"]
return AT
I am really confused since when I use this, I get a 401 error but the program works when I get the token manually.
I also was experiencing the same issue. Then, I realized that Minecraft/Mojang accounts have security questions. If your function is getting the bearer token and your issue is that your access is denied when posting a name change request:
1st make sure your formatting it properly:
headers={'Authorization': f"Bearer {TOKEN}"
Notice the space between "Bearer" and {TOKEN}
2nd: For some reason, Mojang needs you to send a Get request to their security challenges endpoint. You don't have to do anything with the returned data..
When you got the token manually, I'm assuming your browser automatically sent this get request and that's why it worked. If you're using proxies, you'll probably need to send a post request with your security question answers, look at the API link for documentation.
For Example:
res = requests.get(
"https://api.mojang.com/user/security/challenges",
headers={"Authorization": f"Bearer {TOKEN}"},
)
https://wiki.vg/Mojang_API
Go to the bottom under "Security question-answer flow"

How to access the token value without the 'Basic" part using request library?

I am receiving the following token from an application and servicing it at an endpoint in the Flask.
I have the following issue. I want to access the token which is stored in the header of the https packet.
With the code below, I get the a string like this, "Basic veliq89#ei". I do not want to have the Basic in the token_string. I am not sure on how to just access the token value which is veliq89#ei.
token_string = request.environ.get('HTTP_AUTHORIZATION')
Authorization headers usually include method of authentication as the first word in the header value. You simply have to split the header value and get the last part as the token value
token_header = request.environ.get('HTTP_AUTHORIZATION') # "Basic veliq89#ei"
token = token_header.split(maxsplit=1)[1] # "veliq89#ei"

Requests works and URLFetch doesn't

I'm trying to make a request to the particle servers in python in a google app engine app.
In my terminal, I can complete the request simply and successfully with requests as:
res = requests.get('https://api.particle.io/v1/devices', params={"access_token": {ACCESS_TOKEN}})
But in my app, the same thing doesn't work with urlfetch, which keeps telling me it can't find the access token:
url = 'https://api.particle.io/v1/devices'
payload = {"access_token": {ACCESS_TOKEN}}
form_data = urllib.urlencode(payload)
res = urlfetch.fetch(
url=url,
payload=form_data,
method=urlfetch.GET,
headers={
'Content-Type':
'application/x-www-form-urlencoded'
},
follow_redirects=False
)
I have no idea what the problem is, and no way to debug. Thanks!
In a nutshell, your problem is that in your urlfetch sample you're embedding your access token into the request body, and since you're issuing a GET request -which cannot carry any request body with them- this information gets discarded.
Why does your first snippet work?
Because requests.get() takes that optional params argument that means: "take this dictionary I give you, convert all its key/value pairs into a query string and append it to the main URL"
So, behind the curtains, requests.get() is building a string like this:
https://api.particle.io/v1/devices?access_token=ACCESS_TOKEN
That's the correct endpoint you should point your GET requests to.
Why doesn't your second snippet work?
This time, urlfetch.fetch() uses a different syntax than requests.get() (but equivalent nonetheless). The important bit to note here is that payload argument doesn't mean the same as our params argument that you used before in requests.get().
urlfetch.fetch() expects our query string -if any- to be already urlencoded into the URL (that's why urllib.urlencode() comes into play here). On the other hand, payload is where you should put your request body in case you were issuing a POST, PUT or PATCH request, but particle.io's endpoint is not expecting your OAuth access token to be there.
Something like this should work (disclaimer: not tested):
auth = {"access_token": {ACCESS_TOKEN}}
url_params = urllib.urlencode(auth)
url = 'https://api.particle.io/v1/devices?%s' % url_params
res = urlfetch.fetch(
url=url,
method=urlfetch.GET,
follow_redirects=False
)
Notice how now we don't need your previous Content-type header anymore, since we aren't carrying any content after all. Hence, headers parameter can be removed from this example call.
For further reference, take a look at urlfetch.fetch() reference and this SO thread that will hopefully give you a better insight into HTTP methods, parameters and request bodies than my poor explanation here.
PS: If particle.io servers support it (they should), you should move away from this authentication schema and carry your tokens in a Authorization: Bearer <access_token> header instead. Carrying access tokens in URLs is not a good idea because they are much more visible that way and tend to stay logged in servers, hence posing a security risk. On the other hand, in a TLS session all request headers are always encrypted so your auth tokens are well hidden there.
Ok, so, as it turns out, one cannot include a payload for a GET request using Urlfetch. Instead, one has to include the parameters in the url using the '?' syntax as follows:
url = 'https://api.particle.io/v1/devices'
url = url + '?access_token=' + {ACCESS_TOKEN}
res = urlfetch.fetch(
url=url,
method=urlfetch.GET,
follow_redirects=False
)
this worked for me.

Yggdrasil authentication with Python

I decided to try to make an automated login script for Minecraft. However, the new authentication API is stumping me. I can't find any mentions of the new functionality of the API on here. This is my code as it stands:
import requests
import json
data = json.dumps({"agent":{"name":"Minecraft","version":1},"username":"abcdef","password":"abcdef","clientToken":""})
headers = {'Content-Type': 'application/json'}
r = requests.post('https://authserver.mojang.com', data=data, headers=headers)
print (r.text)
Unfortunately, this returns:
{"error":"Method Not Allowed","errorMessage":"The method specified in the request is not allowed for the resource identified by the request URI"}
According to this resource on request format, this error means that I didn't correctly send a post request. However, I clearly declared requests.post(), so my first question is how am I incorrect, and what is the correct way to go about this?
My second question is, since I'm relatively new to Python and JSON, how would I replace the username and password fields with my own data, inside a variable?
You haven't specified an endpoint in your POST request, for example:
https://authserver.mojang.com/authenticate
The root of the website probably does not accept POST requests
http://wiki.vg/Authentication#Authenticate

Categories

Resources