Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ All notable changes to the Zowe Client Python SDK will be documented in this fil

### Bug Fixes

- 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)
- Updated the `pyo3` dependency of the Secrets SDK for technical currency. [#399](https://github.com/zowe/zowe-client-python-sdk/pull/399)
Expand Down
18 changes: 4 additions & 14 deletions src/core/zowe/core_for_zowe_sdk/config_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ def find_profile(self, path: str, profiles: dict[str, Any]) -> Optional[dict[str
for k, v in profiles.items():
if not isinstance(v, dict): # Ensure v is a dictionary
if not self.__suppress_config_file_warnings:
self.__logger.warning("Invalid profile passed when schame validation is off")
self.__logger.warning("Invalid profile passed when schema validation is off")
continue # Skip invalid entries

if segments[0] == k:
Expand Down Expand Up @@ -443,20 +443,10 @@ def __load_secure_properties(self) -> None:
secure_props = CredentialManager.secure_props.get(self.filepath or "", {})
for key, value in secure_props.items():
segments = [name for i, name in enumerate(key.split(".")) if i % 2 == 1]
profiles_obj = self.profiles
property_name = segments.pop()
for i, profile_name in enumerate(segments):
if profiles_obj is None or not isinstance(profiles_obj, dict):
break
if profile_name in profiles_obj:
profiles_obj = profiles_obj[profile_name]
if not isinstance(profiles_obj, dict):
break
if i == len(segments) - 1:
profiles_obj.setdefault("properties", {})
profiles_obj["properties"][property_name] = value
else:
break
profile = self.find_profile(".".join(segments), self.profiles)
if profile is not None:
profile.setdefault("properties", {})[property_name] = value

def __extract_secure_properties(
self, profiles_obj: dict[str, Any], json_path: Optional[str] = "profiles"
Expand Down
52 changes: 52 additions & 0 deletions tests/unit/core/test_profile_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,58 @@ def test_custom_file_and_custom_profile_loading_with_nested_profile(self, get_pa
}
self.assertEqual(props, expected_props)

@mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password)
def test_nested_profile_with_secure_properties_on_child(self, get_pass_func):
"""
Test that secure properties declared on a child profile nested under a
parent (e.g. mainframe.zosmf) are correctly loaded from the vault.

Regression test: __load_secure_properties previously looked for the
child profile name as a direct key of the parent profile dict, but
nested profiles actually live one level deeper under the parent's
own "profiles" key, so secure user/password values were silently
never injected for any profile nested more than one level deep.
"""
custom_file_path = os.path.join(self.custom_dir, self.custom_filename)
config_contents = {
"$schema": "./zowe.schema.json",
"profiles": {
"mainframe": {
"properties": {"host": "example.com"},
"profiles": {
"zosmf": {
"type": "zosmf",
"properties": {"port": 1443},
"secure": ["user", "password"],
}
},
}
},
"defaults": {"zosmf": "mainframe.zosmf"},
}
with open(custom_file_path, "w") as f:
json.dump(config_contents, f)

self.setUpCreds(
custom_file_path,
{
"profiles.mainframe.profiles.zosmf.properties.user": "admin",
"profiles.mainframe.profiles.zosmf.properties.password": "secret",
},
)

prof_manager = ProfileManager(appname=self.custom_appname)
prof_manager.config_dir = self.custom_dir
props: dict = prof_manager.load(profile_name="mainframe.zosmf", validate_schema=False)

expected_props = {
"host": "example.com",
"port": 1443,
"user": "admin",
"password": "secret",
}
self.assertEqual(props, expected_props)

@mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password)
def test_profile_loading_with_user_overridden_properties(self, get_pass_func):
"""
Expand Down
Loading