Discord API 401 Unauthorized with OAuth - python

Quick question: I'm trying to use the Discord API to make a backup of all the messages on a server (or a guild, if you use the official term).
So I implemented OAuth without any problems, I have my access token and I can query some endpoints (I tried /users/#me, /users/#me/guilds). Though, most of them don't work. For example, if I query /users/#me/channels (which is supposed to be the DMs) I get a 401 Unauthorized response from the API. It's the same if I gather a guild id from /users/#me/guilds and then try to list the channels in it with /guilds/guild.id/channels.
The really weird thing is that I do have all the scopes required (I think so, I didn't take the RPC ones since I don't think it's required for what I want to do) and I can't figure it out myself... What is also weird is that on the OAuth authorization screen, I have those two things:
It kind of counterdicts itself... :(
Do you have any ideas you'd like to share ?
Thanks!
Note: I'm using Python but I don't think it's related here, since some endpoints do work with the headers and tokens I have...
Here is my "authentication code":
baseUrl = "https://discordapp.com/api"
def authorize():
scopes = [
"guilds",
"email",
"identify",
"messages.read",
"guilds.join",
"gdm.join",
"connections"
]
urlAuthorize = "{}/oauth2/authorize?client_id={}&scope={}&response_type=code".format(baseUrl, clientid, ('+'.join(scopes)))
pyperclip.copy(urlAuthorize)
code = input("Code: ")
return code
def getAccessToken(code):
url = "{}/oauth2/token".format(baseUrl)
params = {
"client_id" : clientid,
"client_secret" : clientsecret,
"redirect_uri" : "http://localhost",
"grant_type":"authorization_code",
"code" : code,
}
req = requests.post(url, params = params)
return json.loads(req.text)
And the code related to an API request:
def getHeaders():
return {
"Authorization" : "{} {}".format("Bearer", config["accessToken"]),
# "user-agent" : "DiscordBackup/0.0.1"
}
def getRequest(endpoint, asJson = True, additional = None):
url = "{}/{}".format(baseUrl, endpoint)
req = requests.get(url, headers = getHeaders())
print()
print(getHeaders())
print(url)
print(req.text)
if asJson:
return json.loads(req.text)
else:
return req.text
def getMe(): # this works
endpoint = "users/#me"
return getRequest(endpoint)
def getMyDMs(): # this gives me a code 401 Unauthorized
endpoint = "/users/#me/channels"
return getRequest(endpoint)

I came across this post when encountering this issue, and to put it bluntly, there's no way to resolve it.
The messages.read permission is for a local RPC server; https://discordapp.com/developers/docs/topics/oauth2#shared-resources-oauth2-scopes
However, local RPC servers are in private beta and you must sign up/get accepted to use this.
I wanted to create a DM exporter, but that doesn't look likely now.

Related

Zoho CRM API: Python request-based POST or GET authentication + insertion of contacts

The Task##
A django application that allows users to sign up and once the user clicks on the account activation link, Zoho CRM is receiving the data and a contact is created in the CRM section.
The Problem
I am currently working on an absolute masterpiece - the ZOHO API.
I am struggling to set up the native Python code that uses POST/GET requests.
Regarding the zcrmsdk 3.0.0, I have completely given up on this solution unless somebody can provide a fully functional example. The support simply blames my code.
The documentation I consulted:
https://www.zoho.com/crm/developer/docs/api/v2/access-refresh.html,
https://www.zoho.com/crm/developer/docs/api/v2/insert-records.html
Since the post request in postman API works fine I do not understand why it does not work in python code
My approach
Generate an self-client API code on: https://api-console.zoho.com/
Insert that code on Postman and retrieve the access or refresh token
Use this access token in an add_user_contact function that is defined in the documentation
It works! Response is success and it is in Zoho CRM
The permsissions scope I am using is: ZohoCRM.modules.contacts.ALL, ZohoCRM.users.ALL, ZohoCRM.modules.deals.ALL, ZohoCRM.modules.attachments.ALL, ZohoCRM.settings.ALL, AAAserver.profile.ALL
Picture of Post Man POST REQUEST
My own Code
def authenticate_crm():
"""
access to response object id:
response_object.get('data')[0].get('details').get('id')
"""
url = 'https://accounts.zoho.com/oauth/v2/token'
headers = {
'Content-Type': 'application/x-www-form-urlencoded'
}
# one time self-client token here -
request_body = {
"code": "1000.aa8abec144835ab79b8f9141fa1fb170.8ab194e4e668b8452847c7080c2dd479",
"redirect_uri": "http://example.com/yourcallback",
"client_id": "1000.H95VDM1H9KCXIADGF05E0E1XSVZKFQ",
"client_secret": "290e505ec52685fa62a640d874e6560f2fc8632e97",
" grant_type": "authorization_code"
}
response = requests.post(url=url, headers=headers, data=json.dumps(request_body).encode('utf-8'))
if response is not None:
print("HTTP Status Code : " + str(response.status_code))
print(response.json())
I am essentially struggling to convert the Postman API request to a Python request to get the token as part of the workflow. What am I doing wrong here?
The documentation states: Note: For security reasons, pass the below parameters in the body of your request as form-data. (access-refresh link) but passing it in postman as form-data breaks the call completely.
According to their own documentation (which is convoluted, contradictory and full of outdated screenshots) the authentication key is needed only once.
Once the request from above runs, I would take the response in the third image and use the refresh key to add the contact.
I am also open to a solution with the SDK 3.0.0, if anybody can help.
I solved it!
I have changed this line:
response = requests.post(url=url, headers=headers, data=json.dumps(request_body).encode('utf-8'))
to this and added some return statement:
payload = '1000.6d9411488dcac999f02304d1f7843ab2.e14190ee4bae175debf00d2f87143b19&' \
'redirect_uri=http%3A%2F%2Fexample.com%2Fyourcallback&' \
'client_id=1000.H95VDM1H9KCXIADGF05E0E1XSVZKFQ&' \
'client_secret=290e505ec52685fa62a640d874e6560f2fc8632e97&'\
'grant_type=authorization_code'
response = requests.request(method="POST", url=url, headers=headers, data=payload)
if response is not None:
print("HTTP Status Code : " + str(response.status_code))
# print(response.text)
print(response.json())
# catch access and refresh token
at = response.json().get('access_token')
rt = response.json().get('refresh_token')
return at, rt
I do not understand why that is different but that fixed it and I could retrieve keys from ZOHO.

Shortening a long URL with bit.ly v4 (migrate from bit.ly v3) and python 3.7 and bitlyshortener package

I do have some tutorial code that makes use of the bit.ly API v3 for shortening an URL response. I would like to migrate this code to the v4 API but I do not understand how to set up the correct API endpoint. At least I think this is my error and the API documentation is difficult to understand and to adapt for me, as I'm a beginner.
I do have 2 files to work with:
bitlyhelper.py (this needs to be migrated to bit.ly API v4)
views.py
This is the relevant code that relates to the URL shortening.
bitlyhelper.py
import requests
#import json
TOKEN = "my_general_access_token"
ROOT_URL = "https://api-ssl.bitly.com"
SHORTEN = "/v3/shorten?access_token={}&longUrl={}"
class BitlyHelper:
def shorten_url(self, longurl):
try:
url = ROOT_URL + SHORTEN.format(TOKEN, longurl)
r = requests.get(url)
jr = r.json()
return jr['data']['url']
except Exception as e:
print (e)
views.py
from .bitlyhelper import BitlyHelper
BH = BitlyHelper()
#usr_account.route("/account/createtable", methods=["POST"])
#login_required
def account_createtable():
form = CreateTableForm(request.form)
if form.validate():
tableid = DB.add_table(form.tablenumber.data, current_user.get_id())
new_url = BH.shorten_url(config.base_url + "newrequest/" + tableid)
DB.update_table(tableid, new_url)
return redirect(url_for('account.account'))
return render_template("account.html", createtableform=form, tables=DB.get_tables(current_user.get_id()))
The code does work fine when using the v3 API.
I tried a few combinations of the API endpoint, but couldn't get it to work.
for example simply changing the version number does not work.
SHORTEN = "/v4/shorten?access_token={}&longUrl={}"
It would be great if someone could help with setting up the proper API endpoint.
Here is the API documentation: https://dev.bitly.com/v4_documentation.html
This is the relevant part I think:
import requests
header = {
"Authorization": "Bearer <TOKEN HERE>",
"Content-Type": "application/json"
}
params = {
"long_url": form.url.data
}
response = requests.post("https://api-ssl.bitly.com/v4/shorten", json=params, headers=header)
data = response.json()
if 'link' in data.keys(): short_link = data['link']
else: short_link = None
First you want to create the request header with your bearer token.
Make sure to set the content type too.
After setting your header you have to provide the url you would like to shorten for example.
The data variable contains your 201 request.
I got some help and ended up using bitlyshortener from here: https://pypi.org/project/bitlyshortener/
In this way:
from bitlyshortener import Shortener
tokens_pool = ['bitly_general_access_token'] # Use your own.
shortener = Shortener(tokens=tokens_pool, max_cache_size=128)
#usr_account.route("/account/createtable", methods=["POST"])
def account_createtable():
form = CreateTableForm(request.form)
if form.validate():
tableid = DB.add_table(form.tablenumber.data, current_user.get_id())
new_urls = [f'{config.base_url}newrequest/{tableid}']
short_url = shortener.shorten_urls(new_urls)[0]
DB.update_table(tableid, short_url)
return redirect(url_for('account.account'))
return render_template("account.html", createtableform=form, tables=DB.get_tables(current_user.get_id()))
This does work perfectly fine, and bitlyshortener even adds the s to https://my_shortlink automagically.
It makes sense that your v3 code fails against v4, based on this:
Authentication
How you authenticate to the Bitly API has changed with V4. Previously your authentication token would be provided as the access_token query parameter on each request. V4 instead requires that the token be provided as part of the Authorization header on each request.
You will want to move your token out of the args and into the header.
Using https://github.com/bitly/bitly-api-python might possibly be an option,
except that it is some years since it was last updated.

Problem when getting 'Authorization' header on test Flask application with FlaskClient

I'm testing my Flask application using FlaskClient, in order to avoid to run a Flask server always when I'm testing my application.
I've created a 'sign_in' view that returns an 'Authorization' header with a encrypted token when the user logs successfully in my front-end.
This view works normally in a normal environment, it returns the 'Authorization' header correctly, however when I'm testing this view, inside the test environment, it does not return the 'Authorization' header. The view returns None in 'Authorization' header.
I've already tried some solutions on the internet, such as to add self.app.config['TESTING'] = True in my test case, but the terminal raises an error 'FlaskClient' object has no attribute 'config' that I've already tried to look for a solution, but without success.
I would like to know what may be happening.
Does anyone know any solution for this question?
I send my code below for analysis.
Thank you in advance.
view.py
#app.route("/sign_in", methods = ["POST"])
def sign_in():
...
username, password = ...
try:
encoded_jwt_token = auth_login(username, password)
except UserDoesNotExistException as error:
return str(error), error.status_code
resp = Response("Returned Token")
resp.headers['Authorization'] = encoded_jwt_token
return resp
test.py
class TestAPIAuthLogin(TestCase):
def setUp(self):
self.app = catalog_app.test_client()
# self.app.config['TESTING'] = True # config does not exist
def test_get_api_auth_login_user_test(self):
username = "test"
password = get_string_in_hash_sha512("test")
authorization = 'Basic ' + get_string_in_base64(username + ":" + password)
headers = {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json',
'Authorization': authorization
}
response = self.app.get('/sign_in', headers=headers)
# it returns None
authorization = response.headers.get("Authorization")
self.assertIsNotNone(authorization)
self.assertNotEqual(authorization, "")
I believe this may be to do with the way HTTP requests process headers, where they capitalise them and add HTTP_ as a prefix. Try changing your header to HTTP_AUTHORIZATION instead of just Authorization, since the test client will not be setting this properly.
I'm sorry guys for this silly question.
Now I've figure the answer out.
The problem was that I was trying to do a GET request in a view that is using a POST method.
I've just replaced the request from
response = self.app.get('/sign_in', headers=headers)
to
response = self.app.post('/sign_in', headers=headers)
and now it started to work.
I will let this question here in case of someone gets the same silly error.
Thank you so much.
If similar issue for DRF clients, You can use,
client = APIClient()
client.credentials(HTTP_AUTHORIZATION='Token {token}'.format(token=token))
Ref: https://www.django-rest-framework.org/api-guide/testing/#credentialskwargs

Refreshing google access tokens with the refresh tokens

How can I refresh the access tokens in google with the refresh token in python ? I have the refresh token, client id and client secret with me
I have tried with the following code for generating the access token (after expiry)
params = {
"grant_type": "refresh_token",
"client_id": client_id,
"client_secret": client_secret,
"refresh_token": refresh_token
}
authorization_url = "https://www.googleapis.com/oauth2/v4/token"
r = requests.post(authorization_url, data=params)
print(r.text)
if r.ok:
return r.json()['access_token']
else:
return None
I got an error in response like this:
{
"error": "invalid_grant",
"error_description": "Bad Request"
}
But I need to generate the access token.
These are the three top reasons for it not to be working. I am not a Python dev so cant test your code sorry.
Send parms as a string
Check your parms make sure they are sent as a single string separated with &.
Post https://accounts.google.com/o/oauth2/token
client_id={ClientId}&client_secret={ClientSecret}&refresh_token=1/ffYmfI0sjR54Ft9oupubLzrJhD1hZS5tWQcyAvNECCA&grant_type=refresh_token
content type header
Also i think you need to set the content type header to something like application/x-www-form-urlencoded.
Change endpoint
If that doesnt work
Also try using https://accounts.google.com/o/oauth2/token instead of https://www.googleapis.com/oauth2/v4/token I remember there being some issue with one or the other of them sometimes

Amazon Alexa Proactive Events request in Python flask-ask

I am trying to make a request to the Proactive Events API by using the requests module of Python.
However I always receive a response that the scope is invalid.
Can anyone help? What am I doing wrong? My code looks like this:
#ask.launch
def launch():
content_type = "application/x-www-form-urlencoded;charset=utf-8"
client_id = "amzn1.application-oa2-client.6a48XXXXXXX408"
client_secret = "592XXXXxxxxxxx6"
scope = "alexa::proactive_events"
grant_type = "client_credentials"
data = {"grant_type": grant_type, "client_id": client_id, "client_secret": client_secret, "scope": scope}
r = requests.post("https://api.amazon.com/auth/O2/token", data=data, headers={"content-type": content_type})
speech = render_template("welcome")
reprompt = render_template("welcome_reprompt")
return question(speech).reprompt(reprompt)
That is the response I get:
{'error_description': 'The request has an invalid parameter : scope', 'error': 'invalid_scope'}
Since one of the reason you get the invalid scope is that you dont have the events included in your skill manifest I include some steps here. I found quite cumbersome to use the SMAPI to update the skill manifest so instead I used ask cli.
install ask-cli: get authorization code for your amazon acount. In my case the backend is not an AWS lambda function but an external web server
get the skill manifest in json format:
ask api get-skill -s "amzn1.ask.skill.ZZZYYYZZ" --stage development > skill.json
Add the notifications permission and the events elements to the manifest:
{
"name": "alexa::devices:all:notifications:write"
}
and
"events": {
"publications": [
{
"eventName": "AMAZON.AAABBBCC"
}
],
"endpoint": {
"uri": "https://XXXYYYZZ:443/whatevercontext"
}
}
update the manifest:
ask api update-skill -s "amzn1.ask.skill.ZZZYYYZZ" --stage development -f skill.json
enable the notifications in the alexa app for your specific skill
Now you should be able to get the token and next step is to send the notification to the device
Have you tried making the API call via any other method? I just tried that with Postman and it worked for me.
My Python's a bit rusty, but here's the self generated code from Postman for Python. May be this should help?
import http.client
conn = http.client.HTTPConnection("api,amazon,com")
payload = "grant_type=client_credentials&client_id=amzn1.application-oa2-client.whatever-value&client_secret=client-secret&scope=alexa%3A%3Aproactive_events&undefined="
headers = {
'Content-Type': "application/x-www-form-urlencoded"
}
conn.request("POST", "auth,O2,token", payload, headers)
res = conn.getresponse()
data = res.read()
print(data.decode("utf-8"))
As Karthik asked previously have you tried the call via postman? I first suggest trying to via postman before you trying to code it.
If the issue still persists its most likely because you haven't included the necessary permissions required for Proactive Events in your Skill Manifest.
To add the necessary permissions to Skill Manifest you need to use the Skill Management API & ASK Cli.
Follow this section of the documentation and visit the links referenced there to correctly add the required permissions to your skill - https://developer.amazon.com/docs/smapi/proactive-events-api.html#onboard-smapi
Once you have successfully added the events and publications with a skill schema you should be able to successfully generate a token.
Please feel to ask if you want me to elaborate more on the exact steps.
Cheers!
This is what I have tried and it worked:
amazon_token_url = "https://api.amazon.com/auth/O2/token"
headers = {'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'}
body = "grant_type=client_credentials&client_id=amzn1.application-oa2-client.XXXYYYZZ&client_secret=ZZZYYYXXX&scope=alexa::proactive_events"
log.debug("Sending token request with body: %s", body)
resp = requests.post(amazon_token_url, data=body, headers=headers)
resp_json = json.loads(resp.content.decode('utf-8'))
if (resp.status_code < 299) and (resp.status_code > 199):
log.debug("token received from Amazon")
log.debug("Content : %s", resp.content)
token = resp_json["access_token"]
return token

Categories