GSH template provisioner example for webhooks
This is a GSH template provisioner which calls webhooks when flattened memberships are added or removed from provisionable groups. A flattened membership change is when a person was not previously in the group, and now is in the group. Or a user who is in the group, and now is no longer in the group. If a current group member now has a membership from another path, then that is not a change.
Here is an example message:
action will be "membershipAdd" or "membershipRemove"
POST https://sonar.server.whatever/webhook-test/9e2123-234-345-456-56789
Grouper2SONAR_Token: abc123
{
"pennId": "10021368",
"groupName": "penn:nursing:etc:nursingTest",
"action": "membershipAdd",
"description": "Chris Hyzer (mchyzer, 10021368) (active) Staff - Isc-tech Services-network Operations - Application Architect (also: Alumni)",
"name": "Chris Hyzer",
"pennname": "mchyzer",
"email_public": "mchyzer@upenn.edu",
"name_last_public": "Hyzer",
"name_public": "Chris Hyzer",
"name_first_public": "Chris",
"eppn": "mchyzer@upenn.edu",
"preferred_first_name": "Chris",
"last_name": "Hyzer",
"first_name": "Chris"
}The authentication is name/value in HTTP header.
Assigning groups to be webhooks provisionable
People in this group can assign provisioning for the Nursing webhooks provisioner: etc:provisioning:nursing_webhook:nursingWebhookProvisionerUpdaters. The Nursing PennGroups admins group is in this group.
The Nursing webhooks provisioners are:
nursing_webhook_test
nursing_webhook_prod
Both of these are similar though point to different endpoints with different credentials.
External system
The endpoint and credentials have a config ID which must be the same as the provisioner ID.
Webhook GSH template
This custom template has the code to send messages to the webhook endpoint
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import com.fasterxml.jackson.databind.node.ObjectNode;
import edu.internet2.middleware.grouper.SubjectFinder;
import edu.internet2.middleware.grouper.app.externalSystem.WsBearerTokenExternalSystem;
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.app.gshTemplateProvisioner.GshTemplateProvisionerBase;
import edu.internet2.middleware.grouper.app.loader.GrouperLoaderConfig;
import edu.internet2.middleware.grouper.app.provisioning.ProvisioningMembership;
import edu.internet2.middleware.grouper.app.provisioning.ProvisioningObjectChange;
import edu.internet2.middleware.grouper.app.provisioning.targetDao.GrouperProvisionerDaoCapabilities;
import edu.internet2.middleware.grouper.app.provisioning.targetDao.GrouperProvisionerTargetDaoBase;
import edu.internet2.middleware.grouper.app.provisioning.targetDao.TargetDaoDeleteMembershipRequest;
import edu.internet2.middleware.grouper.app.provisioning.targetDao.TargetDaoDeleteMembershipResponse;
import edu.internet2.middleware.grouper.app.provisioning.targetDao.TargetDaoInsertMembershipRequest;
import edu.internet2.middleware.grouper.app.provisioning.targetDao.TargetDaoInsertMembershipResponse;
import edu.internet2.middleware.grouper.util.GrouperHttpClient;
import edu.internet2.middleware.grouper.util.GrouperHttpClientLog;
import edu.internet2.middleware.grouper.util.GrouperHttpMethod;
import edu.internet2.middleware.grouper.util.GrouperUtil;
import edu.internet2.middleware.subject.Subject;
import groovy.transform.CompileStatic;
@CompileStatic
public class Test137nursingWebhooksProvisioner extends GshTemplateV2 {
// add logger
private static final Log LOG = GrouperUtil.getLog(GshTemplateV2.class);
public class NursingWebhooksProvisionerTargetDao extends GrouperProvisionerTargetDaoBase {
@Override
public boolean loggingStart() {
return GrouperHttpClient.logStart(new GrouperHttpClientLog());
}
@Override
public String loggingStop() {
return GrouperHttpClient.logEnd();
}
private String gshTemplateConfigId = null;
public NursingWebhooksProvisionerTargetDao(String theGshTemplateConfigId) {
super();
this.gshTemplateConfigId = theGshTemplateConfigId;
}
public NursingWebhooksProvisionerTargetDao() {
super();
}
@Override
public void registerGrouperProvisionerDaoCapabilities(
GrouperProvisionerDaoCapabilities grouperProvisionerDaoCapabilities) {
grouperProvisionerDaoCapabilities.setCanDeleteMembership(true);
grouperProvisionerDaoCapabilities.setCanInsertMembership(true);
}
public void addAttribute(ObjectNode objectNode, Subject subject, String attributeName) {
String attributeValue = subject.getAttributeValue(attributeName);
if (!GrouperUtil.isBlank(attributeValue)) {
objectNode.put(attributeName, attributeValue);
}
}
public void processMembership(ProvisioningMembership targetMembership, String action) {
String jsonString = "";
try {
String pennId = targetMembership.getProvisioningEntityId();
Subject subject = SubjectFinder.findByIdAndSource(pennId, "pennperson", true);
// null if a group was added or a service principal
if (subject != null) {
GrouperHttpClient grouperHttpClient = new GrouperHttpClient();
grouperHttpClient.assignDoNotLogHeaders(GrouperUtil.toSet("Grouper2SONAR_Token"));
WsBearerTokenExternalSystem.attachAuthenticationToHttpClient(grouperHttpClient, this.gshTemplateConfigId);
grouperHttpClient.assignGrouperHttpMethod(GrouperHttpMethod.post);
grouperHttpClient.assignUrl(GrouperLoaderConfig.retrieveConfig().propertyValueStringRequired("grouper.wsBearerToken." + this.gshTemplateConfigId + ".endpoint"));
grouperHttpClient.addHeader("Content-Type", "application/json");
String groupName = targetMembership.getProvisioningGroupId();
ObjectNode objectNode = GrouperUtil.jsonJacksonNode();
objectNode.put("pennId", pennId);
objectNode.put("groupName", groupName);
objectNode.put("action", action);
if (!StringUtils.isBlank(subject.getDescription())) {
objectNode.put("description", subject.getDescription());
}
// add name
if (!StringUtils.isBlank(subject.getName())) {
objectNode.put("name", subject.getName());
}
List<String> attributes = GrouperUtil.toList("pennname", "email_public", "name_middle_public", "name_last_public",
"name_public", "name_first_public", "eppn", "preferred_first_name",
"last_name", "first_name");
for (String attr : attributes) {
addAttribute(objectNode, subject, attr);
}
jsonString = GrouperUtil.jsonJacksonToString(objectNode);
grouperHttpClient.assignBody(jsonString);
grouperHttpClient.assignAssertResponseCode(200);
grouperHttpClient.executeRequest();
}
targetMembership.setProvisioned(true);
for (ProvisioningObjectChange provisioningObjectChange : GrouperUtil.nonNull(targetMembership.getInternal_objectChanges())) {
provisioningObjectChange.setProvisioned(true);
}
} catch (RuntimeException re) {
GrouperUtil.injectInException(re, "Error provisioning membership: " + targetMembership.getProvisioningEntityId() + ", " + targetMembership.getProvisioningGroupId() + ", jsonString: " + jsonString + ", ");
targetMembership.setProvisioned(false);
targetMembership.setException(re);
for (ProvisioningObjectChange provisioningObjectChange : GrouperUtil.nonNull(targetMembership.getInternal_objectChanges())) {
provisioningObjectChange.setProvisioned(false);
}
if (LOG.isErrorEnabled()) {
LOG.error("Error", re);
}
}
}
@Override
public TargetDaoDeleteMembershipResponse deleteMembership(
TargetDaoDeleteMembershipRequest targetDaoDeleteMembershipRequest) {
ProvisioningMembership targetMembership = targetDaoDeleteMembershipRequest.getTargetMembership();
processMembership(targetMembership, "membershipRemove");
return new TargetDaoDeleteMembershipResponse();
}
@Override
public TargetDaoInsertMembershipResponse insertMembership(
TargetDaoInsertMembershipRequest targetDaoInsertMembershipRequest) {
ProvisioningMembership targetMembership = targetDaoInsertMembershipRequest.getTargetMembership();
processMembership(targetMembership, "membershipAdd");
return new TargetDaoInsertMembershipResponse();
}
}
public class NursingWebhooksProvisionerGshTemplate extends GshTemplateProvisionerBase {
private String gshTemplateConfigId = null;
public NursingWebhooksProvisionerGshTemplate(String theGshTemplateConfigId) {
super();
this.gshTemplateConfigId = theGshTemplateConfigId;
}
public NursingWebhooksProvisionerGshTemplate() {
super();
}
@Override
protected Class<? extends GrouperProvisionerTargetDaoBase> grouperTargetDaoClass() {
return NursingWebhooksProvisionerTargetDao.class;
}
@Override
protected GrouperProvisionerTargetDaoBase grouperTargetDaoInstance() {
return new NursingWebhooksProvisionerTargetDao(this.gshTemplateConfigId);
}
}
@Override
public void gshRunLogic(GshTemplateV2input gshTemplateV2input,
GshTemplateV2output gshTemplateV2output) {
// nursing_webhook_test, nursing_webhook_prod
String templateConfigId = gshTemplateV2input.getGsh_builtin_gshTemplateRuntime().getTemplateConfigId();
gshTemplateV2output.getGsh_builtin_gshTemplateOutput().assignGrouperProvisioner(new NursingWebhooksProvisionerGshTemplate(templateConfigId));
}
}