I've followed the directions in https://developers.google.com/accounts/docs/OAuth2ServiceAccount to use a service account to authenticate to the Google Cloud Storage API. I tried to send a JWT to google's authenticate servers in python, but got an error:
urllib2.HTTPError: HTTP Error 400: Bad Request
It looks like there's something wrong with the way I'm making, signing, or sending the JWT? The error wasn't specific so it could be any part of the process. Does anyone have any ideas?
import Crypto.PublicKey.RSA as RSA
import Crypto.Hash.SHA as SHA
import Crypto.Signature.PKCS1_v1_5 as PKCS1_v1_5
import base64
import json
import time
import urllib2
import urllib
# Settings
json_key_file = 'GooglePM-9f75ad112f87-service.json'
# Load the private key associated with the Google service account
with open(json_key_file) as json_file:
json_data = json.load(json_file)
key = RSA.importKey(json_data['private_key'])
# Create an PKCS1_v1_5 object
signer = PKCS1_v1_5.new(key)
# Encode the JWT header
header_b64 = base64.urlsafe_b64encode(json.dumps({'alg':'RS256','typ':'JWT'}))
# JWT claims
jwt = {
'iss': json_data['client_email'],
'scope': 'https://www.googleapis.com/auth/devstorage.read_write',
'aud': 'https://accounts.google.com/o/oauth2/token',
'exp': int(time.time())+3600,
'iat': int(time.time())
}
jwt_json = json.dumps(jwt)
# Encode the JWT claims
jwt_json_b64 = base64.urlsafe_b64encode(jwt_json)
# Sign the JWT header and claims
msg_hash = SHA.new(header_b64 + "." + jwt_json_b64)
signature_b64 = base64.urlsafe_b64encode(signer.sign(msg_hash))
# Make the complete message
jwt_complete = header_b64 + "." + jwt_json_b64 + "." + signature_b64
data = {'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
'assertion': jwt_complete}
f = urllib2.urlopen("https://accounts.google.com/o/oauth2/token", urllib.urlencode(data))
print f.read()
If I try to use curl to post to the server, I get the invalid grants error:
(venv)$ curl -d 'grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=eyJhbGciOiAiUlMyNTYiLCAidHlwIjogIkpXVCJ9.eyJpc3MiOiAiMTM1MDY3NjIyMTk4LWVhbWUwZnFqdTNvamRoZ29zdDg2dnBpdTBsYW91NnZlQGRldmVsb3Blci5nc2VydmljZWFjY291bnQuY29tIiwgInNjb3BlIjogImh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvZGV2c3RvcmFnZS5yZWFkX3dyaXRlIiwgImF1ZCI6ICJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20vby9vYXV0aDIvdG9rZW4iLCAiZXhwIjogMTQwODY1MTU2OCwgImlhdCI6IDE0MDg2NDg1NTh9.HWC7h3QiOy7QsSuta4leq_Gjwmy9IdF-MUwflPhiohzAJ-Amykd56Ye4Y_Saf_sAc5STzOCmrSPzOTYvGXr6X_T_AmSTxXK2AJ2SpAiEUs2_Wp5h18xTUY3Y_hkKvSZLh5bRzeJ_0xRcmRIPE6tua0FHFwUDdnCIGdh4DGg6i4E%3D' https://accounts.google.com/o/oauth2/token
{
"error" : "invalid_grant"
}
Ok so there's a better way to do this! Google already has a python client API that handles some of the complexity. The following code works after installing google python client API: https://developers.google.com/api-client-library/python/guide/aaa_oauth
from oauth2client.client import SignedJwtAssertionCredentials
import json
import urllib
import urllib2
# Settings
json_key_file = 'GooglePM-9f75ad112f87-service.json'
# Load the private key associated with the Google service account
with open(json_key_file) as json_file:
json_data = json.load(json_file)
# Get and sign JWT
credential = SignedJwtAssertionCredentials(json_data['client_email'], json_data['private_key'], 'https://www.googleapis.com/auth/devstorage.read_write')
jwt_complete = credential._generate_assertion()
# Get token from server
data = {'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
'assertion': jwt_complete}
f = urllib2.urlopen("https://accounts.google.com/o/oauth2/token", urllib.urlencode(data))
print f.read()
maybe, slightly simplier:
import oauth2client.service_account
jsonfile = 'GooglePM-9f7sdf342f87-service.json'
# use static method .from_json_keyfile_name(filename)
credentials = oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name(jsonfile)
Your first algorithm is completely ok and working, but you need SHA256, not SHA. Thank you for your code.
Initial code is also working using Python 3.6. You only have to use bytes instead of str in case of base64:
import requests
import json as js
import Crypto.PublicKey.RSA as RSA
import Crypto.Hash.SHA256 as SHA
import Crypto.Signature.PKCS1_v1_5 as PKCS1_v1_5
import base64
import time
# Settings
json_key_file = 'google-api.json'
# Load the private key associated with the Google service account
with open(json_key_file) as json_file:
json_data = js.load(json_file)
key = RSA.importKey(json_data['private_key'])
# Create an PKCS1_v1_5 object
signer = PKCS1_v1_5.new(key)
header = js.dumps({'alg':'RS256','typ':'JWT'})
# Encode the JWT header
header_b64 = base64.urlsafe_b64encode(header.encode("UTF-8"))
# JWT claims
jwt = {
'iss': json_data['client_email'],
'scope': 'https://www.googleapis.com/auth/analytics.readonly',
'aud': 'https://accounts.google.com/o/oauth2/token',
'exp': int(time.time())+3600,
'iat': int(time.time())
}
jwt_json = js.dumps(jwt)
# Encode the JWT claims
jwt_json_b64 = base64.urlsafe_b64encode(jwt_json.encode("UTF-8"))
# Sign the JWT header and claims
msg_hash = SHA.new((header_b64.decode("UTF-8") + "." + jwt_json_b64.decode("UTF-8")).encode("UTF-8"))
signature_b64 = base64.urlsafe_b64encode(signer.sign(msg_hash))
# Make the complete message
jwt_complete = (header_b64.decode("UTF-8") + "." + jwt_json_b64.decode("UTF-8") + "." + signature_b64.decode("UTF-8")).encode("UTF-8")
data = {'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
'assertion': jwt_complete}
requests.post(url="https://accounts.google.com/o/oauth2/token",data=data).text
Related
I'm attempting to pull email attachments from outlook and store them in an s3 bucket in aws. This is one of my first python projects, and its proving to be very difficult for me and is probably very messy code.
# see https://github.com/AzureAD/microsoft-authentication-library-for-python/blob/dev/sample/device_flow_sample.py
# This authenticates for first time login.
# As long as you call acquire_token_silent before you invoke any graph APIs, the tokens will stay up to date.
# The refresh token is good for 90 days, and automatically updates. Once you login, the tokens will
# be updated and stored in the cache (and persisted to a file), and will stay alive more-or-less indefinitely
# (there are some things that can invalidate it on the server side).
from __future__ import with_statement
import io
import sys
import json
import logging
import os
import tarfile
import atexit
from wsgiref import headers
import requests
import msal
import boto3
import base64
from botocore.exceptions import ClientError
import codecs
print('Starting...')
# logging
logging.basicConfig(level=logging.DEBUG) # Enable DEBUG log for entire script
logging.getLogger("msal").setLevel(logging.INFO) # Optionally disable MSAL DEBUG logs
client=boto3.client('secretsmanager')
# config
config = dict(
authority = "https://login.microsoftonline.com/common",
client_id = '123456',
scope = ["Mail.ReadWrite"],
username = 'username',
cache_file = client.get_secret_value(SecretId="demo-ms-graph")['SecretBinary'],
endpoint = 'https://graph.microsoft.com/v1.0/me/mailFolders/inbox/messages?$expand=attachments&$search="hasAttachments:true"'
)
# cache
cache = msal.SerializableTokenCache()
if os.path.exists(config["cache_file"]):
with tarfile.open('token.cache.tar.gz', "w:gz") as tar:
tar.add(config["cache_file"])
bts = open('token.cache.tar.gz','rb').read()
print("Length before",len(bts))
#sec=client.update_secret(SecretId="demo-ms-graph", SecretBinary=bts)
sec=client.get_secret_value(SecretId="demo-ms-graph")['SecretBinary']
print("Length after",len(sec))
with tarfile.open(fileobj=io.BytesIO(sec), mode='r:gz') as t:
d=t.extractfile('token.cache')
#print file content
print("File content",str(d.read()))
with tarfile.open(fileobj=io.BytesIO(sec), mode='r:gz') as t:
d=t.extractfile('token.cache')
# app
app = msal.PublicClientApplication(
config["client_id"], authority=config["authority"],
token_cache=cache)
print('Connecting to app..')
# exists?
result = None
accounts = app.get_accounts()
if accounts:
logging.info("found accounts in the app")
for a in accounts:
print(a)
if a["username"] == config["username"]:
result = app.acquire_token_silent(config["scope"], account=a)
break
if result and "access_token" in result:
# Calling graph using the access token
graph_data = requests.get( # Use token to call downstream service
config["endpoint"],
headers={'Authorization': 'Bearer ' + result['access_token']},).json()
#print("Graph API call result: %s" % json.dumps(graph_data, indent=2))
else:
print(result.get("error"))
print(result.get("error_description"))
print(result.get("correlation_id")) # You may need this when reporting a bug
main = 'https://graph.microsoft.com/v1.0/me/mailFolders/inbox/messages?$expand=attachments&$search="hasAttachments:true"'
response = requests.get(main, headers={'Authorization': 'Bearer ' + result['access_token']})
if response.status_code != 200:
raise Exception(response.json())
response_json = response.json()
print('Starting upload...')
emails = response_json['value']
s3 = boto3.client('s3')
bucket ='demo-email-app'
for email in emails:
email_id = email['id']
subject = email['subject']
if email['hasAttachments']:
print(subject)
attachments = email['attachments']
for attachment in attachments:
name = attachment['name']
fileContent = json.dumps(email, indent=2)
s3.put_object(Bucket=bucket, Key=name.replace('.', '_') + '.json', Body=fileContent.encode('UTF-8'))
print('Upload Complete')
#download_email_attachments(email_id, headers)
print('All uploads complete')
My secret is stored as binary in secrets manager, and it seems to be able to pull the secret and prints it fine. I'm running into an error in the # exists? section with "AttributeError: 'NoneType' object has no attribute 'get'"
Am I approaching this in the wrong way? I have the token stored in secretsmanager, and am trying to retrieve it for msal to use and authenticate my user to the MS Graph API so I can pull attachments from the Outlook account and store those in an s3 bucket.
https://app.gong.io/settings/api/documentation#overview
import base64
auth = <my_pass>
auth2 = auth.encode('ascii')
base64_bytes = base64.b64encode(auth2)
headers = {"Authorization" : "base64_bytes"}
response = requests.get("https://api.gong.io/v2/users",headers)
print(response)
In the Gong API Page (you must be a technical administrator in Gong), click "Create" to receive an Access Key and an Access Key Secret.
Use the Basic Authorization HTTP header (as per RFC) to access the Public API as shown below:
Authorization: Basic
To create the basic token, combine the Access Key and the Access Key Secret with colon (:) and then encode in Base64 as following:
Base64( : )
Here we go: keep it simple :-)
import requests
#This sets up the https connection
url = "https://api.gong.io/v2/users"
access_key = <access_key>
secret_key = <secret_key>
r = requests.get(url, auth=(access_key,secret_key))
print(r.text)
I want to generate jwt token manually without any library in python. I searched detailed information or tutorial for this but I couldn't. All information about jwt in python is library included. Is there any resource that I can benefit to generate jwt token in python?
I came across this SO thread because I wanted to create a JWT for Zoom without using the JWT library. As recommended by darth baba, I went through the PyJWT source code to recreate the implementation by using the default libraries. Hopefully, this helps some others in the future:
import base64
import json
import hmac
import hashlib
from datetime import datetime, timedelta
api_key = 'XXXXXX'
api_sec = 'XXXXXX'
due_date = datetime.now() + timedelta(minutes=10)
expiry = int(due_date.timestamp())
def base64url_encode(input: bytes):
return base64.urlsafe_b64encode(input).decode('utf-8').replace('=','')
def jwt(api_key, expiry, api_sec):
segments = []
header = {"typ": "JWT", "alg": "HS256"}
payload = {"iss": api_key, "exp": expiry}
json_header = json.dumps(header, separators=(",",":")).encode()
json_payload = json.dumps(payload, separators=(",",":")).encode()
segments.append(base64url_encode(json_header))
segments.append(base64url_encode(json_payload))
signing_input = ".".join(segments).encode()
key = api_sec.encode()
signature = hmac.new(key, signing_input, hashlib.sha256).digest()
segments.append(base64url_encode(signature))
encoded_string = ".".join(segments)
return encoded_string
A senior colleague taught this to me. There is no need huge libraries if you want to make a simple one.
def base64url_decode(input):
return base64.urlsafe_b64decode(input+'==')
def base64url_encode(input):
stringAsBytes = input.encode('ascii')
stringAsBase64 = base64.urlsafe_b64encode(stringAsBytes).decode('utf-8').replace('=','')
return stringAsBase64
def jwt_creator(expiration, userid, userrole):
header = {
"alg": "HS256",
"typ": "JWT"
}
payload = {'expired': expiration,
'userid': userid,
'userrole': userrole
}
secret_key = secrets.token_urlsafe(32)
total_params = str(base64url_encode(json.dumps(header))) + '.' + str(base64url_encode(json.dumps(payload)))
signature = hmac.new(secret_key.encode(), total_params.encode(), hashlib.sha256).hexdigest()
token = total_params + '.' + str(base64url_encode(signature))
return token
PyJWT
PyJWT is a Python library that allows you to encode and decode JSON Web Tokens (JWT).
Installation:
pip install pyjwt
Tutorials for JWT authentication can be found in the official documentation.
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'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)