yading@7
|
1 """
|
yading@7
|
2 The MIT License
|
yading@7
|
3
|
yading@7
|
4 Copyright (c) 2007 Leah Culver
|
yading@7
|
5
|
yading@7
|
6 Permission is hereby granted, free of charge, to any person obtaining a copy
|
yading@7
|
7 of this software and associated documentation files (the "Software"), to deal
|
yading@7
|
8 in the Software without restriction, including without limitation the rights
|
yading@7
|
9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
yading@7
|
10 copies of the Software, and to permit persons to whom the Software is
|
yading@7
|
11 furnished to do so, subject to the following conditions:
|
yading@7
|
12
|
yading@7
|
13 The above copyright notice and this permission notice shall be included in
|
yading@7
|
14 all copies or substantial portions of the Software.
|
yading@7
|
15
|
yading@7
|
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
yading@7
|
17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
yading@7
|
18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
yading@7
|
19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
yading@7
|
20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
yading@7
|
21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
yading@7
|
22 THE SOFTWARE.
|
yading@7
|
23 """
|
yading@7
|
24
|
yading@7
|
25 import cgi
|
yading@7
|
26 import urllib
|
yading@7
|
27 import time
|
yading@7
|
28 import random
|
yading@7
|
29 import urlparse
|
yading@7
|
30 import hmac
|
yading@7
|
31 import binascii
|
yading@7
|
32 import re
|
yading@7
|
33
|
yading@7
|
34
|
yading@7
|
35 VERSION = '1.0' # Hi Blaine!
|
yading@7
|
36 HTTP_METHOD = 'GET'
|
yading@7
|
37 SIGNATURE_METHOD = 'PLAINTEXT'
|
yading@7
|
38
|
yading@7
|
39
|
yading@7
|
40 class OAuthError(RuntimeError):
|
yading@7
|
41 """Generic exception class."""
|
yading@7
|
42 def __init__(self, message='OAuth error occured.'):
|
yading@7
|
43 self.message = message
|
yading@7
|
44
|
yading@7
|
45 def build_authenticate_header(realm=''):
|
yading@7
|
46 """Optional WWW-Authenticate header (401 error)"""
|
yading@7
|
47 return {'WWW-Authenticate': 'OAuth realm="%s"' % realm}
|
yading@7
|
48
|
yading@7
|
49 def escape(s):
|
yading@7
|
50 """Escape a URL including any /."""
|
yading@7
|
51 return urllib.quote(s, safe='~')
|
yading@7
|
52
|
yading@7
|
53 def _utf8_str(s):
|
yading@7
|
54 """Convert unicode to utf-8."""
|
yading@7
|
55 if isinstance(s, unicode):
|
yading@7
|
56 return s.encode("utf-8")
|
yading@7
|
57 else:
|
yading@7
|
58 return str(s)
|
yading@7
|
59
|
yading@7
|
60 def generate_timestamp():
|
yading@7
|
61 """Get seconds since epoch (UTC)."""
|
yading@7
|
62 return int(time.time())
|
yading@7
|
63
|
yading@7
|
64 def generate_nonce(length=8):
|
yading@7
|
65 """Generate pseudorandom number."""
|
yading@7
|
66 return ''.join([str(random.randint(0, 9)) for i in range(length)])
|
yading@7
|
67
|
yading@7
|
68 def generate_verifier(length=8):
|
yading@7
|
69 """Generate pseudorandom number."""
|
yading@7
|
70 return ''.join([str(random.randint(0, 9)) for i in range(length)])
|
yading@7
|
71
|
yading@7
|
72
|
yading@7
|
73 class OAuthConsumer(object):
|
yading@7
|
74 """Consumer of OAuth authentication.
|
yading@7
|
75
|
yading@7
|
76 OAuthConsumer is a data type that represents the identity of the Consumer
|
yading@7
|
77 via its shared secret with the Service Provider.
|
yading@7
|
78
|
yading@7
|
79 """
|
yading@7
|
80 key = None
|
yading@7
|
81 secret = None
|
yading@7
|
82
|
yading@7
|
83 def __init__(self, key, secret):
|
yading@7
|
84 self.key = key
|
yading@7
|
85 self.secret = secret
|
yading@7
|
86
|
yading@7
|
87
|
yading@7
|
88 class OAuthToken(object):
|
yading@7
|
89 """OAuthToken is a data type that represents an End User via either an access
|
yading@7
|
90 or request token.
|
yading@7
|
91
|
yading@7
|
92 key -- the token
|
yading@7
|
93 secret -- the token secret
|
yading@7
|
94
|
yading@7
|
95 """
|
yading@7
|
96 key = None
|
yading@7
|
97 secret = None
|
yading@7
|
98 callback = None
|
yading@7
|
99 callback_confirmed = None
|
yading@7
|
100 verifier = None
|
yading@7
|
101
|
yading@7
|
102 def __init__(self, key, secret):
|
yading@7
|
103 self.key = key
|
yading@7
|
104 self.secret = secret
|
yading@7
|
105
|
yading@7
|
106 def set_callback(self, callback):
|
yading@7
|
107 self.callback = callback
|
yading@7
|
108 self.callback_confirmed = 'true'
|
yading@7
|
109
|
yading@7
|
110 def set_verifier(self, verifier=None):
|
yading@7
|
111 if verifier is not None:
|
yading@7
|
112 self.verifier = verifier
|
yading@7
|
113 else:
|
yading@7
|
114 self.verifier = generate_verifier()
|
yading@7
|
115
|
yading@7
|
116 def get_callback_url(self):
|
yading@7
|
117 if self.callback and self.verifier:
|
yading@7
|
118 # Append the oauth_verifier.
|
yading@7
|
119 parts = urlparse.urlparse(self.callback)
|
yading@7
|
120 scheme, netloc, path, params, query, fragment = parts[:6]
|
yading@7
|
121 if query:
|
yading@7
|
122 query = '%s&oauth_verifier=%s' % (query, self.verifier)
|
yading@7
|
123 else:
|
yading@7
|
124 query = 'oauth_verifier=%s' % self.verifier
|
yading@7
|
125 return urlparse.urlunparse((scheme, netloc, path, params,
|
yading@7
|
126 query, fragment))
|
yading@7
|
127 return self.callback
|
yading@7
|
128
|
yading@7
|
129 def to_string(self):
|
yading@7
|
130 data = {
|
yading@7
|
131 'oauth_token': self.key,
|
yading@7
|
132 'oauth_token_secret': self.secret,
|
yading@7
|
133 }
|
yading@7
|
134 if self.callback_confirmed is not None:
|
yading@7
|
135 data['oauth_callback_confirmed'] = self.callback_confirmed
|
yading@7
|
136 return urllib.urlencode(data)
|
yading@7
|
137
|
yading@7
|
138 def from_string(s):
|
yading@7
|
139 """ Returns a token from something like:
|
yading@7
|
140 oauth_token_secret=xxx&oauth_token=xxx
|
yading@7
|
141 """
|
yading@7
|
142 print "******* %s" % s.__class__
|
yading@7
|
143 #params = urlparse.parse_qs(s, keep_blank_values=False)
|
yading@7
|
144
|
yading@7
|
145 key = re.search("<oauth_token>(\w.+)</oauth_token>", s).groups()[0]
|
yading@7
|
146 print "@@@@@@ key: %s" %key
|
yading@7
|
147 secret = re.search("<oauth_token_secret>(\w.+)</oauth_token_secret>", s).groups()[0]
|
yading@7
|
148 print "@@@@@@ secret: %s" % secret
|
yading@7
|
149 token = OAuthToken(key, secret)
|
yading@7
|
150
|
yading@7
|
151 return token
|
yading@7
|
152 from_string = staticmethod(from_string)
|
yading@7
|
153
|
yading@7
|
154 def __str__(self):
|
yading@7
|
155 return self.to_string()
|
yading@7
|
156
|
yading@7
|
157
|
yading@7
|
158 class OAuthRequest(object):
|
yading@7
|
159 """OAuthRequest represents the request and can be serialized.
|
yading@7
|
160
|
yading@7
|
161 OAuth parameters:
|
yading@7
|
162 - oauth_consumer_key
|
yading@7
|
163 - oauth_token
|
yading@7
|
164 - oauth_signature_method
|
yading@7
|
165 - oauth_signature
|
yading@7
|
166 - oauth_timestamp
|
yading@7
|
167 - oauth_nonce
|
yading@7
|
168 - oauth_version
|
yading@7
|
169 - oauth_verifier
|
yading@7
|
170 ... any additional parameters, as defined by the Service Provider.
|
yading@7
|
171 """
|
yading@7
|
172 parameters = None # OAuth parameters.
|
yading@7
|
173 http_method = HTTP_METHOD
|
yading@7
|
174 http_url = None
|
yading@7
|
175 version = VERSION
|
yading@7
|
176
|
yading@7
|
177 def __init__(self, http_method=HTTP_METHOD, http_url=None, parameters=None):
|
yading@7
|
178 self.http_method = http_method
|
yading@7
|
179 self.http_url = http_url
|
yading@7
|
180 self.parameters = parameters or {}
|
yading@7
|
181
|
yading@7
|
182 def set_parameter(self, parameter, value):
|
yading@7
|
183 self.parameters[parameter] = value
|
yading@7
|
184
|
yading@7
|
185 def get_parameter(self, parameter):
|
yading@7
|
186 try:
|
yading@7
|
187 return self.parameters[parameter]
|
yading@7
|
188 except:
|
yading@7
|
189 raise OAuthError('Parameter not found: %s' % parameter)
|
yading@7
|
190
|
yading@7
|
191 def _get_timestamp_nonce(self):
|
yading@7
|
192 return self.get_parameter('oauth_timestamp'), self.get_parameter(
|
yading@7
|
193 'oauth_nonce')
|
yading@7
|
194
|
yading@7
|
195 def get_nonoauth_parameters(self):
|
yading@7
|
196 """Get any non-OAuth parameters."""
|
yading@7
|
197 parameters = {}
|
yading@7
|
198 for k, v in self.parameters.iteritems():
|
yading@7
|
199 # Ignore oauth parameters.
|
yading@7
|
200 if k.find('oauth_') < 0:
|
yading@7
|
201 parameters[k] = v
|
yading@7
|
202 return parameters
|
yading@7
|
203
|
yading@7
|
204 def to_header(self, realm=''):
|
yading@7
|
205 """Serialize as a header for an HTTPAuth request."""
|
yading@7
|
206 auth_header = 'OAuth realm="%s"' % realm
|
yading@7
|
207 # Add the oauth parameters.
|
yading@7
|
208 if self.parameters:
|
yading@7
|
209 for k, v in self.parameters.iteritems():
|
yading@7
|
210 if k[:6] == 'oauth_':
|
yading@7
|
211 auth_header += ', %s="%s"' % (k, escape(str(v)))
|
yading@7
|
212 return {'Authorization': auth_header}
|
yading@7
|
213
|
yading@7
|
214 def to_postdata(self):
|
yading@7
|
215 """Serialize as post data for a POST request."""
|
yading@7
|
216 return '&'.join(['%s=%s' % (escape(str(k)), escape(str(v))) \
|
yading@7
|
217 for k, v in self.parameters.iteritems()])
|
yading@7
|
218
|
yading@7
|
219 def to_url(self):
|
yading@7
|
220 """Serialize as a URL for a GET request."""
|
yading@7
|
221 return '%s?%s' % (self.get_normalized_http_url(), self.to_postdata())
|
yading@7
|
222
|
yading@7
|
223 def get_normalized_parameters(self):
|
yading@7
|
224 """Return a string that contains the parameters that must be signed."""
|
yading@7
|
225 params = self.parameters
|
yading@7
|
226 try:
|
yading@7
|
227 # Exclude the signature if it exists.
|
yading@7
|
228 del params['oauth_signature']
|
yading@7
|
229 except:
|
yading@7
|
230 pass
|
yading@7
|
231 # Escape key values before sorting.
|
yading@7
|
232 key_values = [(escape(_utf8_str(k)), escape(_utf8_str(v))) \
|
yading@7
|
233 for k,v in params.items()]
|
yading@7
|
234 # Sort lexicographically, first after key, then after value.
|
yading@7
|
235 key_values.sort()
|
yading@7
|
236 # Combine key value pairs into a string.
|
yading@7
|
237 return '&'.join(['%s=%s' % (k, v) for k, v in key_values])
|
yading@7
|
238
|
yading@7
|
239 def get_normalized_http_method(self):
|
yading@7
|
240 """Uppercases the http method."""
|
yading@7
|
241 return self.http_method.upper()
|
yading@7
|
242
|
yading@7
|
243 def get_normalized_http_url(self):
|
yading@7
|
244 """Parses the URL and rebuilds it to be scheme://host/path."""
|
yading@7
|
245 parts = urlparse.urlparse(self.http_url)
|
yading@7
|
246 scheme, netloc, path = parts[:3]
|
yading@7
|
247 # Exclude default port numbers.
|
yading@7
|
248 if scheme == 'http' and netloc[-3:] == ':80':
|
yading@7
|
249 netloc = netloc[:-3]
|
yading@7
|
250 elif scheme == 'https' and netloc[-4:] == ':443':
|
yading@7
|
251 netloc = netloc[:-4]
|
yading@7
|
252 return '%s://%s%s' % (scheme, netloc, path)
|
yading@7
|
253
|
yading@7
|
254 def sign_request(self, signature_method, consumer, token):
|
yading@7
|
255 """Set the signature parameter to the result of build_signature."""
|
yading@7
|
256 # Set the signature method.
|
yading@7
|
257 self.set_parameter('oauth_signature_method',
|
yading@7
|
258 signature_method.get_name())
|
yading@7
|
259 # Set the signature.
|
yading@7
|
260 self.set_parameter('oauth_signature',
|
yading@7
|
261 self.build_signature(signature_method, consumer, token))
|
yading@7
|
262
|
yading@7
|
263 def build_signature(self, signature_method, consumer, token):
|
yading@7
|
264 """Calls the build signature method within the signature method."""
|
yading@7
|
265 return signature_method.build_signature(self, consumer, token)
|
yading@7
|
266
|
yading@7
|
267 def from_request(http_method, http_url, headers=None, parameters=None,
|
yading@7
|
268 query_string=None):
|
yading@7
|
269 """Combines multiple parameter sources."""
|
yading@7
|
270 if parameters is None:
|
yading@7
|
271 parameters = {}
|
yading@7
|
272
|
yading@7
|
273 # Headers
|
yading@7
|
274 if headers and 'Authorization' in headers:
|
yading@7
|
275 auth_header = headers['Authorization']
|
yading@7
|
276 # Check that the authorization header is OAuth.
|
yading@7
|
277 if auth_header[:6] == 'OAuth ':
|
yading@7
|
278 auth_header = auth_header[6:]
|
yading@7
|
279 try:
|
yading@7
|
280 # Get the parameters from the header.
|
yading@7
|
281 header_params = OAuthRequest._split_header(auth_header)
|
yading@7
|
282 parameters.update(header_params)
|
yading@7
|
283 except:
|
yading@7
|
284 raise OAuthError('Unable to parse OAuth parameters from '
|
yading@7
|
285 'Authorization header.')
|
yading@7
|
286
|
yading@7
|
287 # GET or POST query string.
|
yading@7
|
288 if query_string:
|
yading@7
|
289 query_params = OAuthRequest._split_url_string(query_string)
|
yading@7
|
290 parameters.update(query_params)
|
yading@7
|
291
|
yading@7
|
292 # URL parameters.
|
yading@7
|
293 param_str = urlparse.urlparse(http_url)[4] # query
|
yading@7
|
294 url_params = OAuthRequest._split_url_string(param_str)
|
yading@7
|
295 parameters.update(url_params)
|
yading@7
|
296
|
yading@7
|
297 if parameters:
|
yading@7
|
298 return OAuthRequest(http_method, http_url, parameters)
|
yading@7
|
299
|
yading@7
|
300 return None
|
yading@7
|
301 from_request = staticmethod(from_request)
|
yading@7
|
302
|
yading@7
|
303 def from_consumer_and_token(oauth_consumer, token=None,
|
yading@7
|
304 callback=None, verifier=None, http_method=HTTP_METHOD,
|
yading@7
|
305 http_url=None, parameters=None):
|
yading@7
|
306 if not parameters:
|
yading@7
|
307 parameters = {}
|
yading@7
|
308
|
yading@7
|
309 defaults = {
|
yading@7
|
310 'oauth_consumer_key': oauth_consumer.key,
|
yading@7
|
311 'oauth_timestamp': generate_timestamp(),
|
yading@7
|
312 'oauth_nonce': generate_nonce(),
|
yading@7
|
313 'oauth_version': OAuthRequest.version,
|
yading@7
|
314 }
|
yading@7
|
315
|
yading@7
|
316 defaults.update(parameters)
|
yading@7
|
317 parameters = defaults
|
yading@7
|
318
|
yading@7
|
319 if token:
|
yading@7
|
320 parameters['oauth_token'] = token.key
|
yading@7
|
321 if token.callback:
|
yading@7
|
322 parameters['oauth_callback'] = token.callback
|
yading@7
|
323 # 1.0a support for verifier.
|
yading@7
|
324 if verifier:
|
yading@7
|
325 parameters['oauth_verifier'] = verifier
|
yading@7
|
326 elif callback:
|
yading@7
|
327 # 1.0a support for callback in the request token request.
|
yading@7
|
328 parameters['oauth_callback'] = callback
|
yading@7
|
329
|
yading@7
|
330 return OAuthRequest(http_method, http_url, parameters)
|
yading@7
|
331 from_consumer_and_token = staticmethod(from_consumer_and_token)
|
yading@7
|
332
|
yading@7
|
333 def from_token_and_callback(token, callback=None, http_method=HTTP_METHOD,
|
yading@7
|
334 http_url=None, parameters=None):
|
yading@7
|
335 if not parameters:
|
yading@7
|
336 parameters = {}
|
yading@7
|
337
|
yading@7
|
338 parameters['oauth_token'] = token.key
|
yading@7
|
339
|
yading@7
|
340 if callback:
|
yading@7
|
341 parameters['oauth_callback'] = callback
|
yading@7
|
342
|
yading@7
|
343 return OAuthRequest(http_method, http_url, parameters)
|
yading@7
|
344 from_token_and_callback = staticmethod(from_token_and_callback)
|
yading@7
|
345
|
yading@7
|
346 def _split_header(header):
|
yading@7
|
347 """Turn Authorization: header into parameters."""
|
yading@7
|
348 params = {}
|
yading@7
|
349 parts = header.split(',')
|
yading@7
|
350 for param in parts:
|
yading@7
|
351 # Ignore realm parameter.
|
yading@7
|
352 if param.find('realm') > -1:
|
yading@7
|
353 continue
|
yading@7
|
354 # Remove whitespace.
|
yading@7
|
355 param = param.strip()
|
yading@7
|
356 # Split key-value.
|
yading@7
|
357 param_parts = param.split('=', 1)
|
yading@7
|
358 # Remove quotes and unescape the value.
|
yading@7
|
359 params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"'))
|
yading@7
|
360 return params
|
yading@7
|
361 _split_header = staticmethod(_split_header)
|
yading@7
|
362
|
yading@7
|
363 def _split_url_string(param_str):
|
yading@7
|
364 """Turn URL string into parameters."""
|
yading@7
|
365 parameters = cgi.parse_qs(param_str, keep_blank_values=False)
|
yading@7
|
366 for k, v in parameters.iteritems():
|
yading@7
|
367 parameters[k] = urllib.unquote(v[0])
|
yading@7
|
368 return parameters
|
yading@7
|
369 _split_url_string = staticmethod(_split_url_string)
|
yading@7
|
370
|
yading@7
|
371 class OAuthServer(object):
|
yading@7
|
372 """A worker to check the validity of a request against a data store."""
|
yading@7
|
373 timestamp_threshold = 300 # In seconds, five minutes.
|
yading@7
|
374 version = VERSION
|
yading@7
|
375 signature_methods = None
|
yading@7
|
376 data_store = None
|
yading@7
|
377
|
yading@7
|
378 def __init__(self, data_store=None, signature_methods=None):
|
yading@7
|
379 self.data_store = data_store
|
yading@7
|
380 self.signature_methods = signature_methods or {}
|
yading@7
|
381
|
yading@7
|
382 def set_data_store(self, data_store):
|
yading@7
|
383 self.data_store = data_store
|
yading@7
|
384
|
yading@7
|
385 def get_data_store(self):
|
yading@7
|
386 return self.data_store
|
yading@7
|
387
|
yading@7
|
388 def add_signature_method(self, signature_method):
|
yading@7
|
389 self.signature_methods[signature_method.get_name()] = signature_method
|
yading@7
|
390 return self.signature_methods
|
yading@7
|
391
|
yading@7
|
392 def fetch_request_token(self, oauth_request):
|
yading@7
|
393 """Processes a request_token request and returns the
|
yading@7
|
394 request token on success.
|
yading@7
|
395 """
|
yading@7
|
396 try:
|
yading@7
|
397 # Get the request token for authorization.
|
yading@7
|
398 token = self._get_token(oauth_request, 'request')
|
yading@7
|
399 except OAuthError:
|
yading@7
|
400 # No token required for the initial token request.
|
yading@7
|
401 version = self._get_version(oauth_request)
|
yading@7
|
402 consumer = self._get_consumer(oauth_request)
|
yading@7
|
403 try:
|
yading@7
|
404 callback = self.get_callback(oauth_request)
|
yading@7
|
405 except OAuthError:
|
yading@7
|
406 callback = None # 1.0, no callback specified.
|
yading@7
|
407 self._check_signature(oauth_request, consumer, None)
|
yading@7
|
408 # Fetch a new token.
|
yading@7
|
409 token = self.data_store.fetch_request_token(consumer, callback)
|
yading@7
|
410 return token
|
yading@7
|
411
|
yading@7
|
412 def fetch_access_token(self, oauth_request):
|
yading@7
|
413 """Processes an access_token request and returns the
|
yading@7
|
414 access token on success.
|
yading@7
|
415 """
|
yading@7
|
416 version = self._get_version(oauth_request)
|
yading@7
|
417 consumer = self._get_consumer(oauth_request)
|
yading@7
|
418 try:
|
yading@7
|
419 verifier = self._get_verifier(oauth_request)
|
yading@7
|
420 except OAuthError:
|
yading@7
|
421 verifier = None
|
yading@7
|
422 # Get the request token.
|
yading@7
|
423 token = self._get_token(oauth_request, 'request')
|
yading@7
|
424 self._check_signature(oauth_request, consumer, token)
|
yading@7
|
425 new_token = self.data_store.fetch_access_token(consumer, token, verifier)
|
yading@7
|
426 return new_token
|
yading@7
|
427
|
yading@7
|
428 def verify_request(self, oauth_request):
|
yading@7
|
429 """Verifies an api call and checks all the parameters."""
|
yading@7
|
430 # -> consumer and token
|
yading@7
|
431 version = self._get_version(oauth_request)
|
yading@7
|
432 consumer = self._get_consumer(oauth_request)
|
yading@7
|
433 # Get the access token.
|
yading@7
|
434 token = self._get_token(oauth_request, 'access')
|
yading@7
|
435 self._check_signature(oauth_request, consumer, token)
|
yading@7
|
436 parameters = oauth_request.get_nonoauth_parameters()
|
yading@7
|
437 return consumer, token, parameters
|
yading@7
|
438
|
yading@7
|
439 def authorize_token(self, token, user):
|
yading@7
|
440 """Authorize a request token."""
|
yading@7
|
441 return self.data_store.authorize_request_token(token, user)
|
yading@7
|
442
|
yading@7
|
443 def get_callback(self, oauth_request):
|
yading@7
|
444 """Get the callback URL."""
|
yading@7
|
445 return oauth_request.get_parameter('oauth_callback')
|
yading@7
|
446
|
yading@7
|
447 def build_authenticate_header(self, realm=''):
|
yading@7
|
448 """Optional support for the authenticate header."""
|
yading@7
|
449 return {'WWW-Authenticate': 'OAuth realm="%s"' % realm}
|
yading@7
|
450
|
yading@7
|
451 def _get_version(self, oauth_request):
|
yading@7
|
452 """Verify the correct version request for this server."""
|
yading@7
|
453 try:
|
yading@7
|
454 version = oauth_request.get_parameter('oauth_version')
|
yading@7
|
455 except:
|
yading@7
|
456 version = VERSION
|
yading@7
|
457 if version and version != self.version:
|
yading@7
|
458 raise OAuthError('OAuth version %s not supported.' % str(version))
|
yading@7
|
459 return version
|
yading@7
|
460
|
yading@7
|
461 def _get_signature_method(self, oauth_request):
|
yading@7
|
462 """Figure out the signature with some defaults."""
|
yading@7
|
463 try:
|
yading@7
|
464 signature_method = oauth_request.get_parameter(
|
yading@7
|
465 'oauth_signature_method')
|
yading@7
|
466 except:
|
yading@7
|
467 signature_method = SIGNATURE_METHOD
|
yading@7
|
468 try:
|
yading@7
|
469 # Get the signature method object.
|
yading@7
|
470 signature_method = self.signature_methods[signature_method]
|
yading@7
|
471 except:
|
yading@7
|
472 signature_method_names = ', '.join(self.signature_methods.keys())
|
yading@7
|
473 raise OAuthError('Signature method %s not supported try one of the '
|
yading@7
|
474 'following: %s' % (signature_method, signature_method_names))
|
yading@7
|
475
|
yading@7
|
476 return signature_method
|
yading@7
|
477
|
yading@7
|
478 def _get_consumer(self, oauth_request):
|
yading@7
|
479 consumer_key = oauth_request.get_parameter('oauth_consumer_key')
|
yading@7
|
480 consumer = self.data_store.lookup_consumer(consumer_key)
|
yading@7
|
481 if not consumer:
|
yading@7
|
482 raise OAuthError('Invalid consumer.')
|
yading@7
|
483 return consumer
|
yading@7
|
484
|
yading@7
|
485 def _get_token(self, oauth_request, token_type='access'):
|
yading@7
|
486 """Try to find the token for the provided request token key."""
|
yading@7
|
487 token_field = oauth_request.get_parameter('oauth_token')
|
yading@7
|
488 token = self.data_store.lookup_token(token_type, token_field)
|
yading@7
|
489 if not token:
|
yading@7
|
490 raise OAuthError('Invalid %s token: %s' % (token_type, token_field))
|
yading@7
|
491 return token
|
yading@7
|
492
|
yading@7
|
493 def _get_verifier(self, oauth_request):
|
yading@7
|
494 return oauth_request.get_parameter('oauth_verifier')
|
yading@7
|
495
|
yading@7
|
496 def _check_signature(self, oauth_request, consumer, token):
|
yading@7
|
497 timestamp, nonce = oauth_request._get_timestamp_nonce()
|
yading@7
|
498 self._check_timestamp(timestamp)
|
yading@7
|
499 self._check_nonce(consumer, token, nonce)
|
yading@7
|
500 signature_method = self._get_signature_method(oauth_request)
|
yading@7
|
501 try:
|
yading@7
|
502 signature = oauth_request.get_parameter('oauth_signature')
|
yading@7
|
503 except:
|
yading@7
|
504 raise OAuthError('Missing signature.')
|
yading@7
|
505 # Validate the signature.
|
yading@7
|
506 valid_sig = signature_method.check_signature(oauth_request, consumer,
|
yading@7
|
507 token, signature)
|
yading@7
|
508 if not valid_sig:
|
yading@7
|
509 key, base = signature_method.build_signature_base_string(
|
yading@7
|
510 oauth_request, consumer, token)
|
yading@7
|
511 raise OAuthError('Invalid signature. Expected signature base '
|
yading@7
|
512 'string: %s' % base)
|
yading@7
|
513 built = signature_method.build_signature(oauth_request, consumer, token)
|
yading@7
|
514
|
yading@7
|
515 def _check_timestamp(self, timestamp):
|
yading@7
|
516 """Verify that timestamp is recentish."""
|
yading@7
|
517 timestamp = int(timestamp)
|
yading@7
|
518 now = int(time.time())
|
yading@7
|
519 lapsed = abs(now - timestamp)
|
yading@7
|
520 if lapsed > self.timestamp_threshold:
|
yading@7
|
521 raise OAuthError('Expired timestamp: given %d and now %s has a '
|
yading@7
|
522 'greater difference than threshold %d' %
|
yading@7
|
523 (timestamp, now, self.timestamp_threshold))
|
yading@7
|
524
|
yading@7
|
525 def _check_nonce(self, consumer, token, nonce):
|
yading@7
|
526 """Verify that the nonce is uniqueish."""
|
yading@7
|
527 nonce = self.data_store.lookup_nonce(consumer, token, nonce)
|
yading@7
|
528 if nonce:
|
yading@7
|
529 raise OAuthError('Nonce already used: %s' % str(nonce))
|
yading@7
|
530
|
yading@7
|
531
|
yading@7
|
532 class OAuthClient(object):
|
yading@7
|
533 """OAuthClient is a worker to attempt to execute a request."""
|
yading@7
|
534 consumer = None
|
yading@7
|
535 token = None
|
yading@7
|
536
|
yading@7
|
537 def __init__(self, oauth_consumer, oauth_token):
|
yading@7
|
538 self.consumer = oauth_consumer
|
yading@7
|
539 self.token = oauth_token
|
yading@7
|
540
|
yading@7
|
541 def get_consumer(self):
|
yading@7
|
542 return self.consumer
|
yading@7
|
543
|
yading@7
|
544 def get_token(self):
|
yading@7
|
545 return self.token
|
yading@7
|
546
|
yading@7
|
547 def fetch_request_token(self, oauth_request):
|
yading@7
|
548 """-> OAuthToken."""
|
yading@7
|
549 raise NotImplementedError
|
yading@7
|
550
|
yading@7
|
551 def fetch_access_token(self, oauth_request):
|
yading@7
|
552 """-> OAuthToken."""
|
yading@7
|
553 raise NotImplementedError
|
yading@7
|
554
|
yading@7
|
555 def access_resource(self, oauth_request):
|
yading@7
|
556 """-> Some protected resource."""
|
yading@7
|
557 raise NotImplementedError
|
yading@7
|
558
|
yading@7
|
559
|
yading@7
|
560 class OAuthDataStore(object):
|
yading@7
|
561 """A database abstraction used to lookup consumers and tokens."""
|
yading@7
|
562
|
yading@7
|
563 def lookup_consumer(self, key):
|
yading@7
|
564 """-> OAuthConsumer."""
|
yading@7
|
565 raise NotImplementedError
|
yading@7
|
566
|
yading@7
|
567 def lookup_token(self, oauth_consumer, token_type, token_token):
|
yading@7
|
568 """-> OAuthToken."""
|
yading@7
|
569 raise NotImplementedError
|
yading@7
|
570
|
yading@7
|
571 def lookup_nonce(self, oauth_consumer, oauth_token, nonce):
|
yading@7
|
572 """-> OAuthToken."""
|
yading@7
|
573 raise NotImplementedError
|
yading@7
|
574
|
yading@7
|
575 def fetch_request_token(self, oauth_consumer, oauth_callback):
|
yading@7
|
576 """-> OAuthToken."""
|
yading@7
|
577 raise NotImplementedError
|
yading@7
|
578
|
yading@7
|
579 def fetch_access_token(self, oauth_consumer, oauth_token, oauth_verifier):
|
yading@7
|
580 """-> OAuthToken."""
|
yading@7
|
581 raise NotImplementedError
|
yading@7
|
582
|
yading@7
|
583 def authorize_request_token(self, oauth_token, user):
|
yading@7
|
584 """-> OAuthToken."""
|
yading@7
|
585 raise NotImplementedError
|
yading@7
|
586
|
yading@7
|
587
|
yading@7
|
588 class OAuthSignatureMethod(object):
|
yading@7
|
589 """A strategy class that implements a signature method."""
|
yading@7
|
590 def get_name(self):
|
yading@7
|
591 """-> str."""
|
yading@7
|
592 raise NotImplementedError
|
yading@7
|
593
|
yading@7
|
594 def build_signature_base_string(self, oauth_request, oauth_consumer, oauth_token):
|
yading@7
|
595 """-> str key, str raw."""
|
yading@7
|
596 raise NotImplementedError
|
yading@7
|
597
|
yading@7
|
598 def build_signature(self, oauth_request, oauth_consumer, oauth_token):
|
yading@7
|
599 """-> str."""
|
yading@7
|
600 raise NotImplementedError
|
yading@7
|
601
|
yading@7
|
602 def check_signature(self, oauth_request, consumer, token, signature):
|
yading@7
|
603 built = self.build_signature(oauth_request, consumer, token)
|
yading@7
|
604 return built == signature
|
yading@7
|
605
|
yading@7
|
606
|
yading@7
|
607 class OAuthSignatureMethod_HMAC_SHA1(OAuthSignatureMethod):
|
yading@7
|
608
|
yading@7
|
609 def get_name(self):
|
yading@7
|
610 return 'HMAC-SHA1'
|
yading@7
|
611
|
yading@7
|
612 def build_signature_base_string(self, oauth_request, consumer, token):
|
yading@7
|
613 sig = (
|
yading@7
|
614 escape(oauth_request.get_normalized_http_method()),
|
yading@7
|
615 escape(oauth_request.get_normalized_http_url()),
|
yading@7
|
616 escape(oauth_request.get_normalized_parameters()),
|
yading@7
|
617 )
|
yading@7
|
618
|
yading@7
|
619 key = '%s&' % escape(consumer.secret)
|
yading@7
|
620 if token:
|
yading@7
|
621 key += escape(token.secret)
|
yading@7
|
622 raw = '&'.join(sig)
|
yading@7
|
623 return key, raw
|
yading@7
|
624
|
yading@7
|
625 def build_signature(self, oauth_request, consumer, token):
|
yading@7
|
626 """Builds the base signature string."""
|
yading@7
|
627 key, raw = self.build_signature_base_string(oauth_request, consumer,
|
yading@7
|
628 token)
|
yading@7
|
629
|
yading@7
|
630 # HMAC object.
|
yading@7
|
631 try:
|
yading@7
|
632 import hashlib # 2.5
|
yading@7
|
633 hashed = hmac.new(key, raw, hashlib.sha1)
|
yading@7
|
634 except:
|
yading@7
|
635 import sha # Deprecated
|
yading@7
|
636 hashed = hmac.new(key, raw, sha)
|
yading@7
|
637
|
yading@7
|
638 # Calculate the digest base 64.
|
yading@7
|
639 return binascii.b2a_base64(hashed.digest())[:-1]
|
yading@7
|
640
|
yading@7
|
641
|
yading@7
|
642 class OAuthSignatureMethod_PLAINTEXT(OAuthSignatureMethod):
|
yading@7
|
643
|
yading@7
|
644 def get_name(self):
|
yading@7
|
645 return 'PLAINTEXT'
|
yading@7
|
646
|
yading@7
|
647 def build_signature_base_string(self, oauth_request, consumer, token):
|
yading@7
|
648 """Concatenates the consumer key and secret."""
|
yading@7
|
649 sig = '%s&' % escape(consumer.secret)
|
yading@7
|
650 if token:
|
yading@7
|
651 sig = sig + escape(token.secret)
|
yading@7
|
652 return sig, sig
|
yading@7
|
653
|
yading@7
|
654 def build_signature(self, oauth_request, consumer, token):
|
yading@7
|
655 key, raw = self.build_signature_base_string(oauth_request, consumer,
|
yading@7
|
656 token)
|
yading@7
|
657 return key
|