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:
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)
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:
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):
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".
| ||
| ||
| ||
| ||
| ||
| ||
|
| ||
| ||
| ||