Using OpenSAML from JRuby

We’ve had a lot of luck using JRuby on various different projects. One of the great advantages of JRuby is that you have the whole library of existing Java utilities at your disposal, plus all of the existing Ruby gems. I recently was able to integrate OpenSAML into a Ruby on Rails app. This was a great win because we needed functionality that wasn’t implemented in any of the Ruby SAML libraries.

However, what really struck me about it was that, even conflating the styles and conventions of both Ruby and Java together, the resulting code was more clean, readable, and terse than the original Java (in my opinion, at least.)

Below an example of creating a SAML message in Ruby using OpenSAML.

In no small part, this follows this OpenSAML example, but I like the way the Ruby turned out. I’ve also skipped some of the really boring bits like Ruby require statements and Java imports.

1. Define a Helper

First, we define a helper to build things:

def create(type)
  if type.kind_of?(Class) or type.kind_of?(Module)
    type = type.DEFAULT_ELEMENT_NAME
  end
  org.opensaml.xml::Configuration.builder_factory.get_builder(type).build_object(type)
end

Here’s the Java this was based on, though I’ve added a bit to my version:

@SuppressWarnings ("unchecked")
public  T create (Class cls, QName qname)
{
    return (T) ((XMLObjectBuilder)
      Configuration.getBuilderFactory ().getBuilder (qname)).buildObject (qname);
}

The Ruby duck typing does shorten things a lot. While there are legitimate criticisms about duck typings usefulness the OpenSAML API’s heavy use of factories encourage casting anyway, so it is nice to save ourselves some typing.

2. Bootstrap the Library

Now, bootstrap the library:

DefaultBootstrap.bootstrap()

3. Instantiate Objects

Next, we can instantiate some objects to represent the SAML messages:

name_id = create(NameID)
name_id.format = NameIDType.TRANSIENT
name_id.value = "anonymous-1234567890"

subject = create(Subject)
subject.name_id = name_id

issuer = create(Issuer)
issuer.value = "myissuer.com"

assertion = create(Assertion)
assertion.setID "some_id_whatever"
assertion.subject = subject

response = create(Response)
response.signature = signature
response.issuer = issuer
response.assertions << assertion

response.status = create(Status)
response.status.status_code = create(StatusCode)
response.status.status_code.value = StatusCode.SUCCESS_URI

This is the stage where I feel like we've had a big win: We've been able to remove a lot of extraneous text and reduced this down to the things we really care the most about — the message we want to send. Ruby's ability to pass a reference around to just about anything means I was able to simplify the use of the factories to create each node, and JRuby's translation of accessors into attr_accessor-like behavior means we've cut out a lot of the most repetitive pieces.

4. Create XML Objects

And to make it ready to use, we can marshall it into a series of XML objects:

document = document_builder.newDocument
marshaller_factory.get_marshaller(response).marshall response, document

tf = TransformerFactory.newInstance()
transformer = tf.newTransformer()
writer = StringWriter.new()
transformer.transform(DOMSource.new(document), StreamResult.new(writer))

response_string = writer.buffer.to_s

Rails may not be the right tool for every job, but it provides a lot of nice capabilities. Combined with the huge ecosystem of mature Java libraries, it can be a powerful tool.