Stage 1 conversion to JSON for storing Credentials.

Reviewed in http://codereview.appspot.com/4972065/
diff --git a/oauth2client/appengine.py b/oauth2client/appengine.py
index 64fd3ac..2811069 100644
--- a/oauth2client/appengine.py
+++ b/oauth2client/appengine.py
@@ -95,6 +95,16 @@
         None,
         token_uri)
 
+  @classmethod
+  def from_json(cls, json):
+    data = simplejson.loads(json)
+    retval = AccessTokenCredentials(
+        data['scope'],
+        data['audience'],
+        data['assertion_type'],
+        data['token_uri'])
+    return retval
+
   def _generate_assertion(self):
     header = {
       'typ': 'JWT',
@@ -165,17 +175,28 @@
   def get_value_for_datastore(self, model_instance):
     cred = super(CredentialsProperty,
                  self).get_value_for_datastore(model_instance)
-    return db.Blob(pickle.dumps(cred))
+    if cred is None:
+      cred = ''
+    else:
+      cred = cred.to_json()
+    return db.Blob(cred)
 
   # For reading from datastore.
   def make_value_from_datastore(self, value):
     if value is None:
       return None
-    return pickle.loads(value)
+    if len(value) == 0:
+      return None
+    credentials = None
+    try:
+      credentials = Credentials.new_from_json(value)
+    except ValueError:
+      credentials = pickle.loads(value)
+    return credentials
 
   def validate(self, value):
     if value is not None and not isinstance(value, Credentials):
-      raise BadValueError('Property %s must be convertible '
+      raise db.BadValueError('Property %s must be convertible '
                           'to an Credentials instance (%s)' %
                           (self.name, value))
     return super(CredentialsProperty, self).validate(value)
@@ -215,15 +236,15 @@
       oauth2client.Credentials
     """
     if self._cache:
-      credential = self._cache.get(self._key_name)
-      if credential:
-        return pickle.loads(credential)
+      json = self._cache.get(self._key_name)
+      if json:
+        return Credentials.new_from_json(json)
     entity = self._model.get_or_insert(self._key_name)
     credential = getattr(entity, self._property_name)
     if credential and hasattr(credential, 'set_store'):
       credential.set_store(self)
       if self._cache:
-        self._cache.set(self._key_name, pickle.dumps(credentials))
+        self._cache.set(self._key_name, credentials.to_json())
 
     return credential
 
@@ -237,7 +258,7 @@
     setattr(entity, self._property_name, credentials)
     entity.put()
     if self._cache:
-      self._cache.set(self._key_name, pickle.dumps(credentials))
+      self._cache.set(self._key_name, credentials.to_json())
 
 
 class CredentialsModel(db.Model):
diff --git a/oauth2client/client.py b/oauth2client/client.py
index 52f6fb3..2b97d4d 100644
--- a/oauth2client/client.py
+++ b/oauth2client/client.py
@@ -43,6 +43,9 @@
 
 logger = logging.getLogger(__name__)
 
+# Expiry is stored in RFC3339 UTC format
+EXPIRY_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
+
 
 class Error(Exception):
   """Base error for this module."""
@@ -71,10 +74,15 @@
 class Credentials(object):
   """Base class for all Credentials objects.
 
-  Subclasses must define an authorize() method
-  that applies the credentials to an HTTP transport.
+  Subclasses must define an authorize() method that applies the credentials to
+  an HTTP transport.
+
+  Subclasses must also specify a classmethod named 'from_json' that takes a JSON
+  string as input and returns an instaniated Crentials object.
   """
 
+  NON_SERIALIZED_MEMBERS = ['store']
+
   def authorize(self, http):
     """Take an httplib2.Http instance (or equivalent) and
     authorizes it for the set of credentials, usually by
@@ -84,6 +92,58 @@
     """
     _abstract()
 
+  def _to_json(self, strip):
+    """Utility function for creating a JSON representation of an instance of Credentials.
+
+    Args:
+      strip: array, An array of names of members to not include in the JSON.
+
+    Returns:
+       string, a JSON representation of this instance, suitable to pass to
+       from_json().
+    """
+    t = type(self)
+    d = copy.copy(self.__dict__)
+    for member in strip:
+      del d[member]
+    if 'token_expiry' in d and isinstance(d['token_expiry'], datetime.datetime):
+      d['token_expiry'] = d['token_expiry'].strftime(EXPIRY_FORMAT)
+    # Add in information we will need later to reconsistitue this instance.
+    d['_class'] = t.__name__
+    d['_module'] = t.__module__
+    return simplejson.dumps(d)
+
+  def to_json(self):
+    """Creating a JSON representation of an instance of Credentials.
+
+    Returns:
+       string, a JSON representation of this instance, suitable to pass to
+       from_json().
+    """
+    return self._to_json(Credentials.NON_SERIALIZED_MEMBERS)
+
+  @classmethod
+  def new_from_json(cls, s):
+    """Utility class method to instantiate a Credentials subclass from a JSON
+    representation produced by to_json().
+
+    Args:
+      s: string, JSON from to_json().
+
+    Returns:
+      An instance of the subclass of Credentials that was serialized with
+      to_json().
+    """
+    data = simplejson.loads(s)
+    # Find and call the right classmethod from_json() to restore the object.
+    module = data['_module']
+    m = __import__(module)
+    for sub_module in module.split('.')[1:]:
+      m = getattr(m, sub_module)
+    kls = getattr(m, data['_class'])
+    from_json = getattr(kls, 'from_json')
+    return from_json(s)
+
 
 class Flow(object):
   """Base class for all Flow objects."""
@@ -206,6 +266,36 @@
     # refreshed.
     self.invalid = False
 
+  def to_json(self):
+    return self._to_json(Credentials.NON_SERIALIZED_MEMBERS)
+
+  @classmethod
+  def from_json(cls, s):
+    """Instantiate a Credentials object from a JSON description of it. The JSON
+    should have been produced by calling .to_json() on the object.
+
+    Args:
+      data: dict, A deserialized JSON object.
+
+    Returns:
+      An instance of a Credentials subclass.
+    """
+    data = simplejson.loads(s)
+    if 'token_expiry' in data and not isinstance(data['token_expiry'],
+        datetime.datetime):
+      data['token_expiry'] = datetime.datetime.strptime(
+          data['token_expiry'], EXPIRY_FORMAT)
+    retval = OAuth2Credentials(
+        data['access_token'],
+        data['client_id'],
+        data['client_secret'],
+        data['refresh_token'],
+        data['token_expiry'],
+        data['token_uri'],
+        data['user_agent'])
+    retval.invalid = data['invalid']
+    return retval
+
   @property
   def access_token_expired(self):
     """True if the credential is expired or invalid.
@@ -218,7 +308,7 @@
     if not self.token_expiry:
       return False
 
-    now = datetime.datetime.now()
+    now = datetime.datetime.utcnow()
     if now >= self.token_expiry:
       logger.info('access_token is expired. Now: %s, token_expiry: %s',
                   now, self.token_expiry)
@@ -318,7 +408,7 @@
       self.refresh_token = d.get('refresh_token', self.refresh_token)
       if 'expires_in' in d:
         self.token_expiry = datetime.timedelta(
-            seconds=int(d['expires_in'])) + datetime.datetime.now()
+            seconds=int(d['expires_in'])) + datetime.datetime.utcnow()
       else:
         self.token_expiry = None
       if self.store:
@@ -446,6 +536,15 @@
         None,
         user_agent)
 
+
+  @classmethod
+  def from_json(cls, s):
+    data = simplejson.loads(s)
+    retval = AccessTokenCredentials(
+        data['access_token'],
+        data['user_agent'])
+    return retval
+
   def _refresh(self, http_request):
     raise AccessTokenCredentialsError(
         "The access_token is expired or invalid and can't be refreshed.")
@@ -601,7 +700,7 @@
       refresh_token = d.get('refresh_token', None)
       token_expiry = None
       if 'expires_in' in d:
-        token_expiry = datetime.datetime.now() + datetime.timedelta(
+        token_expiry = datetime.datetime.utcnow() + datetime.timedelta(
             seconds=int(d['expires_in']))
 
       logger.info('Successfully retrieved access token: %s' % content)
diff --git a/oauth2client/file.py b/oauth2client/file.py
index b7f9c7d..89140b8 100644
--- a/oauth2client/file.py
+++ b/oauth2client/file.py
@@ -23,7 +23,20 @@
 import pickle
 import threading
 
+
+try:  # pragma: no cover
+  import simplejson
+except ImportError:  # pragma: no cover
+  try:
+    # Try to import from django, should work on App Engine
+    from django.utils import simplejson
+  except ImportError:
+    # Should work for Python2.6 and higher.
+    import json as simplejson
+
+
 from client import Storage as BaseStorage
+from client import Credentials
 
 
 class Storage(BaseStorage):
@@ -40,25 +53,40 @@
       oauth2client.client.Credentials
     """
     self._lock.acquire()
+    credentials = None
     try:
       f = open(self._filename, 'r')
-      credentials = pickle.loads(f.read())
+      content = f.read()
       f.close()
+    except IOError:
+      self._lock.release()
+      return credentials
+
+    # First try reading as JSON, and if that fails fall back to pickle.
+    try:
+      credentials = Credentials.new_from_json(content)
       credentials.set_store(self)
-    except:
-      credentials = None
-    self._lock.release()
+    except ValueError:
+      # TODO(jcgregorio) On a future release remove this path to finally remove
+      # all pickle support.
+      try:
+        credentials = pickle.loads(content)
+        credentials.set_store(self)
+      except:
+        pass
+    finally:
+      self._lock.release()
 
     return credentials
 
   def put(self, credentials):
-    """Write a pickled Credentials to file.
+    """Write Credentials to file.
 
     Args:
       credentials: Credentials, the credentials to store.
     """
     self._lock.acquire()
     f = open(self._filename, 'w')
-    f.write(pickle.dumps(credentials))
+    f.write(credentials.to_json())
     f.close()
     self._lock.release()
diff --git a/oauth2client/multistore_file.py b/oauth2client/multistore_file.py
index 8841194..e3e3f6d 100644
--- a/oauth2client/multistore_file.py
+++ b/oauth2client/multistore_file.py
@@ -21,7 +21,9 @@
         'userAgent': '<user agent>',
         'scope': '<scope>'
       },
-      'credential': '<base64 encoding of pickeled Credential object>'
+      'credential': {
+        # JSON serialized Credentials.
+      }
     }
   ]
 }
@@ -47,6 +49,7 @@
     import json as simplejson
 
 from client import Storage as BaseStorage
+from client import Credentials
 
 logger = logging.getLogger(__name__)
 
@@ -295,7 +298,8 @@
     user_agent = raw_key['userAgent']
     scope = raw_key['scope']
     key = (client_id, user_agent, scope)
-    credential = pickle.loads(base64.b64decode(cred_entry['credential']))
+    credential = None
+    credential = Credentials.new_from_json(simplejson.dumps(cred_entry['credential']))
     return (key, credential)
 
   def _write(self):
@@ -312,7 +316,7 @@
           'userAgent': cred_key[1],
           'scope': cred_key[2]
           }
-      raw_cred = base64.b64encode(pickle.dumps(cred))
+      raw_cred = simplejson.loads(cred.to_json())
       raw_creds.append({'key': raw_key, 'credential': raw_cred})
     self._locked_json_write(raw_data)
 
@@ -330,6 +334,7 @@
       The credential specified or None if not present
     """
     key = (client_id, user_agent, scope)
+
     return self._data.get(key, None)
 
   def _update_credential(self, cred, scope):
diff --git a/oauth2client/tools.py b/oauth2client/tools.py
index dc779b4..574a747 100644
--- a/oauth2client/tools.py
+++ b/oauth2client/tools.py
@@ -129,12 +129,16 @@
     print '--noauth_local_webserver.'
     print
 
+  code = None
   if FLAGS.auth_local_webserver:
     httpd.handle_request()
     if 'error' in httpd.query_params:
       sys.exit('Authentication request was rejected.')
     if 'code' in httpd.query_params:
       code = httpd.query_params['code']
+    else:
+      print 'Failed to find "code" in the query parameters of the redirect.'
+      sys.exit('Try running with --noauth_local_webserver.')
   else:
     code = raw_input('Enter verification code: ').strip()