diff --git a/pyproject.toml b/pyproject.toml index 47ea1aa3..17bc8f0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ [tool.poetry] name="pyracf" - version="1.0b2" + version="1.0b3" description="Python interface to RACF using IRRSMO00 RACF Callable Service." license = "Apache-2.0" authors = [ diff --git a/pyracf/__init__.py b/pyracf/__init__.py index 9254e04e..e99b15f6 100644 --- a/pyracf/__init__.py +++ b/pyracf/__init__.py @@ -2,12 +2,15 @@ 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 .connection.connection_admin import ConnectionAdmin from .data_set.data_set_admin import DataSetAdmin from .group.group_admin import GroupAdmin from .resource.resource_admin import ResourceAdmin +from .scripts.setup_precheck import setup_precheck from .setropts.setropts_admin import SetroptsAdmin from .user.user_admin import UserAdmin diff --git a/pyracf/access/access_admin.py b/pyracf/access/access_admin.py index 46a13e4e..32796da8 100644 --- a/pyracf/access/access_admin.py +++ b/pyracf/access/access_admin.py @@ -17,6 +17,7 @@ def __init__( 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._valid_segment_traits = { "base": { @@ -48,6 +49,7 @@ def __init__( 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, ) # ============================================================================ diff --git a/pyracf/common/downstream_fatal_error.py b/pyracf/common/downstream_fatal_error.py new file mode 100644 index 00000000..69610993 --- /dev/null +++ b/pyracf/common/downstream_fatal_error.py @@ -0,0 +1,69 @@ +"""Exception to use when IRRSMO00 is unable to process a request.""" +from typing import Union + + +class DownstreamFatalError(Exception): + """ + Raised when IRRSMO00 returns with a SAF Return Code of 8, + indicating that the request could not be processed. + """ + + def __init__( + self, + saf_return_code: int, + racf_return_code: int, + racf_reason_code: int, + request_xml: bytes, + run_as_userid: Union[str, None] = None, + result_dictionary: dict = None, + ) -> None: + self.message = "Security request made to IRRSMO00 failed." + self.saf_return_code = saf_return_code + self.racf_return_code = racf_return_code + self.racf_reason_code = racf_reason_code + self.request_xml = request_xml.decode("utf-8") + self.message += ( + f"\n\nSAF Return Code: {self.saf_return_code}\nRACF Return Code:" + + f" {self.racf_return_code}\nRACF Reason Code: {self.racf_reason_code}" + ) + if result_dictionary is not None: + self.message += ( + "\n\nSee results dictionary " + + f"'{self.__class__.__name__}.result' for more details.\n" + + "\n\nYou can also check the specified return and reason codes against " + + "the documented IRRSMO00 return and reason codes for more information " + + "about this error.\n" + + "https://www.ibm.com/docs/en/zos/3.1.0?topic=operations-return-reason-codes" + ) + self.result = result_dictionary + elif ( + (self.saf_return_code == 8) + and (self.racf_return_code == 200) + and (self.racf_reason_code == 16) + ): + self.message += ( + "\n\nCheck to see if the proper RACF permissions are in place.\n" + + "For 'set' or 'alter' functions, you must have at least 'READ' " + + "access to 'IRR.IRRSMO00.PRECHECK' in the 'XFACILIT' class." + ) + elif ( + (self.saf_return_code == 8) + and (self.racf_return_code == 200) + and (self.racf_reason_code == 8) + ): + self.message += ( + "\n\nCheck to see if the proper RACF permissions are in place.\n" + + "For the 'run_as_userid' feature, you must have at least 'UPDATE' " + + f"access to '{run_as_userid.upper()}.IRRSMO00' in the 'SURROGAT' class." + ) + else: + self.message += ( + "\n\nPlease check the specified return and reason codes against " + + "the documented IRRSMO00 return and reason codes for more information " + + "about this error.\n" + + "https://www.ibm.com/docs/en/zos/3.1.0?topic=operations-return-reason-codes" + ) + self.message = f"({self.__class__.__name__}) {self.message}" + + def __str__(self) -> str: + return self.message diff --git a/pyracf/common/irrsmo00.c b/pyracf/common/irrsmo00.c index 8ebdc8d4..774fc254 100644 --- a/pyracf/common/irrsmo00.c +++ b/pyracf/common/irrsmo00.c @@ -3,6 +3,7 @@ #include #include #include +#include #define BUFFER_SIZE (100000) @@ -33,18 +34,20 @@ void null_byte_fix(char* str, unsigned int str_len) { 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", NULL}; + static char *kwlist[] = {"xml_str", "xml_len", "opts", "userid", "userid_len", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "y|II", kwlist, &input_xml, &xml_len, &input_opts)) { + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "y|IIyb", kwlist, &input_xml, &xml_len, &input_opts, &input_userid, &input_userid_len)) { return NULL; } char work_area[1024]; char req_handle[64] = { 0 }; - VarStr_T userid = { 0, {0}}; + VarStr_T userid = { input_userid_len, {0}}; unsigned int alet = 0; unsigned int acee = 0; unsigned char rsp[BUFFER_SIZE+1]; @@ -52,14 +55,16 @@ static PyObject* call_irrsmo00(PyObject* self, PyObject* args, PyObject *kwargs) 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); + IRRSMO64( work_area, alet, - saf_rc, + &saf_rc, alet, - racf_rc, + &racf_rc, alet, - racf_rsn, + &racf_rsn, num_parms, fn, opts, @@ -73,11 +78,12 @@ static PyObject* call_irrsmo00(PyObject* self, PyObject* args, PyObject *kwargs) ); null_byte_fix(rsp,rsp_len); - return Py_BuildValue("y", rsp); + + return Py_BuildValue("yBBB", rsp, 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 from the IRRSMO00 RACF Callable Service.\n"; + "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"; static PyMethodDef cpyracf_methods[] = { {"call_irrsmo00", (PyCFunction)call_irrsmo00, diff --git a/pyracf/common/irrsmo00.py b/pyracf/common/irrsmo00.py index d8a503d6..1c328841 100644 --- a/pyracf/common/irrsmo00.py +++ b/pyracf/common/irrsmo00.py @@ -1,5 +1,6 @@ """Interface to irrsmo00.dll.""" import platform +from typing import Union try: from cpyracf import call_irrsmo00 @@ -19,9 +20,26 @@ def __init__(self) -> None: # Initialize size of output buffer self.buffer_size = 100000 - def call_racf(self, request_xml: bytes, precheck: bool = False) -> str: + def call_racf( + self, + request_xml: bytes, + precheck: bool = False, + run_as_userid: Union[str, None] = None, + ) -> str: """Make request to call_irrsmo00 in the cpyracf Python extension.""" options = 15 if precheck else 13 - return call_irrsmo00( - xml_str=request_xml, xml_len=len(request_xml), opts=options - ).decode("cp1047") + userid = b"" + userid_length = 0 + 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, + ) + if response[0] == b"": + return list(response[1:4]) + return response[0].decode("cp1047") diff --git a/pyracf/common/logger.py b/pyracf/common/logger.py index 805a6222..d4b42eb0 100644 --- a/pyracf/common/logger.py +++ b/pyracf/common/logger.py @@ -4,7 +4,7 @@ import json import os import re -from typing import Union +from typing import List, Union class Logger: @@ -161,7 +161,7 @@ def redact_request_xml( def redact_result_xml( self, - xml_string: str, + security_response: Union[str, List[int]], secret_traits: dict, ) -> str: """ @@ -170,13 +170,17 @@ 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 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()} +\(", xml_string) + match = re.search(rf"{racf_key.upper()} +\(", security_response) if not match: continue - xml_string = self.__redact_string(xml_string, match.end(), ")") - return xml_string + security_response = self.__redact_string( + security_response, match.end(), ")" + ) + return security_response def __colorize_json(self, json_text: str) -> str: updated_json_text = "" diff --git a/pyracf/common/security_admin.py b/pyracf/common/security_admin.py index b1793e25..6a87e5d8 100644 --- a/pyracf/common/security_admin.py +++ b/pyracf/common/security_admin.py @@ -5,6 +5,7 @@ from datetime import datetime from typing import Any, List, Tuple, Union +from .downstream_fatal_error import DownstreamFatalError from .irrsmo00 import IRRSMO00 from .logger import Logger from .security_request import SecurityRequest @@ -12,6 +13,7 @@ from .security_result import SecurityResult from .segment_error import SegmentError from .segment_trait_error import SegmentTraitError +from .userid_error import UserIdError class SecurityAdmin: @@ -20,6 +22,7 @@ class SecurityAdmin: _valid_segment_traits = {} _extracted_key_value_pair_segment_traits_map = {} _case_sensitive_extracted_values = [] + __running_userid = None __logger = Logger() def __init__( @@ -30,6 +33,7 @@ def __init__( 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", @@ -79,11 +83,28 @@ def __init__( self.__replace_valid_segment_traits(replace_existing_segment_traits) if additional_secret_traits is not None: self.__add_additional_secret_traits(additional_secret_traits) + self.set_running_userid(run_as_userid) + + # ============================================================================ + # Run as Other User ID + # ============================================================================ + def set_running_userid(self, userid: Union[str, None]) -> None: + if userid is None: + self.__running_userid = None + return + if not isinstance(userid, str) or len(userid) > 8 or userid == "": + raise UserIdError(userid) + self.__running_userid = userid.upper() + + def get_running_userid(self) -> Union[str, None]: + if isinstance(self.__running_userid, str): + return self.__running_userid.lower() + return self.__running_userid # ============================================================================ # Customize Segment Traits # ============================================================================ - def __update_valid_segment_traits(self, update_valid_segment_traits: dict): + def __update_valid_segment_traits(self, update_valid_segment_traits: dict) -> None: """Update fields to valid segment traits dictionary.""" for segment in update_valid_segment_traits: if segment in self._valid_segment_traits: @@ -95,14 +116,14 @@ def __update_valid_segment_traits(self, update_valid_segment_traits: dict): segment ] - def __replace_valid_segment_traits(self, new_valid_segment_traits: dict): + 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 # ============================================================================ # Secrets Redaction # ============================================================================ - def __add_additional_secret_traits(self, additional_secret_traits: list): + def __add_additional_secret_traits(self, additional_secret_traits: list) -> None: """Add additional fields to be redacted in logger output.""" for secret in additional_secret_traits: if secret in self.__secret_traits: @@ -157,36 +178,63 @@ def _make_request( security_request.dump_request_xml(encoding="utf-8"), secret_traits=self.__secret_traits, ) + 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: - request_xml = self.__logger.redact_request_xml( - security_request.dump_request_xml(encoding="utf-8"), - secret_traits=self.__secret_traits, - ) self.__clear_state(security_request) return request_xml - result_xml = self.__logger.redact_result_xml( + raw_result = self.__logger.redact_result_xml( self.__irrsmo00.call_racf( - security_request.dump_request_xml(), irrsmo00_precheck + security_request.dump_request_xml(), + irrsmo00_precheck, + self.__running_userid, ), self.__secret_traits, ) self.__clear_state(security_request) + if isinstance(raw_result, list): + # When IRRSMO00 encounters some errors, it returns no XML response string. + # When this happens, the C code instead surfaces the return and reason + # codes which causes a DownstreamFatalError to be raised. + raise DownstreamFatalError( + saf_return_code=raw_result[0], + racf_return_code=raw_result[1], + racf_reason_code=raw_result[2], + request_xml=request_xml, + run_as_userid=self.get_running_userid(), + ) 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", result_xml) - results = SecurityResult(result_xml) + self.__logger.log_xml("Result XML", raw_result) + result = SecurityResult(raw_result, self.get_running_userid()) 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( - "Result Dictionary", results.get_result_dictionary() + "Result Dictionary", result.get_result_dictionary() + ) + result_dictionary = result.get_result_dictionary() + if result_dictionary["securityResult"]["returnCode"] >= 8: + # All return codes greater than or equal to 8 are indicative of an issue + # with IRRSMO00 that would stop RACF from generating a command image. + # The user should interogate the result dictionary attached to the + # DownstreamFatalError as well as the return and reason codes to resolve + # the problem. + raise DownstreamFatalError( + saf_return_code=8, + racf_return_code=result_dictionary["securityResult"]["returnCode"], + racf_reason_code=result_dictionary["securityResult"]["reasonCode"], + request_xml=request_xml, + run_as_userid=self.get_running_userid(), + result_dictionary=result_dictionary, ) - result_dictionary = results.get_result_dictionary() if result_dictionary["securityResult"]["returnCode"] != 0: - # All non-zero return codes should cause a SecurityRequestError to be raised. - # Even if a return code of 4 is not indicative of a problem, it it is - # up to the user to interogate the result dictionary attached to the + # All remaining non-zero return codes should cause a SecurityRequestError + # to be raised. Even if a return code of 4 is not indicative of a problem, + # it is up to the user to interogate the result dictionary attached to the # SecurityRequestError and decided whether or not the return code 4 is # indicative of a problem. raise SecurityRequestError(result_dictionary) diff --git a/pyracf/common/security_request_error.py b/pyracf/common/security_request_error.py index 6a20bbeb..6b4b4ae7 100644 --- a/pyracf/common/security_request_error.py +++ b/pyracf/common/security_request_error.py @@ -1,9 +1,10 @@ -"""Exception to use when data returned by IRRSMO00 indicates that the request failed.""" +"""Exception to use when IRRSMO00 processes a request with RACF warnings or errors.""" class SecurityRequestError(Exception): """ - Raised when the return code of a security result returned by IRRSMO00 is NOT equal to 0. + Raised when pyRACF does not raise a DownstreamFatalError, and the SAF Return Code + of a security result returned by IRRSMO00 is non-zero. """ def __init__(self, result: dict) -> None: @@ -21,6 +22,7 @@ def __str__(self) -> str: def contains_error_message( self, security_definition_tag: str, error_message_id: str ): + """Checks to see if specific error message id appears in the security request error.""" commands = self.result["securityResult"][security_definition_tag].get( "commands" ) diff --git a/pyracf/common/security_result.py b/pyracf/common/security_result.py index dba11ae2..6601ee97 100644 --- a/pyracf/common/security_result.py +++ b/pyracf/common/security_result.py @@ -1,6 +1,8 @@ """Generic Security Result Parser.""" +import getpass import re +from typing import Union from xml.etree.ElementTree import Element # Only used for type hints. import defusedxml.ElementTree as XMLParser @@ -9,10 +11,20 @@ class SecurityResult: """Generic Security Result Parser.""" - def __init__(self, result_xml: str) -> None: + def __init__( + self, result_xml: str, running_userid: Union[str, None] = None + ) -> None: self.__result = XMLParser.fromstring(result_xml) self.__result_dictionary = {"securityResult": {}} self.__extract_results() + if running_userid is not None: + self.__result_dictionary["securityResult"][ + "runningUserid" + ] = running_userid.lower() + else: + self.__result_dictionary["securityResult"][ + "runningUserid" + ] = getpass.getuser().lower() def __extract_results(self) -> None: """Extract XML results into a dictionary.""" diff --git a/pyracf/common/userid_error.py b/pyracf/common/userid_error.py new file mode 100644 index 00000000..bafe4f8f --- /dev/null +++ b/pyracf/common/userid_error.py @@ -0,0 +1,20 @@ +""" +Exception to raise when run as userid is given a userid value +that is not a string between 1 to 8 characters in length. +""" + + +class UserIdError(Exception): + """ + Raised when pyRACF would attempt to run as a userid + that is not a string between 1 to 8 characters in length. + """ + + def __init__(self, userid: str) -> None: + self.message = ( + f"Unable to run as userid '{userid}'. Userid must " + + "be a string value between 1 to 8 characters in length." + ) + + def __str__(self) -> str: + return self.message diff --git a/pyracf/connection/connection_admin.py b/pyracf/connection/connection_admin.py index 2527118e..0162ccfb 100644 --- a/pyracf/connection/connection_admin.py +++ b/pyracf/connection/connection_admin.py @@ -17,6 +17,7 @@ def __init__( 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._valid_segment_traits = { "base": { @@ -40,6 +41,7 @@ def __init__( 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, ) # ============================================================================ diff --git a/pyracf/data_set/data_set_admin.py b/pyracf/data_set/data_set_admin.py index 1995e5af..2b1df499 100644 --- a/pyracf/data_set/data_set_admin.py +++ b/pyracf/data_set/data_set_admin.py @@ -20,6 +20,7 @@ def __init__( 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._valid_segment_traits = { "base": { @@ -67,6 +68,7 @@ def __init__( 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 diff --git a/pyracf/group/group_admin.py b/pyracf/group/group_admin.py index 63b22f7b..bd78851d 100644 --- a/pyracf/group/group_admin.py +++ b/pyracf/group/group_admin.py @@ -20,6 +20,7 @@ def __init__( 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._valid_segment_traits = { "base": { @@ -51,6 +52,7 @@ def __init__( 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, ) # ============================================================================ diff --git a/pyracf/resource/resource_admin.py b/pyracf/resource/resource_admin.py index ac5720d6..87d6cd64 100644 --- a/pyracf/resource/resource_admin.py +++ b/pyracf/resource/resource_admin.py @@ -20,6 +20,7 @@ def __init__( 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._valid_segment_traits = { "base": { @@ -241,6 +242,7 @@ def __init__( 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, ) # ============================================================================ @@ -265,9 +267,22 @@ def set_universal_access( ) return self._to_steps(result) + # ============================================================================ + # Individual Access + # ============================================================================ def get_my_access(self, resource: str, class_name: str) -> Union[str, bytes, None]: - """Get the access associated with your own general resource profile.""" + """Get your own level of access associated with a general resource profile.""" + profile = self.extract(resource, class_name, profile_only=True) + return self._get_field(profile, "base", "yourAccess") + + def get_user_access( + self, resource: str, class_name: str, userid: str + ) -> Union[str, bytes, None]: + """Get a target user's own level of access associated with a general resource profile.""" + original_userid = self.get_running_userid() + self.set_running_userid(userid) profile = self.extract(resource, class_name, profile_only=True) + self.set_running_userid(original_userid) return self._get_field(profile, "base", "yourAccess") # ============================================================================ diff --git a/pyracf/scripts/setup_precheck.py b/pyracf/scripts/setup_precheck.py new file mode 100644 index 00000000..998e08a7 --- /dev/null +++ b/pyracf/scripts/setup_precheck.py @@ -0,0 +1,35 @@ +from pyracf import ResourceAdmin, SecurityRequestError + + +def setup_precheck(): + resource_admin = ResourceAdmin() + try: + access = resource_admin.get_my_access("IRR.IRRSMO00.PRECHECK", "XFACILIT") + except SecurityRequestError: + traits_precheck = {"base:universal_access": "None"} + result = resource_admin.add( + "IRR.IRRSMO00.PRECHECK", "XFACILIT", traits=traits_precheck + ) + print( + "'IRR.IRRSMO00.PRECHECK' is now defined with a 'Universal Access' of 'NONE'.\n" + + "Contact your security administrator for 'READ' access before using pyRACF.\n" + + "Other users of pyRACF will also need to have at least 'READ' access.\n" + + "You may also need to refresh the 'XFACILIT' class.\n" + + "Review our documentation at https://ambitus.github.io/pyracf/ as well!" + ) + return result + if access: + print( + "'IRR.IRRSMO00.PRECHECK' is already defined, " + + f"and you already have '{access.upper()}' access!\n" + + "You are ready to start using pyRACF!\n" + + "Please ensure other users of pyRACF also have at least 'READ' access.\n" + + "Review our documentation at https://ambitus.github.io/pyracf/ as well!" + ) + return True + print( + "'IRR.IRRSMO00.PRECHECK' is already defined, but you have no access.\n" + + "Contact your security administrator for 'READ' access before using pyRACF.\n" + + "Review our documentation at https://ambitus.github.io/pyracf/ as well!" + ) + return False diff --git a/pyracf/setropts/setropts_admin.py b/pyracf/setropts/setropts_admin.py index 1aa6505a..f8e7f041 100644 --- a/pyracf/setropts/setropts_admin.py +++ b/pyracf/setropts/setropts_admin.py @@ -17,6 +17,7 @@ def __init__( 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._valid_segment_traits = { "base": { @@ -114,6 +115,7 @@ def __init__( 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, ) # ============================================================================ diff --git a/pyracf/user/user_admin.py b/pyracf/user/user_admin.py index beba75cd..6d1deb9a 100644 --- a/pyracf/user/user_admin.py +++ b/pyracf/user/user_admin.py @@ -20,6 +20,7 @@ def __init__( 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._valid_segment_traits = { "base": { @@ -212,6 +213,7 @@ def __init__( 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, ) # ============================================================================ diff --git a/tests/access/access_log_samples/permit_access_error.log b/tests/access/access_log_samples/permit_access_error.log index 28151cbf..a045350e 100644 --- a/tests/access/access_log_samples/permit_access_error.log +++ b/tests/access/access_log_samples/permit_access_error.log @@ -77,7 +77,8 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/access/access_log_samples/permit_access_success.log b/tests/access/access_log_samples/permit_access_success.log index d82660a9..46976dfa 100644 --- a/tests/access/access_log_samples/permit_access_success.log +++ b/tests/access/access_log_samples/permit_access_success.log @@ -73,7 +73,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/access/access_result_samples/delete_access_result_error.json b/tests/access/access_result_samples/delete_access_result_error.json index 13cb28c9..009fd49a 100644 --- a/tests/access/access_result_samples/delete_access_result_error.json +++ b/tests/access/access_result_samples/delete_access_result_error.json @@ -18,6 +18,7 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/access/access_result_samples/delete_access_result_success.json b/tests/access/access_result_samples/delete_access_result_success.json index d21fb4d1..40f76904 100644 --- a/tests/access/access_result_samples/delete_access_result_success.json +++ b/tests/access/access_result_samples/delete_access_result_success.json @@ -18,6 +18,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/access/access_result_samples/permit_access_result_error.json b/tests/access/access_result_samples/permit_access_result_error.json index 9c13abf1..2e1cf32f 100644 --- a/tests/access/access_result_samples/permit_access_result_error.json +++ b/tests/access/access_result_samples/permit_access_result_error.json @@ -18,6 +18,7 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/access/access_result_samples/permit_access_result_success.json b/tests/access/access_result_samples/permit_access_result_success.json index 594d4204..af3628c5 100644 --- a/tests/access/access_result_samples/permit_access_result_success.json +++ b/tests/access/access_result_samples/permit_access_result_success.json @@ -15,6 +15,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/access/test_access_result_parser.py b/tests/access/test_access_result_parser.py index b8d15926..cbb94348 100644 --- a/tests/access/test_access_result_parser.py +++ b/tests/access/test_access_result_parser.py @@ -36,7 +36,7 @@ def test_access_admin_can_parse_permit_access_success_xml( TestAccessConstants.TEST_PERMIT_ACCESS_RESULT_SUCCESS_DICTIONARY, ) - # Error, UserID MCGINLEY not defined to RACF + # Error, UserId MCGINLEY not defined to RACF def test_access_admin_can_parse_permit_access_error_xml( self, call_racf_mock: Mock, @@ -69,10 +69,7 @@ def test_access_admin_can_parse_delete_access_success_xml( ) # Error User not authorized, delete ignored - def test_access_admin_can_parse_delete_access_error_xml( - self, - call_racf_mock: Mock, - ): + def test_access_admin_can_parse_delete_access_error_xml(self, call_racf_mock: Mock): call_racf_mock.return_value = ( TestAccessConstants.TEST_DELETE_ACCESS_RESULT_ERROR_XML ) diff --git a/tests/common/common_log_samples/alter_user_success.log b/tests/common/common_log_samples/alter_user_success.log new file mode 100644 index 00000000..b6319288 --- /dev/null +++ b/tests/common/common_log_samples/alter_user_success.log @@ -0,0 +1,263 @@ + + [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 + + + + [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: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/alter_user_success_as_eswift.log b/tests/common/common_log_samples/alter_user_success_as_eswift.log new file mode 100644 index 00000000..31effcf7 --- /dev/null +++ b/tests/common/common_log_samples/alter_user_success_as_eswift.log @@ -0,0 +1,263 @@ + + [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 + + + + [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": "eswift" + } +} + + + [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": "eswift" + } +} + + + [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": "eswift" + } +} + diff --git a/tests/common/common_log_samples/extract_resource_precheck_as_squidwrd.log b/tests/common/common_log_samples/extract_resource_precheck_as_squidwrd.log new file mode 100644 index 00000000..fa796fe7 --- /dev/null +++ b/tests/common/common_log_samples/extract_resource_precheck_as_squidwrd.log @@ -0,0 +1,171 @@ + + [pyRACF:Debug] + Request Dictionary + ResourceAdmin.get_user_access() + + +{} + + + [pyRACF:Debug] + Request XML + ResourceAdmin.get_user_access() + + + + + + + + [pyRACF:Debug] + Result XML + ResourceAdmin.get_user_access() + + + + + + + 0 + 0 + 0 + RLIST XFACILIT (IRR.IRRSMO00.PRECHECK) + CLASS NAME + ----- ---- + XFACILIT IRR.IRRSMO00.PRECHECK + + GROUP CLASS NAME + ----- ----- ---- + GXFACILI + + LEVEL OWNER UNIVERSAL ACCESS YOUR ACCESS WARNING + ----- -------- ---------------- ----------- ------- + 00 ESWIFT READ READ NO + + INSTALLATION DATA + ----------------- + NONE + + APPLICATION DATA + ---------------- + NONE + + AUDITING + -------- + FAILURES(READ) + + NOTIFY + ------ + NO USER TO BE NOTIFIED + + + 0 + 0 + + + + [pyRACF:Debug] + Result Dictionary + ResourceAdmin.get_user_access() + + +{ + "securityResult": { + "resource": { + "name": "IRR.IRRSMO00.PRECHECK", + "class": "XFACILIT", + "operation": "listdata", + "requestId": "ResourceRequest", + "commands": [ + { + "safReturnCode": 0, + "returnCode": 0, + "reasonCode": 0, + "image": "RLIST XFACILIT (IRR.IRRSMO00.PRECHECK) ", + "messages": [ + "CLASS NAME", + "----- ----", + "XFACILIT IRR.IRRSMO00.PRECHECK", + " ", + "GROUP CLASS NAME", + "----- ----- ----", + "GXFACILI", + " ", + "LEVEL OWNER UNIVERSAL ACCESS YOUR ACCESS WARNING", + "----- -------- ---------------- ----------- -------", + " 00 ESWIFT READ READ NO", + " ", + "INSTALLATION DATA", + "-----------------", + "NONE", + " ", + "APPLICATION DATA", + "----------------", + "NONE", + " ", + "AUDITING", + "--------", + "FAILURES(READ)", + " ", + "NOTIFY", + "------", + "NO USER TO BE NOTIFIED" + ] + } + ] + }, + "returnCode": 0, + "reasonCode": 0, + "runningUserid": "squidwrd" + } +} + + + [pyRACF:Debug] + Result Dictionary (Formatted Profile) + ResourceAdmin.get_user_access() + + +{ + "securityResult": { + "resource": { + "name": "IRR.IRRSMO00.PRECHECK", + "class": "XFACILIT", + "operation": "listdata", + "requestId": "ResourceRequest", + "commands": [ + { + "safReturnCode": 0, + "returnCode": 0, + "reasonCode": 0, + "image": "RLIST XFACILIT (IRR.IRRSMO00.PRECHECK) ", + "profiles": [ + { + "base": { + "class": "xfacilit", + "name": "irr.irrsmo00.precheck", + "groupClassName": "gxfacili", + "level": 0, + "owner": "eswift", + "universalAccess": "read", + "yourAccess": "read", + "warning": null, + "installationData": null, + "applicationData": null, + "auditing": { + "failures": "read" + }, + "notify": null, + "generic": false + } + } + ] + } + ] + }, + "returnCode": 0, + "reasonCode": 0, + "runningUserid": "squidwrd" + } +} + diff --git a/tests/common/common_request_samples/alter_user_request.xml b/tests/common/common_request_samples/alter_user_request.xml new file mode 100644 index 00000000..39340346 --- /dev/null +++ b/tests/common/common_request_samples/alter_user_request.xml @@ -0,0 +1,11 @@ + + + + + + + /u/clarinet + + + + \ No newline at end of file diff --git a/tests/user/user_request_samples/alter_user_request_replace_segments.xml b/tests/common/common_request_samples/alter_user_request_replace_segments.xml similarity index 100% rename from tests/user/user_request_samples/alter_user_request_replace_segments.xml rename to tests/common/common_request_samples/alter_user_request_replace_segments.xml diff --git a/tests/user/user_request_samples/alter_user_request_update_segments.xml b/tests/common/common_request_samples/alter_user_request_update_segments.xml similarity index 100% rename from tests/user/user_request_samples/alter_user_request_update_segments.xml rename to tests/common/common_request_samples/alter_user_request_update_segments.xml diff --git a/tests/common/common_result_samples/add_resource_result_precheck_uacc_none_error.json b/tests/common/common_result_samples/add_resource_result_precheck_uacc_none_error.json new file mode 100644 index 00000000..aecdcf5c --- /dev/null +++ b/tests/common/common_result_samples/add_resource_result_precheck_uacc_none_error.json @@ -0,0 +1,33 @@ +{ + "securityResult": { + "resource": { + "name": "IRR.IRRSMO00.PRECHECK", + "class": "XFACILIT", + "operation": "set", + "requestId": "ResourceRequest", + "commands": [ + { + "safReturnCode": 8, + "returnCode": 16, + "reasonCode": 4, + "image": "RDEFINE XFACILIT (IRR.IRRSMO00.PRECHECK) ", + "messages": [ + "ICH10103I NOT AUTHORIZED TO DEFINE IRR.IRRSMO00.PRECHECK." + ] + }, + { + "safReturnCode": 8, + "returnCode": 16, + "reasonCode": 4, + "image": "RALTER XFACILIT (IRR.IRRSMO00.PRECHECK) UACC (None)", + "messages": [ + "ICH11102I IRR.IRRSMO00.PRECHECK NOT DEFINED TO CLASS XFACILIT." + ] + } + ] + }, + "returnCode": 4, + "reasonCode": 0, + "runningUserid": "testuser" + } +} \ No newline at end of file diff --git a/tests/common/common_result_samples/add_resource_result_precheck_uacc_none_error.xml b/tests/common/common_result_samples/add_resource_result_precheck_uacc_none_error.xml new file mode 100644 index 00000000..3d033fa7 --- /dev/null +++ b/tests/common/common_result_samples/add_resource_result_precheck_uacc_none_error.xml @@ -0,0 +1,21 @@ + + + + + 8 + 16 + 4 + RDEFINE XFACILIT (IRR.IRRSMO00.PRECHECK) + ICH10103I NOT AUTHORIZED TO DEFINE IRR.IRRSMO00.PRECHECK. + + + 8 + 16 + 4 + RALTER XFACILIT (IRR.IRRSMO00.PRECHECK) UACC (None) + ICH11102I IRR.IRRSMO00.PRECHECK NOT DEFINED TO CLASS XFACILIT. + + + 4 + 0 + \ No newline at end of file diff --git a/tests/common/common_result_samples/add_resource_result_precheck_uacc_none_success.json b/tests/common/common_result_samples/add_resource_result_precheck_uacc_none_success.json new file mode 100644 index 00000000..14731480 --- /dev/null +++ b/tests/common/common_result_samples/add_resource_result_precheck_uacc_none_success.json @@ -0,0 +1,33 @@ +{ + "securityResult": { + "resource": { + "name": "IRR.IRRSMO00.PRECHECK", + "class": "XFACILIT", + "operation": "set", + "requestId": "ResourceRequest", + "commands": [ + { + "safReturnCode": 0, + "returnCode": 0, + "reasonCode": 0, + "image": "RDEFINE XFACILIT (IRR.IRRSMO00.PRECHECK) ", + "messages": [ + "ICH10006I RACLISTED PROFILES FOR XFACILIT WILL NOT REFLECT THE ADDITION(S) UNTIL A SETROPTS REFRESH IS ISSUED." + ] + }, + { + "safReturnCode": 0, + "returnCode": 0, + "reasonCode": 0, + "image": "RALTER XFACILIT (IRR.IRRSMO00.PRECHECK) UACC (None) ", + "messages": [ + "ICH11009I RACLISTED PROFILES FOR XFACILIT WILL NOT REFLECT THE UPDATE(S) UNTIL A SETROPTS REFRESH IS ISSUED." + ] + } + ] + }, + "returnCode": 0, + "reasonCode": 0, + "runningUserid": "testuser" + } +} \ No newline at end of file diff --git a/tests/common/common_result_samples/add_resource_result_precheck_uacc_none_success.xml b/tests/common/common_result_samples/add_resource_result_precheck_uacc_none_success.xml new file mode 100644 index 00000000..210546b8 --- /dev/null +++ b/tests/common/common_result_samples/add_resource_result_precheck_uacc_none_success.xml @@ -0,0 +1,21 @@ + + + + + 0 + 0 + 0 + RDEFINE XFACILIT (IRR.IRRSMO00.PRECHECK) + ICH10006I RACLISTED PROFILES FOR XFACILIT WILL NOT REFLECT THE ADDITION(S) UNTIL A SETROPTS REFRESH IS ISSUED. + + + 0 + 0 + 0 + RALTER XFACILIT (IRR.IRRSMO00.PRECHECK) UACC (None) + ICH11009I RACLISTED PROFILES FOR XFACILIT WILL NOT REFLECT THE UPDATE(S) UNTIL A SETROPTS REFRESH IS ISSUED. + + + 0 + 0 + \ 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 new file mode 100644 index 00000000..290032c6 --- /dev/null +++ b/tests/common/common_result_samples/alter_user_result_success.json @@ -0,0 +1,23 @@ +{ + "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 new file mode 100644 index 00000000..e8492834 --- /dev/null +++ b/tests/common/common_result_samples/alter_user_result_success.xml @@ -0,0 +1,14 @@ + + + + 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_resource_result_precheck_error.xml b/tests/common/common_result_samples/extract_resource_result_precheck_error.xml new file mode 100644 index 00000000..abcdfd89 --- /dev/null +++ b/tests/common/common_result_samples/extract_resource_result_precheck_error.xml @@ -0,0 +1,14 @@ + + + + + 8 + 16 + 4 + RLIST XFACILIT (IRR.IRRSMO00.PRECHECK) + ICH13003I 'IRR.IRRSMO00.PRECHECK' NOT FOUND + + + 4 + 0 + \ No newline at end of file diff --git a/tests/common/common_result_samples/extract_resource_result_precheck_success.xml b/tests/common/common_result_samples/extract_resource_result_precheck_success.xml new file mode 100644 index 00000000..14c45774 --- /dev/null +++ b/tests/common/common_result_samples/extract_resource_result_precheck_success.xml @@ -0,0 +1,40 @@ + + + + + 0 + 0 + 0 + RLIST XFACILIT (IRR.IRRSMO00.PRECHECK) + CLASS NAME + ----- ---- + XFACILIT IRR.IRRSMO00.PRECHECK + + GROUP CLASS NAME + ----- ----- ---- + GXFACILI + + LEVEL OWNER UNIVERSAL ACCESS YOUR ACCESS WARNING + ----- -------- ---------------- ----------- ------- + 00 ESWIFT READ ALTER NO + + INSTALLATION DATA + ----------------- + NONE + + APPLICATION DATA + ---------------- + NONE + + AUDITING + -------- + FAILURES(READ) + + NOTIFY + ------ + NO USER TO BE NOTIFIED + + + 0 + 0 + \ No newline at end of file diff --git a/tests/user/user_result_samples/extract_user_result_base_omvs_csdata_success.json b/tests/common/common_result_samples/extract_user_result_base_omvs_csdata_success.json similarity index 97% rename from tests/user/user_result_samples/extract_user_result_base_omvs_csdata_success.json rename to tests/common/common_result_samples/extract_user_result_base_omvs_csdata_success.json index 48671a57..160740d7 100644 --- a/tests/user/user_result_samples/extract_user_result_base_omvs_csdata_success.json +++ b/tests/common/common_result_samples/extract_user_result_base_omvs_csdata_success.json @@ -64,6 +64,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/user/user_result_samples/extract_user_result_base_omvs_csdata_success.xml b/tests/common/common_result_samples/extract_user_result_base_omvs_csdata_success.xml similarity index 100% rename from tests/user/user_result_samples/extract_user_result_base_omvs_csdata_success.xml rename to tests/common/common_result_samples/extract_user_result_base_omvs_csdata_success.xml 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 new file mode 100644 index 00000000..20ffb3b5 --- /dev/null +++ b/tests/common/common_result_samples/extract_user_result_base_only_success.json @@ -0,0 +1,57 @@ +{ + "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 new file mode 100644 index 00000000..61a46535 --- /dev/null +++ b/tests/common/common_result_samples/extract_user_result_base_only_success.xml @@ -0,0 +1,32 @@ + + + + + 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_common_constants.py b/tests/common/test_common_constants.py new file mode 100644 index 00000000..d4dd266f --- /dev/null +++ b/tests/common/test_common_constants.py @@ -0,0 +1,148 @@ +""" +Sample data for testing Common functions in pyracf. +""" + +from typing import Union + +import tests.test_utilities as TestUtilities + + +def get_sample(sample_file: str) -> Union[str, bytes]: + return TestUtilities.get_sample(sample_file, "common") + + +# ============================================================================ +# Setup Precheck Sample Data +# ============================================================================ + +TEST_EXTRACT_RESOURCE_RESULT_PRECHECK_ERROR_XML = get_sample( + "extract_resource_result_precheck_error.xml" +) +TEST_EXTRACT_RESOURCE_RESULT_PRECHECK_SUCCESS_XML = get_sample( + "extract_resource_result_precheck_success.xml" +) + +TEST_ADD_RESOURCE_PRECHECK_UACC_NONE_SUCCESS_XML = get_sample( + "add_resource_result_precheck_uacc_none_success.xml" +) +TEST_ADD_RESOURCE_PRECHECK_UACC_NONE_SUCCESS_DICTIONARY = get_sample( + "add_resource_result_precheck_uacc_none_success.json" +) + +TEST_ADD_RESOURCE_PRECHECK_UACC_NONE_ERROR_XML = get_sample( + "add_resource_result_precheck_uacc_none_error.xml" +) +TEST_ADD_RESOURCE_PRECHECK_UACC_NONE_ERROR_DICTIONARY = get_sample( + "add_resource_result_precheck_uacc_none_error.json" +) + +TEST_SETUP_PRECHECK_VALIDATED_ACCESS_TEXT = ( + "'IRR.IRRSMO00.PRECHECK' is already defined, and you already have 'ALTER' access!\n" + + "You are ready to start using pyRACF!\n" + + "Please ensure other users of pyRACF also have at least 'READ' access.\n" + + "Review our documentation at https://ambitus.github.io/pyracf/ as well!\n" +) + +TEST_SETUP_PRECHECK_FOUND_NO_ACCESS_TEXT = ( + "'IRR.IRRSMO00.PRECHECK' is already defined, but you have no access.\n" + + "Contact your security administrator for 'READ' access before using pyRACF.\n" + + "Review our documentation at https://ambitus.github.io/pyracf/ as well!\n" +) + +TEST_SETUP_PRECHECK_DEFINED_PROFILE_TEXT = ( + "'IRR.IRRSMO00.PRECHECK' is now defined with a 'Universal Access' of 'NONE'.\n" + + "Contact your security administrator for 'READ' access before using pyRACF.\n" + + "Other users of pyRACF will also need to have at least 'READ' access.\n" + + "You may also need to refresh the 'XFACILIT' class.\n" + + "Review our documentation at https://ambitus.github.io/pyracf/ as well!\n" +) + +# ============================================================================ +# Customize Segment Traits +# ============================================================================ + +# Alter User Traits +TEST_ALTER_USER_CSDATA_AND_OMVS_REQUEST_TRAITS = { + "base:special": False, + "omvs:home_directory": "/u/clarinet", + "omvs:default_shell": False, + "csdata:tstcsfld": "testval", +} +TEST_ALTER_USER_CSDATA_REQUEST_TRAITS = { + "base:special": False, + "csdata:tstcsfld": "testval", +} + +# Valid Segment Traits Updates +TEST_USER_REPLACE_SEGMENT_TRAITS = { + "base": {"base:special": "alt:special"}, + "csdata": {"csdata:tstcsfld": "tstcsfld"}, +} + +TEST_USER_ADDITIONAL_SEGMENT_TRAITS = {"csdata": {"csdata:tstcsfld": "tstcsfld"}} + +# Alter User Requests +TEST_ALTER_USER_REQUEST_REPLACE_SEGMENTS_XML = get_sample( + "alter_user_request_replace_segments.xml" +) +TEST_ALTER_USER_REQUEST_UPDATE_SEGMENTS_XML = get_sample( + "alter_user_request_update_segments.xml" +) + +# Extract User Results +TEST_EXTRACT_USER_RESULT_BASE_OMVS_CSDATA_SUCCESS_XML = get_sample( + "extract_user_result_base_omvs_csdata_success.xml" +) +TEST_EXTRACT_USER_RESULT_BASE_OMVS_CSDATA_SUCCESS_DICTIONARY = get_sample( + "extract_user_result_base_omvs_csdata_success.json" +) + +# ============================================================================ +# Run As UserId +# ============================================================================ + +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" +) + +# ============================================================================ +# Downstream Fatal Error +# ============================================================================ + +TEST_DOWNSTREAM_FATAL_ERROR_PRECHECK_TEXT = ( + "(DownstreamFatalError) Security request made to IRRSMO00 failed." + + "\n\nSAF Return Code: 8\nRACF Return Code: 200\nRACF Reason Code: 16" + + "\n\nCheck to see if the proper RACF permissions are in place.\n" + + "For 'set' or 'alter' functions, you must have at least 'READ' " + + "access to 'IRR.IRRSMO00.PRECHECK' in the 'XFACILIT' class." +) + +TEST_DOWNSTREAM_FATAL_ERROR_SURROGAT_TEXT = ( + "(DownstreamFatalError) Security request made to IRRSMO00 failed." + + "\n\nSAF Return Code: 8\nRACF Return Code: 200\nRACF Reason Code: 8" + + "\n\nCheck to see if the proper RACF permissions are in place.\n" + + "For the 'run_as_userid' feature, you must have at least 'UPDATE' " + + "access to 'ESWIFT.IRRSMO00' in the 'SURROGAT' class." +) + +TEST_DOWNSTREAM_FATAL_ERROR_GENERIC_TEXT = ( + "(DownstreamFatalError) Security request made to IRRSMO00 failed." + + "\n\nSAF Return Code: 8\nRACF Return Code: 2000\nRACF Reason Code: 20" + + "\n\nPlease check the specified return and reason codes against " + + "the documented IRRSMO00 return and reason codes for more information " + + "about this error.\n" + + "https://www.ibm.com/docs/en/zos/3.1.0?topic=operations-return-reason-codes" +) diff --git a/tests/common/test_customize_segment_traits.py b/tests/common/test_customize_segment_traits.py new file mode 100644 index 00000000..80632c1c --- /dev/null +++ b/tests/common/test_customize_segment_traits.py @@ -0,0 +1,66 @@ +"""Test customizing security admin segment traits.""" + +import unittest +from unittest.mock import Mock, patch + +import __init__ + +import tests.common.test_common_constants as TestCommonConstants +from pyracf import UserAdmin +from pyracf.common.irrsmo00 import IRRSMO00 + +# Resolves F401 +__init__ + + +class TestCustomizeSegmentTraits(unittest.TestCase): + maxDiff = None + IRRSMO00.__init__ = Mock(return_value=None) + + # ============================================================================ + # Customize Segment Traits Request Generation + # ============================================================================ + def test_user_admin_build_alter_request_replace_existing_segment_traits(self): + user_admin = UserAdmin( + generate_requests_only=True, + replace_existing_segment_traits=TestCommonConstants.TEST_USER_REPLACE_SEGMENT_TRAITS, + ) + result = user_admin.alter( + "squidwrd", traits=TestCommonConstants.TEST_ALTER_USER_CSDATA_REQUEST_TRAITS + ) + self.assertEqual( + result, TestCommonConstants.TEST_ALTER_USER_REQUEST_REPLACE_SEGMENTS_XML + ) + + def test_user_admin_build_alter_request_update_existing_segment_traits(self): + user_admin = UserAdmin( + generate_requests_only=True, + update_existing_segment_traits=TestCommonConstants.TEST_USER_ADDITIONAL_SEGMENT_TRAITS, + ) + result = user_admin.alter( + "squidwrd", + traits=TestCommonConstants.TEST_ALTER_USER_CSDATA_AND_OMVS_REQUEST_TRAITS, + ) + self.assertEqual( + result, + TestCommonConstants.TEST_ALTER_USER_REQUEST_UPDATE_SEGMENTS_XML, + ) + + # ============================================================================ + # Customize Segment Traits Result Parsing + # ============================================================================ + @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") + def test_user_admin_can_parse_extract_user_base_omvs_csdata_success_xml( + self, + call_racf_mock: Mock, + ): + user_admin = UserAdmin( + update_existing_segment_traits=TestCommonConstants.TEST_USER_REPLACE_SEGMENT_TRAITS + ) + call_racf_mock.return_value = ( + TestCommonConstants.TEST_EXTRACT_USER_RESULT_BASE_OMVS_CSDATA_SUCCESS_XML + ) + self.assertEqual( + user_admin.extract("squidwrd", segments=["omvs", "csdata"]), + TestCommonConstants.TEST_EXTRACT_USER_RESULT_BASE_OMVS_CSDATA_SUCCESS_DICTIONARY, + ) diff --git a/tests/common/test_downstream_fatal_error.py b/tests/common/test_downstream_fatal_error.py new file mode 100644 index 00000000..247e0934 --- /dev/null +++ b/tests/common/test_downstream_fatal_error.py @@ -0,0 +1,57 @@ +"""Test downstream fatal error.""" + +import unittest +from unittest.mock import Mock, patch + +import __init__ + +import tests.common.test_common_constants as TestCommonConstants +from pyracf import DownstreamFatalError, UserAdmin +from pyracf.common.irrsmo00 import IRRSMO00 + +# Resolves F401 +__init__ + + +@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( + self, + call_racf_mock: Mock, + ): + call_racf_mock.return_value = [8, 200, 16] + with self.assertRaises(DownstreamFatalError) as exception: + self.user_admin.set_password("TESTUSER", "Testpass") + self.assertEqual( + exception.exception.message, + TestCommonConstants.TEST_DOWNSTREAM_FATAL_ERROR_PRECHECK_TEXT, + ) + + def test_donwstream_fatal_error_thrown_on_surrogat_error( + self, + call_racf_mock: Mock, + ): + call_racf_mock.return_value = [8, 200, 8] + self.user_admin.set_running_userid("ESWIFT") + with self.assertRaises(DownstreamFatalError) as exception: + self.user_admin.add("squidwrd") + self.assertEqual( + exception.exception.message, + TestCommonConstants.TEST_DOWNSTREAM_FATAL_ERROR_SURROGAT_TEXT, + ) + + def test_donwstream_fatal_error_thrown_on_miscellaneous_error( + self, + call_racf_mock: Mock, + ): + call_racf_mock.return_value = [8, 2000, 20] + with self.assertRaises(DownstreamFatalError) as exception: + self.user_admin.add("squidwrd") + self.assertEqual( + exception.exception.message, + TestCommonConstants.TEST_DOWNSTREAM_FATAL_ERROR_GENERIC_TEXT, + ) diff --git a/tests/common/test_run_as_userid.py b/tests/common/test_run_as_userid.py new file mode 100644 index 00000000..255c4667 --- /dev/null +++ b/tests/common/test_run_as_userid.py @@ -0,0 +1,150 @@ +"""Test functions for run as userid.""" + +import contextlib +import io +import re +import unittest +from unittest.mock import Mock, patch + +import __init__ + +import tests.common.test_common_constants as TestCommonConstants +from pyracf import ResourceAdmin, UserAdmin, UserIdError +from pyracf.common.irrsmo00 import IRRSMO00 + +# Resolves F401 +__init__ + + +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") + def test_set_run_as_userid_on_object_creation( + self, + call_racf_mock: Mock, + ): + 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, + ] + stdout = io.StringIO() + with contextlib.redirect_stdout(stdout): + user_admin.alter( + "squidwrd", + traits=TestCommonConstants.TEST_ALTER_USER_REQUEST_TRAITS, + ) + success_log = self.ansi_escape.sub("", stdout.getvalue()) + self.assertEqual( + success_log, TestCommonConstants.TEST_ALTER_USER_SUCCESS_AS_ESWIFT_LOG + ) + + def test_set_run_as_userid_on_object_creation_raises_userid_error(self): + userid = "ESWIFTTEST" + with self.assertRaises(UserIdError) as exception: + UserAdmin(debug=True, run_as_userid=userid) + self.assertEqual( + exception.exception.message, + f"Unable to run as userid '{userid}'. Userid must " + + "be a string value between 1 to 8 characters in length.", + ) + + @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") + def test_set_running_userid_after_object_creation( + self, + call_racf_mock: Mock, + ): + 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, + ] + stdout = io.StringIO() + with contextlib.redirect_stdout(stdout): + user_admin.alter( + "squidwrd", + traits=TestCommonConstants.TEST_ALTER_USER_REQUEST_TRAITS, + ) + success_log = self.ansi_escape.sub("", stdout.getvalue()) + self.assertEqual( + success_log, TestCommonConstants.TEST_ALTER_USER_SUCCESS_AS_ESWIFT_LOG + ) + + def test_set_running_userid_after_object_creation_raises_userid_error( + self, + ): + userid = "ESWIFTTEST" + user_admin = UserAdmin(debug=True) + with self.assertRaises(UserIdError) as exception: + user_admin.set_running_userid(userid) + self.assertEqual( + exception.exception.message, + f"Unable to run as userid '{userid}'. Userid must " + + "be a string value between 1 to 8 characters in length.", + ) + + @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") + def test_clear_running_userid( + self, + call_racf_mock: Mock, + ): + 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, + ] + stdout = io.StringIO() + with contextlib.redirect_stdout(stdout): + user_admin.alter( + "squidwrd", + traits=TestCommonConstants.TEST_ALTER_USER_REQUEST_TRAITS, + ) + success_log = self.ansi_escape.sub("", stdout.getvalue()) + self.assertEqual(success_log, TestCommonConstants.TEST_ALTER_USER_SUCCESS_LOG) + + def test_get_running_userid(self): + user_admin = UserAdmin(run_as_userid="ESWIFT") + running_user = user_admin.get_running_userid() + self.assertEqual(running_user, TestCommonConstants.TEST_RUNNING_USERID) + + @patch("pyracf.common.irrsmo00.IRRSMO00.call_racf") + def test_get_user_access( + self, + call_racf_mock: Mock, + ): + precheck_profile_as_squidwrd = ( + TestCommonConstants.TEST_EXTRACT_RESOURCE_RESULT_PRECHECK_SUCCESS_XML + ) + precheck_profile_as_squidwrd = precheck_profile_as_squidwrd.replace( + " 00 ESWIFT READ ALTER NO", + " 00 ESWIFT READ READ NO", + ) + resource_admin = ResourceAdmin(debug=True, run_as_userid="ESWIFT") + call_racf_mock.return_value = precheck_profile_as_squidwrd + stdout = io.StringIO() + with contextlib.redirect_stdout(stdout): + access = resource_admin.get_user_access( + "IRR.IRRSMO00.PRECHECK", "XFACILIT", "squidwrd" + ) + success_log = self.ansi_escape.sub("", stdout.getvalue()) + self.assertEqual( + success_log, + TestCommonConstants.TEST_EXTRACT_RESOURCE_PRECHECK_AS_SQUIDWRD_LOG, + ) + self.assertEqual(access, "read") + + def test_get_user_access_raises_userid_error(self): + userid = "squidwrdtest" + resource_admin = ResourceAdmin(debug=True, run_as_userid="ESWIFT") + with self.assertRaises(UserIdError) as exception: + resource_admin.get_user_access("IRR.IRRSMO00.PRECHECK", "XFACILIT", userid) + self.assertEqual( + exception.exception.message, + f"Unable to run as userid '{userid}'. Userid must " + + "be a string value between 1 to 8 characters in length.", + ) diff --git a/tests/common/test_setup_precheck.py b/tests/common/test_setup_precheck.py new file mode 100644 index 00000000..b348adff --- /dev/null +++ b/tests/common/test_setup_precheck.py @@ -0,0 +1,173 @@ +"""Test included script to initialize and evaluate precheck permissions.""" + +import contextlib +import io +import re +import unittest +from unittest.mock import Mock, patch + +import __init__ + +import tests.common.test_common_constants as TestCommonConstants +from pyracf import SecurityRequestError, setup_precheck +from pyracf.common.irrsmo00 import IRRSMO00 + +# Resolves F401 +__init__ + + +@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( + self, + call_racf_mock: Mock, + ): + call_racf_mock.side_effect = [ + TestCommonConstants.TEST_EXTRACT_RESOURCE_RESULT_PRECHECK_ERROR_XML, + TestCommonConstants.TEST_EXTRACT_RESOURCE_RESULT_PRECHECK_ERROR_XML, + TestCommonConstants.TEST_ADD_RESOURCE_PRECHECK_UACC_NONE_SUCCESS_XML, + ] + stdout = io.StringIO() + with contextlib.redirect_stdout(stdout): + result = setup_precheck() + precheck_log = self.ansi_escape.sub("", stdout.getvalue()) + self.assertEqual( + precheck_log, TestCommonConstants.TEST_SETUP_PRECHECK_DEFINED_PROFILE_TEXT + ) + self.assertEqual( + result, + TestCommonConstants.TEST_ADD_RESOURCE_PRECHECK_UACC_NONE_SUCCESS_DICTIONARY, + ) + + def test_setup_precheck_throws_error_when_add_resource_fails( + self, + call_racf_mock: Mock, + ): + call_racf_mock.side_effect = [ + TestCommonConstants.TEST_EXTRACT_RESOURCE_RESULT_PRECHECK_ERROR_XML, + TestCommonConstants.TEST_EXTRACT_RESOURCE_RESULT_PRECHECK_ERROR_XML, + TestCommonConstants.TEST_ADD_RESOURCE_PRECHECK_UACC_NONE_ERROR_XML, + ] + with self.assertRaises(SecurityRequestError) as exception: + setup_precheck() + self.assertEqual( + exception.exception.result, + TestCommonConstants.TEST_ADD_RESOURCE_PRECHECK_UACC_NONE_ERROR_DICTIONARY, + ) + + def test_setup_precheck_works_when_alter_access_exists( + self, + call_racf_mock: Mock, + ): + call_racf_mock.return_value = ( + TestCommonConstants.TEST_EXTRACT_RESOURCE_RESULT_PRECHECK_SUCCESS_XML + ) + stdout = io.StringIO() + with contextlib.redirect_stdout(stdout): + result = setup_precheck() + precheck_log = self.ansi_escape.sub("", stdout.getvalue()) + self.assertEqual( + precheck_log, + TestCommonConstants.TEST_SETUP_PRECHECK_VALIDATED_ACCESS_TEXT, + ) + self.assertEqual(result, True) + + def test_setup_precheck_works_when_control_access_exists( + self, + call_racf_mock: Mock, + ): + extract_with_control_access = ( + TestCommonConstants.TEST_EXTRACT_RESOURCE_RESULT_PRECHECK_SUCCESS_XML + ) + extract_with_control_access = extract_with_control_access.replace( + " 00 ESWIFT READ ALTER NO", + " 00 ESWIFT READ CONTROL NO", + ) + call_racf_mock.return_value = extract_with_control_access + validated_control_access = ( + TestCommonConstants.TEST_SETUP_PRECHECK_VALIDATED_ACCESS_TEXT + ) + validated_control_access = validated_control_access.replace( + "you already have 'ALTER' access!", "you already have 'CONTROL' access!" + ) + stdout = io.StringIO() + with contextlib.redirect_stdout(stdout): + result = setup_precheck() + precheck_log = self.ansi_escape.sub("", stdout.getvalue()) + self.assertEqual(precheck_log, validated_control_access) + self.assertEqual(result, True) + + def test_setup_precheck_works_when_read_access_exists( + self, + call_racf_mock: Mock, + ): + extract_with_read_access = ( + TestCommonConstants.TEST_EXTRACT_RESOURCE_RESULT_PRECHECK_SUCCESS_XML + ) + extract_with_read_access = extract_with_read_access.replace( + " 00 ESWIFT READ ALTER NO", + " 00 ESWIFT READ READ NO", + ) + call_racf_mock.return_value = extract_with_read_access + validated_read_access = ( + TestCommonConstants.TEST_SETUP_PRECHECK_VALIDATED_ACCESS_TEXT + ) + validated_read_access = validated_read_access.replace( + "you already have 'ALTER' access!", "you already have 'READ' access!" + ) + stdout = io.StringIO() + with contextlib.redirect_stdout(stdout): + result = setup_precheck() + precheck_log = self.ansi_escape.sub("", stdout.getvalue()) + self.assertEqual(precheck_log, validated_read_access) + self.assertEqual(result, True) + + def test_setup_precheck_works_when_update_access_exists( + self, + call_racf_mock: Mock, + ): + extract_with_update_access = ( + TestCommonConstants.TEST_EXTRACT_RESOURCE_RESULT_PRECHECK_SUCCESS_XML + ) + extract_with_update_access = extract_with_update_access.replace( + " 00 ESWIFT READ ALTER NO", + " 00 ESWIFT READ UPDATE NO", + ) + call_racf_mock.return_value = extract_with_update_access + validated_update_access = ( + TestCommonConstants.TEST_SETUP_PRECHECK_VALIDATED_ACCESS_TEXT + ) + validated_update_access = validated_update_access.replace( + "you already have 'ALTER' access!", "you already have 'UPDATE' access!" + ) + stdout = io.StringIO() + with contextlib.redirect_stdout(stdout): + result = setup_precheck() + precheck_log = self.ansi_escape.sub("", stdout.getvalue()) + self.assertEqual(precheck_log, validated_update_access) + self.assertEqual(result, True) + + def test_setup_precheck_works_when_none_access_exists( + self, + call_racf_mock: Mock, + ): + extract_with_none_access = ( + TestCommonConstants.TEST_EXTRACT_RESOURCE_RESULT_PRECHECK_SUCCESS_XML + ) + extract_with_none_access = extract_with_none_access.replace( + " 00 ESWIFT READ ALTER NO", + " 00 ESWIFT READ NONE NO", + ) + call_racf_mock.return_value = extract_with_none_access + stdout = io.StringIO() + with contextlib.redirect_stdout(stdout): + result = setup_precheck() + precheck_log = self.ansi_escape.sub("", stdout.getvalue()) + self.assertEqual( + precheck_log, TestCommonConstants.TEST_SETUP_PRECHECK_FOUND_NO_ACCESS_TEXT + ) + self.assertEqual(result, False) diff --git a/tests/connection/connection_log_samples/connect_connection_error.log b/tests/connection/connection_log_samples/connect_connection_error.log index c7473430..1563c4bc 100644 --- a/tests/connection/connection_log_samples/connect_connection_error.log +++ b/tests/connection/connection_log_samples/connect_connection_error.log @@ -79,7 +79,8 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/connection/connection_log_samples/connect_connection_success.log b/tests/connection/connection_log_samples/connect_connection_success.log index f7782fa8..c10b7e01 100644 --- a/tests/connection/connection_log_samples/connect_connection_success.log +++ b/tests/connection/connection_log_samples/connect_connection_success.log @@ -73,7 +73,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/connection/connection_result_samples/connect_connection_result_error.json b/tests/connection/connection_result_samples/connect_connection_result_error.json index 00882176..fe180153 100644 --- a/tests/connection/connection_result_samples/connect_connection_result_error.json +++ b/tests/connection/connection_result_samples/connect_connection_result_error.json @@ -19,6 +19,7 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/connection/connection_result_samples/connect_connection_result_success.json b/tests/connection/connection_result_samples/connect_connection_result_success.json index 00863adc..75d10ecc 100644 --- a/tests/connection/connection_result_samples/connect_connection_result_success.json +++ b/tests/connection/connection_result_samples/connect_connection_result_success.json @@ -15,6 +15,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/connection/connection_result_samples/delete_connection_result_error.json b/tests/connection/connection_result_samples/delete_connection_result_error.json index 38e894a6..193ad0f6 100644 --- a/tests/connection/connection_result_samples/delete_connection_result_error.json +++ b/tests/connection/connection_result_samples/delete_connection_result_error.json @@ -18,6 +18,7 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/connection/connection_result_samples/delete_connection_result_success.json b/tests/connection/connection_result_samples/delete_connection_result_success.json index 4983b418..0b4a3d20 100644 --- a/tests/connection/connection_result_samples/delete_connection_result_success.json +++ b/tests/connection/connection_result_samples/delete_connection_result_success.json @@ -15,6 +15,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/data_set/data_set_log_samples/alter_data_set_error.log b/tests/data_set/data_set_log_samples/alter_data_set_error.log index e637e65f..90cd31b1 100644 --- a/tests/data_set/data_set_log_samples/alter_data_set_error.log +++ b/tests/data_set/data_set_log_samples/alter_data_set_error.log @@ -107,7 +107,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -158,7 +159,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -259,7 +261,8 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/data_set/data_set_log_samples/alter_data_set_success.log b/tests/data_set/data_set_log_samples/alter_data_set_success.log index 131978a6..b76bc4af 100644 --- a/tests/data_set/data_set_log_samples/alter_data_set_success.log +++ b/tests/data_set/data_set_log_samples/alter_data_set_success.log @@ -107,7 +107,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -158,7 +159,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -243,7 +245,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/data_set/data_set_log_samples/extract_data_set_base_only_error.log b/tests/data_set/data_set_log_samples/extract_data_set_base_only_error.log index 2e791411..ced4ca62 100644 --- a/tests/data_set/data_set_log_samples/extract_data_set_base_only_error.log +++ b/tests/data_set/data_set_log_samples/extract_data_set_base_only_error.log @@ -63,7 +63,8 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/data_set/data_set_log_samples/extract_data_set_base_only_success.log b/tests/data_set/data_set_log_samples/extract_data_set_base_only_success.log index 68c4fd2f..1cec2fa0 100644 --- a/tests/data_set/data_set_log_samples/extract_data_set_base_only_success.log +++ b/tests/data_set/data_set_log_samples/extract_data_set_base_only_success.log @@ -107,7 +107,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -158,7 +159,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/data_set/data_set_result_samples/add_data_set_result_error.json b/tests/data_set/data_set_result_samples/add_data_set_result_error.json index ed2fc26a..a3891653 100644 --- a/tests/data_set/data_set_result_samples/add_data_set_result_error.json +++ b/tests/data_set/data_set_result_samples/add_data_set_result_error.json @@ -18,6 +18,7 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/data_set/data_set_result_samples/add_data_set_result_generic_success.json b/tests/data_set/data_set_result_samples/add_data_set_result_generic_success.json index f3c48657..927b7725 100644 --- a/tests/data_set/data_set_result_samples/add_data_set_result_generic_success.json +++ b/tests/data_set/data_set_result_samples/add_data_set_result_generic_success.json @@ -21,6 +21,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/data_set/data_set_result_samples/add_data_set_result_success.json b/tests/data_set/data_set_result_samples/add_data_set_result_success.json index 78dfe698..545e29db 100644 --- a/tests/data_set/data_set_result_samples/add_data_set_result_success.json +++ b/tests/data_set/data_set_result_samples/add_data_set_result_success.json @@ -21,6 +21,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/data_set/data_set_result_samples/alter_data_set_result_error.json b/tests/data_set/data_set_result_samples/alter_data_set_result_error.json index d4ddbf7f..218ee931 100644 --- a/tests/data_set/data_set_result_samples/alter_data_set_result_error.json +++ b/tests/data_set/data_set_result_samples/alter_data_set_result_error.json @@ -27,6 +27,7 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/data_set/data_set_result_samples/alter_data_set_result_success.json b/tests/data_set/data_set_result_samples/alter_data_set_result_success.json index 58e5faf2..511eb54c 100644 --- a/tests/data_set/data_set_result_samples/alter_data_set_result_success.json +++ b/tests/data_set/data_set_result_samples/alter_data_set_result_success.json @@ -18,6 +18,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/data_set/data_set_result_samples/delete_data_set_result_error.json b/tests/data_set/data_set_result_samples/delete_data_set_result_error.json index d17658da..62f9681b 100644 --- a/tests/data_set/data_set_result_samples/delete_data_set_result_error.json +++ b/tests/data_set/data_set_result_samples/delete_data_set_result_error.json @@ -18,6 +18,7 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/data_set/data_set_result_samples/delete_data_set_result_success.json b/tests/data_set/data_set_result_samples/delete_data_set_result_success.json index 0af42c0e..3b5f86ba 100644 --- a/tests/data_set/data_set_result_samples/delete_data_set_result_success.json +++ b/tests/data_set/data_set_result_samples/delete_data_set_result_success.json @@ -15,6 +15,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/data_set/data_set_result_samples/extract_data_set_result_bad_attribute_error.json b/tests/data_set/data_set_result_samples/extract_data_set_result_bad_attribute_error.json index b5d84c93..dc2463ae 100644 --- a/tests/data_set/data_set_result_samples/extract_data_set_result_bad_attribute_error.json +++ b/tests/data_set/data_set_result_samples/extract_data_set_result_bad_attribute_error.json @@ -20,6 +20,7 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/data_set/data_set_result_samples/extract_data_set_result_base_only_error.json b/tests/data_set/data_set_result_samples/extract_data_set_result_base_only_error.json index cdee6661..cfe352b3 100644 --- a/tests/data_set/data_set_result_samples/extract_data_set_result_base_only_error.json +++ b/tests/data_set/data_set_result_samples/extract_data_set_result_base_only_error.json @@ -18,6 +18,7 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/data_set/data_set_result_samples/extract_data_set_result_base_only_success.json b/tests/data_set/data_set_result_samples/extract_data_set_result_base_only_success.json index 5165ab4c..ede27465 100644 --- a/tests/data_set/data_set_result_samples/extract_data_set_result_base_only_success.json +++ b/tests/data_set/data_set_result_samples/extract_data_set_result_base_only_success.json @@ -39,6 +39,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/data_set/data_set_result_samples/extract_data_set_result_generic_base_only_success.json b/tests/data_set/data_set_result_samples/extract_data_set_result_generic_base_only_success.json index 28daeab4..71e9f714 100644 --- a/tests/data_set/data_set_result_samples/extract_data_set_result_generic_base_only_success.json +++ b/tests/data_set/data_set_result_samples/extract_data_set_result_generic_base_only_success.json @@ -36,6 +36,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/data_set/test_data_set_result_parser.py b/tests/data_set/test_data_set_result_parser.py index c4417d35..5ab769bb 100644 --- a/tests/data_set/test_data_set_result_parser.py +++ b/tests/data_set/test_data_set_result_parser.py @@ -1,5 +1,6 @@ """Test data set profile result parser.""" +import copy import unittest from unittest.mock import Mock, patch @@ -65,6 +66,41 @@ def test_data_set_admin_thows_error_on_add_existing_data_set_profile( + f"'{self.data_set_admin._profile_type}' profile.", ) + def test_dataset_admin_add_surfaces_error_from_preliminary_extract( + self, + call_racf_mock: Mock, + ): + profile_name = "ESWIFT.TEST.T1136242.P3020470" + extract_data_set_xml = ( + TestDataSetConstants.TEST_EXTRACT_DATA_SET_RESULT_BASE_ONLY_ERROR_XML + ) + extract_data_set_xml = extract_data_set_xml.replace( + ( + "ICH35003I NO RACF DESCRIPTION FOUND " + "FOR ESWIFT.TEST.T1136242.P3020470" + ), + "ICH35002I NOT AUTHORIZED TO LIST ESWIFT.TEST.T1136242.P3020470", + ) + call_racf_mock.side_effect = [ + extract_data_set_xml, + TestDataSetConstants.TEST_ADD_DATA_SET_RESULT_SUCCESS_XML, + ] + with self.assertRaises(SecurityRequestError) as exception: + self.data_set_admin.add( + profile_name, + traits=TestDataSetConstants.TEST_ADD_DATA_SET_REQUEST_TRAITS, + ) + extract_data_set_error_json = copy.deepcopy( + TestDataSetConstants.TEST_EXTRACT_DATA_SET_RESULT_BASE_ONLY_ERROR_DICTIONARY + ) + extract_data_set_error_json["securityResult"]["dataSet"]["commands"][0][ + "messages" + ] = ["ICH35002I NOT AUTHORIZED TO LIST ESWIFT.TEST.T1136242.P3020470"] + self.assertEqual( + exception.exception.result, + extract_data_set_error_json, + ) + def test_dataset_admin_avoids_error_on_add_covered_profile( self, call_racf_mock: Mock, diff --git a/tests/function_test/function_test.py b/tests/function_test/function_test.py index e23cf8fd..94660e3a 100644 --- a/tests/function_test/function_test.py +++ b/tests/function_test/function_test.py @@ -1,7 +1,7 @@ import getpass import json -from pyracf import UserAdmin +from pyracf import DownstreamFatalError, UserAdmin def main(): @@ -11,6 +11,24 @@ 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 + # 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") + try: + user_admin.extract(userid) + except DownstreamFatalError as exception: + if ( + (exception.saf_return_code == 8) + and (exception.racf_return_code == 200) + and (exception.racf_reason_code == 8) + ): + print(f"DownstreamFatalError occured as intended:\n\n{exception.message}\n") + return 0 + return 1 + if __name__ == "__main__": main() diff --git a/tests/group/group_log_samples/alter_group_error.log b/tests/group/group_log_samples/alter_group_error.log index 8b1ad45c..34205d68 100644 --- a/tests/group/group_log_samples/alter_group_error.log +++ b/tests/group/group_log_samples/alter_group_error.log @@ -86,7 +86,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -150,7 +151,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -237,7 +239,8 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/group/group_log_samples/alter_group_success.log b/tests/group/group_log_samples/alter_group_success.log index 1e1d1365..7bf5e943 100644 --- a/tests/group/group_log_samples/alter_group_success.log +++ b/tests/group/group_log_samples/alter_group_success.log @@ -86,7 +86,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -150,7 +151,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -229,7 +231,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/group/group_log_samples/extract_group_base_omvs_error.log b/tests/group/group_log_samples/extract_group_base_omvs_error.log index baafe74c..cb969528 100644 --- a/tests/group/group_log_samples/extract_group_base_omvs_error.log +++ b/tests/group/group_log_samples/extract_group_base_omvs_error.log @@ -66,7 +66,8 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/group/group_log_samples/extract_group_base_omvs_success.log b/tests/group/group_log_samples/extract_group_base_omvs_success.log index 7ab0fd76..fcf3adde 100644 --- a/tests/group/group_log_samples/extract_group_base_omvs_success.log +++ b/tests/group/group_log_samples/extract_group_base_omvs_success.log @@ -86,7 +86,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -130,7 +131,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/group/group_result_samples/add_group_result_error.json b/tests/group/group_result_samples/add_group_result_error.json index 4df6b394..6500121b 100644 --- a/tests/group/group_result_samples/add_group_result_error.json +++ b/tests/group/group_result_samples/add_group_result_error.json @@ -8,12 +8,13 @@ "errorFunction": 10, "errorCode": 2000, "errorReason": 68, - "errorMessage": "Invalid attribute value specified.", + "textInError": "name", "errorOffset": 149, - "textInError": "name" + "errorMessage": "Invalid attribute value specified." } }, "returnCode": 2000, - "reasonCode": 68 + "reasonCode": 68, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/group/group_result_samples/add_group_result_success.json b/tests/group/group_result_samples/add_group_result_success.json index 08b867ae..1ae3e4bb 100644 --- a/tests/group/group_result_samples/add_group_result_success.json +++ b/tests/group/group_result_samples/add_group_result_success.json @@ -20,6 +20,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/group/group_result_samples/alter_group_result_error.json b/tests/group/group_result_samples/alter_group_result_error.json index 69479139..77d89a27 100644 --- a/tests/group/group_result_samples/alter_group_result_error.json +++ b/tests/group/group_result_samples/alter_group_result_error.json @@ -22,6 +22,7 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/group/group_result_samples/alter_group_result_success.json b/tests/group/group_result_samples/alter_group_result_success.json index 1ca2aa4a..19a411f6 100644 --- a/tests/group/group_result_samples/alter_group_result_success.json +++ b/tests/group/group_result_samples/alter_group_result_success.json @@ -17,6 +17,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/group/group_result_samples/delete_group_result_error.json b/tests/group/group_result_samples/delete_group_result_error.json index ba44ec86..afefddae 100644 --- a/tests/group/group_result_samples/delete_group_result_error.json +++ b/tests/group/group_result_samples/delete_group_result_error.json @@ -17,6 +17,7 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/group/group_result_samples/delete_group_result_success.json b/tests/group/group_result_samples/delete_group_result_success.json index 7e373842..13ff5a4e 100644 --- a/tests/group/group_result_samples/delete_group_result_success.json +++ b/tests/group/group_result_samples/delete_group_result_success.json @@ -14,6 +14,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/group/group_result_samples/extract_group_result_bad_attribute_error.json b/tests/group/group_result_samples/extract_group_result_bad_attribute_error.json index 1ef0740a..7bbc640c 100644 --- a/tests/group/group_result_samples/extract_group_result_bad_attribute_error.json +++ b/tests/group/group_result_samples/extract_group_result_bad_attribute_error.json @@ -8,12 +8,13 @@ "errorFunction": 10, "errorCode": 2000, "errorReason": 68, - "errorMessage": "Invalid attribute value specified.", + "textInError": "name", "errorOffset": 149, - "textInError": "name" + "errorMessage": "Invalid attribute value specified." } }, "returnCode": 2000, - "reasonCode": 68 + "reasonCode": 68, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/group/group_result_samples/extract_group_result_base_omvs_error.json b/tests/group/group_result_samples/extract_group_result_base_omvs_error.json index 07330edf..745d18f8 100644 --- a/tests/group/group_result_samples/extract_group_result_base_omvs_error.json +++ b/tests/group/group_result_samples/extract_group_result_base_omvs_error.json @@ -17,6 +17,7 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/group/group_result_samples/extract_group_result_base_omvs_success.json b/tests/group/group_result_samples/extract_group_result_base_omvs_success.json index fddbcbf4..2b57fad5 100644 --- a/tests/group/group_result_samples/extract_group_result_base_omvs_success.json +++ b/tests/group/group_result_samples/extract_group_result_base_omvs_success.json @@ -32,6 +32,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/group/group_result_samples/extract_group_result_base_only_error.json b/tests/group/group_result_samples/extract_group_result_base_only_error.json index 3d696bf3..0881b1fb 100644 --- a/tests/group/group_result_samples/extract_group_result_base_only_error.json +++ b/tests/group/group_result_samples/extract_group_result_base_only_error.json @@ -17,6 +17,7 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/group/group_result_samples/extract_group_result_base_only_success.json b/tests/group/group_result_samples/extract_group_result_base_only_success.json index 9323d833..8ae9b377 100644 --- a/tests/group/group_result_samples/extract_group_result_base_only_success.json +++ b/tests/group/group_result_samples/extract_group_result_base_only_success.json @@ -52,6 +52,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/group/test_group_constants.py b/tests/group/test_group_constants.py index a971c3be..478a1ca9 100644 --- a/tests/group/test_group_constants.py +++ b/tests/group/test_group_constants.py @@ -51,7 +51,7 @@ def get_sample(sample_file: str) -> Union[str, bytes]: TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_ERROR_XML = get_sample( "extract_group_result_base_only_error.xml" ) -TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_ERROR_JSON = get_sample( +TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_ERROR_DICTIONARY = get_sample( "extract_group_result_base_only_error.json" ) TEST_EXTRACT_GROUP_RESULT_BAD_ATTRIBUTE_ERROR_XML = get_sample( diff --git a/tests/group/test_group_result_parser.py b/tests/group/test_group_result_parser.py index b02441da..d86a1766 100644 --- a/tests/group/test_group_result_parser.py +++ b/tests/group/test_group_result_parser.py @@ -1,5 +1,6 @@ """Test group result parser.""" +import copy import unittest from unittest.mock import Mock, patch @@ -9,6 +10,7 @@ from pyracf import ( AddOperationError, AlterOperationError, + DownstreamFatalError, GroupAdmin, SecurityRequestError, ) @@ -62,6 +64,37 @@ def test_group_admin_throws_error_on_add_existing_group( + f"'{group_name}' already exists as a '{self.group_admin._profile_type}' profile.", ) + def test_group_admin_add_surfaces_error_from_preliminary_extract( + self, + call_racf_mock: Mock, + ): + group_name = "TESTGRP0" + extract_group_error_xml = ( + TestGroupConstants.TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_ERROR_XML + ) + extract_group_error_xml = extract_group_error_xml.replace( + "ICH51003I NAME NOT FOUND IN RACF DATA SET", + "ICH32004I NOT AUTHORIZED TO ISSUE LISTGRP", + ) + call_racf_mock.side_effect = [ + extract_group_error_xml, + TestGroupConstants.TEST_ADD_GROUP_RESULT_SUCCESS_XML, + ] + with self.assertRaises(SecurityRequestError) as exception: + self.group_admin.add( + group_name, traits=TestGroupConstants.TEST_ADD_GROUP_REQUEST_TRAITS + ) + extract_group_error_json = copy.deepcopy( + TestGroupConstants.TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_ERROR_DICTIONARY + ) + extract_group_error_json["securityResult"]["group"]["commands"][0][ + "messages" + ] = ["ICH32004I NOT AUTHORIZED TO ISSUE LISTGRP"] + self.assertEqual( + exception.exception.result, + extract_group_error_json, + ) + # Error in command, TESTGRPP0 is bad GROUP def test_group_admin_can_parse_add_group_error_xml( self, @@ -71,7 +104,7 @@ def test_group_admin_can_parse_add_group_error_xml( TestGroupConstants.TEST_EXTRACT_GROUP_RESULT_BAD_ATTRIBUTE_ERROR_XML, TestGroupConstants.TEST_ADD_GROUP_RESULT_ERROR_XML, ] - with self.assertRaises(SecurityRequestError) as exception: + with self.assertRaises(DownstreamFatalError) as exception: self.group_admin.add( "TESTGRPP0", traits=TestGroupConstants.TEST_ADD_GROUP_REQUEST_TRAITS ) diff --git a/tests/resource/resource_log_samples/alter_resource_error.log b/tests/resource/resource_log_samples/alter_resource_error.log index 922c6157..cb8b9b79 100644 --- a/tests/resource/resource_log_samples/alter_resource_error.log +++ b/tests/resource/resource_log_samples/alter_resource_error.log @@ -107,7 +107,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -154,7 +155,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -247,7 +249,8 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/resource/resource_log_samples/alter_resource_success.log b/tests/resource/resource_log_samples/alter_resource_success.log index 5f5a6f9b..9a960990 100644 --- a/tests/resource/resource_log_samples/alter_resource_success.log +++ b/tests/resource/resource_log_samples/alter_resource_success.log @@ -107,7 +107,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -154,7 +155,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -243,7 +245,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/resource/resource_log_samples/extract_resource_base_error.log b/tests/resource/resource_log_samples/extract_resource_base_error.log index 077b1b20..e4b44174 100644 --- a/tests/resource/resource_log_samples/extract_resource_base_error.log +++ b/tests/resource/resource_log_samples/extract_resource_base_error.log @@ -63,7 +63,8 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/resource/resource_log_samples/extract_resource_base_success.log b/tests/resource/resource_log_samples/extract_resource_base_success.log index 63625d61..b521d899 100644 --- a/tests/resource/resource_log_samples/extract_resource_base_success.log +++ b/tests/resource/resource_log_samples/extract_resource_base_success.log @@ -107,7 +107,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -154,7 +155,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/resource/resource_result_samples/add_resource_result_error.json b/tests/resource/resource_result_samples/add_resource_result_error.json index 8eb303da..3ea06721 100644 --- a/tests/resource/resource_result_samples/add_resource_result_error.json +++ b/tests/resource/resource_result_samples/add_resource_result_error.json @@ -30,6 +30,7 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/resource/resource_result_samples/add_resource_result_success.json b/tests/resource/resource_result_samples/add_resource_result_success.json index 3427b256..996e945e 100644 --- a/tests/resource/resource_result_samples/add_resource_result_success.json +++ b/tests/resource/resource_result_samples/add_resource_result_success.json @@ -27,6 +27,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/resource/resource_result_samples/alter_resource_result_error.json b/tests/resource/resource_result_samples/alter_resource_result_error.json index 54f8f39e..8d7e21f1 100644 --- a/tests/resource/resource_result_samples/alter_resource_result_error.json +++ b/tests/resource/resource_result_samples/alter_resource_result_error.json @@ -23,6 +23,7 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/resource/resource_result_samples/alter_resource_result_success.json b/tests/resource/resource_result_samples/alter_resource_result_success.json index 6e939576..f42229c4 100644 --- a/tests/resource/resource_result_samples/alter_resource_result_success.json +++ b/tests/resource/resource_result_samples/alter_resource_result_success.json @@ -21,6 +21,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/resource/resource_result_samples/delete_resource_result_error.json b/tests/resource/resource_result_samples/delete_resource_result_error.json index 249de09e..d087012e 100644 --- a/tests/resource/resource_result_samples/delete_resource_result_error.json +++ b/tests/resource/resource_result_samples/delete_resource_result_error.json @@ -18,6 +18,7 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/resource/resource_result_samples/delete_resource_result_success.json b/tests/resource/resource_result_samples/delete_resource_result_success.json index ce780202..ee02d11e 100644 --- a/tests/resource/resource_result_samples/delete_resource_result_success.json +++ b/tests/resource/resource_result_samples/delete_resource_result_success.json @@ -18,6 +18,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/resource/resource_result_samples/extract_resource_result_bad_class_error.json b/tests/resource/resource_result_samples/extract_resource_result_bad_class_error.json index dde77d51..3ded6f42 100644 --- a/tests/resource/resource_result_samples/extract_resource_result_bad_class_error.json +++ b/tests/resource/resource_result_samples/extract_resource_result_bad_class_error.json @@ -20,6 +20,7 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/resource/resource_result_samples/extract_resource_result_base_error.json b/tests/resource/resource_result_samples/extract_resource_result_base_error.json index ed43a015..4e720da6 100644 --- a/tests/resource/resource_result_samples/extract_resource_result_base_error.json +++ b/tests/resource/resource_result_samples/extract_resource_result_base_error.json @@ -18,6 +18,7 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/resource/resource_result_samples/extract_resource_result_base_success.json b/tests/resource/resource_result_samples/extract_resource_result_base_success.json index b605727b..0a07ed06 100644 --- a/tests/resource/resource_result_samples/extract_resource_result_base_success.json +++ b/tests/resource/resource_result_samples/extract_resource_result_base_success.json @@ -35,6 +35,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/resource/resource_result_samples/extract_resource_result_multi_base_success.json b/tests/resource/resource_result_samples/extract_resource_result_multi_base_success.json index 395494ec..4013eeed 100644 --- a/tests/resource/resource_result_samples/extract_resource_result_multi_base_success.json +++ b/tests/resource/resource_result_samples/extract_resource_result_multi_base_success.json @@ -55,6 +55,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/resource/test_resource_result_parser.py b/tests/resource/test_resource_result_parser.py index 525366ee..16aeb732 100644 --- a/tests/resource/test_resource_result_parser.py +++ b/tests/resource/test_resource_result_parser.py @@ -1,5 +1,6 @@ """Test general resource profile result parser.""" +import copy import unittest from unittest.mock import Mock, patch @@ -63,6 +64,40 @@ def test_resource_admin_throws_error_on_add_existing_profile( + f"'{profile_name}' already exists as a profile in the '{class_name}' class.", ) + def test_resource_admin_add_surfaces_error_from_preliminary_extract( + self, + call_racf_mock: Mock, + ): + profile_name = "TESTING" + class_name = "ELIJTEST" + extract_resource_xml = ( + TestResourceConstants.TEST_EXTRACT_RESOURCE_RESULT_BASE_ERROR_XML + ) + extract_resource_xml = extract_resource_xml.replace( + "ICH13003I TESTING NOT FOUND", + "ICH13002I NOT AUTHORIZED TO LIST TESTING", + ) + call_racf_mock.side_effect = [ + extract_resource_xml, + TestResourceConstants.TEST_ADD_RESOURCE_RESULT_SUCCESS_XML, + ] + with self.assertRaises(SecurityRequestError) as exception: + self.resource_admin.add( + profile_name, + class_name, + traits=TestResourceConstants.TEST_ADD_RESOURCE_REQUEST_TRAITS, + ) + extract_resource_error_json = copy.deepcopy( + TestResourceConstants.TEST_EXTRACT_RESOURCE_RESULT_BASE_ERROR_DICTIONARY + ) + extract_resource_error_json["securityResult"]["resource"]["commands"][0][ + "messages" + ] = ["ICH13002I NOT AUTHORIZED TO LIST TESTING"] + self.assertEqual( + exception.exception.result, + extract_resource_error_json, + ) + def test_resource_admin_avoids_error_on_add_covered_profile( self, call_racf_mock: Mock, diff --git a/tests/setropts/setropts_log_samples/alter_setropts_error.log b/tests/setropts/setropts_log_samples/alter_setropts_error.log index 246e426c..73318452 100644 --- a/tests/setropts/setropts_log_samples/alter_setropts_error.log +++ b/tests/setropts/setropts_log_samples/alter_setropts_error.log @@ -74,7 +74,8 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/setropts/setropts_log_samples/alter_setropts_success.log b/tests/setropts/setropts_log_samples/alter_setropts_success.log index 7bd71b7a..14f7da8a 100644 --- a/tests/setropts/setropts_log_samples/alter_setropts_success.log +++ b/tests/setropts/setropts_log_samples/alter_setropts_success.log @@ -70,7 +70,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/setropts/setropts_log_samples/list_setropts_success.log b/tests/setropts/setropts_log_samples/list_setropts_success.log index 90eea0fb..ad81e599 100644 --- a/tests/setropts/setropts_log_samples/list_setropts_success.log +++ b/tests/setropts/setropts_log_samples/list_setropts_success.log @@ -330,7 +330,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -972,7 +973,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } 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 5676a8a7..50dd9443 100644 --- a/tests/setropts/setropts_log_samples/list_setropts_success_2.log +++ b/tests/setropts/setropts_log_samples/list_setropts_success_2.log @@ -248,7 +248,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -578,7 +579,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } 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 c27d8df3..7888880f 100644 --- a/tests/setropts/setropts_log_samples/list_setropts_success_3.log +++ b/tests/setropts/setropts_log_samples/list_setropts_success_3.log @@ -598,7 +598,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -2034,7 +2035,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/setropts/setropts_result_samples/alter_setropts_result_error.json b/tests/setropts/setropts_result_samples/alter_setropts_result_error.json index be701b2c..dda84501 100644 --- a/tests/setropts/setropts_result_samples/alter_setropts_result_error.json +++ b/tests/setropts/setropts_result_samples/alter_setropts_result_error.json @@ -18,6 +18,7 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/setropts/setropts_result_samples/alter_setropts_result_success.json b/tests/setropts/setropts_result_samples/alter_setropts_result_success.json index 555eacc8..1911029e 100644 --- a/tests/setropts/setropts_result_samples/alter_setropts_result_success.json +++ b/tests/setropts/setropts_result_samples/alter_setropts_result_success.json @@ -16,6 +16,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/setropts/setropts_result_samples/list_setropts_result_success.json b/tests/setropts/setropts_result_samples/list_setropts_result_success.json index 48d0e4ae..1539c14c 100644 --- a/tests/setropts/setropts_result_samples/list_setropts_result_success.json +++ b/tests/setropts/setropts_result_samples/list_setropts_result_success.json @@ -630,6 +630,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/setropts/setropts_result_samples/list_setropts_result_success_2.json b/tests/setropts/setropts_result_samples/list_setropts_result_success_2.json index e118dc3e..7dd230a2 100644 --- a/tests/setropts/setropts_result_samples/list_setropts_result_success_2.json +++ b/tests/setropts/setropts_result_samples/list_setropts_result_success_2.json @@ -318,6 +318,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/setropts/setropts_result_samples/list_setropts_result_success_3.json b/tests/setropts/setropts_result_samples/list_setropts_result_success_3.json index 7dc8ff10..bc6e0b35 100644 --- a/tests/setropts/setropts_result_samples/list_setropts_result_success_3.json +++ b/tests/setropts/setropts_result_samples/list_setropts_result_success_3.json @@ -1424,6 +1424,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/test_runner.py b/tests/test_runner.py index 6253f828..214e1a6c 100644 --- a/tests/test_runner.py +++ b/tests/test_runner.py @@ -1,14 +1,20 @@ """Used to drive all unit tests for pyRACF.""" +import getpass import sys import unittest +from unittest.mock import Mock import __init__ 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_customize_segment_traits import TestCustomizeSegmentTraits +from tests.common.test_downstream_fatal_error import TestDownstreamFatalError from tests.common.test_logger import TestLogger +from tests.common.test_run_as_userid import TestRunAsUserId +from tests.common.test_setup_precheck import TestSetupPrecheck from tests.connection.test_connection_debug_logging import TestConnectionDebugLogging from tests.connection.test_connection_request_builder import ( TestConnectionRequestBuilder, @@ -53,13 +59,18 @@ def __test_suite() -> unittest.TestSuite: """Load and run each unit test class.""" + getpass.getuser = Mock(return_value="testuser") test_suite = unittest.TestSuite() test_loader = unittest.TestLoader() test_classes = [ TestAccessResultParser, TestAccessRequestBuilder, TestAccessDebugLogging, + TestCustomizeSegmentTraits, + TestSetupPrecheck, TestLogger, + TestDownstreamFatalError, + TestRunAsUserId, TestConnectionResultParser, TestConnectionRequestBuilder, TestConnectionSetters, diff --git a/tests/test_utilities.py b/tests/test_utilities.py index db14398a..6e5398d8 100644 --- a/tests/test_utilities.py +++ b/tests/test_utilities.py @@ -57,7 +57,7 @@ def get_sample(sample_file: str, function_group: str) -> Union[str, bytes]: result_samples = f"{function_group}/{function_group}_result_samples" request_samples = f"{function_group}/{function_group}_request_samples" log_samples = f"{function_group}/{function_group}_log_samples" - if f"{function_group}_result" in sample_file: + if "_result" in sample_file: result_sample_file = f"{result_samples}/{sample_file}" if sample_file[-4:] == ".xml": return get_xml(result_sample_file) diff --git a/tests/user/test_user_constants.py b/tests/user/test_user_constants.py index d5d4cb21..5129946f 100644 --- a/tests/user/test_user_constants.py +++ b/tests/user/test_user_constants.py @@ -94,7 +94,7 @@ def get_sample(sample_file: str) -> Union[str, bytes]: TEST_EXTRACT_USER_RESULT_BASE_ONLY_ERROR_XML = get_sample( "extract_user_result_base_only_error.xml" ) -TEST_EXTRACT_USER_RESULT_BASE_ONLY_ERROR_JSON = get_sample( +TEST_EXTRACT_USER_RESULT_BASE_ONLY_ERROR_DICTIONARY = get_sample( "extract_user_result_base_only_error.json" ) TEST_EXTRACT_USER_RESULT_WITH_CLASS_AUTHORIZATIONS = get_sample( @@ -435,43 +435,3 @@ def get_sample(sample_file: str) -> Union[str, bytes]: "extract_user_base_omvs_success.log" ) TEST_EXTRACT_USER_BASE_OMVS_ERROR_LOG = get_sample("extract_user_base_omvs_error.log") - -# ============================================================================ -# Customize Segment Traits -# ============================================================================ - -# Alter User Traits -TEST_ALTER_USER_CSDATA_AND_OMVS_REQUEST_TRAITS = { - "base:special": False, - "omvs:home_directory": "/u/clarinet", - "omvs:default_shell": False, - "csdata:tstcsfld": "testval", -} -TEST_ALTER_USER_CSDATA_REQUEST_TRAITS = { - "base:special": False, - "csdata:tstcsfld": "testval", -} - -# Valid Segment Traits Updates -TEST_USER_REPLACE_SEGMENT_TRAITS = { - "base": {"base:special": "alt:special"}, - "csdata": {"csdata:tstcsfld": "tstcsfld"}, -} - -TEST_USER_ADDITIONAL_SEGMENT_TRAITS = {"csdata": {"csdata:tstcsfld": "tstcsfld"}} - -# Alter User Requests -TEST_ALTER_USER_REQUEST_REPLACE_SEGMENTS_XML = get_sample( - "alter_user_request_replace_segments.xml" -) -TEST_ALTER_USER_REQUEST_UPDATE_SEGMENTS_XML = get_sample( - "alter_user_request_update_segments.xml" -) - -# Extract User Results -TEST_EXTRACT_USER_RESULT_BASE_OMVS_CSDATA_SUCCESS_XML = get_sample( - "extract_user_result_base_omvs_csdata_success.xml" -) -TEST_EXTRACT_USER_RESULT_BASE_OMVS_CSDATA_SUCCESS_DICTIONARY = get_sample( - "extract_user_result_base_omvs_csdata_success.json" -) diff --git a/tests/user/test_user_request_builder.py b/tests/user/test_user_request_builder.py index 7c7a15f5..655ac77a 100644 --- a/tests/user/test_user_request_builder.py +++ b/tests/user/test_user_request_builder.py @@ -92,35 +92,6 @@ def test_user_admin_build_add_user_request_passphrase_and_password_redacted( self.assertNotIn(self.test_password, result.decode("utf-8")) self.assertNotIn(self.test_passphrase, result.decode("utf-8")) - # ============================================================================ - # Customize Segment Traits - # ============================================================================ - def test_user_admin_build_alter_request_replace_existing_segment_traits(self): - user_admin = UserAdmin( - generate_requests_only=True, - replace_existing_segment_traits=TestUserConstants.TEST_USER_REPLACE_SEGMENT_TRAITS, - ) - result = user_admin.alter( - "squidwrd", traits=TestUserConstants.TEST_ALTER_USER_CSDATA_REQUEST_TRAITS - ) - self.assertEqual( - result, TestUserConstants.TEST_ALTER_USER_REQUEST_REPLACE_SEGMENTS_XML - ) - - def test_user_admin_build_alter_request_update_existing_segment_traits(self): - user_admin = UserAdmin( - generate_requests_only=True, - update_existing_segment_traits=TestUserConstants.TEST_USER_ADDITIONAL_SEGMENT_TRAITS, - ) - result = user_admin.alter( - "squidwrd", - traits=TestUserConstants.TEST_ALTER_USER_CSDATA_AND_OMVS_REQUEST_TRAITS, - ) - self.assertEqual( - result, - TestUserConstants.TEST_ALTER_USER_REQUEST_UPDATE_SEGMENTS_XML, - ) - # ============================================================================ # Request Builder Errors # ============================================================================ diff --git a/tests/user/test_user_result_parser.py b/tests/user/test_user_result_parser.py index c7789f98..4111ce75 100644 --- a/tests/user/test_user_result_parser.py +++ b/tests/user/test_user_result_parser.py @@ -1,5 +1,6 @@ """Test user result parser.""" +import copy import unittest from unittest.mock import Mock, patch @@ -9,6 +10,7 @@ from pyracf import ( AddOperationError, AlterOperationError, + DownstreamFatalError, SecurityRequestError, UserAdmin, ) @@ -66,6 +68,37 @@ def test_user_admin_throws_error_on_add_existing_user( + f"'{profile_name}' already exists as a '{self.user_admin._profile_type}' profile.", ) + def test_user_admin_add_surfaces_error_from_preliminary_extract( + self, + call_racf_mock: Mock, + ): + profile_name = "squidwrd" + extract_user_error_xml = ( + TestUserConstants.TEST_EXTRACT_USER_RESULT_BASE_ONLY_ERROR_XML + ) + extract_user_error_xml = extract_user_error_xml.replace( + "ICH30001I UNABLE TO LOCATE USER ENTRY SQUIDWRD", + "ICH30010I NOT AUTHORIZED TO ISSUE LISTUSER", + ) + call_racf_mock.side_effect = [ + extract_user_error_xml, + TestUserConstants.TEST_ADD_USER_RESULT_SUCCESS_XML, + ] + with self.assertRaises(SecurityRequestError) as exception: + self.user_admin.add( + profile_name, traits=TestUserConstants.TEST_ADD_USER_REQUEST_TRAITS + ) + extract_user_error_json = copy.deepcopy( + TestUserConstants.TEST_EXTRACT_USER_RESULT_BASE_ONLY_ERROR_DICTIONARY + ) + extract_user_error_json["securityResult"]["user"]["commands"][0]["messages"] = [ + "ICH30010I NOT AUTHORIZED TO ISSUE LISTUSER" + ] + self.assertEqual( + exception.exception.result, + extract_user_error_json, + ) + # Error in command, SQUIDWARD is bad USERID def test_user_admin_can_parse_add_user_error_xml( self, @@ -75,7 +108,7 @@ def test_user_admin_can_parse_add_user_error_xml( TestUserConstants.TEST_EXTRACT_USER_RESULT_BAD_ATTRIBUTE_XML, TestUserConstants.TEST_ADD_USER_RESULT_ERROR_XML, ] - with self.assertRaises(SecurityRequestError) as exception: + with self.assertRaises(DownstreamFatalError) as exception: self.user_admin.add( "squidward", traits=TestUserConstants.TEST_ADD_USER_REQUEST_TRAITS, @@ -443,24 +476,6 @@ def test_user_admin_can_parse_delete_user_error_xml( TestUserConstants.TEST_DELETE_USER_RESULT_ERROR_DICTIONARY, ) - # ============================================================================ - # Extract User with Customized Segment Traits - # ============================================================================ - def test_user_admin_can_parse_extract_user_base_omvs_csdata_success_xml( - self, - call_racf_mock: Mock, - ): - user_admin = UserAdmin( - update_existing_segment_traits=TestUserConstants.TEST_USER_REPLACE_SEGMENT_TRAITS - ) - call_racf_mock.return_value = ( - TestUserConstants.TEST_EXTRACT_USER_RESULT_BASE_OMVS_CSDATA_SUCCESS_XML - ) - self.assertEqual( - user_admin.extract("squidwrd", segments=["omvs", "csdata"]), - TestUserConstants.TEST_EXTRACT_USER_RESULT_BASE_OMVS_CSDATA_SUCCESS_DICTIONARY, - ) - # ============================================================================ # Add Additional Secrets # ============================================================================ diff --git a/tests/user/user_log_samples/alter_user_additional_secret_added_error.log b/tests/user/user_log_samples/alter_user_additional_secret_added_error.log index 4a3f324b..2e02e6c4 100644 --- a/tests/user/user_log_samples/alter_user_additional_secret_added_error.log +++ b/tests/user/user_log_samples/alter_user_additional_secret_added_error.log @@ -98,7 +98,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -161,7 +162,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -277,7 +279,8 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/user/user_log_samples/alter_user_additional_secret_added_success.log b/tests/user/user_log_samples/alter_user_additional_secret_added_success.log index 36746060..1ac51538 100644 --- a/tests/user/user_log_samples/alter_user_additional_secret_added_success.log +++ b/tests/user/user_log_samples/alter_user_additional_secret_added_success.log @@ -98,7 +98,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -161,7 +162,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -269,7 +271,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/user/user_log_samples/alter_user_error.log b/tests/user/user_log_samples/alter_user_error.log index d73d98a3..b7a36eb6 100644 --- a/tests/user/user_log_samples/alter_user_error.log +++ b/tests/user/user_log_samples/alter_user_error.log @@ -98,7 +98,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -161,7 +162,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -277,7 +279,8 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/user/user_log_samples/alter_user_passphrase_and_password_error.log b/tests/user/user_log_samples/alter_user_passphrase_and_password_error.log index bdab5be8..37da0cac 100644 --- a/tests/user/user_log_samples/alter_user_passphrase_and_password_error.log +++ b/tests/user/user_log_samples/alter_user_passphrase_and_password_error.log @@ -98,7 +98,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -161,7 +162,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -287,7 +289,8 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/user/user_log_samples/alter_user_passphrase_and_password_success.log b/tests/user/user_log_samples/alter_user_passphrase_and_password_success.log index 1f9589a3..3f062048 100644 --- a/tests/user/user_log_samples/alter_user_passphrase_and_password_success.log +++ b/tests/user/user_log_samples/alter_user_passphrase_and_password_success.log @@ -98,7 +98,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -161,7 +162,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -279,7 +281,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/user/user_log_samples/alter_user_passphrase_error.log b/tests/user/user_log_samples/alter_user_passphrase_error.log index 21a29802..49998753 100644 --- a/tests/user/user_log_samples/alter_user_passphrase_error.log +++ b/tests/user/user_log_samples/alter_user_passphrase_error.log @@ -98,7 +98,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -161,7 +162,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -282,7 +284,8 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/user/user_log_samples/alter_user_passphrase_success.log b/tests/user/user_log_samples/alter_user_passphrase_success.log index 036f42b0..822a202f 100644 --- a/tests/user/user_log_samples/alter_user_passphrase_success.log +++ b/tests/user/user_log_samples/alter_user_passphrase_success.log @@ -98,7 +98,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -161,7 +162,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -274,7 +276,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/user/user_log_samples/alter_user_password_error.log b/tests/user/user_log_samples/alter_user_password_error.log index ffc81d57..50124960 100644 --- a/tests/user/user_log_samples/alter_user_password_error.log +++ b/tests/user/user_log_samples/alter_user_password_error.log @@ -98,7 +98,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -161,7 +162,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -282,7 +284,8 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/user/user_log_samples/alter_user_password_success.log b/tests/user/user_log_samples/alter_user_password_success.log index 5b51838c..a2c61cfa 100644 --- a/tests/user/user_log_samples/alter_user_password_success.log +++ b/tests/user/user_log_samples/alter_user_password_success.log @@ -98,7 +98,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -161,7 +162,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -274,7 +276,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/user/user_log_samples/alter_user_success.log b/tests/user/user_log_samples/alter_user_success.log index 5cd64554..b6319288 100644 --- a/tests/user/user_log_samples/alter_user_success.log +++ b/tests/user/user_log_samples/alter_user_success.log @@ -98,7 +98,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -161,7 +162,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -254,7 +256,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/user/user_log_samples/extract_user_base_omvs_error.log b/tests/user/user_log_samples/extract_user_base_omvs_error.log index e88166fd..7112bf11 100644 --- a/tests/user/user_log_samples/extract_user_base_omvs_error.log +++ b/tests/user/user_log_samples/extract_user_base_omvs_error.log @@ -66,7 +66,8 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/user/user_log_samples/extract_user_base_omvs_success.log b/tests/user/user_log_samples/extract_user_base_omvs_success.log index 1e82f6e9..bf86b4e3 100644 --- a/tests/user/user_log_samples/extract_user_base_omvs_success.log +++ b/tests/user/user_log_samples/extract_user_base_omvs_success.log @@ -126,7 +126,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } @@ -202,7 +203,8 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/user/user_result_samples/add_user_result_error.json b/tests/user/user_result_samples/add_user_result_error.json index 543c008a..43f7ae97 100644 --- a/tests/user/user_result_samples/add_user_result_error.json +++ b/tests/user/user_result_samples/add_user_result_error.json @@ -8,12 +8,13 @@ "errorFunction": 10, "errorCode": 2000, "errorReason": 68, - "errorMessage": "Invalid attribute value specified.", + "textInError": "name", "errorOffset": 149, - "textInError": "name" + "errorMessage": "Invalid attribute value specified." } }, "returnCode": 2000, - "reasonCode": 68 + "reasonCode": 68, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/user/user_result_samples/add_user_result_success.json b/tests/user/user_result_samples/add_user_result_success.json index 157ce959..d9948401 100644 --- a/tests/user/user_result_samples/add_user_result_success.json +++ b/tests/user/user_result_samples/add_user_result_success.json @@ -23,6 +23,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/user/user_result_samples/alter_user_result_error.json b/tests/user/user_result_samples/alter_user_result_error.json index e387aea5..41c6bea0 100644 --- a/tests/user/user_result_samples/alter_user_result_error.json +++ b/tests/user/user_result_samples/alter_user_result_error.json @@ -22,6 +22,7 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/user/user_result_samples/alter_user_result_error_uid_secret.json b/tests/user/user_result_samples/alter_user_result_error_uid_secret.json index 4aef456c..7bfd2100 100644 --- a/tests/user/user_result_samples/alter_user_result_error_uid_secret.json +++ b/tests/user/user_result_samples/alter_user_result_error_uid_secret.json @@ -22,6 +22,7 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } diff --git a/tests/user/user_result_samples/alter_user_result_extended_success.json b/tests/user/user_result_samples/alter_user_result_extended_success.json index 926a9785..1e35ff47 100644 --- a/tests/user/user_result_samples/alter_user_result_extended_success.json +++ b/tests/user/user_result_samples/alter_user_result_extended_success.json @@ -17,6 +17,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/user/user_result_samples/alter_user_result_passphrase_and_password_error.json b/tests/user/user_result_samples/alter_user_result_passphrase_and_password_error.json index d937bb8e..1e500c26 100644 --- a/tests/user/user_result_samples/alter_user_result_passphrase_and_password_error.json +++ b/tests/user/user_result_samples/alter_user_result_passphrase_and_password_error.json @@ -22,6 +22,7 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/user/user_result_samples/alter_user_result_passphrase_and_password_success.json b/tests/user/user_result_samples/alter_user_result_passphrase_and_password_success.json index bdbb9d44..11e1006c 100644 --- a/tests/user/user_result_samples/alter_user_result_passphrase_and_password_success.json +++ b/tests/user/user_result_samples/alter_user_result_passphrase_and_password_success.json @@ -17,6 +17,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/user/user_result_samples/alter_user_result_passphrase_error.json b/tests/user/user_result_samples/alter_user_result_passphrase_error.json index 5b067f5c..ca3927c2 100644 --- a/tests/user/user_result_samples/alter_user_result_passphrase_error.json +++ b/tests/user/user_result_samples/alter_user_result_passphrase_error.json @@ -22,6 +22,7 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/user/user_result_samples/alter_user_result_passphrase_success.json b/tests/user/user_result_samples/alter_user_result_passphrase_success.json index 153669eb..b351e894 100644 --- a/tests/user/user_result_samples/alter_user_result_passphrase_success.json +++ b/tests/user/user_result_samples/alter_user_result_passphrase_success.json @@ -17,6 +17,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/user/user_result_samples/alter_user_result_password_error.json b/tests/user/user_result_samples/alter_user_result_password_error.json index b2792339..83761c1c 100644 --- a/tests/user/user_result_samples/alter_user_result_password_error.json +++ b/tests/user/user_result_samples/alter_user_result_password_error.json @@ -22,6 +22,7 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/user/user_result_samples/alter_user_result_password_success.json b/tests/user/user_result_samples/alter_user_result_password_success.json index 44dcb6a3..63945d16 100644 --- a/tests/user/user_result_samples/alter_user_result_password_success.json +++ b/tests/user/user_result_samples/alter_user_result_password_success.json @@ -17,6 +17,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/user/user_result_samples/alter_user_result_success.json b/tests/user/user_result_samples/alter_user_result_success.json index 70cc68c6..290032c6 100644 --- a/tests/user/user_result_samples/alter_user_result_success.json +++ b/tests/user/user_result_samples/alter_user_result_success.json @@ -17,6 +17,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/user/user_result_samples/delete_user_result_error.json b/tests/user/user_result_samples/delete_user_result_error.json index b18d2e08..5fbb870e 100644 --- a/tests/user/user_result_samples/delete_user_result_error.json +++ b/tests/user/user_result_samples/delete_user_result_error.json @@ -17,6 +17,7 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/user/user_result_samples/delete_user_result_success.json b/tests/user/user_result_samples/delete_user_result_success.json index d4c214bd..9b297980 100644 --- a/tests/user/user_result_samples/delete_user_result_success.json +++ b/tests/user/user_result_samples/delete_user_result_success.json @@ -14,6 +14,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/user/user_result_samples/extract_user_result_bad_attribute_error.json b/tests/user/user_result_samples/extract_user_result_bad_attribute_error.json index c15ae7cf..71233b35 100644 --- a/tests/user/user_result_samples/extract_user_result_bad_attribute_error.json +++ b/tests/user/user_result_samples/extract_user_result_bad_attribute_error.json @@ -8,12 +8,13 @@ "errorFunction": 10, "errorCode": 2000, "errorReason": 68, - "errorMessage": "Invalid attribute value specified.", + "textInError": "name", "errorOffset": 149, - "textInError": "name" + "errorMessage": "Invalid attribute value specified." } }, "returnCode": 2000, - "reasonCode": 68 + "reasonCode": 68, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/user/user_result_samples/extract_user_result_base_omvs_error.json b/tests/user/user_result_samples/extract_user_result_base_omvs_error.json index 31ea4ca0..b6c49a8c 100644 --- a/tests/user/user_result_samples/extract_user_result_base_omvs_error.json +++ b/tests/user/user_result_samples/extract_user_result_base_omvs_error.json @@ -17,6 +17,7 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/user/user_result_samples/extract_user_result_base_omvs_success.json b/tests/user/user_result_samples/extract_user_result_base_omvs_success.json index c007bcc9..42d1a19d 100644 --- a/tests/user/user_result_samples/extract_user_result_base_omvs_success.json +++ b/tests/user/user_result_samples/extract_user_result_base_omvs_success.json @@ -64,6 +64,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/user/user_result_samples/extract_user_result_base_omvs_tso_revoke_resume.json b/tests/user/user_result_samples/extract_user_result_base_omvs_tso_revoke_resume.json index c035e486..f12c6d97 100644 --- a/tests/user/user_result_samples/extract_user_result_base_omvs_tso_revoke_resume.json +++ b/tests/user/user_result_samples/extract_user_result_base_omvs_tso_revoke_resume.json @@ -76,6 +76,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/user/user_result_samples/extract_user_result_base_only_error.json b/tests/user/user_result_samples/extract_user_result_base_only_error.json index 7edd69fd..8de62923 100644 --- a/tests/user/user_result_samples/extract_user_result_base_only_error.json +++ b/tests/user/user_result_samples/extract_user_result_base_only_error.json @@ -17,6 +17,7 @@ ] }, "returnCode": 4, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/user/user_result_samples/extract_user_result_base_only_success.json b/tests/user/user_result_samples/extract_user_result_base_only_success.json index db9bb8bc..20ffb3b5 100644 --- a/tests/user/user_result_samples/extract_user_result_base_only_success.json +++ b/tests/user/user_result_samples/extract_user_result_base_only_success.json @@ -51,6 +51,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file diff --git a/tests/user/user_result_samples/extract_user_result_extra_messages_success.json b/tests/user/user_result_samples/extract_user_result_extra_messages_success.json index fec77e9c..6f836ece 100644 --- a/tests/user/user_result_samples/extract_user_result_extra_messages_success.json +++ b/tests/user/user_result_samples/extract_user_result_extra_messages_success.json @@ -51,6 +51,7 @@ ] }, "returnCode": 0, - "reasonCode": 0 + "reasonCode": 0, + "runningUserid": "testuser" } } \ No newline at end of file