def parse_authorization_header(header_value, param_delimiter=',', strict=True): """ Parses the OAuth Authorization header. :: 'OAuth realm="example.com",oauth_nonce="123456789",..." -> ({"oauth_nonce": ["123456789"], ...}, "example.com") :see: Authorization Header http://tools.ietf.org/html/rfc5849#section-3.5.1 :param header_value: Header value. :param param_delimiter: The delimiter used to separate header value parameters. According to the Specification, this must be a comma ",". However, certain services like Yahoo! use "&" instead. Comma is default. If you want to use another delimiter character, the ``strict`` argument to this function must also be set to ``False``. See https://github.com/oauth/oauth-ruby/pull/12 :param strict: When ``True``, more strict checking will be performed. The authorization header value must be on a single line. The param delimiter MUST be a comma. When ``False``, the parser is a bit lenient. :returns: A tuple of (Dictionary of parameter name value pairs, realm). realm will be ``None`` if the authorization header does not have a ``realm`` parameter. """ header_value = utf8_decode_if_bytes(header_value) realm = None params = {} for name, value \ in _parse_authorization_header_l(header_value, param_delimiter=param_delimiter, strict=strict): # We do keep track of multiple values because they will be # detected by the sanitization below and flagged as an error # in the Authorization header value. # #params[name] = [value] if not name == OAUTH_PARAM_REALM: # The ``realm`` parameter is not included into the protocol # parameters list. if name in params: params[name].append(value) else: params[name] = [value] else: realm = value # Sanitize and check parameters. Raises an error if # multiple values are found. Should help with debugging # clients. params = request_query_remove_non_oauth(params) return params, realm
def _parse_credentials_response(cls, response, strict=True): """ Parses the entity-body of the OAuth server response to an OAuth credential request. :param response: An instance of :class:`pyoauth.http.ResponseAdapter`. :param strict: ``True`` (default) for string response parsing; ``False`` to be a bit lenient. Some non-compliant OAuth servers return credentials without setting the content-type. Setting this to ``False`` will not raise an error, but will still warn you that the response content-type is not valid. :returns: A tuple of the form:: (pyoauth.oauth1.Credentials instance, other parameters) """ if not response.status: raise InvalidHttpResponseError( "Invalid status code: `%r`" % response.status) if not response.reason: raise InvalidHttpResponseError( "Invalid status message: `%r`" % response.reason) if not response.body: raise InvalidHttpResponseError( "Body is invalid or empty: `%r`" % response.body) if not response.headers: raise InvalidHttpResponseError( "Headers are invalid or not specified: `%r`" % \ response.headers) if response.error: raise HttpError("Could not fetch credentials: HTTP %d - %s" \ % (response.status, response.reason,)) # The response body must be form URL-encoded. if not response.is_body_form_urlencoded(): if strict: raise InvalidContentTypeError( "OAuth credentials server response must " \ "have Content-Type: `%s`; got %r" % (CONTENT_TYPE_FORM_URLENCODED, response.content_type)) else: logging.warning( "Response parsing strict-mode disabled -- " \ "OAuth server credentials response specifies invalid " \ "Content-Type: expected %r; got %r", CONTENT_TYPE_FORM_URLENCODED, response.content_type) params = parse_qs(response.body) # Ensure the keys to this dictionary are unicode strings in Python 3.x. params = map_dict(lambda k, v: (utf8_decode_if_bytes(k), v), params) credentials = Credentials(identifier=params[OAUTH_PARAM_TOKEN][0], shared_secret=params[OAUTH_PARAM_TOKEN_SECRET][0]) return credentials, params
def percent_decode(value): """ Percent-decodes according to the OAuth spec:: percent_decode('c%40') -> 'c@' :see: Percent Encoding (http://tools.ietf.org/html/rfc5849#section-3.6) :param value: Value to percent-decode. '+' is treated as a ' ' character. :returns: Percent-decoded value. """ return utf8_decode_if_bytes(unquote_plus(value))
def predicate(name, _): """ Removes any parameters beginning with ``oauth_``. :param name: The parameter name. :returns: ``True`` if should be included; ``False`` otherwise. """ # This gets rid of any params beginning with "oauth_" name = utf8_decode_if_bytes(name) if not name.startswith(OAUTH_PARAM_PREFIX): return True else: logging.warning( "Protocol parameter ignored from URL query parameters: `%r`", name) return False
def predicate(name, value): """ Allows only valid oauth parameters. Raises an error if multiple values are specified. :param name: Protocol parameter name. :param value: Protocol parameter value. :returns: ``True`` if the parameter should be included; ``False`` otherwise. """ name = utf8_decode_if_bytes(name) if name.startswith(OAUTH_PARAM_PREFIX): # This gets rid of "realm" or any non-OAuth param. if len(value) > 1: # Multiple values for a protocol parameter are not allowed. # We don't silently discard values because failing fast # is better than simply logging and waiting for the user # to figure it out all by herself. # # See Making Requests # (http://tools.ietf.org/html/rfc5849#section-3.1) # Point 2. Each parameter MUST NOT appear more than once per # request, so we disallow multiple values for a protocol # parameter. raise InvalidOAuthParametersError( "Multiple protocol parameter values found %r=%r" \ % (name, value)) elif name in (OAUTH_PARAM_CONSUMER_SECRET, OAUTH_PARAM_TOKEN_SECRET, ): raise InsecureOAuthParametersError( "[SECURITY-ISSUE] Client attempting to transmit "\ "confidential protocol parameter `%r`. Communication "\ "is insecure if this is in your server logs." % (name, )) else: return True else: logging.warning("Invalid protocol parameter ignored: `%r`", name) return False
def test_does_not_encode_else_to_unicode(self): self.assertEqual(text.utf8_decode_if_bytes(constants.UNICODE_STRING), constants.UNICODE_STRING) self.assertTrue(builtins.is_unicode(text.utf8_decode_if_bytes(constants.UNICODE_STRING))) self.assertEqual(text.utf8_decode_if_bytes(constants.UNICODE_STRING2), constants.UNICODE_STRING2) self.assertTrue(builtins.is_unicode(text.utf8_decode_if_bytes(constants.UNICODE_STRING2))) self.assertEqual(text.utf8_decode_if_bytes(None), None) self.assertEqual(text.utf8_decode_if_bytes(False), False) self.assertEqual(text.utf8_decode_if_bytes(5), 5) self.assertEqual(text.utf8_decode_if_bytes([]), []) self.assertEqual(text.utf8_decode_if_bytes(()), ()) self.assertEqual(text.utf8_decode_if_bytes({}), {}) self.assertEqual(text.utf8_decode_if_bytes(object), object)
def test_encodes_bytes_to_unicode(self): self.assertEqual(text.utf8_decode_if_bytes(constants.UTF8_BYTES), constants.UNICODE_STRING) self.assertTrue(builtins.is_unicode(text.utf8_decode_if_bytes(constants.UTF8_BYTES))) self.assertEqual(text.utf8_decode_if_bytes(constants.UTF8_BYTES2), constants.UNICODE_STRING2) self.assertTrue(builtins.is_unicode(text.utf8_decode_if_bytes(constants.UTF8_BYTES2)))
def test_does_not_encode_else_to_unicode(self): self.assertEqual(utf8_decode_if_bytes(unicode_string), unicode_string) self.assertTrue(is_unicode(utf8_decode_if_bytes(unicode_string))) self.assertEqual(utf8_decode_if_bytes(unicode_string2), unicode_string2) self.assertTrue(is_unicode(utf8_decode_if_bytes(unicode_string2))) self.assertEqual(utf8_decode_if_bytes(None), None) self.assertEqual(utf8_decode_if_bytes(False), False) self.assertEqual(utf8_decode_if_bytes(5), 5) self.assertEqual(utf8_decode_if_bytes([]), []) self.assertEqual(utf8_decode_if_bytes(()), ()) self.assertEqual(utf8_decode_if_bytes({}), {}) self.assertEqual(utf8_decode_if_bytes(object), object)
def test_encodes_bytes_to_unicode(self): self.assertEqual(utf8_decode_if_bytes(utf8_bytes), unicode_string) self.assertTrue(is_unicode(utf8_decode_if_bytes(utf8_bytes))) self.assertEqual(utf8_decode_if_bytes(utf8_bytes2), unicode_string2) self.assertTrue(is_unicode(utf8_decode_if_bytes(utf8_bytes2)))