Gspread Not Working with Google Colab "Authenticate User" and Oauth2Client - google-colaboratory

In Google Colab, when using the example below, I am now getting an error. This worked for years, it stopped working today for me.
When utilizing the example:
Cell 1
from google.colab import auth
auth.authenticate_user()
Cell 2
import gspread
from oauth2client.client import GoogleCredentials
gc = gspread.authorize(GoogleCredentials.get_application_default())`
Error:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-3-ac9436b5eeee> in <module>()
2 from oauth2client.client import GoogleCredentials
3
----> 4 gc = gspread.authorize(GoogleCredentials.get_application_default())
/usr/local/lib/python3.7/dist-packages/gspread/__init__.py in authorize(credentials, client_class)
38 """
39
---> 40 client = client_class(auth=credentials)
41 return client
/usr/local/lib/python3.7/dist-packages/gspread/client.py in __init__(self, auth, session)
38 def __init__(self, auth, session=None):
39 if auth is not None:
---> 40 self.auth = convert_credentials(auth)
41 self.session = session or AuthorizedSession(self.auth)
42 else:
/usr/local/lib/python3.7/dist-packages/gspread/utils.py in convert_credentials(credentials)
57
58 raise TypeError(
---> 59 "Credentials need to be from either oauth2client or from google-auth."
60 )
61
TypeError: Credentials need to be from either oauth2client or from google-auth.
I don't know where to go from here. I reached out to Google Cloud support, but realized this was out of their scope. The authentication flow is working from a Google standpoint.

Google colab has recently changed their API. The old API has been deprecated and a new one is made available.
Here is a the new code snipet that is provided by Google and works fine with gspread
from google.colab import auth
auth.authenticate_user()
import gspread
from google.auth import default
creds, _ = default()
gc = gspread.authorize(creds)
sh = gc.create('A new spreadsheet')

I had to restart the runtime and follow a new protocol. I think what we were using before is now deprecated...
https://colab.research.google.com/notebooks/snippets/sheets.ipynb#scrollTo=6d0xJz3VzLOo

You should change the authentication method following this guide
Head into Google developers console
In the box labeled “Search for APIs and Services”, search for "Google Drive API" and "Google Sheets" and enable it
Go to “APIs & Services > Credentials” and choose “Create credentials > Service account key”.
Fill out the form
Click “Create” and “Done”.
Press “Manage service accounts” above Service Accounts.
Press on ⋮ near recently created service account and select “Manage keys” and then click on “ADD KEY > Create new key”.
Select JSON key type and press “Create”.
You're going to get a json like this:
{
"type": "service_account",
"project_id": "api-project-XXX",
"private_key_id": "2cd … ba4",
"private_key": "-----BEGIN PRIVATE KEY-----\nNrDyLw … jINQh/9\n-----END PRIVATE KEY-----\n",
"client_email": "473000000000-yoursisdifferent#developer.gserviceaccount.com",
"client_id": "473 … hd.apps.googleusercontent.com",
...
}
Go to your spreadsheet and share it with a client_email from the step above. Just like you do with any other Google account.
Put the json file inside a folder in your drive
Change your cells to:
import gspread
json_dir = 'drive/MyDrive/credentials/credentials.json'
gc = gspread.service_account(filename=json_dir)
where json_dir is the path to json file created in step 8
Hope it helps

Related

Google Sheet API access with Application Default credentials

I am trying to access Google Sheet (read only mode) from Python (runs in GKE).
I am able to get application default creds, but getting scopes issue (as I am missing https://www.googleapis.com/auth/spreadsheets.readonly scope). See code below:
from googleapiclient.discovery import build
from oauth2client import client
creds=client.GoogleCredentials.get_application_default()
service = build('sheets', 'v4', credentials=creds)
sheet = service.spreadsheets()
sheet.values().get(spreadsheetId='XXXXXXXXXX', range='Sheet1!A:C').execute()
The output is:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python3.5/dist-packages/oauth2client/_helpers.py", line 133, in positional_wrapper
return wrapped(*args, **kwargs)
File "/usr/local/lib/python3.5/dist-packages/googleapiclient/http.py", line 840, in execute
raise HttpError(resp, content, uri=self.uri)
googleapiclient.errors.HttpError: <HttpError 403 when requesting https://sheets.googleapis.com/v4/spreadsheets/XXXXXX/values/Sheet1%21A%3AC?alt=json returned "Request had insufficient authentication scopes.">
I tried to read any documentation available, but al of the relevant scope related is using external service account JSON file.
Is there a way to use Application Default and add the required scope?
Answer:
You need to define your scopes in the create_scoped() method of GoogleCredentials.get_application_default().
Code snippet:
creds = GoogleCredentials.get_application_default().create_scoped(
['https://www.googleapis.com/auth/spreadsheets.readonly'])
References:
oauth2client.client module - oauth2client 4.1.2 documentation
Source code for oauth2client.client - GoogleCredentials.create_scoped
The library oauth2client is now deprecated. Use this library google-auth instead.
The user guide is here:
https://google-auth.readthedocs.io/en/master/user-guide.html
So this code:
from oauth2client.client import GoogleCredentials
from googleapiclient.discovery import build
scopes = [
'https://www.googleapis.com/auth/cloud-platform'
'https://www.googleapis.com/auth/spreadsheets.readonly',
]
credentials = GoogleCredentials.get_application_default().create_scoped(scopes)
service = build('sheets', 'v4', credentials=credentials)
Should be replaced with this code:
from google import auth
from googleapiclient.discovery import build
scopes = [
'https://www.googleapis.com/auth/cloud-platform'
'https://www.googleapis.com/auth/spreadsheets.readonly',
]
credentials, project_id = auth.default(scopes=scopes) # Returns a tuple.
service = build('sheets', 'v4', credentials=credentials)
Also, another library google-auth-httplib2 might be needed for the library google-auth to work properly based on this reply:
https://github.com/googleapis/google-auth-library-python/issues/190#issuecomment-322837328

Is there a way to authenticate OAUTH2.0 of google API through terminal?

I am fetching google photos from my account using Google Photo API. Now there is a need for me to execute that php file via terminal, but the problem is that I can't authenticate with Google API in doing so. Is there a way to do this, and if yes, then how shall it be done?
Yes, it is possible, you need an interactive login for the first authentication but then you can save the token and refresh it automatically as required.
I have implemented this class in Python to do just that.
from requests.adapters import HTTPAdapter
from requests_oauthlib import OAuth2Session
from pathlib import Path
from urllib3.util.retry import Retry
from typing import List, Optional
from json import load, dump, JSONDecodeError
import logging
log = logging.getLogger(__name__)
# OAuth endpoints given in the Google API documentation
authorization_base_url = "https://accounts.google.com/o/oauth2/v2/auth"
token_uri = "https://www.googleapis.com/oauth2/v4/token"
class Authorize:
def __init__(
self, scope: List[str], token_file: Path,
secrets_file: Path, max_retries: int = 5
):
""" A very simple class to handle Google API authorization flow
for the requests library. Includes saving the token and automatic
token refresh.
Args:
scope: list of the scopes for which permission will be granted
token_file: full path of a file in which the user token will be
placed. After first use the previous token will also be read in from
this file
secrets_file: full path of the client secrets file obtained from
Google Api Console
"""
self.max_retries = max_retries
self.scope: List[str] = scope
self.token_file: Path = token_file
self.session = None
self.token = None
try:
with secrets_file.open('r') as stream:
all_json = load(stream)
secrets = all_json['installed']
self.client_id = secrets['client_id']
self.client_secret = secrets['client_secret']
self.redirect_uri = secrets['redirect_uris'][0]
self.token_uri = secrets['token_uri']
self.extra = {
'client_id': self.client_id,
'client_secret': self.client_secret}
except (JSONDecodeError, IOError):
print('missing or bad secrets file: {}'.format(secrets_file))
exit(1)
def load_token(self) -> Optional[str]:
try:
with self.token_file.open('r') as stream:
token = load(stream)
except (JSONDecodeError, IOError):
return None
return token
def save_token(self, token: str):
with self.token_file.open('w') as stream:
dump(token, stream)
self.token_file.chmod(0o600)
def authorize(self):
""" Initiates OAuth2 authentication and authorization flow
"""
token = self.load_token()
if token:
self.session = OAuth2Session(self.client_id, token=token,
auto_refresh_url=self.token_uri,
auto_refresh_kwargs=self.extra,
token_updater=self.save_token)
else:
self.session = OAuth2Session(self.client_id, scope=self.scope,
redirect_uri=self.redirect_uri,
auto_refresh_url=self.token_uri,
auto_refresh_kwargs=self.extra,
token_updater=self.save_token)
# Redirect user to Google for authorization
authorization_url, _ = self.session.authorization_url(
authorization_base_url,
access_type="offline",
prompt="select_account")
print('Please go here and authorize,', authorization_url)
# Get the authorization verifier code from the callback url
response_code = input('Paste the response token here:')
# Fetch the access token
self.token = self.session.fetch_token(
self.token_uri, client_secret=self.client_secret,
code=response_code)
self.save_token(self.token)
# note we want retries on POST as well, need to review this once we
# start to do methods that write to Google Photos
retries = Retry(total=self.max_retries,
backoff_factor=0.1,
status_forcelist=[500, 502, 503, 504],
method_whitelist=frozenset(['GET', 'POST']),
raise_on_status=False)
self.session.mount('https://', HTTPAdapter(max_retries=retries))

'FirebaseApplication' object has no attribute 'Authentication'

I'm using the following code to fetch data from Firebase on Raspberry pi but it shows following error. However i'm able to fetch data without authentication.
from firebase.firebase import FirebaseApplication
from firebase.firebase import FirebaseAuthentication
firebase = firebase.FirebaseApplication('https://myapp.firebaseio.com/',
authentication =None)
authentication = firebase.Authentication('secretkey',
'prateekrai266#gmail.com', extra={'id': 123})
firebase.authentication = authentication
print (authentication.extra)
user = authentication.get_user()
print (user.firebase_auth_token)
result = firebase.get('/messages', None)
it shows following error
Traceback (most recent call last):
File "/home/pi/code/dataauth.py", line 7, in authentication =
firebase.Authentication('secretkey', 'prateekrai266#gmail.com',
extra={'id': 123}) AttributeError: 'FirebaseApplication' object has no
attribute 'Authentication'
I'm able to fetch data without authentication i.e. setting rules to true, by following code
from firebase.firebase import FirebaseApplication
firebase = firebase.FirebaseApplication('https://myapp.firebaseio.com/',
None)
result = firebase.get('/messages', None)
I presume you are using python-firebase which is a bit deprecated and not updated for long time. I've tried to fix your problem, found solution for your issue, but problem with authenticating still occurs.
Code:
from firebase.firebase import FirebaseApplication
from firebase.firebase import FirebaseAuthentication
DSN = config['databaseURL'] # 'https://myapp.firebaseio.com/'
SECRET = config['userPass'] # 'secretkey'
EMAIL =config['userEmail'] # 'prateekrai266#gmail.com'
authentication = FirebaseAuthentication(SECRET,EMAIL, True, True)
firebase = FirebaseApplication(DSN, authentication)
firebase.get('/messages', None)
I would suggest to move into pyrebase which is more updated and as I've just checked works.
https://github.com/thisbejim/Pyrebase
Code that works for me:
import pyrebase
config = {
'apiKey': "YYY",
'authDomain': "XXXXXXXX.firebaseapp.com",
'databaseURL': "https://XXXXXXXX.firebaseio.com",
'projectId': "XXXXXXXX",
'storageBucket': "XXXXXXXX.appspot.com",
'messagingSenderId': "ZZZ",
}
firebase = pyrebase.initialize_app(config)
userEmail = 'youremailadresthatyouaddedtofirebasedatabaseauthenticatedusers'
userPass = 'yourpasswordsetupforthisemail'
auth = firebase.auth()
# Log the user in
user = auth.sign_in_with_email_and_password(userEmail, userPass)
# Get a reference to the database service
db = firebase.database()
# data to save
data = {
"name": "Mortimer 'Morty' Smith"
}
# Pass the user's idToken to the push method
results = db.child("test").push(data, user['idToken'])
print results
results = db.child("test").get(user['idToken'])
print results.val()

Having trouble with authentication when doing rest api to retrieve Bigquery job information

I am trying to do http request to get information for a job that was submitted by another script. This script has the job id and the project. I read on Oauth20 and saw that using Application Default Credentials is the recommended way.
I have exported the default auth:
export GOOGLE_APPLICATION_CREDENTIALS= /test/proj-service-key-file.json
Here is my code:
from google.oauth2.service_account import Credentials
from google.cloud import bigquery
import requests
from oauth2client.client import GoogleCredentials
from google.auth.transport.requests import AuthorizedSession
import google.auth
credentials = GoogleCredentials.get_application_default()
session = google.auth.transport.requests.AuthorizedSession(credentials)
job_url="https://www.googleapis.com/bigquery/v2/projects/" + self.project + "/jobs/" + job_id + "?maxResults=1"
job_query_results = session.request('GET',job_url)
I am getting the following error:
self.credentials.before_request(
AttributeError: '_JWTAccessCredentials' object has no attribute 'before_request'
Any suggestions is appreciated.
Try deleting the space in your export statement:
export GOOGLE_APPLICATION_CREDENTIALS=/test/proj-service-key-file.json
I got it to work. I had to set up the scopes explicity and that did the trick:
scopes = ['https://www.googleapis.com/auth/cloud-platform',
'https://www.googleapis.com/auth/devstorage.full_control',
'https://www.googleapis.com/auth/bigquery']
credentials = credentials.create_scoped(scopes)
http = httplib2.Http()
if credentials.access_token_expired:
credentials.refresh(http)
http = credentials.authorize(http)
response, content = http.request(job_url)

OAuth2Decorator: Using developer's token to run API calls for user

For the "normal" oauth2 dance, I get to specify the user and get a corresponding token.
This allows me to make API calls masquerading as that user, i.e. on his behalf.
It can also allow the user to make calls masquerading as me.
A use case is bigquery where I don't have to grant table access to the user and I can specify my own preferred level of control.
Using the simplified OAuth2Decorator, I don't seem to have this option.
Am I right to say that?
Or is there a work-around?
In general, what is the best practice? To use the proper oauth (comprising of Flow, Credentials and Storage)? Or to use OAuth2Decorator.
Thank you very much.
You can certainly use an OAuth2Decorator
Here is an example:
main.py
import bqclient
import httplib2
import os
from django.utils import simplejson as json
from google.appengine.api import memcache
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from oauth2client.appengine import oauth2decorator_from_clientsecrets
PROJECT_ID = "xxxxxxxxxxx"
DATASET = "your_dataset"
QUERY = "select columns from dataset.table"
CLIENT_SECRETS = os.path.join(os.path.dirname(__file__),'client_secrets.json')
http = httplib2.Http(memcache)
decorator = oauth2decorator_from_clientsecrets(CLIENT_SECRETS,
'https://www.googleapis.com/auth/bigquery')
bq = bqclient.BigQueryClient(http, decorator)
class MainHandler(webapp.RequestHandler):
#decorator.oauth_required
def get(self):
data = {'data': json.dumps(bq.Query(QUERY, PROJECT_ID))}
template = os.path.join(os.path.dirname(__file__), 'index.html')
self.response.out.write(render(template, data))
application = webapp.WSGIApplication([('/', MainHandler),], debug=True)
def main():
run_wsgi_app(application)
if __name__ == '__main__':
main()
bqclient.py that gets imported in your main.py which handles BigQuery actions
from apiclient.discovery import build
class BigQueryClient(object):
def __init__(self, http, decorator):
"""Creates the BigQuery client connection"""
self.service = build('bigquery', 'v2', http=http)
self.decorator = decorator
def Query(self, query, project, timeout_ms=10):
query_config = {
'query': query,
'timeoutMs': timeout_ms
}
decorated = self.decorator.http()
queryReply = (self.service.jobs()
.query(projectId=project, body=query_config)
.execute(decorated))
jobReference=queryReply['jobReference']
while(not queryReply['jobComplete']):
queryReply = self.service.jobs().getQueryResults(
projectId=jobReference['projectId'],
jobId=jobReference['jobId'],
timeoutMs=timeout_ms).execute(decorated)
return queryReply
where all your authentication details are kept in a json file client_secrets.json
{
"web": {
"client_id": "xxxxxxxxxxxxxxx",
"client_secret": "xxxxxxxxxxxxxxx",
"redirect_uris": ["http://localhost:8080/oauth2callback"],
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token"
}
}
finally, don't forget to add these lines to your app.yaml:
- url: /oauth2callback
script: oauth2client/appengine.py
Hope that helps.
I am not sure I completely understand the use case, but if you are creating an application for others to use without their having to authorize access based on their own credentials, I would recommend using App Engine service accounts.
An example of this type of auth flow is described in the App Engine service accounts + Prediction API article.
Also, see this part and this part of the App Engine Datastore to BigQuery codelab, which also uses this authorization method.
The code might look something like this:
import httplib2
# Available in the google-api-python-client lib
from apiclient.discovery import build
from oauth2client.appengine import AppAssertionCredentials
# BigQuery Scope
SCOPE = 'https://www.googleapis.com/auth/bigquery'
# Instantiate and authorize a BigQuery API client
credentials = AppAssertionCredentials(scope=SCOPE)
http = credentials.authorize(httplib2.Http())
bigquery_service = build("bigquery", "v2", http=http)
# Make some calls to the API
jobs = bigquery_service.jobs()
result = jobs.insert(projectId='some_project_id',body='etc, etc')