Making a SOAP call from IDM Policy

Making a SOAP call from IDM Policy

 

Approving a Workflow Programmatically

 



The IDM engine includes tokens for a great many functions, including a number of common workflow operations. These tokens are the bridge from the metadirectory (Novell) side of IDM to the workflow (SilverStream) side.

While many important API calls are represented, there are dozens and dozens that are not. Recently, I had occasion to wish to have a workflow react to actions taken by the IDM engine..

Specifically, the client had a complex business process represented in several loopback drivers. Their business process was to create new users with a random name in a staging container, and have the IDM engine rename the user to a format that was unique and matched their policy. What was desired was that ager the workflow had performed its Entity action (a write to the IDVault), the resulting name would be returned to the workflow, so it was recorded in the comment history, as well as so in some cases a REST call could be made to the service that had made the original PRD request with the final name.

In order to do this, we implemented a new java class that would accept a variable from the IDM engine in its native object class (NodeSet) and return it’s result in the same class. This makes it easy to use the XML tokens to prepare the XML before sending and retrieve results from the response.

 

What you gonna call?

 



My favorite tool for working with SOAP is SoapUI. This GUI tool allows you to import a Web Service Description Language (WSDL) document which most well designed SOAP endpoints have as a URL, and produce a list of the available API calls as well as generate samples.

To approve a workflow requires a similar call (“forwardAsProxy()”) but making that call from an IDM workflow requires either creating another workflow or deploying a custom java class.

The process for approving a policy is actually a two step process which requires one call to get the ID of the waiting process. To provide credit where credit is due, this while I had done this once before I could not remember where, but Fernando Freitas did document it in the forums for posterity. To see this post, go to:
https://forums.novell.com/showthread.php/497207-SOAP-forwardRequest-error-_Reason-Entity-not-found

The way that this type of a rule is created is based on the concept that you can leverage SoapUI to create sample documents by importing the WSDL. To do this, get the WSDL URL and reference it in.



SOAP UI will generate each of the API calls as separate requests.



The next step is to test the rules manually to verify that you understand what needs to be sent to take a particular action and/or return a particular result. Once you verify that, you can copy the request text directly into a text token inside Designer. I surround that with a parse-xml token to convert the text into a node-set.



The code then populates the form using XPath tokens such as:



Before are ready to make the call, you need to create variables to pass in all the parameters to the SOAPCALL class. These include:


    • URL

 

    • Username

 

    • Password

 

  • SOAPAction



Fortunately, since you have just succeeded in testing your SOAP call via SoapUI, you have several of these parameters in the panel in the lower left quadrant (just click on your sample request)



The one parameter that’s not here is SOAPAction, but that becomes visible when you select the API name (for example, start):



Once you have populated all the necessary variables, you can invoke the java class by calling do-set-local-variable to set a node-set variable representing the response, passing in connection parameters as well as the request as a node-set.



Specific values can be parsed out of the result in this manner using an XPath token:



Generally, this is accomplished by pasting the response document from SoapUI and use the XPath browser inside the XPath token in Designer to determine the appropriate XPath expression to return the text you are looking for.

I have developed such a class. While it’s “custom” from the perspective that it’s not part of the product, it’s generic in that it can make any SOAP call. It is designed to easily integrate into IDM policies so that parameters can be set and returned data can be retrieved using the standard XPATH tokens.

Deploying the Class

The class was provided within the file SOAPCALL-0.6.0.jar. This file must be copied to the server where the driver is running (either the engine or remote loader) and placed in the subdirectory /opt/novell/eDirectory/lib/classes/dirxml. The engine or remote loader must be restarted for this to become usable.

Using the Class in a Policy

In order to use this class within a policy, it is necessary to declare it within the policy element as a namespace. Other namespaces for the requesting and responding XML documents should also be added to assure that the XML tokens will function correctly.

The following name space declaration has been added:

<policy 
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAPCALL="http://www.novell.com/nxsl/java/com.rareprogeny.idm.SOAP"
xmlns:nsl="http://www.novell.com/provisioning/service" xmlns:ser="http://www.novell.com/provisioning/service" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">




Using the Class in a Rule

This has been implemented within the following IDM rule:




The rule was populated by pasting the sample request from SoapUI into a text token and surrounding it with a parse-xml token. This was placed in a do-set-local-variable of type nodeset.

The nodeset can then be populated using XML tokens with parameter values. Additionally, string variables are used to store each of the parameters to be passed into the java class.

The class is invoked within another do-set-local-variable of type nodeset because the result returned on the SOAP call will be delivered to IDM as a nodeset.

The resulting nodeset can be parsed using the XML tokens to retrieve a value, which can be passed into a second call in the same manner.

 

SOAP Call Source Code

 



The entire SOAP call code is provided as a single java class.

package com.rareprogeny.idm;

/*

Class com.rareprogeny.idm.SOAPCALL

License

Copyright (c) 2018, Robert Rawson d/b/a Rare Progeny Software Publishing

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

About Module "SOAPCALL"

This Java class is a simple mechanism to make http or https calls from
NetIQ Identity Manager to a web service via SOAP. Leveraging the class
"NodeSet", it enables easy direct integration with the NetIQ IDM Meta-
Directory engine.

*/

import java.io.FileInputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.util.Base64;
import java.util.Hashtable;
import java.util.Set;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.w3c.dom.*;

import com.novell.nds.dirxml.driver.Trace;
import com.novell.nds.dirxml.driver.XmlDocument;
import com.novell.xml.xpath.NodeSet;


public class SOAP {

public static String KEYSTORE_PATH = System.getenv("JAVA_HOME") + "/jre/lib/security/cacerts";
public static String KEYSTORE_PWD = "changeit";
private static Hashtable headerOptions = new Hashtable();
public static String useProtocol = "TLSv1";
public static String characterEncoding = "UTF-8";
public static boolean noCertificateCheck = false;
public static Trace tracer;
private static boolean testMode = false;

public SOAP() {
// TODO Auto-generated constructor stub

}

public static void main(String[] args) {
// TODO Auto-generated method stub
testMode = true;
try
{
System.out.println(getStringFromDoc(httpRequest(args[0], getDocFromString(new String(Files.readAllBytes(Paths.get(args[1])))), args[2], args[3], args[4])));
}
catch (Exception e)
{
}
}

private static void tracertrace(String message, int level)
{
if (testMode)
{
System.out.println(message);
}
else
{
tracer.trace(message, level);
}
}

public static NodeSet httpRequest(NodeSet xml, String url, String soapAction, String userName, String password) {
if (!testMode) tracer = new Trace("[CIS] SOAP");

tracertrace("NodeSet httpRequest()",42);
tracertrace(xml.toString(),42);

Document xmlDocument = (Document) xml.getNativeValue();
Document resultDocument =httpRequest(url, xmlDocument, soapAction, userName, password);

tracertrace(" >>> response as a Document object >>> ",42);
tracertrace(getStringFromDoc(xmlDocument),42);
tracertrace(" <<< response as a Document object <<>> response as a NodeSet object >>> ",42);
NodeSet resultSet = new NodeSet();
resultSet.add((Node) resultDocument);
tracertrace(resultSet.getStringValue(),42);
tracertrace(" <<< response as a NodeSet object <<< ",42);

return resultSet;

}

private static Document httpRequest(String url, Document xml, String soapAction, String userName, String password) {
Document result;

tracertrace("URL: "+url,5);
tracertrace("SOAPAction: "+soapAction,5);
tracertrace("User Name: "+userName ,5);
tracertrace("*** Begin Request Document ***",3);
tracertrace(getStringFromDoc(xml), 3);
tracertrace("*** End Request Document ***",3);

try {
// Convert DOM to string
String soapXml = getStringFromDoc(xml);
// Set up connection
URL soapURL = new URL(url);

String protocol = url.split("/")[0].toLowerCase();
URLConnection conn = soapURL.openConnection();
HttpURLConnection hconn = (HttpURLConnection) conn;
hconn.setRequestMethod("POST");
hconn.setRequestProperty("Content-Length",
"" + Integer.toString(soapXml.getBytes().length));
hconn.setRequestProperty("Content-Type", "text/xml;charset=" + characterEncoding);
String authString = userName + ":" + password;
byte[] message = authString
.getBytes(StandardCharsets.UTF_8);
String authCred = Base64.getEncoder().encodeToString(message);
hconn.setRequestProperty("Authorization", "Basic "+authCred);
hconn.setRequestProperty("Accept-Encoding", "gzip,deflate");
hconn.setRequestProperty("Accept", "text/xml");




//hconn.setUseCaches(false);
hconn.setDoInput(true);
hconn.setDoOutput(true);
hconn.setRequestProperty("SOAPAction", soapAction);

if (protocol.equalsIgnoreCase("https:"))
{
HttpsURLConnection hsconn = (HttpsURLConnection) hconn;

System.setProperty("javax.net.ssl.trustStore", KEYSTORE_PATH);
System.setProperty("javax.net.ssl.trustStorePassword", KEYSTORE_PWD);

// System.setProperty("https.protocols", useProtocol);
SSLContext sc = SSLContext.getInstance(useProtocol);

// Init the SSLContext with a TrustManager[] and SecureRandom()
TrustManager[] tm = getTrustManager();


if (noCertificateCheck)
{
tm = omniTrust();
}

sc.init(new KeyManager[] {}, tm, null);

hsconn.setSSLSocketFactory(sc.getSocketFactory());

}

java.io.OutputStreamWriter wr = new java.io.OutputStreamWriter(
conn.getOutputStream(), characterEncoding);

// send request
tracertrace("Transmitting via "+protocol ,3);
System.out.println(soapXml);
wr.write(soapXml);
wr.flush();
wr.close();

if ((hconn.getResponseCode() != HttpURLConnection.HTTP_CREATED) && (hconn.getResponseCode() != HttpURLConnection.HTTP_OK)) {
tracertrace("Received http response code: " + new Integer(hconn.getResponseCode()).toString()+ " "+ hconn.getResponseMessage(),3 );
return errorDoc("Received http response code: "+ new Integer(hconn.getResponseCode()).toString(),hconn.getResponseMessage(),userName);
}

// collect response
java.io.BufferedReader rd = new java.io.BufferedReader(
new java.io.InputStreamReader(conn.getInputStream()));
String resultStr = "";
String line;

while ((line = rd.readLine()) != null) {
System.out.println(line);
resultStr += line;
}

tracertrace("*** Begin Response Document ***",3);
tracertrace(resultStr, 3);
tracertrace("*** End Response Document ***",3);

result = getDocFromString(resultStr);

} catch (Exception e) {
tracertrace(e.getMessage(),3);
return errorDoc(e.toString(), e.getMessage(),userName);

}
return result;
}



public static String getStringFromDoc(Document doc) {
try {
DOMSource domSource = new DOMSource(doc);
Transformer transformer = TransformerFactory.newInstance()
.newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION,
"yes");
transformer.setOutputProperty(OutputKeys.METHOD, "xml");
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(
"{http://xml.apache.org/xslt}indent-amount", "4");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StringWriter sw = new StringWriter();
StreamResult sr = new StreamResult(sw);
transformer.transform(domSource, sr);
String result = sw.toString();
return result;
} catch (Exception e) {
e.printStackTrace();
return "";
}

}

public static Document getDocFromString(String xmlString) {
try {
DocumentBuilder db = DocumentBuilderFactory.newInstance()
.newDocumentBuilder();
return db.parse(new InputSource(new StringReader(xmlString)));
} catch (Exception e) {
return null;
}

}




private static TrustManager[] getTrustManager() {
try {
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());

KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(KEYSTORE_PATH),
KEYSTORE_PWD.toCharArray());
tmf.init(ks);

// Get hold of the trust manager

for (TrustManager tm : tmf.getTrustManagers()) {
if (tm instanceof X509TrustManager) {
TrustManager[] tms = { tm };
return tms;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

private static TrustManager[] omniTrust() {
// Create a trust manager that does not validate certificate chains
return new TrustManager[] { new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}

public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}

public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
}
};
};

protected static Document errorDoc(String errorType, String errorDetail,
String actor) {
Document error = newDoc();
Element envelope = (Element) error.createElement("soap:Envelope");

envelope.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:soap",
"http://schemas.xmlsoap.org/soap/envelope/");

error.appendChild(envelope);
Element header = (Element) error.createElement("soap:Header");
Element body = (Element) error.createElement("soap:Body");
envelope.appendChild(header);
envelope.appendChild(body);

Element fault = (Element) error.createElement("soap:Fault");
body.appendChild(fault);

Element faultcode = (Element) error.createElement("faultcode");
Element faultstring = (Element) error.createElement("faultstring");
Element faultactor = (Element) error.createElement("faultactor");

fault.appendChild(faultcode);
fault.appendChild(faultstring);
fault.appendChild(faultactor);

faultcode.appendChild(error.createTextNode(blankNull(errorType,"No type returned")));
faultstring.appendChild(error.createTextNode(blankNull(errorDetail, "No detail returned")));
faultactor.appendChild(error.createTextNode(blankNull(actor,"Unknown actor")));
return error;

}

protected static String blankNull(String targ)
{
return blankNull(targ, "");
}

protected static String blankNull(String targ, String altText)
{
if (targ == null)
return altText;
else
return targ;
}
protected static Document newDoc() {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder builder = factory.newDocumentBuilder();
return builder.newDocument();
}

catch (ParserConfigurationException pce) {
pce.printStackTrace();
return null;
}

}


}





Labels (1)

DISCLAIMER:

Some content on Community Tips & Information pages is not officially supported by Micro Focus. Please refer to our Terms of Use for more detail.
Comments

Is it possible to get the actual JAR file mentioned

@gamaro  I can ask Rob about it.  We work together and we also have rolled the code backawards and forwards into a custom HTTP Shim that can be useful as well.

 

Hey geoffc

I'm actually looking at this for a customer of mine. I'm in the support
side of MicroFocus and my customer is looking to create a soap call
during event X.
I don't mind passing your info to them as a way to get them what they
want. They are a Quebec based company.
Top Contributors
Version history
Revision #:
2 of 2
Last update:
‎2020-01-30 11:27
Updated by:
 
The opinions expressed above are the personal opinions of the authors, not of Micro Focus. By using this site, you accept the Terms of Use and Rules of Participation. Certain versions of content ("Material") accessible here may contain branding from Hewlett-Packard Company (now HP Inc.) and Hewlett Packard Enterprise Company. As of September 1, 2017, the Material is now offered by Micro Focus, a separately owned and operated company. Any reference to the HP and Hewlett Packard Enterprise/HPE marks is historical in nature, and the HP and Hewlett Packard Enterprise/HPE marks are the property of their respective owners.