diff --git a/conftest.py b/conftest.py index 7e6dc39f..5938587a 100644 --- a/conftest.py +++ b/conftest.py @@ -83,9 +83,3 @@ def enterprise_config(pytestconfig): return core.HttpConfiguration(uri, api_key) else: pytest.skip("--enterprise-uri or --enterprise-api-key setting not found") - - -@pytest.fixture(scope="session", autouse=True) -def pydantic_forbid_extra_fields(): - """Fixture to disable allowing extra fields in our Pydantic models.""" - JsonModel.Config.extra = Extra.forbid diff --git a/nisystemlink/clients/core/_jupyter_http_configuration.py b/nisystemlink/clients/core/_jupyter_http_configuration.py index 04bd8ea0..cb3936c0 100644 --- a/nisystemlink/clients/core/_jupyter_http_configuration.py +++ b/nisystemlink/clients/core/_jupyter_http_configuration.py @@ -3,8 +3,10 @@ """Implementation of JupyterHttpConfiguration.""" import os +import sys from nisystemlink.clients import core +from nisystemlink.clients.core._internal._path_constants import PathConstants class JupyterHttpConfiguration(core.HttpConfiguration): @@ -20,6 +22,28 @@ def __init__(self) -> None: Raises: KeyError: if the expected environment variables are not set. """ - super().__init__( - os.environ[self._HTTP_URI_ENV_VAR], os.environ[self._HTTP_API_KEY_ENV_VAR] + http_uri = os.environ[self._HTTP_URI_ENV_VAR] + api_key = os.environ[self._HTTP_API_KEY_ENV_VAR] + systemlink_server_cert_path = ( + PathConstants.application_data_directory + / "Certificates" + / "http-server" + / "http-server.cer" ) + + # SystemLink Server 26Q3 restricts notebook executions (by default). Access to the `HttpConfigurations` folder + # is no longer granted for notebooks running under the JupyterHub/NotebookExecution services. Since config + # files under `HttpConfigurations` are no longer readable, creating client objects from this library, from SLS + # notebooks, will default to using this `JupyterHttpConfiguration`. However, this does not set a `cert_path`, + # so HTTPS requests will fail. + # When running SystemLink Server notebooks (Windows), we will therefore pass the Web Server CA certificate's + # path as the `cert_path`, if the file exists. This will allow clients created with the default configuration + # to use this CA certificate. + # If the file does not exist (when the Web Server is configured in HTTP mode), do not pass it; an invalid + # `cert_path` will lead to errors. + if sys.platform.startswith("win") and os.path.exists( + systemlink_server_cert_path + ): + super().__init__(http_uri, api_key, cert_path=systemlink_server_cert_path) + else: + super().__init__(http_uri, api_key) diff --git a/tests/core/test_jupyter_http_configuration.py b/tests/core/test_jupyter_http_configuration.py new file mode 100644 index 00000000..53edfa0b --- /dev/null +++ b/tests/core/test_jupyter_http_configuration.py @@ -0,0 +1,110 @@ +import os +import pathlib +from unittest.mock import patch + +from nisystemlink.clients.core import JupyterHttpConfiguration + + +_HTTP_URI_ENV_VAR = "SYSTEMLINK_HTTP_URI" +_HTTP_API_KEY_ENV_VAR = "SYSTEMLINK_API_KEY" +_SYSTEMLINK_SERVER_CERT_PATH = ( + pathlib.Path(r"C:\ProgramData\National Instruments\Skyline") + / "Certificates" + / "http-server" + / "http-server.cer" +) +_SYSTEMLINK_API_KEY_HEADER = "x-ni-api-key" + + +class TestJupyterHttpConfiguration: + # Patch PathConstants._application_data_directory so that we have the proper Windows path + # when running the tests on Linux. + @patch( + "nisystemlink.clients.core._internal._path_constants.PathConstants._application_data_directory", + pathlib.Path(r"C:\ProgramData\National Instruments\Skyline"), + ) + def test__cert_file_exists_on_windows__cert_path_is_passed(self): + def mock_exists(path): + if path == _SYSTEMLINK_SERVER_CERT_PATH: + return True + return False + + with patch("sys.platform", "win32"): + with patch("os.path.exists", side_effect=mock_exists): + with patch.dict( + os.environ, + { + _HTTP_URI_ENV_VAR: "https://my-uri", + _HTTP_API_KEY_ENV_VAR: "my-api-key", + }, + ): + config = JupyterHttpConfiguration() + assert config.server_uri == "https://my-uri" + assert config.api_keys[_SYSTEMLINK_API_KEY_HEADER] == "my-api-key" + assert config.cert_path == _SYSTEMLINK_SERVER_CERT_PATH + + # Patch PathConstants._application_data_directory so that we have the proper Windows path + # when running the tests on Linux. + @patch( + "nisystemlink.clients.core._internal._path_constants.PathConstants._application_data_directory", + pathlib.Path(r"C:\ProgramData\National Instruments\Skyline"), + ) + def test__cert_file_does_not_exist_on_windows__cert_path_is_not_passed(self): + def mock_exists(path): + if path == _SYSTEMLINK_SERVER_CERT_PATH: + return False + return True + + with patch("sys.platform", "win32"): + with patch("os.path.exists", side_effect=mock_exists): + with patch.dict( + os.environ, + { + _HTTP_URI_ENV_VAR: "https://my-uri", + _HTTP_API_KEY_ENV_VAR: "my-api-key", + }, + ): + config = JupyterHttpConfiguration() + assert config.server_uri == "https://my-uri" + assert config.api_keys[_SYSTEMLINK_API_KEY_HEADER] == "my-api-key" + assert config.cert_path is None + + def test__cert_file_exists_on_linux__cert_path_is_not_passed(self): + def mock_exists(path): + if path == _SYSTEMLINK_SERVER_CERT_PATH: + return True + return False + + with patch("sys.platform", "linux"): + with patch("os.path.exists", side_effect=mock_exists): + with patch.dict( + os.environ, + { + _HTTP_URI_ENV_VAR: "https://my-uri", + _HTTP_API_KEY_ENV_VAR: "my-api-key", + }, + ): + config = JupyterHttpConfiguration() + assert config.server_uri == "https://my-uri" + assert config.api_keys[_SYSTEMLINK_API_KEY_HEADER] == "my-api-key" + assert config.cert_path is None + + def test__cert_file_does_not_exist_on_linux__cert_path_is_not_passed(self): + def mock_exists(path): + if path == _SYSTEMLINK_SERVER_CERT_PATH: + return False + return True + + with patch("sys.platform", "linux"): + with patch("os.path.exists", side_effect=mock_exists): + with patch.dict( + os.environ, + { + _HTTP_URI_ENV_VAR: "https://my-uri", + _HTTP_API_KEY_ENV_VAR: "my-api-key", + }, + ): + config = JupyterHttpConfiguration() + assert config.server_uri == "https://my-uri" + assert config.api_keys[_SYSTEMLINK_API_KEY_HEADER] == "my-api-key" + assert config.cert_path is None