15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)""" 25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)The MIT License 35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Copyright (c) 2007-2010 Leah Culver, Joe Stump, Mark Paschal, Vic Fryzel 55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Permission is hereby granted, free of charge, to any person obtaining a copy 75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)of this software and associated documentation files (the "Software"), to deal 85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)in the Software without restriction, including without limitation the rights 95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)copies of the Software, and to permit persons to whom the Software is 115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)furnished to do so, subject to the following conditions: 125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)The above copyright notice and this permission notice shall be included in 145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)all copies or substantial portions of the Software. 155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)THE SOFTWARE. 235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)""" 245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import urllib 265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import time 275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import random 285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import urlparse 295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import hmac 305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import binascii 315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import httplib2 325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)try: 345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) from urlparse import parse_qs, parse_qsl 355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)except ImportError: 365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) from cgi import parse_qs, parse_qsl 375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)VERSION = '1.0' # Hi Blaine! 405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)HTTP_METHOD = 'GET' 415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)SIGNATURE_METHOD = 'PLAINTEXT' 425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class Error(RuntimeError): 455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Generic exception class.""" 465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def __init__(self, message='OAuth error occurred.'): 485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self._message = message 495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) @property 515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def message(self): 525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """A hack to get around the deprecation errors in 2.6.""" 535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return self._message 545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def __str__(self): 565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return self._message 575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class MissingSignature(Error): 605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) pass 615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def build_authenticate_header(realm=''): 645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Optional WWW-Authenticate header (401 error)""" 655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return {'WWW-Authenticate': 'OAuth realm="%s"' % realm} 665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def build_xoauth_string(url, consumer, token=None): 695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Build an XOAUTH string for use in SMTP/IMPA authentication.""" 705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) request = Request.from_consumer_and_token(consumer, token, 715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) "GET", url) 725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) signing_method = SignatureMethod_HMAC_SHA1() 745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) request.sign_request(signing_method, consumer, token) 755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) params = [] 775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for k, v in sorted(request.iteritems()): 785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if v is not None: 795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) params.append('%s="%s"' % (k, escape(v))) 805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return "%s %s %s" % ("GET", url, ','.join(params)) 825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def escape(s): 855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Escape a URL including any /.""" 865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return urllib.quote(s, safe='~') 875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def generate_timestamp(): 905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Get seconds since epoch (UTC).""" 915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return int(time.time()) 925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def generate_nonce(length=8): 955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Generate pseudorandom number.""" 965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return ''.join([str(random.randint(0, 9)) for i in range(length)]) 975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def generate_verifier(length=8): 1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Generate pseudorandom number.""" 1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return ''.join([str(random.randint(0, 9)) for i in range(length)]) 1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class Consumer(object): 1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """A consumer of OAuth-protected services. 1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) The OAuth consumer is a "third-party" service that wants to access 1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) protected resources from an OAuth service provider on behalf of an end 1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) user. It's kind of the OAuth client. 1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) Usually a consumer must be registered with the service provider by the 1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) developer of the consumer software. As part of that process, the service 1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) provider gives the consumer a *key* and a *secret* with which the consumer 1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) software can identify itself to the service. The consumer will include its 1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) key in each request to identify itself, but will use its secret only when 1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) signing requests, to prove that the request is from that particular 1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) registered consumer. 1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) Once registered, the consumer can then use its consumer credentials to ask 1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) the service provider for a request token, kicking off the OAuth 1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) authorization process. 1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """ 1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) key = None 1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) secret = None 1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def __init__(self, key, secret): 1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.key = key 1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.secret = secret 1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if self.key is None or self.secret is None: 1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) raise ValueError("Key and secret must be set.") 1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def __str__(self): 1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) data = {'oauth_consumer_key': self.key, 1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 'oauth_consumer_secret': self.secret} 1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return urllib.urlencode(data) 1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class Token(object): 1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """An OAuth credential used to request authorization or a protected 1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) resource. 1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) Tokens in OAuth comprise a *key* and a *secret*. The key is included in 1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) requests to identify the token being used, but the secret is used only in 1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) the signature, to prove that the requester is who the server gave the 1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) token to. 1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) When first negotiating the authorization, the consumer asks for a *request 1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) token* that the live user authorizes with the service provider. The 1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) consumer then exchanges the request token for an *access token* that can 1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) be used to access protected resources. 1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """ 1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) key = None 1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) secret = None 1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) callback = None 1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) callback_confirmed = None 1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) verifier = None 1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def __init__(self, key, secret): 1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.key = key 1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.secret = secret 1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if self.key is None or self.secret is None: 1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) raise ValueError("Key and secret must be set.") 1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def set_callback(self, callback): 1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.callback = callback 1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.callback_confirmed = 'true' 1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def set_verifier(self, verifier=None): 1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if verifier is not None: 1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.verifier = verifier 1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else: 1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.verifier = generate_verifier() 1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def get_callback_url(self): 1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if self.callback and self.verifier: 1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # Append the oauth_verifier. 1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) parts = urlparse.urlparse(self.callback) 1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) scheme, netloc, path, params, query, fragment = parts[:6] 1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if query: 1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) query = '%s&oauth_verifier=%s' % (query, self.verifier) 1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else: 1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) query = 'oauth_verifier=%s' % self.verifier 1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return urlparse.urlunparse((scheme, netloc, path, params, 1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) query, fragment)) 1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return self.callback 1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def to_string(self): 1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Returns this token as a plain string, suitable for storage. 1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) The resulting string includes the token's secret, so you should never 1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) send or store this string where a third party can read it. 1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """ 1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) data = { 2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 'oauth_token': self.key, 2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 'oauth_token_secret': self.secret, 2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if self.callback_confirmed is not None: 2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) data['oauth_callback_confirmed'] = self.callback_confirmed 2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return urllib.urlencode(data) 2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) @staticmethod 2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def from_string(s): 2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Deserializes a token from a string like one returned by 2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) `to_string()`.""" 2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if not len(s): 2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) raise ValueError("Invalid parameter string.") 2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) params = parse_qs(s, keep_blank_values=False) 2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if not len(params): 2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) raise ValueError("Invalid parameter string.") 2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) try: 2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) key = params['oauth_token'][0] 2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) except Exception: 2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) raise ValueError("'oauth_token' not found in OAuth request.") 2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) try: 2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) secret = params['oauth_token_secret'][0] 2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) except Exception: 2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) raise ValueError("'oauth_token_secret' not found in " 2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) "OAuth request.") 2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) token = Token(key, secret) 2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) try: 2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) token.callback_confirmed = params['oauth_callback_confirmed'][0] 2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) except KeyError: 2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) pass # 1.0, no callback confirmed. 2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return token 2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def __str__(self): 2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return self.to_string() 2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def setter(attr): 2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) name = attr.__name__ 2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def getter(self): 2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) try: 2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return self.__dict__[name] 2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) except KeyError: 2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) raise AttributeError(name) 2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def deleter(self): 2525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) del self.__dict__[name] 2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return property(getter, attr, deleter) 2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class Request(dict): 2585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """The parameters and information for an HTTP request, suitable for 2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) authorizing with OAuth credentials. 2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) When a consumer wants to access a service's protected resources, it does 2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) so using a signed HTTP request identifying itself (the consumer) with its 2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) key, and providing an access token authorized by the end user to access 2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) those resources. 2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """ 2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) version = VERSION 2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def __init__(self, method=HTTP_METHOD, url=None, parameters=None): 2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.method = method 2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.url = url 2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if parameters is not None: 2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.update(parameters) 2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) @setter 2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def url(self, value): 2795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.__dict__['url'] = value 2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if value is not None: 2815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) scheme, netloc, path, params, query, fragment = urlparse.urlparse(value) 2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # Exclude default port numbers. 2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if scheme == 'http' and netloc[-3:] == ':80': 2855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) netloc = netloc[:-3] 2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) elif scheme == 'https' and netloc[-4:] == ':443': 2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) netloc = netloc[:-4] 2885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if scheme not in ('http', 'https'): 2895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) raise ValueError("Unsupported URL %s (%s)." % (value, scheme)) 2905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # Normalized URL excludes params, query, and fragment. 2925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.normalized_url = urlparse.urlunparse((scheme, netloc, path, None, None, None)) 2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else: 2945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.normalized_url = None 2955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.__dict__['url'] = None 2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) @setter 2985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def method(self, value): 2995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.__dict__['method'] = value.upper() 3005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def _get_timestamp_nonce(self): 3025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return self['oauth_timestamp'], self['oauth_nonce'] 3035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def get_nonoauth_parameters(self): 3055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Get any non-OAuth parameters.""" 3065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return dict([(k, v) for k, v in self.iteritems() 3075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if not k.startswith('oauth_')]) 3085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def to_header(self, realm=''): 3105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Serialize as a header for an HTTPAuth request.""" 3115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) oauth_params = ((k, v) for k, v in self.items() 3125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if k.startswith('oauth_')) 3135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) stringy_params = ((k, escape(str(v))) for k, v in oauth_params) 3145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) header_params = ('%s="%s"' % (k, v) for k, v in stringy_params) 3155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) params_header = ', '.join(header_params) 3165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) auth_header = 'OAuth realm="%s"' % realm 3185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if params_header: 3195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) auth_header = "%s, %s" % (auth_header, params_header) 3205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return {'Authorization': auth_header} 3225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def to_postdata(self): 3245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Serialize as post data for a POST request.""" 3255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # tell urlencode to deal with sequence values and map them correctly 3265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # to resulting querystring. for example self["k"] = ["v1", "v2"] will 3275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # result in 'k=v1&k=v2' and not k=%5B%27v1%27%2C+%27v2%27%5D 3285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return urllib.urlencode(self, True).replace('+', '%20') 3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def to_url(self): 3315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Serialize as a URL for a GET request.""" 3325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) base_url = urlparse.urlparse(self.url) 3335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) try: 3345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) query = base_url.query 3355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) except AttributeError: 3365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # must be python <2.5 3375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) query = base_url[4] 3385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) query = parse_qs(query) 3395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for k, v in self.items(): 3405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) query.setdefault(k, []).append(v) 3415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) try: 3435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) scheme = base_url.scheme 3445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) netloc = base_url.netloc 3455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) path = base_url.path 3465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) params = base_url.params 3475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) fragment = base_url.fragment 3485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) except AttributeError: 3495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # must be python <2.5 3505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) scheme = base_url[0] 3515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) netloc = base_url[1] 3525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) path = base_url[2] 3535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) params = base_url[3] 3545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) fragment = base_url[5] 3555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) url = (scheme, netloc, path, params, 3575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) urllib.urlencode(query, True), fragment) 3585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return urlparse.urlunparse(url) 3595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def get_parameter(self, parameter): 3615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ret = self.get(parameter) 3625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if ret is None: 3635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) raise Error('Parameter not found: %s' % parameter) 3645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return ret 3665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def get_normalized_parameters(self): 3685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Return a string that contains the parameters that must be signed.""" 3695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) items = [] 3705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for key, value in self.iteritems(): 3715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if key == 'oauth_signature': 3725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) continue 3735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # 1.0a/9.1.1 states that kvp must be sorted by key, then by value, 3745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # so we unpack sequence values into multiple items for sorting. 3755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if hasattr(value, '__iter__'): 3765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) items.extend((key, item) for item in value) 3775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else: 3785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) items.append((key, value)) 3795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # Include any query string parameters from the provided URL 3815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) query = urlparse.urlparse(self.url)[4] 3825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) url_items = self._split_url_string(query).items() 3845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) non_oauth_url_items = list([(k, v) for k, v in url_items if not k.startswith('oauth_')]) 3855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) items.extend(non_oauth_url_items) 3865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) encoded_str = urllib.urlencode(sorted(items)) 3885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # Encode signature parameters per Oauth Core 1.0 protocol 3895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # spec draft 7, section 3.6 3905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # (http://tools.ietf.org/html/draft-hammer-oauth-07#section-3.6) 3915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # Spaces must be encoded with "%20" instead of "+" 3925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return encoded_str.replace('+', '%20').replace('%7E', '~') 3935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def sign_request(self, signature_method, consumer, token): 3955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Set the signature parameter to the result of sign.""" 3965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if 'oauth_consumer_key' not in self: 3985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self['oauth_consumer_key'] = consumer.key 3995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if token and 'oauth_token' not in self: 4015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self['oauth_token'] = token.key 4025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self['oauth_signature_method'] = signature_method.name 4045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self['oauth_signature'] = signature_method.sign(self, consumer, token) 4055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) @classmethod 4075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def make_timestamp(cls): 4085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Get seconds since epoch (UTC).""" 4095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return str(int(time.time())) 4105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) @classmethod 4125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def make_nonce(cls): 4135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Generate pseudorandom number.""" 4145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return str(random.randint(0, 100000000)) 4155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) @classmethod 4175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def from_request(cls, http_method, http_url, headers=None, parameters=None, 4185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) query_string=None): 4195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Combines multiple parameter sources.""" 4205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if parameters is None: 4215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) parameters = {} 4225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # Headers 4245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if headers and 'Authorization' in headers: 4255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) auth_header = headers['Authorization'] 4265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # Check that the authorization header is OAuth. 4275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if auth_header[:6] == 'OAuth ': 4285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) auth_header = auth_header[6:] 4295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) try: 4305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # Get the parameters from the header. 4315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) header_params = cls._split_header(auth_header) 4325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) parameters.update(header_params) 4335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) except: 4345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) raise Error('Unable to parse OAuth parameters from ' 4355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 'Authorization header.') 4365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # GET or POST query string. 4385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if query_string: 4395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) query_params = cls._split_url_string(query_string) 4405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) parameters.update(query_params) 4415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # URL parameters. 4435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) param_str = urlparse.urlparse(http_url)[4] # query 4445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) url_params = cls._split_url_string(param_str) 4455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) parameters.update(url_params) 4465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if parameters: 4485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return cls(http_method, http_url, parameters) 4495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return None 4515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) @classmethod 4535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def from_consumer_and_token(cls, consumer, token=None, 4545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) http_method=HTTP_METHOD, http_url=None, parameters=None): 4555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if not parameters: 4565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) parameters = {} 4575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) defaults = { 4595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 'oauth_consumer_key': consumer.key, 4605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 'oauth_timestamp': cls.make_timestamp(), 4615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 'oauth_nonce': cls.make_nonce(), 4625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 'oauth_version': cls.version, 4635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 4645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) defaults.update(parameters) 4665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) parameters = defaults 4675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if token: 4695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) parameters['oauth_token'] = token.key 4705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if token.verifier: 4715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) parameters['oauth_verifier'] = token.verifier 4725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return Request(http_method, http_url, parameters) 4745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) @classmethod 4765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def from_token_and_callback(cls, token, callback=None, 4775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) http_method=HTTP_METHOD, http_url=None, parameters=None): 4785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if not parameters: 4805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) parameters = {} 4815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) parameters['oauth_token'] = token.key 4835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if callback: 4855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) parameters['oauth_callback'] = callback 4865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return cls(http_method, http_url, parameters) 4885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) @staticmethod 4905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def _split_header(header): 4915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Turn Authorization: header into parameters.""" 4925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) params = {} 4935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) parts = header.split(',') 4945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for param in parts: 4955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # Ignore realm parameter. 4965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if param.find('realm') > -1: 4975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) continue 4985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # Remove whitespace. 4995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) param = param.strip() 5005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # Split key-value. 5015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) param_parts = param.split('=', 1) 5025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # Remove quotes and unescape the value. 5035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"')) 5045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return params 5055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) @staticmethod 5075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def _split_url_string(param_str): 5085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Turn URL string into parameters.""" 5095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) parameters = parse_qs(param_str, keep_blank_values=False) 5105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for k, v in parameters.iteritems(): 5115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) parameters[k] = urllib.unquote(v[0]) 5125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return parameters 5135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class Client(httplib2.Http): 5165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """OAuthClient is a worker to attempt to execute a request.""" 5175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def __init__(self, consumer, token=None, cache=None, timeout=None, 5195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) proxy_info=None): 5205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if consumer is not None and not isinstance(consumer, Consumer): 5225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) raise ValueError("Invalid consumer.") 5235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if token is not None and not isinstance(token, Token): 5255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) raise ValueError("Invalid token.") 5265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.consumer = consumer 5285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.token = token 5295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.method = SignatureMethod_HMAC_SHA1() 5305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) httplib2.Http.__init__(self, cache=cache, timeout=timeout, 5325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) proxy_info=proxy_info) 5335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def set_signature_method(self, method): 5355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if not isinstance(method, SignatureMethod): 5365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) raise ValueError("Invalid signature method.") 5375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.method = method 5395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def request(self, uri, method="GET", body=None, headers=None, 5415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) redirections=httplib2.DEFAULT_MAX_REDIRECTS, connection_type=None): 5425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) DEFAULT_CONTENT_TYPE = 'application/x-www-form-urlencoded' 5435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if not isinstance(headers, dict): 5455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) headers = {} 5465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) is_multipart = method == 'POST' and headers.get('Content-Type', 5485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) DEFAULT_CONTENT_TYPE) != DEFAULT_CONTENT_TYPE 5495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if body and method == "POST" and not is_multipart: 5515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) parameters = dict(parse_qsl(body)) 5525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else: 5535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) parameters = None 5545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) req = Request.from_consumer_and_token(self.consumer, 5565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) token=self.token, http_method=method, http_url=uri, 5575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) parameters=parameters) 5585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) req.sign_request(self.method, self.consumer, self.token) 5605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if method == "POST": 5625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) headers['Content-Type'] = headers.get('Content-Type', 5635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) DEFAULT_CONTENT_TYPE) 5645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if is_multipart: 5655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) headers.update(req.to_header()) 5665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else: 5675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) body = req.to_postdata() 5685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) elif method == "GET": 5695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) uri = req.to_url() 5705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) else: 5715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) headers.update(req.to_header()) 5725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return httplib2.Http.request(self, uri, method=method, body=body, 5745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) headers=headers, redirections=redirections, 5755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) connection_type=connection_type) 5765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class Server(object): 5795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """A skeletal implementation of a service provider, providing protected 5805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) resources to requests from authorized consumers. 5815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) This class implements the logic to check requests for authorization. You 5835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) can use it with your web server or web framework to protect certain 5845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) resources with OAuth. 5855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """ 5865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) timestamp_threshold = 300 # In seconds, five minutes. 5885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) version = VERSION 5895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) signature_methods = None 5905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def __init__(self, signature_methods=None): 5925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.signature_methods = signature_methods or {} 5935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def add_signature_method(self, signature_method): 5955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.signature_methods[signature_method.name] = signature_method 5965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return self.signature_methods 5975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def verify_request(self, request, consumer, token): 5995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Verifies an api call and checks all the parameters.""" 6005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) version = self._get_version(request) 6025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self._check_signature(request, consumer, token) 6035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) parameters = request.get_nonoauth_parameters() 6045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return parameters 6055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def build_authenticate_header(self, realm=''): 6075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Optional support for the authenticate header.""" 6085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return {'WWW-Authenticate': 'OAuth realm="%s"' % realm} 6095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def _get_version(self, request): 6115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Verify the correct version request for this server.""" 6125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) try: 6135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) version = request.get_parameter('oauth_version') 6145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) except: 6155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) version = VERSION 6165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if version and version != self.version: 6185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) raise Error('OAuth version %s not supported.' % str(version)) 6195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return version 6215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def _get_signature_method(self, request): 6235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Figure out the signature with some defaults.""" 6245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) try: 6255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) signature_method = request.get_parameter('oauth_signature_method') 6265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) except: 6275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) signature_method = SIGNATURE_METHOD 6285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) try: 6305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # Get the signature method object. 6315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) signature_method = self.signature_methods[signature_method] 6325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) except: 6335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) signature_method_names = ', '.join(self.signature_methods.keys()) 6345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) raise Error('Signature method %s not supported try one of the following: %s' % (signature_method, signature_method_names)) 6355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return signature_method 6375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def _get_verifier(self, request): 6395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return request.get_parameter('oauth_verifier') 6405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def _check_signature(self, request, consumer, token): 6425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) timestamp, nonce = request._get_timestamp_nonce() 6435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self._check_timestamp(timestamp) 6445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) signature_method = self._get_signature_method(request) 6455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) try: 6475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) signature = request.get_parameter('oauth_signature') 6485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) except: 6495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) raise MissingSignature('Missing oauth_signature.') 6505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # Validate the signature. 6525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) valid = signature_method.check(request, consumer, token, signature) 6535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if not valid: 6555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) key, base = signature_method.signing_base(request, consumer, token) 6565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) raise Error('Invalid signature. Expected signature base ' 6585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 'string: %s' % base) 6595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) built = signature_method.sign(request, consumer, token) 6615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def _check_timestamp(self, timestamp): 6635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Verify that timestamp is recentish.""" 6645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) timestamp = int(timestamp) 6655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) now = int(time.time()) 6665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) lapsed = now - timestamp 6675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if lapsed > self.timestamp_threshold: 6685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) raise Error('Expired timestamp: given %d and now %s has a ' 6695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 'greater difference than threshold %d' % (timestamp, now, 6705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) self.timestamp_threshold)) 6715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class SignatureMethod(object): 6745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """A way of signing requests. 6755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) The OAuth protocol lets consumers and service providers pick a way to sign 6775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) requests. This interface shows the methods expected by the other `oauth` 6785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) modules for signing requests. Subclass it and implement its methods to 6795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) provide a new way to sign requests. 6805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """ 6815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def signing_base(self, request, consumer, token): 6835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Calculates the string that needs to be signed. 6845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) This method returns a 2-tuple containing the starting key for the 6865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) signing and the message to be signed. The latter may be used in error 6875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) messages to help clients debug their software. 6885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """ 6905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) raise NotImplementedError 6915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def sign(self, request, consumer, token): 6935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Returns the signature for the given request, based on the consumer 6945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) and token also provided. 6955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) You should use your implementation of `signing_base()` to build the 6975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) message to sign. Otherwise it may be less useful for debugging. 6985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """ 7005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) raise NotImplementedError 7015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 7025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def check(self, request, consumer, token, signature): 7035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Returns whether the given signature is the correct signature for 7045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) the given consumer and token signing the given request.""" 7055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) built = self.sign(request, consumer, token) 7065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return built == signature 7075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 7085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 7095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class SignatureMethod_HMAC_SHA1(SignatureMethod): 7105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) name = 'HMAC-SHA1' 7115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 7125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def signing_base(self, request, consumer, token): 7135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if request.normalized_url is None: 7145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) raise ValueError("Base URL for request is not set.") 7155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 7165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) sig = ( 7175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) escape(request.method), 7185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) escape(request.normalized_url), 7195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) escape(request.get_normalized_parameters()), 7205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ) 7215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 7225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) key = '%s&' % escape(consumer.secret) 7235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if token: 7245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) key += escape(token.secret) 7255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) raw = '&'.join(sig) 7265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return key, raw 7275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 7285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def sign(self, request, consumer, token): 7295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Builds the base signature string.""" 7305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) key, raw = self.signing_base(request, consumer, token) 7315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 7325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # HMAC object. 7335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) try: 7345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) from hashlib import sha1 as sha 7355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) except ImportError: 7365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) import sha # Deprecated 7375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 7385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) hashed = hmac.new(key, raw, sha) 7395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 7405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) # Calculate the digest base 64. 7415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return binascii.b2a_base64(hashed.digest())[:-1] 7425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 7435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 7445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class SignatureMethod_PLAINTEXT(SignatureMethod): 7455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 7465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) name = 'PLAINTEXT' 7475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 7485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def signing_base(self, request, consumer, token): 7495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) """Concatenates the consumer key and secret with the token's 7505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) secret.""" 7515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) sig = '%s&' % escape(consumer.secret) 7525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if token: 7535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) sig = sig + escape(token.secret) 7545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return sig, sig 7555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 7565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) def sign(self, request, consumer, token): 7575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) key, raw = self.signing_base(request, consumer, token) 7585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return raw 759