Skip to content

Commit

Permalink
Added Extract to Add Function
Browse files Browse the repository at this point in the history
-Used preliminary extract to determine if add is valid (in user)
-Updated unit testing framework to account for this
-Moved checking of redacted secrets and debug logging to alter (for User) due to Add changes

Signed-off-by: Elijah Swift <elijah.swift@ibm.com>
  • Loading branch information
ElijahSwiftIBM committed Oct 25, 2023
1 parent 81e313b commit ee2df75
Show file tree
Hide file tree
Showing 60 changed files with 3,421 additions and 1,650 deletions.
1 change: 1 addition & 0 deletions pyracf/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Make security admin subclasses available from package root."""
from .access.access_admin import AccessAdmin
from .common.add_operation_error import AddOperationError
from .common.alter_operation_error import AlterOperationError
from .common.invalid_segment_name_error import InvalidSegmentNameError
from .common.invalid_segment_trait_error import InvalidSegmentTraitError
Expand Down
24 changes: 24 additions & 0 deletions pyracf/common/add_operation_error.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""Exception to use when Add operation would alter an existing profile."""


class AddOperationError(Exception):
"""
Raised when a profile passed into an Add is successfully extracted.
"""

def __init__(self, profile_name: str, class_name: str) -> None:
self.message = "Security request made to IRRSMO00 failed."
admin_types = ["USER", "GROUP", "DATASET"]
if class_name not in admin_types:
self.message += (
"\n\nTarget profile "
+ f"'{profile_name}' already exists as a profile in the {class_name} class."
)
else:
self.message += (
"\n\nTarget profile "
+ f"'{profile_name}' already exists as a {class_name} profile."
)

def __str__(self) -> str:
return self.message
2 changes: 1 addition & 1 deletion pyracf/common/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ def redact_result_xml(
match = re.search(rf"{racf_key.upper()} +\(", xml_string)
if not match:
continue
xml_string = self.__redact_string(xml_string, match.end(), ") ")
xml_string = self.__redact_string(xml_string, match.end(), ")")
return xml_string

def __colorize_json(self, json_text: str) -> str:
Expand Down
16 changes: 8 additions & 8 deletions pyracf/common/security_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def __init__(
self.__preserved_segment_traits = {}
self._trait_map = {}
self.__debug = debug
self.__generate_requests_only = generate_requests_only
self._generate_requests_only = generate_requests_only
if update_existing_segment_traits is not None:
self.__update_valid_segment_traits(update_existing_segment_traits)
if replace_existing_segment_traits is not None:
Expand Down Expand Up @@ -123,7 +123,7 @@ def _extract_and_check_result(
) -> dict:
"""Extract a RACF profile."""
result = self._make_request(security_request)
if self.__generate_requests_only:
if self._generate_requests_only:
return result
self._format_profile(result)
if self.__debug:
Expand Down Expand Up @@ -155,7 +155,7 @@ def _make_request(
security_request.dump_request_xml(encoding="utf-8"),
secret_traits=self.__secret_traits,
)
if self.__generate_requests_only:
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,
Expand Down Expand Up @@ -202,7 +202,7 @@ def _to_steps(self, results: Union[List[dict], dict, bytes]) -> Union[dict, byte
"""
if isinstance(results, dict) or isinstance(results, bytes):
results = [results]
if self.__generate_requests_only:
if self._generate_requests_only:
concatenated_xml = b""
for request_xml in results:
if request_xml:
Expand Down Expand Up @@ -320,8 +320,8 @@ def _get_profile(
self, result: Union[dict, bytes], index: int = 0
) -> Union[dict, bytes]:
"""Extract the profile section from a result dictionary."""
if self.__generate_requests_only:
# Allows this function to work with "self.__generate_requests_only" mode.
if self._generate_requests_only:
# Allows this function to work with "self._generate_requests_only" mode.
return result
return result["securityResult"][self.__profile_type]["commands"][0]["profiles"][
index
Expand All @@ -331,8 +331,8 @@ def _get_field(
self, profile: Union[dict, bytes], segment: str, field: str
) -> Union[bytes, Any, None]:
"""Extract the value of a field from a segment in a profile."""
if self.__generate_requests_only:
# Allows this function to work with "self.__generate_requests_only" mode.
if self._generate_requests_only:
# Allows this function to work with "self._generate_requests_only" mode.
return profile
try:
return profile[segment][field]
Expand Down
12 changes: 12 additions & 0 deletions pyracf/common/security_request_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,15 @@ def __init__(self, result: dict) -> None:

def __str__(self) -> str:
return self.message

def scan_for_error(self, security_definition_tag: str, error_id: str):
commands = self.result["securityResult"][security_definition_tag].get(
"commands"
)
if not isinstance(commands, list):
return False
messages = commands[0].get("messages", [])
if error_id in "".join(messages):
return True
else:
return False
2 changes: 1 addition & 1 deletion pyracf/setropts/setropts_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def get_class_attributes(self, class_name: str) -> Union[list, bytes]:
"""Get RACF get attributes."""
profile = self.list_racf_options(options_only=True)
if not isinstance(profile, dict):
# Allows this function to work with "self.__generate_requests_only" mode.
# Allows this function to work with "self._generate_requests_only" mode.
return profile
return [
class_type
Expand Down
25 changes: 21 additions & 4 deletions pyracf/user/user_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from typing import List, Union

from pyracf.common.add_operation_error import AddOperationError
from pyracf.common.alter_operation_error import AlterOperationError
from pyracf.common.security_admin import SecurityAdmin
from pyracf.common.security_request_error import SecurityRequestError
Expand Down Expand Up @@ -399,13 +400,29 @@ def set_omvs_program(
# ============================================================================
def add(self, userid: str, traits: dict = {}) -> Union[dict, bytes]:
"""Create a new user."""
self._build_segment_dictionaries(traits)
user_request = UserRequest(userid, "set")
self._build_xml_segments(user_request)
return self._make_request(user_request)
if self._generate_requests_only:
self._build_segment_dictionaries(traits)
user_request = UserRequest(userid, "set")
self._build_xml_segments(user_request)
return self._make_request(user_request)
try:
self.extract(userid)
except SecurityRequestError as exception:
if not exception.scan_for_error("user", "ICH30001I"):
raise exception
self._build_segment_dictionaries(traits)
user_request = UserRequest(userid, "set")
self._build_xml_segments(user_request)
return self._make_request(user_request)
raise AddOperationError(userid, "USER")

def alter(self, userid: str, traits: dict = {}) -> Union[dict, bytes]:
"""Alter an existing user."""
if self._generate_requests_only:
self._build_segment_dictionaries(traits)
user_request = UserRequest(userid, "set")
self._build_xml_segments(user_request, alter=True)
return self._make_request(user_request, irrsmo00_precheck=True)
try:
self.extract(userid)
except SecurityRequestError:
Expand Down
8 changes: 4 additions & 4 deletions tests/group/test_group_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ def get_sample(sample_file: str) -> Union[str, bytes]:
TEST_EXTRACT_GROUP_RESULT_BASE_OMVS_ERROR_DICTIONARY = get_sample(
"extract_group_result_base_omvs_error.json"
)
TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_NO_OMVS_SUCCESS_XML = get_sample(
"extract_group_result_base_only_no_omvs_success.xml"
TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_SUCCESS_XML = get_sample(
"extract_group_result_base_only_success.xml"
)
TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_NO_OMVS_SUCCESS_JSON = get_sample(
"extract_group_result_base_only_no_omvs_success.json"
TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_SUCCESS_JSON = get_sample(
"extract_group_result_base_only_success.json"
)

# Delete Group
Expand Down
16 changes: 8 additions & 8 deletions tests/group/test_group_getters.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def test_group_admin_has_group_special_authority_returns_true_when_group_special
call_racf_mock: Mock,
):
call_racf_mock.return_value = (
TestGroupConstants.TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_NO_OMVS_SUCCESS_XML
TestGroupConstants.TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_SUCCESS_XML
)
self.assertTrue(
self.group_admin.has_group_special_authority("TESTGRP0", "ESWIFT")
Expand Down Expand Up @@ -63,7 +63,7 @@ def test_group_admin_has_group_operations_authority_returns_true_when_group_oper
call_racf_mock: Mock,
):
call_racf_mock.return_value = (
TestGroupConstants.TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_NO_OMVS_SUCCESS_XML
TestGroupConstants.TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_SUCCESS_XML
)
self.assertTrue(
self.group_admin.has_group_operations_authority("TESTGRP0", "LEONARD")
Expand Down Expand Up @@ -99,7 +99,7 @@ def test_group_admin_has_group_auditor_authority_returns_true_when_group_auditor
call_racf_mock: Mock,
):
group_extract_auditor = (
TestGroupConstants.TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_NO_OMVS_SUCCESS_XML
TestGroupConstants.TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_SUCCESS_XML
)
group_extract_auditor = group_extract_auditor.replace(
"<message> CONNECT ATTRIBUTES=SPECIAL</message>",
Expand All @@ -115,7 +115,7 @@ def test_group_admin_has_group_auditor_authority_returns_false_when_not_group_au
call_racf_mock: Mock,
):
call_racf_mock.return_value = (
TestGroupConstants.TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_NO_OMVS_SUCCESS_XML
TestGroupConstants.TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_SUCCESS_XML
)
self.assertFalse(
self.group_admin.has_group_auditor_authority("TESTGRP0", "ESWIFT")
Expand All @@ -140,7 +140,7 @@ def test_group_admin_has_group_access_attribute_returns_true_when_grpacc(
call_racf_mock: Mock,
):
group_extract_grpacc = (
TestGroupConstants.TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_NO_OMVS_SUCCESS_XML
TestGroupConstants.TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_SUCCESS_XML
)
group_extract_grpacc = group_extract_grpacc.replace(
"<message> CONNECT ATTRIBUTES=OPERATIONS</message>",
Expand All @@ -156,7 +156,7 @@ def test_group_admin_has_group_access_attribute_returns_false_when_not_grpacc(
call_racf_mock: Mock,
):
call_racf_mock.return_value = (
TestGroupConstants.TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_NO_OMVS_SUCCESS_XML
TestGroupConstants.TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_SUCCESS_XML
)
self.assertFalse(
self.group_admin.has_group_access_attribute("TESTGRP0", "LEONARD")
Expand Down Expand Up @@ -201,7 +201,7 @@ def test_group_admin_get_omvs_gid_returns_none_when_no_omvs_segment_exists(
call_racf_mock: Mock,
):
call_racf_mock.return_value = (
TestGroupConstants.TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_NO_OMVS_SUCCESS_XML
TestGroupConstants.TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_SUCCESS_XML
)
self.assertIsNone(self.group_admin.get_omvs_gid("TESTGRP0"))

Expand Down Expand Up @@ -237,6 +237,6 @@ def test_group_admin_get_ovm_gid_returns_none_when_no_ovm_segment_exists(
call_racf_mock: Mock,
):
call_racf_mock.return_value = (
TestGroupConstants.TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_NO_OMVS_SUCCESS_XML
TestGroupConstants.TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_SUCCESS_XML
)
self.assertIsNone(self.group_admin.get_ovm_gid("TESTGRP0"))
10 changes: 5 additions & 5 deletions tests/group/test_group_result_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def test_group_admin_can_parse_alter_group_success_xml(
call_racf_mock: Mock,
):
call_racf_mock.side_effect = [
TestGroupConstants.TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_NO_OMVS_SUCCESS_XML,
TestGroupConstants.TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_SUCCESS_XML,
TestGroupConstants.TEST_ALTER_GROUP_RESULT_SUCCESS_XML,
]
self.assertEqual(
Expand Down Expand Up @@ -96,7 +96,7 @@ def test_group_admin_can_parse_alter_group_error_xml(
call_racf_mock: Mock,
):
call_racf_mock.side_effect = [
TestGroupConstants.TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_NO_OMVS_SUCCESS_XML,
TestGroupConstants.TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_SUCCESS_XML,
TestGroupConstants.TEST_ALTER_GROUP_RESULT_ERROR_XML,
]
with self.assertRaises(SecurityRequestError) as exception:
Expand Down Expand Up @@ -124,16 +124,16 @@ def test_group_admin_can_parse_extract_group_base_omvs_success_xml(
TestGroupConstants.TEST_EXTRACT_GROUP_RESULT_BASE_OMVS_SUCCESS_DICTIONARY,
)

def test_group_admin_can_parse_extract_group_base_only_no_omvs_success_xml(
def test_group_admin_can_parse_extract_group_base_only_success_xml(
self,
call_racf_mock: Mock,
):
call_racf_mock.return_value = (
TestGroupConstants.TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_NO_OMVS_SUCCESS_XML
TestGroupConstants.TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_SUCCESS_XML
)
self.assertEqual(
self.group_admin.extract("TESTGRP0"),
TestGroupConstants.TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_NO_OMVS_SUCCESS_JSON,
TestGroupConstants.TEST_EXTRACT_GROUP_RESULT_BASE_ONLY_SUCCESS_JSON,
)

# Error in environment, TESTGRP0 already deleted/not added
Expand Down
Loading

0 comments on commit ee2df75

Please sign in to comment.