Skip to content

Commit 85336a0

Browse files
authored
Merge pull request #26 from ambitus/robust-error-handling
Robust error handling
2 parents fde9010 + eead54f commit 85336a0

File tree

170 files changed

+6451
-2928
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

170 files changed

+6451
-2928
lines changed

pyracf/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
"""Make security admin subclasses available from package root."""
22
from .access.access_admin import AccessAdmin
3+
from .common.add_operation_error import AddOperationError
4+
from .common.alter_operation_error import AlterOperationError
35
from .common.security_request_error import SecurityRequestError
6+
from .common.segment_error import SegmentError
7+
from .common.segment_trait_error import SegmentTraitError
48
from .connection.connection_admin import ConnectionAdmin
59
from .data_set.data_set_admin import DataSetAdmin
610
from .group.group_admin import GroupAdmin

pyracf/access/access_admin.py

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33
from typing import List, Union
44

5-
from pyracf.access.access_request import AccessRequest
65
from pyracf.common.security_admin import SecurityAdmin
76

7+
from .access_request import AccessRequest
8+
89

910
class AccessAdmin(SecurityAdmin):
1011
"""RACF Access Administration."""
@@ -25,8 +26,7 @@ def __init__(
2526
"base:model_profile": "racf:fprofile",
2627
"base:model_profile_generic": "racf:fgeneric",
2728
"base:model_profile_volume": "racf:fvolume",
28-
"base:id": "authid",
29-
"base:profile": "racf:profile", # Not documented?
29+
"base:auth_id": "authid",
3030
"base:reset": "racf:reset",
3131
"base:volume": "racf:volume",
3232
"base:when_partner_lu_name": "racf:whenappc",
@@ -53,23 +53,7 @@ def __init__(
5353
# ============================================================================
5454
# Base Functions
5555
# ============================================================================
56-
def add(
57-
self,
58-
resource: str,
59-
class_name: str,
60-
auth_id: str,
61-
traits: dict,
62-
volume: Union[str, None] = None,
63-
generic: bool = False,
64-
) -> Union[dict, bytes]:
65-
"""Create a new permission."""
66-
traits["base:id"] = auth_id
67-
self._build_segment_dictionaries(traits)
68-
access_request = AccessRequest(resource, class_name, "set", volume, generic)
69-
self._add_traits_directly_to_request_xml_with_no_segments(access_request)
70-
return self._make_request(access_request)
71-
72-
def alter(
56+
def permit(
7357
self,
7458
resource: str,
7559
class_name: str,
@@ -78,8 +62,8 @@ def alter(
7862
volume: Union[str, None] = None,
7963
generic: bool = False,
8064
) -> Union[dict, bytes]:
81-
"""Alter an existing permission."""
82-
traits["base:id"] = auth_id
65+
"""Create or change a permission"""
66+
traits["base:auth_id"] = auth_id
8367
self._build_segment_dictionaries(traits)
8468
access_request = AccessRequest(resource, class_name, "set", volume, generic)
8569
self._add_traits_directly_to_request_xml_with_no_segments(
@@ -96,7 +80,7 @@ def delete(
9680
generic: bool = False,
9781
) -> Union[dict, bytes]:
9882
"""Delete a permission."""
99-
traits = {"base:id": auth_id}
83+
traits = {"base:auth_id": auth_id}
10084
self._build_segment_dictionaries(traits)
10185
access_request = AccessRequest(resource, class_name, "del", volume, generic)
10286
self._add_traits_directly_to_request_xml_with_no_segments(access_request)

pyracf/common/add_operation_error.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""Exception to use when Add operation would alter an existing profile."""
2+
3+
4+
class AddOperationError(Exception):
5+
"""
6+
Raised when a profile cannot be added because it already exists.
7+
"""
8+
9+
def __init__(self, profile_name: str, class_name: str) -> None:
10+
self.message = "Refusing to make security request to IRRSMO00."
11+
admin_types = ["user", "group", "dataSet"]
12+
if class_name not in admin_types:
13+
self.message += (
14+
"\n\nTarget profile "
15+
+ f"'{profile_name}' already exists as a profile in the '{class_name}' class."
16+
)
17+
else:
18+
self.message += (
19+
"\n\nTarget profile "
20+
+ f"'{profile_name}' already exists as a '{class_name}' profile."
21+
)
22+
23+
def __str__(self) -> str:
24+
return self.message
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""Exception to use when Alter operation would add a new profile."""
2+
3+
4+
class AlterOperationError(Exception):
5+
"""
6+
Raised when a profile cannot be altered because it does not exist.
7+
"""
8+
9+
def __init__(self, profile_name: str, class_name: str) -> None:
10+
self.message = "Refusing to make security request to IRRSMO00."
11+
admin_types = ["user", "group", "dataSet"]
12+
if class_name not in admin_types:
13+
self.message += (
14+
"\n\nTarget profile "
15+
+ f"'{profile_name}' does not exist as a profile in the '{class_name}' class."
16+
)
17+
else:
18+
self.message += (
19+
"\n\nTarget profile "
20+
+ f"'{profile_name}' does not exist as a '{class_name}' profile."
21+
)
22+
23+
def __str__(self) -> str:
24+
return self.message

pyracf/common/irrsmo00.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def __init__(self) -> None:
2121

2222
def call_racf(self, request_xml: bytes, precheck: bool = False) -> str:
2323
"""Make request to call_irrsmo00 in the cpyracf Python extension."""
24-
options = 11 if precheck else 9
24+
options = 15 if precheck else 13
2525
return call_irrsmo00(
2626
xml_str=request_xml, xml_len=len(request_xml), opts=options
2727
).decode("cp1047")

pyracf/common/security_admin.py

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
from .security_request import SecurityRequest
1111
from .security_request_error import SecurityRequestError
1212
from .security_result import SecurityResult
13+
from .segment_error import SegmentError
14+
from .segment_trait_error import SegmentTraitError
1315

1416

1517
class SecurityAdmin:
@@ -64,13 +66,13 @@ def __init__(
6466
"base:passphrase": "racf:phrase",
6567
}
6668
self.__irrsmo00 = IRRSMO00()
67-
self.__profile_type = profile_type
69+
self._profile_type = profile_type
6870
self._segment_traits = {}
6971
# used to preserve segment traits for debug logging.
7072
self.__preserved_segment_traits = {}
7173
self._trait_map = {}
7274
self.__debug = debug
73-
self.__generate_requests_only = generate_requests_only
75+
self._generate_requests_only = generate_requests_only
7476
if update_existing_segment_traits is not None:
7577
self.__update_valid_segment_traits(update_existing_segment_traits)
7678
if replace_existing_segment_traits is not None:
@@ -123,7 +125,7 @@ def _extract_and_check_result(
123125
) -> dict:
124126
"""Extract a RACF profile."""
125127
result = self._make_request(security_request)
126-
if self.__generate_requests_only:
128+
if self._generate_requests_only:
127129
return result
128130
self._format_profile(result)
129131
if self.__debug:
@@ -155,7 +157,7 @@ def _make_request(
155157
security_request.dump_request_xml(encoding="utf-8"),
156158
secret_traits=self.__secret_traits,
157159
)
158-
if self.__generate_requests_only:
160+
if self._generate_requests_only:
159161
request_xml = self.__logger.redact_request_xml(
160162
security_request.dump_request_xml(encoding="utf-8"),
161163
secret_traits=self.__secret_traits,
@@ -202,7 +204,7 @@ def _to_steps(self, results: Union[List[dict], dict, bytes]) -> Union[dict, byte
202204
"""
203205
if isinstance(results, dict) or isinstance(results, bytes):
204206
results = [results]
205-
if self.__generate_requests_only:
207+
if self._generate_requests_only:
206208
concatenated_xml = b""
207209
for request_xml in results:
208210
if request_xml:
@@ -260,17 +262,33 @@ def __validate_and_add_trait(
260262

261263
def _build_bool_segment_dictionaries(self, segments: List[str]) -> None:
262264
"""Build segment dictionaries for profile extract."""
265+
bad_segments = []
263266
for segment in segments:
264267
if segment in self._valid_segment_traits:
265268
self._segment_traits[segment] = True
269+
else:
270+
bad_segments.append(segment)
271+
if bad_segments:
272+
raise SegmentError(bad_segments, self._profile_type)
266273
# preserve segment traits for debug logging.
267274
self.__preserved_segment_traits = self._segment_traits
268275

269276
def _build_segment_dictionaries(self, traits: dict) -> None:
270277
"""Build segemnt dictionaries for each segment."""
278+
bad_traits = []
271279
for trait in traits:
280+
trait_valid = False
272281
for segment in self._valid_segment_traits:
273-
self.__validate_and_add_trait(trait, segment, traits[trait])
282+
trait_valid = self.__validate_and_add_trait(
283+
trait, segment, traits[trait]
284+
)
285+
if trait_valid:
286+
break
287+
if not trait_valid:
288+
bad_traits.append(trait)
289+
if bad_traits:
290+
raise SegmentTraitError(bad_traits, self._profile_type)
291+
274292
# preserve segment traits for debug logging.
275293
self.__preserved_segment_traits = self._segment_traits
276294

@@ -302,10 +320,10 @@ def _get_profile(
302320
self, result: Union[dict, bytes], index: int = 0
303321
) -> Union[dict, bytes]:
304322
"""Extract the profile section from a result dictionary."""
305-
if self.__generate_requests_only:
306-
# Allows this function to work with "self.__generate_requests_only" mode.
323+
if self._generate_requests_only:
324+
# Allows this function to work with "self._generate_requests_only" mode.
307325
return result
308-
return result["securityResult"][self.__profile_type]["commands"][0]["profiles"][
326+
return result["securityResult"][self._profile_type]["commands"][0]["profiles"][
309327
index
310328
]
311329

@@ -317,8 +335,8 @@ def _get_field(
317335
string: bool = False,
318336
) -> Union[bytes, Any, None]:
319337
"""Extract the value of a field from a segment in a profile."""
320-
if self.__generate_requests_only:
321-
# Allows this function to work with "self.__generate_requests_only" mode.
338+
if self._generate_requests_only:
339+
# Allows this function to work with "self._generate_requests_only" mode.
322340
return profile
323341
try:
324342
field = profile[segment][field]
@@ -353,15 +371,15 @@ def _format_profile_generic(self, messages: str) -> None:
353371
current_segment = messages[i].split()[0].lower()
354372
profile[current_segment] = {}
355373
i += 2
356-
if self.__profile_type in ("dataSet", "resource"):
374+
if self._profile_type in ("dataSet", "resource"):
357375
i = self.__format_data_set_generic_profile_data(
358376
messages, profile, current_segment, i
359377
)
360-
if self.__profile_type == "user":
378+
if self._profile_type == "user":
361379
i = self.__format_user_profile_data(
362380
messages, profile, current_segment, i
363381
)
364-
if self.__profile_type == "group":
382+
if self._profile_type == "group":
365383
i = self.__format_group_profile_data(
366384
messages, profile, current_segment, i
367385
)

pyracf/common/security_request_error.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,17 @@ def __init__(self, result: dict) -> None:
1717

1818
def __str__(self) -> str:
1919
return self.message
20+
21+
def contains_error_message(
22+
self, security_definition_tag: str, error_message_id: str
23+
):
24+
commands = self.result["securityResult"][security_definition_tag].get(
25+
"commands"
26+
)
27+
if not isinstance(commands, list):
28+
return False
29+
messages = commands[0].get("messages", [])
30+
if error_message_id in "".join(messages):
31+
return True
32+
else:
33+
return False

pyracf/common/segment_error.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"""Exception to use when the user passes bad segment name(s) on an extract request."""
2+
3+
4+
class SegmentError(Exception):
5+
"""
6+
Raised when a user passes a bad segment name on an extract request.
7+
"""
8+
9+
def __init__(self, bad_segments: list, profile_type: str) -> None:
10+
self.message = "Unable to build Security Request.\n\n"
11+
for segment in bad_segments:
12+
self.message += (
13+
f"'{segment}' is not a known segment for '{profile_type}'.\n"
14+
)
15+
16+
def __str__(self) -> str:
17+
return self.message

pyracf/common/segment_trait_error.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
"""Exception to use when the user passes bad segment-trait name(s) on an add/alter request."""
2+
3+
4+
class SegmentTraitError(Exception):
5+
"""
6+
Raised when a user passes a bad segment-trait combination in the traits dictionary.
7+
"""
8+
9+
def __init__(self, bad_traits: list, profile_type: str) -> None:
10+
self.message = "Unable to build Security Request.\n\n"
11+
for trait in bad_traits:
12+
self.message += (
13+
f"'{trait}' is not a known segment-trait "
14+
+ f"combination for '{profile_type}'.\n"
15+
)
16+
17+
def __str__(self) -> str:
18+
return self.message

0 commit comments

Comments
 (0)