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 URI 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)
 | 
