Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
12cd2b0
Error and Error Handling Changes
ElijahSwiftIBM Nov 24, 2023
f5754c2
Add base functionality to run as another user
ElijahSwiftIBM Nov 24, 2023
721e878
Typing changes
ElijahSwiftIBM Nov 24, 2023
a6e53c4
Update irrsmo00.py
ElijahSwiftIBM Nov 24, 2023
f4756c7
Surface IRRSMO00 Return and Reason Codes
ElijahSwiftIBM Nov 25, 2023
f261c44
Merge branch 'feature/handle-empty-return' into feature/run_as_userid
ElijahSwiftIBM Nov 25, 2023
85e7109
Fold Surrogat Error into Null Response
ElijahSwiftIBM Nov 26, 2023
a6e927d
Minor changes for doc
ElijahSwiftIBM Nov 26, 2023
723a381
Refactoring and Standardization Changes
ElijahSwiftIBM Nov 29, 2023
a1b9be1
Updates
ElijahSwiftIBM Nov 29, 2023
eda3184
Complete Transition to UserIdError
ElijahSwiftIBM Nov 29, 2023
8db616c
Changing error text and formatting
ElijahSwiftIBM Nov 30, 2023
3c594f5
Rearrange Errors
ElijahSwiftIBM Nov 30, 2023
8a227de
add running_user even when not specified
ElijahSwiftIBM Dec 1, 2023
000b71f
Name Changes
ElijahSwiftIBM Dec 4, 2023
04c028d
Naming and Docstring Updates
ElijahSwiftIBM Dec 4, 2023
46be4b8
Naming and convention fixes
ElijahSwiftIBM Dec 4, 2023
d05d4c4
Minor changes
ElijahSwiftIBM Dec 6, 2023
2baa080
Minor changes
ElijahSwiftIBM Dec 6, 2023
3c4bee2
Add unit test for Add operations
ElijahSwiftIBM Dec 12, 2023
a958401
Docstring changes
ElijahSwiftIBM Dec 18, 2023
dbdf9c1
Add Testcase for SetupPrecheck error
ElijahSwiftIBM Dec 19, 2023
2d7c512
Update test_setup_precheck.py
ElijahSwiftIBM Dec 19, 2023
588449f
Update security_admin.py
ElijahSwiftIBM Dec 19, 2023
07c92cc
Change DownstreamFatalError Test Constants
ElijahSwiftIBM Dec 19, 2023
f9db7fc
Update irrsmo00.py
ElijahSwiftIBM Dec 19, 2023
37b2214
Update irrsmo00.c
ElijahSwiftIBM Dec 20, 2023
0baee16
Function and Unit Test Changes
ElijahSwiftIBM Dec 20, 2023
19c5df0
Change docstrings and version number
ElijahSwiftIBM Dec 21, 2023
af81fc7
Drive userid on get_running_userid to lowercase
ElijahSwiftIBM Dec 26, 2023
f4171c2
Update security_admin.py
ElijahSwiftIBM Jan 3, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
3 changes: 3 additions & 0 deletions pyracf/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 2 additions & 0 deletions pyracf/access/access_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down Expand Up @@ -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,
)

# ============================================================================
Expand Down
69 changes: 69 additions & 0 deletions pyracf/common/downstream_fatal_error.py
Original file line number Diff line number Diff line change
@@ -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.
"""
Comment thread
lcarcaramo marked this conversation as resolved.

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
22 changes: 14 additions & 8 deletions pyracf/common/irrsmo00.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>

#define BUFFER_SIZE (100000)

Expand Down Expand Up @@ -33,33 +34,37 @@ 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];
memset(rsp, 0, BUFFER_SIZE);
unsigned int saf_rc=0, racf_rc=0, racf_rsn=0;
unsigned int num_parms=17, fn=1, opts = input_opts, rsp_len = sizeof(rsp)-1;

strncpy(userid.str, input_userid, userid.len);

IRRSMO64(
work_area,
alet,
saf_rc,
&saf_rc,
alet,
racf_rc,
&racf_rc,
alet,
racf_rsn,
&racf_rsn,
num_parms,
fn,
opts,
Expand All @@ -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,
Expand Down
26 changes: 22 additions & 4 deletions pyracf/common/irrsmo00.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Interface to irrsmo00.dll."""
import platform
from typing import Union

try:
from cpyracf import call_irrsmo00
Expand All @@ -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(
Comment thread
lcarcaramo marked this conversation as resolved.
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")
14 changes: 9 additions & 5 deletions pyracf/common/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import json
import os
import re
from typing import Union
from typing import List, Union


class Logger:
Expand Down Expand Up @@ -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:
"""
Expand All @@ -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 = ""
Expand Down
Loading