Skip to content

Commit

Permalink
Merge pull request #57 from ambitus/feature/run_as_userid
Browse files Browse the repository at this point in the history
Run as UserID and Error handling changes
  • Loading branch information
lcarcaramo authored Jan 4, 2024
2 parents 468a1cb + f4171c2 commit fd9d7aa
Show file tree
Hide file tree
Showing 151 changed files with 2,332 additions and 283 deletions.
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.
"""

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(
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

0 comments on commit fd9d7aa

Please sign in to comment.