If you are in an environment that uses Novell Directory services, you may have enabled some of your web apps to perform authentication against the novell user accounts, typically using the CFLDAP tag. This works great, and it's a great way to incorporate "single sign-on" in your institution.

The problem I've experienced, however, is that there are many users with Novell accounts that NEVER log into workstations, and their accounts are setup to require password changes periodically. Since they never log into a workstation, they are:

  • Never informed of this policy
  • Never informed when their password expires

So your glorious web app may one day stop letting them in because their novell password expired. It's not a big deal to retrieve the password expiration date (and "grace" logins remaining) using CFLDAP, the LDAP server just has to be configured to serve up the user attributes. It is a bigger deal, however, to allow the user to change their password from anywhere other than a novell-client enabled workstation. You can see from this "tid" that it is possible to do over LDAP, however there is a slight catch: The user can change their password via a LDAP modify call, however they must have full rights on their own account. As you've probably guessed, that's NOT a good idear. The document confirms this:

"A request has been submitted to Novell Engineering to specify access controls directly on the userpassword attribute. Once a user has the write privilege on her ACL attribute, the user could potentially change any of their attributes."

But it does continue onward to say that, for NDS 8+, you can let a user modify their password by passing in two modifications in one LDAP call, one to delete the current password and another to add the new one.

Uh-oh. I don't think CFLDAP can do that, not in one call at least. Yup, it can't. Are we hosed? Of course not... enter Novell's JLDAP classes, and CFMX's tight java integration.

Novell's JLDAP classes are a java code library that contains virtually everything you need for talking to LDAP servers. Novell has made the library available to everyone, via OpenLdap.org. After downloading the library, you have to make it accessable to CFMX. The easiest and most typical way to do that is to place ldap.jar inside <coldfusionmx-root>/wwwroot/web-inf/lib.

The JLDAP classes are pretty easy to use, and I am far from a java developer... they come with full code examples and a heavily annotated javadocs. The basic concepts are especially easy... the CFML to connect to an LDAP server with JLDAP looks like this:

<cfset ldapConn = createObject("java", "com.novell.ldap.LDAPConnection").init()/>
<cfset ldapConn.connect( "myLDAPHost", "myLDAPport" )/>
<--- note we can also pass the host in the "host:port" format, and ignore the second argument --->

Ok, typically the next thing you would do is "bind" to the directory. This basically means that we are logging in, and if you just wanted to check someones credentials, this is all you would have to do (if it fails then we aren't using the right distinguished name, aka dn, or the right password). Binding is 2 lines of code:

<cfset userPwObj = createobject("java", "java.lang.String").init(myUser'sPassword)/>
<!--- We need to place the password in an instance of java.lang.String because it needs to be passed to the LDAP as a byte array rather than a string. --->
<cfset ldapConn.bind(3, "myUserDn", userPwObj.getBytes("UTF8"))/>

Ok, so now that we've got the basics down, lets get back to our problem. AS mentioned, the reason we can't change the users password with a CFLDAP call is that we need to pass in an array of modifcations, rather than just one. Typically you would just make CFLDAP calls for each modification, however this won't let the user change their password (and it's ugly... each CFLDAP tag connects to the LDAP server, binds, does the modifications, and disconnects). The biggest hurdle in creating the proper array is that CFML is a "typeless" language, but our JLDAP class(es) expect to be passed an array of "com.novell.ldap.LDAPModification". This is not an easy task in CFML. The way we perform this feat is to use Java's "reflection" API to create the strongly-typed array. I must thank the blogs of Christian Cantrell, Tim Blair, and Sean Corfield for inspiration on how to do this in CF. What we need to do, to get ourselves an array of "com.novell.ldap.LDAPModification", is this: (in pseudocode)

  • create a instance of "com.novell.ldap.LDAPAttribute"
  • pass it to the constructor for "com.novell.ldap.LDAPModification"
  • now, use "java.lang.reflect.Array" (but don't call init(), it's not needed), and use the newinstance() method to get an array of the necessary type.

Here's the code:

<cfset deletePasswordAttribute = createobject("java", "com.novell.ldap.LDAPAttribute").init("userPassword",myUser'sCurrentPassword)/>
<cfset refArrayObj = createobject("java", "java.lang.reflect.Array")/>
<!--- this next variable is used to pull static properties, like .DELETE, below --->
<cfset LDAPModClass = createobject("java", "com.novell.ldap.LDAPModification")/>
<cfset deletePasswordModifcation = createobject("java", "com.novell.ldap.LDAPModification").init(LDAPModClass.DELETE,deletePasswordAttribute)/>
<cfset LDAPModArray = refArrayObj.newInstance(deletePasswordModifcation.getClass(), 2)/>

So, with that code, we just created an array of "com.novell.ldap.LDAPModification" with a size of 2. To place items in the array, we do this (notice I re-used the LDAPModification instance above):

<cfset refArrayObj.set(LDAPModArray, 0, deletePasswordModifcation)/>

To complete our array, we just do the same for the addPassword modification:

<cfset addPasswordAttribute = createobject("java", "com.novell.ldap.LDAPAttribute").init("userPassword",myUser'sNewPassword)/>
<cfset var addPasswordModifcation = createobject("java", "com.novell.ldap.LDAPModification").init(LDAPModClass.ADD,addPasswordAttribute)/> 
<cfset refArrayObj.set(LDAPModArray, 1, addPasswordModifcation)/>

Believe it or not, all we have to do is pass this array of "modifcations" to the server, and we are done (assuming we have already connected to server and performed a bind for the current user):

<cfset ldapConn.modify( userDN, LDAPModArray)/>

Finally, we disconnect:

<cfset ldapConn.disconnect()/> 

One last thing... it's probably a good idea to securely connect to your LDAP server. Using JLDAP, you can either use SSL, or TLS (TLS allows the connection to start unencrypted, then switch to secure, and then back, all on the same port). I couldn't get TLS working, but I think that was an issue with the LDAP server and not my code. To use SSL, the main hurdle is importing the LDAP server's certificate into the CFMX server's JRE's keystore. We do this using Sun's keytool utility:

  • 1) Use a browser like IE and browse to the secure url of the ldapserver, e.g https://myLdapServerIp:636
  • 2) Click view certifcate, details, copy to file... make sure you choose 'Base-64 Encoded'.
  • 3) Move this file to your server, into <cfusion-root>/runtime/jre/lib/security/
  • 4) Open DOS command prompt on server

Follwing instructions taken from Brandon Purcell's blog (site1.cer is the name of the certificate file you placed on the server, and you will need a unique alias for each certificate that you import):

  • 5) type PATH=%PATH%;C:\CFUSIONMX\RUNTIME\JRE\BIN (Assuming CF is installed at this location)
  • 6)cd\
  • 7)cd cfusionmx\runtime\jre\lib\security
  • 8)keytool -import -noprompt -alias SITE1 -file site1.cer -keystore .\cacerts  storepass changeit (assuming you haven't changed the default java password yet)

Once you've imported the certifcate, connecting to your LDAP server over SSL is easy. When you instantiate your "com.novell.ldap.LDAPConnection" object, all you have to do is pass it an instance of "com.novell.ldap.LDAPJSSESecureSocketFactory". The code looks like:

<cfset ssf = createObject("java", "com.novell.ldap.LDAPJSSESecureSocketFactory").init()/>
<cfset ldapConn  = createObject("java", "com.novell.ldap.LDAPConnection").init(ssf)/>

The code to use TLS is virtually the same... you pass an instance of ""com.novell.ldap.LDAPJSSEStartTLSFactory" to the connection's constructor, and when you want your connection to begin secure transmissions, just call:

<cfset ldapConn.startTLS()/>

Hopefully this post sheds some ever increasing light on using CFMX's java capabilities for things that it can't do "out of the box".


01/19/2005 04:36 P - Steve Moore said...
I am trying to perform a simple authentication check to Novell e-directory using CFMX via LDAP. I have followed the information on this page, and can successfully connect using SSL. However, when I try to bind I get a java error related to "No trusted certificate found". I notice that the certificate has expired and am thinking that is the problem, however we do not have a certificate from a legitimate certificate authority such as Verisign. Is this required? Please e-mail me if anyone knows about this. Thanks.

01/19/2005 04:45 P - Dave Ross said...

Steve-

We aren't using a certificate from an authority and have no issues. I would re-issue your certificate and try it again (although it's odd you are able to connect but cannot bind... maybe this has something to do with client (user) certificates, which I don't know too much about). Keep me posted...


01/20/2005 07:23 P - Sarge said...
Good to see someone try JLDAP w/CFMX. I'll have to put some code together as well (good work Dave). Steve, I agree with Dave that it's odd you can connect and not bind.  However, I would put the LDAP server's cert into the trustStore file instead of the keyStore file. CFLDAP does not support  Client Certificate authentication via CFLDAP. What you may want to do is grab the user's DN from the cert and pass that to CFLDAP's username attribute. This may also require some modification on the LDAP side as well -- especially if you are enforcing client cert authentication.

03/22/2006 12:54 P - Terrence Ryan said...
I've used all of your JLDAP stuff to write a CFC that encapsulates a whole bunch of ldap functionality.  I was wondering if you would object to me sharing it for free on my site, assuming I give you credit?

03/22/2006 01:05 P - Dave Ross said...
of course not, go right ahead! (give me the url so I can point people there too)

03/22/2006 02:38 P - Terrence Ryan said...
http://www.numtopia.com/terry/code_java_ldap.cfm
Enjoy.

12/03/2007 01:02 P - Melinda Deering said...
I have a slightly different issue with CF and Netware.  I would like the contents of a Netware volume to be displayed on a web page written in ColdFusion.  Is this possible?  I have given the CF server permissions to that volume, but no luck.  Thanks!

03/25/2009 01:44 A - Jacqueline said...
Java (Indonesian: Jawa) is an island of Indonesia and the site of its capital city, Jakarta. Once the centre of powerful Hindu kingdoms, Islamic sultanates, and the core of the colonial Dutch East Indies, Java now plays a dominant role in the economic and political life of Indonesia. Home to a population of 130 million in 2006, it is the most populous island in the world, ahead of Honsh?, the main island of Japan. Java is also one of the most densely populated regions on Earth. If the island was a country, it would be the tenth most populous in the world, just ahead of Japan. To encourage people during March Madness On Demand through web they are usually using java script. Formed mostly as the result of volcanic events, Java is the 13th largest island in the world and the fifth largest island in Indonesia. A chain of volcanic mountains forms an east-west spine along the island. It has three main languages, and most residents are bilingual, with Indonesian as their second language. While the majority of Javanese are Muslim, Java has a diverse mixture of religious beliefs and cultures.

03/25/2009 01:44 A - Jacqueline said...
Java (Indonesian: Jawa) is an island of Indonesia and the site of its capital city, Jakarta. Once the centre of powerful Hindu kingdoms, Islamic sultanates, and the core of the colonial Dutch East Indies, Java now plays a dominant role in the economic and political life of Indonesia. Home to a population of 130 million in 2006, it is the most populous island in the world, ahead of Honsh?, the main island of Japan. Java is also one of the most densely populated regions on Earth. If the island was a country, it would be the tenth most populous in the world, just ahead of Japan. To encourage people during March Madness On Demand through web they are usually using java script. Formed mostly as the result of volcanic events, Java is the 13th largest island in the world and the fifth largest island in Indonesia. A chain of volcanic mountains forms an east-west spine along the island. It has three main languages, and most residents are bilingual, with Indonesian as their second language. While the majority of Javanese are Muslim, Java has a diverse mixture of religious beliefs and cultures.

Post a comment:

(required, will not be displayed)
 


   You will be sent an email asking you to validate your comment.



Driven by Farcry Open Source CMS. Dressed in Aura.
Powered by ColdFusion MX.