Package oauth2client :: Module client |
[hide private]
[frames] | no frames]

# Source Code for Module oauth2client.client

       1  # Copyright (C) 2010 Google Inc.
       2  #
       3  # Licensed under the Apache License, Version 2.0 (the "License");
       4  # you may not use this file except in compliance with the License.
       5  # You may obtain a copy of the License at
       6  #
       7  #      http://www.apache.org/licenses/LICENSE-2.0
       8  #
       9  # Unless required by applicable law or agreed to in writing, software
      10  # distributed under the License is distributed on an "AS IS" BASIS,
      11  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12  # See the License for the specific language governing permissions and
      13  # limitations under the License.
      14
      15  """An OAuth 2.0 client.
      16
      17  Tools for interacting with OAuth 2.0 protected resources.
      18  """
      19
      20  __author__ = 'jcgregorio@google.com (Joe Gregorio)'
      21
      22  import base64
      23  import clientsecrets
      24  import copy
      25  import datetime
      26  import httplib2
      27  import logging
      28  import os
      29  import sys
      30  import time
      31  import urllib
      32  import urlparse
      33
      34  from oauth2client import GOOGLE_AUTH_URI
      35  from oauth2client import GOOGLE_REVOKE_URI
      36  from oauth2client import GOOGLE_TOKEN_URI
      37  from oauth2client import util
      38  from oauth2client.anyjson import simplejson
      39
      40  HAS_OPENSSL = False
      41  HAS_CRYPTO = False
      42  try:
      43    from oauth2client import crypt
      44    HAS_CRYPTO = True
      45    if crypt.OpenSSLVerifier is not None:
      46      HAS_OPENSSL = True
      47  except ImportError:
      48    pass
      49
      50  try:
      51    from urlparse import parse_qsl
      52  except ImportError:
      53    from cgi import parse_qsl
      54
      55  logger = logging.getLogger(__name__)
      56
      57  # Expiry is stored in RFC3339 UTC format
      58  EXPIRY_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
      59
      60  # Which certs to use to validate id_tokens received.
      61  ID_TOKEN_VERIFICATON_CERTS = 'https://www.googleapis.com/oauth2/v1/certs'
      62
      63  # Constant to use for the out of band OAuth 2.0 flow.
      64  OOB_CALLBACK_URN = 'urn:ietf:wg:oauth:2.0:oob'
      65
      66  # Google Data client libraries may need to set this to [401, 403].
      67  REFRESH_STATUS_CODES = [401]
      68
      69
      70 -class Error(Exception):
      71    """Base error for this module."""
      72
      73
      74 -class FlowExchangeError(Error):
      75    """Error trying to exchange an authorization grant for an access token."""
      76
      77
      78 -class AccessTokenRefreshError(Error):
      79    """Error trying to refresh an expired access token."""
      80
      81
      82 -class TokenRevokeError(Error):
      83    """Error trying to revoke a token."""
      84
      85
      86 -class UnknownClientSecretsFlowError(Error):
      87    """The client secrets file called for an unknown type of OAuth 2.0 flow. """
      88
      89
      90 -class AccessTokenCredentialsError(Error):
      91    """Having only the access_token means no refresh is possible."""
      92
      93
      94 -class VerifyJwtTokenError(Error):
      95    """Could on retrieve certificates for validation."""
      96
      97
      98 -class NonAsciiHeaderError(Error):
      99    """Header names and values must be ASCII strings."""
     100
     101
     102 -def _abstract():
     103    raise NotImplementedError('You need to override this function')
     104
     105
     106 -class MemoryCache(object):
     107    """httplib2 Cache implementation which only caches locally."""
     108
     109 -  def __init__(self):
     110      self.cache = {}
     111
     112 -  def get(self, key):
     113      return self.cache.get(key)
     114
     115 -  def set(self, key, value):
     116      self.cache[key] = value
     117
     118 -  def delete(self, key):
     119      self.cache.pop(key, None)
     120
     121
     122 -class Credentials(object):
     123    """Base class for all Credentials objects.
     124
     125    Subclasses must define an authorize() method that applies the credentials to
     126    an HTTP transport.
     127
     128    Subclasses must also specify a classmethod named 'from_json' that takes a JSON
     129    string as input and returns an instaniated Credentials object.
     130    """
     131
     132    NON_SERIALIZED_MEMBERS = ['store']
     133
     134 -  def authorize(self, http):
     135      """Take an httplib2.Http instance (or equivalent) and authorizes it.
     136
     137      Authorizes it for the set of credentials, usually by replacing
     138      http.request() with a method that adds in the appropriate headers and then
     139      delegates to the original Http.request() method.
     140
     141      Args:
     142        http: httplib2.Http, an http object to be used to make the refresh
     143          request.
     144      """
     145      _abstract()
     146
     147 -  def refresh(self, http):
     148      """Forces a refresh of the access_token.
     149
     150      Args:
     151        http: httplib2.Http, an http object to be used to make the refresh
     152          request.
     153      """
     154      _abstract()
     155
     156 -  def revoke(self, http):
     157      """Revokes a refresh_token and makes the credentials void.
     158
     159      Args:
     160        http: httplib2.Http, an http object to be used to make the revoke
     161          request.
     162      """
     163      _abstract()
     164
     165 -  def apply(self, headers):
     166      """Add the authorization to the headers.
     167
     168      Args:
     169        headers: dict, the headers to add the Authorization header to.
     170      """
     171      _abstract()
     172
     173 -  def _to_json(self, strip):
     174      """Utility function that creates JSON repr. of a Credentials object.
     175
     176      Args:
     177        strip: array, An array of names of members to not include in the JSON.
     178
     179      Returns:
     180         string, a JSON representation of this instance, suitable to pass to
     181         from_json().
     182      """
     183      t = type(self)
     184      d = copy.copy(self.__dict__)
     185      for member in strip:
     186        if member in d:
     187          del d[member]
     188      if 'token_expiry' in d and isinstance(d['token_expiry'], datetime.datetime):
     189        d['token_expiry'] = d['token_expiry'].strftime(EXPIRY_FORMAT)
     190      # Add in information we will need later to reconsistitue this instance.
     191      d['_class'] = t.__name__
     192      d['_module'] = t.__module__
     193      return simplejson.dumps(d)
     194
     195 -  def to_json(self):
     196      """Creating a JSON representation of an instance of Credentials.
     197
     198      Returns:
     199         string, a JSON representation of this instance, suitable to pass to
     200         from_json().
     201      """
     202      return self._to_json(Credentials.NON_SERIALIZED_MEMBERS)
     203
     204    @classmethod
     205 -  def new_from_json(cls, s):
     206      """Utility class method to instantiate a Credentials subclass from a JSON
     207      representation produced by to_json().
     208
     209      Args:
     210        s: string, JSON from to_json().
     211
     212      Returns:
     213        An instance of the subclass of Credentials that was serialized with
     214        to_json().
     215      """
     216      data = simplejson.loads(s)
     217      # Find and call the right classmethod from_json() to restore the object.
     218      module = data['_module']
     219      try:
     220        m = __import__(module)
     221      except ImportError:
     222        # In case there's an object from the old package structure, update it
     223        module = module.replace('.apiclient', '')
     224        m = __import__(module)
     225
     226      m = __import__(module, fromlist=module.split('.')[:-1])
     227      kls = getattr(m, data['_class'])
     228      from_json = getattr(kls, 'from_json')
     229      return from_json(s)
     230
     231    @classmethod
     232 -  def from_json(cls, s):
     233      """Instantiate a Credentials object from a JSON description of it.
     234
     235      The JSON should have been produced by calling .to_json() on the object.
     236
     237      Args:
     238        data: dict, A deserialized JSON object.
     239
     240      Returns:
     241        An instance of a Credentials subclass.
     242      """
     243      return Credentials()
     244
     245
     246 -class Flow(object):
     247    """Base class for all Flow objects."""
     248    pass
     249
     250
     251 -class Storage(object):
     252    """Base class for all Storage objects.
     253
     254    Store and retrieve a single credential. This class supports locking
     255    such that multiple processes and threads can operate on a single
     256    store.
     257    """
     258
     259 -  def acquire_lock(self):
     260      """Acquires any lock necessary to access this Storage.
     261
     262      This lock is not reentrant.
     263      """
     264      pass
     265
     266 -  def release_lock(self):
     267      """Release the Storage lock.
     268
     269      Trying to release a lock that isn't held will result in a
     270      RuntimeError.
     271      """
     272      pass
     273
     274 -  def locked_get(self):
     275      """Retrieve credential.
     276
     277      The Storage lock must be held when this is called.
     278
     279      Returns:
     280        oauth2client.client.Credentials
     281      """
     282      _abstract()
     283
     284 -  def locked_put(self, credentials):
     285      """Write a credential.
     286
     287      The Storage lock must be held when this is called.
     288
     289      Args:
     290        credentials: Credentials, the credentials to store.
     291      """
     292      _abstract()
     293
     294 -  def locked_delete(self):
     295      """Delete a credential.
     296
     297      The Storage lock must be held when this is called.
     298      """
     299      _abstract()
     300
     301 -  def get(self):
     302      """Retrieve credential.
     303
     304      The Storage lock must *not* be held when this is called.
     305
     306      Returns:
     307        oauth2client.client.Credentials
     308      """
     309      self.acquire_lock()
     310      try:
     311        return self.locked_get()
     312      finally:
     313        self.release_lock()
     314
     315 -  def put(self, credentials):
     316      """Write a credential.
     317
     318      The Storage lock must be held when this is called.
     319
     320      Args:
     321        credentials: Credentials, the credentials to store.
     322      """
     323      self.acquire_lock()
     324      try:
     325        self.locked_put(credentials)
     326      finally:
     327        self.release_lock()
     328
     329 -  def delete(self):
     330      """Delete credential.
     331
     332      Frees any resources associated with storing the credential.
     333      The Storage lock must *not* be held when this is called.
     334
     335      Returns:
     336        None
     337      """
     338      self.acquire_lock()
     339      try:
     340        return self.locked_delete()
     341      finally:
     342        self.release_lock()
     343
     344
     345 -def clean_headers(headers):
     346    """Forces header keys and values to be strings, i.e not unicode.
     347
     348    The httplib module just concats the header keys and values in a way that may
     349    make the message header a unicode string, which, if it then tries to
     350    contatenate to a binary request body may result in a unicode decode error.
     351
     352    Args:
     353      headers: dict, A dictionary of headers.
     354
     355    Returns:
     356      The same dictionary but with all the keys converted to strings.
     357    """
     358    clean = {}
     359    try:
     360      for k, v in headers.iteritems():
     361        clean[str(k)] = str(v)
     362    except UnicodeEncodeError:
     363      raise NonAsciiHeaderError(k + ': ' + v)
     364    return clean
     365
     366
     367 -def _update_query_params(uri, params):
     368    """Updates a URI with new query parameters.
     369
     370    Args:
     371      uri: string, A valid URI, with potential existing query parameters.
     372      params: dict, A dictionary of query parameters.
     373
     374    Returns:
     375      The same URI but with the new query parameters added.
     376    """
     377    parts = list(urlparse.urlparse(uri))
     378    query_params = dict(parse_qsl(parts[4])) # 4 is the index of the query part
     379    query_params.update(params)
     380    parts[4] = urllib.urlencode(query_params)
     381    return urlparse.urlunparse(parts)
     382
     383
     384 -class OAuth2Credentials(Credentials):
     385    """Credentials object for OAuth 2.0.
     386
     387    Credentials can be applied to an httplib2.Http object using the authorize()
     388    method, which then adds the OAuth 2.0 access token to each request.
     389
     390    OAuth2Credentials objects may be safely pickled and unpickled.
     391    """
     392
     393    @util.positional(8)
     394 -  def __init__(self, access_token, client_id, client_secret, refresh_token,
     395                 token_expiry, token_uri, user_agent, revoke_uri=None,
     396                 id_token=None, token_response=None):
     397      """Create an instance of OAuth2Credentials.
     398
     399      This constructor is not usually called by the user, instead
     400      OAuth2Credentials objects are instantiated by the OAuth2WebServerFlow.
     401
     402      Args:
     403        access_token: string, access token.
     404        client_id: string, client identifier.
     405        client_secret: string, client secret.
     406        refresh_token: string, refresh token.
     407        token_expiry: datetime, when the access_token expires.
     408        token_uri: string, URI of token endpoint.
     409        user_agent: string, The HTTP User-Agent to provide for this application.
     410        revoke_uri: string, URI for revoke endpoint. Defaults to None; a token
     411          can't be revoked if this is None.
     412        id_token: object, The identity of the resource owner.
     413        token_response: dict, the decoded response to the token request. None
     414          if a token hasn't been requested yet. Stored because some providers
     415          (e.g. wordpress.com) include extra fields that clients may want.
     416
     417      Notes:
     418        store: callable, A callable that when passed a Credential
     419          will store the credential back to where it came from.
     420          This is needed to store the latest access_token if it
     421          has expired and been refreshed.
     422      """
     423      self.access_token = access_token
     424      self.client_id = client_id
     425      self.client_secret = client_secret
     426      self.refresh_token = refresh_token
     427      self.store = None
     428      self.token_expiry = token_expiry
     429      self.token_uri = token_uri
     430      self.user_agent = user_agent
     431      self.revoke_uri = revoke_uri
     432      self.id_token = id_token
     433      self.token_response = token_response
     434
     435      # True if the credentials have been revoked or expired and can't be
     436      # refreshed.
     437      self.invalid = False
     438
     439 -  def authorize(self, http):
     440      """Authorize an httplib2.Http instance with these credentials.
     441
     442      The modified http.request method will add authentication headers to each
     443      request and will refresh access_tokens when a 401 is received on a
     444      request. In addition the http.request method has a credentials property,
     445      http.request.credentials, which is the Credentials object that authorized
     446      it.
     447
     448      Args:
     449         http: An instance of httplib2.Http
     450           or something that acts like it.
     451
     452      Returns:
     453         A modified instance of http that was passed in.
     454
     455      Example:
     456
     457        h = httplib2.Http()
     458        h = credentials.authorize(h)
     459
     460      You can't create a new OAuth subclass of httplib2.Authenication
     461      because it never gets passed the absolute URI, which is needed for
     462      signing. So instead we have to overload 'request' with a closure
     463      that adds in the Authorization header and then calls the original
     464      version of 'request()'.
     465      """
     466      request_orig = http.request
     467
     468      # The closure that will replace 'httplib2.Http.request'.
     469      @util.positional(1)
     470      def new_request(uri, method='GET', body=None, headers=None,
     471                      redirections=httplib2.DEFAULT_MAX_REDIRECTS,
     472                      connection_type=None):
     473        if not self.access_token:
     474          logger.info('Attempting refresh to obtain initial access_token')
     475          self._refresh(request_orig)
     476
     477        # Modify the request headers to add the appropriate
     478        # Authorization header.
     479        if headers is None:
     480          headers = {}
     481        self.apply(headers)
     482
     483        if self.user_agent is not None:
     484          if 'user-agent' in headers:
     485            headers['user-agent'] = self.user_agent + ' ' + headers['user-agent']
     486          else:
     487            headers['user-agent'] = self.user_agent
     488
     489        resp, content = request_orig(uri, method, body, clean_headers(headers),
     490                                     redirections, connection_type)
     491
     492        if resp.status in REFRESH_STATUS_CODES:
     493          logger.info('Refreshing due to a %s' % str(resp.status))
     494          self._refresh(request_orig)
     495          self.apply(headers)
     496          return request_orig(uri, method, body, clean_headers(headers),
     497                              redirections, connection_type)
     498        else:
     499          return (resp, content)
     500
     501      # Replace the request method with our own closure.
     502      http.request = new_request
     503
     504      # Set credentials as a property of the request method.
     505      setattr(http.request, 'credentials', self)
     506
     507      return http
     508
     509 -  def refresh(self, http):
     510      """Forces a refresh of the access_token.
     511
     512      Args:
     513        http: httplib2.Http, an http object to be used to make the refresh
     514          request.
     515      """
     516      self._refresh(http.request)
     517
     518 -  def revoke(self, http):
     519      """Revokes a refresh_token and makes the credentials void.
     520
     521      Args:
     522        http: httplib2.Http, an http object to be used to make the revoke
     523          request.
     524      """
     525      self._revoke(http.request)
     526
     527 -  def apply(self, headers):
     528      """Add the authorization to the headers.
     529
     530      Args:
     531        headers: dict, the headers to add the Authorization header to.
     532      """
     533      headers['Authorization'] = 'Bearer ' + self.access_token
     534
     535 -  def to_json(self):
     536      return self._to_json(Credentials.NON_SERIALIZED_MEMBERS)
     537
     538    @classmethod
     539 -  def from_json(cls, s):
     540      """Instantiate a Credentials object from a JSON description of it. The JSON
     541      should have been produced by calling .to_json() on the object.
     542
     543      Args:
     544        data: dict, A deserialized JSON object.
     545
     546      Returns:
     547        An instance of a Credentials subclass.
     548      """
     549      data = simplejson.loads(s)
     550      if 'token_expiry' in data and not isinstance(data['token_expiry'],
     551          datetime.datetime):
     552        try:
     553          data['token_expiry'] = datetime.datetime.strptime(
     554              data['token_expiry'], EXPIRY_FORMAT)
     555        except:
     556          data['token_expiry'] = None
     557      retval = cls(
     558          data['access_token'],
     559          data['client_id'],
     560          data['client_secret'],
     561          data['refresh_token'],
     562          data['token_expiry'],
     563          data['token_uri'],
     564          data['user_agent'],
     565          revoke_uri=data.get('revoke_uri', None),
     566          id_token=data.get('id_token', None),
     567          token_response=data.get('token_response', None))
     568      retval.invalid = data['invalid']
     569      return retval
     570
     571    @property
     572 -  def access_token_expired(self):
     573      """True if the credential is expired or invalid.
     574
     575      If the token_expiry isn't set, we assume the token doesn't expire.
     576      """
     577      if self.invalid:
     578        return True
     579
     580      if not self.token_expiry:
     581        return False
     582
     583      now = datetime.datetime.utcnow()
     584      if now >= self.token_expiry:
     585        logger.info('access_token is expired. Now: %s, token_expiry: %s',
     586                    now, self.token_expiry)
     587        return True
     588      return False
     589
     590 -  def set_store(self, store):
     591      """Set the Storage for the credential.
     592
     593      Args:
     594        store: Storage, an implementation of Stroage object.
     595          This is needed to store the latest access_token if it
     596          has expired and been refreshed. This implementation uses
     597          locking to check for updates before updating the
     598          access_token.
     599      """
     600      self.store = store
     601
     602 -  def _updateFromCredential(self, other):
     603      """Update this Credential from another instance."""
     604      self.__dict__.update(other.__getstate__())
     605
     606 -  def __getstate__(self):
     607      """Trim the state down to something that can be pickled."""
     608      d = copy.copy(self.__dict__)
     609      del d['store']
     610      return d
     611
     612 -  def __setstate__(self, state):
     613      """Reconstitute the state of the object from being pickled."""
     614      self.__dict__.update(state)
     615      self.store = None
     616
     617 -  def _generate_refresh_request_body(self):
     618      """Generate the body that will be used in the refresh request."""
     619      body = urllib.urlencode({
     620          'grant_type': 'refresh_token',
     621          'client_id': self.client_id,
     622          'client_secret': self.client_secret,
     623          'refresh_token': self.refresh_token,
     624          })
     625      return body
     626
     627 -  def _generate_refresh_request_headers(self):
     628      """Generate the headers that will be used in the refresh request."""
     629      headers = {
     630          'content-type': 'application/x-www-form-urlencoded',
     631      }
     632
     633      if self.user_agent is not None:
     634        headers['user-agent'] = self.user_agent
     635
     636      return headers
     637
     638 -  def _refresh(self, http_request):
     639      """Refreshes the access_token.
     640
     641      This method first checks by reading the Storage object if available.
     642      If a refresh is still needed, it holds the Storage lock until the
     643      refresh is completed.
     644
     645      Args:
     646        http_request: callable, a callable that matches the method signature of
     647          httplib2.Http.request, used to make the refresh request.
     648
     649      Raises:
     650        AccessTokenRefreshError: When the refresh fails.
     651      """
     652      if not self.store:
     653        self._do_refresh_request(http_request)
     654      else:
     655        self.store.acquire_lock()
     656        try:
     657          new_cred = self.store.locked_get()
     658          if (new_cred and not new_cred.invalid and
     659              new_cred.access_token != self.access_token):
     660            logger.info('Updated access_token read from Storage')
     661            self._updateFromCredential(new_cred)
     662          else:
     663            self._do_refresh_request(http_request)
     664        finally:
     665          self.store.release_lock()
     666
     667 -  def _do_refresh_request(self, http_request):
     668      """Refresh the access_token using the refresh_token.
     669
     670      Args:
     671        http_request: callable, a callable that matches the method signature of
     672          httplib2.Http.request, used to make the refresh request.
     673
     674      Raises:
     675        AccessTokenRefreshError: When the refresh fails.
     676      """
     677      body = self._generate_refresh_request_body()
     678      headers = self._generate_refresh_request_headers()
     679
     680      logger.info('Refreshing access_token')
     681      resp, content = http_request(
     682          self.token_uri, method='POST', body=body, headers=headers)
     683      if resp.status == 200:
     684        # TODO(jcgregorio) Raise an error if loads fails?
     685        d = simplejson.loads(content)
     686        self.token_response = d
     687        self.access_token = d['access_token']
     688        self.refresh_token = d.get('refresh_token', self.refresh_token)
     689        if 'expires_in' in d:
     690          self.token_expiry = datetime.timedelta(
     691              seconds=int(d['expires_in'])) + datetime.datetime.utcnow()
     692        else:
     693          self.token_expiry = None
     694        if self.store:
     695          self.store.locked_put(self)
     696      else:
     697        # An {'error':...} response body means the token is expired or revoked,
     698        # so we flag the credentials as such.
     699        logger.info('Failed to retrieve access token: %s' % content)
     700        error_msg = 'Invalid response %s.' % resp['status']
     701        try:
     702          d = simplejson.loads(content)
     703          if 'error' in d:
     704            error_msg = d['error']
     705            self.invalid = True
     706            if self.store:
     707              self.store.locked_put(self)
     708        except StandardError:
     709          pass
     710        raise AccessTokenRefreshError(error_msg)
     711
     712 -  def _revoke(self, http_request):
     713      """Revokes the refresh_token and deletes the store if available.
     714
     715      Args:
     716        http_request: callable, a callable that matches the method signature of
     717          httplib2.Http.request, used to make the revoke request.
     718      """
     719      self._do_revoke(http_request, self.refresh_token)
     720
     721 -  def _do_revoke(self, http_request, token):
     722      """Revokes the credentials and deletes the store if available.
     723
     724      Args:
     725        http_request: callable, a callable that matches the method signature of
     726          httplib2.Http.request, used to make the refresh request.
     727        token: A string used as the token to be revoked. Can be either an
     728          access_token or refresh_token.
     729
     730      Raises:
     731        TokenRevokeError: If the revoke request does not return with a 200 OK.
     732      """
     733      logger.info('Revoking token')
     734      query_params = {'token': token}
     735      token_revoke_uri = _update_query_params(self.revoke_uri, query_params)
     736      resp, content = http_request(token_revoke_uri)
     737      if resp.status == 200:
     738        self.invalid = True
     739      else:
     740        error_msg = 'Invalid response %s.' % resp.status
     741        try:
     742          d = simplejson.loads(content)
     743          if 'error' in d:
     744            error_msg = d['error']
     745        except StandardError:
     746          pass
     747        raise TokenRevokeError(error_msg)
     748
     749      if self.store:
     750        self.store.delete()
     751
     752
     753 -class AccessTokenCredentials(OAuth2Credentials):
     754    """Credentials object for OAuth 2.0.
     755
     756    Credentials can be applied to an httplib2.Http object using the
     757    authorize() method, which then signs each request from that object
     758    with the OAuth 2.0 access token. This set of credentials is for the
     759    use case where you have acquired an OAuth 2.0 access_token from
     760    another place such as a JavaScript client or another web
     761    application, and wish to use it from Python. Because only the
     762    access_token is present it can not be refreshed and will in time
     763    expire.
     764
     765    AccessTokenCredentials objects may be safely pickled and unpickled.
     766
     767    Usage:
     768      credentials = AccessTokenCredentials('<an access token>',
     769        'my-user-agent/1.0')
     770      http = httplib2.Http()
     771      http = credentials.authorize(http)
     772
     773    Exceptions:
     774      AccessTokenCredentialsExpired: raised when the access_token expires or is
     775        revoked.
     776    """
     777
     778 -  def __init__(self, access_token, user_agent, revoke_uri=None):
     779      """Create an instance of OAuth2Credentials
     780
     781      This is one of the few types if Credentials that you should contrust,
     782      Credentials objects are usually instantiated by a Flow.
     783
     784      Args:
     785        access_token: string, access token.
     786        user_agent: string, The HTTP User-Agent to provide for this application.
     787        revoke_uri: string, URI for revoke endpoint. Defaults to None; a token
     788          can't be revoked if this is None.
     789      """
     790      super(AccessTokenCredentials, self).__init__(
     791          access_token,
     792          None,
     793          None,
     794          None,
     795          None,
     796          None,
     797          user_agent,
     798          revoke_uri=revoke_uri)
     799
     800
     801    @classmethod
     802 -  def from_json(cls, s):
     803      data = simplejson.loads(s)
     804      retval = AccessTokenCredentials(
     805          data['access_token'],
     806          data['user_agent'])
     807      return retval
     808
     809 -  def _refresh(self, http_request):
     810      raise AccessTokenCredentialsError(
     811          'The access_token is expired or invalid and can\'t be refreshed.')
     812
     813 -  def _revoke(self, http_request):
     814      """Revokes the access_token and deletes the store if available.
     815
     816      Args:
     817        http_request: callable, a callable that matches the method signature of
     818          httplib2.Http.request, used to make the revoke request.
     819      """
     820      self._do_revoke(http_request, self.access_token)
     821
     822
     823 -class AssertionCredentials(OAuth2Credentials):
     824    """Abstract Credentials object used for OAuth 2.0 assertion grants.
     825
     826    This credential does not require a flow to instantiate because it
     827    represents a two legged flow, and therefore has all of the required
     828    information to generate and refresh its own access tokens. It must
     829    be subclassed to generate the appropriate assertion string.
     830
     831    AssertionCredentials objects may be safely pickled and unpickled.
     832    """
     833
     834    @util.positional(2)
     835 -  def __init__(self, assertion_type, user_agent=None,
     836                 token_uri=GOOGLE_TOKEN_URI,
     837                 revoke_uri=GOOGLE_REVOKE_URI,
     838                 **unused_kwargs):
     839      """Constructor for AssertionFlowCredentials.
     840
     841      Args:
     842        assertion_type: string, assertion type that will be declared to the auth
     843          server
     844        user_agent: string, The HTTP User-Agent to provide for this application.
     845        token_uri: string, URI for token endpoint. For convenience
     846          defaults to Google's endpoints but any OAuth 2.0 provider can be used.
     847        revoke_uri: string, URI for revoke endpoint.
     848      """
     849      super(AssertionCredentials, self).__init__(
     850          None,
     851          None,
     852          None,
     853          None,
     854          None,
     855          token_uri,
     856          user_agent,
     857          revoke_uri=revoke_uri)
     858      self.assertion_type = assertion_type
     859
     860 -  def _generate_refresh_request_body(self):
     861      assertion = self._generate_assertion()
     862
     863      body = urllib.urlencode({
     864          'assertion': assertion,
     865          'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
     866          })
     867
     868      return body
     869
     870 -  def _generate_assertion(self):
     871      """Generate the assertion string that will be used in the access token
     872      request.
     873      """
     874      _abstract()
     875
     876 -  def _revoke(self, http_request):
     877      """Revokes the access_token and deletes the store if available.
     878
     879      Args:
     880        http_request: callable, a callable that matches the method signature of
     881          httplib2.Http.request, used to make the revoke request.
     882      """
     883      self._do_revoke(http_request, self.access_token)
     884
     885
     886  if HAS_CRYPTO:
     887    # PyOpenSSL and PyCrypto are not prerequisites for oauth2client, so if it is
     888    # missing then don't create the SignedJwtAssertionCredentials or the
     889    # verify_id_token() method.
     890
     891 -  class SignedJwtAssertionCredentials(AssertionCredentials):
     892      """Credentials object used for OAuth 2.0 Signed JWT assertion grants.
     893
     894      This credential does not require a flow to instantiate because it represents
     895      a two legged flow, and therefore has all of the required information to
     896      generate and refresh its own access tokens.
     897
     898      SignedJwtAssertionCredentials requires either PyOpenSSL, or PyCrypto 2.6 or
     899      later. For App Engine you may also consider using AppAssertionCredentials.
     900      """
     901
     902      MAX_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
     903
     904      @util.positional(4)
     905 -    def __init__(self,
     906          service_account_name,
     907          private_key,
     908          scope,
     909          private_key_password='notasecret',
     910          user_agent=None,
     911          token_uri=GOOGLE_TOKEN_URI,
     912          revoke_uri=GOOGLE_REVOKE_URI,
     913          **kwargs):
     914        """Constructor for SignedJwtAssertionCredentials.
     915
     916        Args:
     917          service_account_name: string, id for account, usually an email address.
     918          private_key: string, private key in PKCS12 or PEM format.
     919          scope: string or iterable of strings, scope(s) of the credentials being
     920            requested.
     921          private_key_password: string, password for private_key, unused if
     922            private_key is in PEM format.
     923          user_agent: string, HTTP User-Agent to provide for this application.
     924          token_uri: string, URI for token endpoint. For convenience
     925            defaults to Google's endpoints but any OAuth 2.0 provider can be used.
     926          revoke_uri: string, URI for revoke endpoint.
     927          kwargs: kwargs, Additional parameters to add to the JWT token, for
     928            example sub=joe@xample.org."""
     929
     930        super(SignedJwtAssertionCredentials, self).__init__(
     931            None,
     932            user_agent=user_agent,
     933            token_uri=token_uri,
     934            revoke_uri=revoke_uri,
     935            )
     936
     937        self.scope = util.scopes_to_string(scope)
     938
     939        # Keep base64 encoded so it can be stored in JSON.
     940        self.private_key = base64.b64encode(private_key)
     941
     942        self.private_key_password = private_key_password
     943        self.service_account_name = service_account_name
     944        self.kwargs = kwargs
     945
     946      @classmethod
     947 -    def from_json(cls, s):
     948        data = simplejson.loads(s)
     949        retval = SignedJwtAssertionCredentials(
     950            data['service_account_name'],
     951            base64.b64decode(data['private_key']),
     952            data['scope'],
     953            private_key_password=data['private_key_password'],
     954            user_agent=data['user_agent'],
     955            token_uri=data['token_uri'],
     956            **data['kwargs']
     957            )
     958        retval.invalid = data['invalid']
     959        retval.access_token = data['access_token']
     960        return retval
     961
     962 -    def _generate_assertion(self):
     963        """Generate the assertion that will be used in the request."""
     964        now = long(time.time())
     965        payload = {
     966            'aud': self.token_uri,
     967            'scope': self.scope,
     968            'iat': now,
     969            'exp': now + SignedJwtAssertionCredentials.MAX_TOKEN_LIFETIME_SECS,
     970            'iss': self.service_account_name
     971        }
     972        payload.update(self.kwargs)
     973        logger.debug(str(payload))
     974
     975        private_key = base64.b64decode(self.private_key)
     976        return crypt.make_signed_jwt(crypt.Signer.from_string(
     977            private_key, self.private_key_password), payload)
     978
     979    # Only used in verify_id_token(), which is always calling to the same URI
     980    # for the certs.
     981    _cached_http = httplib2.Http(MemoryCache())
     982
     983    @util.positional(2)
     984 -  def verify_id_token(id_token, audience, http=None,
     985        cert_uri=ID_TOKEN_VERIFICATON_CERTS):
     986      """Verifies a signed JWT id_token.
     987
     988      This function requires PyOpenSSL and because of that it does not work on
     989      App Engine.
     990
     991      Args:
     992        id_token: string, A Signed JWT.
     993        audience: string, The audience 'aud' that the token should be for.
     994        http: httplib2.Http, instance to use to make the HTTP request. Callers
     995          should supply an instance that has caching enabled.
     996        cert_uri: string, URI of the certificates in JSON format to
     997          verify the JWT against.
     998
     999      Returns:
    1000        The deserialized JSON in the JWT.
    1001
    1002      Raises:
    1003        oauth2client.crypt.AppIdentityError if the JWT fails to verify.
    1004      """
    1005      if http is None:
    1006        http = _cached_http
    1007
    1008      resp, content = http.request(cert_uri)
    1009
    1010      if resp.status == 200:
    1011        certs = simplejson.loads(content)
    1012        return crypt.verify_signed_jwt_with_certs(id_token, certs, audience)
    1013      else:
    1014        raise VerifyJwtTokenError('Status code: %d' % resp.status)
    1015
    1016
    1017 -def _urlsafe_b64decode(b64string):
    1018    # Guard against unicode strings, which base64 can't handle.
    1019    b64string = b64string.encode('ascii')
    1020    padded = b64string + '=' * (4 - len(b64string) % 4)
    1021    return base64.urlsafe_b64decode(padded)
    1022
    1023
    1024 -def _extract_id_token(id_token):
    1025    """Extract the JSON payload from a JWT.
    1026
    1027    Does the extraction w/o checking the signature.
    1028
    1029    Args:
    1030      id_token: string, OAuth 2.0 id_token.
    1031
    1032    Returns:
    1033      object, The deserialized JSON payload.
    1034    """
    1035    segments = id_token.split('.')
    1036
    1037    if (len(segments) != 3):
    1038      raise VerifyJwtTokenError(
    1039        'Wrong number of segments in token: %s' % id_token)
    1040
    1041    return simplejson.loads(_urlsafe_b64decode(segments[1]))
    1042
    1043
    1044 -def _parse_exchange_token_response(content):
    1045    """Parses response of an exchange token request.
    1046
    1047    Most providers return JSON but some (e.g. Facebook) return a
    1048    url-encoded string.
    1049
    1050    Args:
    1051      content: The body of a response
    1052
    1053    Returns:
    1054      Content as a dictionary object. Note that the dict could be empty,
    1055      i.e. {}. That basically indicates a failure.
    1056    """
    1057    resp = {}
    1058    try:
    1059      resp = simplejson.loads(content)
    1060    except StandardError:
    1061      # different JSON libs raise different exceptions,
    1062      # so we just do a catch-all here
    1063      resp = dict(parse_qsl(content))
    1064
    1065    # some providers respond with 'expires', others with 'expires_in'
    1066    if resp and 'expires' in resp:
    1067      resp['expires_in'] = resp.pop('expires')
    1068
    1069    return resp
    1070
    1071
    1072  @util.positional(4)
    1073 -def credentials_from_code(client_id, client_secret, scope, code,
    1074                            redirect_uri='postmessage', http=None,
    1075                            user_agent=None, token_uri=GOOGLE_TOKEN_URI,
    1076                            auth_uri=GOOGLE_AUTH_URI,
    1077                            revoke_uri=GOOGLE_REVOKE_URI):
    1078    """Exchanges an authorization code for an OAuth2Credentials object.
    1079
    1080    Args:
    1081      client_id: string, client identifier.
    1082      client_secret: string, client secret.
    1083      scope: string or iterable of strings, scope(s) to request.
    1084      code: string, An authroization code, most likely passed down from
    1085        the client
    1086      redirect_uri: string, this is generally set to 'postmessage' to match the
    1087        redirect_uri that the client specified
    1088      http: httplib2.Http, optional http instance to use to do the fetch
    1089      token_uri: string, URI for token endpoint. For convenience
    1090        defaults to Google's endpoints but any OAuth 2.0 provider can be used.
    1091      auth_uri: string, URI for authorization endpoint. For convenience
    1092        defaults to Google's endpoints but any OAuth 2.0 provider can be used.
    1093      revoke_uri: string, URI for revoke endpoint. For convenience
    1094        defaults to Google's endpoints but any OAuth 2.0 provider can be used.
    1095
    1096    Returns:
    1097      An OAuth2Credentials object.
    1098
    1099    Raises:
    1100      FlowExchangeError if the authorization code cannot be exchanged for an
    1101       access token
    1102    """
    1103    flow = OAuth2WebServerFlow(client_id, client_secret, scope,
    1104                               redirect_uri=redirect_uri, user_agent=user_agent,
    1105                               auth_uri=auth_uri, token_uri=token_uri,
    1106                               revoke_uri=revoke_uri)
    1107
    1108    credentials = flow.step2_exchange(code, http=http)
    1109    return credentials
    1110
    1111
    1112  @util.positional(3)
    1113 -def credentials_from_clientsecrets_and_code(filename, scope, code,
    1114                                              message = None,
    1115                                              redirect_uri='postmessage',
    1116                                              http=None,
    1117                                              cache=None):
    1118    """Returns OAuth2Credentials from a clientsecrets file and an auth code.
    1119
    1120    Will create the right kind of Flow based on the contents of the clientsecrets
    1121    file or will raise InvalidClientSecretsError for unknown types of Flows.
    1122
    1123    Args:
    1124      filename: string, File name of clientsecrets.
    1125      scope: string or iterable of strings, scope(s) to request.
    1126      code: string, An authorization code, most likely passed down from
    1127        the client
    1128      message: string, A friendly string to display to the user if the
    1129        clientsecrets file is missing or invalid. If message is provided then
    1130        sys.exit will be called in the case of an error. If message in not
    1131        provided then clientsecrets.InvalidClientSecretsError will be raised.
    1132      redirect_uri: string, this is generally set to 'postmessage' to match the
    1133        redirect_uri that the client specified
    1134      http: httplib2.Http, optional http instance to use to do the fetch
    1135      cache: An optional cache service client that implements get() and set()
    1136        methods. See clientsecrets.loadfile() for details.
    1137
    1138    Returns:
    1139      An OAuth2Credentials object.
    1140
    1141    Raises:
    1142      FlowExchangeError if the authorization code cannot be exchanged for an
    1143       access token
    1144      UnknownClientSecretsFlowError if the file describes an unknown kind of Flow.
    1145      clientsecrets.InvalidClientSecretsError if the clientsecrets file is
    1146        invalid.
    1147    """
    1148    flow = flow_from_clientsecrets(filename, scope, message=message, cache=cache,
    1149                                   redirect_uri=redirect_uri)
    1150    credentials = flow.step2_exchange(code, http=http)
    1151    return credentials
    1152
    1153
    1154 -class OAuth2WebServerFlow(Flow):
    1155    """Does the Web Server Flow for OAuth 2.0.
    1156
    1157    OAuth2WebServerFlow objects may be safely pickled and unpickled.
    1158    """
    1159
    1160    @util.positional(4)
    1161 -  def __init__(self, client_id, client_secret, scope,
    1162                 redirect_uri=None,
    1163                 user_agent=None,
    1164                 auth_uri=GOOGLE_AUTH_URI,
    1165                 token_uri=GOOGLE_TOKEN_URI,
    1166                 revoke_uri=GOOGLE_REVOKE_URI,
    1167                 **kwargs):
    1168      """Constructor for OAuth2WebServerFlow.
    1169
    1170      The kwargs argument is used to set extra query parameters on the
    1171      auth_uri. For example, the access_type and approval_prompt
    1172      query parameters can be set via kwargs.
    1173
    1174      Args:
    1175        client_id: string, client identifier.
    1176        client_secret: string client secret.
    1177        scope: string or iterable of strings, scope(s) of the credentials being
    1178          requested.
    1179        redirect_uri: string, Either the string 'urn:ietf:wg:oauth:2.0:oob' for
    1180          a non-web-based application, or a URI that handles the callback from
    1181          the authorization server.
    1182        user_agent: string, HTTP User-Agent to provide for this application.
    1183        auth_uri: string, URI for authorization endpoint. For convenience
    1184          defaults to Google's endpoints but any OAuth 2.0 provider can be used.
    1185        token_uri: string, URI for token endpoint. For convenience
    1186          defaults to Google's endpoints but any OAuth 2.0 provider can be used.
    1187        revoke_uri: string, URI for revoke endpoint. For convenience
    1188          defaults to Google's endpoints but any OAuth 2.0 provider can be used.
    1189        **kwargs: dict, The keyword arguments are all optional and required
    1190                          parameters for the OAuth calls.
    1191      """
    1192      self.client_id = client_id
    1193      self.client_secret = client_secret
    1194      self.scope = util.scopes_to_string(scope)
    1195      self.redirect_uri = redirect_uri
    1196      self.user_agent = user_agent
    1197      self.auth_uri = auth_uri
    1198      self.token_uri = token_uri
    1199      self.revoke_uri = revoke_uri
    1200      self.params = {
    1201          'access_type': 'offline',
    1202          'response_type': 'code',
    1203      }
    1204      self.params.update(kwargs)
    1205
    1206    @util.positional(1)
    1207 -  def step1_get_authorize_url(self, redirect_uri=None):
    1208      """Returns a URI to redirect to the provider.
    1209
    1210      Args:
    1211        redirect_uri: string, Either the string 'urn:ietf:wg:oauth:2.0:oob' for
    1212          a non-web-based application, or a URI that handles the callback from
    1213          the authorization server. This parameter is deprecated, please move to
    1214          passing the redirect_uri in via the constructor.
    1215
    1216      Returns:
    1217        A URI as a string to redirect the user to begin the authorization flow.
    1218      """
    1219      if redirect_uri is not None:
    1220        logger.warning(('The redirect_uri parameter for'
    1221            'OAuth2WebServerFlow.step1_get_authorize_url is deprecated. Please'
    1222            'move to passing the redirect_uri in via the constructor.'))
    1223        self.redirect_uri = redirect_uri
    1224
    1225      if self.redirect_uri is None:
    1226        raise ValueError('The value of redirect_uri must not be None.')
    1227
    1228      query_params = {
    1229          'client_id': self.client_id,
    1230          'redirect_uri': self.redirect_uri,
    1231          'scope': self.scope,
    1232      }
    1233      query_params.update(self.params)
    1234      return _update_query_params(self.auth_uri, query_params)
    1235
    1236    @util.positional(2)
    1237 -  def step2_exchange(self, code, http=None):
    1238      """Exhanges a code for OAuth2Credentials.
    1239
    1240      Args:
    1241        code: string or dict, either the code as a string, or a dictionary
    1242          of the query parameters to the redirect_uri, which contains
    1243          the code.
    1244        http: httplib2.Http, optional http instance to use to do the fetch
    1245
    1246      Returns:
    1247        An OAuth2Credentials object that can be used to authorize requests.
    1248
    1249      Raises:
    1250        FlowExchangeError if a problem occured exchanging the code for a
    1251        refresh_token.
    1252      """
    1253
    1254      if not (isinstance(code, str) or isinstance(code, unicode)):
    1255        if 'code' not in code:
    1256          if 'error' in code:
    1257            error_msg = code['error']
    1258          else:
    1259            error_msg = 'No code was supplied in the query parameters.'
    1260          raise FlowExchangeError(error_msg)
    1261        else:
    1262          code = code['code']
    1263
    1264      body = urllib.urlencode({
    1265          'grant_type': 'authorization_code',
    1266          'client_id': self.client_id,
    1267          'client_secret': self.client_secret,
    1268          'code': code,
    1269          'redirect_uri': self.redirect_uri,
    1270          'scope': self.scope,
    1271          })
    1272      headers = {
    1273          'content-type': 'application/x-www-form-urlencoded',
    1274      }
    1275
    1276      if self.user_agent is not None:
    1277        headers['user-agent'] = self.user_agent
    1278
    1279      if http is None:
    1280        http = httplib2.Http()
    1281
    1282      resp, content = http.request(self.token_uri, method='POST', body=body,
    1283                                   headers=headers)
    1284      d = _parse_exchange_token_response(content)
    1285      if resp.status == 200 and 'access_token' in d:
    1286        access_token = d['access_token']
    1287        refresh_token = d.get('refresh_token', None)
    1288        token_expiry = None
    1289        if 'expires_in' in d:
    1290          token_expiry = datetime.datetime.utcnow() + datetime.timedelta(
    1291              seconds=int(d['expires_in']))
    1292
    1293        if 'id_token' in d:
    1294          d['id_token'] = _extract_id_token(d['id_token'])
    1295
    1296        logger.info('Successfully retrieved access token')
    1297        return OAuth2Credentials(access_token, self.client_id,
    1298                                 self.client_secret, refresh_token, token_expiry,
    1299                                 self.token_uri, self.user_agent,
    1300                                 revoke_uri=self.revoke_uri,
    1301                                 id_token=d.get('id_token', None),
    1302                                 token_response=d)
    1303      else:
    1304        logger.info('Failed to retrieve access token: %s' % content)
    1305        if 'error' in d:
    1306          # you never know what those providers got to say
    1307          error_msg = unicode(d['error'])
    1308        else:
    1309          error_msg = 'Invalid response: %s.' % str(resp.status)
    1310        raise FlowExchangeError(error_msg)
    1311
    1312
    1313  @util.positional(2)
    1314 -def flow_from_clientsecrets(filename, scope, redirect_uri=None,
    1315                              message=None, cache=None):
    1316    """Create a Flow from a clientsecrets file.
    1317
    1318    Will create the right kind of Flow based on the contents of the clientsecrets
    1319    file or will raise InvalidClientSecretsError for unknown types of Flows.
    1320
    1321    Args:
    1322      filename: string, File name of client secrets.
    1323      scope: string or iterable of strings, scope(s) to request.
    1324      redirect_uri: string, Either the string 'urn:ietf:wg:oauth:2.0:oob' for
    1325        a non-web-based application, or a URI that handles the callback from
    1326        the authorization server.
    1327      message: string, A friendly string to display to the user if the
    1328        clientsecrets file is missing or invalid. If message is provided then
    1329        sys.exit will be called in the case of an error. If message in not
    1330        provided then clientsecrets.InvalidClientSecretsError will be raised.
    1331      cache: An optional cache service client that implements get() and set()
    1332        methods. See clientsecrets.loadfile() for details.
    1333
    1334    Returns:
    1335      A Flow object.
    1336
    1337    Raises:
    1338      UnknownClientSecretsFlowError if the file describes an unknown kind of Flow.
    1339      clientsecrets.InvalidClientSecretsError if the clientsecrets file is
    1340        invalid.
    1341    """
    1342    try:
    1343      client_type, client_info = clientsecrets.loadfile(filename, cache=cache)
    1344      if client_type in (clientsecrets.TYPE_WEB, clientsecrets.TYPE_INSTALLED):
    1345        constructor_kwargs = {
    1346            'redirect_uri': redirect_uri,
    1347            'auth_uri': client_info['auth_uri'],
    1348            'token_uri': client_info['token_uri'],
    1349        }
    1350        revoke_uri = client_info.get('revoke_uri')
    1351        if revoke_uri is not None:
    1352          constructor_kwargs['revoke_uri'] = revoke_uri
    1353        return OAuth2WebServerFlow(
    1354            client_info['client_id'], client_info['client_secret'],
    1355            scope, **constructor_kwargs)
    1356
    1357    except clientsecrets.InvalidClientSecretsError:
    1358      if message:
    1359        sys.exit(message)
    1360      else:
    1361        raise
    1362    else:
    1363      raise UnknownClientSecretsFlowError(
    1364          'This OAuth 2.0 flow is unsupported: %r' % client_type)
    1365