Skip to content

Commit

Permalink
Adding support to specify client certs (#272)
Browse files Browse the repository at this point in the history
Allows custom SSL certificates to be specified for HTTP requests
  • Loading branch information
dkmalav authored and michaelboulton committed Feb 21, 2019
1 parent 34c6c8e commit 1858fcb
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 2 deletions.
8 changes: 7 additions & 1 deletion tavern/_plugins/rest/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,11 @@ def get_request_args(rspec, test_block_config):
"headers",
"files",
"timeout",
"cert",
# Ideally this would just be passed through but requests seems to error
# if we pass a list instead of a tuple, so we have to manually convert
# it further down
# "auth",
# "auth"
]

optional_with_default = {"verify": True, "stream": False}
Expand Down Expand Up @@ -111,6 +112,10 @@ def add_request_args(keys, optional):
if "auth" in fspec:
request_args["auth"] = tuple(fspec["auth"])

if "cert" in fspec:
if isinstance(fspec["cert"], list):
request_args["cert"] = tuple(fspec["cert"])

if "timeout" in fspec:
# Needs to be a tuple, it being a list doesn't work
if isinstance(fspec["timeout"], list):
Expand Down Expand Up @@ -186,6 +191,7 @@ def __init__(self, session, rspec, test_block_config):
"files",
"stream",
"timeout",
"cert",
# "cookies",
# "hooks",
}
Expand Down
31 changes: 31 additions & 0 deletions tavern/schemas/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,3 +388,34 @@ def check_is_timeout_val(v):
check_is_timeout_val(value)

return True


def validate_verify_bool_or_str(value, rule_obj, path):
"""Make sure the 'verify' key is either a bool or a str"""
# pylint: disable=unused-argument

if not isinstance(value, (bool, str)) and not is_bool_like(value):
raise BadSchemaError(
"'verify' has to be either a boolean or the path to a CA_BUNDLE file or directory with certificates of trusted CAs"
)

return True


def validate_cert_tuple_or_str(value, rule_obj, path):
"""Make sure the 'cert' key is either a str or tuple"""
# pylint: disable=unused-argument

err_msg = (
"The 'cert' key must be a single file (containing the private key and the certificate) "
"or as a tuple of both files"
)

if not isinstance(value, (str, tuple, list)):
raise BadSchemaError(err_msg)

if isinstance(value, (list, tuple)):
if len(value) != 2:
raise BadSchemaError(err_msg)

return True
7 changes: 6 additions & 1 deletion tavern/schemas/tests.schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,14 @@ schema;stage:
required: false
func: validate_timeout_tuple_or_float

cert:
func: validate_cert_tuple_or_str
type: any
required: false

verify:
type: any
func: bool_variable
func: validate_verify_bool_or_str
required: false

meta:
Expand Down
16 changes: 16 additions & 0 deletions tests/unit/test_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,22 @@ def test_headers_no_content_type_change(self, req, includes, extra_headers):

assert "content-type" not in [i.lower() for i in args["headers"].keys()]

@pytest.mark.parametrize("cert_value", ("a", ("a", "b"), ["a", "b"]))
def test_cert_with_valid_values(self, req, includes, cert_value):
req["cert"] = cert_value
args = get_request_args(req, includes)
if isinstance(cert_value, list):
assert args["cert"] == (cert_value[0], cert_value[1])
else:
assert args["cert"] == cert_value

@pytest.mark.parametrize("verify_values", (True, False, "a"))
def test_verity_with_valid_values(self, req, includes, verify_values):
req["verify"] = verify_values
args = get_request_args(req, includes)

assert args["verify"] == verify_values


class TestExtFunctions:
def test_get_from_function(self, req, includes):
Expand Down
28 changes: 28 additions & 0 deletions tests/unit/test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,31 @@ def test_timeout_tuple_fail(self, test_dict, incorrect_value):

with pytest.raises(BadSchemaError):
verify_tests(test_dict)


class TestCert:
@pytest.mark.parametrize("correct_value", ("a", ("a", "b"), ["a", "b"]))
def test_cert_as_string_tuple_list(self, test_dict, correct_value):
test_dict["stages"][0]["request"]["cert"] = correct_value
verify_tests(test_dict)

@pytest.mark.parametrize(
"incorrect_value", (None, True, {}, ("a", "b", "c"), [], ["a"], ["a", "b", "c"])
)
def test_cert_as_tuple(self, test_dict, incorrect_value):
test_dict["stages"][0]["request"]["cert"] = incorrect_value
with pytest.raises(BadSchemaError):
verify_tests(test_dict)


class TestVerify:
@pytest.mark.parametrize("correct_value", ("a", True, False))
def test_verify_with_string_boolean(self, test_dict, correct_value):
test_dict["stages"][0]["request"]["verify"] = correct_value
verify_tests(test_dict)

@pytest.mark.parametrize("incorrect_value", (None, 1, {}, [], ("a", "b")))
def test_verify_with_incorrect_value(self, test_dict, incorrect_value):
test_dict["stages"][0]["request"]["verify"] = incorrect_value
with pytest.raises(BadSchemaError):
verify_tests(test_dict)

0 comments on commit 1858fcb

Please sign in to comment.