Im using GSpread trying to pass the content on my JSON file (Google API Service Application credentials) as a python Dictionary on my script. Im trying to not to carry a json file wherever I take my script.
I get the following error when I tried to pass a dictionary instead of a json file on the following line:
credentials = ServiceAccountCredentials.from_json_keyfile_name(auth_gdrive(), scope)
TypeError: expected str, bytes or os.PathLike object, not set
### auth_gdrive() returns a dictionary like this:
def auth_gdrive():
dic = {
"type": "miauuuuuu",
"pass": "miauuuu"
}
Im not allow to show whats really in the dic.
Since I wanted to pass the credentials details from within my application , and not from a json file I couldn't use:
ServiceAccountCredentials.from_json_keyfile_name()
from_json_keyfile_name() expects a json file. But looking into the docs I found the following:
ServiceAccountCredentials.from_json_keyfile_dict()
This will expect an dict object , this is all I needed.
Link:
https://oauth2client.readthedocs.io/en/latest/source/oauth2client.service_account.html
Thank you everyone again
Additional tip: I am using Google API to read Google Drive files, but also using AWS. I stored the service account credentials in AWS Secrets Manager, so that I did not need a file. I copy-pasted each key-value pair from the downloaded JSON file into AWS Secrets Manager. But I kept getting the error:
Traceback (most recent call last):
File "./copy_from_google_drive_to_s3.py", line 301, in <module>
sys.exit(main())
File "./copy_from_google_drive_to_s3.py", line 96, in main
keyfile_dict=keyDict, scopes=scopes,
File "/usr/local/lib/python3.7/site-packages/oauth2client/service_account.py", line 253, in from_json_keyfile_dict
revoke_uri=revoke_uri)
File "/usr/local/lib/python3.7/site-packages/oauth2client/service_account.py", line 185, in _from_parsed_json_keyfile
signer = crypt.Signer.from_string(private_key_pkcs8_pem)
File "/usr/local/lib/python3.7/site-packages/oauth2client/_pure_python_crypt.py", line 182, in from_string
raise ValueError('No key could be detected.')
ValueError: No key could be detected.
I had to convert the string representation of newline back into newline:
# Last part of using AWS Secrets Manager, returns json string.
sa_creds = get_secret_value_response['SecretString']
# Convert JSON string to dict.
sa_creds = json.loads(sa_creds)
# In the private key, 1-char newline got replaced with 2-char '\n'
sa_creds['private_key'] = sa_creds['private_key'].replace('\\n', '\n')
credentials = ServiceAccountCredentials.from_json_keyfile_dict(
keyfile_dict=sa_creds,
scopes=['https://www.googleapis.com/auth/drive.readonly',]
)
My solution is close to Bob McCormick. The difference is that it's using the credentials method for using service account info instead of JSON file.
Here i'm using Googles Secret Manager to import service account information so that my code can connect to a different GCP project:
from google.cloud import secretmanager
from google.oauth2 import service_account
# Create the Secret Manager client.
secret_client = secretmanager.SecretManagerServiceClient()
# Build the resource name of the secret version.
name = f"projects/{project-id}/secrets/{very-secret-secret-name}/versions/latest"
# Access the secret version.
secret_response = secret_client.access_secret_version(request={"name": name})
# Getting the secret data
secret_payload = json.loads(secret_response.payload.data.decode("UTF-8"))
# Applying the credentials as INFO instead of JSON
credentials = service_account.Credentials.from_service_account_info(
secret_payload,
scopes=["https://www.googleapis.com/auth/cloud-platform"],
)
Since you're using ServiceAccountCredentials, I'm assuming you're using OAuth2 for authorization. You can skip the json file by using oauth2client.SignedJwtAssertionCredentials to create the appropriate credentials object and pass that to gspread.authorize.
import gspread
from oauth2client.client import SignedJwtAssertionCredentials
credentials = SignedJwtAssertionCredentials(service_account_name, private_key.encode(),
['https://spreadsheets.google.com/feeds'])
gclient = gspread.authorize(credentials)
UPDATE: It appears that oauth2client.SignedJwtAssertionCredentials has been deprecated in favor of oauth2client.service_account.ServiceAccountCredentials, which only supports json and p12 keyfiles.
Related
I've a cloud function in Python that need to access a Google Shared Drive.
I've get a JSON file with the credential of the account (can't be a service account because the shared drive required user from the domain) with this doc: https://developers.google.com/workspace/guides/create-credentials#desktop-app
how I get the JSON
The JSON file looks like:
{"installed":{"client_id":"xxx.apps.googleusercontent.com","project_id":"xxx-my-project","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"xxx-my-client-secret","redirect_uris":["http://localhost"]}}
In my cloud function, I use this lines:
import google.auth
from google.oauth2.credentials import Credentials
[...]
creds = Credentials.from_authorized_user_file('client_secrets.json', scopes=None)
service = build('drive', 'v3', credentials=creds)
I've put the client_secrets.json file at the same level as my main.py.
At first I've needed to remove: {"installed": and the last } because the error was that client_id etc... parameters were missing.
The error I got now and don't understand why is:
google.auth.exceptions.RefreshError: ('invalid_request: Missing required parameter: refresh_token', {'error': 'invalid_request', 'error_description': 'Missing required parameter: refresh_token'})
Where could I get this value and why is the downloaded JSON file not working without modification ?
I've tried adding:
"refresh_token":"" or "refresh_token":None but it was not OK ;-)
Thanks for you help in this.
I am using the Google API Explorer in Python to manage service accounts and push new keys. That would be the method documented here: https://developers.google.com/resources/api-libraries/documentation/iam/v1/python/latest/iam_v1.projects.serviceAccounts.keys.html#upload
After I generated a key following the required convention (X509_PEM format) I call the method upload() like in the sample below.
import googleapiclient.discovery
from google.oauth2 import service_account
if __name__ == "__main__":
credentials = service_account.Credentials.from_service_account_file(
filename="path/to/credentials.json",
scopes=["https://www.googleapis.com/auth/cloud-platform"]
)
iam_service = googleapiclient.discovery.build("iam", "v1", credentials=credentials)
public_key = "-----BEGIN CERTIFICATE-----\nMIIFDDCCAvQCCQD3eTyPcnYUejANBgkqhkiG9w0BAQUFADBIMUYwRAYDVQQDDD10\nYW5ndXktYWJlbC1zYUBhaWdueC10ZXN0LWNsb3VkLXNldHVwLmlhbS5nc2Vydmlj\nZWFjY291bnQuY29tMB4XDTIyMDIwOTE2MzkyMFoXDTIzMDIwOTE2MzkyMFowSDFG\nMEQGA1UEAww9dGFuZ3V5LWFiZWwtc2FAYWlnbngtdGVzdC1jbG91ZC1zZXR1cC5p\nYW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC\nAgoCggIBAJimPN7VYTGikobRD6E29crjdKgB1H3uSh+ViHHGLVjuwMtBVafXeiEB\nBAes/IXWUCOosLOG0HKi253+xqZAAleX5g9zFOvUMjhpdJydCVLOxVEs6EfjgikD\nU8eiaUJig4sO6VWMFLxELOkMPmKfQN8ASE0ZG/j20tez4/Refo5YqN3Fkt6WNeiO\nrQ69KpcGRGPgpo1oT0TViopkeQD+yNBfesXrrPpGYfzQxGKFzkVE5zTRiIBRAAhf\nrjfZnLK1rOUPp8ZMfrapZja5xT4sL7Ug2fKVtmULtJK5k9X36bpY6JbwTSPhyi/m\ncLw49XOpqfoBe03fxQJMes8VH1dwifBRi5wL2Q0s6Kq6s9ZFT3aWAO6WP+l1pVMA\nB605edOjgauMsdc/0Fky+IOrwnGTrAyekEfxb7zd1yr9t4d5x/WYQlRnhp9JjuS6\nvA5ZGmtn2NaqADWSJ4MuVY7j6Fn9WWQVokRnyb3DgbqhaDuLHN3H6wbgA8U+k0ou\nmEwOx4VllDkGzFw9xfZdU8jDJu7uM+00s/MHxX30wzdJwnWspBFIpmHhaQQOp2D6\nbsggo1Lzn4Xe94ozrhG6mFaoLopL+2SpjBK3LO1L3zMiH65VK3K/CDxZdrBfT5CC\nkOJzcBNQxc8a3bQFauZQ9ZAbx1/bFtALXYF/tDNwYh4TUAJuWj01AgMBAAEwDQYJ\nKoZIhvcNAQEFBQADggIBAHq9SlyKIJwEy5okpXBpKPm7oq+W4Sud7eQNoq+Vg95f\nr4R0crHjayj3GaFF5IC1NSq2WRoPqMVZ76uFHRZwnGTOqjIyty4fq8NSe7HJ2jak\nRdKBBdnxND+VJrsx8ACGA+GZcNw/s5huekduD22iU3dkDYhERDZUHHcTlakoJxTO\ndOpLcTzPtTcGOn/cerIpHS2Sdb05qnUicDuOCuD6sz7KufLAEzjKPfve7S7sUtGq\n7aqaGjZpR3xf5LkmQCYKwuaFoK3Vz96l3MgHzf2+dxezcDTfUnwU48es04PObasE\noeTlRGvlhkP2QZdlXaXcOZ83sDhgoYcqqaf2EoUm/ycYJ1QjKMw+mlCawx7I4HFi\n1cDt+xQXOlzI/0eEKhNSM7Vh7wZtzsedga5Pye+i3FqxK/tdoa3jCDmwLdoNps+W\nyrf7JdzSYqHILVSYt3CiUzKsgpyvtK/GDrhHsrIZM2oLQK+4auRSOrfW5x13v9pk\nlTUlkLEW0F3V5dhiToccYX+d/WG2pKEB/at8MSwTAvj/ZLhD7Q0oqrpAVSP0K4Lo\nhSkTRJov3fyWWfrsEsg7BEWy9iCJYbymy8NupFf6VJxBFdcME9F/pPI18v0hKa8T\nmA1ZcJLjPsZE0zmXtDfozlmZIXT2/wP+BKV8p2Rt7dZToMdBLbHc3wL3B9CpoiAM\n-----END CERTIFICATE-----"
iam_service.projects().serviceAccounts().keys().upload(
name="projects/my-cloud-project/serviceAccounts/my-sa#my-cloud-project.iam.gserviceaccount.com",
body={
"publicKeyData": public_key
}).execute()
This call returns the following error:
<HttpError 400 when requesting https://iam.googleapis.com/v1/projects/aignx-test-cloud-setup/serviceAccounts/tanguy-abel-sa#aignx-test-cloud-setup.iam.gserviceaccount.com/keys:upload?alt=json returned "Invalid value at 'public_key_data' (TYPE_BYTES), Base64 decoding failed for public_key = "-----BEGIN CERTIFICATE-----\nMIIFDDCCAvQCCQD3eTyPcnYUejANBgkqhkiG9w0BAQUFADBIMUYwRAYDVQQDDD10\nYW5ndXktYWJlbC1zYUBhaWdueC10ZXN0LWNsb3VkLXNldHVwLmlhbS5nc2Vydmlj\nZWFjY291bnQuY29tMB4XDTIyMDIwOTE2MzkyMFoXDTIzMDIwOTE2MzkyMFowSDFG\nMEQGA1UEAww9dGFuZ3V5LWFiZWwtc2FAYWlnbngtdGVzdC1jbG91ZC1zZXR1cC5p\nYW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC\nAgoCggIBAJimPN7VYTGikobRD6E29crjdKgB1H3uSh+ViHHGLVjuwMtBVafXeiEB\nBAes/IXWUCOosLOG0HKi253+xqZAAleX5g9zFOvUMjhpdJydCVLOxVEs6EfjgikD\nU8eiaUJig4sO6VWMFLxELOkMPmKfQN8ASE0ZG/j20tez4/Refo5YqN3Fkt6WNeiO\nrQ69KpcGRGPgpo1oT0TViopkeQD+yNBfesXrrPpGYfzQxGKFzkVE5zTRiIBRAAhf\nrjfZnLK1rOUPp8ZMfrapZja5xT4sL7Ug2fKVtmULtJK5k9X36bpY6JbwTSPhyi/m\ncLw49XOpqfoBe03fxQJMes8VH1dwifBRi5wL2Q0s6Kq6s9ZFT3aWAO6WP+l1pVMA\nB605edOjgauMsdc/0Fky+IOrwnGTrAyekEfxb7zd1yr9t4d5x/WYQlRnhp9JjuS6\nvA5ZGmtn2NaqADWSJ4MuVY7j6Fn9WWQVokRnyb3DgbqhaDuLHN3H6wbgA8U+k0ou\nmEwOx4VllDkGzFw9xfZdU8jDJu7uM+00s/MHxX30wzdJwnWspBFIpmHhaQQOp2D6\nbsggo1Lzn4Xe94ozrhG6mFaoLopL+2SpjBK3LO1L3zMiH65VK3K/CDxZdrBfT5CC\nkOJzcBNQxc8a3bQFauZQ9ZAbx1/bFtALXYF/tDNwYh4TUAJuWj01AgMBAAEwDQYJ\nKoZIhvcNAQEFBQADggIBAHq9SlyKIJwEy5okpXBpKPm7oq+W4Sud7eQNoq+Vg95f\nr4R0crHjayj3GaFF5IC1NSq2WRoPqMVZ76uFHRZwnGTOqjIyty4fq8NSe7HJ2jak\nRdKBBdnxND+VJrsx8ACGA+GZcNw/s5huekduD22iU3dkDYhERDZUHHcTlakoJxTO\ndOpLcTzPtTcGOn/cerIpHS2Sdb05qnUicDuOCuD6sz7KufLAEzjKPfve7S7sUtGq\n7aqaGjZpR3xf5LkmQCYKwuaFoK3Vz96l3MgHzf2+dxezcDTfUnwU48es04PObasE\noeTlRGvlhkP2QZdlXaXcOZ83sDhgoYcqqaf2EoUm/ycYJ1QjKMw+mlCawx7I4HFi\n1cDt+xQXOlzI/0eEKhNSM7Vh7wZtzsedga5Pye+i3FqxK/tdoa3jCDmwLdoNps+W\nyrf7JdzSYqHILVSYt3CiUzKsgpyvtK/GDrhHsrIZM2oLQK+4auRSOrfW5x13v9pk\nlTUlkLEW0F3V5dhiToccYX+d/WG2pKEB/at8MSwTAvj/ZLhD7Q0oqrpAVSP0K4Lo\nhSkTRJov3fyWWfrsEsg7BEWy9iCJYbymy8NupFf6VJxBFdcME9F/pPI18v0hKa8T\nmA1ZcJLjPsZE0zmXtDfozlmZIXT2/wP+BKV8p2Rt7dZToMdBLbHc3wL3B9CpoiAM\n-----END CERTIFICATE-----"".
I am able to upload the same certificate using the gcloud cli:
gcloud iam service-accounts keys upload certificate.pem --iam-account my-cloud-project#my-cloud-project.iam.gserviceaccount.com --project my-cloud-project --format json
I tried to adjust the format of the key I send within the payload in my Python code, but nothing goes through; I tried to strip the line breaks, with and without the wrapper. The documentation I linked above does not specify the excepted format in the payload. This documentation seems to describe the same format as the one I use: https://cloud.google.com/iot/docs/concepts/device-security#public_key_format
Any idea how I could fix my call? Thanks!
After analysing the payload sent by the gcloud command, I understood that the field publicKeyData must be base64-encoded. When looking at the error this actually makes sense. So that simply fixed it:
import base64
...
iam_service.projects().serviceAccounts().keys().upload(
name="projects/my-cloud-project/serviceAccounts/my-sa#my-cloud-project.iam.gserviceaccount.com",
body={
"publicKeyData": base64.b64encode(public_key.encode("utf-8")).decode("utf-8")
}).execute()
Note that the string is encoded, base64-encoded, then decoded again to be sent as a string and not bytes.
I'm trying to set the environment variable from a dict but getting and error when connecting.
#service account pulls in airflow variable that contains the json dict with service_account credentials
service_account = Variable.get('google_cloud_credentials')
os.environ["GOOGLE_APPLICATION_CREDENTIALS"]=str(service_account)
error
PermissionDeniedError: Error executing an HTTP request: HTTP response code 403 with body '<?xml version='1.0' encoding='UTF-8'?><Error><Code>AccessDenied</Code><Message>Access denied.</Message><Details>Anonymous caller does not have storage.objects.get access to the Google Cloud Storage object.</Details></Error>'
when reading if I use and point to file then there are no issues.
os.environ["GOOGLE_APPLICATION_CREDENTIALS"]=/file/path/service_account.json
I'm wondering is there a way to convert the dict object to an os path like object? I don't want to store the json file on the container and airflow/google documentation isn't clear at all.
The Python stringio package lets you create a file-like object backed by a string, but that won't help here because the consumer of this environment variable is expecting a file path, not a file-like object. I don't think it's possible to do what you're trying to do. Is there a reason you don't want to just put the credentials in a file?
There is a way to do it, but the Google documentation is terrible. So I wrote a Github gist to document the recipe that I and a colleague (Imee Cuison) developed to use the key securely. Sample code below:
import json
from google.oauth2.service_account import Credentials
from google.cloud import secretmanager
def access_secret(project_id:str, secret_id:str, version_id:str="latest")->str:
"""Return the secret in string format"""
# Create the Secret Manager client.
client = secretmanager.SecretManagerServiceClient()
# Build the resource name of the secret version.
name = f"projects/{project_id}/secrets/{secret_id}/versions/{version_id}"
# Access the secret version.
response = client.access_secret_version(name=name)
# Return the decoded payload.
return response.payload.data.decode('UTF-8')
def get_credentials_from_token(token:str)->Credentials:
"""Given an authentication token, return a Credentials object"""
credential_dict = json.loads(secret_payload)
return Credentials.from_service_account_info(credential_dict)
credentials_secret = access_secret("my_project", "my_secret")
creds = get_credentials_from_token(credentials_secret)
# And now you can use the `creds` Credentials object to authenticate to an API
Putting the service account into the repository is not a good practice. As a best practice; You need to use authentication propagating from the default google auth within your application.
For instance, using Google Cloud Kubernetes you can use the following python code :
from google.cloud.container_v1 import ClusterManagerClient
credentials, project = google.auth.default(
scopes=['https://www.googleapis.com/auth/cloud-platform', ])
credentials.refresh(google.auth.transport.requests.Request())
cluster_manager = ClusterManagerClient(credentials=credentials)
Hi there first and foremost this is my first time using Googles services. I'm trying to develop an app with the Google AutoML Vision Api (Custom Model). I have already build a custom model and generated the API keys(I hope I did it correctly tho).
After many attempts of developing via Ionics & Android and failing to connect to the to the API.
I have now taken the prediction modelling given codes in Python (on Google Colab) and even with that I still get an error message saying that Could not automatically determine credentials. I'm not sure where I have gone wrong in this. Please help. Dying.
#installing & importing libraries
!pip3 install google-cloud-automl
import sys
from google.cloud import automl_v1beta1
from google.cloud.automl_v1beta1.proto import service_pb2
#import key.json file generated by GOOGLE_APPLICATION_CREDENTIALS
from google.colab import files
credentials = files.upload()
#explicit function given by Google accounts
[https://cloud.google.com/docs/authentication/production#auth-cloud-implicit-python][1]
def explicit():
from google.cloud import storage
# Explicitly use service account credentials by specifying the private key
# file.
storage_client = storage.Client.from_service_account_json(credentials)
# Make an authenticated API request
buckets = list(storage_client.list_buckets())
print(buckets)
#import image for prediction
from google.colab import files
YOUR_LOCAL_IMAGE_FILE = files.upload()
#prediction code from modelling
def get_prediction(content, project_id, model_id):
prediction_client = automl_v1beta1.PredictionServiceClient()
name = 'projects/{}/locations/uscentral1/models/{}'.format(project_id,
model_id)
payload = {'image': {'image_bytes': content }}
params = {}
request = prediction_client.predict(name, payload, params)
return request # waits till request is returned
#print function substitute with values
content = YOUR_LOCAL_IMAGE_FILE
project_id = "REDACTED_PROJECT_ID"
model_id = "REDACTED_MODEL_ID"
print (get_prediction(content, project_id, model_id))
Error Message when run the last line of code:
credentials = files.upload()
storage_client = storage.Client.from_service_account_json(credentials)
these two lines are the issue I think.
The first one actually loads the contents of the file, but the second one expects a path to a file, instead of the contents.
Lets tackle the first line first:
I see that just passing the credentials you get after calling credentials = files.upload() will not work as explained in the docs for it. Doing it like you're doing, the credentials don't actually contain the value of the file directly, but rather a dictionary for filenames & contents.
Assuming you're only uploading the 1 credentials file, you can get the contents of the file like this (stolen from this SO answer):
from google.colab import files
uploaded = files.upload()
credentials_as_string = uploaded[uploaded.keys()[0]]
So now we actually have the contents of the uploaded file as a string, next step is to create an actual credentials object out of it.
This answer on Github shows how to create a credentials object from a string converted to json.
import json
from google.oauth2 import service_account
credentials_as_dict = json.loads(credentials_as_string)
credentials = service_account.Credentials.from_service_account_info(credentials_as_dict)
Finally we can create the storage client object using this credentials object:
storage_client = storage.Client(credentials=credentials)
Please note I've not tested this though, so please give it a go and see if it actually works.
I'm trying to access a Google Spreadsheet via the GData API using oAuth 2.0 service-account credentials created for a Python 2.7 app hosted on Google App Engine.
The app uses the recent gdata-python-client from Google, v. 2.0.18 (gdata and atom).
The app uses the recent google-api-python-client-gae, v. 1.2.
In the Google Developer Console for this project (in this example referred to as "my-gae-app"), I have created a Service Account and delegated domain-wide authority to the service account as described here
The desired spreadsheet in Google Drive belongs to a Google Apps for Work domain, here referred to as "mygoogleappsdomain.com".
I have granted read+write access for the spreadsheet to my-gae-app#appspot.gserviceaccount.com and to the email address shown for this service account and which is assigned to clientEmail variable in the code below. Not sure which of the two email addresses would be actually needed, so I assigned both. The user with the impersonateUser email address also has read+write access to this spreadsheet.
With Google API Python Client's AppAssertionCredentials I can access the meta-data of the desired spreadsheet via the Google Drive API. However, if I try to access the spreadsheet's content using gdata, I'm getting errors. The best result I could get so far with the service account is using SignedJwtAssertionCredentials, as suggested here. However, I'm stuck with this AccessRefreshTokenError: access denied and I don't understand what's going wrong.
import os
import httplib2
from google.appengine.api import memcache
from apiclient.discovery import build
from oauth2client.client import SignedJwtAssertionCredentials
import gdata.spreadsheets.client
import gdata.spreadsheet.service
# AppAssertionCredentials is not supported in gdata python client library,
# so we use SignedJwtAssertionCredentials with the credential
# file of this service account.
# Load the key in PKCS 12 format that you downloaded from the Google API
# Console when you created your Service account.
clientEmail = '10346........-g3dp......................3m1em8#developer.gserviceaccount.com'
p12File = 'app.p12'
path = os.path.join(ROOT_DIR, 'data', 'oAuth2', p12File)
impersonateUser = 'user#mygoogleappsdomain.com'
spreadsheetKey = '1mcJHJ...................................juQMw' # ID copied from URL of desired spreadsheet in Google Drive
with open(path) as f:
privateKey = f.read()
f.close()
# Getting credentials with AppAssertionCredentials only worked successfully
# for Google API Client Library for Python, e.g. accessing file's meta-data.
# So we use SignedJwtAssertionCredentials, as suggested in
# https://stackoverflow.com/questions/16026286/using-oauth2-with-service-account-on-gdata-in-python
credentials = SignedJwtAssertionCredentials(
clientEmail,
privateKey,
scope=(
'https://www.googleapis.com/auth/drive.file ',
# added the scope above as suggested somewhere else,
# but error occurs with and with-out this scope
'https://www.googleapis.com/auth/drive',
'https://spreadsheets.google.com/feeds',
'https://docs.google.com/feeds'
),
sub=impersonateUser
)
http = httplib2.Http()
http = credentials.authorize(http)
auth2token = gdata.gauth.OAuth2TokenFromCredentials(credentials)
# error will occur, wether using SpreadsheetsService() or SpreadsheetsClient()
#srv = gdata.spreadsheet.service.SpreadsheetsService()
#srv = auth2token.authorize(srv)
clt = gdata.spreadsheets.client.SpreadsheetsClient()
clt = auth2token.authorize(clt)
# Until here no errors
wks = clt.get_worksheets(spreadsheetKey)
# AccessTokenRefreshError: access_denied
This is the error I get in the remote shell:
s~my-gae-app> wks = clt.get_worksheets(spreadsheetKey)
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "gdata/spreadsheets/client.py", line 108, in get_worksheets
**kwargs)
File "gdata/client.py", line 640, in get_feed
**kwargs)
File "gdata/client.py", line 267, in request
uri=uri, auth_token=auth_token, http_request=http_request, **kwargs)
File "atom/client.py", line 122, in request
return self.http_client.request(http_request)
File "gdata/gauth.py", line 1344, in new_request
refresh_response = self._refresh(request_orig)
File "gdata/gauth.py", line 1485, in _refresh
self.credentials._refresh(httplib2.Http().request)
File "/usr/local/lib/python2.7/dist-packages/oauth2client/client.py", line 653, in _refresh
self._do_refresh_request(http_request)
File "/usr/local/lib/python2.7/dist-packages/oauth2client/client.py", line 710, in _do_refresh_request
raise AccessTokenRefreshError(error_msg)
AccessTokenRefreshError: access_denied
I'm not sure if this indicates that this service-account is denied access to the spreadsheet, or if there was an error with refreshing the access token. Do you know what's wrong with this code or setup?
I've figured out that calling SignedJwtAssertionCredentials with-out the sub parameter (for the "impersonated" user) will not produce AccessTokenRefreshError: access_denied
import os
import httplib2
from google.appengine.api import memcache
from apiclient.discovery import build
from oauth2client.client import SignedJwtAssertionCredentials
import gdata.spreadsheets.client
import gdata.spreadsheet.service
# AppAssertionCredentials is not supported in gdata python client library,
# so we use SignedJwtAssertionCredentials with the credential
# file of this service account.
# Load the key in PKCS 12 format that you downloaded from the Google API
# Console when you created your Service account.
clientEmail = '10346........-g3dp......................3m1em8#developer.gserviceaccount.com'
p12File = 'app.p12'
path = os.path.join(ROOT_DIR, 'data', 'oAuth2', p12File)
impersonateUser = 'user#mygoogleappsdomain.com'
spreadsheetKey = '1mcJHJ...................................juQMw' # ID copied from URL of desired spreadsheet in Google Drive
with open(path) as f:
privateKey = f.read()
f.close()
# Getting credentials with AppAssertionCredentials only worked successfully
# for Google API Client Library for Python, e.g. accessing file's meta-data.
# So we use SignedJwtAssertionCredentials, as suggested in
# http://stackoverflow.com/questions/16026286/using-oauth2-with-service-account-on-gdata-in-python
# but with-out the sub parameter!
credentials = SignedJwtAssertionCredentials(
clientEmail,
privateKey,
scope=(
'https://www.googleapis.com/auth/drive.file ',
# added the scope above as suggested somewhere else,
# but error occurs with and with-out this scope
'https://www.googleapis.com/auth/drive',
'https://spreadsheets.google.com/feeds',
'https://docs.google.com/feeds'
),
# sub=impersonateUser
)
http = httplib2.Http()
http = credentials.authorize(http)
auth2token = gdata.gauth.OAuth2TokenFromCredentials(credentials)
# this pattern would eventually also work using SpreadsheetsService()
# SpreadsheetsService methods are different from SpreadsheetsClient, though
#srv = gdata.spreadsheet.service.SpreadsheetsService()
#srv = auth2token.authorize(srv)
clt = gdata.spreadsheets.client.SpreadsheetsClient()
clt = auth2token.authorize(clt)
wks = clt.get_worksheets(spreadsheetKey)
# works now!