Unifying OAuth handling between gdata and newer Google APIs
T

2

1

I'm working with the Google Contacts API and Google Calendar API in Python. The former is a GData API and the latter is a Google API... API, so while clients are available, they're each covered by separate clients -- here's GData, and Google API.

The problem I'm running into working with these clients is that they both have their own way of dealing with OAuth2. The GData library provides gdata.gauth.token_to_blob(auth_token) and gdata.gauth.token_from_blob(auth_token) methods to translate auth tokens to/from strings to store in a database, while the google-api library provides a method on App Engine (the platform I'm writing for) to store the OAuth credentials.

I don't see a clear way to store a single thing (whether it be an access token or credentials) accessible to both APIs, but I really don't want users to have to authenticate twice. Is there a way to accomplish this, short of ditching Google's client libraries and writing straight HTTP calls?

Triny answered 15/11, 2013 at 0:59 Comment(0)
M
3

I was able to get the following working. It uses the oauth2decorator to do the heavy lifting, then it uses a small helper class TokenFromOAuth2Creds to apply those same credentials to the gdata client.

I should preface that I'm no gdata expert - and there may be better ways of doing this - and I haven't thoroughly tested.

import webapp2
import httplib2
from oauth2client.appengine import oauth2decorator_from_clientsecrets
from apiclient.discovery import build

import gdata.contacts.client

decorator = oauth2decorator_from_clientsecrets(
  "client_secrets.json",
  scope=["https://www.google.com/m8/feeds", "https://www.googleapis.com/auth/calendar.readonly"]
)


# Helper class to add headers to gdata
class TokenFromOAuth2Creds:
  def __init__(self, creds):
    self.creds = creds
  def modify_request(self, req):
    if self.creds.access_token_expired or not self.creds.access_token:
      self.creds.refresh(httplib2.Http())
    self.creds.apply(req.headers)


class MainHandler(webapp2.RequestHandler):
  @decorator.oauth_required
  def get(self):
    # This object is all we need for google-api-python-client access
    http = decorator.http()

    # Create a gdata client
    gd_client = gdata.contacts.client.ContactsClient(source='<var>YOUR_APPLICATION_NAME</var>')

    # And tell it to use the same credentials
    gd_client.auth_token = TokenFromOAuth2Creds(decorator.get_credentials())

    # Use Contacts API with gdata library
    feed = gd_client.GetContacts()
    for i, entry in enumerate(feed.entry):
      self.response.write('\n%s %s' % (i+1, entry.name.full_name.text if entry.name else ''))

    # Use Calendar API with google-api-python-client
    service = build("calendar", "v3")
    result = service.calendarList().list().execute(http=http)
    self.response.write(repr(result))

app = webapp2.WSGIApplication([
  ("/", MainHandler),
  (decorator.callback_path, decorator.callback_handler()),
], debug=True)

Note, if you're not using the decorator, and have gotten your credentials object by other means, you can create the same pre-authorized http object by:

http = credentials.authorize(httplib2.Http())

An alternative to using gdata is to use the http object (returned by decorator.http()) directly - this object will automatically add the right authorization headers for you - this can be used to make requests to either API, but you'll need to handle crafting the request and parsing the XML/JSON yourself:

class MainHandler(webapp2.RequestHandler):
  @decorator.oauth_required
  def get(self):
    http = decorator.http()

    self.response.write(http.request('https://www.google.com/m8/feeds/contacts/default/full')[1])    
    self.response.write(http.request('https://www.googleapis.com/calendar/v3/users/me/calendarList')[1])

Docs for httplib2: http://httplib2.googlecode.com/hg/doc/html/libhttplib2.html#http-objects

Magalymagan answered 15/11, 2013 at 22:36 Comment(2)
Thanks for the answer @aeijdenberg. When you say "an alternative to using gdata" do you mean to hit the HTTP endpoints without the gdata library, and use something else to process the feed? I may go that route, but I'm not sure how to use the http object directly as you suggest. The code that I see in the library docs just passes the object into the library methods; it isn't called directly.Triny
Added section to the above.Magalymagan
H
0

It seems that you'll need to hack your way and rewrite the GData API to let you use a token from a Storage.

But even if you are able to make GData behave as you want keep in mind that tokens are given for certain scope(s), so you will need to force the GData API to use the Google Calendar API and vice versa. I don't know if it can be done, though.

Hippocras answered 15/11, 2013 at 13:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.