The Python package pysear to work with RACF is great. The source is on github, and the documentation starts here. It is well documented, and there are good examples.
I’ve managed to do a lot of processing with very little of my own code.
One project I’ve been meaning to do for a time is to extract the contents of a RACF database and compare them with a different database and show the differences. IBM provides a batch program, and a very large Rexx exec. This has some bugs and is not very nice to use. There is a Rexx interface, which worked, but I found I was writing a lot of code. Then I found the pysear code.
Background
The data returned for userids (and other types of data) have segments.
You can display the base segment for a user.
tso lu colin
To display the tso base segment
tso lu colin tso
Field names returned by pysear have the segment name as a prefix, for example base:max_incorrect_password_attempts.
My first query
What are the active classes in RACF?
See the example.
from sear import sear
import json
import sys
result = sear(
{
"operation": "extract",
"admin_type": "racf-options"
},
)
json_data = json.dumps(result.result , indent=2)
print(json_data)
For error handling see error handling
This produces output like
{
"profile": {
"base": {
"base:active_classes": [
"DATASET",
"USER",...
],
"base:add_creator_to_access_list": true,
...
"base:max_incorrect_password_attempts": 3,
...
}
To process the active classes one at a time you need code like
for ac in result.result["profile"]["base"]["base:active_classes"]:
print("Active class:",ac)
The returned attributes are called traits. See here for the traits for RACF options. The traits show
| Trait | |
| RACF Key | revoke |
| Data Types | String |
| Operators Allowed | “set”,”delete” |
| Supported Operations | “alter”,”extract” |
For this attribute because it is a single valued object, you can set it or delete it.
You can use this attribute for example
result = sear(
{
"operation": "alter",
"admin_type": "racf-options",
"traits": {
"base:max_incorrect_password_attempts": 5,
},
},
)
The trait “base:active_classes” is list of classes [“DATASET”, “USER”,…]
The trait is
| Trait | |
| RACF Key | classact |
| Data Types | string |
| Operators Allowed | "add", "remove" |
| Supported Operations | "alter", "extract" |
Because it is a list, you can add or remove an element, you do not use set or delete which would replace the whole list.
Some traits, such as use counts, have Operators Allowed of N/A. You can only extract and display the information.
My second query
What are the userids in RACF?
The traits are listed here, and code examples are here.
I used
from sear import sear
import json
# get all userids begining with ZWE
users = sear(
{
"operation": "search",
"admin_type": "user",
"userid_filter": "ZWE",
},
)
profiles = users.result["profiles"]
# Now process each profile in turn.
# because this is for userid profiles we need admin_type=user and userid=....
for profile in profiles:
user = sear(
{
"operation": "extract",
"admin_type": "user",
"userid": profile,
},
)
segments = user.result["profile"]
#print("segment",segments)
for segment in segments: # eg base or omvs
for w1,v1 in segments[segment].items():
#print(w1,v1)
#for w2,v2 in v1.items():
# print(w1,w2,v2 )
json_data = json.dumps(v1 , indent=2)
print(w1,json_data)
This gave
==PROFILE=== ZWESIUSR
base:auditor false
base:automatic_dataset_protection false
base:create_date "05/06/20"
base:default_group "ZWEADMIN"
base:group_connections [
{
...
"base:group_connection_group": "IZUADMIN",
...
"base:group_connection_owner": "IBMUSER",
...
},
{
...
"base:group_connection_group": "IZUUSER",
...
}
...
omvs:default_shell "/bin/sh"
omvs:home_directory "/apps/zowe/v10/home/zwesiusr"
omvs:uid 990017
===PROFILE=== ZWESVUSR
...
Notes on using search and extract
If you use “operation”: “search” you need a ….._filter. If you use extract you use the data type directly, such as “userid”:…
Processing resources
You can process RACF resources. For example a OPERCMDS provide for MVS.DISPLAY commands.
The sear command need a “class”:…. value, for example
result = sear(
{
"operation": "search",
"admin_type": "resource",
"class": "OPERCMDS",
"resource_filter": "MVS.**",
},
)
result = sear(
{
"operation": "extract",
"admin_type": "resource",
"resource": "MVS.DISPLAY",
"class": "Opercmds",
},
)
The value of the class is converted to upper case.
Changing a profile
If you change a profile, for example to issue the PERMIT command
from sear import sear
import json
result = sear(
{ "operation": "alter",
"admin_type": "permission",
"resource": "MVS.DISPLAY.*",
"userid": "ADCDG",
"traits": {
"base:access": "CONTROL"
},
"class": "OPERCMDS"
},
)
json_data = json.dumps(result.result , indent=2)
print(json_data)
The output was
{
"commands": [
{
"command": "PERMIT MVS.DISPLAY.* CLASS(OPERCMDS)ACCESS (CONTROL) ID(ADCDG)",
"messages": [
"ICH06011I RACLISTED PROFILES FOR OPERCMDS WILL NOT REFLECT THE UPDATE(S) UNTIL A SETROPTS REFRESH IS ISSUED"
]
}
],
"return_codes": {
"racf_reason_code": 0,
"racf_return_code": 0,
"saf_return_code": 0,
"sear_return_code": 0
}
}
Error handling
Return codes and errors messages
There are two layers of error handling.
- Invalid requests – problems detected by pysear
- Non zero return code from the underlying RACF code.
If pysear detects a problem it returns it in
result.result.get("errors")
For example you have specified an invalid parameter such as “userzzz“:”MINE”
If you do not have this field, then the request was passed to the RACF service. This returns multiple values. See IRRSMO00 return and reason codes. There will be values for
- SAF return code
- RACF return code
- RACF reason code
- sear return code.
If the RACF return code is zero then the request was successful.
To make error handling easier – and have one error handling for all requests I used
try:
result = try_sear(search)
except Exception as ex:
print("Exception-Colin Line112:",ex)
quit()
Where try_sear was
def try_sear(data):
# execute the request
result = sear(data)
if result.result.get("errors") != None:
print("Request:",result.request)
print("Error with request:",result.result["errors"])
raise ValueError("errors")
elif (result.result["return_codes"]["racf_reason_code"] != 0):
rcs = result.result["return_codes"]
print("SAF Return code",rcs["saf_return_code"],
"RACF Return code", rcs["racf_return_code"],
"RACF Reason code",["racf_reason_code"],
)
raise ValueError("return codes")
return result
Overall
This interface is very easy to do.
I use it to extract definitions from one RACF database, save them as JSON files. Repeat with a different (historical) RACF database, then compare the two JSON files to see the differences.
Note: The sear command only works with the active database, so I had to make the historical database active, run the commands, and switch back to the current data base.