1

I'm trying to create a GAE app in which a user can visit the appspot domain, be authorized (or not) using OAuth2 and then have one of their Google spreadsheets modified in an automated way using gdata.spreadsheet.service. I've gotten this to work using SignedJwtAssertionCredentials, but in that case the user must specifically allow editing from the app; I'm trying to skip this step, and have the app modify the user's spreadsheet from their own account using OAuth2.

The documentation provided by Google says that decorators are the easiest way to accomplish this, doing something like this:

from apiclient.discovery import build
from google.appengine.ext import webapp
from oauth2client.appengine import OAuth2Decorator

decorator = OAuth2Decorator(
  client_id='your_client_id',
  client_secret='your_client_secret',
  scope='https://www.googleapis.com/auth/calendar')

service = build('calendar', 'v3')

...

  @decorator.oauth_required
  def get(self):
    # Get the authorized Http object created by the decorator.
    http = decorator.http()
    # Call the service using the authorized Http object.
    request = service.events().list(calendarId='primary')
    response = request.execute(http=http)

but I have no idea what to the do with this service object to accomplish my goal with the spreadsheet modification. Any general tips or specific tips on how to work with the service object would be helpful.

1
  • I use simpleauth for oauth with gae if you want to try it. Commented Sep 18, 2014 at 1:09

1 Answer 1

1

In the provided example you're constructing a calendar service using Google Calendar API, which is not GData based API. For GData based API's you'll have to use gdata.gauth instead.

Please note that gdata.spreadsheet.service will not work with gdata.gauth as it only supports deprecated ClientLogin (check the SpreadsheetsService constructor available at [1]). You should use gdata.spreadsheets.client instead.

Complete SpreadsheetsClient documentation is available at [2]. You may consider this example where a worksheet is added to the spreadsheet:

import webapp2
import cgi
import atom.data
import gdata.data
import gdata.spreadsheets.client

from oauth2client.client import OAuth2WebServerFlow

SCOPE = 'https://spreadsheets.google.com/feeds'

flow = OAuth2WebServerFlow(
  client_id='your_client_id',
  client_secret='your_client_secret',
  scope=SCOPE,
  redirect_uri='https://your_app.appspot.com/oauth2callback',
  response_type='code')


class OAuthCalback(webapp2.RequestHandler):
    def get(self):
        # Get auth code
        auth_code = cgi.escape(self.request.get('code'))

        # Exchange auth code for credentials
        credentials = flow.step2_exchange(auth_code)

        # Get token from credentials
        auth2token = gdata.gauth.OAuth2Token(client_id=credentials.client_id,
          client_secret=credentials.client_secret,
          scope=SCOPE,
          access_token=credentials.access_token,
          refresh_token=credentials.refresh_token,
          user_agent='AppEngine-Google;(+http://code.google.com/appengine; appid: your_app_id)')

        # Construct client
        spreadsheets_client = gdata.spreadsheets.client.SpreadsheetsClient(source='https://your_app.appspot.com', auth_token=auth2token)

        # Authorize it
        auth2token.authorize(spreadsheets_client)

        # Spreadsheet key
        key = 'your_spreadsheet_key'

        # Add worksheet to the spreadsheet
        entry = spreadsheets_client.add_worksheet(key, 'test', 7, 10)


class MainHandler(webapp2.RequestHandler):
    def get(self):
        # Get url to start authorization
        auth_url = flow.step1_get_authorize_url()

        # Render link
        content = '<a style="display:inline" href="' + auth_url + ' "target="_blank">Authorize</a>'
        self.response.out.write(content)


app = webapp2.WSGIApplication([('/', MainHandler),
                                ('/oauth2callback', OAuthCalback),
                                ], debug=True)

And regarding OAuth, I would use OAuth2WebServerFlow instead (See [3] for more information). Credentials object can be serialized and deserialized with pickle. Even easier way to store credentials object is described at [4].

[1] - https://code.google.com/p/gdata-python-client/source/browse/src/gdata/spreadsheet/service.py?r=f7a9cb244df430d960f6187ee0fbf85fe0218aac
[2] - https://gdata-python-client.googlecode.com/hg/pydocs/gdata.spreadsheets.client.html#SpreadsheetsClient
[3] - https://developers.google.com/api-client-library/python/guide/aaa_oauth#OAuth2WebServerFlow
[4] - https://developers.google.com/api-client-library/python/guide/google_app_engine#Credentials

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks for your reply. I could not get this to work; I'm getting an error: FlowExchangeError: invalid_request on credentials = flow.step2_exchange(auth_code)
Thats weird... I can reproduce this error if I click "Cancel" on the consent screen (see postimg.org/image/ot8xdeoj7). Please add: import logging, error = cgi.escape(self.request.get('error')) and logging.error(error) (just before credentials = flow.step2_exchange(auth_code)). oauthclient does logging too so you should see why its failing in log entries of your app logs at appengine.google.com. Can you share that?
When I go to app_name.appspot.com/oauth2callback I get almost the exact same error message trace as your image (except for the specifics of the implementation). When I click "Authorize", no error is generated in the logs, but I'm redirected to a Google page that says, "Error: invalid_client" and below that "no application name" with some info on the request
Getting FlowExchangeError: invalid_request when visiting app_name.appspot.com/oauth2callback directly is expected as /oauth2callback implements a get method handler which is expecting authorization code supplied in the request. This handler is designed to be called by the Google server handling OAuth. As for Error: invalid_client, this is expected if the Consent screen is not set (eg postimg.org/image/kd8bhftb7 for example). Also, make sure Client ID is set up for web app and its redirect uris is populated with the redirect api for your app (eg postimg.org/image/hbxoiqcxr).
Also, keep in mind the code I provided is for a refernce. oauth2callback handler should not perform any real operations, but construct, save credentials object and redirect to a handler designed to construct the client and perform further operations.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.