I want to access AWS AppSync API using Python code and confused with requests library.
Auth mode is Cognito user pool. My questions are:
How to get access tokens from Cognito user pool?
How to make queries, mutations, and handle subscriptions?
I tried to do it with auth mode API key. but I am getting the following error.
import requests
import json
URL = "https://vtcarmq7zzeadnkwzcgfr24irm.appsync-api.us-east-1.amazonaws.com/graphql"
headers = {"x-api-key":"da2-bwuyzqchhfgyxemcmdinjegb7e"}
data = json.dumps({
"query": '''
listTodos(filter:{
title:{
contains:"g"
}
} ) {
items{
id title duedate
}
}
'''
} )
r = requests.request("POST", URL , data = data , headers = headers)
print(r.text)
{ "errors" : [ {
"message" : "Unable to parse GraphQL query.",
"errorType" : "MalformedHttpRequestException" } ] }
I have seen this video https://www.youtube.com/watch?v=2U4RsbFO4bA&t=1172s
In this video, for authentication using cognito user pool, he says to make a call to cognito user pool and get the tokens and pass it to aws appsync in headers.
I am new to aws and python request module, trying to write python code for this video.
graphql-python/gql supports AWS AppSync since version 3.0.0rc0.
It supports queries, mutation and even subscriptions on the realtime endpoint.
It supports IAM, api key and JWT authentication methods.
The documentation is available here
Here is an example of a mutation using the API Key authentication:
import asyncio
import os
import sys
from urllib.parse import urlparse
from gql import Client, gql
from gql.transport.aiohttp import AIOHTTPTransport
from gql.transport.appsync_auth import AppSyncApiKeyAuthentication
# Uncomment the following lines to enable debug output
# import logging
# logging.basicConfig(level=logging.DEBUG)
async def main():
# Should look like:
# https://XXXXXXXXXXXXXXXXXXXXXXXXXX.appsync-api.REGION.amazonaws.com/graphql
url = os.environ.get("AWS_GRAPHQL_API_ENDPOINT")
api_key = os.environ.get("AWS_GRAPHQL_API_KEY")
if url is None or api_key is None:
print("Missing environment variables")
sys.exit()
# Extract host from url
host = str(urlparse(url).netloc)
auth = AppSyncApiKeyAuthentication(host=host, api_key=api_key)
transport = AIOHTTPTransport(url=url, auth=auth)
async with Client(
transport=transport, fetch_schema_from_transport=False,
) as session:
query = gql(
"""
mutation createMessage($message: String!) {
createMessage(input: {message: $message}) {
id
message
createdAt
}
}"""
)
variable_values = {"message": "Hello world!"}
result = await session.execute(query, variable_values=variable_values)
print(result)
asyncio.run(main())
Related
I'm developing a discord bot in py. But i can't seem to figure out how to check if a user is live on twitch. When i check for their stream title. I could make the bot announce the streams if the word stream was in it. But the bot kept checking it over and over again breaking the channel. What other solution is there to check for a streamer being live? I couldnt figure out how the api works
A common approach using python Requests library, this will be able to tell you IF a user is live:
import requests
CLIENT_ID = 'my-twitch-client-id'
OAUTH_TOKEN = 'my-twitch-oauth-token'
def is_user_live(username):
endpoint = 'https://api.twitch.tv/helix/streams'
my_headers = {
'Client-ID': CLIENT_ID,
'Authorization': f'Bearer {OAUTH_TOKEN}'
}
my_params = {'user_login': username}
response = requests.get(endpoint, headers=my_headers, params=my_params)
data = response.json()['data']
if len(data) == 0:
return False
return data[0]['type'] == 'live'
What I'd recommend for your bot is that you subscribe to the events webhook when you initialize the bot. You will need to create a webserver to handle the POST requests that Twitch will send you, this will be able to tell you WHEN a user goes live. A cruuude example using Flask:
import requests
import json
from flask import request
CLIENT_ID = 'my-twitch-client-id'
OAUTH_TOKEN = 'my-twitch-oauth-token'
MY_SECRET = 'an-arbitrary-secret'
app = your_flask_app()
#app.route('/my_webhook/<user_id>')
def my_webhook(user_id):
check_secret(request) # sha256 of your secret and content-length
data = request.get_json()['data']
if len(data) > 0:
your_bot.user_is_live(data)
else:
your_bot.user_is_offline(data)
return 'OK'
def subscribe_to_webhook(user_id):
endpoint = 'https://api.twitch.tv/helix/webhooks/hub'
topic = 'https://api.twitch.tv/helix/streams'
my_headers = {
'Client-ID': CLIENT_ID,
'Authorization': f'Bearer {OAUTH_TOKEN}'
}
payload = {
'hub.callback': f'http://my_server.url/my_webhook/{user_id}',
'hub.mode': 'subscribe',
'hub.topic': f'{topic}?user_id={user_id}',
'hub.lease_seconds': 864000,
'hub.secret': MY_SECRET
}
response = requests.post(endpoint, headers=my_headers, data=json.dumps(payload))
return response.ok
I have a problem working with python-quickbooks package, I try to follow the docs: https://pypi.org/project/python-quickbooks/
Here is my code:
from django.conf import settings
from intuitlib.client import AuthClient
from quickbooks import QuickBooks
from quickbooks.objects.account import Account
auth_client = AuthClient(
client_id=settings.QUICKBOOKS_CLIENT_ID,
client_secret=settings.QUICKBOOKS_CLIENT_SECRET,
environment='sandbox',
redirect_uri=settings.QUICKBOOKS_REDIRECT_URI,
)
client = QuickBooks(
auth_client=auth_client,
refresh_token=settings.QUICKBOOKS_REFRESH_TOKEN,
company_id=settings.QUICKBOOKS_REALM_ID
)
account = Account()
account.from_json(
{
"AccountType": "Accounts Receivable",
"Name": "MyJobs"
}
)
account.save(qb=client)
However, this results in error:
What am I doing wrong here?
You have to provide ACCESS_TOKEN in AuthClient.
In order to get an access token, you have to pass authorization. You can check details about the authorization process here https://developer.intuit.com/app/developer/qbo/docs/develop/authentication-and-authorization/oauth-2.0-playground
Also here is a repo with an example of how to use AuthClient: https://github.com/IntuitDeveloper/SampleOAuth2_UsingPythonClient
from intuitlib.client import AuthClient
from quickbooks.client import QuickBooks, Environments
auth_client = AuthClient(CLIENT_ID, CLIENT_SECRET, REDIRECT_URL, Environments.SANDBOX, ACCESS_TOKEN)
qbo_client = QuickBooks(
auth_client=auth_client,
refresh_token=REFRESH_TOKEN,
company_id=REALM_ID,
)
I found a piece of code on Azure documentation that allows getting credentials without MFA. But I'm wondering if is possible to use it to connect to PowerBI API.
The piece of code that I'm using is:
import adal
import requests
from msrestazure.azure_active_directory import AADTokenCredentials
def authenticate_client_key():
authority_host_uri = 'https://login.microsoftonline.com'
tenant = 'tenant'
authority_uri = authority_host_uri + '/' + tenant
resource_uri = 'https://management.core.windows.net/'
client_id = 'clientid'
client_secret = 'client-secret'
context = adal.AuthenticationContext(authority_uri, api_version=None)
mgmt_token = context.acquire_token_with_client_credentials(resource_uri, client_id, client_secret)
credentials = AADTokenCredentials(mgmt_token, client_id)
return credentials
source: https://azure.microsoft.com/en-us/resources/samples/data-lake-analytics-python-auth-options/
According to the code written on PowerShell, the aim is to insert the access_token into the header of the following POST request
POST https://api.powerbi.com/v1.0/myorg/groups/me/datasets/{dataset_id}/refreshes
Source:https://powerbi.microsoft.com/en-us/blog/announcing-data-refresh-apis-in-the-power-bi-service/
I have tried to use the credentials into the POST request, but seems is not working.
I have tried
url = 'https://api.powerbi.com/v1.0/myorg/groups/me/datasets/datasetid/refreshes'
requests.post(url,data=mgmt_token)
Is it possible to merge this two codes?
Regards,
You can use the pypowerbi package to refresh Power BI datasets or you can check how to do it yourself by inspecting the code. https://github.com/cmberryau/pypowerbi
pip install pypowerbi
import adal
from pypowerbi.client import PowerBIClient
# you might need to change these, but i doubt it
authority_url = 'https://login.windows.net/common'
resource_url = 'https://analysis.windows.net/powerbi/api'
api_url = 'https://api.powerbi.com'
# change these to your credentials
client_id = '00000000-0000-0000-0000-000000000000'
username = 'someone#somecompany.com'
password = 'averygoodpassword'
# first you need to authenticate using adal
context = adal.AuthenticationContext(authority=authority_url,
validate_authority=True,
api_version=None)
# get your authentication token
token = context.acquire_token_with_username_password(resource=resource_url,
client_id=client_id,
username=username,
password=password)
# create your powerbi api client
client = PowerBIClient(api_url, token)
# Refresh the desired dataset (dataset and group IDs can be taken from the browser URL)
client.datasets.refresh_dataset(dataset_id='data-set-id-goes-here',
notify_option='MailOnCompletion',
group_id='group-id-goes-here')
Your code for acquiring an access token looks ok, but to use it with Power BI REST API, you must change resource_uri to be https://analysis.windows.net/powerbi/api.
When making a request to Power BI REST API, you must add Authorization header with value Bearer {accessToken}, where {accessToken} is the token acquired. I can't write in python, but you should do something like this:
headers = {'Authorization': 'Bearer ' + accessToken, 'Content-Type': 'application/json'}
url = 'https://api.powerbi.com/v1.0/myorg/groups/me/datasets/datasetid/refreshes'
requests.post(url, headers=headers)
(of course, you need to replace datasetid with actual value in url).
For example, here is how it can be done in C#:
string redirectUri = "https://login.live.com/oauth20_desktop.srf";
string resourceUri = "https://analysis.windows.net/powerbi/api";
string authorityUri = "https://login.windows.net/common/oauth2/authorize";
string clientId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
string powerBIApiUrl = $"https://api.powerbi.com/v1.0/myorg/datasets/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/refreshes";
AuthenticationContext authContext = new AuthenticationContext(authorityUri, new TokenCache());
var authenticationResult = await authContext.AcquireTokenAsync(resourceUri, clientId, new Uri(redirectUri), new PlatformParameters(PromptBehavior.Auto));
var accessToken = authenticationResult.AccessToken;
var request = WebRequest.Create(powerBIApiUrl) as HttpWebRequest;
request.KeepAlive = true;
request.Method = "POST";
request.ContentLength = 0;
request.Headers.Add("Authorization", String.Format("Bearer {0}", accessToken));
using (Stream writer = request.GetRequestStream())
{
var response = (HttpWebResponse)request.GetResponse();
}
I'm using Google API, specifically Youtube's Data API, to get comments above a video, using video ID. I'm using python example from Google Documentation here, but I've the following error:
Missing property "redirect_uris" in a client type of "web"
So, this is my client_secrets_....json file:
{
"installed": {
"client_id":"xxxxxxxxxxxx-xxxxje3a7fk4jbs63g6m1a2mand4sa3h.apps.googleusercontent.com",
"project_id":"progettotesicuozzo",
"auth_uri":"https://accounts.google.com/o/oauth2/auth",
"token_uri":"https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs",
"client_secret":"XXXXXXXXXXX",
"redirect_uris":[
"urn:ietf:wg:oauth:2.0:oob",
"http://localhost"
]
}
}
This is the code:
# Usage example:
# python test.py --videoid='<video_id>' --text='<text>'
import httplib2
import os
import sys
import json
from apiclient.discovery import build_from_document
from apiclient.errors import HttpError
from oauth2client.client import flow_from_clientsecrets
from oauth2client.file import Storage
from oauth2client.tools import argparser, run_flow
CLIENT_SECRETS_FILE = "./client_secret_....json"
YOUTUBE_READ_ONLY_SCOPE = "https://www.googleapis.com/auth/youtube.readonly"
YOUTUBE_API_SERVICE_NAME = "youtube"
YOUTUBE_API_VERSION = "v3"
MISSING_CLIENT_SECRETS_MESSAGE = """WARNING: Please configure OAuth 2.0
To make this sample run you will need to populate the client_secrets.json file found at: %s with information from the APIs Console https://console.developers.google.com
For more information about the client_secrets.json file format, please visit: https://developers.google.com /api-client-library/python/guide/aaa_client_secrets""" % os.path.abspath(os.path.join(os.path.dirname(__file__), CLIENT_SECRETS_FILE))
def get_authenticated_service(args):
flow = flow_from_clientsecrets(CLIENT_SECRETS_FILE, scope=YOUTUBE_READ_ONLY_SCOPE, message=MISSING_CLIENT_SECRETS_MESSAGE)
storage = Storage("%s-oauth2.json" % sys.argv[0])
credentials = storage.get()
if credentials is None or credentials.invalid:
credentials = run_flow(flow, storage, args)
with open("youtube-v3-discoverydocument.json", "r", encoding="utf8") as f:
doc = f.read()
return build_from_document(doc, http=credentials.authorize(httplib2.Http()))
def get_comment_threads(youtube, video_id):
results = youtube.commentThreads().list(
part="snippet",
videoId=video_id,
textFormat="plainText"
).execute()
for item in results["items"]:
comment = item["snippet"]["topLevelComment"]
author = comment["snippet"]["authorDisplayName"]
text = comment["snippet"]["textDisplay"]
print("Comment by " + author + ": " + text)
return results["items"]
def get_comments(youtube, parent_id):
results = youtube.comments().list(
part="snippet",
parentId=parent_id,
textFormat="plainText"
).execute()
for item in results["items"]:
author = item["snippet"]["authorDisplayName"]
text = item["snippet"]["textDisplay"]
print("Comment by " + author + ": " + text)
return results["items"]
if __name__ == '__main__':
# The "videoid" option specifies the YouTube video ID that uniquely
# identifies the video for which the comment will be inserted.
argparser.add_argument("--videoid", help="Required; ID for video for which the comment will be inserted.")
# The "text" option specifies the text that will be used as comment.
#argparser.add_argument("--text", help="Required; text that will be used as comment.")
args = argparser.parse_args()
if not args.videoid:
exit("Please specify videoid using the --videoid= parameter.")
youtube = get_authenticated_service(args)
# All the available methods are used in sequence just for the sake of an example.
try:
video_comment_threads = get_comment_threads(youtube, args.videoid)
parent_id = video_comment_threads[0]["id"]
video_comments = get_comments(youtube, parent_id)
except HttpError as e:
print("An HTTP error " + str(e.resp.status) + " occurred:\n" + json.dumps(str(e.content)))
else:
print("Inserted, listed, updated, moderated, marked and deleted comments.")
How can I configure well my json file, please?
After fixing my Json file I am now faced with this error:
An HTTP error 403 occurred:
{
"error": {
"errors": [
{
"domain": "global",
"reason": "insufficientPermissions",
"message": "Insufficient Permission"
}
],
"code": 403,
"message": "Insufficient Permission"
}
}
I've requested a my personal video, using the ID from the web URL, but I got the error, after I've been prompted to the redirect_uri.
When you create your project on google developer console you will be prompted to download the JSon file. You should not change this file you appear to have removed the redirect URI. Redirect URI is created as part of browser credentials. It is needed to tell the server where to return the response to.
Example:
{
"web":{
"client_id":"xxxxx-i6cjd1hkjntu5bkdkjj5cdnpcu4iju8p.apps.googleusercontent.com",
"project_id":"daimto-tutorials-101",
"auth_uri":"https://accounts.google.com/o/oauth2/auth",
"token_uri":"https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs",
"client_secret":"xxxxx",
"redirect_uris":[
"http://localhost/google-api-php-client-samples/Analytics/Oauth2.php",
"http://localhost/google-api-php-client-samples/Calendar/Oauth2.php"
],
"javascript_origins":[
"https://www.example.com"
]
}
}
Authorized redirect URIs For use with requests from a web server. This
is the path in your application that users are redirected to after
they have authenticated with Google. The path will be appended with
the authorization code for access. Must have a protocol. Cannot
contain URL fragments or relative paths. Cannot be a public IP
address.
insufficientPermissions means that the user you are authenticating with does not have access to do what you are trying to do. You authenticated with a user and a channel. you only have access to the videos within that channel.
I've managed to setup an API Gateway secured with Cognito. The unauthenticated user role has an access policy that should grant it access to the gateway. I've also managed to use boto3 to retrieve an identity ID from the pool and obtain the associated open ID token, as well as the associated secret and access keys.
How do I now make a call to the gateway using these credentials? Is there a way to use boto3 to handle signing a request to a particular method on the API?
My code is based largely on the questioner's own answer, but I've tried to make it clearer where all the values come from.
import boto3
import requests
from requests_aws4auth import AWS4Auth
# Use 'pip install boto3 requests requests-aws4auth' to get these
region_name = 'ap-southeast-2' # or 'us-west-1' or whatever
# 12 decimal digits from your AWS login page
account_id = '123456789012'
# I've only found this in the sample code for other languages, e.g. JavaScript
# Services→Cognito→Manage Federated Identities→(your-id-pool)→Sample code
identity_pool_id = 'ap-southeast-2:fedcba98-7654-3210-1234-56789abcdef0'
# Create a new identity
boto3.setup_default_session(region_name = region_name)
identity_client = boto3.client('cognito-identity', region_name=region_name)
identity_response = identity_client.get_id(AccountId=account_id,
IdentityPoolId=identity_pool_id)
# We normally wouldn't log this, but to illustrate:
identity_id = identity_response['IdentityId']
print ('identity_id:', identity_id) # good idea not to log this
# Get the identity's credentials
credentials_response = identity_client.get_credentials_for_identity(IdentityId=identity_id)
credentials = credentials_response['Credentials']
access_key_id = credentials['AccessKeyId']
secret_key = credentials['SecretKey']
service = 'execute-api'
session_token = credentials['SessionToken']
expiration = credentials['Expiration']
# Again, we normally wouldn't log this:
print ('access_key_id', access_key_id)
print ('secret_key', secret_key)
print ('session_token', session_token)
print ('expiration', expiration)
# The access_key_id will look something like 'AKIABC123DE456FG7890', similar to
# Services→IAM→Users→(AWS_USER_NAME)→Security credentials→Access key ID
# Get the authorisation object
auth = AWS4Auth(access_key_id, secret_key, region_name, service,
session_token=session_token)
current_app['auth'] = auth
# Just an illustration again:
print ('auth: %(service)s(%(date)s) %(region)s:%(access_id)s' % auth.__dict__)
# We'll use that object to send a request to our app. This app doesn't
# exist in real life, though, so you'll need to edit the following quite
# heavily:
# Services→Cognito→Manage your User Pools→(your-user-pool)→Apps→App name
app_name = 'my-app-name'
api_path = 'dev/helloworld'
method = 'GET'
headers = {}
body = ''
url = 'https://%s.%s.%s.amazonaws.com/%s' % (app_name, service, region_name,
api_path)
response = requests.request(method, url, auth=auth, data=body, headers=headers)
The following code (and the requests-aws4auth library) did the job:
import boto3
import datetime
import json
from requests_aws4auth import AWS4Auth
import requests
boto3.setup_default_session(region_name='us-east-1')
identity = boto3.client('cognito-identity', region_name='us-east-1')
account_id='XXXXXXXXXXXXXXX'
identity_pool_id='us-east-1:YYY-YYYY-YYY-YY'
api_prefix='ZZZZZZZZZ'
response = identity.get_id(AccountId=account_id, IdentityPoolId=identity_pool_id)
identity_id = response['IdentityId']
print ("Identity ID: %s"%identity_id)
resp = identity.get_credentials_for_identity(IdentityId=identity_id)
secretKey = resp['Credentials']['SecretKey']
accessKey = resp['Credentials']['AccessKeyId']
sessionToken = resp['Credentials']['SessionToken']
expiration = resp['Credentials']['Expiration']
print ("\nSecret Key: %s"%(secretKey))
print ("\nAccess Key %s"%(accessKey))
print ("\nSession Token: %s"%(sessionToken))
print ("\nExpiration: %s"%(expiration))
method = 'GET'
headers = {}
body = ''
service = 'execute-api'
url = 'https://%s.execute-api.us-east-1.amazonaws.com/dev/helloworld' % api_prefix
region = 'us-east-1'
auth = AWS4Auth(accessKey, secretKey, region, service, session_token=sessionToken)
response = requests.request(method, url, auth=auth, data=body, headers=headers)
print(response.text)
Next code is working really well.
Hope to help:
from pprint import pprint
import requests
from pycognito import Cognito
USER_POOL_ID = 'eu-central-1_XXXXXXXXXXX'
CLIENT_ID = 'XXXXXXXXXXXX'
CLIENT_SECRET = 'XXXXXXXXXXX'
u = Cognito(USER_POOL_ID,CLIENT_ID, client_secret=CLIENT_SECRET, username='cognito user name')
u.authenticate('cognito user password')
id_token = u.id_token
headers = {'Authorization': 'Bearer ' + id_token}
api_url = 'https://XXXXXXXXXXX.execute-api.eu-central-1.amazonaws.com/stage/XXXXXXXXXXX'
r = requests.get(api_url, headers=headers)
pprint(dict(r.headers))
print(r.status_code)
print(r.text)
Here is an example from our public docs: http://docs.aws.amazon.com/general/latest/gr/sigv4-signed-request-examples.html
Cognito creds are no different than any other temporary creds, and the signing process is also the same. If you want to move back to Python the example above should be good, or I would guess that there are third-party libraries out there to do the signature for you.
identity_pool_id how to get
If you have not federated pool which could give you "identity_pool_id" ,
execution code below will give you identity_pool_id
import boto3
boto3.setup_default_session(
aws_access_key_id='AKIAJ7TBC72BPWNEWIDQ',
aws_secret_access_key='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',
region_name='ap-southeast-2')
client = boto3.client('cognito-identity')
response = client.list_identity_pools(MaxResults=3,)
print("IdentityPoolId-- ", response)