Yohann
Yohann

Reputation: 6346

'OAuth2Token' object has no attribute 'redirect_uri' and save access token with Appengine and gdata

I try a simple HelloWorld with gdata, appengine and OAuth2. I read this post and the official post from Google.

Problem 1

According to the first post post, my app fails at the part 7 "Use the code to get an access token" :

Traceback (most recent call last):
  File "[$PATH]/dev/outils/google_appengine/google/appengine/ext/webapp/_webapp25.py", line 701, in __call__
    handler.get(*groups)
  File "[$PATH]/dev/outils/google_appengine/google/appengine/ext/webapp/util.py", line 68, in check_login
    handler_method(self, *args)
  File "[$PATH]/dev/projets/xxx/main.py", line 76, in get
    token.get_access_token(url.query)
  File "[$PATH]/dev/projets/xxx/gdata/gauth.py", line 1296, in get_access_token
    'redirect_uri': self.redirect_uri,
AttributeError: 'OAuth2Token' object has no attribute 'redirect_uri'

I provide the redirect_uri in the method generate_authorize_url() and i filled 2 "Redirect URIs" on Google APIs console :

Why the redirect_uri is loosed ?

Solution : See @bossylobster answer.


Problem 2 Now, i want to save this new access token like this :

access_token_key = 'access_token_%s' % current_user.user_id()
gdata.gauth.ae_save(token, access_token_key)

Theses lines throw this exception :

Traceback (most recent call last):
  File "[$PATH]/dev/outils/google_appengine/google/appengine/ext/webapp/_webapp25.py", line 701, in __call__
    handler.get(*groups)
  File "[$PATH]/dev/outils/google_appengine/google/appengine/ext/webapp/util.py", line 68, in check_login
    handler_method(self, *args)
  File "[$PATH]/dev/projets/xxx/main.py", line 89, in get
    gdata.gauth.ae_save(token, access_token_key)
  File "[$PATH]/dev/projets/xxx/gdata/gauth.py", line 1593, in ae_save
    return gdata.alt.app_engine.set_token(key_name, token_to_blob(token))
  File "[$PATH]/dev/projets/xxx/gdata/alt/app_engine.py", line 92, in set_token
    if Token(key_name=unique_key, t=token_str).put():
  File "[$PATH]/dev/outils/google_appengine/google/appengine/ext/db/__init__.py", line 973, in __init__
    prop.__set__(self, value)
  File "[$PATH]/dev/outils/google_appengine/google/appengine/ext/db/__init__.py", line 613, in __set__
    value = self.validate(value)
  File "[$PATH]/dev/outils/google_appengine/google/appengine/ext/db/__init__.py", line 2779, in validate
    (self.name, self.data_type.__name__, err))
BadValueError: Property t must be convertible to a Blob instance (Blob() argument should be str instance, not unicode) 

But gdata.gauth.access_token calls gdata.gauth.upgrade_to_access_token which return the token with some modification.

If i try with token_to_blob, i have this exception UnsupportedTokenType: Unable to serialize token of type <type 'unicode'>

** How save the new access Token ?**

main.py :

from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app, login_required
from google.appengine.api import users

import gdata.gauth
import atom.http_core


SETTINGS = {
    'APP_NAME': 'xxx',
    'CLIENT_ID':'xxx.apps.googleusercontent.com',
    'CLIENT_SECRET':'xxx',
    'SCOPES': ['https://www.google.com/m8/feeds/', 'https://docs.google.com/feeds/', 'https://www.google.com/calendar/feeds/'],
    'USER_AGENT' : 'xxxs',
    'OAUTH2CALLBACK':'http://localhost:8080/oauth2callback'
    #'OAUTH2CALLBACK':'http://example.com/oauth2callback'
    }

class Home(webapp.RequestHandler):

    def get(self):
        """Home"""
        if users.get_current_user():
            self.redirect("/step1")
        else:
            self.response.out.write("<a href='/step1'>Sign in google</a><br />")

class Fetcher(webapp.RequestHandler):

    @login_required
    def get(self):
        """This handler is responsible for fetching an initial OAuth
        request token and redirecting the user to the approval page."""

        current_user = users.get_current_user()

        #create token
        token = gdata.gauth.OAuth2Token(client_id = SETTINGS['CLIENT_ID'],
                                        client_secret = SETTINGS['CLIENT_SECRET'],
                                        scope = ' '.join(SETTINGS['SCOPES']),
                                        user_agent = SETTINGS['USER_AGENT'])



        url = token.generate_authorize_url(redirect_uri = SETTINGS['OAUTH2CALLBACK'])
        #save token to datastore
        gdata.gauth.ae_save(token, current_user.user_id())

        message = """<a href="%s">
        Request token for the Google Documents Scope</a>"""

        self.response.out.write(message % url)
        self.response.out.write(" ; redirect uri : %s" % token.redirect_uri)

class RequestTokenCallback(webapp.RequestHandler):


    @login_required
    def get(self):
        """When the user grants access, they are redirected back to this
        handler where their authorized request token is exchanged for a
        long-lived access token."""

        current_user = users.get_current_user()

        #get token from callback uri
        url = atom.http_core.Uri.parse_uri(self.request.uri)

        # get token from datastore
        token = gdata.gauth.ae_load(current_user.user_id())
        # SOLUTION 1
        token.redirect_uri = SETTINGS['OAUTH2CALLBACK']

        if isinstance(token, gdata.gauth.OAuth2Token):
            if 'error' in url.query:
                pass
            else:
                token.get_access_token(url.query)

         gdata.gauth.ae_save(gdata.gauth.token_to_blob(token), "access_token_" + current_user.user_id())

def main():
    application = webapp.WSGIApplication([('/', Home),
                                          ('/step1', Fetcher),
                                          ('/oauth2callback', RequestTokenCallback)],
                                         debug = True)
    run_wsgi_app(application)

if __name__ == '__main__':
    main()

app.yaml :

application: xxx
version: 2
runtime: python
api_version: 1

handlers:

- url: .*
  script: main.py

Upvotes: 0

Views: 2197

Answers (1)

bossylobster
bossylobster

Reputation: 10163

When you call AeLoad, you need to look at AeSave. You'll notice in the source code that token_to_blob is called.

However, in the source for token_to_blob, the redirect_uri is not saved to the blob, so you'll need to keep it around and call:

token = gdata.gauth.ae_load(current_user.user_id())
token.redirect_uri = SETTINGS['OAUTH2CALLBACK']

For reference, see another related post.

Answer to Question 2: Read the traceback: Blob() argument should be str instance, not unicode. Which version of Python are you using locally?

Upvotes: 0

Related Questions