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