diff --git a/Jenkinsfile b/Jenkinsfile index 85387d5e..ff4783af 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -175,7 +175,7 @@ def clean_python_environment() { def install_poetry(python) { echo "Installing Poetry..." - sh "bash -c 'curl -sSL https://install.python-poetry.org | ${python} -'" + sh "bash -c 'curl -sSL https://install.python-poetry.org' | ${python} -" } def build_poetry_environment(python) { diff --git a/pyproject.toml b/pyproject.toml index f8b4ab15..46884260 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,10 @@ [build-system] build-backend = "poetry.core.masonry.api" - requires = ["poetry-core>=1.7.0", "setuptools>=61"] + requires = ["poetry-core>=1.9.0", "setuptools>=69"] [tool.poetry] name="pyracf" - version="1.0b4" + version="1.0b5" description="Python interface to RACF using IRRSMO00 RACF Callable Service." license = "Apache-2.0" authors = [ @@ -44,13 +44,14 @@ defusedxml = ">=0.7.1" [tool.poetry.group.dev.dependencies] - isort = ">=5.12.0" - pre-commit = ">=3.4.0" - black = ">=23.9.1" - flake8 = ">=6.1.0" - pylint = ">=3.0.0" - coverage = ">=7.3.2" - wheel = ">=0.41.2" + isort = ">=5.13.2" + pre-commit = ">=3.6.0" + black = ">=24.1.1" + flake8 = ">=7.0.0" + pylint = ">=3.0.3" + coverage = ">=7.4.1" + wheel = ">=0.42.0" + ebcdic = ">=1.1.1" [tool.isort] profile = "black" diff --git a/pyracf/__init__.py b/pyracf/__init__.py index e99b15f6..e94c963a 100644 --- a/pyracf/__init__.py +++ b/pyracf/__init__.py @@ -1,12 +1,13 @@ """Make security admin subclasses available from package root.""" + from .access.access_admin import AccessAdmin -from .common.add_operation_error import AddOperationError -from .common.alter_operation_error import AlterOperationError -from .common.downstream_fatal_error import DownstreamFatalError -from .common.security_request_error import SecurityRequestError -from .common.segment_error import SegmentError -from .common.segment_trait_error import SegmentTraitError -from .common.userid_error import UserIdError +from .common.exceptions.add_operation_error import AddOperationError +from .common.exceptions.alter_operation_error import AlterOperationError +from .common.exceptions.downstream_fatal_error import DownstreamFatalError +from .common.exceptions.security_request_error import SecurityRequestError +from .common.exceptions.segment_error import SegmentError +from .common.exceptions.segment_trait_error import SegmentTraitError +from .common.exceptions.userid_error import UserIdError from .connection.connection_admin import ConnectionAdmin from .data_set.data_set_admin import DataSetAdmin from .group.group_admin import GroupAdmin diff --git a/pyracf/access/access_admin.py b/pyracf/access/access_admin.py index 32796da8..7135fef6 100644 --- a/pyracf/access/access_admin.py +++ b/pyracf/access/access_admin.py @@ -12,7 +12,9 @@ class AccessAdmin(SecurityAdmin): def __init__( self, + irrsmo00_result_buffer_size: Union[int, None] = None, debug: bool = False, + dump_mode: bool = False, generate_requests_only: bool = False, update_existing_segment_traits: Union[dict, None] = None, replace_existing_segment_traits: Union[dict, None] = None, @@ -44,7 +46,9 @@ def __init__( } super().__init__( "permission", + irrsmo00_result_buffer_size=irrsmo00_result_buffer_size, debug=debug, + dump_mode=dump_mode, generate_requests_only=generate_requests_only, update_existing_segment_traits=update_existing_segment_traits, replace_existing_segment_traits=replace_existing_segment_traits, diff --git a/pyracf/common/add_operation_error.py b/pyracf/common/exceptions/add_operation_error.py similarity index 100% rename from pyracf/common/add_operation_error.py rename to pyracf/common/exceptions/add_operation_error.py diff --git a/pyracf/common/alter_operation_error.py b/pyracf/common/exceptions/alter_operation_error.py similarity index 100% rename from pyracf/common/alter_operation_error.py rename to pyracf/common/exceptions/alter_operation_error.py diff --git a/pyracf/common/downstream_fatal_error.py b/pyracf/common/exceptions/downstream_fatal_error.py similarity index 99% rename from pyracf/common/downstream_fatal_error.py rename to pyracf/common/exceptions/downstream_fatal_error.py index 69610993..9820202f 100644 --- a/pyracf/common/downstream_fatal_error.py +++ b/pyracf/common/exceptions/downstream_fatal_error.py @@ -1,4 +1,5 @@ """Exception to use when IRRSMO00 is unable to process a request.""" + from typing import Union diff --git a/pyracf/common/security_request_error.py b/pyracf/common/exceptions/security_request_error.py similarity index 100% rename from pyracf/common/security_request_error.py rename to pyracf/common/exceptions/security_request_error.py diff --git a/pyracf/common/segment_error.py b/pyracf/common/exceptions/segment_error.py similarity index 100% rename from pyracf/common/segment_error.py rename to pyracf/common/exceptions/segment_error.py diff --git a/pyracf/common/segment_trait_error.py b/pyracf/common/exceptions/segment_trait_error.py similarity index 100% rename from pyracf/common/segment_trait_error.py rename to pyracf/common/exceptions/segment_trait_error.py diff --git a/pyracf/common/userid_error.py b/pyracf/common/exceptions/userid_error.py similarity index 100% rename from pyracf/common/userid_error.py rename to pyracf/common/exceptions/userid_error.py diff --git a/pyracf/common/irrsmo00.c b/pyracf/common/irrsmo00.c index 774fc254..ba31b1a9 100644 --- a/pyracf/common/irrsmo00.c +++ b/pyracf/common/irrsmo00.c @@ -1,3 +1,4 @@ +#define PY_SSIZE_T_CLEAN #include #include @@ -5,103 +6,156 @@ #include #include -#define BUFFER_SIZE (100000) - #pragma linkage(IRRSMO64, OS) -typedef struct { - unsigned char len; - char str[8]; -} VarStr_T; - -// This function changes any null character not preceded by '>' to a blank character. -// 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) { - for (int i = 1; i < str_len; i++){ - if (str[i] == 0) { - if (str[i-1] == 0x6E) { - return; - } - else { - str[i] = 0x40; - } - } - } -} - -static PyObject* call_irrsmo00(PyObject* self, PyObject* args, PyObject *kwargs) { - const unsigned int xml_len; - const unsigned int input_opts; - const uint8_t input_userid_len; - const char *input_xml; - const char *input_userid; - - static char *kwlist[] = {"xml_str", "xml_len", "opts", "userid", "userid_len", NULL}; +typedef struct +{ + unsigned char running_userid_length; + char running_userid[8]; +} running_userid_t; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "y|IIyb", kwlist, &input_xml, &xml_len, &input_opts, &input_userid, &input_userid_len)) { +static PyObject *call_irrsmo00(PyObject *self, PyObject *args, PyObject *kwargs) +{ + const char *request_xml; + const unsigned int request_xml_length; + const unsigned int result_buffer_size; + const unsigned int irrsmo00_options; + const char *running_userid; + const uint8_t running_userid_length; + + static char *kwlist[] = { + "request_xml", + "request_xml_length", + "result_buffer_size", + "irrsmo00_options", + "running_userid", + "running_userid_length", + NULL}; + + if ( + !PyArg_ParseTupleAndKeywords( + args, + kwargs, + "y|IIIyb", + kwlist, + &request_xml, + &request_xml_length, + &result_buffer_size, + &irrsmo00_options, + &running_userid, + &running_userid_length)) + { return NULL; } - char work_area[1024]; - char req_handle[64] = { 0 }; - VarStr_T userid = { input_userid_len, {0}}; + char req_handle[64] = {0}; + running_userid_t running_userid_struct = {running_userid_length, {0}}; unsigned int alet = 0; unsigned int acee = 0; - unsigned char rsp[BUFFER_SIZE+1]; - memset(rsp, 0, BUFFER_SIZE); - unsigned int saf_rc=0, racf_rc=0, racf_rsn=0; - unsigned int num_parms=17, fn=1, opts = input_opts, rsp_len = sizeof(rsp)-1; - - strncpy(userid.str, input_userid, userid.len); + unsigned char result_buffer[result_buffer_size]; + memset(result_buffer, 0, result_buffer_size); + unsigned int saf_rc = 0; + unsigned int racf_rc = 0; + unsigned int racf_rsn = 0; + unsigned int num_parms = 17; + unsigned int fn = 1; + + strncpy( + running_userid_struct.running_userid, + running_userid, + running_userid_struct.running_userid_length); IRRSMO64( - work_area, - alet, - &saf_rc, - alet, - &racf_rc, - alet, - &racf_rsn, - num_parms, - fn, - opts, - xml_len, - input_xml, - req_handle, - userid, - acee, - rsp_len, - rsp - ); - - null_byte_fix(rsp,rsp_len); - - return Py_BuildValue("yBBB", rsp, saf_rc, racf_rc, racf_rsn); + work_area, + alet, + &saf_rc, + alet, + &racf_rc, + alet, + &racf_rsn, + num_parms, + fn, + irrsmo00_options, + request_xml_length, + request_xml, + req_handle, + running_userid_struct, + acee, + result_buffer_size, + result_buffer); + + // https://docs.python.org/3/c-api/arg.html#c.Py_BuildValue + // + // According to the Python 3 C API documentation: + // When memory buffers are passed as parameters to supply data to + // build objects, as for the s and s# formats, the required data is + // copied. Buffers provided by the caller are never referenced by + // the objects created by Py_BuildValue(). In other words, if your + // code invokes malloc() and passes the allocated memory to + // Py_BuildValue(), your code is responsible for calling free() for + // that memory once Py_BuildValue() returns. + // + // y# (bytes) [const char *, Py_ssize_t] + // This converts a C string and its lengths to a Python object. + // If the C string pointer is NULL, None is returned. + // + // https://docs.python.org/3/c-api/arg.html#c.Py_BuildValue + // + // So, given that 'result_buffer' is a stack allocated buffer + // and that Python creates a copy of the buffer, which Python's + // garbage collection should be responsible for, we do not need + // to do any memory mangement here. 'result_buffer' will simply + // just be popped off the stack when this function returns. + // + // Also, according to the Python3 C API documentation, 'y#' should + // be just giving us a copy copy of exactly what is in the buffer, + // without attempting to do any transformations to the data. + // The following GeesForGeeks article futher confirms that we are + // going to get a bytes object that is completely unmanipulated. + // https://www.geeksforgeeks.org/c-strings-conversion-to-python/ + // + // In this case, all post processing of the data is handled on + // the Python side. + // + // Also note that when two or more return values are provided, + // Py_BuildValue() will return a Tuple. + + return Py_BuildValue( + "y#BBB", + result_buffer, + result_buffer_size, + saf_rc, + racf_rc, + racf_rsn); } static char call_irrsmo00_docs[] = - "call_irrsmo00(input_xml: bytes, xml_len: uint, opts: uint): Returns an XML response string and return and reason codes from the IRRSMO00 RACF Callable Service.\n"; + "call_irrsmo00(\n" + " request_xml: bytes,\n" + " request_xml_length: uint,\n" + " result_buffer_size: uint,\n" + " irrsmo00_options: uint,\n" + " running_userid: bytes,\n" + " running_userid_length: uint,\n" + ") -> List[bytes,int,int,int]:\n" + "# Returns an XML result string and return and reason " + "codes from the IRRSMO00 RACF Callable Service.\n"; static PyMethodDef cpyracf_methods[] = { - {"call_irrsmo00", (PyCFunction)call_irrsmo00, - METH_VARARGS | METH_KEYWORDS, call_irrsmo00_docs}, - {NULL} -}; + {"call_irrsmo00", (PyCFunction)call_irrsmo00, + METH_VARARGS | METH_KEYWORDS, call_irrsmo00_docs}, + {NULL}}; -static struct PyModuleDef cpyracf_module_def = -{ +static struct PyModuleDef cpyracf_module_def = { PyModuleDef_HEAD_INIT, - "cpyracf", + "cpyracf", "C code that enables pyRACF to call the IRRSMO00 RACF callable service.\n", -1, - cpyracf_methods -}; + cpyracf_methods}; PyMODINIT_FUNC PyInit_cpyracf(void) { - Py_Initialize(); - return PyModule_Create(&cpyracf_module_def); + Py_Initialize(); + return PyModule_Create(&cpyracf_module_def); } diff --git a/pyracf/common/irrsmo00.py b/pyracf/common/irrsmo00.py index 1c328841..58198f43 100644 --- a/pyracf/common/irrsmo00.py +++ b/pyracf/common/irrsmo00.py @@ -1,4 +1,5 @@ """Interface to irrsmo00.dll.""" + import platform from typing import Union @@ -16,9 +17,36 @@ def call_irrsmo00() -> None: class IRRSMO00: """Interface to irrsmo00 callable service through cpyracf Python extension.""" - def __init__(self) -> None: - # Initialize size of output buffer - self.buffer_size = 100000 + def __init__(self, result_buffer_size=16384) -> None: + # Initialize size of the result buffer (16 kilobytes by default) + self.__result_buffer_size = result_buffer_size + self.__raw_result_xml = b"" + + def get_raw_result_xml(self) -> bytes: + """Get the current preserved raw result XML from IRRSMO00.""" + return self.__raw_result_xml + + def clear_raw_result_xml(self) -> None: + """Clear the current preserved raw result XML from IRRSMO00.""" + self.__raw_result_xml = b"" + + def __null_byte_fix(self, result_xml: bytes) -> bytes: + """ + This function replaces all null bytes that exist before the + last occurance the '>' (0x6E in IBM-1047) character in the + result XML with ' ' (0x40 in IBM-1047) characters. + This is a workaround for an issue where profile data embedded + in result XML returned by IRROSMO00 sometimes includes null + bytes instead of properly encoded text, which causes the + returned xml to be truncated. + """ + result_xml = bytearray(result_xml) + last_greater_than = result_xml.rfind(b"\x6e") + for i in range(last_greater_than): + if result_xml[i] == 0: + # 64 is 0x40, which is a space character in IBM-1047 encoding. + result_xml[i] = 64 + return bytes(result_xml) def call_racf( self, @@ -27,19 +55,38 @@ def call_racf( run_as_userid: Union[str, None] = None, ) -> str: """Make request to call_irrsmo00 in the cpyracf Python extension.""" - options = 15 if precheck else 13 - userid = b"" - userid_length = 0 + irrsmo00_options = 15 if precheck else 13 + running_userid = b"" if run_as_userid: - userid = run_as_userid.encode("cp1047") - userid_length = len(run_as_userid) - response = call_irrsmo00( - xml_str=request_xml, - xml_len=len(request_xml), - opts=options, - userid=userid, - userid_len=userid_length, + running_userid = run_as_userid.encode("cp1047") + result = call_irrsmo00( + request_xml=request_xml, + request_xml_length=len(request_xml), + result_buffer_size=self.__result_buffer_size, + irrsmo00_options=irrsmo00_options, + running_userid=running_userid, + running_userid_length=len(running_userid), ) - if response[0] == b"": - return list(response[1:4]) - return response[0].decode("cp1047") + # Preserve raw result XML just in case we need to create a dump. + # If the decoded result XML cannot be parsed with the XML parser, + # a dump may need to be taken to aid in problem determination. + self.__raw_result_xml = result[0] + # Replace any null bytes in the result XML with spaces. + result_xml = self.__null_byte_fix(result[0]) + # 'irrsmo00.c' returns a raw unmodified bytes object containing a copy + # of the exact contents of the result xml buffer that the IRRSMO00 + # callable service populates. + # + # The first occurance of a null byte '0x00' is the end of the IBM-1047 + # encoded result XML and all of the trailing null bytes should be removed + # from the result XML to ensure that downstream XML parsing is successful. + result_xml_length = len(result_xml) + null_terminator_index = result_xml.find(b"\x00") + if null_terminator_index != -1: + result_xml_length = null_terminator_index + # If 'result_xml_length' is 0, this indicates that the IRRSMO00 callable + # service was unable to process the request. in this case, would should + # only return the return and reasons codes for error handling downstream. + if result_xml_length == 0: + return list(result[1:4]) + return result_xml[:result_xml_length].decode("cp1047") diff --git a/pyracf/common/security_admin.py b/pyracf/common/security_admin.py index 4ad7c086..d916b1fe 100644 --- a/pyracf/common/security_admin.py +++ b/pyracf/common/security_admin.py @@ -2,86 +2,124 @@ import platform import re +import xml.etree.ElementTree from datetime import datetime from typing import Any, List, Tuple, Union -from .downstream_fatal_error import DownstreamFatalError +from .exceptions.downstream_fatal_error import DownstreamFatalError +from .exceptions.security_request_error import SecurityRequestError +from .exceptions.segment_error import SegmentError +from .exceptions.segment_trait_error import SegmentTraitError +from .exceptions.userid_error import UserIdError from .irrsmo00 import IRRSMO00 -from .logger import Logger from .security_request import SecurityRequest -from .security_request_error import SecurityRequestError from .security_result import SecurityResult -from .segment_error import SegmentError -from .segment_trait_error import SegmentTraitError -from .userid_error import UserIdError +from .utilities.dumper import Dumper +from .utilities.logger import Logger class SecurityAdmin: """Base Class for RACF Administration Interface.""" + _common_base_traits_data_set_generic = { + "base:aclcnt": "racf:aclcnt", + "base:aclacnt": "racf:aclacnt", + "base:aclacs": "racf:aclacs", + "base:aclid": "racf:aclid", + "base:acl2cnt": "racf:acl2cnt", + "base:acl2acnt": "racf:acl2acnt", + "base:acl2acs": "racf:acl2acs", + "base:acl2cond": "racf:acl2cond", + "base:acl2ent": "racf:acl2ent", + "base:acl2id": "racf:acl2id", + "base:acsaltr": "racf:acsaltr", + "base:acscntl": "racf:acscntl", + "base:acsread": "racf:acsread", + "base:acsupdt": "racf:acsupdt", + "base:all": "racf:all", + "base:audaltr": "racf:audaltr", + "base:audcntl": "racf:audcntl", + "base:audnone": "racf:audnone", + "base:audread": "racf:audread", + "base:audupdt": "racf:audupdt", + "base:authuser": "racf:authuser", + "base:fvolume": "racf:fvolume", + "base:gaudaltr": "racf:gaudaltr", + "base:gaudcntl": "racf:gaudcntl", + "base:gaudnone": "racf:gaudnone", + "base:gaudread": "racf:gaudread", + "base:gaudupdt": "racf:gaudupdt", + "base:generic": "racf:generic", + } + _valid_segment_traits = {} _extracted_key_value_pair_segment_traits_map = {} _case_sensitive_extracted_values = [] __running_userid = None - __logger = Logger() + _logger = Logger() + __dumper = Dumper() def __init__( self, profile_type: str, + irrsmo00_result_buffer_size: Union[int, None] = None, debug: bool = False, + dump_mode: bool = False, generate_requests_only: bool = False, update_existing_segment_traits: Union[dict, None] = None, replace_existing_segment_traits: Union[dict, None] = None, additional_secret_traits: Union[List[str], None] = None, run_as_userid: Union[str, None] = None, ) -> None: - self._common_base_traits_data_set_generic = { - "base:aclcnt": "racf:aclcnt", - "base:aclacnt": "racf:aclacnt", - "base:aclacs": "racf:aclacs", - "base:aclid": "racf:aclid", - "base:acl2cnt": "racf:acl2cnt", - "base:acl2acnt": "racf:acl2acnt", - "base:acl2acs": "racf:acl2acs", - "base:acl2cond": "racf:acl2cond", - "base:acl2ent": "racf:acl2ent", - "base:acl2id": "racf:acl2id", - "base:acsaltr": "racf:acsaltr", - "base:acscntl": "racf:acscntl", - "base:acsread": "racf:acsread", - "base:acsupdt": "racf:acsupdt", - "base:all": "racf:all", - "base:audaltr": "racf:audaltr", - "base:audcntl": "racf:audcntl", - "base:audnone": "racf:audnone", - "base:audread": "racf:audread", - "base:audupdt": "racf:audupdt", - "base:authuser": "racf:authuser", - "base:fvolume": "racf:fvolume", - "base:gaudaltr": "racf:gaudaltr", - "base:gaudcntl": "racf:gaudcntl", - "base:gaudnone": "racf:gaudnone", - "base:gaudread": "racf:gaudread", - "base:gaudupdt": "racf:gaudupdt", - "base:generic": "racf:generic", - } self.__secret_traits = { "base:password": "racf:password", "base:passphrase": "racf:phrase", } - self.__irrsmo00 = IRRSMO00() + if irrsmo00_result_buffer_size: + if ( + not isinstance(irrsmo00_result_buffer_size, int) + or irrsmo00_result_buffer_size < 10000 + ): + raise ValueError( + "IRRSMO00 result buffer size must be an " + + "integer value greater than or equal to '10000'." + ) + elif irrsmo00_result_buffer_size > 100000000: + self._logger.log_warning( + "IRRSMO00 result buffer sizes greater than '100000000' may " + + "result in a 'SIGKILL' signal to be raised, which is NOT " + + "recoverable and will lead to the Python process that " + + "pyRACF is running under to be killed." + ) + self.__irrsmo00 = IRRSMO00(result_buffer_size=irrsmo00_result_buffer_size) + else: + self.__irrsmo00 = IRRSMO00() self._profile_type = profile_type self._segment_traits = {} # used to preserve segment traits for debug logging. self.__preserved_segment_traits = {} self._trait_map = {} self.__debug = debug + if self.__debug: + self._logger.log_warning( + "'Debug Logging' is enabled. This feature should only " + + "be used for development and debugging." + ) + self.__dump_mode = dump_mode + if self.__dump_mode: + self._logger.log_warning( + "'Dump Mode' is enabled. This feature should only " + + "be used for development and debugging." + ) self._generate_requests_only = generate_requests_only if update_existing_segment_traits is not None: + self._logger.log_experimental("Update Existing Segment Traits") self.__update_valid_segment_traits(update_existing_segment_traits) if replace_existing_segment_traits is not None: + self._logger.log_experimental("Replace Existing Segment Traits") self.__replace_valid_segment_traits(replace_existing_segment_traits) if additional_secret_traits is not None: + self._logger.log_experimental("Add Additional Secret Traits") self.__add_additional_secret_traits(additional_secret_traits) self.set_running_userid(run_as_userid) @@ -120,6 +158,20 @@ def __replace_valid_segment_traits(self, new_valid_segment_traits: dict) -> None """Replace field data in valid segment traits dictionary""" self._valid_segment_traits = new_valid_segment_traits + # ============================================================================ + # Dump Mode + # ============================================================================ + def __raw_dump(self) -> None: + raw_result_xml = self.__irrsmo00.get_raw_result_xml() + dump_file_path = self.__dumper.raw_dump(raw_result_xml) + self._logger.log_info( + f"Raw security result XML has been written to '{dump_file_path}'.\n" + ) + if self.__debug: + # Note, since the hex dump is logged to the console, + # secrets will be redacted. + self._logger.log_hex_dump(raw_result_xml, self.__secret_traits) + # ============================================================================ # Secrets Redaction # ============================================================================ @@ -153,9 +205,7 @@ def _extract_and_check_result( # No need to redact anything here since the result dictionary # already has secrets redacted when it is built, and profile # extract doesn't return any secrets anyways. - self.__logger.log_dictionary( - "Result Dictionary (Formatted Profile)", result - ) + self._logger.log_dictionary("Result Dictionary (Formatted Profile)", result) return result def _make_request( @@ -168,24 +218,24 @@ def _make_request( Note: Secrets are redacted from all data returned to the user and log messages. """ if self.__debug: - self.__logger.log_dictionary( + self._logger.log_dictionary( "Request Dictionary", self.__preserved_segment_traits, secret_traits=self.__secret_traits, ) - self.__logger.log_xml( + self._logger.log_xml( "Request XML", security_request.dump_request_xml(encoding="utf-8"), secret_traits=self.__secret_traits, ) - request_xml = self.__logger.redact_request_xml( + request_xml = self._logger.redact_request_xml( security_request.dump_request_xml(encoding="utf-8"), secret_traits=self.__secret_traits, ) if self._generate_requests_only: self.__clear_state(security_request) return request_xml - raw_result = self.__logger.redact_result_xml( + raw_result = self._logger.redact_result_xml( self.__irrsmo00.call_racf( security_request.dump_request_xml(), irrsmo00_precheck, @@ -195,7 +245,7 @@ def _make_request( ) self.__clear_state(security_request) if isinstance(raw_result, list): - # When IRRSMO00 encounters some errors, it returns no XML response string. + # When IRRSMO00 encounters some errors, it returns no result XML string. # When this happens, the C code instead surfaces the return and reason # codes which causes a DownstreamFatalError to be raised. raise DownstreamFatalError( @@ -208,12 +258,29 @@ def _make_request( if self.__debug: # No need to redact anything here since the raw result XML # already has secrets redacted when it is built. - self.__logger.log_xml("Result XML", raw_result) - result = SecurityResult(raw_result, self.get_running_userid()) + self._logger.log_xml("Result XML", raw_result) + try: + result = SecurityResult(raw_result, self.get_running_userid()) + except xml.etree.ElementTree.ParseError as e: + # If the result XML cannot be parsed, a dump of the raw result + # XML will be created to aid in problem determination. + self._logger.log_fatal( + "Unable to parse security result XML returned by IRRSMO00." + ) + self.__raw_dump() + self.__irrsmo00.clear_raw_result_xml() + # After creating the dump, re-raise the exception. + raise e + if self.__dump_mode: + # If in dump mode a dump of the raw result XML + # will be created even if the raw security result was + # able to be processed successfully. + self.__raw_dump() + self.__irrsmo00.clear_raw_result_xml() if self.__debug: # No need to redact anything here since the result dictionary # already has secrets redacted when it is built. - self.__logger.log_dictionary( + self._logger.log_dictionary( "Result Dictionary", result.get_result_dictionary() ) result_dictionary = result.get_result_dictionary() @@ -574,12 +641,12 @@ def __format_user_list_data( profile[current_segment]["users"][user_index]["access"] = self._cast_from_str( user_fields[1] ) - profile[current_segment]["users"][user_index][ - "accessCount" - ] = self._cast_from_str(user_fields[2]) - profile[current_segment]["users"][user_index][ - "universalAccess" - ] = self._cast_from_str(user_fields[3]) + profile[current_segment]["users"][user_index]["accessCount"] = ( + self._cast_from_str(user_fields[2]) + ) + profile[current_segment]["users"][user_index]["universalAccess"] = ( + self._cast_from_str(user_fields[3]) + ) self.__add_key_value_pairs_to_segment( current_segment, @@ -678,9 +745,9 @@ def __format_tabular_data( subfield: self._clean_and_separate(value_tokens[-1].rstrip(")")) } else: - profile[current_segment][field][ - subfield - ] = self._clean_and_separate(value_tokens[-1].rstrip(")")) + profile[current_segment][field][subfield] = ( + self._clean_and_separate(value_tokens[-1].rstrip(")")) + ) elif field in list_fields: profile[current_segment][field] = [] profile[current_segment][field].append(self._clean_and_separate(value)) diff --git a/pyracf/common/utilities/dumper.py b/pyracf/common/utilities/dumper.py new file mode 100644 index 00000000..2e98687d --- /dev/null +++ b/pyracf/common/utilities/dumper.py @@ -0,0 +1,42 @@ +"""Create raw dump files containing raw result XML returned by IRRSMO00.""" + +import hashlib +import os +from datetime import datetime + + +class Dumper: + def raw_dump(self, raw_result_xml: bytes) -> str: + """Dump raw result XML returned by IRRSMO00 to a dump file.""" + dot_pyracf_directory = os.path.join(os.path.expanduser("~"), ".pyracf") + dump_directory = os.path.join(dot_pyracf_directory, "dump") + if not os.path.exists(dump_directory): + os.makedirs(dump_directory, mode=0o700) + # umask may override the permission bits we set + # upon creation of directories. It also is a possiblity + # that existing directories have permssion bits set that + # are too permissive, so always exlpicitely setting the + # the permission bits on directories using os.chmod() + # ensures that directories always have the the correct + # permissions. + if oct(os.stat(dot_pyracf_directory).st_mode)[-3:] != "700": + os.chmod(dot_pyracf_directory, 0o700) + if oct(os.stat(dump_directory).st_mode)[-3:] != "700": + os.chmod(dump_directory, 0o700) + timestamp = self.__get_timestamp() + md5_hash = hashlib.md5(raw_result_xml).hexdigest() + dump_file_name = f"pyracf.{timestamp}.{md5_hash}.dump" + dump_file_path = os.path.join(dump_directory, dump_file_name) + with open(dump_file_path, "wb", opener=self.__opener) as dump_file_writer: + dump_file_writer.write(raw_result_xml) + return dump_file_path + + def __opener(self, path: str, flags: int) -> int: + return os.open(path, flags, 0o600) + + def __get_timestamp(self) -> str: + """ + 'datetime' is immutable, so we need this function to allow the + timestamp generataion function to be mocked for unit tests. + """ + return datetime.now().strftime("%Y%m%d-%H%M%S") diff --git a/pyracf/common/logger.py b/pyracf/common/utilities/logger.py similarity index 70% rename from pyracf/common/logger.py rename to pyracf/common/utilities/logger.py index d4b42eb0..abe85099 100644 --- a/pyracf/common/logger.py +++ b/pyracf/common/utilities/logger.py @@ -4,6 +4,7 @@ import json import os import re +import struct from typing import List, Union @@ -13,33 +14,48 @@ class Logger: __ansi_reset = "\033[0m" __ansi_gray = "\033[2m" __ansi_green = "\033[32m" + __ansi_red = "\033[0;31m" + __ansi_yellow = "\033[1;33m" __ansi_blue = "\033[34m" + __ansi_light_blue = "\033[1;34m" __ansi_orange = "\033[38;5;214m" __ansi_cyan = "\033[96m" __ansi_purple_background = "\033[1;45m" def __gray(self, string: str) -> str: - """Make contents of string gray.""" + """Make the text in a string gray.""" return self.__colorize_string(self.__ansi_gray, string) def __green(self, string: str) -> str: - """Make contents of string green.""" + """Make the text in a string green.""" return self.__colorize_string(self.__ansi_green, string) + def __red(self, string: str) -> str: + """Make the text in a string red.""" + return self.__colorize_string(self.__ansi_red, string) + + def __yellow(self, string: str) -> str: + """Make the text in a string yellow.""" + return self.__colorize_string(self.__ansi_yellow, string) + def __blue(self, string: str) -> str: - """Make contents of string blue.""" + """Make the text in a string blue.""" return self.__colorize_string(self.__ansi_blue, string) + def __light_blue(self, string: str) -> str: + """Make the text in a string light blue.""" + return self.__colorize_string(self.__ansi_light_blue, string) + def __orange(self, string: str) -> str: - """Make contents of string orange.""" + """Make the text in a string orange.""" return self.__colorize_string(self.__ansi_orange, string) def __cyan(self, string: str) -> str: - """Make contents of string cyan.""" + """Make the text in a string cyan.""" return self.__colorize_string(self.__ansi_cyan, string) def __purple_background(self, string: str) -> str: - """Make contents of string magenta.""" + """Make background color of a string magenta.""" return self.__colorize_string(self.__ansi_purple_background, string) def __colorize_string(self, ansi_color: str, string: str) -> str: @@ -73,6 +89,18 @@ def log_xml( colorized_indented_xml_string = self.__colorize_xml(indented_xml_string) self.log_debug(header_message, colorized_indented_xml_string) + def log_info(self, message: str): + """Log an informational message to the console.""" + print(f"[ {self.__cyan('INFO')} ] {message}") + + def log_warning(self, message: str): + """Log a warning message to the console.""" + print(f"[ {self.__yellow('WARN')} ] {message}") + + def log_fatal(self, message: str): + """Log a fatal error message to the console.""" + print(f"[ {self.__red('FATAL')} ] {message}") + def log_debug(self, header_message: str, message: str) -> None: """Log function to use for debug logging.""" stack = list(reversed(inspect.stack())) @@ -96,6 +124,12 @@ def log_debug(self, header_message: str, message: str) -> None: ) print(f"{header}\n{message}") + def log_experimental(self, feature: str) -> None: + self.log_warning( + f"'{feature}' is an experimental feature. This feature is " + + "subject to major changes and even being removed entirely." + ) + def __redact_request_dictionary( self, dictionary: dict, @@ -114,17 +148,20 @@ def __redact_request_dictionary( def __redact_string( self, - input_string: str, + input_string: Union[str, bytes], start_ind: int, - end_pattern: str, + end_pattern: Union[str, bytes], ): """ Redacts characters in a string between a starting index and ending pattern. Replaces the identified characters with '********' regardless of the original length. """ + asterisks = "********" + if isinstance(input_string, bytes): + asterisks = asterisks.encode("cp1047") pre_keyword = input_string[:start_ind] post_keyword = end_pattern.join(input_string[start_ind:].split(end_pattern)[1:]) - return pre_keyword + "********" + end_pattern + post_keyword + return pre_keyword + asterisks + end_pattern + post_keyword def redact_request_xml( self, @@ -161,7 +198,7 @@ def redact_request_xml( def redact_result_xml( self, - security_response: Union[str, List[int]], + security_result: Union[str, bytes, List[int]], secret_traits: dict, ) -> str: """ @@ -170,17 +207,24 @@ def redact_result_xml( 'TRAIT (value)' This function also accounts for varied amounts of whitespace in the pattern. """ - if isinstance(security_response, list): - return security_response + if isinstance(security_result, list): + return security_result for xml_key in secret_traits.values(): racf_key = xml_key.split(":")[1] if ":" in xml_key else xml_key - match = re.search(rf"{racf_key.upper()} +\(", security_response) + end_pattern = ")" + if isinstance(security_result, bytes): + match = re.search( + rf"{racf_key.upper()} +\(", security_result.decode("cp1047") + ) + end_pattern = end_pattern.encode("cp1047") + else: + match = re.search(rf"{racf_key.upper()} +\(", security_result) if not match: continue - security_response = self.__redact_string( - security_response, match.end(), ")" + security_result = self.__redact_string( + security_result, match.end(), end_pattern ) - return security_response + return security_result def __colorize_json(self, json_text: str) -> str: updated_json_text = "" @@ -310,3 +354,58 @@ def __indent_xml(self, minified_xml: str) -> str: current_line = f"<{current_line}>" indented_xml += f"{' ' * indent_level}{current_line}\n" return indented_xml[:-2] + + def log_hex_dump(self, raw_result_xml: bytes, secret_traits: dict) -> None: + """ + Log the raw result XML returned by IRRSMO00 as a hex dump. + """ + hex_row = "" + hex_row_size = 0 + interpreted_row = "" + i = 0 + hex_dump = "" + # Redact secrets from raw result because we are logging it to the console. + raw_result_xml = self.redact_result_xml( + raw_result_xml, + secret_traits, + ) + for byte in raw_result_xml: + color_function = self.__green + char = struct.pack("B", byte).decode("cp1047") + # Non-displayable characters should be interpreted as '.'. + match len(repr(char)): + # All non-displayable characters should be red. + # Two exceptions are 0x00 and 0xff, which are + # overriden later on. + case 6: + color_function = self.__red + char = "." + # '\t', '\r', and '\n' control characters should be yellow. + case 4: + color_function = self.__yellow + char = "." + if byte == 0: + # Null bytes (0x00) should have no color. + # 'str()' will return an unmodified version of the string passed to it. + color_function = str + elif byte == 255: + # 0xFF should be light blue. + color_function = self.__light_blue + hex_row += f"{color_function(hex(byte)[2:].rjust(2, '0'))}" + hex_row_size += 2 + interpreted_row += f"{color_function(char)}" + i += 1 + if i % 2 == 0: + hex_row += " " + hex_row_size += 1 + if i % 16 == 0: + row_index = hex(i - 16)[2:].rjust(8, "0") + hex_dump += f"{row_index}: {hex_row} {interpreted_row}\n" + hex_row = "" + hex_row_size = 0 + interpreted_row = "" + if interpreted_row != "": + row_index = hex(i - len(interpreted_row))[2:].rjust(8, "0") + interpreted_row = " " * (40 - hex_row_size) + interpreted_row + hex_dump += f"{row_index}: {hex_row} {interpreted_row}\n" + self.log_debug("Hex Dump", hex_dump) diff --git a/pyracf/connection/connection_admin.py b/pyracf/connection/connection_admin.py index 0162ccfb..414b3fa4 100644 --- a/pyracf/connection/connection_admin.py +++ b/pyracf/connection/connection_admin.py @@ -12,7 +12,9 @@ class ConnectionAdmin(SecurityAdmin): def __init__( self, + irrsmo00_result_buffer_size: Union[int, None] = None, debug: bool = False, + dump_mode: bool = False, generate_requests_only: bool = False, update_existing_segment_traits: Union[dict, None] = None, replace_existing_segment_traits: Union[dict, None] = None, @@ -36,7 +38,9 @@ def __init__( } super().__init__( "groupConnection", + irrsmo00_result_buffer_size=irrsmo00_result_buffer_size, debug=debug, + dump_mode=dump_mode, generate_requests_only=generate_requests_only, update_existing_segment_traits=update_existing_segment_traits, replace_existing_segment_traits=replace_existing_segment_traits, diff --git a/pyracf/data_set/data_set_admin.py b/pyracf/data_set/data_set_admin.py index 940a5763..9233ee0c 100644 --- a/pyracf/data_set/data_set_admin.py +++ b/pyracf/data_set/data_set_admin.py @@ -2,10 +2,10 @@ from typing import List, Union -from pyracf.common.add_operation_error import AddOperationError -from pyracf.common.alter_operation_error import AlterOperationError +from pyracf.common.exceptions.add_operation_error import AddOperationError +from pyracf.common.exceptions.alter_operation_error import AlterOperationError +from pyracf.common.exceptions.security_request_error import SecurityRequestError from pyracf.common.security_admin import SecurityAdmin -from pyracf.common.security_request_error import SecurityRequestError from .data_set_request import DataSetRequest @@ -15,7 +15,9 @@ class DataSetAdmin(SecurityAdmin): def __init__( self, + irrsmo00_result_buffer_size: Union[int, None] = None, debug: bool = False, + dump_mode: bool = False, generate_requests_only: bool = False, update_existing_segment_traits: Union[dict, None] = None, replace_existing_segment_traits: Union[dict, None] = None, @@ -61,19 +63,21 @@ def __init__( "dfp": {"dfp:owner": "racf:resowner", "dfp:ckds_data_key": "racf:datakey"}, "tme": {"tme:roles": "racf:roles"}, } + self._valid_segment_traits["base"].update( + self._common_base_traits_data_set_generic + ) + del self._valid_segment_traits["base"]["base:generic"] super().__init__( "dataSet", + irrsmo00_result_buffer_size=irrsmo00_result_buffer_size, debug=debug, + dump_mode=dump_mode, generate_requests_only=generate_requests_only, update_existing_segment_traits=update_existing_segment_traits, replace_existing_segment_traits=replace_existing_segment_traits, additional_secret_traits=additional_secret_traits, run_as_userid=run_as_userid, ) - self._valid_segment_traits["base"].update( - self._common_base_traits_data_set_generic - ) - del self._valid_segment_traits["base"]["base:generic"] # ============================================================================ # Access diff --git a/pyracf/group/group_admin.py b/pyracf/group/group_admin.py index 8a3514d8..bcc2f707 100644 --- a/pyracf/group/group_admin.py +++ b/pyracf/group/group_admin.py @@ -2,10 +2,10 @@ from typing import List, Union -from pyracf.common.add_operation_error import AddOperationError -from pyracf.common.alter_operation_error import AlterOperationError +from pyracf.common.exceptions.add_operation_error import AddOperationError +from pyracf.common.exceptions.alter_operation_error import AlterOperationError +from pyracf.common.exceptions.security_request_error import SecurityRequestError from pyracf.common.security_admin import SecurityAdmin -from pyracf.common.security_request_error import SecurityRequestError from .group_request import GroupRequest @@ -15,7 +15,9 @@ class GroupAdmin(SecurityAdmin): def __init__( self, + irrsmo00_result_buffer_size: Union[int, None] = None, debug: bool = False, + dump_mode: bool = False, generate_requests_only: bool = False, update_existing_segment_traits: Union[dict, None] = None, replace_existing_segment_traits: Union[dict, None] = None, @@ -47,7 +49,9 @@ def __init__( } super().__init__( "group", + irrsmo00_result_buffer_size=irrsmo00_result_buffer_size, debug=debug, + dump_mode=dump_mode, generate_requests_only=generate_requests_only, update_existing_segment_traits=update_existing_segment_traits, replace_existing_segment_traits=replace_existing_segment_traits, diff --git a/pyracf/resource/resource_admin.py b/pyracf/resource/resource_admin.py index 27219b92..940c37f3 100644 --- a/pyracf/resource/resource_admin.py +++ b/pyracf/resource/resource_admin.py @@ -3,10 +3,10 @@ from collections import Counter from typing import List, Union -from pyracf.common.add_operation_error import AddOperationError -from pyracf.common.alter_operation_error import AlterOperationError +from pyracf.common.exceptions.add_operation_error import AddOperationError +from pyracf.common.exceptions.alter_operation_error import AlterOperationError +from pyracf.common.exceptions.security_request_error import SecurityRequestError from pyracf.common.security_admin import SecurityAdmin -from pyracf.common.security_request_error import SecurityRequestError from .resource_request import ResourceRequest @@ -16,7 +16,9 @@ class ResourceAdmin(SecurityAdmin): def __init__( self, + irrsmo00_result_buffer_size: Union[int, None] = None, debug: bool = False, + dump_mode: bool = False, generate_requests_only: bool = False, update_existing_segment_traits: Union[dict, None] = None, replace_existing_segment_traits: Union[dict, None] = None, @@ -238,7 +240,9 @@ def __init__( } super().__init__( "resource", + irrsmo00_result_buffer_size=irrsmo00_result_buffer_size, debug=debug, + dump_mode=dump_mode, generate_requests_only=generate_requests_only, update_existing_segment_traits=update_existing_segment_traits, replace_existing_segment_traits=replace_existing_segment_traits, diff --git a/pyracf/setropts/setropts_admin.py b/pyracf/setropts/setropts_admin.py index f8e7f041..f63bfe93 100644 --- a/pyracf/setropts/setropts_admin.py +++ b/pyracf/setropts/setropts_admin.py @@ -12,7 +12,9 @@ class SetroptsAdmin(SecurityAdmin): def __init__( self, + irrsmo00_result_buffer_size: Union[int, None] = None, debug: bool = False, + dump_mode: bool = False, generate_requests_only: bool = False, update_existing_segment_traits: Union[dict, None] = None, replace_existing_segment_traits: Union[dict, None] = None, @@ -110,7 +112,9 @@ def __init__( } super().__init__( "systemSettings", + irrsmo00_result_buffer_size=irrsmo00_result_buffer_size, debug=debug, + dump_mode=dump_mode, generate_requests_only=generate_requests_only, update_existing_segment_traits=update_existing_segment_traits, replace_existing_segment_traits=replace_existing_segment_traits, @@ -318,6 +322,7 @@ def remove_raclist_classes( # ============================================================================ def list_racf_options(self, options_only: bool = False) -> Union[dict, bytes]: """List RACF options.""" + self._logger.log_experimental("List RACF Options") self._build_segment_trait_dictionary({"base:list": True}) setropts_request = SetroptsRequest() self._add_traits_directly_to_request_xml_with_no_segments(setropts_request) @@ -708,10 +713,10 @@ def __add_generic_subfield( subdictionary["enabled"] = self._cast_from_str( value_tokens[0] ) - subdictionary[ - "options" - ] = self.__process_generic_subsubfield_options( - value_tokens[1] + subdictionary["options"] = ( + self.__process_generic_subsubfield_options( + value_tokens[1] + ) ) else: subdictionary["enabled"] = self._cast_from_str(value_raw) diff --git a/pyracf/user/user_admin.py b/pyracf/user/user_admin.py index ed5810b0..86db789e 100644 --- a/pyracf/user/user_admin.py +++ b/pyracf/user/user_admin.py @@ -2,10 +2,10 @@ from typing import List, Union -from pyracf.common.add_operation_error import AddOperationError -from pyracf.common.alter_operation_error import AlterOperationError +from pyracf.common.exceptions.add_operation_error import AddOperationError +from pyracf.common.exceptions.alter_operation_error import AlterOperationError +from pyracf.common.exceptions.security_request_error import SecurityRequestError from pyracf.common.security_admin import SecurityAdmin -from pyracf.common.security_request_error import SecurityRequestError from .user_request import UserRequest @@ -15,7 +15,9 @@ class UserAdmin(SecurityAdmin): def __init__( self, + irrsmo00_result_buffer_size: Union[int, None] = None, debug: bool = False, + dump_mode: bool = False, generate_requests_only: bool = False, update_existing_segment_traits: Union[dict, None] = None, replace_existing_segment_traits: Union[dict, None] = None, @@ -208,7 +210,9 @@ def __init__( self._case_sensitive_extracted_values = ["homeDirectory", "defaultShell"] super().__init__( "user", + irrsmo00_result_buffer_size=irrsmo00_result_buffer_size, debug=debug, + dump_mode=dump_mode, generate_requests_only=generate_requests_only, update_existing_segment_traits=update_existing_segment_traits, replace_existing_segment_traits=replace_existing_segment_traits, diff --git a/tests/access/test_access_debug_logging.py b/tests/access/test_access_debug_logging.py index 8f70e86b..2f34c1aa 100644 --- a/tests/access/test_access_debug_logging.py +++ b/tests/access/test_access_debug_logging.py @@ -10,7 +10,6 @@ import tests.access.test_access_constants as TestAccessConstants from pyracf import AccessAdmin, SecurityRequestError -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -19,7 +18,6 @@ @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") class TestAccessDebugLogging(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) access_admin = AccessAdmin(debug=True) ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])") diff --git a/tests/access/test_access_request_builder.py b/tests/access/test_access_request_builder.py index 4fbe20bc..5623a49e 100644 --- a/tests/access/test_access_request_builder.py +++ b/tests/access/test_access_request_builder.py @@ -1,13 +1,11 @@ """Test access request builder.""" import unittest -from unittest.mock import Mock import __init__ import tests.access.test_access_constants as TestAccessConstants from pyracf import AccessAdmin -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -15,7 +13,6 @@ class TestAccessRequestBuilder(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) access_admin = AccessAdmin(generate_requests_only=True) def test_access_admin_build_permit_access_request(self): diff --git a/tests/access/test_access_result_parser.py b/tests/access/test_access_result_parser.py index cbb94348..50c45692 100644 --- a/tests/access/test_access_result_parser.py +++ b/tests/access/test_access_result_parser.py @@ -7,7 +7,6 @@ import tests.access.test_access_constants as TestAccessConstants from pyracf import AccessAdmin, SecurityRequestError -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -16,7 +15,6 @@ @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") class TestAccessResultParser(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) access_admin = AccessAdmin() # ============================================================================ diff --git a/tests/common/common_log_samples/alter_user_password_success_dump_mode.log b/tests/common/common_log_samples/alter_user_password_success_dump_mode.log new file mode 100644 index 00000000..750a5966 --- /dev/null +++ b/tests/common/common_log_samples/alter_user_password_success_dump_mode.log @@ -0,0 +1,429 @@ + + [pyRACF:Debug] + Request Dictionary + UserAdmin.alter() + + +{} + + + [pyRACF:Debug] + Request XML + UserAdmin.alter() + + + + + + + + [pyRACF:Debug] + Result XML + UserAdmin.alter() + + + + + + + 0 + 0 + 0 + LISTUSER SQUIDWRD + 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 + + + 0 + 0 + + +[ INFO ] Raw security result XML has been written to '/u/testuser/.pyracf/dump/pyracf...dump'. + + + [pyRACF:Debug] + Hex Dump + UserAdmin.alter() + + +00000000: 4c6f a794 9340 a585 99a2 8996 957e 7ff1 Definiti +000000c0: 9695 4085 a789 a2a3 a24b 40c1 8484 4083 on exists. Add c +000000d0: 9694 9481 9584 40a2 9289 9797 8584 4084 ommand skipped d +000000e0: a485 4040 a396 4097 9985 8388 8583 9240 ue to precheck +000000f0: 9697 a389 9695 4c61 8995 8696 6e4c 8396 option00 +00000140: 4c99 8581 a296 9583 9684 856e f04c 6199 0ALTUSER SQUIDWR +00000170: c440 4040 4040 d5c1 d4c5 4040 4040 4040 D NAME +00000180: 4040 4d7d e298 a489 84a6 8199 847d 5d40 ('Squidward') +00000190: d6e6 d5c5 d940 4040 4040 4040 4d93 8596 OWNER (leo +000001a0: 9581 9984 5d40 e2d7 c5c3 c9c1 d340 4040 nard) SPECIAL +000001b0: 4040 40d7 c1e2 e2e6 d6d9 c440 4040 404d PASSWORD ( +000001c0: 5c5c 5c5c 5c5c 5c5c 5d40 d6d4 e5e2 4040 ********) OMVS +000001d0: 4040 404d e4c9 c440 4040 4040 4040 4040 (UID +000001e0: 4df2 f4f2 f45d 40c8 d6d4 c540 4040 4040 (2424) HOME +000001f0: 4040 404d 7d61 a461 a298 a489 84a6 9984 ('/u/squidwrd +00000200: 7d5d 40d7 d9d6 c7d9 c1d4 4040 4040 404d ') PROGRAM ( +00000210: 7d61 8289 9561 a288 7d5d 5d4c 6189 9481 '/bin/sh')) +00000240: f04c 6199 85a3 a499 9583 9684 856e 4c99 00....... +00000280: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000290: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000002a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000002b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000002c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000002d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000002e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000002f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000300: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000310: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000320: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000330: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000340: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000350: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000360: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000370: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000380: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000390: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000003a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000003b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000003c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000003d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000003e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000003f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ + + + [pyRACF:Debug] + Result Dictionary + UserAdmin.alter() + + +{ + "securityResult": { + "user": { + "name": "SQUIDWRD", + "operation": "listdata", + "requestId": "UserRequest", + "commands": [ + { + "safReturnCode": 0, + "returnCode": 0, + "reasonCode": 0, + "image": "LISTUSER SQUIDWRD ", + "messages": [ + "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" + ] + } + ] + }, + "returnCode": 0, + "reasonCode": 0, + "runningUserid": "testuser" + } +} + + + [pyRACF:Debug] + Result Dictionary (Formatted Profile) + UserAdmin.alter() + + +{ + "securityResult": { + "user": { + "name": "SQUIDWRD", + "operation": "listdata", + "requestId": "UserRequest", + "commands": [ + { + "safReturnCode": 0, + "returnCode": 0, + "reasonCode": 0, + "image": "LISTUSER SQUIDWRD ", + "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, + "runningUserid": "testuser" + } +} + + + [pyRACF:Debug] + Request Dictionary + UserAdmin.alter() + + +{ + "base": { + "base:name": { + "value": "Squidward", + "operation": null + }, + "base:owner": { + "value": "leonard", + "operation": null + }, + "base:special": { + "value": true, + "operation": null + }, + "base:password": { + "value": "********", + "operation": null + } + }, + "omvs": { + "omvs:uid": { + "value": "2424", + "operation": null + }, + "omvs:home_directory": { + "value": "/u/squidwrd", + "operation": null + }, + "omvs:default_shell": { + "value": "/bin/sh", + "operation": null + } + } +} + + + [pyRACF:Debug] + Request XML + UserAdmin.alter() + + + + + + Squidward + leonard + + ******** + + + 2424 + /u/squidwrd + /bin/sh + + + + + + [pyRACF:Debug] + Result XML + UserAdmin.alter() + + + + + + Definition exists. Add command skipped due to precheck option + + 0 + 0 + 0 + ALTUSER SQUIDWRD NAME ('Squidward') OWNER (leonard) SPECIAL PASSWORD (********) OMVS (UID (2424) HOME ('/u/squidwrd') PROGRAM ('/bin/sh')) + + + 0 + 0 + + +[ INFO ] Raw security result XML has been written to '/u/testuser/.pyracf/dump/pyracf...dump'. + + + [pyRACF:Debug] + Hex Dump + UserAdmin.alter() + + +00000000: 4c6f a794 9340 a585 99a2 8996 957e 7ff1 Definiti +000000c0: 9695 4085 a789 a2a3 a24b 40c1 8484 4083 on exists. Add c +000000d0: 9694 9481 9584 40a2 9289 9797 8584 4084 ommand skipped d +000000e0: a485 4040 a396 4097 9985 8388 8583 9240 ue to precheck +000000f0: 9697 a389 9695 4c61 8995 8696 6e4c 8396 option00 +00000140: 4c99 8581 a296 9583 9684 856e f04c 6199 0ALTUSER SQUIDWR +00000170: c440 4040 4040 d5c1 d4c5 4040 4040 4040 D NAME +00000180: 4040 4d7d e298 a489 84a6 8199 847d 5d40 ('Squidward') +00000190: d6e6 d5c5 d940 4040 4040 4040 4d93 8596 OWNER (leo +000001a0: 9581 9984 5d40 e2d7 c5c3 c9c1 d340 4040 nard) SPECIAL +000001b0: 4040 40d7 c1e2 e2e6 d6d9 c440 4040 404d PASSWORD ( +000001c0: 5c5c 5c5c 5c5c 5c5c 5d40 d6d4 e5e2 4040 ********) OMVS +000001d0: 4040 404d e4c9 c440 4040 4040 4040 4040 (UID +000001e0: 4df2 f4f2 f45d 40c8 d6d4 c540 4040 4040 (2424) HOME +000001f0: 4040 404d 7d61 a461 a298 a489 84a6 9984 ('/u/squidwrd +00000200: 7d5d 40d7 d9d6 c7d9 c1d4 4040 4040 404d ') PROGRAM ( +00000210: 7d61 8289 9561 a288 7d5d 5d4c 6189 9481 '/bin/sh')) +00000240: f04c 6199 85a3 a499 9583 9684 856e 4c99 00....... +00000280: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000290: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000002a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000002b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000002c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000002d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000002e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000002f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000300: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000310: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000320: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000330: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000340: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000350: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000360: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000370: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000380: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000390: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000003a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000003b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000003c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000003d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000003e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000003f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ + + + [pyRACF:Debug] + Result Dictionary + UserAdmin.alter() + + +{ + "securityResult": { + "user": { + "name": "SQUIDWRD", + "operation": "set", + "requestId": "UserRequest", + "info": [ + "Definition exists. Add command skipped due to precheck option" + ], + "commands": [ + { + "safReturnCode": 0, + "returnCode": 0, + "reasonCode": 0, + "image": "ALTUSER SQUIDWRD NAME ('Squidward') OWNER (leonard) SPECIAL PASSWORD (********) OMVS (UID (2424) HOME ('/u/squidwrd') PROGRAM ('/bin/sh'))" + } + ] + }, + "returnCode": 0, + "reasonCode": 0, + "runningUserid": "testuser" + } +} + diff --git a/tests/common/common_log_samples/extract_user_failure_dump_mode.log b/tests/common/common_log_samples/extract_user_failure_dump_mode.log new file mode 100644 index 00000000..0ae1644b --- /dev/null +++ b/tests/common/common_log_samples/extract_user_failure_dump_mode.log @@ -0,0 +1,195 @@ + + [pyRACF:Debug] + Request Dictionary + UserAdmin.extract() + + +{} + + + [pyRACF:Debug] + Request XML + UserAdmin.extract() + + + + + + + + [pyRACF:Debug] + Result XML + UserAdmin.extract() + + + + + + + 0 + 0 + 0 + LISTUSER SQUIDWRD + 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 + + + 0 + 0 + + +[ FATAL ] Unable to parse security result XML returned by IRRSMO00. +[ INFO ] Raw security result XML has been written to '/u/testuser/.pyracf/dump/pyracf...dump'. + + + [pyRACF:Debug] + Hex Dump + UserAdmin.extract() + + +00000000: 4c6f a794 9340 a585 99a2 8996 957e 7ff1 +000000c0: 4ca2 8186 9985 a3a4 9995 8396 8485 6ef0 0 +000000d0: 4c61 a281 8699 85a3 a499 9583 9684 856e +000000e0: 4c99 85a3 a499 9583 9684 856e f04c 6199 00LISTU +00000120: e2c5 d940 e2d8 e4c9 c4e6 d9c4 404c 6189 SER SQUIDWRD US +00000140: c5d9 7ee2 d8e4 c9c4 e6d9 c440 40d5 c1d4 ER=SQUIDWRD NAM +00000150: c57e e2d8 e4c9 c4e6 c1d9 c440 4040 4040 E=SQUIDWARD +00000160: 4040 4040 4040 4040 d6e6 d5c5 d97e d3c5 OWNER=LE +00000170: d6d5 c1d9 c440 4040 c3d9 c5c1 e3c5 c47e ONARD CREATED= +00000180: f2f3 4bf0 f9f4 4c61 9485 a2a2 8187 856e 23.094 +00000190: 4c94 85a2 a281 8785 6e40 c4c5 c6c1 e4d3 DEFAUL +000001a0: e360 c7d9 d6e4 d77e e2e8 e2f1 4040 4040 T-GROUP=SYS1 +000001b0: 40d7 c1e2 e2c4 c1e3 c57e f0f0 4bf0 f0f0 PASSDATE=00.000 +000001c0: 40d7 c1e2 e260 c9d5 e3c5 d9e5 c1d3 7ef1 PASS-INTERVAL=1 +000001d0: f8f6 40d7 c8d9 c1e2 c5c4 c1e3 c57e d561 86 PHRASEDATE=N/ +000001e0: c14c 6194 85a2 a281 8785 6e4c 9485 a2a2 A ATTRIBUTES= +00000200: d5d6 d5c5 4c61 9485 a2a2 8187 856e 4c94 NONE REVOKE D +00000220: c1e3 c57e d5d6 d5c5 4040 40d9 c5e2 e4d4 ATE=NONE RESUM +00000230: c540 c4c1 e3c5 7ed5 d6d5 c54c 6194 85a2 E DATE=NONE L +00000250: c1e2 e360 c1c3 c3c5 e2e2 7ef2 f34b f0f9 AST-ACCESS=23.09 +00000260: f461 f1f2 7af5 f57a f3f7 4c61 9485 a2a2 4/12:55:37 CL +00000280: c1e2 e240 c1e4 e3c8 d6d9 c9e9 c1e3 c9d6 ASS AUTHORIZATIO +00000290: d5e2 7ed5 d6d5 c54c 6194 85a2 a281 8785 NS=NONE NO-IN +000002b0: e2e3 c1d3 d3c1 e3c9 d6d5 60c4 c1e3 c14c STALLATION-DATA< +000002c0: 6194 85a2 a281 8785 6e4c 9485 a2a2 8187 /message> NO-MODEL-NAME +000002e0: 4c61 9485 a2a2 8187 856e 4c94 85a2 a281 LOGON ALLOWE +00000300: c440 4040 4dc4 c1e8 e25d 4040 4040 4040 D (DAYS) +00000310: 4040 4040 4de3 c9d4 c55d 4c61 9485 a2a2 (TIME) -- +00000330: 6060 6060 6060 6060 6060 6060 6060 6060 ---------------- +00000340: 6060 6060 6060 6060 6060 6060 6060 6060 ---------------- +00000350: 6060 6060 6060 6060 6060 604c 6194 85a2 ----------- A +00000370: d5e8 c4c1 e840 4040 4040 4040 4040 4040 NYDAY +00000380: 4040 4040 4040 4040 4040 4040 4040 40c1 A +00000390: d5e8 e3c9 d4c5 4c61 9485 a2a2 8187 856e NYTIME +000003a0: 4c94 85a2 a281 8785 6e40 40c7 d9d6 e4d7 GROUP +000003b0: 7ee2 e8e2 f140 4040 4040 40c1 e4e3 c87e =SYS1 AUTH= +000003c0: e4e2 c540 4040 4040 40c3 d6d5 d5c5 c3e3 USE CONNECT +000003d0: 60d6 e6d5 c5d9 7ed3 c5d6 d5c1 d9c4 4040 -OWNER=LEONARD +000003e0: 40c3 d6d5 d5c5 c3e3 60c4 c1e3 c57e f2f3 CONNECT-DATE=23 +000003f0: 4bf0 f9f4 4c61 9485 a2a2 8187 856e 4c94 .094 CONNE +00000410: c3e3 e27e 4040 4040 f0f0 4040 e4c1 c3c3 CTS= 00 UACC +00000420: 7ed5 d6d5 c540 4040 4040 d3c1 e2e3 60c3 =NONE LAST-C +00000430: d6d5 d5c5 c3e3 7ee4 d5d2 d5d6 e6d5 4c61 ONNECT=UNKNOWN CONNECT ATT +00000460: d9c9 c2e4 e3c5 e27e d5d6 d5c5 4c61 9485 RIBUTES=NONE +00000480: 4040 40d9 c5e5 d6d2 c540 c4c1 e3c5 7ed5 REVOKE DATE=N +00000490: d6d5 c540 4040 d9c5 e2e4 d4c5 40c4 c1e3 ONE RESUME DAT +000004a0: c57e d5d6 d5c5 4c61 9485 a2a2 8187 856e E=NONE +000004b0: 4c94 85a2 a281 8785 6ee2 c5c3 e4d9 c9e3 SECURIT +000004c0: e860 d3c5 e5c5 d37e d5d6 d5c5 40e2 d7c5 Y-LEVEL=NONE SPE +000004d0: c3c9 c6c9 c5c4 4c61 9485 a2a2 8187 856e CIFIED +000004e0: 4c94 85a2 a281 8785 6ec3 c1e3 c5c7 d6d9 CATEGOR +000004f0: e860 c1e4 e3c8 d6d9 c9e9 c1e3 c9d6 d54c Y-AUTHORIZATION< +00000500: 6194 85a2 a281 8785 6e4c 9485 a2a2 8187 /message> NONE SPECIFIE +00000520: c44c 6194 85a2 a281 8785 6e4c 9485 a2a2 DSECURITY-LAB +00000540: c5d3 7ed5 d6d5 c540 e2d7 c5c3 c9c6 c9c5 EL=NONE SPECIFIE +00000550: c44c 6194 85a2 a281 8785 6e4c 6183 9694 D00 +000005a0: 4c61 a285 83a4 9989 a3a8 9985 a2a4 0000 + + + + + [pyRACF:Debug] + Result XML + UserAdmin.extract() + + + + + + + 0 + 0 + 0 + LISTUSER SQUIDWRD + 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 + + + 0 + 0 + + +[ INFO ] Raw security result XML has been written to '/u/testuser/.pyracf/dump/pyracf...dump'. + + + [pyRACF:Debug] + Hex Dump + UserAdmin.extract() + + +00000000: 4c6f a794 9340 a585 99a2 8996 957e 7ff1 +000000c0: 4ca2 8186 9985 a3a4 9995 8396 8485 6ef0 0 +000000d0: 4c61 a281 8699 85a3 a499 9583 9684 856e +000000e0: 4c99 85a3 a499 9583 9684 856e f04c 6199 00LISTU +00000120: e2c5 d940 e2d8 e4c9 c4e6 d9c4 404c 6189 SER SQUIDWRD US +00000140: c5d9 7ee2 d8e4 c9c4 e6d9 c440 40d5 c1d4 ER=SQUIDWRD NAM +00000150: c57e e2d8 e4c9 c4e6 c1d9 c440 4040 4040 E=SQUIDWARD +00000160: 4040 4040 4040 4040 d6e6 d5c5 d97e d3c5 OWNER=LE +00000170: d6d5 c1d9 c440 4040 c3d9 c5c1 e3c5 c47e ONARD CREATED= +00000180: f2f3 4bf0 f9f4 4c61 9485 a2a2 8187 856e 23.094 +00000190: 4c94 85a2 a281 8785 6e40 c4c5 c6c1 e4d3 DEFAUL +000001a0: e360 c7d9 d6e4 d77e e2e8 e2f1 4040 4040 T-GROUP=SYS1 +000001b0: 40d7 c1e2 e2c4 c1e3 c57e f0f0 4bf0 f0f0 PASSDATE=00.000 +000001c0: 40d7 c1e2 e260 c9d5 e3c5 d9e5 c1d3 7ef1 PASS-INTERVAL=1 +000001d0: f8f6 40d7 c8d9 c1e2 c5c4 c1e3 c57e d561 86 PHRASEDATE=N/ +000001e0: c14c 6194 85a2 a281 8785 6e4c 9485 a2a2 A ATTRIBUTES= +00000200: d5d6 d5c5 4c61 9485 a2a2 8187 856e 4c94 NONE REVOKE D +00000220: c1e3 c57e d5d6 d5c5 4040 40d9 c5e2 e4d4 ATE=NONE RESUM +00000230: c540 c4c1 e3c5 7ed5 d6d5 c54c 6194 85a2 E DATE=NONE L +00000250: c1e2 e360 c1c3 c3c5 e2e2 7ef2 f34b f0f9 AST-ACCESS=23.09 +00000260: f461 f1f2 7af5 f57a f3f7 4c61 9485 a2a2 4/12:55:37 CL +00000280: c1e2 e240 c1e4 e3c8 d6d9 c9e9 c1e3 c9d6 ASS AUTHORIZATIO +00000290: d5e2 7ed5 d6d5 c54c 6194 85a2 a281 8785 NS=NONE NO-IN +000002b0: e2e3 c1d3 d3c1 e3c9 d6d5 60c4 c1e3 c14c STALLATION-DATA< +000002c0: 6194 85a2 a281 8785 6e4c 9485 a2a2 8187 /message> NO-MODEL-NAME +000002e0: 4c61 9485 a2a2 8187 856e 4c94 85a2 a281 LOGON ALLOWE +00000300: c440 4040 4dc4 c1e8 e25d 4040 4040 4040 D (DAYS) +00000310: 4040 4040 4de3 c9d4 c55d 4c61 9485 a2a2 (TIME) -- +00000330: 6060 6060 6060 6060 6060 6060 6060 6060 ---------------- +00000340: 6060 6060 6060 6060 6060 6060 6060 6060 ---------------- +00000350: 6060 6060 6060 6060 6060 604c 6194 85a2 ----------- A +00000370: d5e8 c4c1 e840 4040 4040 4040 4040 4040 NYDAY +00000380: 4040 4040 4040 4040 4040 4040 4040 40c1 A +00000390: d5e8 e3c9 d4c5 4c61 9485 a2a2 8187 856e NYTIME +000003a0: 4c94 85a2 a281 8785 6e40 40c7 d9d6 e4d7 GROUP +000003b0: 7ee2 e8e2 f140 4040 4040 40c1 e4e3 c87e =SYS1 AUTH= +000003c0: e4e2 c540 4040 4040 40c3 d6d5 d5c5 c3e3 USE CONNECT +000003d0: 60d6 e6d5 c5d9 7ed3 c5d6 d5c1 d9c4 4040 -OWNER=LEONARD +000003e0: 40c3 d6d5 d5c5 c3e3 60c4 c1e3 c57e f2f3 CONNECT-DATE=23 +000003f0: 4bf0 f9f4 4c61 9485 a2a2 8187 856e 4c94 .094 CONNE +00000410: c3e3 e27e 4040 4040 f0f0 4040 e4c1 c3c3 CTS= 00 UACC +00000420: 7ed5 d6d5 c540 4040 4040 d3c1 e2e3 60c3 =NONE LAST-C +00000430: d6d5 d5c5 c3e3 7ee4 d5d2 d5d6 e6d5 4c61 ONNECT=UNKNOWN CONNECT ATT +00000460: d9c9 c2e4 e3c5 e27e d5d6 d5c5 4c61 9485 RIBUTES=NONE +00000480: 4040 40d9 c5e5 d6d2 c540 c4c1 e3c5 7ed5 REVOKE DATE=N +00000490: d6d5 c540 4040 d9c5 e2e4 d4c5 40c4 c1e3 ONE RESUME DAT +000004a0: c57e d5d6 d5c5 4c61 9485 a2a2 8187 856e E=NONE +000004b0: 4c94 85a2 a281 8785 6ee2 c5c3 e4d9 c9e3 SECURIT +000004c0: e860 d3c5 e5c5 d37e d5d6 d5c5 40e2 d7c5 Y-LEVEL=NONE SPE +000004d0: c3c9 c6c9 c5c4 4c61 9485 a2a2 8187 856e CIFIED +000004e0: 4c94 85a2 a281 8785 6ec3 c1e3 c5c7 d6d9 CATEGOR +000004f0: e860 c1e4 e3c8 d6d9 c9e9 c1e3 c9d6 d54c Y-AUTHORIZATION< +00000500: 6194 85a2 a281 8785 6e4c 9485 a2a2 8187 /message> NONE SPECIFIE +00000520: c44c 6194 85a2 a281 8785 6e4c 9485 a2a2 DSECURITY-LAB +00000540: c5d3 7ed5 d6d5 c540 e2d7 c5c3 c9c6 c9c5 EL=NONE SPECIFIE +00000550: c44c 6194 85a2 a281 8785 6e4c 6183 9694 D00 +000005a0: 4c61 a285 83a4 9989 a3a8 9985 a2a4 93a3 ............... +000005c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000005d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000005e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000005f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000600: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000610: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000620: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000630: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000640: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000650: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000660: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000670: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000680: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000690: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000006a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000006b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000006c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000006d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000006e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000006f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000700: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000710: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000720: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000730: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000740: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000750: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000760: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000770: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000780: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000790: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000007a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000007b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000007c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000007d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000007e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000007f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ + + + [pyRACF:Debug] + Result Dictionary + UserAdmin.extract() + + +{ + "securityResult": { + "user": { + "name": "SQUIDWRD", + "operation": "listdata", + "requestId": "UserRequest", + "commands": [ + { + "safReturnCode": 0, + "returnCode": 0, + "reasonCode": 0, + "image": "LISTUSER SQUIDWRD ", + "messages": [ + "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" + ] + } + ] + }, + "returnCode": 0, + "reasonCode": 0, + "runningUserid": "testuser" + } +} + + + [pyRACF:Debug] + Result Dictionary (Formatted Profile) + UserAdmin.extract() + + +{ + "securityResult": { + "user": { + "name": "SQUIDWRD", + "operation": "listdata", + "requestId": "UserRequest", + "commands": [ + { + "safReturnCode": 0, + "returnCode": 0, + "reasonCode": 0, + "image": "LISTUSER SQUIDWRD ", + "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, + "runningUserid": "testuser" + } +} + diff --git a/tests/common/common_log_samples/alter_user_success.log b/tests/common/common_log_samples/extract_user_success_dump_mode_all_bytes.log similarity index 66% rename from tests/common/common_log_samples/alter_user_success.log rename to tests/common/common_log_samples/extract_user_success_dump_mode_all_bytes.log index b6319288..b8a861db 100644 --- a/tests/common/common_log_samples/alter_user_success.log +++ b/tests/common/common_log_samples/extract_user_success_dump_mode_all_bytes.log @@ -1,7 +1,7 @@ [pyRACF:Debug] Request Dictionary - UserAdmin.alter() + UserAdmin.extract() {} @@ -9,7 +9,7 @@ [pyRACF:Debug] Request XML - UserAdmin.alter() + UserAdmin.extract() @@ -19,7 +19,7 @@ [pyRACF:Debug] Result XML - UserAdmin.alter() + UserAdmin.extract() @@ -55,10 +55,51 @@ 0 +[ INFO ] Raw security result XML has been written to '/u/testuser/.pyracf/dump/pyracf...dump'. + + + [pyRACF:Debug] + Hex Dump + UserAdmin.extract() + + +00000000: 0001 0203 0405 0607 0809 0a0b 0c0d 0e0f ................ +00000010: 1011 1213 1415 1617 1819 1a1b 1c1d 1e1f ................ +00000020: 2021 2223 2425 2627 2829 2a2b 2c2d 2e2f ................ +00000030: 3031 3233 3435 3637 3839 3a3b 3c3d 3e3f ................ +00000040: 4041 4243 4445 4647 4849 4a4b 4c4d 4e4f .âäàáãåçñ¢.<(+| +00000050: 5051 5253 5455 5657 5859 5a5b 5c5d 5e5f &éêëèíîïìß!$*);^ +00000060: 6061 6263 6465 6667 6869 6a6b 6c6d 6e6f -/ÂÄÀÁÃÅÇѦ,%_>? +00000070: 7071 7273 7475 7677 7879 7a7b 7c7d 7e7f øÉÊËÈÍÎÏÌ`:#@'=" +00000080: 8081 8283 8485 8687 8889 8a8b 8c8d 8e8f Øabcdefghi«»ðýþ± +00000090: 9091 9293 9495 9697 9899 9a9b 9c9d 9e9f °jklmnopqrªºæ¸Æ¤ +000000a0: a0a1 a2a3 a4a5 a6a7 a8a9 aaab acad aeaf µ~stuvwxyz¡¿Ð[Þ® +000000b0: b0b1 b2b3 b4b5 b6b7 b8b9 babb bcbd bebf ¬£¥·©§¶¼½¾Ý¨¯]´× +000000c0: c0c1 c2c3 c4c5 c6c7 c8c9 cacb cccd cecf {ABCDEFGHI.ôöòóõ +000000d0: d0d1 d2d3 d4d5 d6d7 d8d9 dadb dcdd dedf }JKLMNOPQR¹ûüùúÿ +000000e0: e0e1 e2e3 e4e5 e6e7 e8e9 eaeb eced eeef .÷STUVWXYZ²ÔÖÒÓÕ +000000f0: f0f1 f2f3 f4f5 f6f7 f8f9 fafb fcfd feff 0123456789³ÛÜÙÚ. +00000100: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000110: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000120: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000130: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000140: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000150: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000160: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000170: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000180: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000190: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000001a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000001b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000001c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000001d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000001e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000001f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ + [pyRACF:Debug] Result Dictionary - UserAdmin.alter() + UserAdmin.extract() { @@ -106,7 +147,7 @@ [pyRACF:Debug] Result Dictionary (Formatted Profile) - UserAdmin.alter() + UserAdmin.extract() { @@ -167,97 +208,3 @@ } } - - [pyRACF:Debug] - Request Dictionary - UserAdmin.alter() - - -{ - "base": { - "base:special": { - "value": false, - "operation": "delete" - } - }, - "omvs": { - "omvs:home_directory": { - "value": "/u/clarinet", - "operation": null - }, - "omvs:default_shell": { - "value": false, - "operation": "delete" - } - } -} - - - [pyRACF:Debug] - Request XML - UserAdmin.alter() - - - - - - - - - /u/clarinet - - - - - - - [pyRACF:Debug] - Result XML - UserAdmin.alter() - - - - - - Definition exists. Add command skipped due to precheck option - - 0 - 0 - 0 - ALTUSER SQUIDWRD NOSPECIAL OMVS (HOME ('/u/clarinet') NOPROGRAM ) - - - 0 - 0 - - - - [pyRACF:Debug] - Result Dictionary - UserAdmin.alter() - - -{ - "securityResult": { - "user": { - "name": "SQUIDWRD", - "operation": "set", - "requestId": "UserRequest", - "info": [ - "Definition exists. Add command skipped due to precheck option" - ], - "commands": [ - { - "safReturnCode": 0, - "returnCode": 0, - "reasonCode": 0, - "image": "ALTUSER SQUIDWRD NOSPECIAL OMVS (HOME ('/u/clarinet') NOPROGRAM )" - } - ] - }, - "returnCode": 0, - "reasonCode": 0, - "runningUserid": "testuser" - } -} - diff --git a/tests/common/common_log_samples/extract_user_success_dump_mode_uneven_byte_boundary.log b/tests/common/common_log_samples/extract_user_success_dump_mode_uneven_byte_boundary.log new file mode 100644 index 00000000..c9a16676 --- /dev/null +++ b/tests/common/common_log_samples/extract_user_success_dump_mode_uneven_byte_boundary.log @@ -0,0 +1,304 @@ + + [pyRACF:Debug] + Request Dictionary + UserAdmin.extract() + + +{} + + + [pyRACF:Debug] + Request XML + UserAdmin.extract() + + + + + + + + [pyRACF:Debug] + Result XML + UserAdmin.extract() + + + + + + + 0 + 0 + 0 + LISTUSER SQUIDWRD + 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 + + + 0 + 0 + + +[ INFO ] Raw security result XML has been written to '/u/testuser/.pyracf/dump/pyracf...dump'. + + + [pyRACF:Debug] + Hex Dump + UserAdmin.extract() + + +00000000: 4c6f a794 9340 a585 99a2 8996 957e 7ff1 +000000c0: 4ca2 8186 9985 a3a4 9995 8396 8485 6ef0 0 +000000d0: 4c61 a281 8699 85a3 a499 9583 9684 856e +000000e0: 4c99 85a3 a499 9583 9684 856e f04c 6199 00LISTU +00000120: e2c5 d940 e2d8 e4c9 c4e6 d9c4 404c 6189 SER SQUIDWRD US +00000140: c5d9 7ee2 d8e4 c9c4 e6d9 c440 40d5 c1d4 ER=SQUIDWRD NAM +00000150: c57e e2d8 e4c9 c4e6 c1d9 c440 4040 4040 E=SQUIDWARD +00000160: 4040 4040 4040 4040 d6e6 d5c5 d97e d3c5 OWNER=LE +00000170: d6d5 c1d9 c440 4040 c3d9 c5c1 e3c5 c47e ONARD CREATED= +00000180: f2f3 4bf0 f9f4 4c61 9485 a2a2 8187 856e 23.094 +00000190: 4c94 85a2 a281 8785 6e40 c4c5 c6c1 e4d3 DEFAUL +000001a0: e360 c7d9 d6e4 d77e e2e8 e2f1 4040 4040 T-GROUP=SYS1 +000001b0: 40d7 c1e2 e2c4 c1e3 c57e f0f0 4bf0 f0f0 PASSDATE=00.000 +000001c0: 40d7 c1e2 e260 c9d5 e3c5 d9e5 c1d3 7ef1 PASS-INTERVAL=1 +000001d0: f8f6 40d7 c8d9 c1e2 c5c4 c1e3 c57e d561 86 PHRASEDATE=N/ +000001e0: c14c 6194 85a2 a281 8785 6e4c 9485 a2a2 A ATTRIBUTES= +00000200: d5d6 d5c5 4c61 9485 a2a2 8187 856e 4c94 NONE REVOKE D +00000220: c1e3 c57e d5d6 d5c5 4040 40d9 c5e2 e4d4 ATE=NONE RESUM +00000230: c540 c4c1 e3c5 7ed5 d6d5 c54c 6194 85a2 E DATE=NONE L +00000250: c1e2 e360 c1c3 c3c5 e2e2 7ef2 f34b f0f9 AST-ACCESS=23.09 +00000260: f461 f1f2 7af5 f57a f3f7 4c61 9485 a2a2 4/12:55:37 CL +00000280: c1e2 e240 c1e4 e3c8 d6d9 c9e9 c1e3 c9d6 ASS AUTHORIZATIO +00000290: d5e2 7ed5 d6d5 c54c 6194 85a2 a281 8785 NS=NONE NO-IN +000002b0: e2e3 c1d3 d3c1 e3c9 d6d5 60c4 c1e3 c14c STALLATION-DATA< +000002c0: 6194 85a2 a281 8785 6e4c 9485 a2a2 8187 /message> NO-MODEL-NAME +000002e0: 4c61 9485 a2a2 8187 856e 4c94 85a2 a281 LOGON ALLOWE +00000300: c440 4040 4dc4 c1e8 e25d 4040 4040 4040 D (DAYS) +00000310: 4040 4040 4de3 c9d4 c55d 4c61 9485 a2a2 (TIME) -- +00000330: 6060 6060 6060 6060 6060 6060 6060 6060 ---------------- +00000340: 6060 6060 6060 6060 6060 6060 6060 6060 ---------------- +00000350: 6060 6060 6060 6060 6060 604c 6194 85a2 ----------- A +00000370: d5e8 c4c1 e840 4040 4040 4040 4040 4040 NYDAY +00000380: 4040 4040 4040 4040 4040 4040 4040 40c1 A +00000390: d5e8 e3c9 d4c5 4c61 9485 a2a2 8187 856e NYTIME +000003a0: 4c94 85a2 a281 8785 6e40 40c7 d9d6 e4d7 GROUP +000003b0: 7ee2 e8e2 f140 4040 4040 40c1 e4e3 c87e =SYS1 AUTH= +000003c0: e4e2 c540 4040 4040 40c3 d6d5 d5c5 c3e3 USE CONNECT +000003d0: 60d6 e6d5 c5d9 7ed3 c5d6 d5c1 d9c4 4040 -OWNER=LEONARD +000003e0: 40c3 d6d5 d5c5 c3e3 60c4 c1e3 c57e f2f3 CONNECT-DATE=23 +000003f0: 4bf0 f9f4 4c61 9485 a2a2 8187 856e 4c94 .094 CONNE +00000410: c3e3 e27e 4040 4040 f0f0 4040 e4c1 c3c3 CTS= 00 UACC +00000420: 7ed5 d6d5 c540 4040 4040 d3c1 e2e3 60c3 =NONE LAST-C +00000430: d6d5 d5c5 c3e3 7ee4 d5d2 d5d6 e6d5 4c61 ONNECT=UNKNOWN CONNECT ATT +00000460: d9c9 c2e4 e3c5 e27e d5d6 d5c5 4c61 9485 RIBUTES=NONE +00000480: 4040 40d9 c5e5 d6d2 c540 c4c1 e3c5 7ed5 REVOKE DATE=N +00000490: d6d5 c540 4040 d9c5 e2e4 d4c5 40c4 c1e3 ONE RESUME DAT +000004a0: c57e d5d6 d5c5 4c61 9485 a2a2 8187 856e E=NONE +000004b0: 4c94 85a2 a281 8785 6ee2 c5c3 e4d9 c9e3 SECURIT +000004c0: e860 d3c5 e5c5 d37e d5d6 d5c5 40e2 d7c5 Y-LEVEL=NONE SPE +000004d0: c3c9 c6c9 c5c4 4c61 9485 a2a2 8187 856e CIFIED +000004e0: 4c94 85a2 a281 8785 6ec3 c1e3 c5c7 d6d9 CATEGOR +000004f0: e860 c1e4 e3c8 d6d9 c9e9 c1e3 c9d6 d54c Y-AUTHORIZATION< +00000500: 6194 85a2 a281 8785 6e4c 9485 a2a2 8187 /message> NONE SPECIFIE +00000520: c44c 6194 85a2 a281 8785 6e4c 9485 a2a2 DSECURITY-LAB +00000540: c5d3 7ed5 d6d5 c540 e2d7 c5c3 c9c6 c9c5 EL=NONE SPECIFIE +00000550: c44c 6194 85a2 a281 8785 6e4c 6183 9694 D00 +000005a0: 4c61 a285 83a4 9989 a3a8 9985 a2a4 93a3 ............... +000005c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000005d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000005e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000005f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000600: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000610: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000620: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000630: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000640: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000650: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000660: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000670: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000680: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000690: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000006a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000006b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000006c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000006d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000006e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000006f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000700: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000710: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000720: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000730: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000740: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000750: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000760: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000770: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000780: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +00000790: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000007a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000007b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000007c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ +000007d0: 0000 0000 0000 00 ....... + + + [pyRACF:Debug] + Result Dictionary + UserAdmin.extract() + + +{ + "securityResult": { + "user": { + "name": "SQUIDWRD", + "operation": "listdata", + "requestId": "UserRequest", + "commands": [ + { + "safReturnCode": 0, + "returnCode": 0, + "reasonCode": 0, + "image": "LISTUSER SQUIDWRD ", + "messages": [ + "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" + ] + } + ] + }, + "returnCode": 0, + "reasonCode": 0, + "runningUserid": "testuser" + } +} + + + [pyRACF:Debug] + Result Dictionary (Formatted Profile) + UserAdmin.extract() + + +{ + "securityResult": { + "user": { + "name": "SQUIDWRD", + "operation": "listdata", + "requestId": "UserRequest", + "commands": [ + { + "safReturnCode": 0, + "returnCode": 0, + "reasonCode": 0, + "image": "LISTUSER SQUIDWRD ", + "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, + "runningUserid": "testuser" + } +} + diff --git a/tests/common/common_request_samples/alter_user_request.xml b/tests/common/common_request_samples/alter_user_request.xml deleted file mode 100644 index 39340346..00000000 --- a/tests/common/common_request_samples/alter_user_request.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - /u/clarinet - - - - \ No newline at end of file diff --git a/tests/common/common_result_samples/alter_user_result_success.json b/tests/common/common_result_samples/alter_user_result_success.json deleted file mode 100644 index 290032c6..00000000 --- a/tests/common/common_result_samples/alter_user_result_success.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "securityResult": { - "user": { - "name": "SQUIDWRD", - "operation": "set", - "requestId": "UserRequest", - "info": [ - "Definition exists. Add command skipped due to precheck option" - ], - "commands": [ - { - "safReturnCode": 0, - "returnCode": 0, - "reasonCode": 0, - "image": "ALTUSER SQUIDWRD NOSPECIAL OMVS (HOME ('/u/clarinet') NOPROGRAM )" - } - ] - }, - "returnCode": 0, - "reasonCode": 0, - "runningUserid": "testuser" - } -} \ No newline at end of file diff --git a/tests/common/common_result_samples/alter_user_result_success.xml b/tests/common/common_result_samples/alter_user_result_success.xml deleted file mode 100644 index e8492834..00000000 --- a/tests/common/common_result_samples/alter_user_result_success.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - Definition exists. Add command skipped due to precheck option - - 0 - 0 - 0 - ALTUSER SQUIDWRD NOSPECIAL OMVS (HOME ('/u/clarinet') NOPROGRAM ) - - - 0 - 0 - \ No newline at end of file diff --git a/tests/common/common_result_samples/extract_user_result_base_only_success.json b/tests/common/common_result_samples/extract_user_result_base_only_success.json deleted file mode 100644 index 20ffb3b5..00000000 --- a/tests/common/common_result_samples/extract_user_result_base_only_success.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "securityResult": { - "user": { - "name": "SQUIDWRD", - "operation": "listdata", - "requestId": "UserRequest", - "commands": [ - { - "safReturnCode": 0, - "returnCode": 0, - "reasonCode": 0, - "image": "LISTUSER SQUIDWRD ", - "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, - "runningUserid": "testuser" - } -} \ No newline at end of file diff --git a/tests/common/common_result_samples/extract_user_result_base_only_success.xml b/tests/common/common_result_samples/extract_user_result_base_only_success.xml deleted file mode 100644 index 61a46535..00000000 --- a/tests/common/common_result_samples/extract_user_result_base_only_success.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - 0 - 0 - 0 - LISTUSER SQUIDWRD - 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 - - - 0 - 0 - \ No newline at end of file diff --git a/tests/common/test_class_attributes.py b/tests/common/test_class_attributes.py new file mode 100644 index 00000000..1a699978 --- /dev/null +++ b/tests/common/test_class_attributes.py @@ -0,0 +1,147 @@ +import unittest +from unittest.mock import Mock, call, patch + +from pyracf import ( + AccessAdmin, + ConnectionAdmin, + DataSetAdmin, + GroupAdmin, + ResourceAdmin, + SetroptsAdmin, + UserAdmin, +) +from pyracf.common.security_admin import SecurityAdmin + + +class TestClassAttributes(unittest.TestCase): + maxDiff = None + admin_types = [ + AccessAdmin, + ConnectionAdmin, + DataSetAdmin, + GroupAdmin, + ResourceAdmin, + SetroptsAdmin, + UserAdmin, + ] + + def test_all_admin_types_support_irrsmo00_result_buffer_size(self): + for admin_type in self.admin_types: + admin_object = admin_type(irrsmo00_result_buffer_size=32768) + self.assertEqual( + admin_object.__dict__["_SecurityAdmin__irrsmo00"].__dict__[ + "_IRRSMO00__result_buffer_size" + ], + 32768, + ) + admin_object = admin_type() + self.assertEqual( + admin_object.__dict__["_SecurityAdmin__irrsmo00"].__dict__[ + "_IRRSMO00__result_buffer_size" + ], + 16384, + ) + + def test_all_admin_types_raise_value_error_when_irrsmo00_result_buffer_size_is_no_good( + self, + ): + for admin_type in self.admin_types: + with self.assertRaises(ValueError) as exception: + admin_type(irrsmo00_result_buffer_size=9999) + self.assertEqual( + str(exception.exception), + "IRRSMO00 result buffer size must be an " + + "integer value greater than or equal to '10000'.", + ) + with self.assertRaises(ValueError) as exception: + admin_type(irrsmo00_result_buffer_size="Plankton") + self.assertEqual( + str(exception.exception), + "IRRSMO00 result buffer size must be an " + + "integer value greater than or equal to '10000'.", + ) + + @patch("pyracf.common.utilities.logger.Logger.log_warning") + def test_all_admin_type_log_a_warning_when_irrsmo00_result_buffer_size_is_very_large( + self, log_warning_mock: Mock + ): + for admin_type in self.admin_types: + admin_type(irrsmo00_result_buffer_size=100000001) + log_warning_mock.assert_has_calls( + [ + call( + "IRRSMO00 result buffer sizes greater than '100000000' may " + + "result in a 'SIGKILL' signal to be raised, which is NOT " + + "recoverable and will lead to the Python process that " + + "pyRACF is running under to be killed." + ) + ] + * len(self.admin_types) + ) + + def test_all_admin_types_support_debug(self): + for admin_type in self.admin_types: + admin_object = admin_type(debug=True) + self.assertEqual(admin_object.__dict__["_SecurityAdmin__debug"], True) + admin_object = admin_type() + self.assertEqual(admin_object.__dict__["_SecurityAdmin__debug"], False) + + def test_all_admin_types_support_dump_mode(self): + for admin_type in self.admin_types: + admin_object = admin_type(dump_mode=True) + self.assertEqual(admin_object.__dict__["_SecurityAdmin__dump_mode"], True) + admin_object = admin_type() + self.assertEqual(admin_object.__dict__["_SecurityAdmin__dump_mode"], False) + + def test_all_admin_types_support_generate_requests_only(self): + for admin_type in self.admin_types: + admin_object = admin_type(generate_requests_only=True) + self.assertEqual(admin_object.__dict__["_generate_requests_only"], True) + admin_object = admin_type() + self.assertEqual(admin_object.__dict__["_generate_requests_only"], False) + + @patch.object(SecurityAdmin, "_SecurityAdmin__update_valid_segment_traits") + def test_all_admin_types_support_update_existing_segment_traits( + self, + update_valid_segment_traits_mock: Mock, + ): + for admin_type in self.admin_types: + admin_type(update_existing_segment_traits={"base": {"base:gary": "gary"}}) + admin_type() + update_valid_segment_traits_mock.assert_has_calls( + [call({"base": {"base:gary": "gary"}})] * len(self.admin_types) + ) + + @patch.object(SecurityAdmin, "_SecurityAdmin__replace_valid_segment_traits") + def test_all_admin_types_support_replace_existing_segment_traits( + self, + replace_valid_segment_traits_mock: Mock, + ): + for admin_type in self.admin_types: + admin_type(replace_existing_segment_traits={"base": {"base:gary": "gary"}}) + admin_type() + replace_valid_segment_traits_mock.assert_has_calls( + [call({"base": {"base:gary": "gary"}})] * len(self.admin_types) + ) + + @patch.object(SecurityAdmin, "_SecurityAdmin__add_additional_secret_traits") + def test_all_admin_types_support_additional_secret_traits( + self, add_additional_secret_traits_mock: Mock + ): + for admin_type in self.admin_types: + admin_type(additional_secret_traits=["base:gary"]) + admin_type() + add_additional_secret_traits_mock.assert_has_calls( + [call(["base:gary"])] * len(self.admin_types) + ) + + def test_all_admin_types_support_run_as_userid(self): + for admin_type in self.admin_types: + admin_object = admin_type(run_as_userid="plankton") + self.assertEqual( + admin_object.__dict__["_SecurityAdmin__running_userid"], "PLANKTON" + ) + admin_object = admin_type() + self.assertEqual( + admin_object.__dict__["_SecurityAdmin__running_userid"], None + ) diff --git a/tests/common/test_common_constants.py b/tests/common/test_common_constants.py index d4dd266f..7a0c83ff 100644 --- a/tests/common/test_common_constants.py +++ b/tests/common/test_common_constants.py @@ -102,18 +102,7 @@ def get_sample(sample_file: str) -> Union[str, bytes]: # ============================================================================ TEST_RUNNING_USERID = "eswift" -TEST_ALTER_USER_REQUEST_TRAITS = { - "base:special": False, - "omvs:home_directory": "/u/clarinet", - "omvs:default_shell": False, -} -TEST_ALTER_USER_REQUEST_XML = get_sample("alter_user_request.xml") -TEST_EXTRACT_USER_RESULT_BASE_ONLY_SUCCESS_XML = get_sample( - "extract_user_result_base_only_success.xml" -) -TEST_ALTER_USER_SUCCESS_LOG = get_sample("alter_user_success.log") TEST_ALTER_USER_SUCCESS_AS_ESWIFT_LOG = get_sample("alter_user_success_as_eswift.log") -TEST_ALTER_USER_RESULT_SUCCESS_XML = get_sample("alter_user_result_success.xml") TEST_EXTRACT_RESOURCE_PRECHECK_AS_SQUIDWRD_LOG = get_sample( "extract_resource_precheck_as_squidwrd.log" ) @@ -146,3 +135,27 @@ def get_sample(sample_file: str) -> Union[str, bytes]: + "about this error.\n" + "https://www.ibm.com/docs/en/zos/3.1.0?topic=operations-return-reason-codes" ) + +# ============================================================================ +# Dump Processing +# ============================================================================ + +TEST_EXTRACT_USER_SUCCESS_DUMP_MODE_LOG = get_sample( + "extract_user_success_dump_mode.log" +) + +TEST_EXTRACT_USER_FAILURE_DUMP_MODE_LOG = get_sample( + "extract_user_failure_dump_mode.log" +) + +TEST_EXTRACT_USER_SUCCESS_UNEVEN_BYTE_BOUNDARY_LOG = get_sample( + "extract_user_success_dump_mode_uneven_byte_boundary.log" +) + +TEST_EXTRACT_USER_SUCCESS_DUMP_MODE_ALL_BYTES_LOG = get_sample( + "extract_user_success_dump_mode_all_bytes.log" +) + +TEST_ALTER_USER_PASSWORD_DUMP_MODE_LOG = get_sample( + "alter_user_password_success_dump_mode.log" +) diff --git a/tests/common/test_customize_segment_traits.py b/tests/common/test_customize_segment_traits.py index 80632c1c..c508d433 100644 --- a/tests/common/test_customize_segment_traits.py +++ b/tests/common/test_customize_segment_traits.py @@ -7,7 +7,6 @@ import tests.common.test_common_constants as TestCommonConstants from pyracf import UserAdmin -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -15,7 +14,6 @@ class TestCustomizeSegmentTraits(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) # ============================================================================ # Customize Segment Traits Request Generation diff --git a/tests/common/test_downstream_fatal_error.py b/tests/common/test_downstream_fatal_error.py index 247e0934..bf179f30 100644 --- a/tests/common/test_downstream_fatal_error.py +++ b/tests/common/test_downstream_fatal_error.py @@ -7,7 +7,6 @@ import tests.common.test_common_constants as TestCommonConstants from pyracf import DownstreamFatalError, UserAdmin -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -16,7 +15,6 @@ @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") class TestDownstreamFatalError(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) user_admin = UserAdmin() def test_donwstream_fatal_error_thrown_on_precheck_error( diff --git a/tests/common/test_dump_processing.py b/tests/common/test_dump_processing.py new file mode 100644 index 00000000..f119e208 --- /dev/null +++ b/tests/common/test_dump_processing.py @@ -0,0 +1,309 @@ +import contextlib +import io +import os +import platform +import re +import shutil +import unittest +import xml.etree.ElementTree +from unittest.mock import Mock, patch + +import __init__ +import ebcdic + +import tests.common.test_common_constants as TestCommonConstants +import tests.user.test_user_constants as TestUserConstants +from pyracf import UserAdmin +from pyracf.common.utilities.dumper import Dumper + +# Resolves F401 +__init__ +ebcdic + + +class TestDumpProcessing(unittest.TestCase): + maxDiff = None + ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])") + dumper = Dumper() + mock_home_directory = os.path.join(os.getcwd(), "common", "sandbox") + os.path.expanduser = Mock(return_value=mock_home_directory) + dot_pyracf_directory = os.path.join(mock_home_directory, ".pyracf") + dump_directory = os.path.join(dot_pyracf_directory, "dump") + timestamp = "20240108-131054" + dump_file_name = f"pyracf.{timestamp}.e2c865db4162bed963bfaa9ef6ac18f0.dump" + dump_file_path = os.path.join(dump_directory, dump_file_name) + generic_dump_file_path = "/u/testuser/.pyracf/dump/pyracf...dump" + dump_bytes = bytes([i for i in range(256)]) + path_separator = "/" + if platform.platform().split("-")[0] == "Windows": + path_separator = "\\" + + @classmethod + def setUpClass(self): + os.makedirs(self.mock_home_directory) + + @classmethod + def tearDownClass(self): + shutil.rmtree(self.mock_home_directory, ignore_errors=True) + + def tearDown(self): + shutil.rmtree(self.dot_pyracf_directory, ignore_errors=True) + + # ============================================================================ + # Directories and Dump File Creation + # ============================================================================ + @patch.object(Dumper, "_Dumper__get_timestamp") + def test_create_pyracf_folder_dump_folder_and_dump_file( + self, get_timestamp_mock: Mock + ): + get_timestamp_mock.return_value = self.timestamp + # Assume '.pyracf' directory does NOT exist. + dump_file_path_actual = self.dumper.raw_dump(self.dump_bytes) + # dump file path + self.assertEqual(dump_file_path_actual, self.dump_file_path) + # permission bits + if platform.platform().split("-")[0] != "Windows": + # Skip these tests on Windows because setting the + # permissions bits on things does not work on Windows. + self.assertEqual( + oct(os.stat(self.dot_pyracf_directory).st_mode)[-3:], "700" + ) + self.assertEqual(oct(os.stat(self.dump_directory).st_mode)[-3:], "700") + self.assertEqual(oct(os.stat(self.dump_file_path).st_mode)[-3:], "600") + # dump file contents + with open(self.dump_file_path, "rb") as dump_file: + self.assertEqual(dump_file.read(), self.dump_bytes) + + @patch.object(Dumper, "_Dumper__get_timestamp") + def test_create_dump_folder_and_dump_file(self, get_timestamp_mock: Mock): + get_timestamp_mock.return_value = self.timestamp + # Assume '.pyracf' directory already exists. + os.makedirs(self.dot_pyracf_directory, mode=0o700) + dump_file_path_actual = self.dumper.raw_dump(self.dump_bytes) + # dump file path + self.assertEqual(dump_file_path_actual, self.dump_file_path) + # permission bits + if platform.platform().split("-")[0] != "Windows": + # Skip these tests on Windows because setting the + # permissions bits on things does not work on Windows. + self.assertEqual( + oct(os.stat(self.dot_pyracf_directory).st_mode)[-3:], "700" + ) + self.assertEqual(oct(os.stat(self.dump_directory).st_mode)[-3:], "700") + self.assertEqual(oct(os.stat(self.dump_file_path).st_mode)[-3:], "600") + # dump file contents + with open(self.dump_file_path, "rb") as dump_file: + self.assertEqual(dump_file.read(), self.dump_bytes) + + @patch.object(Dumper, "_Dumper__get_timestamp") + def test_create_dump_file(self, get_timestamp_mock: Mock): + get_timestamp_mock.return_value = self.timestamp + # Assume '.pyracf' directory and 'dump' directory already exist. + os.makedirs(self.dump_directory, mode=0o700) + dump_file_path_actual = self.dumper.raw_dump(self.dump_bytes) + # dump file path + self.assertEqual(dump_file_path_actual, self.dump_file_path) + # permission bits + if platform.platform().split("-")[0] != "Windows": + # Skip these tests on Windows because setting the + # permissions bits on things does not work on Windows. + self.assertEqual( + oct(os.stat(self.dot_pyracf_directory).st_mode)[-3:], "700" + ) + self.assertEqual(oct(os.stat(self.dump_directory).st_mode)[-3:], "700") + self.assertEqual(oct(os.stat(self.dump_file_path).st_mode)[-3:], "600") + # dump file contents + with open(self.dump_file_path, "rb") as dump_file: + self.assertEqual(dump_file.read(), self.dump_bytes) + + @patch.object(Dumper, "_Dumper__get_timestamp") + def test_pyracf_and_dump_folder_permissions_and_create_dump_file( + self, + get_timestamp_mock: Mock, + ): + get_timestamp_mock.return_value = self.timestamp + # Assume '.pyracf' directory and 'dump' directory already exist. + # Assume '.pyracf' directory and 'dump' directory have the wrong permissions. + os.makedirs(self.dump_directory, mode=0o777) + dump_file_path_actual = self.dumper.raw_dump(self.dump_bytes) + # dump file path + self.assertEqual(dump_file_path_actual, self.dump_file_path) + # permission bits + if platform.platform().split("-")[0] != "Windows": + # Skip these tests on Windows because setting the + # permissions bits on things does not work on Windows. + self.assertEqual( + oct(os.stat(self.dot_pyracf_directory).st_mode)[-3:], "700" + ) + self.assertEqual(oct(os.stat(self.dump_directory).st_mode)[-3:], "700") + self.assertEqual(oct(os.stat(self.dump_file_path).st_mode)[-3:], "600") + # dump file contents + with open(self.dump_file_path, "rb") as dump_file: + self.assertEqual(dump_file.read(), self.dump_bytes) + + # ============================================================================ + # Debug Logging + # ============================================================================ + @patch("pyracf.common.irrsmo00.IRRSMO00.clear_raw_result_xml") + @patch("pyracf.common.utilities.dumper.Dumper.raw_dump") + @patch("pyracf.common.irrsmo00.IRRSMO00.get_raw_result_xml") + @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") + def test_debug_and_dump_mode( + self, + call_racf_mock: Mock, + get_raw_result_xml_mock: Mock, + raw_dump_mock: Mock, + clear_raw_result_xml_mock: Mock, + ): + user_admin = UserAdmin(debug=True, dump_mode=True) + call_racf_mock.return_value = ( + TestUserConstants.TEST_EXTRACT_USER_RESULT_BASE_ONLY_SUCCESS_XML + ) + get_raw_result_xml_mock.return_value = bytes( + TestUserConstants.TEST_EXTRACT_USER_RESULT_BASE_ONLY_SUCCESS_XML, + "cp1047", + ).ljust(2048, b"\00") + raw_dump_mock.return_value = self.generic_dump_file_path + stdout = io.StringIO() + with contextlib.redirect_stdout(stdout): + user_admin.extract("squidwrd") + success_log = self.ansi_escape.sub("", stdout.getvalue()) + self.assertEqual( + success_log, TestCommonConstants.TEST_EXTRACT_USER_SUCCESS_DUMP_MODE_LOG + ) + get_raw_result_xml_mock.assert_called_once() + raw_dump_mock.assert_called_once() + clear_raw_result_xml_mock.assert_called_once() + + @patch("pyracf.common.irrsmo00.IRRSMO00.clear_raw_result_xml") + @patch("pyracf.common.utilities.dumper.Dumper.raw_dump") + @patch("pyracf.common.irrsmo00.IRRSMO00.get_raw_result_xml") + @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") + def test_debug_and_dump_mode_uneven_byte_boundary( + self, + call_racf_mock: Mock, + get_raw_result_xml_mock: Mock, + raw_dump_mock: Mock, + clear_raw_result_xml_mock: Mock, + ): + user_admin = UserAdmin(debug=True, dump_mode=True) + call_racf_mock.return_value = ( + TestUserConstants.TEST_EXTRACT_USER_RESULT_BASE_ONLY_SUCCESS_XML + ) + get_raw_result_xml_mock.return_value = bytes( + TestUserConstants.TEST_EXTRACT_USER_RESULT_BASE_ONLY_SUCCESS_XML, + "cp1047", + ).ljust(2007, b"\00") + raw_dump_mock.return_value = self.generic_dump_file_path + stdout = io.StringIO() + with contextlib.redirect_stdout(stdout): + user_admin.extract("squidwrd") + success_log = self.ansi_escape.sub("", stdout.getvalue()) + self.assertEqual( + success_log, + TestCommonConstants.TEST_EXTRACT_USER_SUCCESS_UNEVEN_BYTE_BOUNDARY_LOG, + ) + get_raw_result_xml_mock.assert_called_once() + raw_dump_mock.assert_called_once() + clear_raw_result_xml_mock.assert_called_once() + + @patch("pyracf.common.irrsmo00.IRRSMO00.clear_raw_result_xml") + @patch("pyracf.common.utilities.dumper.Dumper.raw_dump") + @patch("pyracf.common.irrsmo00.IRRSMO00.get_raw_result_xml") + @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") + def test_debug_and_dump_mode_all_bytes( + self, + call_racf_mock: Mock, + get_raw_result_xml_mock: Mock, + raw_dump_mock: Mock, + clear_raw_ressult_xml_mock: Mock, + ): + user_admin = UserAdmin(debug=True, dump_mode=True) + call_racf_mock.return_value = ( + TestUserConstants.TEST_EXTRACT_USER_RESULT_BASE_ONLY_SUCCESS_XML + ) + get_raw_result_xml_mock.return_value = bytes( + bytearray([i for i in range(256)]) + ).ljust(512, b"\00") + raw_dump_mock.return_value = self.generic_dump_file_path + stdout = io.StringIO() + with contextlib.redirect_stdout(stdout): + user_admin.extract("squidwrd") + success_log = self.ansi_escape.sub("", stdout.getvalue()) + self.assertEqual( + success_log, + TestCommonConstants.TEST_EXTRACT_USER_SUCCESS_DUMP_MODE_ALL_BYTES_LOG, + ) + get_raw_result_xml_mock.assert_called_once() + raw_dump_mock.assert_called_once() + clear_raw_ressult_xml_mock.assert_called_once() + + @patch("pyracf.common.irrsmo00.IRRSMO00.clear_raw_result_xml") + @patch("pyracf.common.utilities.dumper.Dumper.raw_dump") + @patch("pyracf.common.irrsmo00.IRRSMO00.get_raw_result_xml") + @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") + def test_debug_and_dump_mode_secrets_redaction( + self, + call_racf_mock: Mock, + get_raw_result_xml_mock: Mock, + raw_dump_mock: Mock, + clear_raw_result_xml_mock: Mock, + ): + user_admin = UserAdmin(debug=True, dump_mode=True) + call_racf_mock.side_effect = [ + TestUserConstants.TEST_EXTRACT_USER_RESULT_BASE_ONLY_SUCCESS_XML, + TestUserConstants.TEST_ALTER_USER_PASSWORD_RESULT_SUCCESS_XML, + ] + get_raw_result_xml_mock.return_value = bytes( + TestUserConstants.TEST_ALTER_USER_PASSWORD_RESULT_SUCCESS_XML, + "cp1047", + ).ljust(1024, b"\00") + raw_dump_mock.return_value = self.generic_dump_file_path + stdout = io.StringIO() + with contextlib.redirect_stdout(stdout): + user_admin.alter( + "squidwrd", + traits=TestUserConstants.TEST_ALTER_USER_REQUEST_TRAITS_PASSWORD, + ) + success_log = self.ansi_escape.sub("", stdout.getvalue()) + self.assertEqual( + success_log, TestCommonConstants.TEST_ALTER_USER_PASSWORD_DUMP_MODE_LOG + ) + self.assertEqual(get_raw_result_xml_mock.call_count, 2) + self.assertEqual(raw_dump_mock.call_count, 2) + self.assertEqual(clear_raw_result_xml_mock.call_count, 2) + + @patch("pyracf.common.irrsmo00.IRRSMO00.clear_raw_result_xml") + @patch("pyracf.common.utilities.dumper.Dumper.raw_dump") + @patch("pyracf.common.irrsmo00.IRRSMO00.get_raw_result_xml") + @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") + def test_debug_and_dump_mode_xml_parsing_failure( + self, + call_racf_mock: Mock, + get_raw_result_xml_mock: Mock, + raw_dump_mock: Mock, + clear_raw_result_xml_mock: Mock, + ): + user_admin = UserAdmin(debug=True, dump_mode=True) + # Intentionally introduce XML syntax error. + call_racf_mock.return_value = ( + TestUserConstants.TEST_EXTRACT_USER_RESULT_BASE_ONLY_SUCCESS_XML[:-3] + ) + get_raw_result_xml_mock.return_value = bytes( + TestUserConstants.TEST_EXTRACT_USER_RESULT_BASE_ONLY_SUCCESS_XML[:-3], + "cp1047", + ).ljust(2048, b"\00") + raw_dump_mock.return_value = self.generic_dump_file_path + stdout = io.StringIO() + with contextlib.redirect_stdout(stdout): + try: + user_admin.extract("squidwrd") + except xml.etree.ElementTree.ParseError: + pass + error_log = self.ansi_escape.sub("", stdout.getvalue()) + self.assertEqual( + error_log, TestCommonConstants.TEST_EXTRACT_USER_FAILURE_DUMP_MODE_LOG + ) + get_raw_result_xml_mock.assert_called_once() + raw_dump_mock.assert_called_once() + clear_raw_result_xml_mock.assert_called_once() diff --git a/tests/common/test_irrsmo00_interface.py b/tests/common/test_irrsmo00_interface.py new file mode 100644 index 00000000..ccdd0393 --- /dev/null +++ b/tests/common/test_irrsmo00_interface.py @@ -0,0 +1,163 @@ +import unittest +from unittest.mock import Mock, patch + +import ebcdic + +from pyracf.common.irrsmo00 import IRRSMO00 + +# Resolves F401 +ebcdic +IRRSMO00 + + +# @patch.object(IRRSMO00, "_IRRSMO00__call_irrsmo00_wrapper") +@patch("pyracf.common.irrsmo00.call_irrsmo00") +class TestIRRSMO00Interface(unittest.TestCase): + maxDiff = None + irrsmo00 = IRRSMO00() + # deactivate black temporarily + # fmt: off + good_xml = bytes([ + 76, 163, 129, 135, 241, 110, 76, 163, 129, 135, + 242, 110, 210, 197, 232, 241, 64, 126, 64, 229, + 193, 211, 228, 197, 241, 76, 97, 163, 129, 135, + 242, 110, 76, 163, 129, 135, 243, 110, 210, 197, + 232, 242, 64, 126, 64, 64, 64, 64, 64, 76, + 97, 163, 129, 135, 243, 110, 76, 97, 163, 129, + 135, 241, 110, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + ]) + # fmt: on + good_xml_null_terminator_index = good_xml.find(b"\x00") + + # ============================================================================ + # Test IRRSMO00 Result XML Post Processing + # ============================================================================ + def test_irrsmo00_null_byte_fix( + self, + call_irrsmo00_wrapper_mock: Mock, + ): + # deactivate black temporarily + # fmt: off + xml_containing_null_bytes = bytes([ + 76, 163, 129, 135, 241, 110, 76, 163, 129, 135, + 242, 110, 210, 197, 232, 241, 64, 126, 64, 229, + 193, 211, 228, 197, 241, 76, 97, 163, 129, 135, + 242, 110, 76, 163, 129, 135, 243, 110, 210, 197, + 232, 242, 64, 126, 64, 0, 0, 0, 0, 76, + 97, 163, 129, 135, 243, 110, 76, 97, 163, 129, + 135, 241, 110, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + ]) + # fmt: on + call_irrsmo00_wrapper_mock.return_value = [xml_containing_null_bytes, 0, 0, 0] + self.assertEqual( + self.irrsmo00.call_racf(b""), + self.good_xml.decode("cp1047")[: self.good_xml_null_terminator_index], + ) + + def test_irrsmo00_empty_result( + self, + call_irrsmo00_wrapper_mock: Mock, + ): + # Simulate failure due to incomplete 'IRR.IRRSMO00.PRECHECK' setup + call_irrsmo00_wrapper_mock.return_value = [ + bytes([0 for i in range(256)]), + 8, + 200, + 16, + ] + self.assertEqual(self.irrsmo00.call_racf(b""), [8, 200, 16]) + + def test_irrsmo00_result_buffer_full_failure( + self, + call_irrsmo00_wrapper_mock: Mock, + ): + # Simulate scenario where result buffer is too small. + call_irrsmo00_wrapper_mock.return_value = [self.good_xml[:32], 8, 4000, 32] + self.assertEqual( + self.irrsmo00.call_racf(b""), self.good_xml.decode("cp1047")[:32] + ) + + def test_irrsmo00_result_buffer_full_success( + self, + call_irrsmo00_wrapper_mock: Mock, + ): + # Simulate scenario where result buffer is exactly the right size. + call_irrsmo00_wrapper_mock.return_value = [ + self.good_xml[: self.good_xml_null_terminator_index], + 0, + 0, + 0, + ] + self.assertEqual( + self.irrsmo00.call_racf(b""), + self.good_xml.decode("cp1047")[: self.good_xml_null_terminator_index], + ) + + def test_irrsmo00_normal_result(self, call_irrsmo00_wrapper_mock: Mock): + call_irrsmo00_wrapper_mock.return_value = [self.good_xml, 0, 0, 0] + self.assertEqual( + self.irrsmo00.call_racf(b""), + self.good_xml.decode("cp1047")[: self.good_xml_null_terminator_index], + ) + + # ============================================================================ + # Test IRRSMO00 Argument Construction + # ============================================================================ + def test_irrsmo00_minimum_arguments(self, call_irrsmo00_wrapper_mock: Mock): + call_irrsmo00_wrapper_mock.return_value = [self.good_xml, 0, 0, 0] + self.irrsmo00.call_racf(b"some bytes") + call_irrsmo00_wrapper_mock.assert_called_with( + request_xml=b"some bytes", + request_xml_length=10, + result_buffer_size=16384, + irrsmo00_options=13, + running_userid=b"", + running_userid_length=0, + ) + + def test_irrsmo00_with_precheck_set_to_true(self, call_irrsmo00_wrapper_mock: Mock): + call_irrsmo00_wrapper_mock.return_value = [self.good_xml, 0, 0, 0] + self.irrsmo00.call_racf(b"some bytes", precheck=True) + call_irrsmo00_wrapper_mock.assert_called_with( + request_xml=b"some bytes", + request_xml_length=10, + result_buffer_size=16384, + irrsmo00_options=15, + running_userid=b"", + running_userid_length=0, + ) + + def test_irrsmo00_with_run_as_userid_set(self, call_irrsmo00_wrapper_mock: Mock): + call_irrsmo00_wrapper_mock.return_value = [self.good_xml, 0, 0, 0] + self.irrsmo00.call_racf(b"some bytes", run_as_userid="KRABS") + call_irrsmo00_wrapper_mock.assert_called_with( + request_xml=b"some bytes", + request_xml_length=10, + result_buffer_size=16384, + irrsmo00_options=13, + running_userid=b"\xd2\xd9\xc1\xc2\xe2", + running_userid_length=5, + ) + + def test_irrsmo00_with_custom_result_buffer_size( + self, call_irrsmo00_wrapper_mock: Mock + ): + call_irrsmo00_wrapper_mock.return_value = [self.good_xml, 0, 0, 0] + irrsmo00 = IRRSMO00(result_buffer_size=32768) + irrsmo00.call_racf(b"some bytes") + call_irrsmo00_wrapper_mock.assert_called_with( + request_xml=b"some bytes", + request_xml_length=10, + result_buffer_size=32768, + irrsmo00_options=13, + running_userid=b"", + running_userid_length=0, + ) diff --git a/tests/common/test_logger.py b/tests/common/test_logger.py index 82e5cd0a..0d0838fd 100644 --- a/tests/common/test_logger.py +++ b/tests/common/test_logger.py @@ -4,7 +4,7 @@ import __init__ -from pyracf.common.logger import Logger +from pyracf.common.utilities.logger import Logger # Resolves F401 __init__ diff --git a/tests/common/test_run_as_userid.py b/tests/common/test_run_as_userid.py index 255c4667..af90d468 100644 --- a/tests/common/test_run_as_userid.py +++ b/tests/common/test_run_as_userid.py @@ -9,8 +9,8 @@ import __init__ import tests.common.test_common_constants as TestCommonConstants +import tests.user.test_user_constants as TestUserConstants from pyracf import ResourceAdmin, UserAdmin, UserIdError -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -18,7 +18,6 @@ class TestRunAsUserId(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])") @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") @@ -28,14 +27,14 @@ def test_set_run_as_userid_on_object_creation( ): user_admin = UserAdmin(debug=True, run_as_userid="ESWIFT") call_racf_mock.side_effect = [ - TestCommonConstants.TEST_EXTRACT_USER_RESULT_BASE_ONLY_SUCCESS_XML, - TestCommonConstants.TEST_ALTER_USER_RESULT_SUCCESS_XML, + TestUserConstants.TEST_EXTRACT_USER_RESULT_BASE_ONLY_SUCCESS_XML, + TestUserConstants.TEST_ALTER_USER_RESULT_SUCCESS_XML, ] stdout = io.StringIO() with contextlib.redirect_stdout(stdout): user_admin.alter( "squidwrd", - traits=TestCommonConstants.TEST_ALTER_USER_REQUEST_TRAITS, + traits=TestUserConstants.TEST_ALTER_USER_REQUEST_TRAITS, ) success_log = self.ansi_escape.sub("", stdout.getvalue()) self.assertEqual( @@ -60,14 +59,14 @@ def test_set_running_userid_after_object_creation( user_admin = UserAdmin(debug=True) user_admin.set_running_userid("ESWIFT") call_racf_mock.side_effect = [ - TestCommonConstants.TEST_EXTRACT_USER_RESULT_BASE_ONLY_SUCCESS_XML, - TestCommonConstants.TEST_ALTER_USER_RESULT_SUCCESS_XML, + TestUserConstants.TEST_EXTRACT_USER_RESULT_BASE_ONLY_SUCCESS_XML, + TestUserConstants.TEST_ALTER_USER_RESULT_SUCCESS_XML, ] stdout = io.StringIO() with contextlib.redirect_stdout(stdout): user_admin.alter( "squidwrd", - traits=TestCommonConstants.TEST_ALTER_USER_REQUEST_TRAITS, + traits=TestUserConstants.TEST_ALTER_USER_REQUEST_TRAITS, ) success_log = self.ansi_escape.sub("", stdout.getvalue()) self.assertEqual( @@ -95,17 +94,17 @@ def test_clear_running_userid( user_admin = UserAdmin(debug=True, run_as_userid="ESWIFT") user_admin.set_running_userid(userid=None) call_racf_mock.side_effect = [ - TestCommonConstants.TEST_EXTRACT_USER_RESULT_BASE_ONLY_SUCCESS_XML, - TestCommonConstants.TEST_ALTER_USER_RESULT_SUCCESS_XML, + TestUserConstants.TEST_EXTRACT_USER_RESULT_BASE_ONLY_SUCCESS_XML, + TestUserConstants.TEST_ALTER_USER_RESULT_SUCCESS_XML, ] stdout = io.StringIO() with contextlib.redirect_stdout(stdout): user_admin.alter( "squidwrd", - traits=TestCommonConstants.TEST_ALTER_USER_REQUEST_TRAITS, + traits=TestUserConstants.TEST_ALTER_USER_REQUEST_TRAITS, ) success_log = self.ansi_escape.sub("", stdout.getvalue()) - self.assertEqual(success_log, TestCommonConstants.TEST_ALTER_USER_SUCCESS_LOG) + self.assertEqual(success_log, TestUserConstants.TEST_ALTER_USER_SUCCESS_LOG) def test_get_running_userid(self): user_admin = UserAdmin(run_as_userid="ESWIFT") diff --git a/tests/common/test_setup_precheck.py b/tests/common/test_setup_precheck.py index b348adff..69ac67ed 100644 --- a/tests/common/test_setup_precheck.py +++ b/tests/common/test_setup_precheck.py @@ -10,7 +10,6 @@ import tests.common.test_common_constants as TestCommonConstants from pyracf import SecurityRequestError, setup_precheck -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -19,7 +18,6 @@ @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") class TestSetupPrecheck(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])") def test_setup_precheck_works_when_no_setup_done( diff --git a/tests/connection/test_connection_debug_logging.py b/tests/connection/test_connection_debug_logging.py index 7adabd20..ea9b80c1 100644 --- a/tests/connection/test_connection_debug_logging.py +++ b/tests/connection/test_connection_debug_logging.py @@ -10,7 +10,6 @@ import tests.connection.test_connection_constants as TestConnectionConstants from pyracf import ConnectionAdmin, SecurityRequestError -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -19,7 +18,6 @@ @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") class TestConnectionDebugLogging(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) connection_admin = ConnectionAdmin(debug=True) ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])") diff --git a/tests/connection/test_connection_request_builder.py b/tests/connection/test_connection_request_builder.py index 3667acd1..620bf1de 100644 --- a/tests/connection/test_connection_request_builder.py +++ b/tests/connection/test_connection_request_builder.py @@ -1,13 +1,11 @@ """Test connection request builder.""" import unittest -from unittest.mock import Mock import __init__ import tests.connection.test_connection_constants as TestConnectionConstants from pyracf import ConnectionAdmin -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -15,7 +13,6 @@ class TestConnectionRequestBuilder(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) connection_admin = ConnectionAdmin(generate_requests_only=True) def test_connection_admin_build_connect_connection_request(self): diff --git a/tests/connection/test_connection_result_parser.py b/tests/connection/test_connection_result_parser.py index dcd724a2..0df0e9f8 100644 --- a/tests/connection/test_connection_result_parser.py +++ b/tests/connection/test_connection_result_parser.py @@ -7,7 +7,6 @@ import tests.connection.test_connection_constants as TestConnectionConstants from pyracf import ConnectionAdmin, SecurityRequestError -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -16,7 +15,6 @@ @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") class TestConnectionResultParser(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) connection_admin = ConnectionAdmin() # ============================================================================ diff --git a/tests/connection/test_connection_setters.py b/tests/connection/test_connection_setters.py index d79607bc..37d91099 100644 --- a/tests/connection/test_connection_setters.py +++ b/tests/connection/test_connection_setters.py @@ -1,13 +1,11 @@ """Test connection setter functions.""" import unittest -from unittest.mock import Mock import __init__ import tests.connection.test_connection_constants as TestConnectionConstants from pyracf import ConnectionAdmin -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -15,7 +13,6 @@ class TestConnectionSetters(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) connection_admin = ConnectionAdmin(generate_requests_only=True) # ============================================================================ diff --git a/tests/data_set/test_data_set_debug_logging.py b/tests/data_set/test_data_set_debug_logging.py index 01a13452..8edd78d0 100644 --- a/tests/data_set/test_data_set_debug_logging.py +++ b/tests/data_set/test_data_set_debug_logging.py @@ -10,7 +10,6 @@ import tests.data_set.test_data_set_constants as TestDataSetConstants from pyracf import DataSetAdmin, SecurityRequestError -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -19,7 +18,6 @@ @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") class TestDataSetDebugLogging(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) data_set_admin = DataSetAdmin(debug=True) ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])") diff --git a/tests/data_set/test_data_set_getters.py b/tests/data_set/test_data_set_getters.py index 0437b283..63d3f4d1 100644 --- a/tests/data_set/test_data_set_getters.py +++ b/tests/data_set/test_data_set_getters.py @@ -7,7 +7,6 @@ import tests.data_set.test_data_set_constants as TestDataSetConstants from pyracf import DataSetAdmin, SecurityRequestError -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -16,7 +15,6 @@ @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") class TestDataSetGetters(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) data_set_admin = DataSetAdmin() # ============================================================================ diff --git a/tests/data_set/test_data_set_request_builder.py b/tests/data_set/test_data_set_request_builder.py index 5edf78c3..2b0050f1 100644 --- a/tests/data_set/test_data_set_request_builder.py +++ b/tests/data_set/test_data_set_request_builder.py @@ -1,13 +1,11 @@ """Test data set profile request builder.""" import unittest -from unittest.mock import Mock import __init__ import tests.data_set.test_data_set_constants as TestDataSetConstants from pyracf import DataSetAdmin -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -15,7 +13,6 @@ class TestDataSetRequestBuilder(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) data_set_admin = DataSetAdmin(generate_requests_only=True) def test_data_set_admin_build_add_data_set_request(self): diff --git a/tests/data_set/test_data_set_result_parser.py b/tests/data_set/test_data_set_result_parser.py index 5ab769bb..11e4f962 100644 --- a/tests/data_set/test_data_set_result_parser.py +++ b/tests/data_set/test_data_set_result_parser.py @@ -13,7 +13,6 @@ DataSetAdmin, SecurityRequestError, ) -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -22,7 +21,6 @@ @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") class TestDataSetResultParser(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) data_set_admin = DataSetAdmin() # ============================================================================ diff --git a/tests/data_set/test_data_set_setters.py b/tests/data_set/test_data_set_setters.py index fae70532..2ad385a2 100644 --- a/tests/data_set/test_data_set_setters.py +++ b/tests/data_set/test_data_set_setters.py @@ -1,13 +1,11 @@ """Test data set setter functions.""" import unittest -from unittest.mock import Mock import __init__ import tests.data_set.test_data_set_constants as TestDataSetConstants from pyracf import DataSetAdmin -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -15,7 +13,6 @@ class TestDataSetSetters(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) data_set_admin = DataSetAdmin(generate_requests_only=True) def test_data_set_admin_build_set_uacc_request(self): diff --git a/tests/function_test/function_test.py b/tests/function_test/function_test.py index 94660e3a..04a45759 100644 --- a/tests/function_test/function_test.py +++ b/tests/function_test/function_test.py @@ -11,9 +11,9 @@ def main(): json_profile = json.dumps(profile, indent=2) print(f"Profile extract for userid '{userid}' was successful:\n\n{json_profile}\n") - # The following testcase is designed to test the DownstreamFatalError's null response - # trigger by attempting to run as a userid for 'NOTAUSER' which is assumed to not - # exist as a valid userid in this environment. If 'NOTAUSER' is a user, the test + # The following testcase is designed to test the DownstreamFatalError's empty/null + # result trigger by attempting to run as a userid for 'NOTAUSER' which is assumed to + # not exist as a valid userid in this environment. If 'NOTAUSER' is a user, the test # will still function correctly as long as the tester does not have at least 'UPDATE' # access to 'NOTAUSER.IRRSMO00' in the 'SURROGAT' class. user_admin.set_running_userid("NOTAUSER") diff --git a/tests/group/test_group_debug_logging.py b/tests/group/test_group_debug_logging.py index d4d63265..0155cf63 100644 --- a/tests/group/test_group_debug_logging.py +++ b/tests/group/test_group_debug_logging.py @@ -10,7 +10,6 @@ import tests.group.test_group_constants as TestGroupConstants from pyracf import GroupAdmin, SecurityRequestError -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -19,7 +18,6 @@ @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") class TestGroupDebugLogging(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) group_admin = GroupAdmin(debug=True) ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])") diff --git a/tests/group/test_group_getters.py b/tests/group/test_group_getters.py index 25177201..e532c699 100644 --- a/tests/group/test_group_getters.py +++ b/tests/group/test_group_getters.py @@ -7,7 +7,6 @@ import tests.group.test_group_constants as TestGroupConstants from pyracf import GroupAdmin, SecurityRequestError -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -16,7 +15,6 @@ @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") class TestGroupGetters(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) group_admin = GroupAdmin() # ============================================================================ diff --git a/tests/group/test_group_request_builder.py b/tests/group/test_group_request_builder.py index ddd9ba86..ee9ee4c7 100644 --- a/tests/group/test_group_request_builder.py +++ b/tests/group/test_group_request_builder.py @@ -1,13 +1,11 @@ """Test group request builder.""" import unittest -from unittest.mock import Mock import __init__ import tests.group.test_group_constants as TestGroupConstants from pyracf import GroupAdmin -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -15,7 +13,6 @@ class TestGroupRequestBuilder(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) group_admin = GroupAdmin(generate_requests_only=True) def test_group_admin_build_add_group_request(self): diff --git a/tests/group/test_group_result_parser.py b/tests/group/test_group_result_parser.py index d86a1766..139e549a 100644 --- a/tests/group/test_group_result_parser.py +++ b/tests/group/test_group_result_parser.py @@ -14,7 +14,6 @@ GroupAdmin, SecurityRequestError, ) -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -23,7 +22,6 @@ @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") class TestGroupResultParser(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) group_admin = GroupAdmin() # ============================================================================ diff --git a/tests/group/test_group_setters.py b/tests/group/test_group_setters.py index c2f8905a..209e64e6 100644 --- a/tests/group/test_group_setters.py +++ b/tests/group/test_group_setters.py @@ -1,13 +1,11 @@ """Test group setter functions.""" import unittest -from unittest.mock import Mock import __init__ import tests.group.test_group_constants as TestGroupConstants from pyracf import GroupAdmin -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -15,7 +13,6 @@ class TestGroupSetters(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) group_admin = GroupAdmin(generate_requests_only=True) def test_group_admin_build_set_ovm_gid_request(self): diff --git a/tests/resource/test_resource_debug_logging.py b/tests/resource/test_resource_debug_logging.py index 8342649d..cc1fd487 100644 --- a/tests/resource/test_resource_debug_logging.py +++ b/tests/resource/test_resource_debug_logging.py @@ -10,7 +10,6 @@ import tests.resource.test_resource_constants as TestResourceConstants from pyracf import ResourceAdmin, SecurityRequestError -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -19,7 +18,6 @@ @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") class TestResourceDebugLogging(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) resource_admin = ResourceAdmin(debug=True) ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])") diff --git a/tests/resource/test_resource_getters.py b/tests/resource/test_resource_getters.py index e5fe54c9..204fa3ff 100644 --- a/tests/resource/test_resource_getters.py +++ b/tests/resource/test_resource_getters.py @@ -7,7 +7,6 @@ import tests.resource.test_resource_constants as TestResourceConstants from pyracf import ResourceAdmin, SecurityRequestError -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -16,7 +15,6 @@ @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") class TestResourceGetters(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) resource_admin = ResourceAdmin() # ============================================================================ diff --git a/tests/resource/test_resource_request_builder.py b/tests/resource/test_resource_request_builder.py index 7de2a103..ec4b5959 100644 --- a/tests/resource/test_resource_request_builder.py +++ b/tests/resource/test_resource_request_builder.py @@ -1,13 +1,11 @@ """Test general resource profile request builder.""" import unittest -from unittest.mock import Mock import __init__ import tests.resource.test_resource_constants as TestResourceConstants from pyracf import ResourceAdmin -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -15,7 +13,6 @@ class TestResourceRequestBuilder(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) resource_admin = ResourceAdmin(generate_requests_only=True) def test_resource_admin_build_add_resource_request(self): diff --git a/tests/resource/test_resource_result_parser.py b/tests/resource/test_resource_result_parser.py index 16aeb732..7f544c39 100644 --- a/tests/resource/test_resource_result_parser.py +++ b/tests/resource/test_resource_result_parser.py @@ -13,7 +13,6 @@ ResourceAdmin, SecurityRequestError, ) -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -22,7 +21,6 @@ @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") class TestResourceResultParser(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) resource_admin = ResourceAdmin() # ============================================================================ diff --git a/tests/resource/test_resource_setters.py b/tests/resource/test_resource_setters.py index 0bcbd0b9..557c38c6 100644 --- a/tests/resource/test_resource_setters.py +++ b/tests/resource/test_resource_setters.py @@ -7,7 +7,6 @@ import tests.resource.test_resource_constants as TestResourceConstants from pyracf import ResourceAdmin -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -15,7 +14,6 @@ class TestResourceSetters(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) resource_admin = ResourceAdmin(generate_requests_only=True) # ============================================================================ diff --git a/tests/resource/test_resource_subfunction_extracts.py b/tests/resource/test_resource_subfunction_extracts.py index 2f4787aa..8a81a337 100644 --- a/tests/resource/test_resource_subfunction_extracts.py +++ b/tests/resource/test_resource_subfunction_extracts.py @@ -7,7 +7,6 @@ import tests.resource.test_resource_constants as TestResourceConstants from pyracf import ResourceAdmin, SecurityRequestError -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -16,7 +15,6 @@ @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") class TestResourceSubfunctionExtracts(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) resource_admin = ResourceAdmin() # ============================================================================ diff --git a/tests/resource/test_resource_subfunction_requests.py b/tests/resource/test_resource_subfunction_requests.py index e99d80a7..0c1c2493 100644 --- a/tests/resource/test_resource_subfunction_requests.py +++ b/tests/resource/test_resource_subfunction_requests.py @@ -1,13 +1,11 @@ """Test general resource profile setter functions.""" import unittest -from unittest.mock import Mock import __init__ import tests.resource.test_resource_constants as TestResourceConstants from pyracf import ResourceAdmin -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -15,7 +13,6 @@ class TestResourceSubfunctionRequests(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) resource_admin = ResourceAdmin(generate_requests_only=True) # ============================================================================ diff --git a/tests/setropts/setropts_log_samples/list_setropts_success.log b/tests/setropts/setropts_log_samples/list_setropts_success.log index ad81e599..10f08c63 100644 --- a/tests/setropts/setropts_log_samples/list_setropts_success.log +++ b/tests/setropts/setropts_log_samples/list_setropts_success.log @@ -1,3 +1,4 @@ +[ WARN ] 'List RACF Options' is an experimental feature. This feature is subject to major changes and even being removed entirely. [pyRACF:Debug] Request Dictionary diff --git a/tests/setropts/setropts_log_samples/list_setropts_success_2.log b/tests/setropts/setropts_log_samples/list_setropts_success_2.log index 50dd9443..ef33a1ea 100644 --- a/tests/setropts/setropts_log_samples/list_setropts_success_2.log +++ b/tests/setropts/setropts_log_samples/list_setropts_success_2.log @@ -1,3 +1,4 @@ +[ WARN ] 'List RACF Options' is an experimental feature. This feature is subject to major changes and even being removed entirely. [pyRACF:Debug] Request Dictionary diff --git a/tests/setropts/setropts_log_samples/list_setropts_success_3.log b/tests/setropts/setropts_log_samples/list_setropts_success_3.log index 7888880f..58a52c2d 100644 --- a/tests/setropts/setropts_log_samples/list_setropts_success_3.log +++ b/tests/setropts/setropts_log_samples/list_setropts_success_3.log @@ -1,3 +1,4 @@ +[ WARN ] 'List RACF Options' is an experimental feature. This feature is subject to major changes and even being removed entirely. [pyRACF:Debug] Request Dictionary diff --git a/tests/setropts/test_setropts_debug_logging.py b/tests/setropts/test_setropts_debug_logging.py index b6876dd2..ac479a66 100644 --- a/tests/setropts/test_setropts_debug_logging.py +++ b/tests/setropts/test_setropts_debug_logging.py @@ -10,7 +10,6 @@ import tests.setropts.test_setropts_constants as TestSetroptsConstants from pyracf import SecurityRequestError, SetroptsAdmin -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -19,7 +18,6 @@ @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") class TestSetroptsDebugLogging(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) setropts_admin = SetroptsAdmin(debug=True) ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])") diff --git a/tests/setropts/test_setropts_getters.py b/tests/setropts/test_setropts_getters.py index 2f52675d..89cf4f66 100644 --- a/tests/setropts/test_setropts_getters.py +++ b/tests/setropts/test_setropts_getters.py @@ -7,7 +7,6 @@ import tests.setropts.test_setropts_constants as TestSetroptsConstants from pyracf import SecurityRequestError -from pyracf.common.irrsmo00 import IRRSMO00 from pyracf.setropts.setropts_admin import SetroptsAdmin # Resolves F401 @@ -17,7 +16,6 @@ @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") class TestSetroptsGetters(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) setropts_admin = SetroptsAdmin() # ============================================================================ diff --git a/tests/setropts/test_setropts_request_builder.py b/tests/setropts/test_setropts_request_builder.py index de51b00c..7ce6da95 100644 --- a/tests/setropts/test_setropts_request_builder.py +++ b/tests/setropts/test_setropts_request_builder.py @@ -1,13 +1,11 @@ """Test setropts request builder.""" import unittest -from unittest.mock import Mock import __init__ import tests.setropts.test_setropts_constants as TestSetroptsConstants from pyracf import SetroptsAdmin -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -15,7 +13,6 @@ class TestSetroptsRequestBuilder(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) setropts_admin = SetroptsAdmin(generate_requests_only=True) def test_setropts_admin_build_alter_setropts_request(self): diff --git a/tests/setropts/test_setropts_result_parser.py b/tests/setropts/test_setropts_result_parser.py index 3ebfe7bb..02e03a58 100644 --- a/tests/setropts/test_setropts_result_parser.py +++ b/tests/setropts/test_setropts_result_parser.py @@ -7,7 +7,6 @@ import tests.setropts.test_setropts_constants as TestSetroptsConstants from pyracf import SecurityRequestError -from pyracf.common.irrsmo00 import IRRSMO00 from pyracf.setropts.setropts_admin import SetroptsAdmin # Resolves F401 @@ -17,7 +16,6 @@ @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") class TestSetroptsResultParser(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) setropts_admin = SetroptsAdmin() # ============================================================================ diff --git a/tests/setropts/test_setropts_setters.py b/tests/setropts/test_setropts_setters.py index 773c9b08..a8fcc654 100644 --- a/tests/setropts/test_setropts_setters.py +++ b/tests/setropts/test_setropts_setters.py @@ -1,13 +1,11 @@ """Test setropts setter functions.""" import unittest -from unittest.mock import Mock import __init__ import tests.setropts.test_setropts_constants as TestSetroptsConstants from pyracf import SetroptsAdmin -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -15,7 +13,6 @@ class TestSetroptsSetters(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) setropts_admin = SetroptsAdmin(generate_requests_only=True) # ============================================================================ diff --git a/tests/test_runner.py b/tests/test_runner.py index 214e1a6c..44a4f573 100644 --- a/tests/test_runner.py +++ b/tests/test_runner.py @@ -10,8 +10,11 @@ from tests.access.test_access_debug_logging import TestAccessDebugLogging from tests.access.test_access_request_builder import TestAccessRequestBuilder from tests.access.test_access_result_parser import TestAccessResultParser +from tests.common.test_class_attributes import TestClassAttributes from tests.common.test_customize_segment_traits import TestCustomizeSegmentTraits from tests.common.test_downstream_fatal_error import TestDownstreamFatalError +from tests.common.test_dump_processing import TestDumpProcessing +from tests.common.test_irrsmo00_interface import TestIRRSMO00Interface from tests.common.test_logger import TestLogger from tests.common.test_run_as_userid import TestRunAsUserId from tests.common.test_setup_precheck import TestSetupPrecheck @@ -66,8 +69,11 @@ def __test_suite() -> unittest.TestSuite: TestAccessResultParser, TestAccessRequestBuilder, TestAccessDebugLogging, + TestClassAttributes, TestCustomizeSegmentTraits, TestSetupPrecheck, + TestDumpProcessing, + TestIRRSMO00Interface, TestLogger, TestDownstreamFatalError, TestRunAsUserId, diff --git a/tests/user/test_user_constants.py b/tests/user/test_user_constants.py index 43870137..004e7650 100644 --- a/tests/user/test_user_constants.py +++ b/tests/user/test_user_constants.py @@ -213,9 +213,9 @@ def get_sample(sample_file: str) -> Union[str, bytes]: TEST_ALTER_USER_REQUEST_TRAITS_EXTENDED ) TEST_ALTER_USER_REQUEST_TRAITS_PASSPHRASE_AND_PASSWORD["base:password"] = "GIyTTqdF" -TEST_ALTER_USER_REQUEST_TRAITS_PASSPHRASE_AND_PASSWORD[ - "base:passphrase" -] = "PassPhrasesAreCool!" +TEST_ALTER_USER_REQUEST_TRAITS_PASSPHRASE_AND_PASSWORD["base:passphrase"] = ( + "PassPhrasesAreCool!" +) TEST_ALTER_USER_REQUEST_TRAITS_UID_ERROR = dict(TEST_ALTER_USER_REQUEST_TRAITS_EXTENDED) TEST_ALTER_USER_REQUEST_TRAITS_UID_ERROR["omvs:uid"] = 90000000000 diff --git a/tests/user/test_user_debug_logging.py b/tests/user/test_user_debug_logging.py index c3b27d08..efb6f64e 100644 --- a/tests/user/test_user_debug_logging.py +++ b/tests/user/test_user_debug_logging.py @@ -10,7 +10,6 @@ import tests.user.test_user_constants as TestUserConstants from pyracf import SecurityRequestError, UserAdmin -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -19,7 +18,6 @@ @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") class TestUserDebugLogging(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) user_admin = UserAdmin(debug=True) ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])") test_password = "GIyTTqdF" diff --git a/tests/user/test_user_getters.py b/tests/user/test_user_getters.py index f184cb11..f0ce2921 100644 --- a/tests/user/test_user_getters.py +++ b/tests/user/test_user_getters.py @@ -7,7 +7,6 @@ import tests.user.test_user_constants as TestUserConstants from pyracf import SecurityRequestError, UserAdmin -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -16,7 +15,6 @@ @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") class TestUserGetters(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) user_admin = UserAdmin() # ============================================================================ diff --git a/tests/user/test_user_request_builder.py b/tests/user/test_user_request_builder.py index 5c75edf5..a480f1cb 100644 --- a/tests/user/test_user_request_builder.py +++ b/tests/user/test_user_request_builder.py @@ -1,13 +1,11 @@ """Test user request builder.""" import unittest -from unittest.mock import Mock import __init__ import tests.user.test_user_constants as TestUserConstants from pyracf import SegmentError, SegmentTraitError, UserAdmin -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -15,7 +13,6 @@ class TestUserRequestBuilder(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) user_admin = UserAdmin(generate_requests_only=True) test_password = "GIyTTqdF" test_passphrase = "PassPhrasesAreCool!" diff --git a/tests/user/test_user_result_parser.py b/tests/user/test_user_result_parser.py index 4111ce75..ad458f23 100644 --- a/tests/user/test_user_result_parser.py +++ b/tests/user/test_user_result_parser.py @@ -14,7 +14,6 @@ SecurityRequestError, UserAdmin, ) -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -23,7 +22,6 @@ @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") class TestUserResultParser(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) user_admin = UserAdmin() test_password = "GIyTTqdF" test_passphrase = "PassPhrasesAreCool!" diff --git a/tests/user/test_user_setters.py b/tests/user/test_user_setters.py index ac6714fc..d4a6fe77 100644 --- a/tests/user/test_user_setters.py +++ b/tests/user/test_user_setters.py @@ -7,7 +7,6 @@ import tests.user.test_user_constants as TestUserConstants from pyracf import UserAdmin -from pyracf.common.irrsmo00 import IRRSMO00 # Resolves F401 __init__ @@ -15,7 +14,6 @@ class TestUserSetters(unittest.TestCase): maxDiff = None - IRRSMO00.__init__ = Mock(return_value=None) user_admin = UserAdmin(generate_requests_only=True) # ============================================================================