Expose the full token response in OAuth2Client and OAuth2Decorator.

Reviewed in https://codereview.appspot.com/7301099/.
diff --git a/oauth2client/appengine.py b/oauth2client/appengine.py
index fc148b8..a4738f8 100644
--- a/oauth2client/appengine.py
+++ b/oauth2client/appengine.py
@@ -26,6 +26,8 @@
 import os
 import pickle
 import time
+import urllib
+import urlparse
 
 from google.appengine.api import app_identity
 from google.appengine.api import memcache
@@ -34,6 +36,7 @@
 from google.appengine.ext import webapp
 from google.appengine.ext.webapp.util import login_required
 from google.appengine.ext.webapp.util import run_wsgi_app
+from apiclient import discovery
 from oauth2client import GOOGLE_AUTH_URI
 from oauth2client import GOOGLE_REVOKE_URI
 from oauth2client import GOOGLE_TOKEN_URI
@@ -55,6 +58,11 @@
 except ImportError:
   ndb = None
 
+try:
+  from urlparse import parse_qsl
+except ImportError:
+  from cgi import parse_qsl
+
 logger = logging.getLogger(__name__)
 
 OAUTH2CLIENT_NAMESPACE = 'oauth2client#ns'
@@ -570,6 +578,7 @@
                user_agent=None,
                message=None,
                callback_path='/oauth2callback',
+               token_response_param=None,
                **kwargs):
 
     """Constructor for OAuth2Decorator
@@ -592,6 +601,10 @@
       callback_path: string, The absolute path to use as the callback URI. Note
         that this must match up with the URI given when registering the
         application in the APIs Console.
+      token_response_param: string. If provided, the full JSON response
+        to the access token request will be encoded and included in this query
+        parameter in the callback URI. This is useful with providers (e.g.
+        wordpress.com) that include extra fields that the client may want.
       **kwargs: dict, Keyword arguments are be passed along as kwargs to the
         OAuth2WebServerFlow constructor.
     """
@@ -608,6 +621,7 @@
     self._message = message
     self._in_error = False
     self._callback_path = callback_path
+    self._token_response_param = token_response_param
 
   def _display_error_message(self, request_handler):
     request_handler.response.out.write('<html><body>')
@@ -782,6 +796,12 @@
               CredentialsModel, user.user_id(), 'credentials').put(credentials)
           redirect_uri = _parse_state_value(str(self.request.get('state')),
                                             user)
+
+          if decorator._token_response_param and credentials.token_response:
+            resp_json = simplejson.dumps(credentials.token_response)
+            redirect_uri = discovery._add_query_parameter(
+              redirect_uri, decorator._token_response_param, resp_json)
+
           self.redirect(redirect_uri)
 
     return OAuth2Handler
diff --git a/oauth2client/client.py b/oauth2client/client.py
index 918539a..4cc3cda 100644
--- a/oauth2client/client.py
+++ b/oauth2client/client.py
@@ -393,7 +393,7 @@
   @util.positional(8)
   def __init__(self, access_token, client_id, client_secret, refresh_token,
                token_expiry, token_uri, user_agent, revoke_uri=None,
-               id_token=None):
+               id_token=None, token_response=None):
     """Create an instance of OAuth2Credentials.
 
     This constructor is not usually called by the user, instead
@@ -410,6 +410,9 @@
       revoke_uri: string, URI for revoke endpoint. Defaults to None; a token
         can't be revoked if this is None.
       id_token: object, The identity of the resource owner.
+      token_response: dict, the decoded response to the token request. None
+        if a token hasn't been requested yet. Stored because some providers
+        (e.g. wordpress.com) include extra fields that clients may want.
 
     Notes:
       store: callable, A callable that when passed a Credential
@@ -427,6 +430,7 @@
     self.user_agent = user_agent
     self.revoke_uri = revoke_uri
     self.id_token = id_token
+    self.token_response = token_response
 
     # True if the credentials have been revoked or expired and can't be
     # refreshed.
@@ -559,7 +563,8 @@
         data['token_uri'],
         data['user_agent'],
         revoke_uri=data.get('revoke_uri', None),
-        id_token=data.get('id_token', None))
+        id_token=data.get('id_token', None),
+        token_response=data.get('token_response', None))
     retval.invalid = data['invalid']
     return retval
 
@@ -678,6 +683,7 @@
     if resp.status == 200:
       # TODO(jcgregorio) Raise an error if loads fails?
       d = simplejson.loads(content)
+      self.token_response = d
       self.access_token = d['access_token']
       self.refresh_token = d.get('refresh_token', self.refresh_token)
       if 'expires_in' in d:
@@ -1292,7 +1298,8 @@
                                self.client_secret, refresh_token, token_expiry,
                                self.token_uri, self.user_agent,
                                revoke_uri=self.revoke_uri,
-                               id_token=d.get('id_token', None))
+                               id_token=d.get('id_token', None),
+                               token_response=d)
     else:
       logger.info('Failed to retrieve access token: %s' % content)
       if 'error' in d:
diff --git a/tests/test_oauth2client.py b/tests/test_oauth2client.py
index b07ac28..f824f60 100644
--- a/tests/test_oauth2client.py
+++ b/tests/test_oauth2client.py
@@ -142,15 +142,17 @@
 
   def test_token_refresh_success(self):
     for status_code in REFRESH_STATUS_CODES:
+      token_response = {'access_token': '1/3w', 'expires_in': 3600}
       http = HttpMockSequence([
         ({'status': status_code}, ''),
-        ({'status': '200'}, '{"access_token":"1/3w","expires_in":3600}'),
+        ({'status': '200'}, simplejson.dumps(token_response)),
         ({'status': '200'}, 'echo_request_headers'),
         ])
       http = self.credentials.authorize(http)
       resp, content = http.request('http://example.com')
       self.assertEqual('Bearer 1/3w', content['Authorization'])
       self.assertFalse(self.credentials.access_token_expired)
+      self.assertEqual(token_response, self.credentials.token_response)
 
   def test_token_refresh_failure(self):
     for status_code in REFRESH_STATUS_CODES:
@@ -165,6 +167,7 @@
       except AccessTokenRefreshError:
         pass
       self.assertTrue(self.credentials.access_token_expired)
+      self.assertEqual(None, self.credentials.token_response)
 
   def test_token_revoke_success(self):
     _token_revoke_test_helper(
@@ -183,6 +186,7 @@
     http = self.credentials.authorize(http)
     resp, content = http.request('http://example.com')
     self.assertEqual(400, resp.status)
+    self.assertEqual(None, self.credentials.token_response)
 
   def test_to_from_json(self):
     json = self.credentials.to_json()
@@ -221,6 +225,10 @@
     except NonAsciiHeaderError:
       pass
 
+    self.credentials.token_response = 'foobar'
+    instance = OAuth2Credentials.from_json(self.credentials.to_json())
+    self.assertEqual('foobar', instance.token_response)
+
 
 class AccessTokenCredentialsTests(unittest.TestCase):
 
diff --git a/tests/test_oauth2client_appengine.py b/tests/test_oauth2client_appengine.py
index 2d95f08..b8e2588 100644
--- a/tests/test_oauth2client_appengine.py
+++ b/tests/test_oauth2client_appengine.py
@@ -29,7 +29,7 @@
 import os
 import time
 import unittest
-import urlparse
+import urllib
 
 try:
     from urlparse import parse_qs
@@ -121,6 +121,7 @@
       'access_token': 'foo_access_token',
       'refresh_token': 'foo_refresh_token',
       'expires_in': 3600,
+      'extra': 'value',
     }
 
   def request(self, token_uri, method, body, headers, *args, **kwargs):
@@ -499,8 +500,13 @@
         'code': 'foo_access_code',
         'state': 'foo_path:xsrfkey123',
         })
-    self.assertEqual('http://localhost/foo_path', response.headers['Location'])
+    parts = response.headers['Location'].split('?', 1)
+    self.assertEqual('http://localhost/foo_path', parts[0])
     self.assertEqual(None, self.decorator.credentials)
+    if self.decorator._token_response_param:
+        response = parse_qs(parts[1])[self.decorator._token_response_param][0]
+        self.assertEqual(Http2Mock.content,
+                         simplejson.loads(urllib.unquote(response)))
 
     m.UnsetStubs()
     m.VerifyAll()
@@ -629,6 +635,10 @@
     self.assertEqual('dummy_revoke_uri', decorator.flow.revoke_uri)
     self.assertEqual(None, decorator.flow.params.get('user_agent', None))
 
+  def test_token_response_param(self):
+    self.decorator._token_response_param = 'foobar'
+    self.test_required()
+
   def test_decorator_from_client_secrets(self):
     decorator = oauth2decorator_from_clientsecrets(
         datafile('client_secrets.json'),