Permalink
Browse files

updating to address an issue with header authentication

Essentially header authenication would fail under certain circumstances due to
the fact that optional oauth parameters, such as `oauth_verifier` were being
appended multiple times. This is now solved by parsing out these paramters
before generating the `oauth_params` property.

Additionally Token objects have been removed; they add unneeded complexity to
the wrapper. Values of tokens are simply set as attributes directly on the hook
itself. This simplifies this portion of the library and reduces the number of
classes needed without breaking existing functionality.

Finally `get_authenticated_session` has been removed and replaced by
`_get_session`. This also signals an important aspect of this update: state
of the hook was not being saved between authentication steps or even calls.
Now through the use of this new method and instantiation of the hook as an
attribute of the service wrapper, we are able to update attribtes on the
hook object, rather than re-instantiate it on each call.
  • Loading branch information...
1 parent f023e33 commit a47fb4a5ad72f14045dec6bda962522f8c861ae7 @maxcountryman maxcountryman committed May 11, 2012
Showing with 187 additions and 153 deletions.
  1. +7 −8 examples/linkedin-updates.py
  2. +3 −4 examples/twitter-timeline.py
  3. +1 −1 rauth/__init__.py
  4. +63 −45 rauth/hook.py
  5. +6 −24 rauth/oauth.py
  6. +39 −38 rauth/service.py
  7. +7 −12 tests/base.py
  8. +48 −13 tests/test_hook.py
  9. +5 −4 tests/test_oauth.py
  10. +8 −4 tests/test_service.py
@@ -2,14 +2,14 @@
LINKEDIN_API_BASE = 'http://api.linkedin.com/v1/'
-
linkedin = OAuth1Service(
name='linkedin',
consumer_key='tjm826j6uzio',
consumer_secret='1XbHsC7UxtC6EzqW',
request_token_url = 'https://api.linkedin.com/uas/oauth/requestToken',
authorize_url = 'https://api.linkedin.com/uas/oauth/authorize',
- access_token_url = 'https://api.linkedin.com/uas/oauth/accessToken')
+ access_token_url = 'https://api.linkedin.com/uas/oauth/accessToken',
+ header_auth=True)
request_token, request_token_secret = \
linkedin.get_request_token(method='GET')
@@ -31,18 +31,17 @@
response = linkedin.get(
LINKEDIN_API_BASE + 'people/~/network/updates',
- params={'type': 'SHAR',
- 'format': 'json'},
+ params={'type': 'SHAR', 'format': 'json'},
access_token=access_token,
access_token_secret=access_token_secret)
updates = response.content
for i, update in enumerate(updates['values'], 1):
current_share = update['updateContent']['person']['currentShare']
- person = current_share['author']['firstName'] + ' '
- person += current_share['author']['lastName']
- comment = current_share.get('comment', '')
+ person = current_share['author']['firstName'].encode('utf-8') + ' '
+ person += current_share['author']['lastName'].encode('utf-8')
+ comment = current_share.get('comment', '').encode('utf-8')
if not comment:
- comment = current_share['content']['description']
+ comment = current_share['content']['description'].encode('utf-8')
print '{0}. {1} - {2}'.format(i, person, comment)
@@ -7,8 +7,7 @@
consumer_secret='7WAscbSy65GmiVOvMU5EBYn5z80fhQkcFWSLMJJu4',
request_token_url='https://api.twitter.com/oauth/request_token',
access_token_url='https://api.twitter.com/oauth/access_token',
- authorize_url='https://api.twitter.com/oauth/authorize',
- header_auth=True)
+ authorize_url='https://api.twitter.com/oauth/authorize')
request_token, request_token_secret = \
twitter.get_request_token(method='GET')
@@ -18,10 +17,10 @@
print 'Visit this URL in your browser: ' + authorize_url
pin = raw_input('Enter PIN from browser: ')
-response = twitter.get_access_token('GET',
+response = twitter.get_access_token('POST',
request_token=request_token,
request_token_secret=request_token_secret,
- params={'oauth_verifier': pin})
+ data={'oauth_verifier': pin})
data = response.content
access_token = data['oauth_token']
View
@@ -6,4 +6,4 @@
'''
-__version__ = '0.4.1'
+__version__ = '0.4.2'
View
@@ -11,9 +11,9 @@
from hashlib import sha1
from urlparse import parse_qsl, urlsplit, urlunsplit
-from urllib import quote
+from urllib import quote, urlencode
-from rauth.oauth import HmacSha1Signature, Token, Consumer
+from rauth.oauth import HmacSha1Signature
class OAuth1Hook(object):
@@ -63,16 +63,19 @@ class OAuth1Hook(object):
default.
'''
OAUTH_VERSION = '1.0'
- verifier = None
+
+ oauth_callback = None
+ oauth_verifier = None
def __init__(self, consumer_key, consumer_secret, access_token=None,
access_token_secret=None, header_auth=False, signature=None):
- self.consumer = Consumer(consumer_key, consumer_secret)
+ # consumer credentials
+ self.consumer_key = consumer_key
+ self.consumer_secret = consumer_secret
- # intitialize the token and then set it if possible
- self.token = None
- if not None in (access_token, access_token_secret):
- self.token = Token(access_token, access_token_secret)
+ # access token credentials
+ self.access_token = access_token
+ self.access_token_secret = access_token_secret
self.header_auth = header_auth
@@ -89,77 +92,92 @@ def __call__(self, request):
if isinstance(request.data, list):
request.data = dict(request.data)
- # ad hoc determination of parameters datatype: we need to convert to a
- # dict when dealing with proper querystrings
- str_or_bytes = map(lambda x: type(x) in (str, bytes),
- [request.params, request.data])
- is_querystring = False not in str_or_bytes
-
- # prepare a dictionary of both params and data
- if is_querystring:
- params_and_data = \
- parse_qsl(request.params) + parse_qsl(request.data)
- params_and_data = dict(params_and_data)
- else:
- params_and_data = dict(request.params, **request.data)
-
- # set the verifier if we've been provided one
- if 'oauth_verifier' in params_and_data.keys():
- self.verifier = params_and_data['oauth_verifier']
+ # parse optional oauth parameters
+ for param in ('oauth_callback', 'oauth_verifier'):
+ self._parse_optional_param(param, request)
# generate the necessary request params
request.oauth_params = self.oauth_params
- # here we append an oauth_callback parameter if any
- if 'oauth_callback' in params_and_data.keys():
- request.oauth_params['oauth_callback'] = \
- params_and_data['oauth_callback']
-
# this is used in the Normalize Request Parameters step
request.params_and_data = request.oauth_params.copy()
# sign and add the signature to the request params
- self.signature.sign(request, self.consumer, self.token)
-
- # set the content-type if unset
- form_urlencoded = 'application/x-www-form-urlencoded'
- request.headers['content-type'] = \
- request.headers.get('content-type', form_urlencoded)
+ request.oauth_params['oauth_signature'] = \
+ self.signature.sign(request,
+ self.consumer_secret,
+ self.access_token_secret)
- # detect content-type encoding to handle POST data properly
- is_form_urlencoded = request.headers['content-type'] == form_urlencoded
+ request.params_and_data['oauth_signature'] = \
+ request.oauth_params['oauth_signature']
if self.header_auth:
# extract the domain for use as the realm
scheme, netloc, _, _, _ = urlsplit(request.url)
realm = urlunsplit((scheme, netloc, '/', '', ''))
request.headers['Authorization'] = \
- self.auth_header(request.params_and_data, realm=realm)
- elif request.method == 'POST' and is_form_urlencoded:
+ self.auth_header(request.oauth_params, realm=realm)
+ elif request.method == 'POST':
+ content_type = 'application/x-www-form-urlencoded'
+ request.headers['content-type'] = content_type
request.data = request.params_and_data
else:
request.params = request.params_and_data
# we're done with these now
del request.params_and_data
+ def _parse_optional_param(self, oauth_param, request):
+ '''Parses and sets optional oauth parameters on a request.
+
+ :param oauth_param: The OAuth parameter to parse.
+ :param request: The Request object.
+ '''
+ params_is_string = type(request.params) == str
+ data_is_string = type(request.data) == str
+ params = request.params
+ data = request.data
+
+ # special handling if we're handed a string
+ if params_is_string:
+ params = dict(parse_qsl(request.params))
+ if data_is_string:
+ data = dict(parse_qsl(request.data))
+
+ # remove any oauth parameters and set them as attributes
+ if oauth_param in params.keys():
+ setattr(self, oauth_param, params.pop(oauth_param))
+ if oauth_param in data.keys():
+ setattr(self, oauth_param, data.pop(oauth_param))
+
+ # re-encode the params or data if they were a string, without any oauth
+ # params
+ if params_is_string:
+ request.params = urlencode(params)
+
+ if data_is_string:
+ request.data = urlencode(data)
+
@property
def oauth_params(self):
'''This method handles generating the necessary URL parameters the
OAuth provider will expect.'''
oauth_params = {}
- oauth_params['oauth_consumer_key'] = self.consumer.key
+ oauth_params['oauth_consumer_key'] = self.consumer_key
oauth_params['oauth_timestamp'] = int(time.time())
oauth_params['oauth_nonce'] = sha1(str(random.random())).hexdigest()
oauth_params['oauth_version'] = self.OAUTH_VERSION
- if self.token is not None:
- oauth_params['oauth_token'] = self.token.key
+ if self.access_token is not None:
+ oauth_params['oauth_token'] = self.access_token
+
+ if self.oauth_verifier is not None:
+ oauth_params['oauth_verifier'] = self.oauth_verifier
- if self.verifier is not None:
- oauth_params['oauth_verifier'] = self.verifier
+ if self.oauth_callback is not None:
+ oauth_params['oauth_callback'] = self.oauth_callback
oauth_params['oauth_signature_method'] = self.signature.NAME
return oauth_params
View
@@ -13,23 +13,6 @@
from urllib import quote, urlencode
-class OAuthObject(object):
- '''A base class for OAuth token objects.'''
- def __init__(self, key, secret):
- self.key = key
- self.secret = secret
-
-
-class Consumer(OAuthObject):
- '''The consumer token object.'''
- pass
-
-
-class Token(OAuthObject):
- '''The access token object.'''
- pass
-
-
class SignatureMethod(object):
'''A base class for signature methods providing a set of common methods.'''
def _encode_utf8(self, s):
@@ -136,7 +119,7 @@ class HmacSha1Signature(SignatureMethod):
'''
NAME = 'HMAC-SHA1'
- def sign(self, request, consumer, token=None):
+ def sign(self, request, consumer_secret, access_token_secret=None):
'''Sign request parameters.
:param request: The request to sign.
@@ -152,19 +135,18 @@ def sign(self, request, consumer, token=None):
self._escape(params_and_data)]
# set our key
- key = self._escape(consumer.secret) + '&'
- if token is not None:
- key += self._escape(token.secret)
+ key = self._escape(consumer_secret) + '&'
+ if access_token_secret is not None:
+ key += self._escape(access_token_secret)
# build a Signature Base String
signature_base_string = '&'.join(parameters)
# hash the string with HMAC-SHA1
hashed = hmac.new(key, signature_base_string, sha1)
- # add the signature to the request
- request.params_and_data['oauth_signature'] = \
- base64.b64encode(hashed.digest())
+ # return the signature
+ return base64.b64encode(hashed.digest())
class RsaSha1Signature(SignatureMethod):
Oops, something went wrong.

0 comments on commit a47fb4a

Please sign in to comment.