From e8fe0f6653e905e1ab2c02f30c6d1b565cc0014f Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Thu, 2 Nov 2023 14:18:30 -0400 Subject: [PATCH 1/3] Fix extra messages in profile extract bug. Signed-off-by: Leonard Carcaramo --- pyracf/common/security_result.py | 71 +++++++++++-------- tests/user/test_user_constants.py | 6 ++ tests/user/test_user_result_parser.py | 12 ++++ ...ct_user_result_extra_messages_success.json | 56 +++++++++++++++ ...act_user_result_extra_messages_success.xml | 35 +++++++++ 5 files changed, 152 insertions(+), 28 deletions(-) create mode 100644 tests/user/user_result_samples/extract_user_result_extra_messages_success.json create mode 100644 tests/user/user_result_samples/extract_user_result_extra_messages_success.xml diff --git a/pyracf/common/security_result.py b/pyracf/common/security_result.py index 90933827..dba11ae2 100644 --- a/pyracf/common/security_result.py +++ b/pyracf/common/security_result.py @@ -1,5 +1,6 @@ """Generic Security Result Parser.""" +import re from xml.etree.ElementTree import Element # Only used for type hints. import defusedxml.ElementTree as XMLParser @@ -15,58 +16,72 @@ def __init__(self, result_xml: str) -> None: def __extract_results(self) -> None: """Extract XML results into a dictionary.""" - self.definition = self.__result[0] - self.definition.attrib["requestId"] = self.definition.attrib["requestid"] - del self.definition.attrib["requestid"] - definition_tag = self.__to_pascal_case(self.definition.tag.split("}")[-1]) + self.__definition = self.__result[0] + return_code = int(self.__result[1].text) + reason_code = int(self.__result[2].text) + self.__definition.attrib["requestId"] = self.__definition.attrib["requestid"] + del self.__definition.attrib["requestid"] + definition_tag = self.__to_pascal_case(self.__definition.tag.split("}")[-1]) self.__result_dictionary["securityResult"][ definition_tag - ] = self.definition.attrib - self.definition_dictionary = self.__result_dictionary["securityResult"][ + ] = self.__definition.attrib + self.__definition_dictionary = self.__result_dictionary["securityResult"][ definition_tag ] + filter_out_extra_messages = False + if return_code == 0 and self.__definition_dictionary["operation"] == "listdata": + filter_out_extra_messages = True try: - if self.definition[0].tag.split("}")[-1] == "info": + if self.__definition[0].tag.split("}")[-1] == "info": self.__extract_info() - if self.definition[0].tag.split("}")[-1] == "error": + if self.__definition[0].tag.split("}")[-1] == "error": self.__extract_error() else: - self.__extract_commands() + self.__extract_commands(filter_out_extra_messages) except IndexError: # Index Error indicates that there is no # additional information to extract from the definition. pass - return_code = self.__result[1] - self.__result_dictionary["securityResult"]["returnCode"] = int(return_code.text) - reason_code = self.__result[2] - self.__result_dictionary["securityResult"]["reasonCode"] = int(reason_code.text) + self.__result_dictionary["securityResult"]["returnCode"] = return_code + self.__result_dictionary["securityResult"]["reasonCode"] = reason_code def __extract_info(self) -> None: """Extract info section from XML into a list.""" - self.definition_dictionary["info"] = [] - info = self.definition_dictionary["info"] - while self.definition[0].tag.split("}")[-1] == "info": - item = self.definition[0] + self.__definition_dictionary["info"] = [] + info = self.__definition_dictionary["info"] + while self.__definition[0].tag.split("}")[-1] == "info": + item = self.__definition[0] if item.tag.split("}")[-1] != "info": return info.append(item.text) - self.definition.remove(item) + self.__definition.remove(item) - def __extract_commands(self) -> None: + def __extract_commands(self, filter_out_extra_messages: bool) -> None: """Extract commands section from XML into a list.""" - self.definition_dictionary["commands"] = [] - commands = self.definition_dictionary["commands"] - for command in self.definition: - self.__extract_command(commands, command) + self.__definition_dictionary["commands"] = [] + commands = self.__definition_dictionary["commands"] + for command in self.__definition: + self.__extract_command(commands, command, filter_out_extra_messages) - def __extract_command(self, commands: dict, command: Element) -> None: + def __extract_command( + self, + commands: dict, + command: Element, + filter_out_extra_messages: bool, + ) -> None: command_dictionary = {} commands.append(command_dictionary) + message_id_regex = r"[A-Z]{3}[0-9]{5}[A-Z]" for item in command: item_tag = self.__to_pascal_case(item.tag.split("}")[-1]) if item_tag == "message": if "messages" not in command_dictionary: command_dictionary["messages"] = [] + if item.text: + if filter_out_extra_messages and re.match( + message_id_regex, item.text + ): + continue command_dictionary["messages"].append(item.text) else: try: @@ -76,14 +91,14 @@ def __extract_command(self, commands: dict, command: Element) -> None: def __extract_error(self) -> None: """Extract error section from XML into a dictionary.""" - self.definition_dictionary["error"] = {} - error = self.definition[0] + self.__definition_dictionary["error"] = {} + error = self.__definition[0] for item in error: item_tag = self.__to_pascal_case(item.tag.split("}")[-1]) try: - self.definition_dictionary["error"][item_tag] = int(item.text) + self.__definition_dictionary["error"][item_tag] = int(item.text) except ValueError: - self.definition_dictionary["error"][item_tag] = item.text + self.__definition_dictionary["error"][item_tag] = item.text def __to_pascal_case(self, key: str) -> str: """Convert result dictionary keys to pascal case.""" diff --git a/tests/user/test_user_constants.py b/tests/user/test_user_constants.py index e4d05852..d5d4cb21 100644 --- a/tests/user/test_user_constants.py +++ b/tests/user/test_user_constants.py @@ -115,6 +115,12 @@ def get_sample(sample_file: str) -> Union[str, bytes]: TEST_EXTRACT_USER_RESULT_BASE_OMVS_TSO_REVOKE_RESUME_DICTIONARY = get_sample( "extract_user_result_base_omvs_tso_revoke_resume.json" ) +TEST_EXTRACT_USER_RESULT_EXTRA_MESSAGES_SUCCESS_XML = get_sample( + "extract_user_result_extra_messages_success.xml" +) +TEST_EXTRACT_USER_RESULT_EXTRA_MESSAGES_SUCCESS_DICTIONARY = get_sample( + "extract_user_result_extra_messages_success.json" +) # Delete User TEST_DELETE_USER_RESULT_SUCCESS_XML = get_sample("delete_user_result_success.xml") diff --git a/tests/user/test_user_result_parser.py b/tests/user/test_user_result_parser.py index 64cb6739..c7789f98 100644 --- a/tests/user/test_user_result_parser.py +++ b/tests/user/test_user_result_parser.py @@ -196,6 +196,18 @@ def test_user_admin_can_parse_extract_user_and_ignore_command_audit_trail_xml( TestUserConstants.TEST_EXTRACT_USER_RESULT_BASE_OMVS_SUCCESS_DICTIONARY, ) + def test_user_admin_can_parse_extract_user_and_ignore_extra_messages_on_succes_xml( + self, + call_racf_mock: Mock, + ): + call_racf_mock.return_value = ( + TestUserConstants.TEST_EXTRACT_USER_RESULT_EXTRA_MESSAGES_SUCCESS_XML + ) + self.assertEqual( + self.user_admin.extract("squidwrd", segments=["omvs", "tso", "ovm"]), + TestUserConstants.TEST_EXTRACT_USER_RESULT_EXTRA_MESSAGES_SUCCESS_DICTIONARY, + ) + def test_user_admin_can_parse_extract_user_base_omvs_tso_revoke_resume_success_xml( self, call_racf_mock: Mock, diff --git a/tests/user/user_result_samples/extract_user_result_extra_messages_success.json b/tests/user/user_result_samples/extract_user_result_extra_messages_success.json new file mode 100644 index 00000000..fec77e9c --- /dev/null +++ b/tests/user/user_result_samples/extract_user_result_extra_messages_success.json @@ -0,0 +1,56 @@ +{ + "securityResult": { + "user": { + "name": "SQUIDWRD", + "operation": "listdata", + "requestId": "UserRequest", + "commands": [ + { + "safReturnCode": 0, + "returnCode": 0, + "reasonCode": 0, + "image": "LISTUSER SQUIDWRD OMVS TSO OVM ", + "profiles": [ + { + "base": { + "user": "squidwrd", + "name": "squidward", + "owner": "leonard", + "created": "4/4/2023", + "defaultGroup": "sys1", + "passwordDate": null, + "passwordInterval": 186, + "passphraseDate": null, + "attributes": [], + "revokeDate": null, + "resumeDate": null, + "lastAccess": "4/4/2023 12:55 PM", + "classAuthorizations": [], + "logonAllowedDays": "anyday", + "logonAllowedTime": "anytime", + "groups": { + "SYS1": { + "auth": "use", + "connectOwner": "leonard", + "connectDate": "4/4/2023", + "connects": 0, + "uacc": null, + "lastConnect": null, + "connectAttributes": [], + "revokeDate": null, + "resumeDate": null + } + }, + "securityLevel": null, + "categoryAuthorization": null, + "securityLabel": null + } + } + ] + } + ] + }, + "returnCode": 0, + "reasonCode": 0 + } +} \ No newline at end of file diff --git a/tests/user/user_result_samples/extract_user_result_extra_messages_success.xml b/tests/user/user_result_samples/extract_user_result_extra_messages_success.xml new file mode 100644 index 00000000..1927a241 --- /dev/null +++ b/tests/user/user_result_samples/extract_user_result_extra_messages_success.xml @@ -0,0 +1,35 @@ + + + + + 0 + 0 + 0 + LISTUSER SQUIDWRD OMVS TSO OVM + USER=SQUIDWRD NAME=SQUIDWARD OWNER=LEONARD CREATED=23.094 + DEFAULT-GROUP=SYS1 PASSDATE=00.000 PASS-INTERVAL=186 PHRASEDATE=N/A + ATTRIBUTES=NONE + REVOKE DATE=NONE RESUME DATE=NONE + LAST-ACCESS=23.094/12:55:37 + CLASS AUTHORIZATIONS=NONE + NO-INSTALLATION-DATA + NO-MODEL-NAME + LOGON ALLOWED (DAYS) (TIME) + --------------------------------------------- + ANYDAY ANYTIME + GROUP=SYS1 AUTH=USE CONNECT-OWNER=LEONARD CONNECT-DATE=23.094 + CONNECTS= 00 UACC=NONE LAST-CONNECT=UNKNOWN + CONNECT ATTRIBUTES=NONE + REVOKE DATE=NONE RESUME DATE=NONE + SECURITY-LEVEL=NONE SPECIFIED + CATEGORY-AUTHORIZATION + NONE SPECIFIED + SECURITY-LABEL=NONE SPECIFIED + IRR52021I You are not authorized to view OMVS segments. + IRR52021I You are not authorized to view TSO segments. + IRR52021I You are not authorized to view OVM segments. + + + 0 + 0 + \ No newline at end of file From 48c6206c0ef2cfb70b3ad440a9bbc5aa7b7b14f9 Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Thu, 2 Nov 2023 14:37:26 -0400 Subject: [PATCH 2/3] Fix debug logging test case. Signed-off-by: Leonard Carcaramo --- tests/user/test_user_debug_logging.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/user/test_user_debug_logging.py b/tests/user/test_user_debug_logging.py index 346eb239..c3b27d08 100644 --- a/tests/user/test_user_debug_logging.py +++ b/tests/user/test_user_debug_logging.py @@ -247,17 +247,13 @@ def test_alter_user_request_debug_log_password_xml_tags_not_redacted_on_error( with contextlib.redirect_stdout(stdout): try: error_traits = dict( - TestUserConstants.TEST_ALTER_USER_REQUEST_TRAITS_PASSWORD + TestUserConstants.TEST_ALTER_USER_REQUEST_TRAITS_PASSWORD_SIMPLE ) error_traits["omvs:uid"] = 90000000000 self.user_admin.alter( "squidwrd", traits=error_traits, ) - self.user_admin.alter( - "squidwrd", - traits=TestUserConstants.TEST_ALTER_USER_REQUEST_TRAITS_PASSWORD_SIMPLE, - ) except SecurityRequestError: pass error_log = self.ansi_escape.sub("", stdout.getvalue()) From 4b09481dd9496aee7c067fc9f9f17010a4656d9e Mon Sep 17 00:00:00 2001 From: Leonard Carcaramo Date: Thu, 2 Nov 2023 14:40:21 -0400 Subject: [PATCH 3/3] Cleanup. Signed-off-by: Leonard Carcaramo --- pyracf/common/irrsmo00.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyracf/common/irrsmo00.c b/pyracf/common/irrsmo00.c index 2cff3cff..8ebdc8d4 100644 --- a/pyracf/common/irrsmo00.c +++ b/pyracf/common/irrsmo00.c @@ -14,7 +14,7 @@ typedef struct { } VarStr_T; // This function changes any null character not preceded by '>' to a blank character. -// This is a workaround for an issue where the profile data embedded in response xml +// This is a workaround for an issue where profile data embedded in response xml // returned by IRROSMO00 sometimes includes null characters instead of properly // encoded text, which causes the returned xml to be truncated. void null_byte_fix(char* str, unsigned int str_len) {