diff --git a/pyproject.toml b/pyproject.toml index 17bc8f0b..f8b4ab15 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ [tool.poetry] name="pyracf" - version="1.0b3" + version="1.0b4" description="Python interface to RACF using IRRSMO00 RACF Callable Service." license = "Apache-2.0" authors = [ diff --git a/pyracf/common/security_admin.py b/pyracf/common/security_admin.py index 6a87e5d8..4ad7c086 100644 --- a/pyracf/common/security_admin.py +++ b/pyracf/common/security_admin.py @@ -465,27 +465,9 @@ def __format_data_set_generic_profile_data( and messages[i + 1] is not None and ("-" in messages[i + 1]) ): - field = " ".join( - [ - txt.lower().strip() - for txt in list(filter(None, messages[i].split(" "))) - ] + self.__format_tabular_data( + messages, profile, current_segment, i, list_fields ) - field = self._profile_field_to_camel_case(current_segment, field) - value = messages[i + 2] - if "(" in value: - value_tokens = value.split("(") - subfield = self._profile_field_to_camel_case( - current_segment, value_tokens[0].lower() - ) - profile[current_segment][field] = { - subfield: self._clean_and_separate(value_tokens[-1].rstrip(")")) - } - elif field in list_fields: - profile[current_segment][field] = [] - profile[current_segment][field].append(self._clean_and_separate(value)) - else: - profile[current_segment][field] = self._clean_and_separate(value) i += 1 elif "NO INSTALLATION DATA" in messages[i]: profile[current_segment]["installationData"] = None @@ -668,6 +650,44 @@ def __format_semi_tabular_data( messages[i + 2][indexes[j] : ind_e1] ) + def __format_tabular_data( + self, + messages: List[str], + profile: dict, + current_segment: str, + i: int, + list_fields: List[str] = ["volumes"], + ) -> None: + field = " ".join( + [txt.lower().strip() for txt in list(filter(None, messages[i].split(" ")))] + ) + field = self._profile_field_to_camel_case(current_segment, field) + values = ( + [messages[i + 2]] + if "," not in messages[i + 2] + else messages[i + 2].split(",") + ) + for value in values: + if "(" in value: + value_tokens = value.split("(") + subfield = self._profile_field_to_camel_case( + current_segment, value_tokens[0].lower() + ) + if field not in profile[current_segment]: + profile[current_segment][field] = { + subfield: self._clean_and_separate(value_tokens[-1].rstrip(")")) + } + else: + profile[current_segment][field][ + subfield + ] = self._clean_and_separate(value_tokens[-1].rstrip(")")) + elif field in list_fields: + profile[current_segment][field] = [] + profile[current_segment][field].append(self._clean_and_separate(value)) + else: + profile[current_segment][field] = self._clean_and_separate(value) + return + def __add_key_value_pairs_to_segment( self, segment_name: str, diff --git a/pyracf/data_set/data_set_admin.py b/pyracf/data_set/data_set_admin.py index 2b1df499..940a5763 100644 --- a/pyracf/data_set/data_set_admin.py +++ b/pyracf/data_set/data_set_admin.py @@ -142,8 +142,8 @@ def alter( profile = self.extract( data_set, volume=volume, generic=generic, profile_only=True ) - except SecurityRequestError: - raise AlterOperationError(data_set, self._profile_type) + except SecurityRequestError as exception: + raise AlterOperationError(data_set, self._profile_type) from exception if not self._get_field(profile, "base", "name") == data_set.lower(): raise AlterOperationError(data_set, self._profile_type) self._build_segment_trait_dictionary(traits) diff --git a/pyracf/group/group_admin.py b/pyracf/group/group_admin.py index bd78851d..8a3514d8 100644 --- a/pyracf/group/group_admin.py +++ b/pyracf/group/group_admin.py @@ -149,8 +149,8 @@ def alter(self, group: str, traits: dict) -> Union[dict, bytes]: return self._make_request(group_request, irrsmo00_precheck=True) try: self.extract(group) - except SecurityRequestError: - raise AlterOperationError(group, self._profile_type) + except SecurityRequestError as exception: + raise AlterOperationError(group, self._profile_type) from exception self._build_segment_trait_dictionary(traits) group_request = GroupRequest(group, "set") self._build_xml_segments(group_request, alter=True) diff --git a/pyracf/resource/resource_admin.py b/pyracf/resource/resource_admin.py index 87d6cd64..27219b92 100644 --- a/pyracf/resource/resource_admin.py +++ b/pyracf/resource/resource_admin.py @@ -1,5 +1,6 @@ """General Resource Profile Administration.""" +from collections import Counter from typing import List, Union from pyracf.common.add_operation_error import AddOperationError @@ -25,7 +26,7 @@ def __init__( self._valid_segment_traits = { "base": { "base:application_data": "racf:appldata", - "base:audit_alter:": "racf:audaltr", + "base:audit_alter": "racf:audaltr", "base:audit_control": "racf:audcntl", "base:audit_none": "racf:audnone", "base:audit_read": "racf:audread", @@ -285,6 +286,133 @@ def get_user_access( self.set_running_userid(original_userid) return self._get_field(profile, "base", "yourAccess") + # ============================================================================ + # Auditing Rules + # ============================================================================ + def get_audit_rules( + self, resource: str, class_name: str + ) -> Union[dict, bytes, None]: + """Get the auditing rules associated with this general resource profile.""" + profile = self.extract(resource, class_name, profile_only=True) + return self._get_field(profile, "base", "auditing") + + def overwrite_audit_rules_by_attempt( + self, + resource: str, + class_name: str, + success: Union[str, None] = None, + failure: Union[str, None] = None, + all: Union[str, None] = None, + ) -> Union[dict, bytes]: + """ + Overwrites the auditing rules for this general resource profile with new + rules to audit based on specified access attempts. + """ + self.__validate_access_levels(success, failure, all) + traits = {} + if success is not None: + traits[f"base:audit_{success}"] = "success" + if failure is not None: + traits[f"base:audit_{failure}"] = "failure" + if all is not None: + traits[f"base:audit_{all}"] = "all" + result = self.alter(resource, class_name, traits=traits) + return self._to_steps(result) + + def alter_audit_rules_by_attempt( + self, + resource: str, + class_name: str, + success: Union[str, None] = None, + failure: Union[str, None] = None, + all: Union[str, None] = None, + ) -> Union[dict, bytes]: + """ + Alters the auditing rules for this general resource profile with new rules + to audit by access level, preserving existing non-conflicting rules. + """ + self.__validate_access_levels(success, failure, all) + audit_rules = self.get_audit_rules(resource, class_name) + traits = {} + if "success" in audit_rules: + traits[f"base:audit_{audit_rules['success']}"] = "success" + if "failures" in audit_rules: + traits[f"base:audit_{audit_rules['failures']}"] = "failure" + if "all" in audit_rules: + traits[f"base:audit_{audit_rules['all']}"] = "all" + if success is not None: + traits[f"base:audit_{success}"] = "success" + if failure is not None: + traits[f"base:audit_{failure}"] = "failure" + if all is not None: + traits[f"base:audit_{all}"] = "all" + result = self.alter(resource, class_name, traits=traits) + return self._to_steps(result) + + def overwrite_audit_rules_by_access_level( + self, + resource: str, + class_name: str, + alter: Union[str, None] = None, + control: Union[str, None] = None, + read: Union[str, None] = None, + update: Union[str, None] = None, + ) -> Union[dict, bytes]: + """ + Overwrites the auditing rules for this general resource profile with new + rules to audit based on specified access levels. + """ + traits = {} + if alter is not None: + traits["base:audit_alter"] = alter + if control is not None: + traits["base:audit_control"] = control + if read is not None: + traits["base:audit_read"] = read + if update is not None: + traits["base:audit_update"] = update + result = self.alter(resource, class_name, traits=traits) + return self._to_steps(result) + + def alter_audit_rules_by_access_level( + self, + resource: str, + class_name: str, + alter: Union[str, None] = None, + control: Union[str, None] = None, + read: Union[str, None] = None, + update: Union[str, None] = None, + ) -> Union[dict, bytes]: + """ + Alters the auditing rules for this general resource profile with a new + rule to audit alter access, preserving existing non-conflicting rules. + """ + audit_rules = self.get_audit_rules(resource, class_name) + traits = {} + if "success" in audit_rules: + traits[f"base:audit_{audit_rules['success']}"] = "success" + if "failures" in audit_rules: + traits[f"base:audit_{audit_rules['failures']}"] = "failure" + if "all" in audit_rules: + traits[f"base:audit_{audit_rules['all']}"] = "all" + if alter is not None: + traits["base:audit_alter"] = alter + if control is not None: + traits["base:audit_control"] = control + if read is not None: + traits["base:audit_read"] = read + if update is not None: + traits["base:audit_update"] = update + result = self.alter(resource, class_name, traits=traits) + return self._to_steps(result) + + def remove_all_audit_rules( + self, resource: str, class_name: str + ) -> Union[dict, bytes]: + """Clears the auditing rules completely.""" + result = self.alter(resource, class_name, {"base:audit_none": True}) + return self._to_steps(result) + # ============================================================================ # Class Administration # ============================================================================ @@ -527,8 +655,8 @@ def alter(self, resource: str, class_name: str, traits: dict) -> Union[dict, byt return self._make_request(profile_request, irrsmo00_precheck=True) try: profile = self.extract(resource, class_name, profile_only=True) - except SecurityRequestError: - raise AlterOperationError(resource, class_name) + except SecurityRequestError as exception: + raise AlterOperationError(resource, class_name) from exception if not self._get_field(profile, "base", "name") == resource.lower(): raise AlterOperationError(resource, class_name) self._build_segment_trait_dictionary(traits) @@ -589,3 +717,61 @@ def _format_profile(self, result: dict) -> None: del result["securityResult"]["resource"]["commands"][0]["messages"] result["securityResult"]["resource"]["commands"][0]["profiles"] = profiles + + def __validate_access_levels( + self, + success: Union[str, None] = None, + failure: Union[str, None] = None, + all: Union[str, None] = None, + ): + valid_access_levels = ("alter", "control", "read", "update") + value_error_text = ( + "Valid access levels include 'alter', 'control', 'read', and 'update'." + ) + bad_access_levels = [] + for attempt_argument in (success, failure, all): + if ( + attempt_argument is not None + and str(attempt_argument).lower() not in valid_access_levels + ): + bad_access_levels.append(attempt_argument) + match len(bad_access_levels): + case 0: + self.__check_for_duplicates([success, failure, all], "Access Level") + return + case 1: + value_error_text = ( + f"'{bad_access_levels[0]}' is not a valid access level. " + + f"{value_error_text}" + ) + case 2: + value_error_text = ( + f"'{bad_access_levels[0]}' and '{bad_access_levels[1]}' are not valid " + + f"access levels. {value_error_text}" + ) + case _: + bad_access_levels = [ + f"'{bad_access_level}'" for bad_access_level in bad_access_levels + ] + bad_access_levels[-1] = f"and {bad_access_levels[-1]} " + value_error_text = ( + f"{', '.join(bad_access_levels)}are not valid access levels. " + + f"{value_error_text}" + ) + raise ValueError(value_error_text) + + def __check_for_duplicates(self, argument_list: list, argument: str) -> None: + duplicates = [ + key + for (key, value) in Counter(argument_list).items() + if (value > 1 and key is not None) + ] + if duplicates == []: + return + value_error_text = [] + for duplicate in duplicates: + value_error_text.append( + f"'{duplicate}' is provided as an '{argument}' multiple times, which is not " + + "allowed." + ) + raise ValueError("\n".join(value_error_text)) diff --git a/pyracf/user/user_admin.py b/pyracf/user/user_admin.py index 6d1deb9a..ed5810b0 100644 --- a/pyracf/user/user_admin.py +++ b/pyracf/user/user_admin.py @@ -277,12 +277,13 @@ def take_away_auditor_authority(self, userid: str) -> Union[dict, bytes]: # Password # ============================================================================ def set_password( - self, - userid: str, - password: Union[str, bool], + self, userid: str, password: Union[str, bool], expired: Union[bool, None] = None ) -> Union[dict, bytes]: """Set a user's password.""" - result = self.alter(userid, traits={"base:password": password}) + traits = {"base:password": password} + if expired is not None: + traits["base:password_expired"] = expired + result = self.alter(userid, traits=traits) return self._to_steps(result) # ============================================================================ @@ -292,9 +293,13 @@ def set_passphrase( self, userid: str, passphrase: Union[str, bool], + expired: Union[bool, None] = None, ) -> Union[dict, bytes]: """Set a user's passphrase.""" - result = self.alter(userid, traits={"base:passphrase": passphrase}) + traits = {"base:passphrase": passphrase} + if expired is not None: + traits["base:password_expired"] = expired + result = self.alter(userid, traits=traits) return self._to_steps(result) # ============================================================================ @@ -795,8 +800,8 @@ def alter(self, userid: str, traits: dict) -> Union[dict, bytes]: return self._make_request(user_request, irrsmo00_precheck=True) try: self.extract(userid) - except SecurityRequestError: - raise AlterOperationError(userid, self._profile_type) + except SecurityRequestError as exception: + raise AlterOperationError(userid, self._profile_type) from exception self._build_segment_trait_dictionary(traits) user_request = UserRequest(userid, "set") self._build_xml_segments(user_request, alter=True) diff --git a/tests/resource/resource_log_samples/alter_resource_error.log b/tests/resource/resource_log_samples/alter_resource_error.log index cb8b9b79..3f9851ed 100644 --- a/tests/resource/resource_log_samples/alter_resource_error.log +++ b/tests/resource/resource_log_samples/alter_resource_error.log @@ -48,7 +48,7 @@ AUDITING -------- - FAILURES(READ) + SUCCESS(UPDATE),FAILURES(READ) NOTIFY ------ @@ -97,7 +97,7 @@ " ", "AUDITING", "--------", - "FAILURES(READ)", + "SUCCESS(UPDATE),FAILURES(READ)", " ", "NOTIFY", "------", @@ -144,6 +144,7 @@ "installationData": null, "applicationData": null, "auditing": { + "success": "update", "failures": "read" }, "notify": null, diff --git a/tests/resource/resource_log_samples/alter_resource_success.log b/tests/resource/resource_log_samples/alter_resource_success.log index 9a960990..a7996b5b 100644 --- a/tests/resource/resource_log_samples/alter_resource_success.log +++ b/tests/resource/resource_log_samples/alter_resource_success.log @@ -48,7 +48,7 @@ AUDITING -------- - FAILURES(READ) + SUCCESS(UPDATE),FAILURES(READ) NOTIFY ------ @@ -97,7 +97,7 @@ " ", "AUDITING", "--------", - "FAILURES(READ)", + "SUCCESS(UPDATE),FAILURES(READ)", " ", "NOTIFY", "------", @@ -144,6 +144,7 @@ "installationData": null, "applicationData": null, "auditing": { + "success": "update", "failures": "read" }, "notify": null, 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 b521d899..dff9502e 100644 --- a/tests/resource/resource_log_samples/extract_resource_base_success.log +++ b/tests/resource/resource_log_samples/extract_resource_base_success.log @@ -48,7 +48,7 @@ AUDITING -------- - FAILURES(READ) + SUCCESS(UPDATE),FAILURES(READ) NOTIFY ------ @@ -97,7 +97,7 @@ " ", "AUDITING", "--------", - "FAILURES(READ)", + "SUCCESS(UPDATE),FAILURES(READ)", " ", "NOTIFY", "------", @@ -144,6 +144,7 @@ "installationData": null, "applicationData": null, "auditing": { + "success": "update", "failures": "read" }, "notify": null, diff --git a/tests/resource/resource_request_samples/resource_alter_audit_rules_by_access_level.xml b/tests/resource/resource_request_samples/resource_alter_audit_rules_by_access_level.xml new file mode 100644 index 00000000..f6d266a6 --- /dev/null +++ b/tests/resource/resource_request_samples/resource_alter_audit_rules_by_access_level.xml @@ -0,0 +1,9 @@ + + + + success + failure + success + + + \ No newline at end of file diff --git a/tests/resource/resource_request_samples/resource_alter_audit_rules_by_access_level_all.xml b/tests/resource/resource_request_samples/resource_alter_audit_rules_by_access_level_all.xml new file mode 100644 index 00000000..b0384301 --- /dev/null +++ b/tests/resource/resource_request_samples/resource_alter_audit_rules_by_access_level_all.xml @@ -0,0 +1,10 @@ + + + + all + all + success + failure + + + \ No newline at end of file diff --git a/tests/resource/resource_request_samples/resource_alter_audit_rules_by_access_level_multiple.xml b/tests/resource/resource_request_samples/resource_alter_audit_rules_by_access_level_multiple.xml new file mode 100644 index 00000000..f985b460 --- /dev/null +++ b/tests/resource/resource_request_samples/resource_alter_audit_rules_by_access_level_multiple.xml @@ -0,0 +1,10 @@ + + + + success + failure + success + failure + + + \ No newline at end of file diff --git a/tests/resource/resource_request_samples/resource_alter_audit_rules_by_access_level_none.xml b/tests/resource/resource_request_samples/resource_alter_audit_rules_by_access_level_none.xml new file mode 100644 index 00000000..b0abbd6a --- /dev/null +++ b/tests/resource/resource_request_samples/resource_alter_audit_rules_by_access_level_none.xml @@ -0,0 +1,8 @@ + + + + success + all + + + \ No newline at end of file diff --git a/tests/resource/resource_request_samples/resource_alter_audit_rules_by_attempt.xml b/tests/resource/resource_request_samples/resource_alter_audit_rules_by_attempt.xml new file mode 100644 index 00000000..c9ecc085 --- /dev/null +++ b/tests/resource/resource_request_samples/resource_alter_audit_rules_by_attempt.xml @@ -0,0 +1,9 @@ + + + + success + failure + failure + + + \ No newline at end of file diff --git a/tests/resource/resource_request_samples/resource_alter_audit_rules_by_attempt_all.xml b/tests/resource/resource_request_samples/resource_alter_audit_rules_by_attempt_all.xml new file mode 100644 index 00000000..d2deb5b1 --- /dev/null +++ b/tests/resource/resource_request_samples/resource_alter_audit_rules_by_attempt_all.xml @@ -0,0 +1,10 @@ + + + + success + all + success + failure + + + \ No newline at end of file diff --git a/tests/resource/resource_request_samples/resource_alter_audit_rules_by_attempt_multiple.xml b/tests/resource/resource_request_samples/resource_alter_audit_rules_by_attempt_multiple.xml new file mode 100644 index 00000000..205f817d --- /dev/null +++ b/tests/resource/resource_request_samples/resource_alter_audit_rules_by_attempt_multiple.xml @@ -0,0 +1,9 @@ + + + + success + all + failure + + + \ No newline at end of file diff --git a/tests/resource/resource_request_samples/resource_alter_audit_rules_by_attempt_none.xml b/tests/resource/resource_request_samples/resource_alter_audit_rules_by_attempt_none.xml new file mode 100644 index 00000000..b0abbd6a --- /dev/null +++ b/tests/resource/resource_request_samples/resource_alter_audit_rules_by_attempt_none.xml @@ -0,0 +1,8 @@ + + + + success + all + + + \ No newline at end of file diff --git a/tests/resource/resource_request_samples/resource_overwrite_audit_rules_by_access_level.xml b/tests/resource/resource_request_samples/resource_overwrite_audit_rules_by_access_level.xml new file mode 100644 index 00000000..a7aaa608 --- /dev/null +++ b/tests/resource/resource_request_samples/resource_overwrite_audit_rules_by_access_level.xml @@ -0,0 +1,7 @@ + + + + success + + + \ No newline at end of file diff --git a/tests/resource/resource_request_samples/resource_overwrite_audit_rules_by_access_level_all.xml b/tests/resource/resource_request_samples/resource_overwrite_audit_rules_by_access_level_all.xml new file mode 100644 index 00000000..a7cd9040 --- /dev/null +++ b/tests/resource/resource_request_samples/resource_overwrite_audit_rules_by_access_level_all.xml @@ -0,0 +1,10 @@ + + + + success + all + failure + success + + + \ No newline at end of file diff --git a/tests/resource/resource_request_samples/resource_overwrite_audit_rules_by_access_level_multiple.xml b/tests/resource/resource_request_samples/resource_overwrite_audit_rules_by_access_level_multiple.xml new file mode 100644 index 00000000..0e26b296 --- /dev/null +++ b/tests/resource/resource_request_samples/resource_overwrite_audit_rules_by_access_level_multiple.xml @@ -0,0 +1,8 @@ + + + + success + all + + + \ No newline at end of file diff --git a/tests/resource/resource_request_samples/resource_overwrite_audit_rules_by_access_level_none.xml b/tests/resource/resource_request_samples/resource_overwrite_audit_rules_by_access_level_none.xml new file mode 100644 index 00000000..ae369316 --- /dev/null +++ b/tests/resource/resource_request_samples/resource_overwrite_audit_rules_by_access_level_none.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/tests/resource/resource_request_samples/resource_overwrite_audit_rules_by_attempt.xml b/tests/resource/resource_request_samples/resource_overwrite_audit_rules_by_attempt.xml new file mode 100644 index 00000000..6d5fd2f8 --- /dev/null +++ b/tests/resource/resource_request_samples/resource_overwrite_audit_rules_by_attempt.xml @@ -0,0 +1,7 @@ + + + + failure + + + \ No newline at end of file diff --git a/tests/resource/resource_request_samples/resource_overwrite_audit_rules_by_attempt_all.xml b/tests/resource/resource_request_samples/resource_overwrite_audit_rules_by_attempt_all.xml new file mode 100644 index 00000000..ee7d1047 --- /dev/null +++ b/tests/resource/resource_request_samples/resource_overwrite_audit_rules_by_attempt_all.xml @@ -0,0 +1,9 @@ + + + + success + failure + all + + + \ No newline at end of file diff --git a/tests/resource/resource_request_samples/resource_overwrite_audit_rules_by_attempt_multiple.xml b/tests/resource/resource_request_samples/resource_overwrite_audit_rules_by_attempt_multiple.xml new file mode 100644 index 00000000..e6933d07 --- /dev/null +++ b/tests/resource/resource_request_samples/resource_overwrite_audit_rules_by_attempt_multiple.xml @@ -0,0 +1,8 @@ + + + + success + all + + + \ No newline at end of file diff --git a/tests/resource/resource_request_samples/resource_overwrite_audit_rules_by_attempt_none.xml b/tests/resource/resource_request_samples/resource_overwrite_audit_rules_by_attempt_none.xml new file mode 100644 index 00000000..ae369316 --- /dev/null +++ b/tests/resource/resource_request_samples/resource_overwrite_audit_rules_by_attempt_none.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/tests/resource/resource_request_samples/resource_remove_all_audit_rules.xml b/tests/resource/resource_request_samples/resource_remove_all_audit_rules.xml new file mode 100644 index 00000000..8aaac822 --- /dev/null +++ b/tests/resource/resource_request_samples/resource_remove_all_audit_rules.xml @@ -0,0 +1,7 @@ + + + + + + + \ 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 0a07ed06..15afc457 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 @@ -24,6 +24,7 @@ "installationData": null, "applicationData": null, "auditing": { + "success": "update", "failures": "read" }, "notify": null, diff --git a/tests/resource/resource_result_samples/extract_resource_result_base_success.xml b/tests/resource/resource_result_samples/extract_resource_result_base_success.xml index 92f4559f..58c17dfc 100644 --- a/tests/resource/resource_result_samples/extract_resource_result_base_success.xml +++ b/tests/resource/resource_result_samples/extract_resource_result_base_success.xml @@ -24,7 +24,7 @@ AUDITING -------- - FAILURES(READ) + SUCCESS(UPDATE),FAILURES(READ) NOTIFY ------ diff --git a/tests/resource/test_resource_constants.py b/tests/resource/test_resource_constants.py index fe0026ba..a1448ccd 100644 --- a/tests/resource/test_resource_constants.py +++ b/tests/resource/test_resource_constants.py @@ -111,6 +111,67 @@ def get_sample(sample_file: str) -> Union[str, bytes]: # ============================================================================ TEST_RESOURCE_SET_UNIVERSAL_ACCESS_XML = get_sample("resource_set_universal_access.xml") +# Audit Rules Request Samples +TEST_RESOURCE_REMOVE_ALL_AUDIT_RULES_REQUEST_XML = get_sample( + "resource_remove_all_audit_rules.xml" +) +TEST_RESOURCE_ALTER_AUDIT_RULES_BY_ATTEMPT_REQUEST_XML = get_sample( + "resource_alter_audit_rules_by_attempt.xml" +) +TEST_RESOURCE_ALTER_AUDIT_RULES_BY_ATTEMPT_MULT_REQUEST_XML = get_sample( + "resource_alter_audit_rules_by_attempt_multiple.xml" +) +TEST_RESOURCE_ALTER_AUDIT_RULES_BY_ATTEMPT_ALL_REQUEST_XML = get_sample( + "resource_alter_audit_rules_by_attempt_all.xml" +) +TEST_RESOURCE_ALTER_AUDIT_RULES_BY_ATTEMPT_NONE_REQUEST_XML = get_sample( + "resource_alter_audit_rules_by_attempt_none.xml" +) +TEST_RESOURCE_ALTER_AUDIT_RULES_BY_ACCESS_LEVEL_REQUEST_XML = get_sample( + "resource_alter_audit_rules_by_access_level.xml" +) +TEST_RESOURCE_ALTER_AUDIT_RULES_BY_ACCESS_LEVEL_MULT_REQUEST_XML = get_sample( + "resource_alter_audit_rules_by_access_level_multiple.xml" +) +TEST_RESOURCE_ALTER_AUDIT_RULES_BY_ACCESS_LEVEL_ALL_REQUEST_XML = get_sample( + "resource_alter_audit_rules_by_access_level_all.xml" +) +TEST_RESOURCE_ALTER_AUDIT_RULES_BY_ACCESS_LEVEL_NONE_REQUEST_XML = get_sample( + "resource_alter_audit_rules_by_access_level_none.xml" +) +TEST_RESOURCE_OVERWRITE_AUDIT_RULES_BY_ATTEMPT_REQUEST_XML = get_sample( + "resource_overwrite_audit_rules_by_attempt.xml" +) +TEST_RESOURCE_OVERWRITE_AUDIT_RULES_BY_ATTEMPT_MULT_REQUEST_XML = get_sample( + "resource_overwrite_audit_rules_by_attempt_multiple.xml" +) +TEST_RESOURCE_OVERWRITE_AUDIT_RULES_BY_ATTEMPT_ALL_REQUEST_XML = get_sample( + "resource_overwrite_audit_rules_by_attempt_all.xml" +) +TEST_RESOURCE_OVERWRITE_AUDIT_RULES_BY_ATTEMPT_NONE_REQUEST_XML = get_sample( + "resource_overwrite_audit_rules_by_attempt_none.xml" +) +TEST_RESOURCE_OVERWRITE_AUDIT_RULES_BY_ACCESS_LEVEL_REQUEST_XML = get_sample( + "resource_overwrite_audit_rules_by_access_level.xml" +) +# The following Test variables break convention to avoid E501 length errors from Flake8 +TEST_RESOURCE_OVERWRITE_AUDIT_BY_ACCESS_LEVEL_MULT_REQUEST_XML = get_sample( + "resource_overwrite_audit_rules_by_access_level_multiple.xml" +) +TEST_RESOURCE_OVERWRITE_AUDIT_BY_ACCESS_LEVEL_ALL_REQUEST_XML = get_sample( + "resource_overwrite_audit_rules_by_access_level_all.xml" +) +TEST_RESOURCE_OVERWRITE_AUDIT_BY_ACCESS_LEVEL_NONE_REQUEST_XML = get_sample( + "resource_overwrite_audit_rules_by_access_level_none.xml" +) + +# ============================================================================ +# Resource Administration Getters Result Data +# ============================================================================ + +TEST_GET_AUDIT_RULES = {"success": "update", "failures": "read"} +TEST_GET_AUDIT_RULES_SINGLE = {"failures": "read"} +TEST_GET_AUDIT_RULES_WITH_ALL = {"success": "update", "all": "read"} # ============================================================================ # Debug Logging diff --git a/tests/resource/test_resource_getters.py b/tests/resource/test_resource_getters.py index 4f321ed1..e5fe54c9 100644 --- a/tests/resource/test_resource_getters.py +++ b/tests/resource/test_resource_getters.py @@ -60,7 +60,10 @@ def test_resource_admin_get_universal_access_raises_an_exception_when_extract_fa with self.assertRaises(SecurityRequestError): self.resource_admin.get_universal_access("TESTING", "ELIJTEST") - def test_resource_admin_get_my_access_returns_valid_when_read( + # ============================================================================ + # My Access + # ============================================================================ + def test_resource_admin_get_my_access_read( self, call_racf_mock: Mock, ): @@ -71,7 +74,7 @@ def test_resource_admin_get_my_access_returns_valid_when_read( self.resource_admin.get_my_access("TESTING", "ELIJTEST"), "read" ) - def test_resource_admin_get_my_access_returns_valid_when_none( + def test_resource_admin_get_my_access_none( self, call_racf_mock: Mock, ): @@ -95,3 +98,55 @@ def test_resource_admin_get_my_access_raises_an_exception_when_extract_fails( ) with self.assertRaises(SecurityRequestError): self.resource_admin.get_my_access("TESTING", "ELIJTEST") + + # ============================================================================ + # Auditing Rules + # ============================================================================ + def test_resource_admin_get_audit_rules( + self, + call_racf_mock: Mock, + ): + call_racf_mock.return_value = ( + TestResourceConstants.TEST_EXTRACT_RESOURCE_RESULT_BASE_SUCCESS_XML + ) + self.assertEqual( + self.resource_admin.get_audit_rules("TESTING", "ELIJTEST"), + TestResourceConstants.TEST_GET_AUDIT_RULES, + ) + + def test_resource_admin_get_audit_rules_single( + self, + call_racf_mock: Mock, + ): + call_racf_mock.return_value = ( + TestResourceConstants.TEST_EXTRACT_RESOURCE_RESULT_BASE_GENERIC_SUCCESS_XML + ) + self.assertEqual( + self.resource_admin.get_audit_rules("TEST*", "ELIJTEST"), + TestResourceConstants.TEST_GET_AUDIT_RULES_SINGLE, + ) + + def test_resource_admin_get_audit_rules_none( + self, + call_racf_mock: Mock, + ): + profile_with_none_auditing = ( + TestResourceConstants.TEST_EXTRACT_RESOURCE_RESULT_BASE_SUCCESS_XML + ) + profile_with_none_auditing = profile_with_none_auditing.replace( + "SUCCESS(UPDATE),FAILURES(READ)", + "NONE", + ) + call_racf_mock.return_value = profile_with_none_auditing + self.assertIsNone(self.resource_admin.get_audit_rules("TESTING", "ELIJTEST")) + + # Error in environment, TESTING already deleted/not added + def test_resource_admin_get_audit_rules_raises_an_exception_when_extract_fails( + self, + call_racf_mock: Mock, + ): + call_racf_mock.return_value = ( + TestResourceConstants.TEST_EXTRACT_RESOURCE_RESULT_BASE_ERROR_XML + ) + with self.assertRaises(SecurityRequestError): + self.resource_admin.get_audit_rules("TESTING", "ELIJTEST") diff --git a/tests/resource/test_resource_setters.py b/tests/resource/test_resource_setters.py index 3c9ae235..0bcbd0b9 100644 --- a/tests/resource/test_resource_setters.py +++ b/tests/resource/test_resource_setters.py @@ -1,7 +1,7 @@ """Test general resource profile setter functions.""" import unittest -from unittest.mock import Mock +from unittest.mock import Mock, patch import __init__ @@ -18,6 +18,9 @@ class TestResourceSetters(unittest.TestCase): IRRSMO00.__init__ = Mock(return_value=None) resource_admin = ResourceAdmin(generate_requests_only=True) + # ============================================================================ + # Universal Access + # ============================================================================ def test_resource_admin_build_set_universal_access_request(self): result = self.resource_admin.set_universal_access( "TESTING", "ELIJTEST", "ALTER" @@ -25,3 +28,294 @@ def test_resource_admin_build_set_universal_access_request(self): self.assertEqual( result, TestResourceConstants.TEST_RESOURCE_SET_UNIVERSAL_ACCESS_XML ) + + # ============================================================================ + # Auditing Rules + # ============================================================================ + def test_resource_admin_build_remove_all_audit_rules_request(self): + result = self.resource_admin.remove_all_audit_rules("TESTING", "ELIJTEST") + self.assertEqual( + result, + TestResourceConstants.TEST_RESOURCE_REMOVE_ALL_AUDIT_RULES_REQUEST_XML, + ) + + def test_resource_admin_build_overwrite_audit_rules_by_access_level_request(self): + result = self.resource_admin.overwrite_audit_rules_by_access_level( + "TESTING", "ELIJTEST", alter="success" + ) + self.assertEqual( + result, + TestResourceConstants.TEST_RESOURCE_OVERWRITE_AUDIT_RULES_BY_ACCESS_LEVEL_REQUEST_XML, + ) + + def test_resource_admin_build_overwrite_audit_rules_by_access_level_none_request( + self, + ): + result = self.resource_admin.overwrite_audit_rules_by_access_level( + "TESTING", "ELIJTEST" + ) + self.assertEqual( + result, + TestResourceConstants.TEST_RESOURCE_OVERWRITE_AUDIT_BY_ACCESS_LEVEL_NONE_REQUEST_XML, + ) + + def test_resource_admin_build_overwrite_audit_rules_by_access_level_multiple_request( + self, + ): + result = self.resource_admin.overwrite_audit_rules_by_access_level( + "TESTING", "ELIJTEST", alter="success", control="all" + ) + self.assertEqual( + result, + TestResourceConstants.TEST_RESOURCE_OVERWRITE_AUDIT_BY_ACCESS_LEVEL_MULT_REQUEST_XML, + ) + + def test_resource_admin_build_overwrite_audit_rules_by_access_level_all_request( + self, + ): + result = self.resource_admin.overwrite_audit_rules_by_access_level( + "TESTING", + "ELIJTEST", + alter="success", + control="all", + update="success", + read="failure", + ) + self.assertEqual( + result, + TestResourceConstants.TEST_RESOURCE_OVERWRITE_AUDIT_BY_ACCESS_LEVEL_ALL_REQUEST_XML, + ) + + def test_resource_admin_build_overwrite_audit_rules_by_attempt_request(self): + result = self.resource_admin.overwrite_audit_rules_by_attempt( + "TESTING", "ELIJTEST", failure="control" + ) + self.assertEqual( + result, + TestResourceConstants.TEST_RESOURCE_OVERWRITE_AUDIT_RULES_BY_ATTEMPT_REQUEST_XML, + ) + + def test_resource_admin_build_overwrite_audit_rules_by_attempt_none_request(self): + result = self.resource_admin.overwrite_audit_rules_by_attempt( + "TESTING", "ELIJTEST" + ) + self.assertEqual( + result, + TestResourceConstants.TEST_RESOURCE_OVERWRITE_AUDIT_RULES_BY_ATTEMPT_NONE_REQUEST_XML, + ) + + def test_resource_admin_build_overwrite_audit_rules_by_attempt_multiple_request( + self, + ): + result = self.resource_admin.overwrite_audit_rules_by_attempt( + "TESTING", "ELIJTEST", success="alter", all="read" + ) + self.assertEqual( + result, + TestResourceConstants.TEST_RESOURCE_OVERWRITE_AUDIT_RULES_BY_ATTEMPT_MULT_REQUEST_XML, + ) + + def test_resource_admin_build_overwrite_audit_rules_by_attempt_all_request(self): + result = self.resource_admin.overwrite_audit_rules_by_attempt( + "TESTING", "ELIJTEST", success="alter", all="read", failure="update" + ) + self.assertEqual( + result, + TestResourceConstants.TEST_RESOURCE_OVERWRITE_AUDIT_RULES_BY_ATTEMPT_ALL_REQUEST_XML, + ) + + def test_resource_admin_build_overwrite_audit_rules_by_attempt_value_error(self): + bad_success = "problem" + with self.assertRaises(ValueError) as exception: + self.resource_admin.overwrite_audit_rules_by_attempt( + "TESTING", "ELIJTEST", success=bad_success + ) + error_string = ( + f"'{bad_success}' is not a valid access level. Valid access levels include " + + "'alter', 'control', 'read', and 'update'." + ) + self.assertEqual( + str(exception.exception), + error_string, + ) + + def test_resource_admin_build_overwrite_audit_rules_by_attempt_value_duplicates( + self, + ): + success = "alter" + failure = "alter" + with self.assertRaises(ValueError) as exception: + self.resource_admin.overwrite_audit_rules_by_attempt( + "TESTING", "ELIJTEST", success=success, failure=failure + ) + error_string = ( + f"'{success}' is provided as an 'Access Level' multiple times, which is not " + + "allowed." + ) + self.assertEqual( + str(exception.exception), + error_string, + ) + + @patch("pyracf.resource.resource_admin.ResourceAdmin.get_audit_rules") + def test_resource_admin_build_alter_audit_rules_by_access_level_request( + self, + resource_admin_get_audit_rules_mock: Mock, + ): + resource_admin_get_audit_rules_mock.return_value = ( + TestResourceConstants.TEST_GET_AUDIT_RULES + ) + self.assertEqual( + self.resource_admin.alter_audit_rules_by_access_level( + "TESTING", "ELIJTEST", alter="success" + ), + TestResourceConstants.TEST_RESOURCE_ALTER_AUDIT_RULES_BY_ACCESS_LEVEL_REQUEST_XML, + ) + + @patch("pyracf.resource.resource_admin.ResourceAdmin.get_audit_rules") + def test_resource_admin_build_alter_audit_rules_by_access_level_none_request( + self, + resource_admin_get_audit_rules_mock: Mock, + ): + resource_admin_get_audit_rules_mock.return_value = ( + TestResourceConstants.TEST_GET_AUDIT_RULES_WITH_ALL + ) + self.assertEqual( + self.resource_admin.alter_audit_rules_by_access_level( + "TESTING", "ELIJTEST" + ), + TestResourceConstants.TEST_RESOURCE_ALTER_AUDIT_RULES_BY_ACCESS_LEVEL_NONE_REQUEST_XML, + ) + + @patch("pyracf.resource.resource_admin.ResourceAdmin.get_audit_rules") + def test_resource_admin_build_alter_audit_rules_by_access_level_multiple_request( + self, + resource_admin_get_audit_rules_mock: Mock, + ): + resource_admin_get_audit_rules_mock.return_value = ( + TestResourceConstants.TEST_GET_AUDIT_RULES + ) + self.assertEqual( + self.resource_admin.alter_audit_rules_by_access_level( + "TESTING", "ELIJTEST", alter="success", control="failure" + ), + TestResourceConstants.TEST_RESOURCE_ALTER_AUDIT_RULES_BY_ACCESS_LEVEL_MULT_REQUEST_XML, + ) + + @patch("pyracf.resource.resource_admin.ResourceAdmin.get_audit_rules") + def test_resource_admin_build_alter_audit_rules_by_access_level_all_request( + self, + resource_admin_get_audit_rules_mock: Mock, + ): + resource_admin_get_audit_rules_mock.return_value = ( + TestResourceConstants.TEST_GET_AUDIT_RULES + ) + self.assertEqual( + self.resource_admin.alter_audit_rules_by_access_level( + "TESTING", + "ELIJTEST", + alter="success", + control="failure", + update="all", + read="all", + ), + TestResourceConstants.TEST_RESOURCE_ALTER_AUDIT_RULES_BY_ACCESS_LEVEL_ALL_REQUEST_XML, + ) + + @patch("pyracf.resource.resource_admin.ResourceAdmin.get_audit_rules") + def test_resource_admin_build_alter_audit_rules_by_attempt_request( + self, + resource_admin_get_audit_rules_mock: Mock, + ): + resource_admin_get_audit_rules_mock.return_value = ( + TestResourceConstants.TEST_GET_AUDIT_RULES + ) + self.assertEqual( + self.resource_admin.alter_audit_rules_by_attempt( + "TESTING", "ELIJTEST", failure="control" + ), + TestResourceConstants.TEST_RESOURCE_ALTER_AUDIT_RULES_BY_ATTEMPT_REQUEST_XML, + ) + + @patch("pyracf.resource.resource_admin.ResourceAdmin.get_audit_rules") + def test_resource_admin_build_alter_audit_rules_by_attempt_none_request( + self, + resource_admin_get_audit_rules_mock: Mock, + ): + resource_admin_get_audit_rules_mock.return_value = ( + TestResourceConstants.TEST_GET_AUDIT_RULES_WITH_ALL + ) + self.assertEqual( + self.resource_admin.alter_audit_rules_by_attempt("TESTING", "ELIJTEST"), + TestResourceConstants.TEST_RESOURCE_ALTER_AUDIT_RULES_BY_ATTEMPT_NONE_REQUEST_XML, + ) + + @patch("pyracf.resource.resource_admin.ResourceAdmin.get_audit_rules") + def test_resource_admin_build_alter_audit_rules_by_attempt_multiple_request( + self, + resource_admin_get_audit_rules_mock: Mock, + ): + resource_admin_get_audit_rules_mock.return_value = ( + TestResourceConstants.TEST_GET_AUDIT_RULES + ) + self.assertEqual( + self.resource_admin.alter_audit_rules_by_attempt( + "TESTING", "ELIJTEST", failure="control", all="read" + ), + TestResourceConstants.TEST_RESOURCE_ALTER_AUDIT_RULES_BY_ATTEMPT_MULT_REQUEST_XML, + ) + + @patch("pyracf.resource.resource_admin.ResourceAdmin.get_audit_rules") + def test_resource_admin_build_alter_audit_rules_by_attempt_all_request( + self, + resource_admin_get_audit_rules_mock: Mock, + ): + resource_admin_get_audit_rules_mock.return_value = ( + TestResourceConstants.TEST_GET_AUDIT_RULES + ) + self.assertEqual( + self.resource_admin.alter_audit_rules_by_attempt( + "TESTING", "ELIJTEST", failure="control", success="alter", all="read" + ), + TestResourceConstants.TEST_RESOURCE_ALTER_AUDIT_RULES_BY_ATTEMPT_ALL_REQUEST_XML, + ) + + def test_resource_admin_build_alter_audit_rules_by_attempt_value_error(self): + bad_success = "problem" + bad_all = "value" + with self.assertRaises(ValueError) as exception: + self.resource_admin.overwrite_audit_rules_by_attempt( + "TESTING", + "ELIJTEST", + success=bad_success, + all=bad_all, + ) + error_string = ( + f"'{bad_success}' and '{bad_all}' are not valid access levels. Valid " + + "access levels include 'alter', 'control', 'read', and 'update'." + ) + self.assertEqual( + str(exception.exception), + error_string, + ) + + def test_resource_admin_build_alter_audit_rules_by_attempt_value_error_all(self): + bad_success = "problem" + bad_failure = ["improper"] + bad_all = 1234 + with self.assertRaises(ValueError) as exception: + self.resource_admin.overwrite_audit_rules_by_attempt( + "TESTING", + "ELIJTEST", + success=bad_success, + failure=bad_failure, + all=bad_all, + ) + error_string = ( + f"'{bad_success}', '{bad_failure}', and '{bad_all}' are not valid access levels. " + + "Valid access levels include 'alter', 'control', 'read', and 'update'." + ) + self.assertEqual( + str(exception.exception), + error_string, + ) diff --git a/tests/user/test_user_constants.py b/tests/user/test_user_constants.py index 5129946f..43870137 100644 --- a/tests/user/test_user_constants.py +++ b/tests/user/test_user_constants.py @@ -251,8 +251,18 @@ def get_sample(sample_file: str) -> Union[str, bytes]: "user_remove_operations_authority_request.xml" ) TEST_USER_SET_PASSWORD_XML = get_sample("user_set_password_request.xml") +TEST_USER_SET_PASSWORD_NOEXPIRED_XML = get_sample( + "user_set_password_noexpired_request.xml" +) +TEST_USER_SET_PASSWORD_EXPIRED_XML = get_sample("user_set_password_expired_request.xml") TEST_USER_SET_PASSWORD_DELETE_XML = get_sample("user_set_password_delete_request.xml") TEST_USER_SET_PASSPHRASE_XML = get_sample("user_set_passphrase_request.xml") +TEST_USER_SET_PASSPHRASE_NOEXPIRED_XML = get_sample( + "user_set_passphrase_noexpired_request.xml" +) +TEST_USER_SET_PASSPHRASE_EXPIRED_XML = get_sample( + "user_set_passphrase_expired_request.xml" +) TEST_USER_SET_PASSPHRASE_DELETE_XML = get_sample( "user_set_passphrase_delete_request.xml" ) diff --git a/tests/user/test_user_request_builder.py b/tests/user/test_user_request_builder.py index 655ac77a..5c75edf5 100644 --- a/tests/user/test_user_request_builder.py +++ b/tests/user/test_user_request_builder.py @@ -34,7 +34,6 @@ def test_user_admin_build_add_user_base_omvs_tso_revoke_resume_request(self): "squidwrd", traits=TestUserConstants.TEST_ADD_USER_BASE_OMVS_TSO_REVOKE_RESUME_REQUEST_TRAITS, ) - print(result) self.assertEqual( result, TestUserConstants.TEST_ADD_USER_BASE_OMVS_TSO_REVOKE_RESUME_REQUEST_XML, diff --git a/tests/user/test_user_setters.py b/tests/user/test_user_setters.py index 5650aaa9..ac6714fc 100644 --- a/tests/user/test_user_setters.py +++ b/tests/user/test_user_setters.py @@ -68,6 +68,14 @@ def test_user_admin_build_set_password_request(self): result = self.user_admin.set_password("squidwrd", "GIyTTqdF") self.assertEqual(result, TestUserConstants.TEST_USER_SET_PASSWORD_XML) + def test_user_admin_build_set_password_noexpired_request(self): + result = self.user_admin.set_password("squidwrd", "GIyTTqdF", expired=False) + self.assertEqual(result, TestUserConstants.TEST_USER_SET_PASSWORD_NOEXPIRED_XML) + + def test_user_admin_build_set_password_expired_request(self): + result = self.user_admin.set_password("squidwrd", "GIyTTqdF", expired=True) + self.assertEqual(result, TestUserConstants.TEST_USER_SET_PASSWORD_EXPIRED_XML) + def test_user_admin_build_set_password_delete_request(self): result = self.user_admin.set_password("squidwrd", False) self.assertEqual(result, TestUserConstants.TEST_USER_SET_PASSWORD_DELETE_XML) @@ -79,6 +87,20 @@ def test_user_admin_build_set_passphrase_request(self): result = self.user_admin.set_passphrase("squidwrd", "PassPhrasesAreCool!") self.assertEqual(result, TestUserConstants.TEST_USER_SET_PASSPHRASE_XML) + def test_user_admin_build_set_passphrase_noexpired_request(self): + result = self.user_admin.set_passphrase( + "squidwrd", "PassPhrasesAreCool!", expired=False + ) + self.assertEqual( + result, TestUserConstants.TEST_USER_SET_PASSPHRASE_NOEXPIRED_XML + ) + + def test_user_admin_build_set_passphrase_expired_request(self): + result = self.user_admin.set_passphrase( + "squidwrd", "PassPhrasesAreCool!", expired=True + ) + self.assertEqual(result, TestUserConstants.TEST_USER_SET_PASSPHRASE_EXPIRED_XML) + def test_user_admin_build_set_passphrase_delete_request(self): result = self.user_admin.set_passphrase("squidwrd", False) self.assertEqual(result, TestUserConstants.TEST_USER_SET_PASSPHRASE_DELETE_XML) diff --git a/tests/user/user_request_samples/user_set_passphrase_expired_request.xml b/tests/user/user_request_samples/user_set_passphrase_expired_request.xml new file mode 100644 index 00000000..c7d6bbc6 --- /dev/null +++ b/tests/user/user_request_samples/user_set_passphrase_expired_request.xml @@ -0,0 +1,8 @@ + + + + ******** + + + + \ No newline at end of file diff --git a/tests/user/user_request_samples/user_set_passphrase_noexpired_request.xml b/tests/user/user_request_samples/user_set_passphrase_noexpired_request.xml new file mode 100644 index 00000000..a4bd945b --- /dev/null +++ b/tests/user/user_request_samples/user_set_passphrase_noexpired_request.xml @@ -0,0 +1,8 @@ + + + + ******** + + + + \ No newline at end of file diff --git a/tests/user/user_request_samples/user_set_password_expired_request.xml b/tests/user/user_request_samples/user_set_password_expired_request.xml new file mode 100644 index 00000000..4f5982f7 --- /dev/null +++ b/tests/user/user_request_samples/user_set_password_expired_request.xml @@ -0,0 +1,8 @@ + + + + ******** + + + + \ No newline at end of file diff --git a/tests/user/user_request_samples/user_set_password_noexpired_request.xml b/tests/user/user_request_samples/user_set_password_noexpired_request.xml new file mode 100644 index 00000000..54dc47ff --- /dev/null +++ b/tests/user/user_request_samples/user_set_password_noexpired_request.xml @@ -0,0 +1,8 @@ + + + + ******** + + + + \ No newline at end of file