diff options
author | David Gouldin <david@gould.in> | 2012-05-12 15:20:14 -0700 |
---|---|---|
committer | David Gouldin <david@gould.in> | 2012-05-12 15:20:14 -0700 |
commit | bbee30c35e5eb438567e693f6df00644ad3bc4fb (patch) | |
tree | 644edf917cf21123adc4cde9a864ee0b8bf47583 | |
parent | a56cf9c1721e81abb02b8578493cb85bd3307e35 (diff) | |
download | oauthlib-use_request_objects.tar.gz |
Continuing to refactor oauth1 to use Request objects internally.use_request_objects
-rw-r--r-- | oauthlib/common.py | 7 | ||||
-rw-r--r-- | oauthlib/oauth1/rfc5849/__init__.py | 46 | ||||
-rw-r--r-- | oauthlib/oauth1/rfc5849/parameters.py | 47 | ||||
-rw-r--r-- | oauthlib/oauth1/rfc5849/signature.py | 13 | ||||
-rw-r--r-- | tests/oauth1/rfc5849/test_parameters.py | 14 | ||||
-rw-r--r-- | tests/oauth1/rfc5849/test_signatures.py | 32 |
6 files changed, 93 insertions, 66 deletions
diff --git a/oauthlib/common.py b/oauthlib/common.py index 5ec354e..3d10bd8 100644 --- a/oauthlib/common.py +++ b/oauthlib/common.py @@ -152,8 +152,11 @@ class Request(object): @property def uri_query_params(self): - return urlparse.parse_qsl(self.uri_query, keep_blank_values=True, - strict_parsing=True) + if self.uri_query: + return urlparse.parse_qsl(self.uri_query, keep_blank_values=True, + strict_parsing=True) + else: + return [] @property def urlencoded_body(self): diff --git a/oauthlib/oauth1/rfc5849/__init__.py b/oauthlib/oauth1/rfc5849/__init__.py index 7744a48..4fb4a49 100644 --- a/oauthlib/oauth1/rfc5849/__init__.py +++ b/oauthlib/oauth1/rfc5849/__init__.py @@ -96,13 +96,11 @@ class Client(object): def _render(self, request, formencode=False): """Render a signed request according to signature type - Returns a 3-tuple containing the request URI, headers, and body. + Returns a rendered Request instance. If the formencode argument is True and the body contains parameters, it is escaped and returned as a valid formencoded string. """ - # TODO what if there are body params on a header-type auth? - # TODO what if there are query params on a body-type auth? return parameters.prepare_request(request, self.signature_type) def sign(self, uri, http_method=u'GET', body=None, headers=None): @@ -190,29 +188,6 @@ class Server(object): def get_resource_owner_secret(self, resource_owner_key): raise NotImplementedError("Subclasses must implement this function.") - def get_signature_type_and_params(self, uri_query, headers, body): - signature_types_with_oauth_params = filter(lambda s: s[1], ( - (constants.SIGNATURE_TYPE_AUTH_HEADER, utils.filter_oauth_params( - signature.collect_parameters(headers=headers, - exclude_oauth_signature=False))), - (constants.SIGNATURE_TYPE_BODY, utils.filter_oauth_params( - signature.collect_parameters(body=body, - exclude_oauth_signature=False))), - (constants.SIGNATURE_TYPE_QUERY, utils.filter_oauth_params( - signature.collect_parameters(uri_query=uri_query, - exclude_oauth_signature=False))), - )) - - if len(signature_types_with_oauth_params) > 1: - raise ValueError('oauth_ params must come from only 1 signature type but were found in %s' % ', '.join( - [s[0] for s in signature_types_with_oauth_params])) - try: - signature_type, params = signature_types_with_oauth_params[0] - except IndexError: - raise ValueError('oauth_ params are missing. Could not determine signature type.') - - return signature_type, dict(params) - def check_client_key(self, client_key): raise NotImplementedError("Subclasses must implement this function.") @@ -234,13 +209,11 @@ class Server(object): .. _`section 3.2`: http://tools.ietf.org/html/rfc5849#section-3.2 """ - headers = headers or {} - signature_type = None - # FIXME: urlparse does not return unicode! - uri_query = urlparse.urlparse(uri).query - - signature_type, params = self.get_signature_type_and_params(uri_query, - headers, body) + request = Request(uri, http_method=http_method, body=body, + headers=headers) + signature_type = parameters.get_signature_type(request) + params = signature.collect_parameters(request, + exclude_oauth_signature=False) # the parameters may not include duplicate oauth entries filtered_params = utils.filter_oauth_params(params) @@ -307,10 +280,11 @@ class Server(object): signature_type=signature_type, verifier=verifier) - request = Request(uri, http_method, body, headers) - request.oauth_params = params + new_request = request.clone() + new_request.oauth_params = params.items() - client_signature = oauth_client.get_oauth_signature(request) + client_signature = oauth_client.get_oauth_signature(new_request) # FIXME: use near constant time string compare to avoid timing attacks return client_signature == request_signature + diff --git a/oauthlib/oauth1/rfc5849/parameters.py b/oauthlib/oauth1/rfc5849/parameters.py index 315cccf..b0e6459 100644 --- a/oauthlib/oauth1/rfc5849/parameters.py +++ b/oauthlib/oauth1/rfc5849/parameters.py @@ -14,6 +14,53 @@ from urlparse import urlparse, urlunparse from . import constants, utils from oauthlib.common import extract_params, urlencode +def get_signature_type(request): + """**Determine the signature type of a request** + Per `section 3.5`_ of the spec. + + .. _`section 3.5`: http://tools.ietf.org/html/rfc5849#section-3.5 + """ + signature_types = [] + + # When making an OAuth-authenticated request, protocol parameters as + # well as any other parameter using the "oauth_" prefix SHALL be + # included in the request using one and only one of the following + # locations, listed in order of decreasing preference: + + # 1. The HTTP "Authorization" header field as described in + # `Section 3.5.1`_. + # + # .. _`Section 3.5.1`: http://tools.ietf.org/html/rfc5849#section-3.5.1 + if request.headers: + headers_lower = dict( + (k.lower(), v) for k, v in request.headers.items()) + authorization_header = headers_lower.get(u'authorization') + if authorization_header is not None: + header_oauth_params = utils.filter_oauth_params( + utils.parse_authorization_header(authorization_header)) + if header_oauth_params: + signature_types.append(constants.SIGNATURE_TYPE_AUTH_HEADER) + + # 2. The HTTP request entity-body as described in `Section 3.5.2`_. + # + # .. _`Section 3.5.2`: http://tools.ietf.org/html/rfc5849#section-3.5.2 + if utils.filter_oauth_params(extract_params(request.body) or []): + signature_types.append(constants.SIGNATURE_TYPE_BODY) + + # 3. The HTTP request URI query as described in `Section 3.5.3`_. + # + # .. _`Section 3.5.3`: http://tools.ietf.org/html/rfc5849#section-3.5.3 + if utils.filter_oauth_params(request.uri_query_params): + signature_types.append(constants.SIGNATURE_TYPE_QUERY) + + if len(signature_types) > 1: + raise ValueError('oauth_ params must come from only 1 signature type but were found in %s' % ', '.join( + signature_types)) + try: + return signature_types[0] + except IndexError: + raise ValueError('oauth_ params are missing. Could not determine signature type.') + def prepare_headers(request, realm=None): """**Prepare the Authorization header.** diff --git a/oauthlib/oauth1/rfc5849/signature.py b/oauthlib/oauth1/rfc5849/signature.py index 99101d4..edd553b 100644 --- a/oauthlib/oauth1/rfc5849/signature.py +++ b/oauthlib/oauth1/rfc5849/signature.py @@ -167,8 +167,7 @@ def normalize_base_string_uri(uri): # # .. _`section 3.4.1.3`: http://tools.ietf.org/html/rfc5849#section-3.4.1.3 -def collect_parameters(uri_query='', body=[], headers=None, - exclude_oauth_signature=True): +def collect_parameters(request, exclude_oauth_signature=True): """**Parameter Sources** Parameters starting with `oauth_` will be unescaped. @@ -226,7 +225,7 @@ def collect_parameters(uri_query='', body=[], headers=None, .. _`section 3.4.1.3.1`: http://tools.ietf.org/html/rfc5849#section-3.4.1.3.1 """ - headers = headers or {} + headers = request.headers or {} params = [] # The parameters from the following sources are collected into a single @@ -240,9 +239,9 @@ def collect_parameters(uri_query='', body=[], headers=None, # `W3C.REC-html40-19980424`_, Section 17.13.4. # # .. _`RFC3986, Section 3.4`: http://tools.ietf.org/html/rfc3986#section-3.4 - # .. _`W3C.REC-html40-19980424`: http://tools.ietf.org/html/rfc5849#ref-W3C.REC-html40-19980424 - if uri_query: - params.extend(urlparse.parse_qsl(uri_query, keep_blank_values=True)) + # .. _`W3C.REC-html40-19i980424`: http://tools.ietf.org/html/rfc5849#ref-W3C.REC-html40-19980424 + if request.uri_query: + params.extend(urlparse.parse_qsl(request.uri_query, keep_blank_values=True)) # * The OAuth HTTP "Authorization" header field (`Section 3.5.1`_) if # present. The header's content is parsed into a list of name/value @@ -271,7 +270,7 @@ def collect_parameters(uri_query='', body=[], headers=None, # .._`W3C.REC-html40-19980424`: http://tools.ietf.org/html/rfc5849#ref-W3C.REC-html40-19980424 # TODO: enforce header param inclusion conditions - bodyparams = extract_params(body) or [] + bodyparams = extract_params(request.body) or [] params.extend(bodyparams) # ensure all oauth params are unescaped diff --git a/tests/oauth1/rfc5849/test_parameters.py b/tests/oauth1/rfc5849/test_parameters.py index 9765cab..4388123 100644 --- a/tests/oauth1/rfc5849/test_parameters.py +++ b/tests/oauth1/rfc5849/test_parameters.py @@ -7,6 +7,10 @@ from ...unittest import TestCase class ParameterTests(TestCase): + data_params = [ + (u'data_param_foo', u'foo'), + (u'data_param_1', u'1'), + ] auth_only_params = [ (u'oauth_consumer_key', u"9djdj82h48djs9d2"), (u'oauth_token', u"kkk9d7dh3k39sjv7"), @@ -15,9 +19,7 @@ class ParameterTests(TestCase): (u'oauth_nonce', u"7d8f3e4a"), (u'oauth_signature', u"bYT5CMsGcbgUdFHObYMEfcx6bsw=") ] - auth_and_data = list(auth_only_params) - auth_and_data.append((u'data_param_foo', u'foo')) - auth_and_data.append((u'data_param_1', u'1')) + auth_and_data = auth_only_params + data_params realm = u'testrealm' norealm_authorization_header = u' '.join(( u'OAuth', @@ -72,11 +74,11 @@ class ParameterTests(TestCase): {u'Authorization': self.withrealm_authorization_header}) def test_prepare_form_encoded_body(self): - request = Request(u'http://www.google.com/') + request = Request(u'http://www.google.com/', body=self.data_params) request.oauth_params = self.auth_only_params - form_encoded_body = 'data_param_foo=foo&data_param_1=1&oauth_consumer_key=9djdj82h48djs9d2&oauth_token=kkk9d7dh3k39sjv7&oauth_signature_method=HMAC-SHA1&oauth_timestamp=137131201&oauth_nonce=7d8f3e4a&oauth_signature=bYT5CMsGcbgUdFHObYMEfcx6bsw%3D' + form_encoded_body = u'data_param_foo=foo&data_param_1=1&oauth_consumer_key=9djdj82h48djs9d2&oauth_token=kkk9d7dh3k39sjv7&oauth_signature_method=HMAC-SHA1&oauth_timestamp=137131201&oauth_nonce=7d8f3e4a&oauth_signature=bYT5CMsGcbgUdFHObYMEfcx6bsw%3D' self.assertEqual( - urlencode(prepare_form_encoded_body(self.auth_and_data, existing_body)), + urlencode(prepare_form_encoded_body(request).body), form_encoded_body) def test_prepare_request_uri_query(self): diff --git a/tests/oauth1/rfc5849/test_signatures.py b/tests/oauth1/rfc5849/test_signatures.py index fc81ea7..8fa237b 100644 --- a/tests/oauth1/rfc5849/test_signatures.py +++ b/tests/oauth1/rfc5849/test_signatures.py @@ -3,6 +3,7 @@ from __future__ import absolute_import import urllib from oauthlib.oauth1.rfc5849.signature import * +from oauthlib.common import Request from ...unittest import TestCase @@ -85,16 +86,14 @@ class SignatureTests(TestCase): """ We check against parameters multiple times in case things change after more parameters are added. """ - # check against empty parameters - # check against empty parameters - # check against empty parameters - self.assertEquals(collect_parameters(), []) + + uri = u'http://example.com/request?%s' % self.uri_query # Check against uri_query # Check against uri_query # Check against uri_query - - parameters = collect_parameters(uri_query=self.uri_query) + request = Request(uri) + parameters = collect_parameters(request) self.assertEquals(len(parameters), 6) self.assertEquals(parameters[0], ('b5', '=%3D')) @@ -108,9 +107,10 @@ class SignatureTests(TestCase): # check against authorization header as well # check against authorization header as well - parameters = collect_parameters(uri_query=self.uri_query, headers={ + request = Request(uri, headers={ 'Authorization': self.authorization_header, }) + parameters = collect_parameters(request) # Redo the checks against all the parameters. Duplicated code but better safety self.assertEquals(len(parameters), 11) @@ -129,10 +129,10 @@ class SignatureTests(TestCase): # Add in the body. # TODO - add more valid content for the body. Daniel Greenfeld 2012/03/12 # Redo again the checks against all the parameters. Duplicated code but better safety - parameters = collect_parameters(uri_query=self.uri_query, - body=self.body, headers={ - 'Authorization': self.authorization_header, - }) + request = Request(uri, body=self.body, headers={ + 'Authorization': self.authorization_header, + }) + parameters = collect_parameters(request) self.assertEquals(len(parameters), 12) self.assertEquals(parameters[0], ('b5', '=%3D')) self.assertEquals(parameters[1], ('a3', 'a')) @@ -150,11 +150,13 @@ class SignatureTests(TestCase): def test_normalize_parameters(self): """ We copy some of the variables from the test method above.""" + uri = u'http://example.com/request?%s' % self.uri_query + # Create the parameters - parameters = collect_parameters(uri_query=unicode(self.uri_query), - body=unicode(self.body), headers={ - u'Authorization': unicode(self.authorization_header), - }) + request = Request(uri, body=unicode(self.body), headers={ + u'Authorization': unicode(self.authorization_header), + }) + parameters = collect_parameters(request) normalized = normalize_parameters(parameters) # check the parameters type |