245 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			245 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
#!/usr/bin/python2.4
 | 
						|
#
 | 
						|
# Copyright 2010 Google Inc. All Rights Reserved.
 | 
						|
 | 
						|
"""Utilities for OAuth.
 | 
						|
 | 
						|
Utilities for making it easier to work with OAuth.
 | 
						|
"""
 | 
						|
 | 
						|
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
 | 
						|
 | 
						|
import copy
 | 
						|
import httplib2
 | 
						|
import oauth2 as oauth
 | 
						|
import urllib
 | 
						|
import logging
 | 
						|
 | 
						|
try:
 | 
						|
    from urlparse import parse_qs, parse_qsl
 | 
						|
except ImportError:
 | 
						|
    from cgi import parse_qs, parse_qsl
 | 
						|
 | 
						|
 | 
						|
class Error(Exception):
 | 
						|
  """Base error for this module."""
 | 
						|
  pass
 | 
						|
 | 
						|
 | 
						|
class RequestError(Error):
 | 
						|
  """Error occurred during request."""
 | 
						|
  pass
 | 
						|
 | 
						|
 | 
						|
class MissingParameter(Error):
 | 
						|
  pass
 | 
						|
 | 
						|
 | 
						|
def _abstract():
 | 
						|
  raise NotImplementedError('You need to override this function')
 | 
						|
 | 
						|
 | 
						|
def _oauth_uri(name, discovery, params):
 | 
						|
  """Look up the OAuth UR from the discovery
 | 
						|
  document and add query parameters based on
 | 
						|
  params.
 | 
						|
 | 
						|
  name      - The name of the OAuth URI to lookup, one
 | 
						|
              of 'request', 'access', or 'authorize'.
 | 
						|
  discovery - Portion of discovery document the describes
 | 
						|
              the OAuth endpoints.
 | 
						|
  params    - Dictionary that is used to form the query parameters
 | 
						|
              for the specified URI.
 | 
						|
  """
 | 
						|
  if name not in ['request', 'access', 'authorize']:
 | 
						|
    raise KeyError(name)
 | 
						|
  keys = discovery[name]['parameters'].keys()
 | 
						|
  query = {}
 | 
						|
  for key in keys:
 | 
						|
    if key in params:
 | 
						|
      query[key] = params[key]
 | 
						|
  return discovery[name]['url'] + '?' + urllib.urlencode(query)
 | 
						|
 | 
						|
 | 
						|
class Credentials(object):
 | 
						|
  """Base class for all Credentials objects.
 | 
						|
 | 
						|
  Subclasses must define an authorize() method
 | 
						|
  that applies the credentials to an HTTP transport.
 | 
						|
  """
 | 
						|
 | 
						|
  def authorize(self, http):
 | 
						|
    """Take an httplib2.Http instance (or equivalent) and
 | 
						|
    authorizes it for the set of credentials, usually by
 | 
						|
    replacing http.request() with a method that adds in
 | 
						|
    the appropriate headers and then delegates to the original
 | 
						|
    Http.request() method.
 | 
						|
    """
 | 
						|
    _abstract()
 | 
						|
 | 
						|
 | 
						|
class OAuthCredentials(Credentials):
 | 
						|
  """Credentials object for OAuth 1.0a
 | 
						|
  """
 | 
						|
 | 
						|
  def __init__(self, consumer, token, user_agent):
 | 
						|
    """
 | 
						|
    consumer   - An instance of oauth.Consumer.
 | 
						|
    token      - An instance of oauth.Token constructed with
 | 
						|
                 the access token and secret.
 | 
						|
    user_agent - The HTTP User-Agent to provide for this application.
 | 
						|
    """
 | 
						|
    self.consumer = consumer
 | 
						|
    self.token = token
 | 
						|
    self.user_agent = user_agent
 | 
						|
 | 
						|
  def authorize(self, http):
 | 
						|
    """
 | 
						|
    Args:
 | 
						|
       http - An instance of httplib2.Http
 | 
						|
           or something that acts like it.
 | 
						|
 | 
						|
    Returns:
 | 
						|
       A modified instance of http that was passed in.
 | 
						|
 | 
						|
    Example:
 | 
						|
 | 
						|
      h = httplib2.Http()
 | 
						|
      h = credentials.authorize(h)
 | 
						|
 | 
						|
    You can't create a new OAuth
 | 
						|
    subclass of httplib2.Authenication because
 | 
						|
    it never gets passed the absolute URI, which is
 | 
						|
    needed for signing. So instead we have to overload
 | 
						|
    'request' with a closure that adds in the
 | 
						|
    Authorization header and then calls the original version
 | 
						|
    of 'request()'.
 | 
						|
    """
 | 
						|
    request_orig = http.request
 | 
						|
    signer = oauth.SignatureMethod_HMAC_SHA1()
 | 
						|
 | 
						|
    # The closure that will replace 'httplib2.Http.request'.
 | 
						|
    def new_request(uri, method='GET', body=None, headers=None,
 | 
						|
                    redirections=httplib2.DEFAULT_MAX_REDIRECTS,
 | 
						|
                    connection_type=None):
 | 
						|
      """Modify the request headers to add the appropriate
 | 
						|
      Authorization header."""
 | 
						|
      req = oauth.Request.from_consumer_and_token(
 | 
						|
          self.consumer, self.token, http_method=method, http_url=uri)
 | 
						|
      req.sign_request(signer, self.consumer, self.token)
 | 
						|
      if headers == None:
 | 
						|
        headers = {}
 | 
						|
      headers.update(req.to_header())
 | 
						|
      if 'user-agent' in headers:
 | 
						|
        headers['user-agent'] =  self.user_agent + ' ' + headers['user-agent']
 | 
						|
      else:
 | 
						|
        headers['user-agent'] =  self.user_agent
 | 
						|
      return request_orig(uri, method, body, headers,
 | 
						|
                          redirections, connection_type)
 | 
						|
 | 
						|
    http.request = new_request
 | 
						|
    return http
 | 
						|
 | 
						|
 | 
						|
class FlowThreeLegged(object):
 | 
						|
  """Does the Three Legged Dance for OAuth 1.0a.
 | 
						|
  """
 | 
						|
 | 
						|
  def __init__(self, discovery, consumer_key, consumer_secret, user_agent,
 | 
						|
               **kwargs):
 | 
						|
    """
 | 
						|
    discovery       - Section of the API discovery document that describes
 | 
						|
                      the OAuth endpoints.
 | 
						|
    consumer_key    - OAuth consumer key
 | 
						|
    consumer_secret - OAuth consumer secret
 | 
						|
    user_agent      - The HTTP User-Agent that identifies the application.
 | 
						|
    **kwargs        - The keyword arguments are all optional and required
 | 
						|
                      parameters for the OAuth calls.
 | 
						|
    """
 | 
						|
    self.discovery = discovery
 | 
						|
    self.consumer_key = consumer_key
 | 
						|
    self.consumer_secret = consumer_secret
 | 
						|
    self.user_agent = user_agent
 | 
						|
    self.params = kwargs
 | 
						|
    self.request_token = {}
 | 
						|
    required = {}
 | 
						|
    for uriinfo in discovery.itervalues():
 | 
						|
      for name, value in uriinfo['parameters'].iteritems():
 | 
						|
        if value['required'] and not name.startswith('oauth_'):
 | 
						|
          required[name] = 1
 | 
						|
    for key in required.iterkeys():
 | 
						|
      if key not in self.params:
 | 
						|
        raise MissingParameter('Required parameter %s not supplied' % key)
 | 
						|
 | 
						|
  def step1_get_authorize_url(self, oauth_callback='oob'):
 | 
						|
    """Returns a URI to redirect to the provider.
 | 
						|
 | 
						|
    oauth_callback - Either the string 'oob' for a non-web-based application,
 | 
						|
                     or a URI that handles the callback from the authorization
 | 
						|
                     server.
 | 
						|
 | 
						|
    If oauth_callback is 'oob' then pass in the
 | 
						|
    generated verification code to step2_exchange,
 | 
						|
    otherwise pass in the query parameters received
 | 
						|
    at the callback uri to step2_exchange.
 | 
						|
    """
 | 
						|
    consumer = oauth.Consumer(self.consumer_key, self.consumer_secret)
 | 
						|
    client = oauth.Client(consumer)
 | 
						|
 | 
						|
    headers = {
 | 
						|
        'user-agent': self.user_agent,
 | 
						|
        'content-type': 'application/x-www-form-urlencoded'
 | 
						|
    }
 | 
						|
    body = urllib.urlencode({'oauth_callback': oauth_callback})
 | 
						|
    uri = _oauth_uri('request', self.discovery, self.params)
 | 
						|
 | 
						|
    resp, content = client.request(uri, 'POST', headers=headers,
 | 
						|
                                   body=body)
 | 
						|
    if resp['status'] != '200':
 | 
						|
      logging.error('Failed to retrieve temporary authorization: %s' % content)
 | 
						|
      raise RequestError('Invalid response %s.' % resp['status'])
 | 
						|
 | 
						|
    self.request_token = dict(parse_qsl(content))
 | 
						|
 | 
						|
    auth_params = copy.copy(self.params)
 | 
						|
    auth_params['oauth_token'] = self.request_token['oauth_token']
 | 
						|
 | 
						|
    return _oauth_uri('authorize', self.discovery, auth_params)
 | 
						|
 | 
						|
  def step2_exchange(self, verifier):
 | 
						|
    """Exhanges an authorized request token
 | 
						|
    for OAuthCredentials.
 | 
						|
 | 
						|
    verifier - either the verifier token, or a dictionary
 | 
						|
        of the query parameters to the callback, which contains
 | 
						|
        the oauth_verifier.
 | 
						|
    """
 | 
						|
 | 
						|
    if not (isinstance(verifier, str) or isinstance(verifier, unicode)):
 | 
						|
      verifier = verifier['oauth_verifier']
 | 
						|
 | 
						|
    token = oauth.Token(
 | 
						|
        self.request_token['oauth_token'],
 | 
						|
        self.request_token['oauth_token_secret'])
 | 
						|
    token.set_verifier(verifier)
 | 
						|
    consumer = oauth.Consumer(self.consumer_key, self.consumer_secret)
 | 
						|
    client = oauth.Client(consumer, token)
 | 
						|
 | 
						|
    headers = {
 | 
						|
        'user-agent': self.user_agent,
 | 
						|
        'content-type': 'application/x-www-form-urlencoded'
 | 
						|
    }
 | 
						|
 | 
						|
    uri = _oauth_uri('access', self.discovery, self.params)
 | 
						|
    resp, content = client.request(uri, 'POST', headers=headers)
 | 
						|
    if resp['status'] != '200':
 | 
						|
      logging.error('Failed to retrieve access token: %s' % content)
 | 
						|
      raise RequestError('Invalid response %s.' % resp['status'])
 | 
						|
 | 
						|
    oauth_params = dict(parse_qsl(content))
 | 
						|
    token = oauth.Token(
 | 
						|
        oauth_params['oauth_token'],
 | 
						|
        oauth_params['oauth_token_secret'])
 | 
						|
 | 
						|
    return OAuthCredentials(consumer, token, self.user_agent)
 |