1

SCENARIO:

I save token in datastore for each app user when grants the access.

I need to create several GData contacts authorized clients in order to access different user's contacts, i get the tokens for the required users from datastore and create authorized contacts clients, e.g. copy one user's contacts to other user's contacts.

PROBLEM:

The authorized client works as expected and gets the contacts feed of the user whose token is used, but with the token the token expiry date is also saved and it expires in an hour or 45 minutes and then it doesnt work anymore, i read that if the refresh token is there in the token object then token will automatically get new access token if the short lived access token is expired but this is not happening.

Question:

How can i refresh the token after getting it from the datastore if it is expired, so that i can access user's contacts as long as the user does not revoke the access?

CODE :

SCOPE = 'https://www.google.com/m8/feeds'
REDIRECT_URI = 'http://localhost:8888/mainPage'
USER_AGENT = 'shared-contacts'

token = gdata.gauth.OAuth2Token(client_id=CLIENT_ID,
                              client_secret=CLIENT_SECRET,
                              scope=SCOPE,
                              user_agent=USER_AGENT)

class MyEntity(db.Model):
  userEmail = db.StringProperty()
    access_token = ObjectProperty()

class ObjectProperty(db.BlobProperty):
    def validate(self, value):
        try:
            result = pickle.dumps(value)
            return value
        except pickle.PicklingError, e:
            return super(ObjectProperty, self).validate(value)

    def get_value_for_datastore(self, model_instance):
        result = super(ObjectProperty, self).get_value_for_datastore(model_instance)
        result = pickle.dumps(result)
        return db.Blob(result)

    def make_value_from_datastore(self, value):
        try:
            value = pickle.loads(str(value))
        except:
            pass
        return super(ObjectProperty, self).make_value_from_datastore(value)

class MainHandler(webapp2.RequestHandler):
    def get(self):
        user = users.get_current_user()
        if user:
            self.redirect(token.generate_authorize_url(
                                                  redirect_uri=REDIRECT_URI,
                                                  access_type='offline')
                                                  )
        else:
            self.redirect(users.create_login_url("/"))

class MainPage(webapp2.RequestHandler):

    def get(self):
        # Getting the token from datastore, if token exists for this user then
      # proceed otherwise get the access token and save in datastore.

      accessToken = Fetch_Access_Token()

        if accessToken:
            pass
        else:
            url = atom.http_core.Uri.parse_uri(self.request.uri)
            code = url.query
            code = code['code']

            if 'error' in code:
                # User Did Not Grant The Access
                pass
            else:
                token_user_email = users.get_current_user().email()
                token.GetAccessToken(code)
                entity = MyEntity(userEmail=token_user_email, access_token=token)
                entity.put()

        self.response.out.write(template.render('index.html',{}))

class RPCMethods():
    def MoveContacts(self, *args):

      # All tokens except current user
      shared_group_users = shared_users(skip_current_user)

        contact_client = gdata.contacts.client.ContactsClient(source=USER_AGENT)

      for sg_user in shared_group_users.itervalues():

          shared_user_token = sg_user[1]

          # PROBLEM OCCURS HERE, IT WORKS FOR ONLY 1 HOUR AND HERE I NEED
          TO REFRESH THIS TOKEN.

          # For almost an hour i get other user's Data, but after that i
          get current user's Data.

          shared_user_authorized_client = shared_user_token.authorize(contact_client)
          shared_group_contact_feed = shared_user_authorized_client.GetContacts()

application = webapp2.WSGIApplication([
                                      ('/', MainHandler),
                                      ('/mainPage', MainPage),
                                      ('/rpc', RPCHandler),
                                      ], debug=True)

UPDATE:

Working Code:

shared_user_credentials = StorageByKeyName(CredentialsModel, shared_user_credentials_key, 'credentials').get()

    if shared_user_credentials.access_token_expired == True:
        http = httplib2.Http()
        http = shared_user_credentials.authorize(http)
        shared_user_credentials.refresh(http)
  • How are you storing it in the datastore? What are you executing to get to `atom.http_core.Uri.parse_uri(self.request.uri)`? – bossylobster Feb 11 '13 at 17:58
  • @bossylobster The token object i get using gdata.gauth.OAuth2Token, i was unable to save it in the datastore using the provided methods in app engine e.g. ae_save or datastore CredentialsProperty(), so i searched n found one solution. Answer to your question was too long for the comment so i have edited the question and added all the code, please have a look, Thanks – Faizan Khan Feb 12 '13 at 13:45

1 Answers1

2

So the OAuth 2.0 code is "completely fine", but the way you are obtaining the token the subsequent times you need it is the issue.

In another post, I mention that the offline access portion of the OAuth2.0 docs state:

When your application receives a refresh token, it is important to store that refresh token for future use. If your application loses the refresh token, it will have to re-prompt the user for consent before obtaining another refresh token. If you need to re-prompt the user for consent, include the approval_prompt parameter in the authorization code request, and set the value to force.

SIMPLE FIX:

So when you call generate_authorize_url, send approval_prompt='force':

self.redirect(token.generate_authorize_url(redirect_uri=REDIRECT_URI,
                                           access_type='offline',
                                           approval_prompt='force')

You'll notice that approval_prompt is already in the signature for the generate_authorize_url method.

ACTUAL FIX:

When you are storing your token:

token_user_email = users.get_current_user().email()
token.GetAccessToken(code)
entity = MyEntity(userEmail=token_user_email, access_token=token)

you know you'll have an entity set for the current user. I'd recommend storing it instead with

entity = MyEntity(id=token_user_email, access_token=token).

When you make your users go through the OAuth 2.0 flow,

user = users.get_current_user()
if user:
    self.redirect(token.generate_authorize_url(
    ...

you should first check if there is already a token for the given user:

token = MyEntity.get_by_id(user.email())
if token is not None:
    # do something

and you should really, really, REALLY, not use token as a global object!

Honestly, there are so many ways to get this wrong and you're probably better off using the OAuth2Decorator from google-api-python-client.

For some reference on how to combine this decorator with gdata-python-client, check out an answer I posted on another question.

PARTING NOTE:

You should use the ndb datastore library. Instead of having to write that hideous ObjectProperty, you could've simple used ndb.PickleProperty with no effort required on your part.

Community
  • 1
  • 1
bossylobster
  • 9,993
  • 1
  • 42
  • 61
  • thanks alot, clicked on 'useful answer' but not enough reputations i have it says :S i am now trying the oauth flow with decorator using your post "Bridging OAuth 2.0 objects between GData and Discovery". switched from bd to ndb as well n now i dont even need to save the token because "OAuth2TokenFromCredentials" will have one. hopefully token this time ill get refreshed because i cant prompt user to grant access on every refresh using "approval_prompt" because ill need to create many contacts client to work with their contacts – Faizan Khan Feb 13 '13 at 10:56
  • now ill save the credentials object using, storage = StorageByKeyName(CredentialsModel, user.user_id(), 'credentials'), storage.put(decorator.credentials) – Faizan Khan Feb 13 '13 at 11:00
  • the object "Credentials" using decorator after getting it from the datastore, now i am able to refresh it if the access token is expired. the next problem would be when the refresh token will expire, i have read its valid for 14 days. updating the question n including the new code, thanks a lot for your help and for one of the best and most useful articles, bridging discovery n gdata. will disturb you again when i am in trouble again :) – Faizan Khan Feb 14 '13 at 14:50