def _build_request(self, method, request_uri, body=u"", headers={}): # oauthlib expects None instead of empty string method = unicode(method) request_uri = unicode(request_uri) extracted_body = extract_params(body) if extracted_body: # contenttype sometimes is ...form-data; boundary=X contenttype = headers.get('Content-Type', '').split(';', 1)[0] if contenttype == CONTENT_TYPE_FORM: uri, headers, body = self.client.sign( uri=request_uri, http_method=method, body=extracted_body, headers=headers) elif contenttype == CONTENT_TYPE_MULTI: uri, headers, _discard = self.client.sign( uri=request_uri, http_method=method, body=None, headers=headers) else: uri, headers, _discard = self.client.sign( uri=request_uri, http_method=method, body=None, headers=headers) return super(OAUTH1HTTPClient, self)._build_request( method, uri, body=body, headers=headers)
def __call__(self, r): """Add OAuth parameters to the request. Parameters may be included from the body if the content-type is urlencoded, if no content type is set a guess is made. """ # Overwriting url is safe here as request will not modify it past # this point. content_type = r.headers.get('Content-Type', '') if (not content_type and extract_params(r.body) or self.client.signature_type == SIGNATURE_TYPE_BODY): content_type = CONTENT_TYPE_FORM_URLENCODED if not isinstance(content_type, unicode): content_type = content_type.decode('utf-8') is_form_encoded = (CONTENT_TYPE_FORM_URLENCODED in content_type) if is_form_encoded: r.headers['Content-Type'] = CONTENT_TYPE_FORM_URLENCODED r.url, headers, r.body = self.client.sign( unicode(r.url), unicode(r.method), r.body or '', r.headers) elif self.force_include_body: # To allow custom clients to work on non form encoded bodies. r.url, headers, r.body = self.client.sign( unicode(r.url), unicode(r.method), r.body or '', r.headers) else: # Omit body data in the signing of non form-encoded requests r.url, headers, _ = self.client.sign( unicode(r.url), unicode(r.method), None, r.headers) r.prepare_headers(headers) r.url = to_native_string(r.url) return r
def __call__(self, r): """Add OAuth parameters to the request. Parameters may be included from the body if the content-type is urlencoded, if no content type is set a guess is made. """ # Overwriting url is safe here as request will not modify it past # this point. content_type = r.headers.get('Content-Type', '') if not content_type and extract_params(r.body): content_type = CONTENT_TYPE_FORM_URLENCODED if not isinstance(content_type, unicode): content_type = content_type.decode('utf-8') is_form_encoded = (CONTENT_TYPE_FORM_URLENCODED in content_type) if is_form_encoded: r.headers['Content-Type'] = CONTENT_TYPE_FORM_URLENCODED r.url, headers, r.body = self.client.sign( unicode(r.url), unicode(r.method), r.body or '', r.headers) else: r.url, headers, _ = self.client.sign( unicode(r.url), unicode(r.method), r.body or '', r.headers) r.prepare_headers(headers) r.url = to_native_str(r.url) return r
def __call__(self, r): """Add OAuth parameters to the request. Parameters may be included from the body if the content-type is urlencoded, if no content type is set a guess is made. """ # Overwriting url is safe here as request will not modify it past # this point. content_type = r.headers.get('Content-Type'.encode('utf-8'), '') if not isinstance(content_type, unicode): content_type = content_type.decode('utf-8') is_form_encoded = (CONTENT_TYPE_FORM_URLENCODED in content_type) if is_form_encoded or extract_params(r.body): r.headers['Content-Type'] = CONTENT_TYPE_FORM_URLENCODED r.url, r.headers, r.body = self.client.sign( unicode(r.url), unicode(r.method), r.body or '', r.headers) else: # Omit body data in the signing of non form-encoded requests r.url, r.headers, _ = self.client.sign( unicode(r.url), unicode(r.method), None, r.headers) return r
def sign(self, url, method, body, headers): """Returns the url, headers and body inc. the oauth1 stuff Parameters may be included from the body if the content-type is urlencoded, if no content type is set a guess is made. """ content_type = headers.get('Content-Type', '') if (not content_type and extract_params(body) or self.client.signature_type == SIGNATURE_TYPE_BODY): content_type = CONTENT_TYPE_FORM_URLENCODED is_form_encoded = (CONTENT_TYPE_FORM_URLENCODED in content_type) log.debug('Including body in call to sign: %s', is_form_encoded or self.force_include_body) if is_form_encoded: headers['Content-Type'] = CONTENT_TYPE_FORM_URLENCODED url, headers, body = self.client.sign(url, method, body or '', headers) elif self.force_include_body: # To allow custom clients to work on non form encoded bodies. url, headers, body = self.client.sign(url, method, body or '', headers) else: # Omit body data in the signing of non form-encoded requests url, headers, _ = self.client.sign(url, method, None, headers) return url, body, headers
def __call__(self, r): """Add OAuth parameters to the request. Parameters may be included from the body if the content-type is urlencoded, if no content type is set a guess is made. """ # Overwriting url is safe here as request will not modify it past # this point. content_type = r.headers.get("Content-Type".encode("utf-8"), "") if not isinstance(content_type, unicode): content_type = content_type.decode("utf-8") is_form_encoded = CONTENT_TYPE_FORM_URLENCODED in content_type if is_form_encoded or extract_params(r.body): r.headers["Content-Type"] = CONTENT_TYPE_FORM_URLENCODED r.url, r.headers, r.body = self.client.sign(unicode(r.url), unicode(r.method), r.body or "", r.headers) else: # Omit body data in the signing of non form-encoded requests r.url, r.headers, _ = self.client.sign(unicode(r.url), unicode(r.method), None, r.headers) r.url = to_native_str(r.url) return r
def __call__(self, r): contenttype = r.headers.get('Content-Type', None) decoded_body = extract_params(r.data) if contenttype == None and decoded_body != None: r.headers['Content-Type'] = 'application/x-www-form-urlencoded' r.url, r.headers, r.data = self.client.sign( unicode(r.url), unicode(r.method), r.data, r.headers) return r
def __call__(self, r): contenttype = r.headers.get('Content-Type', None) decoded_body = extract_params(r.data) if contenttype == None and decoded_body != None: r.headers['Content-Type'] = 'application/x-www-form-urlencoded' r.url, r.headers, r.data = self.client.sign(unicode(r.url), unicode(r.method), r.data, r.headers) return r
def __call__(self, r): """Add OAuth parameters to the request. Parameters may be included from the body if the content-type is urlencoded, if no content type is set an educated guess is made. """ # split(";") because Content-Type may be "multipart/form-data; boundary=xxxxx" contenttype = r.headers.get('Content-Type', '').split(";")[0].lower() # extract_params will not give params unless the body is a properly # formatted string, a dictionary or a list of 2-tuples. decoded_body = extract_params(r.body) # extract_params can only check the present r.data and does not know # of r.files, thus an extra check is performed. We know that # if files are present the request will not have # Content-type: x-www-form-urlencoded. We guess it will have # a mimetype of multipart/form-data and if this is not the case # we assume the correct header will be set later. _oauth_signed = True if hasattr(r, 'files') and contenttype == CONTENT_TYPE_MULTI_PART: # Omit body data in the signing and since it will always # be empty (cant add paras to body if multipart) and we wish # to preserve body. r.url, r.headers, _ = self.client.sign( unicode(r.full_url), unicode(r.method), None, r.headers) elif decoded_body is not None and contenttype in (CONTENT_TYPE_FORM_URLENCODED, ''): # Normal signing if not contenttype: r.headers['Content-Type'] = CONTENT_TYPE_FORM_URLENCODED r.url, r.headers, r.data = self.client.sign( unicode(r.full_url), unicode(r.method), r.data, r.headers) else: _oauth_signed = False if _oauth_signed: # Both flows add params to the URL by using r.full_url, # so this prevents adding it again later r.params = {} # Having the authorization header, key or value, in unicode will # result in UnicodeDecodeErrors when the request is concatenated # by httplib. This can easily be seen when attaching files. # Note that simply encoding the value is not enough since Python # saves the type of first key set. Thus we remove and re-add. # >>> d = {u'a':u'foo'} # >>> d['a'] = 'foo' # >>> d # { u'a' : 'foo' } u_header = unicode('Authorization') if u_header in r.headers: auth_header = r.headers[u_header].encode('utf-8') del r.headers[u_header] r.headers['Authorization'] = auth_header return r
def prepare_request_uri_query(oauth_params, uri): """Prepare the Request URI Query. Per `section 3.5.3`_ of the spec. .. _`section 3.5.3`: http://tools.ietf.org/html/rfc5849#section-3.5.3 """ # append OAuth params to the existing set of query components sch, net, path, par, query, fra = urlparse(uri) query = urlencode(_append_params(oauth_params, extract_params(query) or [])) return urlunparse((sch, net, path, par, query, fra))
def prepare_request_uri_query(oauth_params, uri): """Prepare the Request URI Query. Per `section 3.5.3`_ of the spec. .. _`section 3.5.3`: http://tools.ietf.org/html/rfc5849#section-3.5.3 """ # append OAuth params to the existing set of query components sch, net, path, par, query, fra = urlparse(uri) query = urlencode(_append_params(oauth_params, extract_params(query) or [])) return urlunparse((sch, net, path, par, query, fra))
async def __call__(self, r): """Add OAuth parameters to the request. Parameters may be included from the body if the content-type is urlencoded, if no content type is set a guess is made. """ # Overwriting url is safe here as request will not modify it past # this point. log.debug("Signing request %s using client %s", r, self.client) r.raw_body = await r.body() content_type = r.headers.get("Content-Type", "") if ( not content_type and extract_params(r.raw_body) or self.client.signature_type == SIGNATURE_TYPE_BODY ): content_type = CONTENT_TYPE_FORM_URLENCODED if not isinstance(content_type, unicode): content_type = content_type.decode("utf-8") is_form_encoded = CONTENT_TYPE_FORM_URLENCODED in content_type log.debug( "Including body in call to sign: %s", is_form_encoded or self.force_include_body, ) if is_form_encoded: r.headers["Content-Type"] = CONTENT_TYPE_FORM_URLENCODED r.url, headers, r.raw_body = self.client.sign( unicode(r.url), unicode(r.method), r.raw_body or "", r.headers ) elif self.force_include_body: # To allow custom clients to work on non form encoded bodies. r.url, headers, r.raw_body = self.client.sign( unicode(r.url), unicode(r.method), r.raw_body or "", r.headers ) else: # Omit body data in the signing of non form-encoded requests # noinspection PyArgumentEqualDefault r.url, headers, _ = self.client.sign( unicode(r.url), unicode(r.method), None, r.headers ) r.prepare_headers(headers) r.url = to_native_string(r.url) log.debug("Updated url: %s", r.url) log.debug("Updated headers: %s", headers) log.debug("Updated body: %r", r.raw_body) return r
def __call__(self, r): """Add OAuth parameters to the request. Parameters may be included from the body if the content-type is urlencoded, if no content type is set a guess is made. """ # Overwriting url is safe here as request will not modify it past # this point. log.debug("Signing request %s using client %s", r, self.client) content_type = r.headers.get("Content-Type", "") if ( not content_type and extract_params(r.body) or self.client.signature_type == SIGNATURE_TYPE_BODY ): content_type = CONTENT_TYPE_FORM_URLENCODED if not isinstance(content_type, unicode): content_type = content_type.decode("utf-8") is_form_encoded = CONTENT_TYPE_FORM_URLENCODED in content_type log.debug( "Including body in call to sign: %s", is_form_encoded or self.force_include_body, ) if is_form_encoded: r.headers["Content-Type"] = CONTENT_TYPE_FORM_URLENCODED r.url, headers, r.body = self.client.sign( unicode(r.url), unicode(r.method), r.body or "", r.headers ) elif self.force_include_body: # To allow custom clients to work on non form encoded bodies. r.url, headers, r.body = self.client.sign( unicode(r.url), unicode(r.method), r.body or "", r.headers ) else: # Omit body data in the signing of non form-encoded requests r.url, headers, _ = self.client.sign( unicode(r.url), unicode(r.method), None, r.headers ) r.prepare_headers(headers) r.url = to_native_string(r.url) log.debug("Updated url: %s", r.url) log.debug("Updated headers: %s", headers) log.debug("Updated body: %r", r.body) return r
def __call__(self, r): """Add OAuth parameters to the request. Parameters may be included from the body if the content-type is urlencoded, if no content type is set an educated guess is made. """ contenttype = r.headers.get('Content-Type', None) # extract_params will not give params unless the body is a properly # formatted string, a dictionary or a list of 2-tuples. decoded_body = extract_params(r.data) if contenttype == None and decoded_body != None: # extract_params can only check the present r.data and does not know # of r.files, thus an extra check is performed. We know that # if files are present the request will not have # Content-type: x-www-form-urlencoded. We guess it will have # a mimetype of multipart/form-encoded and if this is not the case # we assume the correct header will be set later. if r.files: # Omit body data in the signing and since it will always # be empty (cant add paras to body if multipart) and we wish # to preserve body. r.headers['Content-Type'] = 'multipart/form-encoded' r.url, r.headers, _ = self.client.sign(unicode(r.url), unicode(r.method), None, r.headers) else: # Normal signing r.headers['Content-Type'] = 'application/x-www-form-urlencoded' r.url, r.headers, r.data = self.client.sign( unicode(r.url), unicode(r.method), r.data, r.headers) # Having the authorization header, key or value, in unicode will # result in UnicodeDecodeErrors when the request is concatenated # by httplib. This can easily be seen when attaching files. # Note that simply encoding the value is not enough since Python # saves the type of first key set. Thus we remove and re-add. # >>> d = {u'a':u'foo'} # >>> d['a'] = 'foo' # >>> d # { u'a' : 'foo' } u_header = unicode('Authorization') if u_header in r.headers: auth_header = r.headers[u_header].encode('utf-8') del r.headers[u_header] r.headers['Authorization'] = auth_header return r
def test_request_request_token_with_valid_credentials(self): headers = {'wsgi.url_scheme': 'https', 'Authorization': 'oauth_consumer_key="%s", oauth_consumer_secret="%s", oauth_signature_method="PLAINTEXT", oauth_signature="%s&", oauth_timestamp="%d", oauth_nonce="%s", oauth_callback="oob"' % ( self.oauth_client_key, self.oauth_client_secret, self.oauth_client_secret, time.mktime(datetime.datetime.now().timetuple()), 'peanutbutterjellytime' )} response = self.client.post('/request-request-token/', **headers) new_token = Token.objects.filter(type=Token.REQUEST_TOKEN).latest('pk') response_content_params = dict(extract_params(to_unicode(response.content, 'utf-8'))) self.assertEqual(200, response.status_code) self.assertEqual('application/x-www-form-urlencoded', response['Content-Type']) self.assertEqual(new_token.key, response_content_params['oauth_token']) self.assertEqual(new_token.secret, response_content_params['oauth_token_secret'])
def __call__(self, r): """Add OAuth parameters to the request. Parameters may be included from the body if the content-type is urlencoded, if no content type is set an educated guess is made. """ contenttype = r.headers.get('Content-Type', None) # extract_params will not give params unless the body is a properly # formatted string, a dictionary or a list of 2-tuples. decoded_body = extract_params(r.data) if contenttype == None and decoded_body != None: # extract_params can only check the present r.data and does not know # of r.files, thus an extra check is performed. We know that # if files are present the request will not have # Content-type: x-www-form-urlencoded. We guess it will have # a mimetype of multipart/form-encoded and if this is not the case # we assume the correct header will be set later. if r.files: # Omit body data in the signing and since it will always # be empty (cant add paras to body if multipart) and we wish # to preserve body. r.headers['Content-Type'] = 'multipart/form-encoded' r.url, r.headers, _ = self.client.sign( unicode(r.url), unicode(r.method), None, r.headers) else: # Normal signing r.headers['Content-Type'] = 'application/x-www-form-urlencoded' r.url, r.headers, r.data = self.client.sign( unicode(r.url), unicode(r.method), r.data, r.headers) # Having the authorization header, key or value, in unicode will # result in UnicodeDecodeErrors when the request is concatenated # by httplib. This can easily be seen when attaching files. # Note that simply encoding the value is not enough since Python # saves the type of first key set. Thus we remove and re-add. # >>> d = {u'a':u'foo'} # >>> d['a'] = 'foo' # >>> d # { u'a' : 'foo' } u_header = unicode('Authorization') if u_header in r.headers: auth_header = r.headers[u_header].encode('utf-8') del r.headers[u_header] r.headers['Authorization'] = auth_header return r
def __call__(self, r): """Add OAuth parameters to the request. Parameters may be included from the body if the content-type is urlencoded, if no content type is set a guess is made. """ # Overwriting url is safe here as request will not modify it past # this point. is_form_encoded = CONTENT_TYPE_FORM_URLENCODED in r.headers.get("Content-Type", "") if is_form_encoded or extract_params(r.body): r.headers["Content-Type"] = CONTENT_TYPE_FORM_URLENCODED r.url, r.headers, r.body = self.client.sign(unicode(r.url), unicode(r.method), r.body or "", r.headers) else: # Omit body data in the signing of non form-encoded requests r.url, r.headers, _ = self.client.sign(unicode(r.url), unicode(r.method), None, r.headers) # PHB HACK FIX: FORCE URL TO STR - Should be encoded already if type(r.url) == unicode: r.url = str(r.url) # Having the authorization header, key or value, in unicode will # result in UnicodeDecodeErrors when the request is concatenated # by httplib. This can easily be seen when attaching files. # Note that simply encoding the value is not enough since Python # saves the type of first key set. Thus we remove and re-add. # >>> d = {u'a':u'foo'} # >>> d['a'] = 'foo' # >>> d # { u'a' : 'foo' } u_header = unicode("Authorization") if u_header in r.headers: auth_header = r.headers[u_header].encode("utf-8") del r.headers[u_header] r.headers["Authorization"] = auth_header # PHB HACK FIX: FORCE HEADERS TO STR - Should be encoded already new_headers = {} for (k, v) in r.headers.iteritems(): new_headers[str(k)] = str(v) r.headers = new_headers return r
def __call__(self, r): """Add OAuth parameters to the request. Parameters may be included from the body if the content-type is urlencoded, if no content type is set a guess is made. """ # Overwriting url is safe here as request will not modify it past # this point. log.debug('Signing request %s using client %s', r, self.client) content_type = r.headers.get('Content-Type', '') if (not content_type and extract_params(r.body) or self.client.signature_type == SIGNATURE_TYPE_BODY): content_type = CONTENT_TYPE_FORM_URLENCODED if not isinstance(content_type, unicode): content_type = content_type.decode('utf-8') is_form_encoded = (CONTENT_TYPE_FORM_URLENCODED in content_type) if is_form_encoded: r.headers['Content-Type'] = CONTENT_TYPE_FORM_URLENCODED if (self.force_include_body or (not self.force_exclude_body and is_form_encoded)): log.debug('Including body in call to sign') r.url, headers, r.body = self.client.sign( unicode(r.url), unicode(r.method), r.body or '', r.headers) else: log.debug('Excluding body from call to sign') r_headers = r.headers.copy() if is_form_encoded: del r_headers['Content-Type'] r.url, headers, _ = self.client.sign( unicode(r.url), unicode(r.method), None, r_headers) if is_form_encoded: headers['Content-Type'] = CONTENT_TYPE_FORM_URLENCODED r.prepare_headers(headers) r.url = to_native_string(r.url) log.debug('Updated url: %s', r.url) log.debug('Updated headers: %s', headers) log.debug('Updated body: %r', r.body) return r
def __call__(self, r): """Add OAuth parameters to the request. Parameters may be included from the body if the content-type is urlencoded, if no content type is set a guess is made. """ # Overwriting url is safe here as request will not modify it past # this point. content_type = r.headers.get('Content-Type'.encode('utf-8'), '') if not isinstance(content_type, unicode): content_type = content_type.decode('utf-8') is_form_encoded = (CONTENT_TYPE_FORM_URLENCODED in content_type) if is_form_encoded or extract_params(r.body): r.headers['Content-Type'] = CONTENT_TYPE_FORM_URLENCODED r.url, r.headers, r.body = self.client.sign( unicode(r.url), unicode(r.method), r.body or '', r.headers) else: # Omit body data in the signing of non form-encoded requests r.url, r.headers, _ = self.client.sign( unicode(r.url), unicode(r.method), None, r.headers) # Having the authorization header, key or value, in unicode will # result in UnicodeDecodeErrors when the request is concatenated # by httplib. This can easily be seen when attaching files. # Note that simply encoding the value is not enough since Python # saves the type of first key set. Thus we remove and re-add. # >>> d = {u'a':u'foo'} # >>> d['a'] = 'foo' # >>> d # { u'a' : 'foo' } u_header = unicode('Authorization') if u_header in r.headers: auth_header = r.headers[u_header].encode('utf-8') del r.headers[u_header] r.headers['Authorization'] = auth_header return r
def __call__(self, r): """Add OAuth parameters to the request. Parameters may be included from the body if the content-type is urlencoded, if no content type is set a guess is made. """ # Overwriting url is safe here as request will not modify it past # this point. is_form_encoded = (CONTENT_TYPE_FORM_URLENCODED in r.headers.get('Content-Type', '')) if is_form_encoded or extract_params(r.body): r.headers['Content-Type'] = CONTENT_TYPE_FORM_URLENCODED r.url, r.headers, r.body = self.client.sign( unicode(r.url), unicode(r.method), r.body or '', r.headers) else: # Omit body data in the signing of non form-encoded requests r.url, r.headers, _ = self.client.sign(unicode(r.url), unicode(r.method), None, r.headers) # Having the authorization header, key or value, in unicode will # result in UnicodeDecodeErrors when the request is concatenated # by httplib. This can easily be seen when attaching files. # Note that simply encoding the value is not enough since Python # saves the type of first key set. Thus we remove and re-add. # >>> d = {u'a':u'foo'} # >>> d['a'] = 'foo' # >>> d # { u'a' : 'foo' } u_header = unicode('Authorization') if u_header in r.headers: auth_header = r.headers[u_header].encode('utf-8') del r.headers[u_header] r.headers['Authorization'] = auth_header return r
def test_extract_params_twotuple(self): self.assertItemsEqual(extract_params(PARAMS_TWOTUPLE), PARAMS_TWOTUPLE)
def test_extract_params_dict(self): self.assertItemsEqual(extract_params(PARAMS_DICT), PARAMS_TWOTUPLE)
def collect_parameters(uri_query="", body=[], headers=None, exclude_oauth_signature=True, with_realm=False): """**Parameter Sources** Parameters starting with `oauth_` will be unescaped. Body parameters must be supplied as a dict, a list of 2-tuples, or a formencoded query string. Headers must be supplied as a dict. Per `section 3.4.1.3.1`_ of the spec. For example, the HTTP request:: POST /request?b5=%3D%253D&a3=a&c%40=&a2=r%20b HTTP/1.1 Host: example.com Content-Type: application/x-www-form-urlencoded Authorization: OAuth realm="Example", oauth_consumer_key="9djdj82h48djs9d2", oauth_token="kkk9d7dh3k39sjv7", oauth_signature_method="HMAC-SHA1", oauth_timestamp="137131201", oauth_nonce="7d8f3e4a", oauth_signature="djosJKDKJSD8743243%2Fjdk33klY%3D" c2&a3=2+q contains the following (fully decoded) parameters used in the signature base sting:: +------------------------+------------------+ | Name | Value | +------------------------+------------------+ | b5 | =%3D | | a3 | a | | c@ | | | a2 | r b | | oauth_consumer_key | 9djdj82h48djs9d2 | | oauth_token | kkk9d7dh3k39sjv7 | | oauth_signature_method | HMAC-SHA1 | | oauth_timestamp | 137131201 | | oauth_nonce | 7d8f3e4a | | c2 | | | a3 | 2 q | +------------------------+------------------+ Note that the value of "b5" is "=%3D" and not "==". Both "c@" and "c2" have empty values. While the encoding rules specified in this specification for the purpose of constructing the signature base string exclude the use of a "+" character (ASCII code 43) to represent an encoded space character (ASCII code 32), this practice is widely used in "application/x-www-form-urlencoded" encoded values, and MUST be properly decoded, as demonstrated by one of the "a3" parameter instances (the "a3" parameter is used twice in this request). .. _`section 3.4.1.3.1`: http://tools.ietf.org/html/rfc5849#section-3.4.1.3.1 """ headers = headers or {} params = [] # The parameters from the following sources are collected into a single # list of name/value pairs: # * The query component of the HTTP request URI as defined by # `RFC3986, Section 3.4`_. The query component is parsed into a list # of name/value pairs by treating it as an # "application/x-www-form-urlencoded" string, separating the names # and values and decoding them as defined by # `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(urldecode(uri_query)) # * The OAuth HTTP "Authorization" header field (`Section 3.5.1`_) if # present. The header's content is parsed into a list of name/value # pairs excluding the "realm" parameter if present. The parameter # values are decoded as defined by `Section 3.5.1`_. # # .. _`Section 3.5.1`: http://tools.ietf.org/html/rfc5849#section-3.5.1 if headers: headers_lower = dict((k.lower(), v) for k, v in headers.items()) authorization_header = headers_lower.get("authorization") if authorization_header is not None: params.extend( [i for i in utils.parse_authorization_header(authorization_header) if with_realm or i[0] != "realm"] ) # * The HTTP request entity-body, but only if all of the following # conditions are met: # * The entity-body is single-part. # # * The entity-body follows the encoding requirements of the # "application/x-www-form-urlencoded" content-type as defined by # `W3C.REC-html40-19980424`_. # * The HTTP request entity-header includes the "Content-Type" # header field set to "application/x-www-form-urlencoded". # # .._`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 [] params.extend(bodyparams) # ensure all oauth params are unescaped unescaped_params = [] for k, v in params: if k.startswith("oauth_"): v = utils.unescape(v) unescaped_params.append((k, v)) # The "oauth_signature" parameter MUST be excluded from the signature # base string if present. if exclude_oauth_signature: unescaped_params = list(filter(lambda i: i[0] != "oauth_signature", unescaped_params)) return unescaped_params
def collect_parameters(uri_query='', body=None, headers=None, exclude_oauth_signature=True, with_realm=False): """ Gather the request parameters from all the parameter sources. This function is used to extract all the parameters, which are then passed to ``normalize_parameters`` to produce one of the components that make up the *signature base string*. Parameters starting with `oauth_` will be unescaped. Body parameters must be supplied as a dict, a list of 2-tuples, or a form encoded query string. Headers must be supplied as a dict. The rules where the parameters must be sourced from are defined in `section 3.4.1.3.1`_ of RFC 5849. .. _`Sec 3.4.1.3.1`: https://tools.ietf.org/html/rfc5849#section-3.4.1.3.1 """ if body is None: body = [] headers = headers or {} params = [] # The parameters from the following sources are collected into a single # list of name/value pairs: # * The query component of the HTTP request URI as defined by # `RFC3986, Section 3.4`_. The query component is parsed into a list # of name/value pairs by treating it as an # "application/x-www-form-urlencoded" string, separating the names # and values and decoding them as defined by W3C.REC-html40-19980424 # `W3C-HTML-4.0`_, Section 17.13.4. # # .. _`RFC3986, Sec 3.4`: https://tools.ietf.org/html/rfc3986#section-3.4 # .. _`W3C-HTML-4.0`: https://www.w3.org/TR/1998/REC-html40-19980424/ if uri_query: params.extend(urldecode(uri_query)) # * The OAuth HTTP "Authorization" header field (`Section 3.5.1`_) if # present. The header's content is parsed into a list of name/value # pairs excluding the "realm" parameter if present. The parameter # values are decoded as defined by `Section 3.5.1`_. # # .. _`Section 3.5.1`: https://tools.ietf.org/html/rfc5849#section-3.5.1 if headers: headers_lower = {k.lower(): v for k, v in headers.items()} authorization_header = headers_lower.get('authorization') if authorization_header is not None: params.extend([ i for i in utils.parse_authorization_header(authorization_header) if with_realm or i[0] != 'realm' ]) # * The HTTP request entity-body, but only if all of the following # conditions are met: # * The entity-body is single-part. # # * The entity-body follows the encoding requirements of the # "application/x-www-form-urlencoded" content-type as defined by # W3C.REC-html40-19980424 `W3C-HTML-4.0`_. # * The HTTP request entity-header includes the "Content-Type" # header field set to "application/x-www-form-urlencoded". # # .. _`W3C-HTML-4.0`: https://www.w3.org/TR/1998/REC-html40-19980424/ # TODO: enforce header param inclusion conditions bodyparams = extract_params(body) or [] params.extend(bodyparams) # ensure all oauth params are unescaped unescaped_params = [] for k, v in params: if k.startswith('oauth_'): v = utils.unescape(v) unescaped_params.append((k, v)) # The "oauth_signature" parameter MUST be excluded from the signature # base string if present. if exclude_oauth_signature: unescaped_params = list( filter(lambda i: i[0] != 'oauth_signature', unescaped_params)) return unescaped_params
def test_extract_params_blank_string(self): self.assertItemsEqual(extract_params(''), [])
def test_extract_invalid(self): self.assertEqual(extract_params(object()), None) self.assertEqual(extract_params([('')]), None)
def test_extract_params_empty_list(self): self.assertItemsEqual(extract_params([]), [])
def test_extract_params_formencoded(self): self.assertEqual(extract_params(self.params_formencoded), self.params_twotuple)
def test_extract_params_dict(self): self.assertItemsEqual(extract_params(PARAMS_DICT), PARAMS_TWOTUPLE)
def test_extract_params_twotuple(self): self.assertItemsEqual(extract_params(PARAMS_TWOTUPLE), PARAMS_TWOTUPLE)
async def __call__(self, r): """Add OAuth parameters to the request. Parameters may be included from the body if the content-type is urlencoded, if no content type is set a guess is made. Discussion: 1. refactor to work with asks request objects which in particular do not maintain an up to date url including e.g. queries built from a params object? 2. Need to understand oauth possibilities does signing ever change the url for example, if so asks might be broken as the url is not used for io? """ # Overwriting url is safe here as request will not modify it past # this point. def str_(value,encoding): if isinstance(value,str): return value return str(value,encoding) log.debug('Signing request %s using client %s', r, self.client) # Paste over differences in requests and asks Request attribute names. r = MappedAttributesProxy(r,url='uri',body='data') if r.params: # oauth signing is based on full uri/url including the query # part, unfortunately asks doesn't provide this when the query # is passed via params, so we have to fix up here r.url = '{}?{}'.format(r.url.split('?')[0],r.path.split('?')[1]) content_type = r.headers.get('Content-Type', '') if (not content_type and extract_params(r.body) or self.client.signature_type == SIGNATURE_TYPE_BODY): content_type = CONTENT_TYPE_FORM_URLENCODED if not isinstance(content_type, str): content_type = str(content_type,self.encoding) is_form_encoded = (CONTENT_TYPE_FORM_URLENCODED in content_type) log.debug('Including body in call to sign: %s', is_form_encoded or self.force_include_body) if is_form_encoded: r.headers['Content-Type'] = CONTENT_TYPE_FORM_URLENCODED r.url, headers, r.body = self.client.sign( str_(r.url,self.encoding), str_(r.method,self.encoding), r.body or '', r.headers) if self.force_include_body: r.url, headers, r.body = self.client.sign( str_(r.url,self.encoding), str_(r.method,self.encoding), r.body or '', r.headers) else: r.url, headers, r.body = self.client.sign( str_(r.url,self.encoding), str_(r.method,self.encoding), None, r.headers) r.url = str_(r.url,self.encoding) log.debug('Updated url: %s', r.url) log.debug('Updated headers: %s', headers) log.debug('Updated body: %r', r.body) return headers
def test_extract_params_formencoded(self): self.assertItemsEqual(extract_params(PARAMS_FORMENCODED), PARAMS_TWOTUPLE)
def test_extract_params_formencoded(self): self.assertItemsEqual(extract_params(PARAMS_FORMENCODED), PARAMS_TWOTUPLE)
def test_extract_params_blank_string(self): self.assertItemsEqual(extract_params(''), [])
def test_extract_params_twotuple(self): self.assertEqual(extract_params(self.params_twotuple), self.params_twotuple)
def test_extract_non_formencoded_string(self): self.assertEqual(extract_params('not a formencoded string'), None)
def collect_parameters(uri_query='', body=[], headers=None, exclude_oauth_signature=True, with_realm=False): """**Parameter Sources** Parameters starting with `oauth_` will be unescaped. Body parameters must be supplied as a dict, a list of 2-tuples, or a formencoded query string. Headers must be supplied as a dict. Per `section 3.4.1.3.1`_ of the spec. For example, the HTTP request:: POST /request?b5=%3D%253D&a3=a&c%40=&a2=r%20b HTTP/1.1 Host: example.com Content-Type: application/x-www-form-urlencoded Authorization: OAuth realm="Example", oauth_consumer_key="9djdj82h48djs9d2", oauth_token="kkk9d7dh3k39sjv7", oauth_signature_method="HMAC-SHA1", oauth_timestamp="137131201", oauth_nonce="7d8f3e4a", oauth_signature="djosJKDKJSD8743243%2Fjdk33klY%3D" c2&a3=2+q contains the following (fully decoded) parameters used in the signature base sting:: +------------------------+------------------+ | Name | Value | +------------------------+------------------+ | b5 | =%3D | | a3 | a | | c@ | | | a2 | r b | | oauth_consumer_key | 9djdj82h48djs9d2 | | oauth_token | kkk9d7dh3k39sjv7 | | oauth_signature_method | HMAC-SHA1 | | oauth_timestamp | 137131201 | | oauth_nonce | 7d8f3e4a | | c2 | | | a3 | 2 q | +------------------------+------------------+ Note that the value of "b5" is "=%3D" and not "==". Both "c@" and "c2" have empty values. While the encoding rules specified in this specification for the purpose of constructing the signature base string exclude the use of a "+" character (ASCII code 43) to represent an encoded space character (ASCII code 32), this practice is widely used in "application/x-www-form-urlencoded" encoded values, and MUST be properly decoded, as demonstrated by one of the "a3" parameter instances (the "a3" parameter is used twice in this request). .. _`section 3.4.1.3.1`: http://tools.ietf.org/html/rfc5849#section-3.4.1.3.1 """ headers = headers or {} params = [] # The parameters from the following sources are collected into a single # list of name/value pairs: # * The query component of the HTTP request URI as defined by # `RFC3986, Section 3.4`_. The query component is parsed into a list # of name/value pairs by treating it as an # "application/x-www-form-urlencoded" string, separating the names # and values and decoding them as defined by # `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(urldecode(uri_query)) # * The OAuth HTTP "Authorization" header field (`Section 3.5.1`_) if # present. The header's content is parsed into a list of name/value # pairs excluding the "realm" parameter if present. The parameter # values are decoded as defined by `Section 3.5.1`_. # # .. _`Section 3.5.1`: http://tools.ietf.org/html/rfc5849#section-3.5.1 if headers: headers_lower = dict((k.lower(), v) for k, v in headers.items()) authorization_header = headers_lower.get('authorization') if authorization_header is not None: params.extend([ i for i in utils.parse_authorization_header(authorization_header) if with_realm or i[0] != 'realm' ]) # * The HTTP request entity-body, but only if all of the following # conditions are met: # * The entity-body is single-part. # # * The entity-body follows the encoding requirements of the # "application/x-www-form-urlencoded" content-type as defined by # `W3C.REC-html40-19980424`_. # * The HTTP request entity-header includes the "Content-Type" # header field set to "application/x-www-form-urlencoded". # # .._`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 [] params.extend(bodyparams) # ensure all oauth params are unescaped unescaped_params = [] for k, v in params: if k.startswith('oauth_'): v = utils.unescape(v) unescaped_params.append((k, v)) # The "oauth_signature" parameter MUST be excluded from the signature # base string if present. if exclude_oauth_signature: unescaped_params = list( filter(lambda i: i[0] != 'oauth_signature', unescaped_params)) return unescaped_params
def test_extract_non_formencoded_string(self): self.assertEqual(extract_params('not a formencoded string'), None)
def test_extract_invalid(self): self.assertEqual(extract_params(object()), None) self.assertEqual(extract_params([('')]), None)
def test_extract_invalid(self): self.assertIsNone(extract_params(object())) self.assertIsNone(extract_params([('')]))
def test_extract_params_empty_list(self): self.assertItemsEqual(extract_params([]), [])