18ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen""" 28ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenThe MIT License 38ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 48ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenCopyright (c) 2007-2010 Leah Culver, Joe Stump, Mark Paschal, Vic Fryzel 58ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 68ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenPermission is hereby granted, free of charge, to any person obtaining a copy 78ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenof this software and associated documentation files (the "Software"), to deal 88ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenin the Software without restriction, including without limitation the rights 98ae428e0fb7feea16d79853f29447469a93bedffKristian Monsento use, copy, modify, merge, publish, distribute, sublicense, and/or sell 108ae428e0fb7feea16d79853f29447469a93bedffKristian Monsencopies of the Software, and to permit persons to whom the Software is 118ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenfurnished to do so, subject to the following conditions: 128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 138ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenThe above copyright notice and this permission notice shall be included in 148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenall copies or substantial portions of the Software. 158ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 168ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 178ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 188ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 198ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 208ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 218ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 228ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenTHE SOFTWARE. 238ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen""" 248ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 258ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenimport urllib 268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenimport time 278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenimport random 288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenimport urlparse 298ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenimport hmac 308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenimport binascii 318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenimport httplib2 328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsentry: 348ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen from urlparse import parse_qs, parse_qsl 358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenexcept ImportError: 368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen from cgi import parse_qs, parse_qsl 378ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 398ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenVERSION = '1.0' # Hi Blaine! 408ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenHTTP_METHOD = 'GET' 418ae428e0fb7feea16d79853f29447469a93bedffKristian MonsenSIGNATURE_METHOD = 'PLAINTEXT' 428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 448ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenclass Error(RuntimeError): 458ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """Generic exception class.""" 468ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def __init__(self, message='OAuth error occurred.'): 488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen self._message = message 498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 508ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen @property 518ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def message(self): 528ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """A hack to get around the deprecation errors in 2.6.""" 538ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return self._message 548ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 558ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def __str__(self): 568ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return self._message 578ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 588ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 598ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenclass MissingSignature(Error): 608ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen pass 618ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 628ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 638ae428e0fb7feea16d79853f29447469a93bedffKristian Monsendef build_authenticate_header(realm=''): 648ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """Optional WWW-Authenticate header (401 error)""" 658ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return {'WWW-Authenticate': 'OAuth realm="%s"' % realm} 668ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 678ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 688ae428e0fb7feea16d79853f29447469a93bedffKristian Monsendef build_xoauth_string(url, consumer, token=None): 698ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """Build an XOAUTH string for use in SMTP/IMPA authentication.""" 708ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen request = Request.from_consumer_and_token(consumer, token, 718ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen "GET", url) 728ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 738ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen signing_method = SignatureMethod_HMAC_SHA1() 748ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen request.sign_request(signing_method, consumer, token) 758ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 768ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen params = [] 778ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen for k, v in sorted(request.iteritems()): 788ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if v is not None: 798ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen params.append('%s="%s"' % (k, escape(v))) 808ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 818ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return "%s %s %s" % ("GET", url, ','.join(params)) 828ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 838ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 848ae428e0fb7feea16d79853f29447469a93bedffKristian Monsendef escape(s): 858ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """Escape a URL including any /.""" 868ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return urllib.quote(s, safe='~') 878ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 888ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 898ae428e0fb7feea16d79853f29447469a93bedffKristian Monsendef generate_timestamp(): 908ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """Get seconds since epoch (UTC).""" 918ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return int(time.time()) 928ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 938ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 948ae428e0fb7feea16d79853f29447469a93bedffKristian Monsendef generate_nonce(length=8): 958ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """Generate pseudorandom number.""" 968ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return ''.join([str(random.randint(0, 9)) for i in range(length)]) 978ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 988ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 998ae428e0fb7feea16d79853f29447469a93bedffKristian Monsendef generate_verifier(length=8): 1008ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """Generate pseudorandom number.""" 1018ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return ''.join([str(random.randint(0, 9)) for i in range(length)]) 1028ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 1038ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 1048ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenclass Consumer(object): 1058ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """A consumer of OAuth-protected services. 1068ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 1078ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen The OAuth consumer is a "third-party" service that wants to access 1088ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen protected resources from an OAuth service provider on behalf of an end 1098ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen user. It's kind of the OAuth client. 1108ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 1118ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen Usually a consumer must be registered with the service provider by the 1128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen developer of the consumer software. As part of that process, the service 1138ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen provider gives the consumer a *key* and a *secret* with which the consumer 1148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen software can identify itself to the service. The consumer will include its 1158ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen key in each request to identify itself, but will use its secret only when 1168ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen signing requests, to prove that the request is from that particular 1178ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen registered consumer. 1188ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 1198ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen Once registered, the consumer can then use its consumer credentials to ask 1208ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen the service provider for a request token, kicking off the OAuth 1218ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen authorization process. 1228ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """ 1238ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 1248ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen key = None 1258ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen secret = None 1268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 1278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def __init__(self, key, secret): 1288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen self.key = key 1298ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen self.secret = secret 1308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 1318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if self.key is None or self.secret is None: 1328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen raise ValueError("Key and secret must be set.") 1338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 1348ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def __str__(self): 1358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen data = {'oauth_consumer_key': self.key, 1368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 'oauth_consumer_secret': self.secret} 1378ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 1388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return urllib.urlencode(data) 1398ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 1408ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 1418ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenclass Token(object): 1428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """An OAuth credential used to request authorization or a protected 1438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen resource. 1448ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 1458ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen Tokens in OAuth comprise a *key* and a *secret*. The key is included in 1468ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen requests to identify the token being used, but the secret is used only in 1478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen the signature, to prove that the requester is who the server gave the 1488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen token to. 1498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 1508ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen When first negotiating the authorization, the consumer asks for a *request 1518ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen token* that the live user authorizes with the service provider. The 1528ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen consumer then exchanges the request token for an *access token* that can 1538ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen be used to access protected resources. 1548ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """ 1558ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 1568ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen key = None 1578ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen secret = None 1588ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen callback = None 1598ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen callback_confirmed = None 1608ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen verifier = None 1618ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 1628ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def __init__(self, key, secret): 1638ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen self.key = key 1648ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen self.secret = secret 1658ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 1668ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if self.key is None or self.secret is None: 1678ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen raise ValueError("Key and secret must be set.") 1688ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 1698ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def set_callback(self, callback): 1708ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen self.callback = callback 1718ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen self.callback_confirmed = 'true' 1728ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 1738ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def set_verifier(self, verifier=None): 1748ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if verifier is not None: 1758ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen self.verifier = verifier 1768ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen else: 1778ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen self.verifier = generate_verifier() 1788ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 1798ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def get_callback_url(self): 1808ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if self.callback and self.verifier: 1818ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen # Append the oauth_verifier. 1828ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen parts = urlparse.urlparse(self.callback) 1838ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen scheme, netloc, path, params, query, fragment = parts[:6] 1848ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if query: 1858ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen query = '%s&oauth_verifier=%s' % (query, self.verifier) 1868ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen else: 1878ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen query = 'oauth_verifier=%s' % self.verifier 1888ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return urlparse.urlunparse((scheme, netloc, path, params, 1898ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen query, fragment)) 1908ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return self.callback 1918ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 1928ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def to_string(self): 1938ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """Returns this token as a plain string, suitable for storage. 1948ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 1958ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen The resulting string includes the token's secret, so you should never 1968ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen send or store this string where a third party can read it. 1978ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """ 1988ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 1998ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen data = { 2008ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 'oauth_token': self.key, 2018ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 'oauth_token_secret': self.secret, 2028ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen } 2038ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 2048ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if self.callback_confirmed is not None: 2058ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen data['oauth_callback_confirmed'] = self.callback_confirmed 2068ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return urllib.urlencode(data) 2078ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 2088ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen @staticmethod 2098ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def from_string(s): 2108ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """Deserializes a token from a string like one returned by 2118ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen `to_string()`.""" 2128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 2138ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if not len(s): 2148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen raise ValueError("Invalid parameter string.") 2158ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 2168ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen params = parse_qs(s, keep_blank_values=False) 2178ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if not len(params): 2188ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen raise ValueError("Invalid parameter string.") 2198ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 2208ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen try: 2218ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen key = params['oauth_token'][0] 2228ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen except Exception: 2238ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen raise ValueError("'oauth_token' not found in OAuth request.") 2248ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 2258ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen try: 2268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen secret = params['oauth_token_secret'][0] 2278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen except Exception: 2288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen raise ValueError("'oauth_token_secret' not found in " 2298ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen "OAuth request.") 2308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 2318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen token = Token(key, secret) 2328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen try: 2338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen token.callback_confirmed = params['oauth_callback_confirmed'][0] 2348ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen except KeyError: 2358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen pass # 1.0, no callback confirmed. 2368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return token 2378ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 2388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def __str__(self): 2398ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return self.to_string() 2408ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 2418ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 2428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsendef setter(attr): 2438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen name = attr.__name__ 2448ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 2458ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def getter(self): 2468ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen try: 2478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return self.__dict__[name] 2488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen except KeyError: 2498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen raise AttributeError(name) 2508ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 2518ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def deleter(self): 2528ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen del self.__dict__[name] 2538ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 2548ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return property(getter, attr, deleter) 2558ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 2568ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 2578ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenclass Request(dict): 2588ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 2598ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """The parameters and information for an HTTP request, suitable for 2608ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen authorizing with OAuth credentials. 2618ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 2628ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen When a consumer wants to access a service's protected resources, it does 2638ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen so using a signed HTTP request identifying itself (the consumer) with its 2648ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen key, and providing an access token authorized by the end user to access 2658ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen those resources. 2668ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 2678ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """ 2688ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 2698ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen version = VERSION 2708ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 2718ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def __init__(self, method=HTTP_METHOD, url=None, parameters=None): 2728ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen self.method = method 2738ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen self.url = url 2748ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if parameters is not None: 2758ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen self.update(parameters) 2768ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 2778ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen @setter 2788ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def url(self, value): 2798ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen self.__dict__['url'] = value 2808ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if value is not None: 2818ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen scheme, netloc, path, params, query, fragment = urlparse.urlparse(value) 2828ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 2838ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen # Exclude default port numbers. 2848ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if scheme == 'http' and netloc[-3:] == ':80': 2858ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen netloc = netloc[:-3] 2868ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen elif scheme == 'https' and netloc[-4:] == ':443': 2878ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen netloc = netloc[:-4] 2888ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if scheme not in ('http', 'https'): 2898ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen raise ValueError("Unsupported URL %s (%s)." % (value, scheme)) 2908ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 2918ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen # Normalized URL excludes params, query, and fragment. 2928ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen self.normalized_url = urlparse.urlunparse((scheme, netloc, path, None, None, None)) 2938ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen else: 2948ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen self.normalized_url = None 2958ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen self.__dict__['url'] = None 2968ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 2978ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen @setter 2988ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def method(self, value): 2998ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen self.__dict__['method'] = value.upper() 3008ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 3018ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def _get_timestamp_nonce(self): 3028ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return self['oauth_timestamp'], self['oauth_nonce'] 3038ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 3048ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def get_nonoauth_parameters(self): 3058ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """Get any non-OAuth parameters.""" 3068ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return dict([(k, v) for k, v in self.iteritems() 3078ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if not k.startswith('oauth_')]) 3088ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 3098ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def to_header(self, realm=''): 3108ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """Serialize as a header for an HTTPAuth request.""" 3118ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen oauth_params = ((k, v) for k, v in self.items() 3128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if k.startswith('oauth_')) 3138ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen stringy_params = ((k, escape(str(v))) for k, v in oauth_params) 3148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen header_params = ('%s="%s"' % (k, v) for k, v in stringy_params) 3158ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen params_header = ', '.join(header_params) 3168ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 3178ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen auth_header = 'OAuth realm="%s"' % realm 3188ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if params_header: 3198ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen auth_header = "%s, %s" % (auth_header, params_header) 3208ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 3218ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return {'Authorization': auth_header} 3228ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 3238ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def to_postdata(self): 3248ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """Serialize as post data for a POST request.""" 3258ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen # tell urlencode to deal with sequence values and map them correctly 3268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen # to resulting querystring. for example self["k"] = ["v1", "v2"] will 3278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen # result in 'k=v1&k=v2' and not k=%5B%27v1%27%2C+%27v2%27%5D 3288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return urllib.urlencode(self, True).replace('+', '%20') 3298ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 3308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def to_url(self): 3318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """Serialize as a URL for a GET request.""" 3328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen base_url = urlparse.urlparse(self.url) 3338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen try: 3348ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen query = base_url.query 3358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen except AttributeError: 3368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen # must be python <2.5 3378ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen query = base_url[4] 3388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen query = parse_qs(query) 3398ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen for k, v in self.items(): 3408ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen query.setdefault(k, []).append(v) 3418ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 3428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen try: 3438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen scheme = base_url.scheme 3448ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen netloc = base_url.netloc 3458ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen path = base_url.path 3468ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen params = base_url.params 3478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen fragment = base_url.fragment 3488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen except AttributeError: 3498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen # must be python <2.5 3508ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen scheme = base_url[0] 3518ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen netloc = base_url[1] 3528ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen path = base_url[2] 3538ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen params = base_url[3] 3548ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen fragment = base_url[5] 3558ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 3568ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen url = (scheme, netloc, path, params, 3578ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen urllib.urlencode(query, True), fragment) 3588ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return urlparse.urlunparse(url) 3598ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 3608ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def get_parameter(self, parameter): 3618ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen ret = self.get(parameter) 3628ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if ret is None: 3638ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen raise Error('Parameter not found: %s' % parameter) 3648ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 3658ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return ret 3668ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 3678ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def get_normalized_parameters(self): 3688ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """Return a string that contains the parameters that must be signed.""" 3698ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen items = [] 3708ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen for key, value in self.iteritems(): 3718ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if key == 'oauth_signature': 3728ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen continue 3738ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen # 1.0a/9.1.1 states that kvp must be sorted by key, then by value, 3748ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen # so we unpack sequence values into multiple items for sorting. 3758ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if hasattr(value, '__iter__'): 3768ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen items.extend((key, item) for item in value) 3778ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen else: 3788ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen items.append((key, value)) 3798ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 3808ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen # Include any query string parameters from the provided URL 3818ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen query = urlparse.urlparse(self.url)[4] 3828ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 3838ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen url_items = self._split_url_string(query).items() 3848ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen non_oauth_url_items = list([(k, v) for k, v in url_items if not k.startswith('oauth_')]) 3858ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen items.extend(non_oauth_url_items) 3868ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 3878ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen encoded_str = urllib.urlencode(sorted(items)) 3888ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen # Encode signature parameters per Oauth Core 1.0 protocol 3898ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen # spec draft 7, section 3.6 3908ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen # (http://tools.ietf.org/html/draft-hammer-oauth-07#section-3.6) 3918ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen # Spaces must be encoded with "%20" instead of "+" 3928ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return encoded_str.replace('+', '%20').replace('%7E', '~') 3938ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 3948ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def sign_request(self, signature_method, consumer, token): 3958ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """Set the signature parameter to the result of sign.""" 3968ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 3978ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if 'oauth_consumer_key' not in self: 3988ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen self['oauth_consumer_key'] = consumer.key 3998ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 4008ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if token and 'oauth_token' not in self: 4018ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen self['oauth_token'] = token.key 4028ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 4038ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen self['oauth_signature_method'] = signature_method.name 4048ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen self['oauth_signature'] = signature_method.sign(self, consumer, token) 4058ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 4068ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen @classmethod 4078ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def make_timestamp(cls): 4088ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """Get seconds since epoch (UTC).""" 4098ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return str(int(time.time())) 4108ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 4118ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen @classmethod 4128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def make_nonce(cls): 4138ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """Generate pseudorandom number.""" 4148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return str(random.randint(0, 100000000)) 4158ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 4168ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen @classmethod 4178ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def from_request(cls, http_method, http_url, headers=None, parameters=None, 4188ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen query_string=None): 4198ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """Combines multiple parameter sources.""" 4208ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if parameters is None: 4218ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen parameters = {} 4228ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 4238ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen # Headers 4248ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if headers and 'Authorization' in headers: 4258ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen auth_header = headers['Authorization'] 4268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen # Check that the authorization header is OAuth. 4278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if auth_header[:6] == 'OAuth ': 4288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen auth_header = auth_header[6:] 4298ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen try: 4308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen # Get the parameters from the header. 4318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen header_params = cls._split_header(auth_header) 4328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen parameters.update(header_params) 4338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen except: 4348ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen raise Error('Unable to parse OAuth parameters from ' 4358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 'Authorization header.') 4368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 4378ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen # GET or POST query string. 4388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if query_string: 4398ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen query_params = cls._split_url_string(query_string) 4408ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen parameters.update(query_params) 4418ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 4428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen # URL parameters. 4438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen param_str = urlparse.urlparse(http_url)[4] # query 4448ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen url_params = cls._split_url_string(param_str) 4458ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen parameters.update(url_params) 4468ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 4478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if parameters: 4488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return cls(http_method, http_url, parameters) 4498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 4508ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return None 4518ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 4528ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen @classmethod 4538ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def from_consumer_and_token(cls, consumer, token=None, 4548ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen http_method=HTTP_METHOD, http_url=None, parameters=None): 4558ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if not parameters: 4568ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen parameters = {} 4578ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 4588ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen defaults = { 4598ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 'oauth_consumer_key': consumer.key, 4608ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 'oauth_timestamp': cls.make_timestamp(), 4618ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 'oauth_nonce': cls.make_nonce(), 4628ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 'oauth_version': cls.version, 4638ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen } 4648ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 4658ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen defaults.update(parameters) 4668ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen parameters = defaults 4678ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 4688ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if token: 4698ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen parameters['oauth_token'] = token.key 4708ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if token.verifier: 4718ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen parameters['oauth_verifier'] = token.verifier 4728ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 4738ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return Request(http_method, http_url, parameters) 4748ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 4758ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen @classmethod 4768ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def from_token_and_callback(cls, token, callback=None, 4778ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen http_method=HTTP_METHOD, http_url=None, parameters=None): 4788ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 4798ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if not parameters: 4808ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen parameters = {} 4818ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 4828ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen parameters['oauth_token'] = token.key 4838ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 4848ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if callback: 4858ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen parameters['oauth_callback'] = callback 4868ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 4878ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return cls(http_method, http_url, parameters) 4888ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 4898ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen @staticmethod 4908ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def _split_header(header): 4918ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """Turn Authorization: header into parameters.""" 4928ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen params = {} 4938ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen parts = header.split(',') 4948ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen for param in parts: 4958ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen # Ignore realm parameter. 4968ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if param.find('realm') > -1: 4978ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen continue 4988ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen # Remove whitespace. 4998ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen param = param.strip() 5008ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen # Split key-value. 5018ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen param_parts = param.split('=', 1) 5028ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen # Remove quotes and unescape the value. 5038ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen params[param_parts[0]] = urllib.unquote(param_parts[1].strip('\"')) 5048ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return params 5058ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 5068ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen @staticmethod 5078ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def _split_url_string(param_str): 5088ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """Turn URL string into parameters.""" 5098ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen parameters = parse_qs(param_str, keep_blank_values=False) 5108ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen for k, v in parameters.iteritems(): 5118ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen parameters[k] = urllib.unquote(v[0]) 5128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return parameters 5138ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 5148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 5158ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenclass Client(httplib2.Http): 5168ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """OAuthClient is a worker to attempt to execute a request.""" 5178ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 5188ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def __init__(self, consumer, token=None, cache=None, timeout=None, 5198ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen proxy_info=None): 5208ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 5218ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if consumer is not None and not isinstance(consumer, Consumer): 5228ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen raise ValueError("Invalid consumer.") 5238ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 5248ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if token is not None and not isinstance(token, Token): 5258ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen raise ValueError("Invalid token.") 5268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 5278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen self.consumer = consumer 5288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen self.token = token 5298ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen self.method = SignatureMethod_HMAC_SHA1() 5308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 5318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen httplib2.Http.__init__(self, cache=cache, timeout=timeout, 5328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen proxy_info=proxy_info) 5338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 5348ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def set_signature_method(self, method): 5358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if not isinstance(method, SignatureMethod): 5368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen raise ValueError("Invalid signature method.") 5378ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 5388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen self.method = method 5398ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 5408ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def request(self, uri, method="GET", body=None, headers=None, 5418ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen redirections=httplib2.DEFAULT_MAX_REDIRECTS, connection_type=None): 5428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen DEFAULT_CONTENT_TYPE = 'application/x-www-form-urlencoded' 5438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 5448ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if not isinstance(headers, dict): 5458ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen headers = {} 5468ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 5478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen is_multipart = method == 'POST' and headers.get('Content-Type', 5488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen DEFAULT_CONTENT_TYPE) != DEFAULT_CONTENT_TYPE 5498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 5508ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if body and method == "POST" and not is_multipart: 5518ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen parameters = dict(parse_qsl(body)) 5528ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen else: 5538ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen parameters = None 5548ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 5558ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen req = Request.from_consumer_and_token(self.consumer, 5568ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen token=self.token, http_method=method, http_url=uri, 5578ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen parameters=parameters) 5588ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 5598ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen req.sign_request(self.method, self.consumer, self.token) 5608ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 5618ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if method == "POST": 5628ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen headers['Content-Type'] = headers.get('Content-Type', 5638ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen DEFAULT_CONTENT_TYPE) 5648ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if is_multipart: 5658ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen headers.update(req.to_header()) 5668ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen else: 5678ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen body = req.to_postdata() 5688ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen elif method == "GET": 5698ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen uri = req.to_url() 5708ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen else: 5718ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen headers.update(req.to_header()) 5728ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 5738ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return httplib2.Http.request(self, uri, method=method, body=body, 5748ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen headers=headers, redirections=redirections, 5758ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen connection_type=connection_type) 5768ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 5778ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 5788ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenclass Server(object): 5798ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """A skeletal implementation of a service provider, providing protected 5808ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen resources to requests from authorized consumers. 5818ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 5828ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen This class implements the logic to check requests for authorization. You 5838ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen can use it with your web server or web framework to protect certain 5848ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen resources with OAuth. 5858ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """ 5868ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 5878ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen timestamp_threshold = 300 # In seconds, five minutes. 5888ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen version = VERSION 5898ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen signature_methods = None 5908ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 5918ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def __init__(self, signature_methods=None): 5928ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen self.signature_methods = signature_methods or {} 5938ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 5948ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def add_signature_method(self, signature_method): 5958ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen self.signature_methods[signature_method.name] = signature_method 5968ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return self.signature_methods 5978ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 5988ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def verify_request(self, request, consumer, token): 5998ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """Verifies an api call and checks all the parameters.""" 6008ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 6018ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen version = self._get_version(request) 6028ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen self._check_signature(request, consumer, token) 6038ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen parameters = request.get_nonoauth_parameters() 6048ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return parameters 6058ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 6068ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def build_authenticate_header(self, realm=''): 6078ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """Optional support for the authenticate header.""" 6088ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return {'WWW-Authenticate': 'OAuth realm="%s"' % realm} 6098ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 6108ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def _get_version(self, request): 6118ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """Verify the correct version request for this server.""" 6128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen try: 6138ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen version = request.get_parameter('oauth_version') 6148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen except: 6158ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen version = VERSION 6168ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 6178ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if version and version != self.version: 6188ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen raise Error('OAuth version %s not supported.' % str(version)) 6198ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 6208ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return version 6218ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 6228ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def _get_signature_method(self, request): 6238ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """Figure out the signature with some defaults.""" 6248ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen try: 6258ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen signature_method = request.get_parameter('oauth_signature_method') 6268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen except: 6278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen signature_method = SIGNATURE_METHOD 6288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 6298ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen try: 6308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen # Get the signature method object. 6318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen signature_method = self.signature_methods[signature_method] 6328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen except: 6338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen signature_method_names = ', '.join(self.signature_methods.keys()) 6348ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen raise Error('Signature method %s not supported try one of the following: %s' % (signature_method, signature_method_names)) 6358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 6368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return signature_method 6378ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 6388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def _get_verifier(self, request): 6398ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return request.get_parameter('oauth_verifier') 6408ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 6418ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def _check_signature(self, request, consumer, token): 6428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen timestamp, nonce = request._get_timestamp_nonce() 6438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen self._check_timestamp(timestamp) 6448ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen signature_method = self._get_signature_method(request) 6458ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 6468ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen try: 6478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen signature = request.get_parameter('oauth_signature') 6488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen except: 6498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen raise MissingSignature('Missing oauth_signature.') 6508ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 6518ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen # Validate the signature. 6528ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen valid = signature_method.check(request, consumer, token, signature) 6538ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 6548ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if not valid: 6558ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen key, base = signature_method.signing_base(request, consumer, token) 6568ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 6578ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen raise Error('Invalid signature. Expected signature base ' 6588ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 'string: %s' % base) 6598ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 6608ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen built = signature_method.sign(request, consumer, token) 6618ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 6628ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def _check_timestamp(self, timestamp): 6638ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """Verify that timestamp is recentish.""" 6648ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen timestamp = int(timestamp) 6658ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen now = int(time.time()) 6668ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen lapsed = now - timestamp 6678ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if lapsed > self.timestamp_threshold: 6688ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen raise Error('Expired timestamp: given %d and now %s has a ' 6698ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 'greater difference than threshold %d' % (timestamp, now, 6708ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen self.timestamp_threshold)) 6718ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 6728ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 6738ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenclass SignatureMethod(object): 6748ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """A way of signing requests. 6758ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 6768ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen The OAuth protocol lets consumers and service providers pick a way to sign 6778ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen requests. This interface shows the methods expected by the other `oauth` 6788ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen modules for signing requests. Subclass it and implement its methods to 6798ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen provide a new way to sign requests. 6808ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """ 6818ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 6828ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def signing_base(self, request, consumer, token): 6838ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """Calculates the string that needs to be signed. 6848ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 6858ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen This method returns a 2-tuple containing the starting key for the 6868ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen signing and the message to be signed. The latter may be used in error 6878ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen messages to help clients debug their software. 6888ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 6898ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """ 6908ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen raise NotImplementedError 6918ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 6928ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def sign(self, request, consumer, token): 6938ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """Returns the signature for the given request, based on the consumer 6948ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen and token also provided. 6958ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 6968ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen You should use your implementation of `signing_base()` to build the 6978ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen message to sign. Otherwise it may be less useful for debugging. 6988ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 6998ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """ 7008ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen raise NotImplementedError 7018ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 7028ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def check(self, request, consumer, token, signature): 7038ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """Returns whether the given signature is the correct signature for 7048ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen the given consumer and token signing the given request.""" 7058ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen built = self.sign(request, consumer, token) 7068ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return built == signature 7078ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 7088ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 7098ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenclass SignatureMethod_HMAC_SHA1(SignatureMethod): 7108ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen name = 'HMAC-SHA1' 7118ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 7128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def signing_base(self, request, consumer, token): 7138ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if request.normalized_url is None: 7148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen raise ValueError("Base URL for request is not set.") 7158ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 7168ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen sig = ( 7178ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen escape(request.method), 7188ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen escape(request.normalized_url), 7198ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen escape(request.get_normalized_parameters()), 7208ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen ) 7218ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 7228ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen key = '%s&' % escape(consumer.secret) 7238ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if token: 7248ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen key += escape(token.secret) 7258ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen raw = '&'.join(sig) 7268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return key, raw 7278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 7288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def sign(self, request, consumer, token): 7298ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """Builds the base signature string.""" 7308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen key, raw = self.signing_base(request, consumer, token) 7318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 7328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen # HMAC object. 7338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen try: 7348ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen from hashlib import sha1 as sha 7358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen except ImportError: 7368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen import sha # Deprecated 7378ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 7388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen hashed = hmac.new(key, raw, sha) 7398ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 7408ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen # Calculate the digest base 64. 7418ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return binascii.b2a_base64(hashed.digest())[:-1] 7428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 7438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 7448ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenclass SignatureMethod_PLAINTEXT(SignatureMethod): 7458ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 7468ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen name = 'PLAINTEXT' 7478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 7488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def signing_base(self, request, consumer, token): 7498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen """Concatenates the consumer key and secret with the token's 7508ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen secret.""" 7518ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen sig = '%s&' % escape(consumer.secret) 7528ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen if token: 7538ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen sig = sig + escape(token.secret) 7548ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return sig, sig 7558ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen 7568ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen def sign(self, request, consumer, token): 7578ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen key, raw = self.signing_base(request, consumer, token) 7588ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen return raw 759