obtaining AWS credentials using cognito in python boto - python

I'm trying to emulate the flow of my server application creating a temporary access/secret key pair for a mobile device using my own authentication. Mobile device talks to my server and end result is it gets AWS credentials.
I'm using Cognito with a custom developer backend, see documentation here.
To this end, I've made the script below, but my secret/access key credentials don't work:
import time
import traceback
from boto.cognito.identity.layer1 import CognitoIdentityConnection
from boto.sts import STSConnection
from boto.s3.connection import S3Connection
from boto.s3.key import Key
AWS_ACCESS_KEY_ID = "XXXXX"
AWS_SECRET_ACCESS_KEY = "XXXXXX"
# get token
iden_pool_id = "us-east-1:xxx-xxx-xxx-xxxx-xxxx"
role_arn = "arn:aws:iam::xxxx:role/xxxxxxx"
user_id = "xxxx"
role_session_name = "my_session_name_here"
bucket_name = 'xxxxxxxxxx'
connection = CognitoIdentityConnection(aws_access_key_id=AWS_ACCESS_KEY_ID, aws_secret_access_key=AWS_SECRET_ACCESS_KEY)
web_identity_token = connection.get_open_id_token_for_developer_identity(
identity_pool_id=iden_pool_id,
logins={"xxxxxxxxx" : user_id},
identity_id=None,
token_duration=3600)
# use token to get credentials
sts_conn = STSConnection(aws_access_key_id=AWS_ACCESS_KEY_ID, aws_secret_access_key=AWS_SECRET_ACCESS_KEY)
result = sts_conn.assume_role_with_web_identity(
role_arn,
role_session_name,
web_identity_token['Token'],
provider_id=None,
policy=None,
duration_seconds=3600)
print "The user now has an access ID (%s) and a secret access key (%s) and a session/security token (%s)!" % (
result.credentials.access_key, result.credentials.secret_key, result.credentials.session_token)
# just use any call that tests if these credentials work
from boto.ec2.connection import EC2Connection
ec2 = EC2Connection(result.credentials.access_key, result.credentials.secret_key, security_token=result.credentials.session_token)
wait = 1
cumulative_wait_time = 0
while True:
try:
print ec2.get_all_regions()
break
except Exception as e:
print e, traceback.format_exc()
time.sleep(2**wait)
cumulative_wait_time += 2**wait
print "Waited for:", cumulative_wait_time
wait += 1
My thought with the exponential backoff was that perhaps Cognito takes a while to propagate the new access/secret key pair, and thus I might have to wait (pretty unacceptable if so!).
However, this script runs for a 10 minutes and doesn't succeed, which leads me to believe the problem is something else.
Console print out:
The user now has an access ID (xxxxxxxx) and a secret access key (xxxxxxxxxx) and a session/security token (XX...XX)!
EC2ResponseError: 401 Unauthorized
<?xml version="1.0" encoding="UTF-8"?>
<Response><Errors><Error><Code>AuthFailure</Code><Message>AWS was not able to validate the provided access credentials</Message></Error></Errors><RequestID>xxxxxxxxxx</RequestID></Response> Traceback (most recent call last):
File "/home/me/script.py", line 50, in <module>
print ec2.get_all_regions()
File "/home/me/.virtualenvs/venv/local/lib/python2.7/site-packages/boto/ec2/connection.py", line 3477, in get_all_regions
[('item', RegionInfo)], verb='POST')
File "/home/me/.virtualenvs/venv/local/lib/python2.7/site-packages/boto/connection.py", line 1186, in get_list
raise self.ResponseError(response.status, response.reason, body)
EC2ResponseError: EC2ResponseError: 401 Unauthorized
<?xml version="1.0" encoding="UTF-8"?>
<Response><Errors><Error><Code>AuthFailure</Code><Message>AWS was not able to validate the provided access credentials</Message></Error></Errors><RequestID>xxxxxxxxxxxxx</RequestID></Response>
Waited for: 2
...
...
Any thoughts?

You are correctly extracting the access key and secret key from the result of the assume_role_with_web_identity call. However, when using the temporary credentials, you also need to use the security token from the result.
Here is pseudocode describing what you need to do:
http://docs.aws.amazon.com/STS/latest/UsingSTS/using-temp-creds.html#using-temp-creds-sdk
Also note the security_token parameter for EC2Connection
http://boto.readthedocs.org/en/latest/ref/ec2.html#boto.ec2.connection.EC2Connection
Hopefully this solves the problem
-Mark

Related

Office 365 IMAP authentication via OAuth2 and python MSAL library

I'm trying to upgrade a legacy mail bot to authenticate via Oauth2 instead of Basic authentication, as it's now deprecated two days from now.
The document states applications can retain their original logic, while swapping out only the authentication bit
Application developers who have built apps that send, read, or
otherwise process email using these protocols will be able to keep the
same protocol, but need to implement secure, Modern authentication
experiences for their users. This functionality is built on top of
Microsoft Identity platform v2.0 and supports access to Microsoft 365
email accounts.
Note I've explicitly chosen the client credentials flow, because the documentation states
This type of grant is commonly used for server-to-server interactions
that must run in the background, without immediate interaction with a
user.
So I've got a python script that retrieves an Access Token using the MSAL python library. Now I'm trying to authenticate with the IMAP server, using that Access Token. There's some existing threads out there showing how to connect to Google, I imagine my case is pretty close to this one, except I'm connecting to a Office 365 IMAP server. Here's my script
import imaplib
import msal
import logging
app = msal.ConfidentialClientApplication(
'client-id',
authority='https://login.microsoftonline.com/tenant-id',
client_credential='secret-key'
)
result = app.acquire_token_for_client(scopes=['https://graph.microsoft.com/.default'])
def generate_auth_string(user, token):
return 'user=%s\1auth=Bearer %s\1\1' % (user, token)
# IMAP time!
mailserver = 'outlook.office365.com'
imapport = 993
M = imaplib.IMAP4_SSL(mailserver,imapport)
M.debug = 4
M.authenticate('XOAUTH2', lambda x: generate_auth_string('user#mydomain.com', result['access_token']))
print(result)
The IMAP authentication is failing and despite setting M.debug = 4, the output isn't very helpful
22:56.53 > b'DBDH1 AUTHENTICATE XOAUTH2'
22:56.53 < b'+ '
22:56.53 write literal size 2048
22:57.84 < b'DBDH1 NO AUTHENTICATE failed.'
22:57.84 NO response: b'AUTHENTICATE failed.'
Traceback (most recent call last):
File "/home/ubuntu/mini-oauth.py", line 21, in <module>
M.authenticate("XOAUTH2", lambda x: generate_auth_string('user#mydomain.com', result['access_token']))
File "/usr/lib/python3.10/imaplib.py", line 444, in authenticate
raise self.error(dat[-1].decode('utf-8', 'replace'))
imaplib.IMAP4.error: AUTHENTICATE failed.
Any idea where I might be going wrong, or how to get more robust information from the IMAP server about why the authentication is failing?
Things I've looked at
Note this answer no longer works as the suggested scopes fail to generate an Access Token.
The client credentials flow seems to mandate the https://graph.microsoft.com/.default grant. I'm not sure if that includes the scope required for the IMAP resource
https://outlook.office.com/IMAP.AccessAsUser.All?
Verified the code lifted from the Google thread produces the SASL XOAUTH2 string correctly, per example on the MS docs
import base64
user = 'test#contoso.onmicrosoft.com'
token = 'EwBAAl3BAAUFFpUAo7J3Ve0bjLBWZWCclRC3EoAA'
xoauth = "user=%s\1auth=Bearer %s\1\1" % (user, token)
xoauth = xoauth.encode('ascii')
xoauth = base64.b64encode(xoauth)
xoauth = xoauth.decode('ascii')
xsanity = 'dXNlcj10ZXN0QGNvbnRvc28ub25taWNyb3NvZnQuY29tAWF1dGg9QmVhcmVyIEV3QkFBbDNCQUFVRkZwVUFvN0ozVmUwYmpMQldaV0NjbFJDM0VvQUEBAQ=='
print(xoauth == xsanity) # prints True
This thread seems to suggest multiple tokens need to be fetched, one for graph, then another for the IMAP connection; could that be what I'm missing?
Try the below steps.
For Client Credentials Flow you need to assign “Application permissions” in the app registration, instead of “Delegated permissions”.
Add permission “Office 365 Exchange Online / IMAP.AccessAsApp” (application).
Grant admin consent to you application
Service Principals and Exchange.
Once a service principal is registered with Exchange Online, administrators can run the Add-Mailbox Permission cmdlet to assign receive permissions to the service principal.
Use scope 'https://outlook.office365.com/.default'.
Now you can generate the SALS authentication string by combining this access token and the mailbox username to authenticate with IMAP4.
#Python code
def get_access_token():
tenantID = 'abc'
authority = 'https://login.microsoftonline.com/' + tenantID
clientID = 'abc'
clientSecret = 'abc'
scope = ['https://outlook.office365.com/.default']
app = ConfidentialClientApplication(clientID,
authority=authority,
client_credential = clientSecret)
access_token = app.acquire_token_for_client(scopes=scope)
return access_token
def generate_auth_string(user, token):
auth_string = f"user={user}\1auth=Bearer {token}\1\1"
return auth_string
#IMAP AUTHENTICATE
imap = imaplib.IMAP4_SSL(imap_host, 993)
imap.debug = 4
access_token = get_access_token_to_authenticate_imap()
imap.authenticate("XOAUTH2", lambda x:generate_auth_string(
'useremail',
access_token['access_token']))
imap.select('inbox')
The imaplib.IMAP4.error: AUTHENTICATE failed Error occured because one point in the documentation is not that clear.
When setting up the the Service Principal via Powershell you need to enter the App-ID and an Object-ID. Many people will think, it is the Object-ID you see on the overview page of the registered App, but its not!
At this point you need the Object-ID from "Azure Active Directory -> Enterprise Applications --> Your-App --> Object-ID"
New-ServicePrincipal -AppId <APPLICATION_ID> -ServiceId <OBJECT_ID> [-Organization <ORGANIZATION_ID>]
Microsoft says:
The OBJECT_ID is the Object ID from the Overview page of the
Enterprise Application node (Azure Portal) for the application
registration. It is not the Object ID from the Overview of the App
Registrations node. Using the incorrect Object ID will cause an
authentication failure.
Ofcourse you need to take care for the API-permissions and the other stuff, but this was for me the point.
So lets go trough it again, like it is explained on the documentation page.
Authenticate an IMAP, POP or SMTP connection using OAuth
Register the Application in your Tenant
Setup a Client-Key for the application
Setup the API permissions, select the APIs my organization uses tab and search for "Office 365 Exchange Online" -> Application permissions -> Choose IMAP and IMAP.AccessAsApp
Setup the Service Principal and full access for your Application on the mailbox
Check if IMAP is activated for the mailbox
Thats the code I use to test it:
import imaplib
import msal
import pprint
conf = {
"authority": "https://login.microsoftonline.com/XXXXyourtenantIDXXXXX",
"client_id": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXX", #AppID
"scope": ['https://outlook.office365.com/.default'],
"secret": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", #Key-Value
"secret-id": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", #Key-ID
}
def generate_auth_string(user, token):
return f"user={user}\x01auth=Bearer {token}\x01\x01"
if __name__ == "__main__":
app = msal.ConfidentialClientApplication(conf['client_id'], authority=conf['authority'],
client_credential=conf['secret'])
result = app.acquire_token_silent(conf['scope'], account=None)
if not result:
print("No suitable token in cache. Get new one.")
result = app.acquire_token_for_client(scopes=conf['scope'])
if "access_token" in result:
print(result['token_type'])
pprint.pprint(result)
else:
print(result.get("error"))
print(result.get("error_description"))
print(result.get("correlation_id"))
imap = imaplib.IMAP4('outlook.office365.com')
imap.starttls()
imap.authenticate("XOAUTH2", lambda x: generate_auth_string("target_mailbox#example.com", result['access_token']).encode("utf-8"))
After setting up the Service Principal and giving the App full access on the mailbox, wait 15 - 30 minutes for the changes to take effect and test it.
Try with this script:
import json
import msal
import requests
client_id = '***'
client_secret = '***'
tenant_id = '***'
authority = f"https://login.microsoftonline.com/{tenant_id}"
app = msal.ConfidentialClientApplication(
client_id=client_id,
client_credential=client_secret,
authority=authority)
scopes = ["https://graph.microsoft.com/.default"]
result = None
result = app.acquire_token_silent(scopes, account=None)
if not result:
print(
"No suitable token exists in cache. Let's get a new one from Azure Active Directory.")
result = app.acquire_token_for_client(scopes=scopes)
# if "access_token" in result:
# print("Access token is " + result["access_token"])
if "access_token" in result:
userId = "***"
endpoint = f'https://graph.microsoft.com/v1.0/users/{userId}/sendMail'
toUserEmail = "***"
email_msg = {'Message': {'Subject': "Test Sending Email from Python",
'Body': {'ContentType': 'Text', 'Content': "This is a test email."},
'ToRecipients': [{'EmailAddress': {'Address': toUserEmail}}]
},
'SaveToSentItems': 'true'}
r = requests.post(endpoint,
headers={'Authorization': 'Bearer ' + result['access_token']}, json=email_msg)
if r.ok:
print('Sent email successfully')
else:
print(r.json())
else:
print(result.get("error"))
print(result.get("error_description"))
print(result.get("correlation_id"))
Source: https://kontext.tech/article/795/python-send-email-via-microsoft-graph-api

Error Getting Managed Identity Access Token from Azure Function

I'm having an issue retrieving an Azure Managed Identity access token from my Function App. The function gets a token then accesses a Mysql database using that token as the password.
I am getting this response from the function:
9103 (HY000): An error occurred while validating the access token. Please acquire a new token and retry.
Code:
import logging
import mysql.connector
import requests
import azure.functions as func
def main(req: func.HttpRequest) -> func.HttpResponse:
def get_access_token():
URL = "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fossrdbms-aad.database.windows.net&client_id=<client_id>"
headers = {"Metadata":"true"}
try:
req = requests.get(URL, headers=headers)
except Exception as e:
print(str(e))
return str(e)
else:
password = req.json()["access_token"]
return password
def get_mysql_connection(password):
"""
Get a Mysql Connection.
"""
try:
con = mysql.connector.connect(
host='<host>.mysql.database.azure.com',
user='<user>#<db>',
password=password,
database = 'materials_db',
auth_plugin='mysql_clear_password'
)
except Exception as e:
print(str(e))
return str(e)
else:
return "Connected to DB!"
password = get_access_token()
return func.HttpResponse(get_mysql_connection(password))
Running a modified version of this code on a VM with my managed identity works. It seems that the Function App is not allowed to get an access token. Any help would be appreciated.
Note: I have previously logged in as AzureAD Manager to the DB and created this user with all privileges to this DB.
Edit: No longer calling endpoint for VMs.
def get_access_token():
identity_endpoint = os.environ["IDENTITY_ENDPOINT"] # Env var provided by Azure. Local to service doing the requesting.
identity_header = os.environ["IDENTITY_HEADER"] # Env var provided by Azure. Local to service doing the requesting.
api_version = "2019-08-01" # "2018-02-01" #"2019-03-01" #"2019-08-01"
CLIENT_ID = "<client_id>"
resource_requested = "https%3A%2F%2Fossrdbms-aad.database.windows.net"
# resource_requested = "https://ossrdbms-aad.database.windows.net"
URL = f"{identity_endpoint}?api-version={api_version}&resource={resource_requested}&client_id={CLIENT_ID}"
headers = {"X-IDENTITY-HEADER":identity_header}
try:
req = requests.get(URL, headers=headers)
except Exception as e:
print(str(e))
return str(e)
else:
try:
password = req.json()["access_token"]
except:
password = str(req.text)
return password
But now I am getting this Error:
{"error":{"code":"UnsupportedApiVersion","message":"The HTTP resource that matches the request URI 'http://localhost:8081/msi/token?api-version=2019-08-01&resource=https%3A%2F%2Fossrdbms-aad.database.windows.net&client_id=<client_idxxxxx>' does not support the API version '2019-08-01'.","innerError":null}}
Upon inspection this seems to be a general error. This error message is propagated even if it's not the underlying issue. Noted several times in Github.
Is my endpoint correct now?
For this problem, it was caused by the wrong endpoint you request for the access token. We can just use the endpoint http://169.254.169.254/metadata/identity..... in azure VM, but if in azure function we can not use it.
In azure function, we need to get the IDENTITY_ENDPOINT from the environment.
identity_endpoint = os.environ["IDENTITY_ENDPOINT"]
The endpoint is like:
http://127.0.0.1:xxxxx/MSI/token/
You can refer to this tutorial about it, you can also find the python code sample in the tutorial.
In my function code, I also add the client id of the managed identity I created in the token_auth_uri but I'm not sure if the client_id is necessary here (In my case, I use user-assigned identity but not system-assigned identity).
token_auth_uri = f"{identity_endpoint}?resource={resource_uri}&api-version=2019-08-01&client_id={client_id}"
Update:
#r "Newtonsoft.Json"
using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
public static async Task<IActionResult> Run(HttpRequest req, ILogger log)
{
string resource="https://ossrdbms-aad.database.windows.net";
string clientId="xxxxxxxx";
log.LogInformation("C# HTTP trigger function processed a request.");
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(String.Format("{0}/?resource={1}&api-version=2019-08-01&client_id={2}", Environment.GetEnvironmentVariable("IDENTITY_ENDPOINT"), resource,clientId));
request.Headers["X-IDENTITY-HEADER"] = Environment.GetEnvironmentVariable("IDENTITY_HEADER");
request.Method = "GET";
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
StreamReader streamResponse = new StreamReader(response.GetResponseStream());
string stringResponse = streamResponse.ReadToEnd();
log.LogInformation("test:"+stringResponse);
string name = req.Query["name"];
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
return name != null
? (ActionResult)new OkObjectResult($"Hello, {name}")
: new BadRequestObjectResult("Please pass a name on the query string or in the request body");
}
For your latest issue, where you are seeing UnsupportedApiVersion, it is probably this issue: https://github.com/MicrosoftDocs/azure-docs/issues/53726
Here are a couple of options that worked for me:
I am assuming you are hosting the Function app on Linux. I noticed that ApiVersion 2017-09-01 works, but you need to make additional changes (instead of "X-IDENTITY-HEADER", use "secret" header). And also use a system-assigned managed identity for your function app, and not a user assigned identity.
When I hosted the function app on Windows, I didn't have the same issues. So if you want to use an user-assigned managed identity, you can try this option instead. (with the api-version=2019-08-01, and X-IDENTITY-HEADER.

Validate JWT access_token from AAD Azure Fails

I need to validate a azure jwt access_token from the service I'm working on. We are currently making a request to https://graph.microsoft.com/beta/me. If the request succeeded, the token is valid. Unfortunately we will not be able to keep doing that.
I have tried a variety of ideas for this. None of them with success. Even jwt.io does not recognize the signature, even though jwt kid and the kid from one of the available signatures in jwk_uri matches.
Based on this blog post I have created a following solution (also available on github) .
My implementation is very similar to the blog post with a few changes:
#!/usr/bin/env python2
import jwt
import requests
import sys
from cryptography.x509 import load_pem_x509_certificate
from cryptography.hazmat.backends import default_backend
def get_public_key(access_token):
""" Retrieve public key for access token """
token_header = jwt.get_unverified_header(access_token)
res = requests.get('https://login.microsoftonline.com/common/.well-known/openid-configuration')
jwk_uri = res.json()['jwks_uri']
res = requests.get(jwk_uri)
jwk_keys = res.json()
x5c = None
# Iterate JWK keys and extract matching x5c chain
for key in jwk_keys['keys']:
if key['kid'] == token_header['kid']:
x5c = key['x5c']
break
else:
raise Exception('Certificate not found in {}'.format(jwk_uri))
cert = ''.join([
'-----BEGIN CERTIFICATE-----\n',
x5c[0],
'\n-----END CERTIFICATE-----\n',
])
try:
public_key = load_pem_x509_certificate(cert.encode(), default_backend()).public_key()
except Exception as error:
raise Exception('Failed to load public key:', error)
return public_key, key['kid']
def main():
print '\n'
if len(sys.argv) < 2 or '-h' in sys.argv:
print 'Run it again passing acces token:\n\tpython jwt_validation.py <access_token>'
sys.exit(1)
access_token = sys.argv[1]
audience = 'https://graph.microsoft.com'
public_key, kid = get_public_key(access_token)
try:
jwt.decode(
access_token,
public_key,
algorithms='RS256',
audience=audience,
)
except Exception as error:
print 'key {} did not worked, error:'.format(kid), error
sys.exit(1)
print('Key worked!')
if __name__ == '__main__':
main()
This solution returns Invalid Signature for a valid access_token with the following traceback:
Traceback (most recent call last):
File "jwt_validation/jwt_validation.py", line 63, in <module>
main()
File "jwt_validation/jwt_validation.py", line 57, in main
audience=audience,
File "~/.pyenv/versions/jwt-validation-tool/lib/python2.7/site-packages/jwt/api_jwt.py", line 93, in decode
jwt, key=key, algorithms=algorithms, options=options, **kwargs
File "~/.pyenv/versions/jwt-validation-tool/lib/python2.7/site-packages/jwt/api_jws.py", line 157, in decode
key, algorithms)
File "~/.pyenv/versions/jwt-validation-tool/lib/python2.7/site-packages/jwt/api_jws.py", line 224, in _verify_signature
raise InvalidSignatureError('Signature verification failed')
jwt.exceptions.InvalidSignatureError: Signature verification failed
Any ideas of what I could be wrong would be helpful.
I ran into simillar issue, after couple of days investigating I've found in my case that I am requesting token with built in scopes (openid and profile) and without any custom scope which results in issuing tokens with different audiance (MS Graph) and hence tokens signed with with different public key (because I think that issued access_token is just a forward of the delegated MS Graph scope).
I resolved the issue by adding a custom scope in my app registration (Expose API section) and now my access_tokens are issued with valid audiance and I am able to check signature with my app public key.
Usually the audience claim is used to point to the client_id of the client application you have registered in Azure/Microsoft. Looks to me that the error is occurring due to a mismatch of claims in jwt(probably audience). Check whether you have set the correct client_id for the audience variable.

Google Adwords API authentication issue

Just getting started on the Adwords API, for some reason I can't seem to connect at all.
The code below, straight from the tutorial throws the error:
Traceback (most recent call last):
File "<pyshell#12>", line 1, in <module>
client = AdWordsClient(path=os.path.join('Users', 'ravinthambapillai', 'Google Drive', 'client_secrets.json'))
File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/adspygoogle/adwords/AdWordsClient.py", line 151, in __init__
self._headers = self.__LoadAuthCredentials()
File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/adspygoogle/adwords/AdWordsClient.py", line 223, in __LoadAuthCredentials
return super(AdWordsClient, self)._LoadAuthCredentials()
File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/adspygoogle/common/Client.py", line 94, in _LoadAuthCredentials
raise ValidationError(msg)
**ValidationError: Authentication data is missing.**
from adspygoogle.adwords.AdWordsClient import AdWordsClient
from adspygoogle.common import Utils
client = AdWordsClient(path=os.path.join('Users', 'this-user', 'this-folder', 'client_secrets.json'))
It looks like there's two issues. First, try removing the last path element, as far as I recall, the path parameter expects a directory that contains the authentication pickle, logs etc. This approach requires that you already have a valid auth_token.pkl.
Second, it appears that you're using OAuth2 for authentication (I'm guessing by the client_secrets.json file). For this to work, you'll need to use the oauth2client library and provide an oauth2credentials instance in the headers parameter to AdWordsClient.
The following is straight from the file examples/adspygoogle/adwords/v201302/misc/use_oauth2.py in the client distribution and should give you an idea how it works:
# We're using the oauth2client library:
# http://code.google.com/p/google-api-python-client/downloads/list
flow = OAuth2WebServerFlow(
client_id=oauth2_client_id,
client_secret=oauth2_client_secret,
# Scope is the server address with '/api/adwords' appended.
scope='https://adwords.google.com/api/adwords',
user_agent='oauth2 code example')
# Get the authorization URL to direct the user to.
authorize_url = flow.step1_get_authorize_url()
print ('Log in to your AdWords account and open the following URL: \n%s\n' %
authorize_url)
print 'After approving the token enter the verification code (if specified).'
code = raw_input('Code: ').strip()
credential = None
try:
credential = flow.step2_exchange(code)
except FlowExchangeError, e:
sys.exit('Authentication has failed: %s' % e)
# Create the AdWordsUser and set the OAuth2 credentials.
client = AdWordsClient(headers={
'developerToken': '%s++USD' % email,
'clientCustomerId': client_customer_id,
'userAgent': 'OAuth2 Example',
'oauth2credentials': credential
})
I am not familiar with the AdWordsClient api but are you sure your path is correct?
your current join produces a relative path, do you need an absolute one?
>>> import os
>>> os.path.join('Users', 'this-user')
'Users/this-user'
For testing you could hardcode the absoulte path in to make sure it is not a path issue
I would also make sure that 'client_secrets.json exists, and that it is readable by the user executing python

LinkedIn API Python Key Error 2.7

This code is available online to run a map of your connections in linkedin
This uses linkedin api.
I'm able to connect fine and everything runs okay till the last script of actually writing the data to a csv.
Whenever I run the code
import oauth2 as oauth
import urlparse
import simplejson
import codecs
CONSUMER_KEY = "xxx"
CONSUMER_SECRET = "xxx"
OAUTH_TOKEN = "xxx"
OAUTH_TOKEN_SECRET = "xxx"
OUTPUT = "linked.csv"
def linkedin_connections():
# Use your credentials to build the oauth client
consumer = oauth.Consumer(key=CONSUMER_KEY, secret=CONSUMER_SECRET)
token = oauth.Token(key=OAUTH_TOKEN, secret=OAUTH_TOKEN_SECRET)
client = oauth.Client(consumer, token)
# Fetch first degree connections
resp, content = client.request('http://api.linkedin.com/v1/people/~/connections?format=json')
results = simplejson.loads(content)
# File that will store the results
output = codecs.open(OUTPUT, 'w', 'utf-8')
# Loop thru the 1st degree connection and see how they connect to each other
for result in results["values"]:
con = "%s %s" % (result["firstName"].replace(",", " "), result["lastName"].replace(",", " "))
print >>output, "%s,%s" % ("John Henry", con)
# This is the trick, use the search API to get related connections
u = "https://api.linkedin.com/v1/people/%s:(relation-to-viewer:(related-connections))?format=json" % result["id"]
resp, content = client.request(u)
rels = simplejson.loads(content)
try:
for rel in rels['relationToViewer']['relatedConnections']['values']:
sec = "%s %s" % (rel["firstName"].replace(",", " "), rel["lastName"].replace(",", " "))
print >>output, "%s,%s" % (con, sec)
except:
pass
if __name__ == '__main__':
linkedin_connections()
for result in results["values"]:
KeyError: 'values'
When I run this I get an error message:
Traceback (most recent call last):
File "linkedin-2-query.py", line 51, in <module>
linkedin_connections()
File "linkedin-2-query.py", line 35, in linkedin_connections
for result in results["values"]:
KeyError: 'values'
Any suggestions or help would be greatly appreciated!
I encountered the same issue working through the post Visualizing your LinkedIn graph using Gephi – Part 1.
Python raises a KeyError whenever a dict() object is requested (using the format a = adict[key]) and the key is not in the dictionary. KeyError - Python Wiki
After searching a bit and adding some print statements, I realize that my OAuth session has expired, so the OAuth token in my linkedin-2-query.py script was is longer valid.
Since the OAuth token is invalid, the LinkedIn API does not return a dictionary with the key "values" like the script expects. Instead, the API returns the string 'N'. Python tries to find the dict key "values"in the string 'N', fails, and generates the KeyError: 'values'.
So a new, valid OAuth token & secret should get the API to return a dict containing connection data.
I run the linkedin-1-oauth.py script again, and then visit the LinkedIn Application details page to find my new OAuth token. (The screenshot omits the values for my app. You should see alphanumeric values for each Key, Token, & Secret.)
...
I then update my linkedin-2-query.py script with the new OAuth User Token and OAuth User Secret
OAUTH_TOKEN = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # your updated OAuth User Token
OAUTH_TOKEN_SECRET = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # your updated OAuth User Secret
After updating the OAuth token & secret, I immediately run my linkedin-2-query.py script. Hooray, it runs without errors and retrieves my connection data from the API.

Categories

Resources