diff --git a/arango/client.py b/arango/client.py index f7a47171..dc2045bd 100644 --- a/arango/client.py +++ b/arango/client.py @@ -45,8 +45,13 @@ class ArangoClient: the de-serialized object. If not given, ``json.loads`` is used by default. :type deserializer: callable - :param verify_certificate: Verify TLS certificates. - :type verify_certificate: bool + :param verify_override: Override TLS certificate verification. This will + override the verify method of the underlying HTTP client. + None: Do not change the verification behavior of the underlying HTTP client. + True: Verify TLS certificate using the system CA certificates. + False: Do not verify TLS certificate. + str: Path to a custom CA bundle file or directory. + :type verify_override: Union[bool, str, None] """ def __init__( @@ -57,7 +62,7 @@ def __init__( http_client: Optional[HTTPClient] = None, serializer: Callable[..., str] = lambda x: dumps(x), deserializer: Callable[[str], Any] = lambda x: loads(x), - verify_certificate: bool = True, + verify_override: Union[bool, str, None] = None, ) -> None: if isinstance(hosts, str): self._hosts = [host.strip("/") for host in hosts.split(",")] @@ -79,9 +84,10 @@ def __init__( self._deserializer = deserializer self._sessions = [self._http.create_session(h) for h in self._hosts] - # set flag for SSL/TLS certificate verification - for session in self._sessions: - session.verify = verify_certificate + # override SSL/TLS certificate verification if provided + if verify_override is not None: + for session in self._sessions: + session.verify = verify_override def __repr__(self) -> str: return f"" diff --git a/docs/certificates.rst b/docs/certificates.rst index 5dde7191..6440df20 100644 --- a/docs/certificates.rst +++ b/docs/certificates.rst @@ -9,13 +9,20 @@ By default, self-signed certificates will cause trouble when connecting. client = ArangoClient(hosts="https://localhost:8529") -In order to make connections work even when using self-signed certificates, the -`verify_certificates` option can be disabled when creating the `ArangoClient` -instance: +To make connections work even when using self-signed certificates, you can +provide the certificate CA bundle or turn the verification off. + +If you want to have fine-grained control over the HTTP connection, you should define +your HTTP client as described in the :ref:`HTTPClients` section. + +The ``ArangoClient`` class provides an option to override the verification behavior, +no matter what has been defined in the underlying HTTP session. +You can use this option to disable verification or provide a custom CA bundle without +defining a custom HTTP Client. .. code-block:: python - client = ArangoClient(hosts="https://localhost:8529", verify_certificate=False) + client = ArangoClient(hosts="https://localhost:8529", verify_override=False) This will allow connecting, but the underlying `urllib3` library may still issue warnings due to the insecurity of using self-signed certificates. @@ -26,5 +33,4 @@ application: .. code-block:: python import requests - requests.packages.urllib3.disable_warnings() - + requests.packages.urllib3.disable_warnings() diff --git a/docs/http.rst b/docs/http.rst index 652d1025..44ff1724 100644 --- a/docs/http.rst +++ b/docs/http.rst @@ -1,3 +1,5 @@ +.. _HTTPClients: + HTTP Clients ------------ diff --git a/tests/test_client.py b/tests/test_client.py index 042779ff..0a170b90 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,7 +1,9 @@ import json +from typing import Union import pytest from pkg_resources import get_distribution +from requests import Session from arango.client import ArangoClient from arango.database import StandardDatabase @@ -108,3 +110,54 @@ def send_request( # Set verify to True to send a test API call on initialization. client.db(db.name, username, password, verify=True) assert http_client.counter == 1 + + +def test_client_override_validate() -> None: + # custom http client that manipulates the underlying session + class MyHTTPClient(DefaultHTTPClient): + def __init__(self, verify: Union[None, bool, str]) -> None: + super().__init__() + self.verify = verify + + def create_session(self, host: str) -> Session: + session = super().create_session(host) + session.verify = self.verify + return session + + def assert_verify( + http_client_verify: Union[None, str, bool], + arango_override: Union[None, str, bool], + expected_result: Union[None, str, bool], + ) -> None: + http_client = MyHTTPClient(verify=http_client_verify) + client = ArangoClient( + hosts="http://127.0.0.1:8529", + http_client=http_client, + verify_override=arango_override, + ) + # make sure there is at least 1 session + assert len(client._sessions) > 0 + for session in client._sessions: + # make sure the final session.verify has expected value + assert session.verify == expected_result + + assert_verify(None, None, None) + assert_verify(None, True, True) + assert_verify(None, False, False) + assert_verify(None, "test", "test") + + assert_verify(True, None, True) + assert_verify(True, True, True) + assert_verify(True, "test", "test") + assert_verify(True, False, False) + + assert_verify(False, None, False) + assert_verify(False, True, True) + assert_verify(False, "test", "test") + assert_verify(False, False, False) + + assert_verify("test", None, "test") + assert_verify("test", True, True) + assert_verify("test", False, False) + assert_verify("test", False, False) + assert_verify("test", "foo", "foo")