4 Copyright (c) 2007 Leah Culver 6 Permission is hereby granted, free of charge, to any person obtaining a copy 7 of this software and associated documentation files (the "Software"), to deal 8 in the Software without restriction, including without limitation the rights 9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 copies of the Software, and to permit persons to whom the Software is 11 furnished to do so, subject to the following conditions: 13 The above copyright notice and this permission notice shall be included in 14 all copies or substantial portions of the Software. 16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 37 SIGNATURE_METHOD =
'PLAINTEXT' 41 """Generic exception class.""" 42 def __init__(self, message='OAuth error occured.'):
46 """Optional WWW-Authenticate header (401 error)""" 47 return {
'WWW-Authenticate':
'OAuth realm="%s"' % realm}
50 """Escape a URL including any /.""" 51 return urllib.quote(s, safe=
'~')
54 """Convert unicode to utf-8.""" 55 if isinstance(s, unicode):
56 return s.encode(
"utf-8")
61 """Get seconds since epoch (UTC).""" 62 return int(time.time())
65 """Generate pseudorandom number.""" 66 return ''.join([str(random.randint(0, 9))
for i
in range(length)])
69 """Generate pseudorandom number.""" 70 return ''.join([str(random.randint(0, 9))
for i
in range(length)])
74 """Consumer of OAuth authentication. 76 OAuthConsumer is a data type that represents the identity of the Consumer 77 via its shared secret with the Service Provider. 89 """OAuthToken is a data type that represents an End User via either an access 93 secret -- the token secret 99 callback_confirmed =
None 111 if verifier
is not None:
119 parts = urlparse.urlparse(self.
callback)
120 scheme, netloc, path, params, query, fragment = parts[:6]
122 query =
'%s&oauth_verifier=%s' % (query, self.
verifier)
124 query =
'oauth_verifier=%s' % self.
verifier 125 return urlparse.urlunparse((scheme, netloc, path, params,
131 'oauth_token': self.
key,
132 'oauth_token_secret': self.
secret,
136 return urllib.urlencode(data)
139 """ Returns a token from something like: 140 oauth_token_secret=xxx&oauth_token=xxx 142 print "******* %s" % s.__class__
145 key = re.search(
"<oauth_token>(\w.+)</oauth_token>", s).
groups()[0]
146 print "@@@@@@ key: %s" %key
147 secret = re.search(
"<oauth_token_secret>(\w.+)</oauth_token_secret>", s).
groups()[0]
148 print "@@@@@@ secret: %s" % secret
152 from_string = staticmethod(from_string)
159 """OAuthRequest represents the request and can be serialized. 164 - oauth_signature_method 170 ... any additional parameters, as defined by the Service Provider. 173 http_method = HTTP_METHOD
177 def __init__(self, http_method=HTTP_METHOD, http_url=None, parameters=None):
189 raise OAuthError(
'Parameter not found: %s' % parameter)
196 """Get any non-OAuth parameters.""" 198 for k, v
in self.parameters.iteritems():
200 if k.find(
'oauth_') < 0:
205 """Serialize as a header for an HTTPAuth request.""" 206 auth_header =
'OAuth realm="%s"' % realm
209 for k, v
in self.parameters.iteritems():
210 if k[:6] ==
'oauth_':
211 auth_header +=
', %s="%s"' % (k,
escape(str(v)))
212 return {
'Authorization': auth_header}
215 """Serialize as post data for a POST request.""" 216 return '&'.join([
'%s=%s' % (
escape(str(k)),
escape(str(v))) \
217 for k, v
in self.parameters.iteritems()])
220 """Serialize as a URL for a GET request.""" 224 """Return a string that contains the parameters that must be signed.""" 228 del params[
'oauth_signature']
233 for k,v
in params.items()]
237 return '&'.join([
'%s=%s' % (k, v)
for k, v
in key_values])
240 """Uppercases the http method.""" 241 return self.http_method.upper()
244 """Parses the URL and rebuilds it to be scheme://host/path.""" 245 parts = urlparse.urlparse(self.
http_url)
246 scheme, netloc, path = parts[:3]
248 if scheme ==
'http' and netloc[-3:] ==
':80':
250 elif scheme ==
'https' and netloc[-4:] ==
':443':
252 return '%s://%s%s' % (scheme, netloc, path)
255 """Set the signature parameter to the result of build_signature.""" 258 signature_method.get_name())
264 """Calls the build signature method within the signature method.""" 265 return signature_method.build_signature(self, consumer, token)
267 def from_request(http_method, http_url, headers=None, parameters=None,
269 """Combines multiple parameter sources.""" 270 if parameters
is None:
274 if headers
and 'Authorization' in headers:
275 auth_header = headers[
'Authorization']
277 if auth_header[:6] ==
'OAuth ':
278 auth_header = auth_header[6:]
281 header_params = OAuthRequest._split_header(auth_header)
282 parameters.update(header_params)
284 raise OAuthError(
'Unable to parse OAuth parameters from ' 285 'Authorization header.')
289 query_params = OAuthRequest._split_url_string(query_string)
290 parameters.update(query_params)
293 param_str = urlparse.urlparse(http_url)[4]
294 url_params = OAuthRequest._split_url_string(param_str)
295 parameters.update(url_params)
301 from_request = staticmethod(from_request)
304 callback=
None, verifier=
None, http_method=HTTP_METHOD,
305 http_url=
None, parameters=
None):
310 'oauth_consumer_key': oauth_consumer.key,
313 'oauth_version': OAuthRequest.version,
316 defaults.update(parameters)
317 parameters = defaults
320 parameters[
'oauth_token'] = token.key
322 parameters[
'oauth_callback'] = token.callback
325 parameters[
'oauth_verifier'] = verifier
328 parameters[
'oauth_callback'] = callback
331 from_consumer_and_token = staticmethod(from_consumer_and_token)
334 http_url=
None, parameters=
None):
338 parameters[
'oauth_token'] = token.key
341 parameters[
'oauth_callback'] = callback
344 from_token_and_callback = staticmethod(from_token_and_callback)
347 """Turn Authorization: header into parameters.""" 349 parts = header.split(
',')
352 if param.find(
'realm') > -1:
355 param = param.strip()
357 param_parts = param.split(
'=', 1)
359 params[param_parts[0]] = urllib.unquote(param_parts[1].strip(
'\"'))
361 _split_header = staticmethod(_split_header)
364 """Turn URL string into parameters.""" 365 parameters = cgi.parse_qs(param_str, keep_blank_values=
False)
366 for k, v
in parameters.iteritems():
367 parameters[k] = urllib.unquote(v[0])
369 _split_url_string = staticmethod(_split_url_string)
372 """A worker to check the validity of a request against a data store.""" 373 timestamp_threshold = 300
375 signature_methods =
None 378 def __init__(self, data_store=None, signature_methods=None):
393 """Processes a request_token request and returns the 394 request token on success. 398 token = self.
_get_token(oauth_request,
'request')
409 token = self.data_store.fetch_request_token(consumer, callback)
413 """Processes an access_token request and returns the 414 access token on success. 423 token = self.
_get_token(oauth_request,
'request')
425 new_token = self.data_store.fetch_access_token(consumer, token, verifier)
429 """Verifies an api call and checks all the parameters.""" 434 token = self.
_get_token(oauth_request,
'access')
436 parameters = oauth_request.get_nonoauth_parameters()
437 return consumer, token, parameters
440 """Authorize a request token.""" 441 return self.data_store.authorize_request_token(token, user)
444 """Get the callback URL.""" 445 return oauth_request.get_parameter(
'oauth_callback')
448 """Optional support for the authenticate header.""" 449 return {
'WWW-Authenticate':
'OAuth realm="%s"' % realm}
452 """Verify the correct version request for this server.""" 454 version = oauth_request.get_parameter(
'oauth_version')
457 if version
and version != self.
version:
458 raise OAuthError(
'OAuth version %s not supported.' % str(version))
462 """Figure out the signature with some defaults.""" 464 signature_method = oauth_request.get_parameter(
465 'oauth_signature_method')
467 signature_method = SIGNATURE_METHOD
472 signature_method_names =
', '.join(self.signature_methods.keys())
473 raise OAuthError(
'Signature method %s not supported try one of the ' 474 'following: %s' % (signature_method, signature_method_names))
476 return signature_method
479 consumer_key = oauth_request.get_parameter(
'oauth_consumer_key')
480 consumer = self.data_store.lookup_consumer(consumer_key)
486 """Try to find the token for the provided request token key.""" 487 token_field = oauth_request.get_parameter(
'oauth_token')
488 token = self.data_store.lookup_token(token_type, token_field)
490 raise OAuthError(
'Invalid %s token: %s' % (token_type, token_field))
494 return oauth_request.get_parameter(
'oauth_verifier')
497 timestamp, nonce = oauth_request._get_timestamp_nonce()
502 signature = oauth_request.get_parameter(
'oauth_signature')
506 valid_sig = signature_method.check_signature(oauth_request, consumer,
509 key, base = signature_method.build_signature_base_string(
510 oauth_request, consumer, token)
511 raise OAuthError(
'Invalid signature. Expected signature base ' 513 built = signature_method.build_signature(oauth_request, consumer, token)
516 """Verify that timestamp is recentish.""" 517 timestamp = int(timestamp)
518 now = int(time.time())
519 lapsed = abs(now - timestamp)
521 raise OAuthError(
'Expired timestamp: given %d and now %s has a ' 522 'greater difference than threshold %d' %
526 """Verify that the nonce is uniqueish.""" 527 nonce = self.data_store.lookup_nonce(consumer, token, nonce)
529 raise OAuthError(
'Nonce already used: %s' % str(nonce))
533 """OAuthClient is a worker to attempt to execute a request.""" 539 self.
token = oauth_token
549 raise NotImplementedError
553 raise NotImplementedError
556 """-> Some protected resource.""" 557 raise NotImplementedError
561 """A database abstraction used to lookup consumers and tokens.""" 564 """-> OAuthConsumer.""" 565 raise NotImplementedError
569 raise NotImplementedError
573 raise NotImplementedError
577 raise NotImplementedError
581 raise NotImplementedError
585 raise NotImplementedError
589 """A strategy class that implements a signature method.""" 592 raise NotImplementedError
595 """-> str key, str raw.""" 596 raise NotImplementedError
600 raise NotImplementedError
604 return built == signature
614 escape(oauth_request.get_normalized_http_method()),
615 escape(oauth_request.get_normalized_http_url()),
616 escape(oauth_request.get_normalized_parameters()),
619 key =
'%s&' %
escape(consumer.secret)
621 key +=
escape(token.secret)
626 """Builds the base signature string.""" 633 hashed = hmac.new(key, raw, hashlib.sha1)
636 hashed = hmac.new(key, raw, sha)
639 return binascii.b2a_base64(hashed.digest())[:-1]
648 """Concatenates the consumer key and secret.""" 649 sig =
'%s&' %
escape(consumer.secret)
651 sig = sig +
escape(token.secret)
def set_parameter(self, parameter, value)
def fetch_request_token(self, oauth_request)
def get_normalized_http_url(self)
def _check_nonce(self, consumer, token, nonce)
def check_signature(self, oauth_request, consumer, token, signature)
def get_callback(self, oauth_request)
def __init__(self, http_method=HTTP_METHOD, http_url=None, parameters=None)
def fetch_access_token(self, oauth_consumer, oauth_token, oauth_verifier)
static const OptionGroupDef groups[]
def generate_nonce(length=8)
def __init__(self, key, secret)
def authorize_token(self, token, user)
def get_callback_url(self)
def build_signature_base_string(self, oauth_request, consumer, token)
def fetch_access_token(self, oauth_request)
def build_signature(self, oauth_request, oauth_consumer, oauth_token)
def fetch_access_token(self, oauth_request)
def build_authenticate_header(realm='')
def build_signature(self, oauth_request, consumer, token)
def to_header(self, realm='')
def __init__(self, data_store=None, signature_methods=None)
def lookup_nonce(self, oauth_consumer, oauth_token, nonce)
def _get_consumer(self, oauth_request)
def authorize_request_token(self, oauth_token, user)
def _get_verifier(self, oauth_request)
def sign_request(self, signature_method, consumer, token)
def __init__(self, message='OAuth error occured.')
def get_nonoauth_parameters(self)
def fetch_request_token(self, oauth_request)
def __init__(self, oauth_consumer, oauth_token)
def generate_verifier(length=8)
def lookup_token(self, oauth_consumer, token_type, token_token)
def set_verifier(self, verifier=None)
def get_parameter(self, parameter)
def access_resource(self, oauth_request)
def get_normalized_parameters(self)
def fetch_request_token(self, oauth_consumer, oauth_callback)
def __init__(self, key, secret)
def _get_timestamp_nonce(self)
def _get_signature_method(self, oauth_request)
def set_callback(self, callback)
def build_signature_base_string(self, oauth_request, oauth_consumer, oauth_token)
def add_signature_method(self, signature_method)
def build_signature(self, oauth_request, consumer, token)
def verify_request(self, oauth_request)
def build_signature(self, signature_method, consumer, token)
def lookup_consumer(self, key)
def build_authenticate_header(self, realm='')
def set_data_store(self, data_store)
def get_normalized_http_method(self)
def _check_timestamp(self, timestamp)
def _get_version(self, oauth_request)
def _get_token(self, oauth_request, token_type='access')
def build_signature_base_string(self, oauth_request, consumer, token)
def _check_signature(self, oauth_request, consumer, token)