1010__author__ = 'jcgregorio@google.com (Joe Gregorio)'
1111
1212import copy
13- import urllib
13+ import httplib2
1414import oauth2 as oauth
15+ import urllib
16+ import logging
1517
1618try :
1719 from urlparse import parse_qs , parse_qsl
1820except ImportError :
1921 from cgi import parse_qs , parse_qsl
22+
23+
2024class MissingParameter (Exception ):
2125 pass
2226
23- def abstract ():
24- raise NotImplementedError ("You need to override this function" )
25-
2627
27- class TokenStore (object ):
28- def get (user , service ):
29- """Returns an oauth.Token based on the (user, service) returning
30- None if there is no Token for that (user, service).
31- """
32- abstract ()
28+ def _abstract ():
29+ raise NotImplementedError ("You need to override this function" )
3330
34- def set (user , service , token ):
35- abstract ()
3631
3732buzz_discovery = {
3833 'required' : ['domain' , 'scope' ],
@@ -50,7 +45,19 @@ def set(user, service, token):
5045 },
5146 }
5247
48+
5349def _oauth_uri (name , discovery , params ):
50+ """Look up the OAuth UR from the discovery
51+ document and add query parameters based on
52+ params.
53+
54+ name - The name of the OAuth URI to lookup, one
55+ of 'request', 'access', or 'authorize'.
56+ discovery - Portion of discovery document the describes
57+ the OAuth endpoints.
58+ params - Dictionary that is used to form the query parameters
59+ for the specified URI.
60+ """
5461 if name not in ['request' , 'access' , 'authorize' ]:
5562 raise KeyError (name )
5663 keys = []
@@ -62,9 +69,100 @@ def _oauth_uri(name, discovery, params):
6269 query [key ] = params [key ]
6370 return discovery [name ]['url' ] + '?' + urllib .urlencode (query )
6471
65- class Flow3LO (object ):
72+
73+ class Credentials (object ):
74+ """Base class for all Credentials objects.
75+
76+ Subclasses must define an authorize() method
77+ that applies the credentials to an HTTP transport.
78+ """
79+
80+ def authorize (self , http ):
81+ """Take an httplib2.Http instance (or equivalent) and
82+ authorizes it for the set of credentials, usually by
83+ replacing http.request() with a method that adds in
84+ the appropriate headers and then delegates to the original
85+ Http.request() method.
86+ """
87+ _abstract ()
88+
89+
90+ class OAuthCredentials (Credentials ):
91+ """Credentials object for OAuth 1.0a
92+ """
93+
94+ def __init__ (self , consumer , token , user_agent ):
95+ """
96+ consumer - An instance of oauth.Consumer.
97+ token - An instance of oauth.Token constructed with
98+ the access token and secret.
99+ user_agent - The HTTP User-Agent to provide for this application.
100+ """
101+ self .consumer = consumer
102+ self .token = token
103+ self .user_agent = user_agent
104+
105+ def authorize (self , http ):
106+ """
107+ Args:
108+ http - An instance of httplib2.Http
109+ or something that acts like it.
110+
111+ Returns:
112+ A modified instance of http that was passed in.
113+
114+ Example:
115+
116+ h = httplib2.Http()
117+ h = credentials.authorize(h)
118+
119+ You can't create a new OAuth
120+ subclass of httplib2.Authenication because
121+ it never gets passed the absolute URI, which is
122+ needed for signing. So instead we have to overload
123+ 'request' with a closure that adds in the
124+ Authorization header and then calls the original version
125+ of 'request()'.
126+ """
127+ request_orig = http .request
128+ signer = oauth .SignatureMethod_HMAC_SHA1 ()
129+
130+ # The closure that will replace 'httplib2.Http.request'.
131+ def new_request (uri , method = "GET" , body = None , headers = None ,
132+ redirections = httplib2 .DEFAULT_MAX_REDIRECTS ,
133+ connection_type = None ):
134+ """Modify the request headers to add the appropriate
135+ Authorization header."""
136+ req = oauth .Request .from_consumer_and_token (
137+ self .consumer , self .token , http_method = method , http_url = uri )
138+ req .sign_request (signer , self .consumer , self .token )
139+ if headers == None :
140+ headers = {}
141+ headers .update (req .to_header ())
142+ if 'user-agent' not in headers :
143+ headers ['user-agent' ] = self .user_agent
144+ return request_orig (uri , method , body , headers ,
145+ redirections , connection_type )
146+
147+ http .request = new_request
148+ return http
149+
150+
151+ class FlowThreeLegged (object ):
152+ """Does the Three Legged Dance for OAuth 1.0a.
153+ """
154+
66155 def __init__ (self , discovery , consumer_key , consumer_secret , user_agent ,
67156 ** kwargs ):
157+ """
158+ discovery - Section of the API discovery document that describes
159+ the OAuth endpoints.
160+ consumer_key - OAuth consumer key
161+ consumer_secret - OAuth consumer secret
162+ user_agent - The HTTP User-Agent that identifies the application.
163+ **kwargs - The keyword arguments are all optional and required
164+ parameters for the OAuth calls.
165+ """
68166 self .discovery = discovery
69167 self .consumer_key = consumer_key
70168 self .consumer_secret = consumer_secret
@@ -75,14 +173,17 @@ def __init__(self, discovery, consumer_key, consumer_secret, user_agent,
75173 if key not in self .params :
76174 raise MissingParameter ('Required parameter %s not supplied' % key )
77175
78- def step1 (self , oauth_callback = 'oob' ):
176+ def step1_get_authorize_url (self , oauth_callback = 'oob' ):
79177 """Returns a URI to redirect to the provider.
80178
81- If oauth_callback is 'oob' then the next call
82- should be to step2_pin, otherwise oauth_callback
83- is a URI and the next call should be to
84- step2_callback() with the query parameters
85- received at that callback.
179+ oauth_callback - Either the string 'oob' for a non-web-based application,
180+ or a URI that handles the callback from the authorization
181+ server.
182+
183+ If oauth_callback is 'oob' then pass in the
184+ generated verification code to step2_exchange,
185+ otherwise pass in the query parameters received
186+ at the callback uri to step2_exchange.
86187 """
87188 consumer = oauth .Consumer (self .consumer_key , self .consumer_secret )
88189 client = oauth .Client (consumer )
@@ -93,26 +194,36 @@ def step1(self, oauth_callback='oob'):
93194 }
94195 body = urllib .urlencode ({'oauth_callback' : oauth_callback })
95196 uri = _oauth_uri ('request' , self .discovery , self .params )
197+
96198 resp , content = client .request (uri , 'POST' , headers = headers ,
97199 body = body )
98200 if resp ['status' ] != '200' :
99- print content
201+ logging . error ( 'Failed to retrieve temporary authorization: %s' % content )
100202 raise Exception ('Invalid response %s.' % resp ['status' ])
101203
102204 self .request_token = dict (parse_qsl (content ))
103205
104206 auth_params = copy .copy (self .params )
105207 auth_params ['oauth_token' ] = self .request_token ['oauth_token' ]
106208
107- uri = _oauth_uri ('authorize' , self .discovery , auth_params )
108- return uri
209+ return _oauth_uri ('authorize' , self .discovery , auth_params )
109210
110- def step2_pin (self , pin ):
111- """Returns an oauth_token and oauth_token_secret in a dictionary"""
211+ def step2_exchange (self , verifier ):
212+ """Exhanges an authorized request token
213+ for OAuthCredentials.
214+
215+ verifier - either the verifier token, or a dictionary
216+ of the query parameters to the callback, which contains
217+ the oauth_verifier.
218+ """
112219
113- token = oauth .Token (self .request_token ['oauth_token' ],
220+ if not (isinstance (verifier , str ) or isinstance (verifier , unicode )):
221+ verifier = verifier ['oauth_verifier' ]
222+
223+ token = oauth .Token (
224+ self .request_token ['oauth_token' ],
114225 self .request_token ['oauth_token_secret' ])
115- token .set_verifier (pin )
226+ token .set_verifier (verifier )
116227 consumer = oauth .Consumer (self .consumer_key , self .consumer_secret )
117228 client = oauth .Client (consumer , token )
118229
@@ -123,8 +234,13 @@ def step2_pin(self, pin):
123234
124235 uri = _oauth_uri ('access' , self .discovery , self .params )
125236 resp , content = client .request (uri , 'POST' , headers = headers )
126- return dict (parse_qsl (content ))
237+ if resp ['status' ] != '200' :
238+ logging .error ('Failed to retrieve access token: %s' % content )
239+ raise Exception ('Invalid response %s.' % resp ['status' ])
240+
241+ oauth_params = dict (parse_qsl (content ))
242+ token = oauth .Token (
243+ oauth_params ['oauth_token' ],
244+ oauth_params ['oauth_token_secret' ])
127245
128- def step2_callback (self , query_params ):
129- """Returns an access token via oauth.Token"""
130- pass
246+ return OAuthCredentials (consumer , token , self .user_agent )
0 commit comments