Saturday, June 4, 2011

How to do NTLMv2 authentication in TIBCO BusinessWorks

As a proof of concept I had to test if TIBCO could perform authentication from its BusinessWorks suite to a Microsoft Dynamics CRM web service using ‘Integrated Windows Authentication’.
TIBCO BusinessWorks has all the necessary tools for connectivity, transformation and orchestration of processes but unfortunately it has no support for Integrated Windows Authentication. But I don’t consider it as a flaw of TIBCO BusinessWorks. Integrated Windows Authentication is specific to Microsoft products and the protocol that is currently in scope for the POC, NTLM, is a proprietary protocol.

What is the goal of the POC?

Authenticate TIBCO when calling the Microsoft Dynamics CRM web service. The authentication needs to be done using the NTLMv2 protocol. The account I use is a designated system account for TIBCO, which has received the correct access.



How did I start?

A lot of developers think: ‘what I do, I do better’. Well, I am more in favor of ‘use instead of build’. So first I started to find solutions on the internet that might do the trick for us. Since that didn’t work out well, I started to use some libraries that implement NTLM and to see if it works with TIBCO BusinessWorks.

I also wanted to find a solution as fast as possible. So instead of trying to investigate further on why something doesn’t work by the book, I just tried a different library/application.


So here is a summary of things I’ve tried:



Proxy solutions:

NTLMAPS: This is a tool that was used at a client side but stopped working for them after they switched to a new Active Directory domain. For my POC, and using the latest NTLMAPS version, I constantly received a 401 error back. So I had to quickly give up on this.

CNTLM: A rewrite of NTLMAPS and I managed to get authenticated when I was trying it from a Non-MS browser like chrome or Firefox. However the tool was prompting me for credentials for user authentication, which were then used for NTLM authentication. I quickly tried to configure my SOAP Request-Reply activity using HTTP Authentication and a correctly set Identity but unfortunately it didn't work. I didn’t investigate further on this.


Since the proxy solutions did not work out well, I tried to use a Java Code activity and tried to use libraries implementing the NTLM protocol.



Client solutions:


According to the documentation on Apache it should support NTLMv2 but I didn’t manage to get it to work. Although following the guidelines, authentication was always failing with a 401 error. Maybe I was doing something wrong but since TIBCO BusinessWorks is also using (an older) HTTPClient in its third party library repository, I decided not to investigate further on this.  Just to be sure that an upgrade would cause a nasty side effect.



On http://devsac.blogspot.com/2010/10/supoprt-for-ntlmv2-with-apache.html I found an interesting article about configuring the HTTPClient 3.x of Apache with the JCIFS library to get NTLM support.

I didn’t try this one because on the site of JCIFS, they themselves recommend to use the Jespa library if you’re looking for full NTLM support.


Unfortunately the Jespa library is not open-source and has some limitations when you integrate directly with Active Directory. However in my situation I only needed a small portion of this library. I needed to establish a connection and needed a provider that will authenticate against the NTLMv2 protocol. So for my POC there is no impact.



Proxy setup

I’ve made a small TIBCO BW project, which can act as a proxy, between TIBCO BusinessWorks and MS Dynamics CRM web services. This service is working identically as the NTLMAPS application. It will sit as a proxy between the Soap Request-Reply activities and the endpoint.
 
How does the Forward request activity look like?


1)     First I defined some input parameters so I could dynamically configure my process:

 
2)     Configuring the Java Code: Updating the import statements
import java.util.*;
import java.io.*;
import java.net.URL;
import java.security.PrivilegedExceptionAction;
import jespa.http.HttpURLConnection;
import jespa.security.PasswordCredential;
import jespa.security.RunAs;
3)     Configuring the Java Code: Add an inner class

This class will perform the POST action and return the soap reply.
public class HttpPost implements PrivilegedExceptionAction
{

private URL url = null;
private HttpURLConnection conn = null;
private OutputStreamWriter wout = null;
private BufferedReader rd  = null;
private StringBuilder sb = null;
private String line = null;
private String responseMessage = null;
private int responseCode = 0;
private String responseBody = null;
private String endpoint;

public HttpPost(String endpoint){
       this.endpoint = endpoint;
}

public Object run() throws Exception
{

       url = new URL(endpoint);
       conn = new HttpURLConnection(url);
       try {
       conn.setDoOutput(true);
       conn.setDoInput(true);
       conn.setRequestMethod("POST");
       conn.addRequestProperty("SOAPAction", soapAction);
       conn.addRequestProperty("Content-Type", contentType);
       conn.setReadTimeout(timeout);

       // Set the input
       wout = new OutputStreamWriter( conn.getOutputStream() );
       wout.write(soapRequest);
       wout.flush(); // this triggers the POST
       wout.close();

       // Get the response
       rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
       sb = new StringBuilder();
       while ((line = rd.readLine()) != null) {
             sb.append(line + "\n");
       }
       rd.close();
      
        } catch (IOException ioe) {
             System.err.println(ioe.getMessage()); // such as '404 Not Found'
             rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
             sb = new StringBuilder();
             while ((line = rd.readLine()) != null) {
                    sb.append(line + "\n");
             }
             rd.close();
        } finally {
             responseCode = conn.getResponseCode();
             responseMessage = conn.getResponseMessage();
             responseBody = sb.toString();
             conn.disconnect();
             wout=null;
             rd = null;
             sb = null;
             conn = null;
        }
        return null;
}

public int getResponseCode()
{
       return this.responseCode;
}

public String getResponseMessage()
{
       return this.responseMessage;
}

public String getResponseBody()
{
       return this.responseBody;
}

}

4)     Implement the invoke function
org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger("bw.logger");
HttpPost t = new HttpPost(endpoint);
RunAs.runAs(t, new PasswordCredential(domain + "\\" + userName, password.toCharArray()));
logger.info("Server replied with HTTP status code: " + t.getResponseCode() + " " + t.getResponseMessage());
soapReply = t.getResponseBody();

Using the proxy class

When configuring my Soap Request-Reply message, I only need to configure a Proxy Configuration which points to my HTTP Receiver. My HTTP Receiver will forward the request and returns back the correct response.



Update: As some readers have commented, there seems to be a bug inside the above code. 

The updated project can be downloaded here. This project has updated java code that improves the handling of the soap request/response. You'll have to change the global variables so the authentication group is updated with your login credentials.
Also note that since BusinessWorks version 5.10, Tibco has added NTLM authentication support. See the release notes at https://docs.tibco.com/

Author: Günther

12 comments:

  1. Wow, great article. I work for TIBCO and am impressed with what you did here. Good to know about the Jespa library. :)

    Thanks,
    Alex.

    ReplyDelete
  2. Hi Günther,
    I followed your instructions, but I have this error:

    While executing [invoke] encountered [java.security.PrivilegedActionException] : [null at java.security.AccessController.doPrivileged(Native Method)]
    BW-JAVA-100001
    invoke
    java.security.PrivilegedActionException
    null at java.security.AccessController.doPrivileged(Native Method)


    Do you know how can I solve it?
    Regards

    Elia

    ReplyDelete
  3. I have the same problem as Elia :(

    ReplyDelete
  4. Hi Elia, Rami,

    We've continued development on it and I thought it's more easy to share the project than to update the code :-) The project can be download here: https://docs.google.com/open?id=0B9yIgxv3SNZkSEU3ZzROZEdDY1E

    You'll see that the code is changed a bit compared to the blog entry.
    Main improvement is on using the ExecutorService class to monitor the time a request is taking. There are also some bugfixes that can explain the nullpointer exception.

    We're using it now already for almost one year in production without problems.
    You'll have to change the global variables to the correct user credentials. These should be the windows domain users credentials that have access to the soap webservices.

    I also want to share with you that since BW 5.10, Tibco has added support on NTLM authentication. However I've not used nor experimented with it yet.

    regards

    ReplyDelete
  5. Hi,
    We have to invoke MS CRM 4.0 webservices from Tibco BW. I am getting an error "Credentials cannot be used for NTLM authentication". I came across your blog and have downloaded your project. As I am new to this Tibco I dont know how to deploy this. Can you please guide me in this?

    Thanks,
    Boopesh

    ReplyDelete
  6. This comment has been removed by a blog administrator.

    ReplyDelete
  7. This comment has been removed by the author.

    ReplyDelete
  8. Hello, first of all thank for the post and code.
    Im facing an error while executing the java activity.
    Can you help me ?

    Thanks,
    Joao

    Some of the request:

    text/xml;charset=UTF-8
    UTF-8
    60000

    Error:

    BW-JAVA-100001
    invoke
    java.util.concurrent.ExecutionException

    java.security.PrivilegedActionException: java.io.IOException: Request must be sent (by closing the stream) before the response can be received at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:232)

    ReplyDelete
    Replies
    1. Hi,
      Were you able to get past this error? I am also stuck with the same error.

      Regards,
      Aditi.

      Delete
  9. This comment has been removed by a blog administrator.

    ReplyDelete
  10. How did you configure "Proxy Configuration" to invoke your "HTTP Receiver". Could you please attach a screenshot to illustrate?

    ReplyDelete
  11. Hi. Is this NTLMv1 or NTLMv2? I think TIBCO still doesnt support NTLMv2. Can you confirm please

    ReplyDelete