comparison musixmatch-master/7digital-python/lib/oauth.py @ 7:8c29444cb5fd

Just did some work
author Yading Song <yading.song@eecs.qmul.ac.uk>
date Sat, 20 Apr 2013 19:01:57 +0200
parents
children
comparison
equal deleted inserted replaced
2:e0a7176da80a 7:8c29444cb5fd
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
191 def _get_timestamp_nonce(self):
192 return self.get_parameter('oauth_timestamp'), self.get_parameter(
193 'oauth_nonce')
194
195 def get_nonoauth_parameters(self):
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
223 def get_normalized_parameters(self):
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
239 def get_normalized_http_method(self):
240 """Uppercases the http method."""
241 return self.http_method.upper()
242
243 def get_normalized_http_url(self):
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
607 class OAuthSignatureMethod_HMAC_SHA1(OAuthSignatureMethod):
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
642 class OAuthSignatureMethod_PLAINTEXT(OAuthSignatureMethod):
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