Package scapi :: Module authentication
[hide private]
[frames] | no frames]

Source Code for Module scapi.authentication

  1  ##    SouncCloudAPI implements a Python wrapper around the SoundCloud RESTful 
  2  ##    API 
  3  ## 
  4  ##    Copyright (C) 2008  Diez B. Roggisch 
  5  ##    Contact mailto:deets@soundcloud.com 
  6  ## 
  7  ##    This library is free software; you can redistribute it and/or 
  8  ##    modify it under the terms of the GNU Lesser General Public 
  9  ##    License as published by the Free Software Foundation; either 
 10  ##    version 2.1 of the License, or (at your option) any later version. 
 11  ## 
 12  ##    This library is distributed in the hope that it will be useful, 
 13  ##    but WITHOUT ANY WARRANTY; without even the implied warranty of 
 14  ##    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
 15  ##    Lesser General Public License for more details. 
 16  ## 
 17  ##    You should have received a copy of the GNU Lesser General Public 
 18  ##    License along with this library; if not, write to the Free Software 
 19  ##    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 20   
 21  import base64 
 22  import time, random 
 23  import urlparse 
 24  import hmac 
 25  import hashlib 
 26  from scapi.util import escape 
 27  import logging 
 28   
 29   
 30  USE_DOUBLE_ESCAPE_HACK = True 
 31  """ 
 32  There seems to be an uncertainty on the way 
 33  parameters are to be escaped. For now, this 
 34  variable switches between two escaping mechanisms. 
 35   
 36  If True, the passed parameters - GET or POST - are 
 37  escaped *twice*. 
 38  """ 
 39   
 40  logger = logging.getLogger(__name__) 
 41   
42 -class OAuthSignatureMethod_HMAC_SHA1(object):
43 44 FORBIDDEN = ['realm', 'oauth_signature'] 45
46 - def get_name(self):
47 return 'HMAC-SHA1'
48
49 - def build_signature(self, request, parameters, consumer_secret, token_secret, oauth_parameters):
50 if logger.level == logging.DEBUG: 51 logger.debug("request: %r", request) 52 logger.debug("parameters: %r", parameters) 53 logger.debug("consumer_secret: %r", consumer_secret) 54 logger.debug("token_secret: %r", token_secret) 55 logger.debug("oauth_parameters: %r", oauth_parameters) 56 57 58 temp = {} 59 temp.update(oauth_parameters) 60 for p in self.FORBIDDEN: 61 if p in temp: 62 del temp[p] 63 if parameters is not None: 64 temp.update(parameters) 65 sig = ( 66 escape(self.get_normalized_http_method(request)), 67 escape(self.get_normalized_http_url(request)), 68 self.get_normalized_parameters(temp), # these are escaped in the method already 69 ) 70 71 key = '%s&' % consumer_secret 72 if token_secret is not None: 73 key += token_secret 74 raw = '&'.join(sig) 75 logger.debug("raw basestring: %s", raw) 76 logger.debug("key: %s", key) 77 # hmac object 78 hashed = hmac.new(key, raw, hashlib.sha1) 79 # calculate the digest base 64 80 signature = escape(base64.b64encode(hashed.digest())) 81 return signature
82 83
84 - def get_normalized_http_method(self, request):
85 return request.get_method().upper()
86 87 88 # parses the url and rebuilds it to be scheme://host/path
89 - def get_normalized_http_url(self, request):
90 url = request.get_full_url() 91 parts = urlparse.urlparse(url) 92 url_string = '%s://%s%s' % (parts.scheme, parts.netloc, parts.path) 93 return url_string
94 95
96 - def get_normalized_parameters(self, params):
97 if params is None: 98 params = {} 99 try: 100 # exclude the signature if it exists 101 del params['oauth_signature'] 102 except: 103 pass 104 key_values = [] 105 106 for key, values in params.iteritems(): 107 if isinstance(values, file): 108 continue 109 if isinstance(values, (int, long, float)): 110 values = str(values) 111 if isinstance(values, (list, tuple)): 112 values = [str(v) for v in values] 113 if isinstance(values, basestring): 114 values = [values] 115 if USE_DOUBLE_ESCAPE_HACK and not key.startswith("ouath"): 116 key = escape(key) 117 for v in values: 118 v = v.encode("utf-8") 119 key = key.encode("utf-8") 120 if USE_DOUBLE_ESCAPE_HACK and not key.startswith("oauth"): 121 # this is a dirty hack to make the 122 # thing work with the current server-side 123 # implementation. Or is it by spec? 124 v = escape(v) 125 key_values.append(escape("%s=%s" % (key, v))) 126 # sort lexicographically, first after key, then after value 127 key_values.sort() 128 # combine key value pairs in string 129 return escape('&').join(key_values)
130 131
132 -class OAuthAuthenticator(object):
133 OAUTH_API_VERSION = '1.0' 134 AUTHORIZATION_HEADER = "Authorization" 135
136 - def __init__(self, consumer, consumer_secret, token, secret, signature_method=OAuthSignatureMethod_HMAC_SHA1()):
137 self._consumer, self._token, self._secret = consumer, token, secret 138 self._consumer_secret = consumer_secret 139 self._signature_method = signature_method 140 random.seed()
141 142
143 - def augment_request(self, req, parameters, use_multipart=False, oauth_callback=None, oauth_verifier=None):
144 oauth_parameters = { 145 'oauth_consumer_key': self._consumer, 146 'oauth_timestamp': self.generate_timestamp(), 147 'oauth_nonce': self.generate_nonce(), 148 'oauth_version': self.OAUTH_API_VERSION, 149 'oauth_signature_method' : self._signature_method.get_name(), 150 #'realm' : "http://soundcloud.com", 151 } 152 if self._token is not None: 153 oauth_parameters['oauth_token'] = self._token 154 155 if oauth_callback is not None: 156 oauth_parameters['oauth_callback'] = oauth_callback 157 158 if oauth_verifier is not None: 159 oauth_parameters['oauth_verifier'] = oauth_verifier 160 161 # in case we upload large files, we don't 162 # sign the request over the parameters 163 if use_multipart: 164 parameters = None 165 166 oauth_parameters['oauth_signature'] = self._signature_method.build_signature(req, 167 parameters, 168 self._consumer_secret, 169 self._secret, 170 oauth_parameters) 171 def to_header(d): 172 return ",".join('%s="%s"' % (key, value) for key, value in sorted(oauth_parameters.items()))
173 174 req.add_header(self.AUTHORIZATION_HEADER, "OAuth %s" % to_header(oauth_parameters))
175
176 - def generate_timestamp(self):
177 return int(time.time())# * 1000.0)
178
179 - def generate_nonce(self, length=8):
180 return ''.join(str(random.randint(0, 9)) for i in range(length))
181 182
183 -class BasicAuthenticator(object):
184
185 - def __init__(self, user, password, consumer, consumer_secret):
186 self._base64string = base64.encodestring("%s:%s" % (user, password))[:-1] 187 self._x_auth_header = 'OAuth oauth_consumer_key="%s" oauth_consumer_secret="%s"' % (consumer, consumer_secret)
188
189 - def augment_request(self, req, parameters):
190 req.add_header("Authorization", "Basic %s" % self._base64string) 191 req.add_header("X-Authorization", self._x_auth_header)
192