Skip to content

Commit

Permalink
Fix remaining lint issues (#23)
Browse files Browse the repository at this point in the history
* Fix remaining lint issues

* Undo some changes
  • Loading branch information
AaronAtDuo authored Jun 12, 2024
1 parent c3862f4 commit e35609e
Show file tree
Hide file tree
Showing 10 changed files with 51 additions and 32 deletions.
2 changes: 2 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[flake8]
max-line-length = 89
14 changes: 10 additions & 4 deletions duo_hmac/duo_hmac.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ def get_authentication_components(
Use the provided request components and calculate
- The final url (host + path + query string)
- The request body (if any)
- The request headers (including the authorization header per Duo's HMAC specification)
- The request headers (including the authorization
header per Duo's HMAC specification)
"""
duo_hmac_validation.validate_headers(in_headers)

Expand All @@ -50,7 +51,8 @@ def get_authentication_components(
# We need the request timestamp in RFC 2822 format
date_string = self.date_string_provider.get_rfc_2822_date_string()

# Duo does not currently support splitting parameters between the query string and body.
# Duo does not currently support splitting parameters
# between the query string and body.
# Put parameters in the correct place depending on the http method
# (body for POST, PUT, and PATCH, query string otherwise)
params_go_in_body = http_method.upper() in ("POST", "PUT", "PATCH")
Expand All @@ -75,7 +77,8 @@ def get_authentication_components(
if query_string:
uri = f"{uri}?{query_string}"

# Assemble final headers from input headers, authorization header, and content-type header
# Assemble final headers from input headers, authorization header, and
# content-type header
out_headers = dict(in_headers)
out_headers["Authorization"] = authn_header
if params_go_in_body:
Expand Down Expand Up @@ -120,7 +123,10 @@ def _generate_authentication_header(
return f"Basic {b64}"

def _sign_canonical_string(self, canon_string: str) -> hmac.HMAC:
"""Generate the SHA512 signature of the canonical string using the SKEY as the shared secret"""
"""
Generate the SHA512 signature of the canonical string
using the SKEY as the shared secret
"""
skey_bytes = self.skey.encode("utf-8")
canon_bytes = canon_string.encode("utf-8")

Expand Down
3 changes: 2 additions & 1 deletion duo_hmac/duo_hmac_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ def prepare_parameters(
def jsonize_parameters(parameters: dict) -> str:
"""Turn a parameter dictionary into a JSON string"""
if parameters is None:
# Is this the best choice? Should we return None instead (or allow json.dumps to return None)?
# Is this the best choice? Should we return None instead (or allow
# json.dumps to return None)?
parameters = {}

return json.dumps(parameters, sort_keys=True, separators=(",", ":"))
Expand Down
3 changes: 2 additions & 1 deletion duo_hmac/duo_hmac_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ def validate_headers(headers: dict[str, str]):
if key_lower.startswith("x-duo"):
if key_lower in headers_seen:
problems.append(
f"Duplicate x-duo headers are not supported, {key_lower} is duplicated."
f"Duplicate x-duo headers are not supported, \
{key_lower} is duplicated."
)
else:
headers_seen.add(key_lower)
Expand Down
8 changes: 6 additions & 2 deletions generate_curl_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,12 @@ def main():

parser = argparse.ArgumentParser(
prog="Duo API call generator for curl",
description="Generates a curl call for a Duo API call. Provide the HTTP method (default 'get'), the API path, and the call parameters as key=value pairs",
epilog="CLI flags: -m <HTTP method> -a <api path> -p key1=value1 key2=value2 ...",
description="""Generates a curl call for a Duo API call.
Provide the HTTP method (default 'get'),
the API path, and the call parameters as
key=value pairs""",
epilog="""CLI flags: -m <HTTP method> -a <api path>
-p key1=value1 key2=value2 ...""",
)
args_dict = get_arguments(parser)

Expand Down
2 changes: 1 addition & 1 deletion requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
requests >= 2.32.0
requests >= 2.32.0
2 changes: 1 addition & 1 deletion test.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#! /bin/bash

python3 -m unittest discover test/
python3 - m unittest discover test/
26 changes: 14 additions & 12 deletions test/test_canonicalization.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
API_PATH = "/test/the/api"
EMPTY_STRING = ""
# sha512 hash of the empty string; calculated with an external tool
EMPTY_STRING_HASH = "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
EMPTY_STRING_HASH = "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e" # noqa: E501
# sha512 hash of the "empty json" string '{}'; calculated with an external tool
EMPTY_JSON_HASH = "27c74670adb75075fad058d5ceaf7b20c4e7786c83bae8a32f626f9782af34c9a33c2046ef60fd2a7878d378e29fec851806bbd9a67878f3a9f1cda4830763fd"
EMPTY_JSON_HASH = "27c74670adb75075fad058d5ceaf7b20c4e7786c83bae8a32f626f9782af34c9a33c2046ef60fd2a7878d378e29fec851806bbd9a67878f3a9f1cda4830763fd" # noqa: E501

# With GET and no parameters, body, or header, expect:
# provided date string
Expand All @@ -27,14 +27,16 @@
# an empty line since there are no query string parameters
# the sha512 of the empty string since there is no body
# the sha512 of the empty string since there are no headers
EXPECTED_GET_NO_PARAMS = f"{DATE_STRING}\n{HTTP_GET}\n{API_HOST}\n{API_PATH}\n{EMPTY_STRING}\n{EMPTY_STRING_HASH}\n{EMPTY_STRING_HASH}"
# For POST with no parameters, empty body, no headers, the main difference is line 6, where we expect the hash of empty json
EXPECTED_POST_NO_PARAMS = f"{DATE_STRING}\n{HTTP_POST}\n{API_HOST}\n{API_PATH}\n{EMPTY_STRING}\n{EMPTY_JSON_HASH}\n{EMPTY_STRING_HASH}"
EXPECTED_GET_NO_PARAMS = f"{DATE_STRING}\n{HTTP_GET}\n{API_HOST}\n{API_PATH}\n{EMPTY_STRING}\n{EMPTY_STRING_HASH}\n{EMPTY_STRING_HASH}" # noqa: E501
# For POST with no parameters, empty body, no headers, the main difference
# is line 6, where we expect the hash of empty json
EXPECTED_POST_NO_PARAMS = f"{DATE_STRING}\n{HTTP_POST}\n{API_HOST}\n{API_PATH}\n{EMPTY_STRING}\n{EMPTY_JSON_HASH}\n{EMPTY_STRING_HASH}" # noqa: E501


class TestGenerateCanonicalStringBasics(unittest.TestCase):
# Test the handling of the date string, method, host, and path.
# The parameter, body, and headers all have dedicated canonicalization methods, tested separately
# The parameter, body, and headers all have dedicated canonicalization
# methods, tested separately
def test_get_no_parameter(self):
actual = duo_canonicalize.generate_canonical_string(
DATE_STRING, HTTP_GET, API_HOST, API_PATH, None, None, None
Expand Down Expand Up @@ -103,7 +105,7 @@ def test_empty_parameters(self):
b"punctuation": [b"!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"],
b"whitespace": [b"\t\n\x0b\x0c\r "],
},
"digits=0123456789&letters=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ&punctuation=%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F%3A%3B%3C%3D%3E%3F%40%5B%5C%5D%5E_%60%7B%7C%7D~&whitespace=%09%0A%0B%0C%0D%20",
"digits=0123456789&letters=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ&punctuation=%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F%3A%3B%3C%3D%3E%3F%40%5B%5C%5D%5E_%60%7B%7C%7D~&whitespace=%09%0A%0B%0C%0D%20", # noqa: E501
),
(
"Test unicode",
Expand All @@ -121,7 +123,7 @@ def test_empty_parameters(self):
"\ufba9\u41aa\ubd83\u840b\u2615\u3e6e\u652d\ua8b5\ud56bU"
],
},
"%E4%9A%9A%E2%A1%BB%E3%97%90%E8%BB%B3%E6%9C%A7%E5%80%AA%E0%A0%90%ED%82%91%C3%88%EC%85%B0=%E0%BD%85%E1%A9%B6%E3%90%9A%E6%95%8C%EC%88%BF%E9%AC%89%EA%AF%A2%E8%8D%83%E1%AC%A7%E6%83%90&%E7%91%89%E7%B9%8B%EC%B3%BB%E5%A7%BF%EF%B9%9F%E8%8E%B7%EA%B7%8C%E9%80%8C%EC%BF%91%E7%A0%93=%E8%B6%B7%E5%80%A2%E9%8B%93%E4%8B%AF%E2%81%BD%E8%9C%B0%EA%B3%BE%E5%98%97%E0%A5%86%E4%B8%B0&%E7%91%B0%E9%8C%94%E9%80%9C%E9%BA%AE%E4%83%98%E4%88%81%E8%8B%98%E8%B1%B0%E1%B4%B1%EA%81%82=%E1%9F%99%E0%AE%A8%E9%8D%98%EA%AB%9F%EA%90%AA%E4%A2%BE%EF%AE%96%E6%BF%A9%EB%9F%BF%E3%8B%B3&%EC%8B%85%E2%B0%9D%E2%98%A0%E3%98%97%E9%9A%B3F%E8%98%85%E2%83%A8%EA%B0%A1%E5%A4%B4=%EF%AE%A9%E4%86%AA%EB%B6%83%E8%90%8B%E2%98%95%E3%B9%AE%E6%94%AD%EA%A2%B5%ED%95%ABU",
"%E4%9A%9A%E2%A1%BB%E3%97%90%E8%BB%B3%E6%9C%A7%E5%80%AA%E0%A0%90%ED%82%91%C3%88%EC%85%B0=%E0%BD%85%E1%A9%B6%E3%90%9A%E6%95%8C%EC%88%BF%E9%AC%89%EA%AF%A2%E8%8D%83%E1%AC%A7%E6%83%90&%E7%91%89%E7%B9%8B%EC%B3%BB%E5%A7%BF%EF%B9%9F%E8%8E%B7%EA%B7%8C%E9%80%8C%EC%BF%91%E7%A0%93=%E8%B6%B7%E5%80%A2%E9%8B%93%E4%8B%AF%E2%81%BD%E8%9C%B0%EA%B3%BE%E5%98%97%E0%A5%86%E4%B8%B0&%E7%91%B0%E9%8C%94%E9%80%9C%E9%BA%AE%E4%83%98%E4%88%81%E8%8B%98%E8%B1%B0%E1%B4%B1%EA%81%82=%E1%9F%99%E0%AE%A8%E9%8D%98%EA%AB%9F%EA%90%AA%E4%A2%BE%EF%AE%96%E6%BF%A9%EB%9F%BF%E3%8B%B3&%EC%8B%85%E2%B0%9D%E2%98%A0%E3%98%97%E9%9A%B3F%E8%98%85%E2%83%A8%EA%B0%A1%E5%A4%B4=%EF%AE%A9%E4%86%AA%EB%B6%83%E8%90%8B%E2%98%95%E3%B9%AE%E6%94%AD%EA%A2%B5%ED%95%ABU", # noqa: E501
),
]

Expand Down Expand Up @@ -175,19 +177,19 @@ def test_empty_body(self):
(
"Ascii string body",
"I am an ascii string",
"c1710f1224e4973bfbb9ca1a297e63756a4b1736ebd1b646ba3d63a392b73d24a52ac8b4afb6eafca6dfe91f09e2e75117c377398a7d2f22136b05c038b94151",
"c1710f1224e4973bfbb9ca1a297e63756a4b1736ebd1b646ba3d63a392b73d24a52ac8b4afb6eafca6dfe91f09e2e75117c377398a7d2f22136b05c038b94151", # noqa: E501
),
(
"Unicode string body",
"î ❤ ựṉịʗƠΔѤ",
"b713c8cc2bfe672cf55133d81ca6fa802628c7c8968d444c186434146bcd0275a510d3fd725b0a8132882c4a60d8457420f252f84e4edd00fcf12aa7c4eb2246",
"b713c8cc2bfe672cf55133d81ca6fa802628c7c8968d444c186434146bcd0275a510d3fd725b0a8132882c4a60d8457420f252f84e4edd00fcf12aa7c4eb2246", # noqa: E501
),
(
"JSON string body",
json.dumps(
{"foo": "bar", "baz": 1, "nested": {"objects": {"are": {"neat": True}}}}
),
"cd97c6ef2f1db6a660f2b7b71235d16902d532e3a4b54afc07acebbf34a265d8f77c344706f3222bcb28009d8c4c5259daa388bddb7dcc1b163982dce6a0c1ec",
"cd97c6ef2f1db6a660f2b7b71235d16902d532e3a4b54afc07acebbf34a265d8f77c344706f3222bcb28009d8c4c5259daa388bddb7dcc1b163982dce6a0c1ec", # noqa: E501
),
]

Expand Down Expand Up @@ -217,7 +219,7 @@ def test_case_insensitive(self):
}

# Calculated with an external tool expecting lowercase header keys
expected = "60be11a30e0756f2ee2afdce1db849b987dcf86c1133394bd7bbbc9877920330c4d78aceacbb377ab8cbd9a8efe6a410fed4047376635ac71226ab46ca10d2b1"
expected = "60be11a30e0756f2ee2afdce1db849b987dcf86c1133394bd7bbbc9877920330c4d78aceacbb377ab8cbd9a8efe6a410fed4047376635ac71226ab46ca10d2b1" # noqa: E501
actual = duo_canonicalize.canonicalize_x_duo_headers(mixed_case_headers)

self.assertEqual(expected, actual)
Expand Down
20 changes: 11 additions & 9 deletions test/test_hmac.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,15 @@ def assert_components(
self.assertEqual(expected_body, actual_body)
self.assertDictEqual(expected_headers, actual_headers)

# For all tests, the expected Authorization header was calculated using duo_client_python
# For all tests, the expected Authorization header was calculated using
# duo_client_python

def test_get_no_params_no_headers(self):
expected_uri = f"{API_HOST}{API_PATH}"
expected_body = None
expected_headers = {
"x-duo-date": DATE_STRING,
"Authorization": "Basic RElBQkNERUZHSElKS0xNTk9QUVI6YzQ1NDYzOWQ3NWI0ZTNkOTliOGIxZDVlNDFjZDdiYjJkMmQ4YmE1NWY2ODExZjc4NmRmYjBlZGQ0ZmFjZDJmM2E1ZTZkNmM4MzdmMzFmNjgyNjcwNjMyNWI0ZWQ3ZGNkYzVmMTExNjQ5NDhlNTdhNzAzMmE1MjQ5OTBlMDE1ODM=",
"Authorization": "Basic RElBQkNERUZHSElKS0xNTk9QUVI6YzQ1NDYzOWQ3NWI0ZTNkOTliOGIxZDVlNDFjZDdiYjJkMmQ4YmE1NWY2ODExZjc4NmRmYjBlZGQ0ZmFjZDJmM2E1ZTZkNmM4MzdmMzFmNjgyNjcwNjMyNWI0ZWQ3ZGNkYzVmMTExNjQ5NDhlNTdhNzAzMmE1MjQ5OTBlMDE1ODM=", # noqa: E501
}

self.assert_components(
Expand All @@ -57,7 +58,7 @@ def test_post_no_params_no_headers(self):
expected_headers = {
"x-duo-date": DATE_STRING,
"Content-type": "application/json",
"Authorization": "Basic RElBQkNERUZHSElKS0xNTk9QUVI6MTJhMDVkNzgzYjJlNThlMzZmMzdkZjhhNjkwNzgzNTQ5NmZiZTIwZmIzZDA0YjE1MDM2YzgyYjE2OTRmYzU4ZDFjMDQ1MWI5MzdmYjliYTZlN2MyYjQ0ZDg5YjQ3M2FmNzA4MTY2MTgzZDIxNmFlYTEzZTUyNzQyYTU3ZjIzOWY=",
"Authorization": "Basic RElBQkNERUZHSElKS0xNTk9QUVI6MTJhMDVkNzgzYjJlNThlMzZmMzdkZjhhNjkwNzgzNTQ5NmZiZTIwZmIzZDA0YjE1MDM2YzgyYjE2OTRmYzU4ZDFjMDQ1MWI5MzdmYjliYTZlN2MyYjQ0ZDg5YjQ3M2FmNzA4MTY2MTgzZDIxNmFlYTEzZTUyNzQyYTU3ZjIzOWY=", # noqa: E501
}

self.assert_components(
Expand All @@ -69,7 +70,7 @@ def test_get_one_param_no_headers(self):
expected_body = None
expected_headers = {
"x-duo-date": DATE_STRING,
"Authorization": "Basic RElBQkNERUZHSElKS0xNTk9QUVI6ODhmZTgwZjNiMjQyYjk5MmY0YjMwMTQwOGQ1MjRhODg2Mjc0ZDNlZDBjNGM3YmQxODRlMWI0ZmYzNzhlNjhlYTA1ZDk0MzNjMDk5MzgwNzhjNDk1MTdhNmM0MjY0Yzk1MGJlOWZmNWNjMjhhZDJkNDQ4Y2VhMjRiYzkzODg3Y2E=",
"Authorization": "Basic RElBQkNERUZHSElKS0xNTk9QUVI6ODhmZTgwZjNiMjQyYjk5MmY0YjMwMTQwOGQ1MjRhODg2Mjc0ZDNlZDBjNGM3YmQxODRlMWI0ZmYzNzhlNjhlYTA1ZDk0MzNjMDk5MzgwNzhjNDk1MTdhNmM0MjY0Yzk1MGJlOWZmNWNjMjhhZDJkNDQ4Y2VhMjRiYzkzODg3Y2E=", # noqa: E501
}

in_params = {"foo": "bar"}
Expand All @@ -83,7 +84,7 @@ def test_post_one_param_no_headers(self):
expected_headers = {
"x-duo-date": DATE_STRING,
"Content-type": "application/json",
"Authorization": "Basic RElBQkNERUZHSElKS0xNTk9QUVI6NjQzNzcwYWYzMTAwNmM1ODRhNzU4ZDkyNjI1MGU0NmE5MGQ3OTEwMGQyMWY3YTAzMTNjM2U3N2Q2NGZhM2M1ZDJjOTRlMmM5MDgxYTJiNjUzNDNjYzNkNWYyZWQyMWY3MzAwZWE1MGIwMDY0MGNiMTc2MGYzMjMxOTIzMDdkMzc=",
"Authorization": "Basic RElBQkNERUZHSElKS0xNTk9QUVI6NjQzNzcwYWYzMTAwNmM1ODRhNzU4ZDkyNjI1MGU0NmE5MGQ3OTEwMGQyMWY3YTAzMTNjM2U3N2Q2NGZhM2M1ZDJjOTRlMmM5MDgxYTJiNjUzNDNjYzNkNWYyZWQyMWY3MzAwZWE1MGIwMDY0MGNiMTc2MGYzMjMxOTIzMDdkMzc=", # noqa: E501
}

in_params = {"foo": "bar"}
Expand All @@ -99,7 +100,7 @@ def test_get_multi_param_multi_headers(self):
"x-duo-bar": "foo",
"non-duo-bar": "duo",
"x-duo-date": DATE_STRING,
"Authorization": "Basic RElBQkNERUZHSElKS0xNTk9QUVI6ZDQ2NTU2NzI2ODAwNDE1ZGM3OGNlZmMzZmI0ZTExZGNmM2VlMTM0MjkwNGYyNzZlZDVjOGUzNDI3ODc4YmQ1Mzc2ZWE2YzU1NTFiOTBiZjcwN2ZhYjUzZjZmMWQyMGExMTQ4OTg4OTg3MDVkMjgyNjg4MjRlZGQwYmU1ZjFkNTM=",
"Authorization": "Basic RElBQkNERUZHSElKS0xNTk9QUVI6ZDQ2NTU2NzI2ODAwNDE1ZGM3OGNlZmMzZmI0ZTExZGNmM2VlMTM0MjkwNGYyNzZlZDVjOGUzNDI3ODc4YmQ1Mzc2ZWE2YzU1NTFiOTBiZjcwN2ZhYjUzZjZmMWQyMGExMTQ4OTg4OTg3MDVkMjgyNjg4MjRlZGQwYmU1ZjFkNTM=", # noqa: E501
}

in_params = {"foo": "bar", "one": "1", "bool": "true"}
Expand All @@ -126,7 +127,7 @@ def test_post_multi_params_multi_headers(self):
"non-duo-bar": "duo",
"x-duo-date": DATE_STRING,
"Content-type": "application/json",
"Authorization": "Basic RElBQkNERUZHSElKS0xNTk9QUVI6ZGVmN2I3MzU5YjAzOTk1NDNiNjFkM2QxYTQyNTJjMTkwOGViNDg2MDM5MTY4YWE3ZDFjOTM1NDVmMDUyZTEyMTA2MmU0ZDBkM2NhZTgwZjBmMTI1ZGM0OTdjNDNjNTNiNjJjOWRiNThjYWNkMjEzOTRhM2IxN2FkOTcyZTM3OTM=",
"Authorization": "Basic RElBQkNERUZHSElKS0xNTk9QUVI6ZGVmN2I3MzU5YjAzOTk1NDNiNjFkM2QxYTQyNTJjMTkwOGViNDg2MDM5MTY4YWE3ZDFjOTM1NDVmMDUyZTEyMTA2MmU0ZDBkM2NhZTgwZjBmMTI1ZGM0OTdjNDNjNTNiNjJjOWRiNThjYWNkMjEzOTRhM2IxN2FkOTcyZTM3OTM=", # noqa: E501
}

in_params = {"foo": "bar", "one": "1", "bool": "true"}
Expand All @@ -150,15 +151,16 @@ def test_post_non_string_parameter_types(self):
expected_headers = {
"x-duo-date": DATE_STRING,
"Content-type": "application/json",
"Authorization": "Basic RElBQkNERUZHSElKS0xNTk9QUVI6NjhjYWI2YTQyYmUyMTZhMGUwNTU3NzJlZDhkODg3MGIzODNmYzk4NmVlNGJkN2I5MjM0Njg5ZTIzOWJlNjc3YzhlZGY2MWUzM2VhZGRkODNlMmI5NDE0Yzk4ZmYzMGJmY2EwYmYyZTFmNDQ4MzEwNzRmNWM0NzRiZjRhZjlmZDc=",
"Authorization": "Basic RElBQkNERUZHSElKS0xNTk9QUVI6NjhjYWI2YTQyYmUyMTZhMGUwNTU3NzJlZDhkODg3MGIzODNmYzk4NmVlNGJkN2I5MjM0Njg5ZTIzOWJlNjc3YzhlZGY2MWUzM2VhZGRkODNlMmI5NDE0Yzk4ZmYzMGJmY2EwYmYyZTFmNDQ4MzEwNzRmNWM0NzRiZjRhZjlmZDc=", # noqa: E501
}

in_params = {"foo": "bar", "one": "1", "bool": "true"}
self.assert_components(
HTTP_POST, in_params, None, expected_uri, expected_body, expected_headers
)

# As written, non-string parameters don't work for GET calls. This is arguably a bug but test the existing behavior for now
# As written, non-string parameters don't work for GET calls. This is
# arguably a bug but test the existing behavior for now
def test_unsupported_get_parameter_types(self):
in_params1 = {
"foo": "bar",
Expand Down
3 changes: 2 additions & 1 deletion test/test_hmac_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ def test_multiple_parameters(self):
actual = duo_hmac_utils.normalize_parameters(test_input)
self.assertDictEqual(expected, actual)

# None of these make any sense, but they work! Hopefully we can break them all some day by enforcing sensible typing
# None of these make any sense, but they work! Hopefully we can break
# them all some day by enforcing sensible typing
edge_case_test_cases = [
("None 1-item list value", {"string": [None]}, {b"string": [None]}),
(
Expand Down

0 comments on commit e35609e

Please sign in to comment.