def test_alphabets_are_not_encoded(self): lowercase_alphabets = [chr(x) for x in range(ord('a'), ord('z') + 1)] uppercase_alphabers = [chr(x) for x in range(ord('A'), ord('Z') + 1)] alphabets = lowercase_alphabets + uppercase_alphabers for alphabet in alphabets: self.assertEqual(percent_encode(alphabet), alphabet.encode("ascii"), "Alphabets should not be encoded.")
def test_character_encoding_is_uppercase(self): for char in self._unsafe_characters: for c in percent_encode(char): if isinstance(c, int): c = chr(c) if c.isalpha(): self.assertTrue( c.isupper(), "Percent-encoding is not uppercase: %r for char: %r" \ % (c, char))
def test_oauth_test_cases(self): # http://wiki.oauth.net/w/page/12238556/TestCases ex = [ ('abcABC123', 'abcABC123'), ('-._~', '-._~'), ('%', '%25'), ('+', '%2B'), ('&=*', '%26%3D%2A'), (u'\u000A', '%0A'), (u'\u0020', '%20'), (u'\u007F', '%7F'), (u'\u0080', '%C2%80'), (u'\u3001', '%E3%80%81'), ] for k, v in ex: assert_equal(percent_encode(k), v)
def _generate_plaintext_signature(client_shared_secret, token_or_temporary_shared_secret=None): """ Calculates the PLAINTEXT signature. :param client_shared_secret: Client (consumer) shared secret. :param token_or_temporary_shared_secret: Token/temporary credentials shared secret if available. :returns: PLAINTEXT signature. """ client_shared_secret = client_shared_secret or "" token_or_temporary_shared_secret = token_or_temporary_shared_secret or "" return "&".join([ percent_encode(a) for a in [ client_shared_secret, token_or_temporary_shared_secret]])
def generate_signature_base_string(method, url, oauth_params): """ Calculates a signature base string based on the URL, method, and oauth parameters. Any query parameter by the name "oauth_signature" will be excluded from the base string. :see: Signature base string (http://tools.ietf.org/html/rfc5849#section-3.4.1) :param method: HTTP request method. :param url: The URL. If this includes a query string, query parameters are first extracted and encoded as well. All protocol-specific parameters will be ignored from the query string. :param oauth_params: Protocol-specific parameters must be specified in this dictionary. All non-protocol parameters will be ignored. :returns: Base string. """ allowed_methods = ("POST", "GET", "PUT", "DELETE", "OPTIONS", "TRACE", "HEAD", "CONNECT", "PATCH") method_normalized = method.upper() if method_normalized not in allowed_methods: raise InvalidHttpMethodError("Method must be one of the HTTP methods %s: got `%s` instead" % (allowed_methods, method)) if not url: raise InvalidUrlError("URL must be specified: got `%r`" % (url, )) if not isinstance(oauth_params, dict): raise InvalidOAuthParametersError("Dictionary required: got `%r`" % (oauth_params, )) scheme, netloc, path, matrix_params, query, fragment = urlparse_normalized(url) query_string = _generate_signature_base_string_query(query, oauth_params) normalized_url = urlunparse((scheme, netloc, path, matrix_params, None, None)) return "&".join([ percent_encode(e) for e in [ method_normalized, normalized_url, query_string]])
def test_unsafe_characters_are_encoded(self): for char in self._unsafe_characters: self.assertNotEqual(percent_encode(char), char)
def test_unicode_utf8_encoded(self): assert_equal(percent_encode(self.uni_unicode_object), "%C2%AE")
def test_space_is_not_encoded_as_plus(self): self.assertNotEqual(percent_encode(" "), b("+")) self.assertEqual(percent_encode(" "), b("%20"))
def test_safe_symbols_are_not_encoded(self): safe_symbols = ["-", ".", "_", "~"] for symbol in safe_symbols: self.assertEqual(percent_encode(symbol), symbol.encode("ascii"), "Symbol %s should not be encoded." % (symbol,))
def test_digits_are_not_encoded(self): digits = [str(x) for x in range(10)] for digit in digits: self.assertEqual(percent_encode(digit), digit.encode("ascii"), "Digits should not be encoded.")
def test_utf8_bytestring_left_as_is(self): assert_equal(percent_encode(self.uni_utf8_bytes), "%C2%AE")
def test_unicode_utf8_encoded(self): self.assertEqual(percent_encode(self.uni_unicode_object), b("%C2%AE"))
def test_non_string_values_are_stringified(self): self.assertEqual(percent_encode(True), b("True")) self.assertEqual(percent_encode(5), b("5"))
def test_digits_are_not_encoded(self): digits = [str(x) for x in range(10)] for digit in digits: assert_equal(percent_encode(digit), digit, "Digits should not be encoded.")
def test_space_is_not_encoded_as_plus(self): assert_not_equal(percent_encode(" "), "+") assert_equal(percent_encode(" "), "%20")
def test_non_string_values_are_stringified(self): assert_equal(percent_encode(True), "True") assert_equal(percent_encode(5), "5")
def test_character_encoding_is_uppercase(self): for char in self._unsafe_characters: for c in percent_encode(char): if c.isalpha(): assert_true(c.isupper(), "Percent-encoding is not uppercase: %r for char: %r" % (c, char))
def test_percent_encoded(self): for char in self._unsafe_characters: self.assertEqual( percent_encode(char)[0], b("%")[0], "Character not percent-encoded.")
def test_oauth_test_cases(self): # http://wiki.oauth.net/w/page/12238556/TestCases ex = constants.percent_encode_test_cases for k, v in ex: self.assertEqual(percent_encode(k), v)
def verify_hmac_sha1_signature(signature, base_string, client_shared_secret, token_shared_secret=None, debug=False): """ Verifies an HMAC-SHA1 signature for a base string. :see: HMAC-SHA1 (http://tools.ietf.org/html/rfc5849#section-3.4.2) :param signature: The signature to verify. :param base_string: The base string. :param client_shared_secret: Client (consumer) shared secret. :param token_shared_secret: Token/temporary credentials shared secret if available. :param debug: Default ``False``. ``True`` to turn on debugging mode, which attempts to find out why signature verification fails, if it does; ``False`` otherwise. :returns: A tuple of (whether signature matches (boolean), error message (None if it succeeded)). """ key = _generate_plaintext_signature(client_shared_secret, token_shared_secret) check_ok = (signature == hmac_sha1_base64_digest(key, base_string)) if check_ok: err = None else: err = "Invalid signature" if not check_ok and debug: # Try to find out why it didn't match. # We need to help the poor human souls on the other # side of this mess who are trying to debug their OAuth clients. # This is not going to detect 100% of the cases, because it is # too easy to screw up on the client side. Anything could be wrong. # We're just trying to find out some common problems. # Assume correct base string but detect incorrect signature encoding. key = _generate_plaintext_signature(client_shared_secret, token_shared_secret, _percent_encode=False) if signature == hmac_sha1_base64_digest(key, base_string): return check_ok, "Invalid signature: signature elements " \ "are not percent-encoded properly" # Assume correct base string but detect missing ampersands in signature. if client_shared_secret and not token_shared_secret: key = percent_encode(client_shared_secret) + SYMBOL_AMPERSAND if signature == hmac_sha1_base64_digest(key, base_string): return check_ok, "Invalid signature: missing ampersand `&` " \ "after client shared secret in signature" elif not client_shared_secret and token_shared_secret: key = SYMBOL_AMPERSAND + percent_encode(token_shared_secret) if signature == hmac_sha1_base64_digest(key, base_string): return check_ok, "Invalid signature: missing ampersand `&` "\ "before token secret in signature" elif not client_shared_secret and not token_shared_secret: key = SYMBOL_AMPERSAND if signature == hmac_sha1_base64_digest(key, base_string): return check_ok, "Invalid signature: missing ampersand `&` "\ "without secrets in signature" elif client_shared_secret and token_shared_secret: key = percent_encode(client_shared_secret) + \ SYMBOL_AMPERSAND + percent_encode(token_shared_secret) if signature == hmac_sha1_base64_digest(key, base_string): return check_ok, "Invalid signature: missing ampersand `&` "\ "between signature secrets" # Assume incorrect base string return check_ok, "Invalid signature: check base string?" return check_ok, err
def test_utf8_bytestring_left_as_is(self): self.assertEqual(percent_encode(self.uni_utf8_bytes), b("%C2%AE"))