Skip to content

Commit

Permalink
Merge pull request #31 from ambitus/bug/filter_out_tso_messages_on_su…
Browse files Browse the repository at this point in the history
…ccess

Bug/filter out tso messages on success
  • Loading branch information
ElijahSwiftIBM authored Nov 2, 2023
2 parents e8b82cf + 4b09481 commit 05903d2
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 34 deletions.
2 changes: 1 addition & 1 deletion pyracf/common/irrsmo00.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
71 changes: 43 additions & 28 deletions pyracf/common/security_result.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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:
Expand All @@ -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."""
Expand Down
6 changes: 6 additions & 0 deletions tests/user/test_user_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
6 changes: 1 addition & 5 deletions tests/user/test_user_debug_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
12 changes: 12 additions & 0 deletions tests/user/test_user_result_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="IBM-1047"?>
<securityresult xmlns="http://www.ibm.com/systems/zos/saf/IRRSMO00Result1">
<user name="SQUIDWRD" operation="listdata" requestid="UserRequest">
<command>
<safreturncode>0</safreturncode>
<returncode>0</returncode>
<reasoncode>0</reasoncode>
<image>LISTUSER SQUIDWRD OMVS TSO OVM </image>
<message>USER=SQUIDWRD NAME=SQUIDWARD OWNER=LEONARD CREATED=23.094</message>
<message> DEFAULT-GROUP=SYS1 PASSDATE=00.000 PASS-INTERVAL=186 PHRASEDATE=N/A</message>
<message> ATTRIBUTES=NONE</message>
<message> REVOKE DATE=NONE RESUME DATE=NONE</message>
<message> LAST-ACCESS=23.094/12:55:37</message>
<message> CLASS AUTHORIZATIONS=NONE</message>
<message> NO-INSTALLATION-DATA</message>
<message> NO-MODEL-NAME</message>
<message> LOGON ALLOWED (DAYS) (TIME)</message>
<message> ---------------------------------------------</message>
<message> ANYDAY ANYTIME</message>
<message> GROUP=SYS1 AUTH=USE CONNECT-OWNER=LEONARD CONNECT-DATE=23.094</message>
<message> CONNECTS= 00 UACC=NONE LAST-CONNECT=UNKNOWN</message>
<message> CONNECT ATTRIBUTES=NONE</message>
<message> REVOKE DATE=NONE RESUME DATE=NONE</message>
<message>SECURITY-LEVEL=NONE SPECIFIED</message>
<message>CATEGORY-AUTHORIZATION</message>
<message> NONE SPECIFIED</message>
<message>SECURITY-LABEL=NONE SPECIFIED</message>
<message>IRR52021I You are not authorized to view OMVS segments.</message>
<message>IRR52021I You are not authorized to view TSO segments.</message>
<message>IRR52021I You are not authorized to view OVM segments.</message>
</command>
</user>
<returncode>0</returncode>
<reasoncode>0</reasoncode>
</securityresult>

0 comments on commit 05903d2

Please sign in to comment.