diff --git a/duo_client/client.py b/duo_client/client.py index 1748aee..cc87d3c 100644 --- a/duo_client/client.py +++ b/duo_client/client.py @@ -36,6 +36,7 @@ from .https_wrapper import CertValidatingHTTPSConnection DEFAULT_CA_CERTS = os.path.join(os.path.dirname(__file__), 'ca_certs.pem') +CA_BUNDLE_VERSION = '1.0' def canon_params(params): @@ -243,7 +244,14 @@ def __init__(self, ikey, skey, host, if ca_certs is None: ca_certs = DEFAULT_CA_CERTS self.ca_certs = ca_certs - self.user_agent = user_agent + ca_pinning_status = 'disabled' if disable_ca_pinning else 'enabled' + if user_agent: + self.user_agent = ( + f"{user_agent} ca_bundle/{CA_BUNDLE_VERSION}" + f" (ca_pinning={ca_pinning_status})" + ) + else: + self.user_agent = user_agent self.set_proxy(host=None, proxy_type=None) self.paging_limit = paging_limit self.digestmod = digestmod diff --git a/tests/test_client.py b/tests/test_client.py index 6128f27..ce7f300 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -806,6 +806,50 @@ def test_disable_ca_pinning_with_disable_ca_certs_raises(self): self.assertIn("Cannot both disable CA pinning", str(ctx.exception)) +class TestUserAgent(unittest.TestCase): + """Tests for user agent string including CA bundle version and pinning status.""" + + def test_default_user_agent_includes_ca_bundle_version(self): + client = duo_client.client.Client('ikey', 'skey', 'host.example.com') + self.assertIn( + f"ca_bundle/{duo_client.client.CA_BUNDLE_VERSION}", + client.user_agent, + ) + + def test_default_user_agent_includes_ca_pinning_enabled(self): + client = duo_client.client.Client('ikey', 'skey', 'host.example.com') + self.assertIn("(ca_pinning=enabled)", client.user_agent) + + def test_user_agent_includes_ca_pinning_disabled(self): + client = duo_client.client.Client('ikey', 'skey', 'host.example.com', + disable_ca_pinning=True) + self.assertIn("(ca_pinning=disabled)", client.user_agent) + + def test_default_user_agent_format(self): + client = duo_client.client.Client('ikey', 'skey', 'host.example.com') + expected = ( + f"Duo API Python/{duo_client.client.__version__}" + f" ca_bundle/{duo_client.client.CA_BUNDLE_VERSION}" + f" (ca_pinning=enabled)" + ) + self.assertEqual(client.user_agent, expected) + + def test_custom_user_agent_includes_ca_fields(self): + client = duo_client.client.Client('ikey', 'skey', 'host.example.com', + user_agent='MyApp/1.0') + expected = ( + f"MyApp/1.0" + f" ca_bundle/{duo_client.client.CA_BUNDLE_VERSION}" + f" (ca_pinning=enabled)" + ) + self.assertEqual(client.user_agent, expected) + + def test_empty_user_agent_not_modified(self): + client = duo_client.client.Client('ikey', 'skey', 'host.example.com', + user_agent='') + self.assertEqual(client.user_agent, '') + + class TestDisableCaPinningConnect(unittest.TestCase): """Tests that _connect() uses the correct connection type."""