Not only is it possible to write an ALUI Identity Service in Ruby on Rails, it's remarkably easy. I was able to do the entire authentication part in fewer than 15 lines of code! However, I ran into problems on the synchronization side and ended up writing that part in Java. Read on for all the gory details.
As part of building the suite of social applications for BEA Participate 2008, we're designing a social application framework in Ruby on Rails and integrating it with ALI 6.5. Not being a big fan of LDAP, I decided to put the users of the social application framework in the database (which is MySQL). Now, when we integrate with ALI, we need to sync this user repository (just as many enterprises do with Active Directory or LDAP).
So I set out to build an IDS to pull in users, groups and memberships in Ruby on Rails.
It's pretty obvious that Ruby on Rails favors REST over SOAP for their web service support. However, they still support SOAP for interoperability and it mostly works. I did have to make one patch to Ruby's core XML processing libraries to get things humming along. I haven't submitted the patch back to Ruby yet, but at some point I will. Basically, the problem was that the parser didn't recognize the UTF-8 encoding if it was enclosed in quotes ("UTF-8"). This patch suggestion guided me in the right direction, but I ended up doing something a little different because the suggested patch didn't work.
I changed line 27 of lib/ruby/1.8/rexml/encoding.rb as follows:
enc = enc.nil? ? nil : enc.upcase.gsub('"','') #that's a double quote inside single quotes
Now that Ruby's XML parser recognized UTF-8 as a valid format, it decided that it didn't support UTF-8! To work around this, I installed iconv, which is available for Windows and *nix and works seamlessly with Ruby. In fact, after installation, all the XML parsing issues went bye-bye.
Now, on to the IDS code. From your rails project, type:
ruby script/generate web_service Authenticate
This creates app/apis/authenticate_api.rb. In that file, place the following lines of code:
class AuthenticateApi < ActionWebService::API::Base
api_method :Authenticate, :expects => [{:Username =>
:string}, {:Password =>
:string}, {:NameValuePairs =>
[:string]}], :returns =>
[:string]
end
All you're doing here is extending ActionWebService
and declaring the input/output params for your web service. Now type the following command:
ruby script/generate controller Authenticate
This creates the controller, where, if you stick with direct dispatching (which I recommend), you'll be doing all the heavy lifting. (And there isn't much.) This file should contain the following:
class AuthenticateController < ApplicationController
web_service_dispatching_mode :direct
wsdl_service_name 'Authenticate'
web_service_scaffold :invokedef Authenticate(username, password, nameValuePairs)
if User.authenticate(username, password)
return ""
else
raise "-102" #generic username/password failure code
end
end
end
Replace User.authenticate with whatever mechanism you're using to authenticate your users. (I'm using the login_generator gem.) That's all there is to it! Just point your AWS to http://localhost:3000/authenticate/api
and you're off to the races.
Now, if you want to do some functional testing (independently of the portal), rails sets up a nice web service scaffold UI to let you invoke your web service and examine the result. Just visit http://localhost:3000/authenticate/invoke
to see all of that tasty goodness.
There you have it -- a Ruby on Rails-based IDS for ALUI in fewer than 15 lines of code!
The synchronization side of the IDS was almost just as simple to write, but after countless hours of debugging, I gave up on it and re-wrote it in Java using the supported ALUI IDK. Although I never could quite put my finger on it, it seemed the problem had something to do with some subtleties about how BEA's XML parser was handing UTF-8 newlines. I'll post the code here just in case anyone has an interest in trying to get it to work. Caveat: this code is untested and currently it fails on the call to GetGroups because of the aforementioned problems.
In app/apis/synchronize_api.rb:
class SynchronizeApi < ActionWebService::API::Base
api_method :Initialize, :expects =>
[{:NameValuePairs =>
[:string]}], :returns =>
[:integer]
api_method :GetGroups, :returns =>
[[:string]]
api_method :GetUsers, :returns =>
[[:string]]
api_method :GetMembers, :expects =>
[{:GroupID => :string}], :returns =>
[[:string]]
api_method :Shutdown
end
In app/controllers/synchronize_controller.rb:
class SynchronizeController < ApplicationController
web_service_dispatching_mode :direct
wsdl_service_name 'Synchronize'
web_service_scaffold :invokedef Initialize(nameValuePairs)
session['initialized'] = true
return 2
enddef GetGroups()
if session['initialized']
session['initialized'] = false
groups = Group.find_allgroupNames = Array.new
for group in groups
groupNames << "<SecureObject Name=\"#{group.name}\" AuthName=\"#{group.name}\" UniqueName=\"#{group.id}\"/>"
end
return groupNames
else
return nil
end
enddef GetUsers()
if session['initialized']
session['initialized'] = false
users = User.find_alluserNames = Array.new
for user in users
userNames << "<SecureObject Name=\"#{user.login}\" AuthName=\"#{user.login}\" UniqueName=\"#{user.id}\"/>"
endreturn userNames
else
return nil
end
enddef Shutdown()
return nil
end
end
Comments
Comments are listed in date ascending order (oldest first)