Grouper custom template via GSH membership trace - V2
This example is for v4+, not yet tested on v6. It is still a work-in-progress.
The web services API does not currently provide a method to do a membership trace. This template allows a user to perform a membership trace (not privilege membership, yet) for a given subject in a particular group. It is intended to be called via WS (if you're in the UI, you can just to the trace there), so it only returns JSON and not UI output lines.
If a user has more than one path to group membership, there will be multiple entries in the JSON array returned by the template.
This currently works for our (SFU's) needs, but it could be expanded to do privilege traces, customizing the stringPath delimiter, displaying composite group information, etc. In our environment this is intended to be used in reporting scripts by Grouper admins who have full root access, so it doesn't consider any privileges on the groups in the path trace.
Example
request
$ curl -X "POST" "https://yourgrouper.yourschool.edu/grouper-ws/servicesRest/v4_0_000/gshTemplateExec" \
-H 'Content-Type: application/json' \
-u 'user:pass' \
-d $'{
"WsRestGshTemplateExecRequest": {
"ownerStemLookup": {
"stemName": "etc:templates"
},
"configId": "membershipTrace",
"ownerType": "stem",
"inputs": [
{
"name": "gsh_input_subjectId",
"value": "grahamb"
},
{
"name": "gsh_input_groupName",
"value": "resource:app:vpn:vpn-users"
}
],
"actAsSubjectLookup": {
"subjectId": "grahamb"
}
}
}' \
| jq -r .WsGshTemplateExecResult.wsOutput
response
{
"WsGshTemplateExecResult": {
"resultMetadata": {
"resultCode": "SUCCESS",
"resultMessage": "Success for: clientVersion: 4.0.0, configId: membershipTrace, ownerType: stem , inputs: Array size: 2: [0]: edu.internet2.middleware.grouper.ws.coresoap.WsGshTemplateInput@12e96496\n[1]: edu.internet2.middleware.grouper.ws.coresoap.WsGshTemplateInput@7681d6fe\n\n, actAsSubject: WsSubjectLookup[subjectId=grahamb], paramNames: \n, params: null",
"success": "T"
},
"responseMetadata": {
"millis": "124",
"serverVersion": "4.20.3"
},
"transaction": true,
"gshValidationLines": [],
"gshOutputLines": [],
"wsOutput": "[ {\n \"type\" : \"effective\",\n \"path\" : [ \"basis:role:hap-employees\", \"ref:role:employees\", \"ref:role:staff-faculty\", \"resource:app:vpn:role-includes\", \"resource:app:vpn:includes-rollup\", \"resource:app:vpn:vpn-users\" ],\n \"stringPath\" : \"basis:role:hap-employees → ref:role:employees → ref:role:staff-faculty → resource:app:vpn:role-includes → resource:app:vpn:includes-rollup → resource:app:vpn:vpn-users\"\n} ]"
}
}JSON-parse'd wsOutput
[
{
"type": "effective",
"path": [
"basis:role:hap-employees",
"ref:role:employees",
"ref:role:staff-faculty",
"resource:app:vpn:role-includes",
"resource:app:vpn:includes-rollup",
"resource:app:vpn:vpn-users"
],
"stringPath": "basis:role:hap-employees → ref:role:employees → ref:role:staff-faculty → resource:app:vpn:role-includes → resource:app:vpn:includes-rollup → resource:app:vpn:vpn-users"
}
]
Config
grouper.properties
grouperGshTemplate.membershipTrace.folderShowOnDescendants = certainFolders
grouperGshTemplate.membershipTrace.folderShowType = certainFolders
grouperGshTemplate.membershipTrace.folderUuidToShow = etc\u003Atemplates
grouperGshTemplate.membershipTrace.groupUuidCanRun = etc\u003Asysadmingroup
grouperGshTemplate.membershipTrace.input.0.description = Subject ID
grouperGshTemplate.membershipTrace.input.0.formElementType = text
grouperGshTemplate.membershipTrace.input.0.label = Subject ID
grouperGshTemplate.membershipTrace.input.0.name = gsh_input_subjectId
grouperGshTemplate.membershipTrace.input.0.required = true
grouperGshTemplate.membershipTrace.input.0.type = string
grouperGshTemplate.membershipTrace.input.0.validationType = none
grouperGshTemplate.membershipTrace.input.1.description = Group Name
grouperGshTemplate.membershipTrace.input.1.formElementType = text
grouperGshTemplate.membershipTrace.input.1.label = Group Name
grouperGshTemplate.membershipTrace.input.1.name = gsh_input_groupName
grouperGshTemplate.membershipTrace.input.1.required = false
grouperGshTemplate.membershipTrace.input.1.type = string
grouperGshTemplate.membershipTrace.input.1.validationType = none
grouperGshTemplate.membershipTrace.numberOfInputs = 2
grouperGshTemplate.membershipTrace.runAsType = currentUser
grouperGshTemplate.membershipTrace.securityRunType = specifiedGroup
grouperGshTemplate.membershipTrace.showOnFolders = true
grouperGshTemplate.membershipTrace.templateDescription = Runs a membership trace on the specified subject and group
grouperGshTemplate.membershipTrace.templateName = Membership Trace
grouperGshTemplate.membershipTrace.templateType = gsh
grouperGshTemplate.membershipTrace.templateVersion = V2
GSH Script
import edu.internet2.middleware.grouper.GrouperSession;
import edu.internet2.middleware.grouper.SubjectFinder;
import edu.internet2.middleware.grouper.GroupFinder;
import edu.internet2.middleware.grouper.FieldFinder;
import edu.internet2.middleware.grouper.app.gsh.template.GshTemplateOutput;
import edu.internet2.middleware.grouper.app.gsh.template.GshTemplateRuntime;
import edu.internet2.middleware.grouper.app.gsh.template.GshTemplateV2;
import edu.internet2.middleware.grouper.app.gsh.template.GshTemplateV2input;
import edu.internet2.middleware.grouper.app.gsh.template.GshTemplateV2output;
import edu.internet2.middleware.grouper.util.GrouperUtil;
import edu.internet2.middleware.subject.Subject;
import edu.internet2.middleware.grouper.membership.MembershipPathGroup;
import edu.internet2.middleware.grouper.membership.MembershipPath;
import edu.internet2.middleware.grouper.membership.MembershipPathNode;
import com.fasterxml.jackson.databind.ObjectMapper;
// you need a class that extends GshTemplateV2
public class MembershipTraceTemplate extends GshTemplateV2 {
// implement the gshRunLogic method, this is what is called when the template executes
public void gshRunLogic(GshTemplateV2input gshTemplateV2input, GshTemplateV2output gshTemplateV2output) {
GshTemplateOutput gsh_builtin_gshTemplateOutput = gshTemplateV2output.getGsh_builtin_gshTemplateOutput();
String gsh_builtin_ownerStemName = gshTemplateV2input.getGsh_builtin_ownerStemName();
String gsh_input_subjectId = gshTemplateV2input.getGsh_builtin_inputString("gsh_input_subjectId");
String gsh_input_groupName = gshTemplateV2input.getGsh_builtin_inputString("gsh_input_groupName");
GrouperSession grouperSession = GrouperSession.startRootSession();
Subject subject = SubjectFinder.findById(gsh_input_subjectId);
Group group = GroupFinder.findByName(grouperSession, gsh_input_groupName, true);
Field field = FieldFinder.find("members");
MembershipPathGroup membershipPathGroup = MembershipPathGroup.analyze(group, subject, field);
List<Map<String, Object>> allPaths = [];
for (MembershipPath path : membershipPathGroup.getMembershipPaths()) {
List<String> pathNodes = [];
List<MembershipPathNode> nodes = path.getMembershipPathNodes();
for (int i = 0; i < nodes.size(); i++) {
MembershipPathNode node = nodes.get(i);
Group nodeGroup = node.getOwnerGroup();
String nodeName = nodeGroup.getName();
pathNodes.add(nodeName);
}
Map<String, Object> pathMap = new LinkedHashMap<>();
pathMap.put("type", path.getMembershipType().toString().toLowerCase());
pathMap.put("path", pathNodes);
pathMap.put("stringPath", pathNodes.join(" → "));
allPaths.add(pathMap);
}
ObjectMapper mapper = new ObjectMapper();
String jsonOutput = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(allPaths);
gsh_builtin_gshTemplateOutput.setWsOutput(jsonOutput);
}
}
gsh_builtin_gshTemplateRuntime.assignGshTemplateV2(new MembershipTraceTemplate());