diff --git a/CHANGELOG.md b/CHANGELOG.md index 96904ce6..84dc1339 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ All notable changes to the Zowe Client Python SDK will be documented in this fil ### Bug Fixes +- **Breaking**: Removed support for loading a JSON schema from a remote `http(s)://` URL via the `$schema` config property. Local schema files are still supported. [#412](https://github.com/zowe/zowe-client-python-sdk/pull/412) - Fixed secure `user`/`password` properties not being loaded for team-config profiles nested more than one level deep which caused 401 errors. [#411](https://github.com/zowe/zowe-client-python-sdk/pull/411) - Redacted request headers and restricted log directory/file to owner-only access. [#404](https://github.com/zowe/zowe-client-python-sdk/pull/404) - Fixed `Jobs.get_job_output_as_files` writing to a directory it never created, and made job output paths stay within the target directory. [#403](https://github.com/zowe/zowe-client-python-sdk/pull/403) diff --git a/src/core/zowe/core_for_zowe_sdk/config_file.py b/src/core/zowe/core_for_zowe_sdk/config_file.py index ef462127..e36b1b2b 100644 --- a/src/core/zowe/core_for_zowe_sdk/config_file.py +++ b/src/core/zowe/core_for_zowe_sdk/config_file.py @@ -19,14 +19,13 @@ from typing import Any, NamedTuple, Optional, Union import json5 -import requests from .credential_manager import CredentialManager from .custom_warnings import ProfileNotFoundWarning, ProfileParsingWarning from .exceptions import ProfileNotFound from .logger import Log from .profile_constants import GLOBAL_CONFIG_NAME, TEAM_CONFIG, USER_CONFIG -from .validators import validate_config_json +from .validators import REMOTE_SCHEMA_UNSUPPORTED, validate_config_json HOME = os.path.expanduser("~") GLOBAL_CONFIG_LOCATION = os.path.join(HOME, ".zowe") @@ -170,6 +169,11 @@ def schema_list(self, cwd: Optional[str] = None) -> list[dict[str, Any]]: ------- list[dict[str, Any]] properties from schema + + Raises + ------ + ValueError + When the $schema property points to a remote URL, which is not supported """ schema: Optional[Union[str, dict[str, Any]]] = self.schema_property @@ -180,15 +184,8 @@ def schema_list(self, cwd: Optional[str] = None) -> list[dict[str, Any]]: schema_json: dict[str, Any] = {} if schema.startswith(("https://", "http://")): - try: - response = requests.get(schema) - response.raise_for_status() # Ensure it's a valid response - schema_json = response.json() - except requests.RequestException as e: - if not self.__suppress_config_file_warnings: - warnings.warn(f"Invalid schema request: {e}") - self.__logger.warning(f"Invalid schema request: {e}") - return [] + # remote schema loading is not supported + raise ValueError(f"{REMOTE_SCHEMA_UNSUPPORTED}: {schema}") elif schema.startswith("file://") or os.path.isfile(schema): try: diff --git a/src/core/zowe/core_for_zowe_sdk/validators.py b/src/core/zowe/core_for_zowe_sdk/validators.py index f340c75f..8d1b8276 100644 --- a/src/core/zowe/core_for_zowe_sdk/validators.py +++ b/src/core/zowe/core_for_zowe_sdk/validators.py @@ -11,12 +11,13 @@ """ import os -from typing import Union, Any +from typing import Any, Union import json5 -import requests from jsonschema import validate +REMOTE_SCHEMA_UNSUPPORTED = "Loading a JSON schema from a remote URL is not supported" + def validate_config_json(path_config_json: Union[str, dict[str, Any]], path_schema_json: str, cwd: str) -> None: """ @@ -30,10 +31,18 @@ def validate_config_json(path_config_json: Union[str, dict[str, Any]], path_sche Absolute path to zowe.schema.json cwd: str Path of the current working directory + + Raises + ------ + ValueError + When path_schema_json is a remote URL, which is not supported """ - # checks if the path_schema_json point to an internet URI and download the schema using the URI + # remote ($schema pointing to an http(s):// URL) schema loading is not supported; only local files may be used if path_schema_json.startswith("https://") or path_schema_json.startswith("http://"): - schema_json = requests.get(path_schema_json).json() + raise ValueError( + f"{REMOTE_SCHEMA_UNSUPPORTED}: {path_schema_json}. " + "Use a local file path for the $schema property instead." + ) # checks if the path_schema_json is a file elif os.path.isfile(path_schema_json) or path_schema_json.startswith("file://"): diff --git a/tests/unit/core/test_config.py b/tests/unit/core/test_config.py index 2fe193df..b5878e6a 100644 --- a/tests/unit/core/test_config.py +++ b/tests/unit/core/test_config.py @@ -1,5 +1,6 @@ import importlib.util import os +from unittest import mock import json5 from jsonschema import ValidationError, validate @@ -136,6 +137,18 @@ def test_validate_config_json_with_block_comments(self): loaded_schema = json5.load(open(commented_schema_path, encoding="utf-8")) expected = validate(loaded_config, loaded_schema) - result = validate_config_json(commented_config_path, commented_schema_path, cwd=os.path.dirname(commented_config_path)) + result = validate_config_json( + commented_config_path, commented_schema_path, cwd=os.path.dirname(commented_config_path) + ) self.assertEqual(result, expected) + + def test_validate_config_json_rejects_remote_schema(self): + """Test validate_config_json rejects http(s):// schema URLs without making a network request.""" + with mock.patch("requests.get") as mock_get: + with self.assertRaises(ValueError): + validate_config_json(self.original_file_path, "https://example.com/zowe.schema.json", cwd=FIXTURES_PATH) + with self.assertRaises(ValueError): + validate_config_json(self.original_file_path, "http://example.com/zowe.schema.json", cwd=FIXTURES_PATH) + + mock_get.assert_not_called() diff --git a/tests/unit/core/test_profile_manager.py b/tests/unit/core/test_profile_manager.py index 9c763ce1..185bd36b 100644 --- a/tests/unit/core/test_profile_manager.py +++ b/tests/unit/core/test_profile_manager.py @@ -348,6 +348,18 @@ def test_validate_schema_logger(self, get_pass_func, mock_logger_warning: mock.M config_file.validate_schema() self.assertEqual(mock_logger_warning.call_args[0][0], "Could not find $schema property") + def test_schema_list_rejects_remote_schema(self): + """Test that schema_list does not fetch a remote schema URL and raises an error instead.""" + with mock.patch("requests.get") as mock_get: + config_file = ConfigFile( + name="zowe_abcd", type="User Config", schema_property="https://example.com/zowe.schema.json" + ) + config_file.suppress_config_warnings(False) + with self.assertRaises(ValueError): + config_file.schema_list() + + mock_get.assert_not_called() + @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password_exception) def test_secure_props_loading_warning(self, get_pass_func): """