Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implements secure value loading method for multiple Windows credentials #191

Merged
merged 46 commits into from
Aug 29, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
724fad7
Implementws secure value loading method for multiple Windows credentials
samadpls Jun 23, 2023
4752106
Modified implementation of the `__get_secure_value` method
samadpls Jun 26, 2023
6032bcc
fix condition statement
samadpls Jun 26, 2023
c5e997b
modified the logic
samadpls Jun 27, 2023
078e8bb
added comments
samadpls Jun 27, 2023
94fff12
still working not yet compelete
samadpls Jun 27, 2023
eb24899
removed the logic from zosmf_profile
samadpls Jun 28, 2023
079e298
implemneted the get and set logic in config_file
samadpls Jun 29, 2023
e5d817c
typo
samadpls Jun 29, 2023
1eeb12d
fixed typo
samadpls Jun 29, 2023
1997c43
updated test file
samadpls Jun 29, 2023
d55cf99
updated the logic
samadpls Jul 3, 2023
b848160
updated the `_retrieve_password` and added a unit for it
samadpls Jul 4, 2023
12caa47
updated `set_secure_props` and added unit test for testing `normal_cr…
samadpls Jul 4, 2023
2bb7a1f
added
samadpls Jul 4, 2023
0241a08
Added unit test for `set_secure_props`, modified `_retrieve_password`…
samadpls Jul 5, 2023
cb50b26
updated the logic
samadpls Jul 6, 2023
4ac5b7d
updated testcases
samadpls Jul 6, 2023
f75fc33
updated
samadpls Jul 6, 2023
1a4e21e
updated a `CHANGELOG.md`
samadpls Jul 6, 2023
3c5e293
added mock patch
samadpls Jul 6, 2023
7385860
addressed comments and update test cases and `set_secure_prop` logic
samadpls Jul 19, 2023
5046976
Updated the encoding to UTF-16 for Windows
samadpls Jul 28, 2023
8de7a6e
Update WIN32_CRED_MAX_STRING_LENGTH value, refactor ConfigFile set_pr…
samadpls Jul 30, 2023
2b0a708
updated credential manager
samadpls Jul 30, 2023
2ce0dc4
Update credential_manager.py
samadpls Jul 31, 2023
1d93782
Update credential_manager.py
samadpls Jul 31, 2023
a50d56c
refactor: defined charset for file
samadpls Jul 31, 2023
d249d10
fixed the typo in the config file
samadpls Jul 31, 2023
ae629f9
refactor: refactor credential manager
samadpls Aug 3, 2023
07ac8ce
typo fixed
samadpls Aug 3, 2023
4ea12a7
Merge branch 'main' into multi-credentials
samadpls Aug 7, 2023
9d618f5
added unit tests for the `CredentialManager` class and Addressed comment
samadpls Aug 8, 2023
792ded3
added
samadpls Aug 8, 2023
a1b9c66
typo fixed
samadpls Aug 8, 2023
6c71b94
typo fixed in load_secure_props()
samadpls Aug 9, 2023
85ea9e0
fixed the typo mistake in `load_secure_prop`
samadpls Aug 10, 2023
bf4829e
Added condition for non-Windows system
samadpls Aug 11, 2023
99d3fb1
updat the test cases
samadpls Aug 11, 2023
6c16930
Refactored the `save_secure_props` function.
samadpls Aug 16, 2023
39eb6dc
updated the test case
samadpls Aug 17, 2023
ceb3093
removed unused imports
samadpls Aug 22, 2023
fd729be
removed else condition
samadpls Aug 23, 2023
2c1ae28
fixed typo mistakes
samadpls Aug 23, 2023
716ef7d
fixed typo
samadpls Aug 23, 2023
58178c0
updated setUpCred method
samadpls Aug 23, 2023
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 src/core/zowe/core_for_zowe_sdk/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@
"ZoweCredentialKey": "Zowe-Plugin",
"ZoweServiceName": "Zowe",
"ZoweAccountName": "secure_config_props",
"WIN32_CRED_MAX_STRING_LENGTH" : 2560
}
43 changes: 40 additions & 3 deletions src/core/zowe/core_for_zowe_sdk/zosmf_profile.py
samadpls marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ def load(self):
)

with open(profile_file, "r") as fileobj:
profile_yaml = yaml.safe_load(fileobj)
yaml_content = fileobj.read()
profile_yaml = yaml.safe_load(yaml_content)

zosmf_host = profile_yaml["host"]
if "port" in profile_yaml:
Expand All @@ -94,13 +95,28 @@ def load(self):
)

def __get_secure_value(self, name):
"""Retrieve a secure value from the keyring."""
service_name = constants["ZoweCredentialKey"]
account_name = "zosmf_{}_{}".format(self.profile_name, name)

secret_value = ""
if sys.platform == "win32":
service_name += "/" + account_name

secret_value = keyring.get_password(service_name, account_name)
# Try to retrieve the secure value without an index
secret_value = keyring.get_password(service_name, account_name)
if secret_value is None:
# Retrieve the secure value with an index
index = 1
while True:
field_name = f"{account_name}-{index}"
temp_value = keyring.get_password(service_name, field_name)
if temp_value is None:
break
secret_value += temp_value
index += 1
else:
secret_value = keyring.get_password(service_name, account_name)

if sys.platform == "win32":
secret_value = secret_value.encode("utf-16")
Expand All @@ -119,11 +135,32 @@ def __load_secure_credentials(self):
try:
zosmf_user = self.__get_secure_value("user")
zosmf_password = self.__get_secure_value("password")
if sys.platform == "win32":
if len(zosmf_user) > constants["WIN32_CRED_MAX_STRING_LENGTH"]:
# Split user value across multiple fields
self.__split_and_store_secure_value("user", zosmf_user)
if len(zosmf_password) > constants["WIN32_CRED_MAX_STRING_LENGTH"]:
# Split password value across multiple fields
self.__split_and_store_secure_value("password", zosmf_password)
except Exception as e:
raise SecureProfileLoadFailed(self.profile_name, e)
else:
return (zosmf_user, zosmf_password)


def __split_and_store_secure_value(self, field_name, value):
"""Split and store a secure value across multiple fields."""
service_name = constants["ZoweCredentialKey"]
account_name = f"zosmf_{self.profile_name}_{field_name}"

# Delete any previously used fields storing this value
keyring.delete_password(service_name, account_name)
sliced_values = [value[i:i+constants["WIN32_CRED_MAX_STRING_LENGTH"]] for i in range(0, len(value), constants["WIN32_CRED_MAX_STRING_LENGTH"])]

for index, temp_value in enumerate(sliced_values, start=1):
keyring.set_password(service_name, f"{account_name}-{index}", temp_value)

# Append null byte to mark the termination of the last field
keyring.set_password(service_name, f"{account_name}-{len(sliced_values)+1}", "\0")

if HAS_KEYRING and sys.platform.startswith("linux"):
from contextlib import closing
Expand Down
70 changes: 69 additions & 1 deletion tests/unit/test_zowe_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from jsonschema import validate, ValidationError
from zowe.core_for_zowe_sdk.validators import validate_config_json
import commentjson

from zowe.core_for_zowe_sdk.constants import constants
from pyfakefs.fake_filesystem_unittest import TestCase
from zowe.core_for_zowe_sdk import (
ApiConnection,
Expand Down Expand Up @@ -158,6 +158,74 @@ def test_object_should_be_instance_of_class(self):
zosmf_profile = ZosmfProfile(self.profile_name)
self.assertIsInstance(zosmf_profile, ZosmfProfile)

@mock.patch("builtins.open", mock.mock_open(read_data="yaml_content"))
@mock.patch("yaml.safe_load")
def test_load(self, yaml_load_mock):
"""Test the load method."""
zosmf_profile = ZosmfProfile(self.profile_name)
profile_file = os.path.join(
zosmf_profile.profiles_dir, f"{self.profile_name}.yaml"
)

# Mock the return values
profile_yaml = {
"host": "example.com",
"port": 443,
"user": "test_user",
"password": "test_password",
"rejectUnauthorized": True,
}
yaml_load_mock.return_value = profile_yaml
# Set secure values for user and password
profile_yaml["user"] = constants["SecureValuePrefix"] + "secure_user"
profile_yaml["password"] = constants["SecureValuePrefix"] + "secure_password"
# Mock the private methods
# zosmf_profile._ZosmfProfile__load_secure_credentials = mock.MagicMock(return_value=("secure_user", "secure_password"))
zosmf_profile._ZosmfProfile__get_secure_value = mock.MagicMock(side_effect=["secure_user", "secure_password"])
zosmf_profile._ZosmfProfile__split_and_store_secure_value = mock.MagicMock()

# Call the load method
result = zosmf_profile.load()

# Assertions
yaml_load_mock.assert_called_once_with("yaml_content") # Ensure safe_load is called with the correct argument
zosmf_profile._ZosmfProfile__load_secure_credentials.assert_called_once()
zosmf_profile._ZosmfProfile__get_secure_value.assert_called_with("user")
zosmf_profile._ZosmfProfile__get_secure_value.assert_called_with("password")
zosmf_profile._ZosmfProfile__split_and_store_secure_value.assert_not_called()

expected_connection = ApiConnection(
"example.com", "secure_user", "secure_password", True
)
self.assertEqual(result, expected_connection)

# Additional test case for long credentials
long_user = "x" * 1000
long_password = "y" * 2000
profile_yaml["user"] = long_user
profile_yaml["password"] = long_password

# Reset the mocks
yaml_load_mock.reset_mock()
zosmf_profile._ZosmfProfile__get_secure_value.reset_mock()
zosmf_profile._ZosmfProfile__split_and_store_secure_value.reset_mock()

# Call the load method with long credentials
result = zosmf_profile.load()

# Assertions
yaml_load_mock.assert_called_once_with("yaml_content")
zosmf_profile._ZosmfProfile__load_secure_credentials.assert_called_once()
zosmf_profile._ZosmfProfile__get_secure_value.assert_called_with("user")
zosmf_profile._ZosmfProfile__get_secure_value.assert_called_with("password")
zosmf_profile._ZosmfProfile__split_and_store_secure_value.assert_called_with("user", long_user)
zosmf_profile._ZosmfProfile__split_and_store_secure_value.assert_called_with("password", long_password)

expected_connection = ApiConnection(
"example.com", "secure_user", "secure_password", True
)
self.assertEqual(result, expected_connection)


class TestZosmfProfileManager(TestCase):
"""ProfileManager class unit tests."""
Expand Down