Skip to content

Commit

Permalink
updating docstrings, simplifying OAuth 2.0 auth methods
Browse files Browse the repository at this point in the history
  • Loading branch information
maxcountryman committed Mar 8, 2012
1 parent 169ebbe commit a38ca1a
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 22 deletions.
3 changes: 2 additions & 1 deletion tests/test_service.py
Expand Up @@ -50,7 +50,8 @@ def test_init_with_access_token(self):


def test_get_authorize_url(self): def test_get_authorize_url(self):
authorize_url = self.service.get_authorize_url() authorize_url = self.service.get_authorize_url()
expected_url = 'http://example.com/authorize?client_id=123' expected_url = \
'http://example.com/authorize?response_type=code&client_id=123'
self.assertEqual(expected_url, authorize_url) self.assertEqual(expected_url, authorize_url)


@patch.object(requests.Session, 'request') @patch.object(requests.Session, 'request')
Expand Down
2 changes: 1 addition & 1 deletion webauth/__init__.py
Expand Up @@ -6,4 +6,4 @@
''' '''




__version__ = '0.1.3' __version__ = '0.1.4'
15 changes: 14 additions & 1 deletion webauth/hook.py
Expand Up @@ -51,6 +51,15 @@ class OAuth1Hook(object):
Additionally some services will make use of header authentication. This is Additionally some services will make use of header authentication. This is
provided by passing :class:`__init__` the `auth_header` parameter as provided by passing :class:`__init__` the `auth_header` parameter as
`True`. `True`.
:param consumer_key: Client consumer key.
:param consumer_secret: Client consumer secret.
:param access_token: Access token key.
:param access_token_secret: Access token secret.
:param header_auth: Authenication via header, defauls to False.
:param signature: A signature method used to sign request parameters.
Defaults to None. If None the `HmacSha1Signature` method is used as
default.
''' '''
OAUTH_VERSION = '1.0' OAUTH_VERSION = '1.0'


Expand Down Expand Up @@ -131,7 +140,11 @@ def generate_oauth_params(self):
return oauth_params return oauth_params


def generate_authorization_header(self, oauth_params, realm=None): def generate_authorization_header(self, oauth_params, realm=None):
'''This method constructs an authorization header.''' '''This method constructs an authorization header.
:param oauth_params: The OAuth parameters to be added to the header.
:param realm: The authentication realm. Defaults to None.
'''
auth_header = 'OAuth realm="{0}"'.format(realm) auth_header = 'OAuth realm="{0}"'.format(realm)
params = '' params = ''
for k, v in oauth_params.items(): for k, v in oauth_params.items():
Expand Down
19 changes: 16 additions & 3 deletions webauth/oauth.py
Expand Up @@ -40,11 +40,17 @@ def _encode_utf8(self, s):
return unicode(s, 'utf-8').encode('utf-8') return unicode(s, 'utf-8').encode('utf-8')


def _escape(self, s): def _escape(self, s):
'''Escapes a string, ensuring it is encoded as a UTF-8 octet.''' '''Escapes a string, ensuring it is encoded as a UTF-8 octet.
:param s: A string to be encoded.
'''
return quote(self._encode_utf8(s), safe='~') return quote(self._encode_utf8(s), safe='~')


def _remove_qs(self, url): def _remove_qs(self, url):
'''Removes a query string from a URL before signing.''' '''Removes a query string from a URL before signing.
:param url: The URL to strip.
'''
# split 'em up # split 'em up
scheme, netloc, path, query, fragment = urlsplit(url) scheme, netloc, path, query, fragment = urlsplit(url)


Expand All @@ -69,6 +75,8 @@ def _normalize_request_parameters(self, request):
Otherwise we build a series intermediary lists of tuples depending on Otherwise we build a series intermediary lists of tuples depending on
the type of `request.params` and `request.data`. the type of `request.params` and `request.data`.
:param request: The request object that will be normalized.
''' '''
if type(request.params) != str and type(request.data) != str: if type(request.params) != str and type(request.data) != str:
# if neither params nor data are a string, i.e. both are dicts # if neither params nor data are a string, i.e. both are dicts
Expand Down Expand Up @@ -131,7 +139,12 @@ class HmacSha1Signature(SignatureMethod):
NAME = 'HMAC-SHA1' NAME = 'HMAC-SHA1'


def sign(self, request, consumer, token=None): def sign(self, request, consumer, token=None):
'''Sign request parameters.''' '''Sign request parameters.
:param request: The request to sign.
:param consumer: The consumer token object.
:param token: The access token object.
'''


# the necessary parameters we'll sign # the necessary parameters we'll sign
url = self._remove_qs(request.url) url = self._remove_qs(request.url)
Expand Down
141 changes: 125 additions & 16 deletions webauth/service.py
Expand Up @@ -15,6 +15,7 @@




def _parse_response(response): def _parse_response(response):
'''Attempts to coerce response.content into a dictionary.'''
if isinstance(response.content, str): if isinstance(response.content, str):
try: try:
content = json.loads(response.content) content = json.loads(response.content)
Expand All @@ -27,7 +28,41 @@ def _parse_response(response):




class OAuth2Service(object): class OAuth2Service(object):
'''An OAuth 2.0 Service container.''' '''An OAuth 2.0 Service container.
This class is similar in nature to the OAuth1Service container but does
not make use of a request's hook. Instead the OAuth 2.0 spec is currently
simple enough that we can wrap it around requests directly.
You might intialize :class:`OAuth2Service` something like this::
service = OAuth2Service(
name='example',
consumer_key='123',
consumer_secret='456',
access_token_url='http://example.com/token',
authorize_url='http://example.com/authorize')
Given the simplicity of OAuth 2.0 now this object `service` can be used to
retrieve an access token in two steps::
# the return URL is used to validate the request
url = service.get_authorize_url(redirect_uri='http://example.com/',
response_type='code')
# once the above URL is consumed by a client we can ask for an access
# token. note that the code is retrieved from the redirect URL above,
# as set by the provider
token = service.get_access_token(code='foobar',
grant_type='authorization_code',
redirect_uri='http://example.com/')
:param name: The service name.
:param consumer_key: Client consumer key.
:param consumer_secret: Client consumer secret.
:param access_token_url: Access token endpoint.
:param authorize_url: Authorize endpoint.
:param access_token: An access token, defaults to None.
'''
def __init__(self, name, consumer_key, consumer_secret, access_token_url, def __init__(self, name, consumer_key, consumer_secret, access_token_url,
authorize_url, access_token=None): authorize_url, access_token=None):
self.name = name self.name = name
Expand All @@ -42,14 +77,35 @@ def __init__(self, name, consumer_key, consumer_secret, access_token_url,
if access_token is not None: if access_token is not None:
self.access_token = access_token self.access_token = access_token


def get_authorize_url(self, **params): def get_authorize_url(self, response_type=None, **params):
'''Returns a proper authorize URL.''' '''Returns a proper authorize URL.
:param reponse_type: The response type, usually 'code'. Defaults to
None, if set to None, appends 'response_type=code' to the request
querystring.
:param **params: Additional arguments to be added to the request
querystring.
'''
# defaults to 'code'
if response_type is None:
params.update({'response_type': 'code'})

params.update({'client_id': self.consumer_key}) params.update({'client_id': self.consumer_key})
params = '?' + urlencode(params) params = '?' + urlencode(params)
return self.authorize_url + params return self.authorize_url + params


def get_access_token(self, **data): def get_access_token(self, grant_type=None, **data):
'''Retrieves the access token.''' '''Retrieves the access token.
:param grant_type: The response type, usually 'authorization_code'.
Defaults to None, if set to None, appends
'grant_type=authorization_code' to the request body.
:param **data: Arguments to be passed in the body of the request.
'''
# defaults to authorization_code
if grant_type is None:
data.update({'grant_type': 'authorization_code'})

data.update(dict(client_id=self.consumer_key, data.update(dict(client_id=self.consumer_key,
client_secret=self.consumer_secret)) client_secret=self.consumer_secret))


Expand All @@ -63,7 +119,19 @@ def get_access_token(self, **data):


def request(self, http_method, url, access_token=None, **params): def request(self, http_method, url, access_token=None, **params):
'''Sends a request to an OAuth 2.0 endpoint, properly wrapped around '''Sends a request to an OAuth 2.0 endpoint, properly wrapped around
requests.''' requests.
The first time an access token is provided it will be saved on the
object for convenience.
:param http_method: A string representation of the HTTP method to be
used.
:param url: The resource to be requested.
:param access_token: The access token as returned by
:class:`get_access_token`
:param **params: Additional arguments to be added to the request
querystring.
'''
if access_token is None and self.access_token is None: if access_token is None and self.access_token is None:
raise ValueError('Access token must be set!') raise ValueError('Access token must be set!')
elif access_token is not None: elif access_token is not None:
Expand All @@ -83,14 +151,14 @@ class OAuth1Service(object):
'''An OAuth 1.0/a Service container. '''An OAuth 1.0/a Service container.
This class provides a container for an OAuth Service provider. It utilizes This class provides a container for an OAuth Service provider. It utilizes
the OAuthHook module which in turn is hooked into Python Requests. the OAuthHook object which in turn is hooked into Python Requests. This
Primarily this object can be used to provide a clean interface to provider object can be used to streamline the process of authenticating with and
endpoints and helps streamline the process of making OAuth requests. using an OAuth 1.0/a service provider.
You might intialize :class:`OAuthService` something like this:: You might intialize :class:`OAuth1Service` something like this::
service = OAuth1Service( service = OAuth1Service(
'example', name='example',
consumer_key='123', consumer_key='123',
consumer_secret='456', consumer_secret='456',
request_token_url='http://example.com/request_token', request_token_url='http://example.com/request_token',
Expand Down Expand Up @@ -120,6 +188,14 @@ class OAuth1Service(object):
Finally the :class:`get_authenticated_session` method returns a wrapped Finally the :class:`get_authenticated_session` method returns a wrapped
session and can be used once the access token has been made available. session and can be used once the access token has been made available.
This provides simple access to the providers endpoints. This provides simple access to the providers endpoints.
:param name: The service name.
:param consumer_key: Client consumer key.
:param consumer_secret: Client consumer secret.
:param request_token_url: Request token endpoint.
:param access_token_url: Access token endpoint.
:param authorize_url: Authorize endpoint.
:param header_auth: Authenication via header, defauls to False.
''' '''
def __init__(self, name, consumer_key, consumer_secret, request_token_url, def __init__(self, name, consumer_key, consumer_secret, request_token_url,
access_token_url, authorize_url, header_auth=False): access_token_url, authorize_url, header_auth=False):
Expand All @@ -128,6 +204,7 @@ def __init__(self, name, consumer_key, consumer_secret, request_token_url,
self.consumer_key = consumer_key self.consumer_key = consumer_key
self.consumer_secret = consumer_secret self.consumer_secret = consumer_secret


# authorization endpoints
self.request_token_url = request_token_url self.request_token_url = request_token_url
self.access_token_url = access_token_url self.access_token_url = access_token_url
self.authorize_url = authorize_url self.authorize_url = authorize_url
Expand All @@ -137,14 +214,23 @@ def __init__(self, name, consumer_key, consumer_secret, request_token_url,


def _construct_session(self, **kwargs): def _construct_session(self, **kwargs):
'''Construct the request session, supplying the consumer key and '''Construct the request session, supplying the consumer key and
secret.''' secret.
:param **kwargs: Extra arguments to be passed to the OAuth1Hook
constructor.
'''
hook = OAuth1Hook(consumer_key=self.consumer_key, hook = OAuth1Hook(consumer_key=self.consumer_key,
consumer_secret=self.consumer_secret, consumer_secret=self.consumer_secret,
**kwargs) **kwargs)
return requests.session(hooks={'pre_request': hook}) return requests.session(hooks={'pre_request': hook})


def get_request_token(self, http_method, **data): def get_request_token(self, http_method, **data):
'''Gets a request token from the request token endpoint.''' '''Gets a request token from the request token endpoint.
:param http_method: A string representation of the HTTP method to be
used.
:param **data: Arguments to be passed in the body of the request.
'''
auth_session = \ auth_session = \
self._construct_session(header_auth=self.header_auth) self._construct_session(header_auth=self.header_auth)


Expand All @@ -159,14 +245,30 @@ def get_request_token(self, http_method, **data):
return data['oauth_token'], data['oauth_token_secret'] return data['oauth_token'], data['oauth_token_secret']


def get_authorize_url(self, request_token, **params): def get_authorize_url(self, request_token, **params):
'''Returns a proper authorize URL.''' '''Returns a proper authorize URL.
:param request_token: The request token as returned by
:class:`get_request_token`.
:param **params: Additional arguments to be added to the request
querystring.
'''
params.update({'oauth_token': quote(request_token)}) params.update({'oauth_token': quote(request_token)})
params = '?' + urlencode(params) params = '?' + urlencode(params)
return self.authorize_url + params return self.authorize_url + params


def get_access_token(self, request_token, request_token_secret, def get_access_token(self, request_token, request_token_secret,
http_method, **params): http_method, **params):
'''Retrieves the access token.''' '''Retrieves the access token.
:param request_token: The request token as returned by
:class:`get_request_token`
:param request_token_secret: The request token secret as returned by
:class:`get_request_token`
:param http_method: A string representation of the HTTP method to be
used.
:param **params: Additional arguments to be added to the request
querystring.
'''
auth_session = self._construct_session( auth_session = self._construct_session(
access_token=request_token, access_token=request_token,
access_token_secret=request_token_secret, access_token_secret=request_token_secret,
Expand All @@ -183,7 +285,14 @@ def get_access_token(self, request_token, request_token_secret,


def get_authenticated_session(self, access_token, access_token_secret, def get_authenticated_session(self, access_token, access_token_secret,
header_auth=False): header_auth=False):
'''Returns an authenticated Requests session utilizing the hook.''' '''Returns an authenticated Requests session utilizing the hook.
:param access_token: The access token as returned by
:class:`get_access_token`
:param access_token_secret: The access token secret as returned by
:class:`get_access_token`
:param header_auth: Authenication via header, defauls to False.
'''
return self._construct_session(access_token=access_token, return self._construct_session(access_token=access_token,
access_token_secret=access_token_secret, access_token_secret=access_token_secret,
header_auth=header_auth) header_auth=header_auth)

0 comments on commit a38ca1a

Please sign in to comment.