Using Ruby, SOAP, and Apache to implement SSO for SalesForce.com

Since I have been talking about Java vs. Ruby, I figured I would give a recent example of where Ruby really solved my problems simply and easily.  My company uses SalesForce.com for their CRM solution.  Our sales, finance, and fulfillment teams have logins that are maintained and managed through the website.  This is a pain for both the users and our IT team as people now have to remember their SalesForce username and password as well as their internal user name and password.

To combat this problem, SalesForce has created a method that allows authentication of users via a SOAP request.  Very cool, solves the problem, so I signed up to implement it.  SalesForce supplies a WSDL documenting the services that need to be supported and you supply the URL that implements the defined services.  Should be simple to connect to our ActiveDirectory server using LDAP to perform the validation of users.

The Initial Attempt: Using Java Web Services…

Our standard development environment is Java 5, Maven 2 for builds, Apache 2 for HTTP(S), and JBoss 4.0.5 as an application server.  I thought it would be a simple exercise to use JAX-WS combined with JAX-WS Maven Plugin for auto-generation of my stubs and the JBoss WS to simply deploy.  First problem was getting the Maven plugin working correctly.  After some trial and error, I got my POM setup correctly and was deploying the EAR.  Unfortunately, I realized that JBoss was assuming that Tomcat was running on port 8080, causing a problem in the WSDL that was being referenced.  So I had to find the JBoss property to tweak to fix that.  Next problem wound up being that the default org.jboss.ws.soap.SOAPMessageImpl did not implement the setProperty method causing an UnsupportedOperationException.  Awesome.  To try to fix that issue, I wound up trying the following (in different combinations):

  • Upgrading JBoss to 4.2.1.
  • Upgrading JBoss WS to 2.0 with JBoss 4.2.1
  • Trying Java 6 as JAX-WS is in the standard JDK with both JBoss 4.0.5 and JBoss 4.2.1

With all of these combinations, I kept running into class loading issues and little help on the web.  After much frustration, I said to hell with this, how can I do it in ruby….

SOAP4R to the rescue

SOAP4R is the best know SOAP library for ruby and the gem is easily installed

> gem install soap4r –source http://dev.ctor.org/download/

Now I could auto-generate my stubs from the WSDL and put in my business logic.

Generating Stubs from the WSDL

In general, the documentation for soap4r is non-helpful, but by looking at the source code and googling around a bit, I was able to get the gist.  From the supplied SalesForce WSDL, I was able to quickly generate my stubs:

require ‘rubygems’
gem ‘soap4r’
require ‘wsdl/soap/wsdl2ruby’
DIR = File.dirname(“.”)
gen = WSDL::SOAP::WSDL2Ruby.new
gen.basedir=File.dirname(DIR)
gen.location=File.join(DIR,”AuthenticationService.wsdl”)
gen.logger.level=Logger::DEBUG
gen.opt[‘classdef’] = “SforceAuth”
gen.opt[‘client_skelton’] = nil
gen.opt[‘servant_skelton’] = nil
gen.opt[‘cgi_stub’] = nil
gen.opt[‘standalone_server_stub’] = nil
gen.opt[‘mapping_registry’] = nil
gen.opt[‘driver’] = nil
gen.opt[‘force’] = true
gen.run

 Running this produces seven files:

File Name Purpose
SforceAuthServant.rb Stub class that is used for the implementation.  The server side implementation code will need to go in here.
SforceAuthenticationService.rb If you would like to run a standalone server, this will be the file that you run.
SforceAuthenticationService.cgi If you would like to use a web server (e.g. Apache) and CGI, this will be the file that you use.
SforceAuthenticationServiceClient.rb If you would like to have a client to test your code, this will be the file that you run.
SforceAuth.rb Class definitions for the request object and the response object.
SforceAuthMappingRegistry.rb Class used to map SOAP requests and responses to ruby objects.
SforceAuthDriver.rb Driver class used by the client to call into the server.

 

Customizing the Generated Ruby Files to Get A Working System

Of the seven files created, I needed three of them (the client, the standalone server, and the CGI script) needed to be executable.  To get them to run, I needed to set the executable flag:

> chmod +x SforceAuthenticationService.rb SforceAuthenticationService.cgi SforceAuthenticationServiceClient.rb

In addition, because I am using Ruby Gems to manage my dependencies, I needed to add the following two lines to the top of each of these three files:

require ‘rubygems’
gem ‘soap4r

To test the implementation, I started up the standalone authentication server (which listens on port 10080 by default) and called it from the client.

> SforceAuthenticationService.rb &

> SforceAuthenticationServiceClient.rb http://localhost:10080

If all is working, you will get a NoMethodError exception being thrown.  To remedy this problem edit the SforceAuthServant.rb file and on line 15, change the line from raise NotImplementedError.new to {:authenticated => true}.  Restarting the standalone server and rerunning the client will result in success.

Now, I just needed to implement the code to call the LDAP server.  First step was obtaining and installing the Gem for the Ruby net-ldap project.  Validating the username and password was as simple as putting this code into authenticate method for the SforceAuthServant class:

def authenticate(parameters)

ldap = Net::LDAP.new
ldap.host = “ldap.hmsonline.com”
ldap.auth parameters.username[0], parameters.password[0]
if ldap.bind_as(
    :base => “dc=com”,
    :filter => “(sAMAccountName=”+parameters.username[0]+”)”,
    :password => “”
)
  return {:authenticated=>true}
else
  return {:authenticated=>false}
end

end 

Again, restarting the standalone server and using the client (with some tweaks to send the right parameters over) validated that the service was working successfully.

Configuring Apache to use the CGI

Obviously, I want SalesForce to use HTTPS when sending usernames and passwords.  We use
Apache as our web server for SSL.  To get the CGI set up, I made sure that the directory with the files was accessible from the Apache configuration and then added a .htaccess file

Options +ExecCGI
AddHandler cgi-script .cgi

Restarted Apache and I was done.  The whole exercise took about an hour, and that was with me figuring out what was going on.  The soap4r team has done a great job making setting up and and deploying a SOAP service quick, powerful, and easy to manage.