Paul’s blog

Random comments on Idm, Architecture, and other projects

Paul’s blog header image 2

Accessing complex soap web services via mutual X.509 authentication from Groovy, using the MIT Roles web service as an example

November 8th, 2010 · No Comments

MIT Information Services has developed and supports a small number of soap based web services which interact with various infrastructure components. Basic information about the web services can be found here. One of the recurring design patterns of these services is the use of mutual authentication, using X.509 certificates. The servers each have an MIT server certificate (e.g. hostname.mit.edu). Callers of the web service must also have an MIT certificate, but these are normally application certificates, existing in the app.mit.edu namespace. (e.g. your-app-name.app.mit.edu).

Application certificates are used so that you can have one certificate which is used from multiple hosts. This is helpful when using certain clustering and fail over techniques. It’s also useful when you have multiple applications hosted on a single server. It’s easy to manage the different privileges assigned to each of the applications when they are using different application certificates. Information about acquiring application certificates and configuring a Java keystore can be found here.

I recently needed to track down and resolve some bugs in the Roles web service. As part of the task I needed to repeatedly call many of the APIs using a data set of over 250 users. I decided that using some Groovy scripts would be a quick way to do this. This article describes many of the steps involved, and some of the things I learned along the way.

Preparing Roles

As mentioned above, you’ll need to obtain an MIT application certificate in order for your code to authenticate to the Roles web service. Several steps also need to be taken to prepare the Roles system to accept and use your application certificate.

Once you know the name which will appear in the application certificate, you’ll need to have an external identity established in Roles. This is simply a mapping from the name in the application certificate to an eight  character identifier in Roles, which can be used as a Subject of an Authorization.

In my case the name in my application certificate is ‘paulsprototypes.app.mit.edu’. An external identifier was created in Roles with the identifier ‘pbh$dev’. By convention most of the external identifiers created in Roles contain a ‘$’, this convention is intended to make it clear the identifier is not a normal MIT username, and will not conflict with any MIT Kerberos user names.

Requests to set up an external identifier should be sent to the ‘business-help’ mailing list.

Next, your application will have to be granted the necessary privileges to make calls to the Roles web service. You’ll need at least two authorizations to be assigned. First you need to know which Roles category your application needs to access, in general an application will not be given access to all categories. Applications typically only need access to one category.  Once you know the category name, send mail to ‘business-help’ asking for authorizations for:


META + + RUN ROLES SERVICE PROCEDURES +
META + + VIEW AUTH BY CATEGORY +

If I want my application certificate to be able to use the web service to access to Math Blog category, I would send mail asking for the following:


META + PBH$DEV + RUN ROLES SERVICE PROCEDURES + CATMATB
META + PBH$DEV + VIEW AUTH BY CATEGORY + CATMATB

Finally, you’ll also need to get operations to update a property file on the roles web service server. Send mail to ‘roles-support’ telling them your application certificate needs access to the Roles web service. In the message, include the name from your application certificate.

A summary of the preparatory steps are:

  1. Determine which Roles Category you will be using
  2. Obtain an application certificate, which your application will use to authenticate to the web service
  3. Have an external identifier for your application created in Roles
  4. Have the necessary roles authorizations assigned to the external identifier
  5. Have operations enable your application certificate for access to the roles web service server

Getting Groovy ready to make a Roles web service call

The GroovyWS module is documented at http://groovy.codehaus.org/GroovyWS. It can be used to consume and/or publish WS-I compliant web services. It is based on the Apache CXF services framework. Although the Roles web services uses the Axis 1.4 framework, the GroovyWS module easily interoperates with the Roles Web Service.

Using Groovy Grape, you can easily incorporate the GroovyWS module into a Groovy script.  I tend to think this is much easier than using Maven and an external POM fle.


@Grab(group='org.codehaus.groovy.modules', module='groovyws',version='0.5.2')
import groovyx.net.ws.WSClient

Since the Roles web service requires mutual authentication using X.509 certificates you also need to configure Java keystores, and configure GroovyWS to use the keystores. Once again, information about configuring Java keystores with MIT certficates can be found here.

Getting GroovyWS to use the keystores is very easy:


Map certKeystore = [
"https.keystore":"/src/sts-test/wsdl-test/certs/paulsprototypes.jks",
"https.keystore.pass":"MyClientKeystorePassword",
"https.truststore":"/src/sts-test/wsdl-test/certs/serverTrustStore.jks",
"https.truststore.pass":"MyKeystorePasswordToTrustTheService"
]

myProxy = new WSClient(“https://rolesws.mit.edu/rolesws/services/roles?wsdl”, this.class.classLoader)
myProxy.setSSLProperties(certKeystore)
myProxy.initialize()

 

At this point your code is configured to use mutual authentication via X.509 certificates when communicating with the Roles web service.

The Roles web service returns complex types. Furthermore, the web service was written without much thought or testing done to see how it would interact with clients which were using frameworks other than AXIS to consume the web service. Many of the input and output parameters are named using conventions that are not well suited to Java and Groovy. Because of this, you can’t always use the WSDL to tell you exactly what your parameter names should be. For example, if the WSDL shows a parameter named function_name, you will have to use functionName in your code. If you examine the WSDL you’ll also see some functions use ‘userName’ as a parameter, while others use ‘UserName’. I’ve found that when calling these from Groovy, I always need to name the parameter ‘userName’.

A typical use of the Roles web service for an application is to determine if a user is authorized in a particular context. There are two APIs commonly used to do this.

‘isUserAuthorized’ can be used when the authorizations are always explicitly defined. If the authorizations may be defined by data driven rules, aka implied authorizations, then ‘isUserAuthorizedExt’ should be used instead.

We’ll take a look at ‘isUserAuthorizedExt’.


def iUAE = myProxy.create("rolesservice.IsUserAuthorizedExt")

Note, although the WSDL shows the function name as ‘isUserAuthorizedExt’, when creating the interface in the client, you have to use ‘IsUserAuthorizedExt’. Although when we actually call the function we will use the original case convention ‘isUserAuthorizedExt’.


iUAE.userName = "MMCGRATH" // Who we are making the query about
iUAE.functionCategory = "LIBP" // The Library Patrons category
iUAE.functionName = "ACCESS LIBRARY MATERIALS"
iUAE.qualifierCode = "LIB_SLOAN_A"
iUAE.proxyUserName = "PBH" // This could be your application identifier, or // in the case of a web application, it would usually be the authenticated
// user that is making the query
iUAE.realOrImplied = "B" // "R" means a ‘real’ or explicitly defined
// authorization. "I" means an authorization created by a rule, or
// an implied authorization. "B" means both R and I.

 

Note, setting ‘realOrImplied’ to “R” is equivalent to using the isUserAuthorized function instead of isUserAuthorizedExt.

The isUserAuthorized function returns a complex type, although it should also be a single value of true or false.


def result = myProxy.isUserAuthorized(
iUAE.userName,
iUAE.functionCategory,
iUAE.functionName,
iUAE.qualifierCode,
iUAE.proxyUserName,
iUAE.realOrImplied

)

if (null == result){
println “The result was null!”
}

if (“false” == result.toString() ){
println “${iUAE.userName} is not authorized.”
}

if (“true” == result.toString()){
println “${iUAE.userName} IS authorized for ${iUAE.functionName} + ${iUAE.qualifierCode} in Category: ${iUAE.functionCategory}”
}

 

 

Of course this type of call should always be encapsulated in a try / catch wrapper to handle exceptions.

Other Roles web service functions may return multiple values, of more complex types. In these cases you may need to iterate over the response and perform more complex parsing to interpret the results.

If you want to use the GroovyWS module in Grails, there is documentation about how to set this up manually here. There is also a Grails WSClient plugin which makes it very easy to add this functionality to a Grails application.

Let me know if you have questions or if you would like to see examples in Perl, PHP, or other languages.

Tags: Groovy · Roles

0 responses so far ↓

  • There are no comments yet...Kick things off by filling out the form below.

You must log in to post a comment.