Moving over OAuth 1.0 to use Storage, and updating samples to handle token revocation.
diff --git a/apiclient/ext/file.py b/apiclient/ext/file.py
index 052a91b..be72497 100644
--- a/apiclient/ext/file.py
+++ b/apiclient/ext/file.py
@@ -10,8 +10,10 @@
import pickle
+from apiclient.oauth import Storage as BaseStorage
-class Storage(object):
+
+class Storage(BaseStorage):
"""Store and retrieve a single credential to and from a file."""
def __init__(self, filename):
@@ -29,6 +31,7 @@
f.close()
except:
credentials = None
+ credentials.set_store(self.put)
return credentials
diff --git a/apiclient/oauth.py b/apiclient/oauth.py
index 1cc6cef..aacdaef 100644
--- a/apiclient/oauth.py
+++ b/apiclient/oauth.py
@@ -7,13 +7,13 @@
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
+
import copy
import httplib2
import logging
import oauth2 as oauth
import urllib
import urlparse
-
from anyjson import simplejson
try:
@@ -36,6 +36,10 @@
pass
+class CredentialsInvalidError(Error):
+ pass
+
+
def _abstract():
raise NotImplementedError('You need to override this function')
@@ -84,6 +88,29 @@
pass
+class Storage(object):
+ """Base class for all Storage objects.
+
+ Store and retrieve a single credential.
+ """
+
+ def get(self):
+ """Retrieve credential.
+
+ Returns:
+ apiclient.oauth.Credentials
+ """
+ _abstract()
+
+ def put(self, credentials):
+ """Write a credential.
+
+ Args:
+ credentials: Credentials, the credentials to store.
+ """
+ _abstract()
+
+
class OAuthCredentials(Credentials):
"""Credentials object for OAuth 1.0a
"""
@@ -98,6 +125,39 @@
self.consumer = consumer
self.token = token
self.user_agent = user_agent
+ self.store = None
+
+ # True if the credentials have been revoked
+ self._invalid = False
+
+ @property
+ def invalid(self):
+ """True if the credentials are invalid, such as being revoked."""
+ return getattr(self, "_invalid", False)
+
+ def set_store(self, store):
+ """Set the storage for the credential.
+
+ Args:
+ store: callable, a callable that when passed a Credential
+ will store the credential back to where it came from.
+ This is needed to store the latest access_token if it
+ has been revoked.
+ """
+ self.store = store
+
+ def __getstate__(self):
+ """Trim the state down to something that can be pickled.
+ """
+ d = copy.copy(self.__dict__)
+ del d['store']
+ return d
+
+ def __setstate__(self, state):
+ """Reconstitute the state of the object from being pickled.
+ """
+ self.__dict__.update(state)
+ self.store = None
def authorize(self, http):
"""
@@ -148,6 +208,15 @@
response_code = resp.status
if response_code in [301, 302]:
uri = resp['location']
+
+ # Update the stored credential if it becomes invalid.
+ if response_code == 401:
+ logging.info('Access token no longer valid: %s' % content)
+ self._invalid = True
+ if self.store is not None:
+ self.store(self)
+ raise CredentialsInvalidError("Credentials are no longer valid.")
+
return resp, content
http.request = new_request
diff --git a/oauth2client/client.py b/oauth2client/client.py
index 3527a48..e972564 100644
--- a/oauth2client/client.py
+++ b/oauth2client/client.py
@@ -208,7 +208,8 @@
d = simplejson.loads(content)
if 'error' in d:
self._invalid = True
- self.store(self)
+ if self.store is not None:
+ self.store(self)
except:
pass
logging.error('Failed to retrieve access token: %s' % content)
diff --git a/samples/buzz/buzz.py b/samples/buzz/buzz.py
index a4f6840..cf3581b 100644
--- a/samples/buzz/buzz.py
+++ b/samples/buzz/buzz.py
@@ -14,7 +14,8 @@
from apiclient.discovery import build
from apiclient.oauth import FlowThreeLegged
from apiclient.ext.authtools import run
-
+from apiclient.ext.file import Storage
+from apiclient.oauth import CredentialsInvalidError
import httplib2
import pickle
@@ -25,11 +26,8 @@
def main():
- try:
- f = open("buzz.dat", "r")
- credentials = pickle.loads(f.read())
- f.close()
- except:
+ credentials = Storage('buzz.dat').get()
+ if credentials is None or credentials.invalid == True:
buzz_discovery = build("buzz", "v1").auth_discovery()
flow = FlowThreeLegged(buzz_discovery,
@@ -49,43 +47,49 @@
developerKey="AIzaSyDRRpR3GS1F1_jKNNM9HCNd2wJQyPG3oN0")
activities = p.activities()
- # Retrieve the first two activities
- activitylist = activities.list(
- max_results='2', scope='@self', userId='@me').execute()
- print "Retrieved the first two activities"
+ try:
+ # Retrieve the first two activities
+ activitylist = activities.list(
+ max_results='2', scope='@self', userId='@me').execute()
+ print "Retrieved the first two activities"
- # Retrieve the next two activities
- if activitylist:
- activitylist = activities.list_next(activitylist).execute()
- print "Retrieved the next two activities"
+ # Retrieve the next two activities
+ if activitylist:
+ activitylist = activities.list_next(activitylist).execute()
+ print "Retrieved the next two activities"
- # Add a new activity
- new_activity_body = {
- "data": {
- 'title': 'Testing insert',
- 'object': {
- 'content': u'Just a short note to show that insert is working. ☄',
- 'type': 'note'}
- }
- }
- activity = activities.insert(userId='@me', body=new_activity_body).execute()
- print "Added a new activity"
-
- activitylist = activities.list(
- max_results='2', scope='@self', userId='@me').execute()
-
- # Add a comment to that activity
- comment_body = {
- "data": {
- "content": "This is a comment"
+ # Add a new activity
+ new_activity_body = {
+ "data": {
+ 'title': 'Testing insert',
+ 'object': {
+ 'content':
+ u'Just a short note to show that insert is working. ☄',
+ 'type': 'note'}
}
- }
- item = activitylist['items'][0]
- comment = p.comments().insert(
- userId=item['actor']['id'], postId=item['id'], body=comment_body
- ).execute()
- print 'Added a comment to the new activity'
- pprint.pprint(comment)
+ }
+ activity = activities.insert(
+ userId='@me', body=new_activity_body).execute()
+ print "Added a new activity"
+
+ activitylist = activities.list(
+ max_results='2', scope='@self', userId='@me').execute()
+
+ # Add a comment to that activity
+ comment_body = {
+ "data": {
+ "content": "This is a comment"
+ }
+ }
+ item = activitylist['items'][0]
+ comment = p.comments().insert(
+ userId=item['actor']['id'], postId=item['id'], body=comment_body
+ ).execute()
+ print 'Added a comment to the new activity'
+ pprint.pprint(comment)
+ except CredentialsInvalidError:
+ print 'Your credentials are no longer valid.'
+ print 'Please re-run this application to re-authorize.'
if __name__ == '__main__':
main()
diff --git a/samples/latitude/latitude.py b/samples/latitude/latitude.py
index 1dd1c39..c2ec635 100644
--- a/samples/latitude/latitude.py
+++ b/samples/latitude/latitude.py
@@ -28,7 +28,7 @@
def main():
credentials = Storage('latitude.dat').get()
- if credentials is None:
+ if credentials is None or credentials.invalid == True:
auth_discovery = build("latitude", "v1").auth_discovery()
flow = FlowThreeLegged(auth_discovery,
# You MUST have a consumer key and secret tied to a
diff --git a/samples/oauth2/latitude/latitude.py b/samples/oauth2/latitude/latitude.py
index e0458f4..56df083 100644
--- a/samples/oauth2/latitude/latitude.py
+++ b/samples/oauth2/latitude/latitude.py
@@ -17,15 +17,14 @@
from oauth2client.file import Storage
from oauth2client.client import OAuth2WebServerFlow
from oauth2client.tools import run
+from apiclient.oauth import CredentialsInvalidError
# Uncomment to get detailed logging
#httplib2.debuglevel = 4
def main():
- storage = Storage('latitude.dat')
- credentials = storage.get()
-
+ credentials = Storage('latitude.dat').get()
if credentials is None or credentials.invalid:
flow = OAuth2WebServerFlow(
client_id='433807057907.apps.googleusercontent.com',
@@ -50,7 +49,11 @@
"altitude": 35
}
}
- print p.currentLocation().insert(body=body).execute()
+ try:
+ print p.currentLocation().insert(body=body).execute()
+ except CredentialsInvalidError:
+ print 'Your credentials are no longer valid.'
+ print 'Please re-run this application to re-authorize.'
if __name__ == '__main__':
main()