Active Directory LDAP Authentication

I recently had a project for a client where I needed to authenticate with their implementation of Microsoft’s ADAM. Behind the scenes they are syncing their Active Directory as well as their SAP data with ADAM.

Using ColdFusion, I was able to to do something like the following:


<cfif Trim(selUsername) neq "" and Trim(selPassword) neq "">
<cfset SearchFilter="sAMAccountName=#selUsername#">
<cfset viewfieldlistLookup = "sAMAccountName,distinguishedName,cn,memberOf">
    <cftry>
      <cfldap action="QUERY"
		      name="qry_authenticate_user"
		      attributes="#viewfieldlistLookup#"
		      filter="#SearchFilter#"
		      sort="sn"
		      start="#LDAPBase#"
		      server="#LDAPServer#"
		      username="#selUsername#"
		      password="#selPassword#">
      <cfcatch type = "Any">
        <cfset err = "#err#
	<li>Unable to find that username and password.</li>
">
      </cfcatch>
    </cftry>
<cfelse>
  <cfset err = "#err#
	<li>Username and Password are required fields</li>
">
</cfif><strong>
</strong>
<cfif err eq "" and IsDefined( "qry_authenticate_user.recordcount" ) and qry_authenticate_user.recordcount gt 0>
  <cfset variables.IsValid = 1>
<cfelse>
  <cfset variables.IsValid = 0>
</cfif>
...

The above works because all users of this organization has read only and search access for ADAM.

Now here is the interesting thing. For a different client, they don’t have an implementation of MS’ ADAM directory so I’m tasked with authenticating directly with their Active Directory. This company does not give the user read only or search capability of their Active Directory. So, I ended up having to rewrite it a bit by doing the following:


<cfset variables.IsValid = 0>
<cfset err = "">

<cfif Trim(selUsername) neq "" and Trim(selPassword) neq "">
    <cfset SearchFilter="sAMAccountName=#selUsername#">
    <cfset viewfieldlistLookup =
"cn,displayName,distinguishedName,givenName,homeDirectory,name,objectClass,sAMAccountName,sn,mail">
    <cftry>
        <cfldap action="QUERY"
                name="qryCheckUser"
                attributes="#viewfieldlistLookup#"
                start="#LDAPBase#"
                server="#LDAPServer#"
                username="#LDAPUsername#"
                password="#LDAPPassword#"
                filter="#SearchFilter#">
                <cfif qryCheckUser.RecordCount gt 0>
                    <cfset curDisplayName = "#qryCheckUser.displayName#">
                    <cfldap action="QUERY"
                            name="qryAuthenticateUser"
                            attributes="#viewfieldlistLookup#"
                            start="#LDAPBase#"
                            server="#LDAPServer#"
                            username="#curDisplayName#"
                                password="#selPassword#">
                    <cfif qryAuthenticateUser.RecordCount gt 0>
                        <cfset variables.IsValid = 1>
                    <cfelse>
                        <cfset err = "#err#
	<li>Username and/or Password Failed</li>
">
                        <cfset variables.IsValid = 0>
                    </cfif>
                <cfelse>
                    <cfset err = "#err#
	<li>Username and/or Password Failed</li>
">
                    <cfset variables.IsValid = 0>
                </cfif>
        <cfcatch type = "Any">
            <cfset err = "#err#
	<li>Technical Error Connecting to LDAP Server.  Please notify IT.</li>
">
            <cfset variables.IsValid = 0>
        </cfcatch>
    </cftry>
<!---
    Here is a list of Active Directory errors you could optionally test for:
    525 - user not found
    52e - invalid credentials
    530 - not permitted to logon at this time
    532 - password expired
    533 - account disabled
    701 - account expired
    773 - user must reset password
--->
<cfelse>
    <cfset err = "#err#
	<li>Username and Password are required fields</li>
">
</cfif>

Because the user doesn’t have permission to query AD via LDAP, I must use a system account to look up that person to find out if they exist first. There is an interesting twist in this second example. After a long time searching, I gave up on trying to find out why I couldn’t pass their NT username (sAMAccountName) and their password like I did in the first example with ADAM. After much trial and error, the only way I figured out how to do it is by looking up their DisplayName and passing that along with their supplied password instead. I think looking up their Distinguished Name (DN) works as well.

I’m assuming that AD blocks the passing of their NT Username for some obscure reason or more likely I have made a mistake. I’ll look to others reading this post for any insight. The current code works but I’m not 100% sure it’s the best solution.

I suppose another option is to set NT Authentication as a requirement for the directory within IIS and handle it that way. A colleague showed me a quick example which seemed like a pretty graceful way to handle things. I just am unclear on the compatibility of this option with other browsers, Servers like Apache, as well as possible problems from remote connections via VPN.

So, there you go, a couple of examples of how I have used ColdFusion to connect to two different LDAP compliant directories.

I’m looking for suggestions to make this better as well as any feedback you might have regarding the use of NT Authentication at the Web server level instead.

Updating/Adding an Image to an LDAP server.

I recently ran into a problem with adding an image to an LDAP server using CFLDAP. It appeared that the only way to pass an image into the CFLDAP was to readbinary it and then convert it to Base64. Passing that back to the server worked without error but the attribute jpegPhoto was stored as text. Since I was able to see other images where stored as binary, I knew something wasn’t right. I tried everything I could think of and posted my questions on many a board such as the wonderful mailing list at HouseOfFusion.com with this entry and this entry.

After many trials and tribulations, I was able to come up with a solution with the help of a colleague at work named Peter Jacoby (thanks Peter!) The solution involves using cffile to upload and readbinary into a variable and then doing a cfhttp post handing the file name to an aspx file that then handles the binary insert and prints a 1 if successful. I then look for a 1 in the cfhttp.fileContent to see if it is successful. If you read through the second entry link above you will find the code.

Thanks again Peter!

ColdFusion and Microsoft’s ADAM Chapter 1

So one of my clients needs me to integrate a ColdFusion application with the backend directory of ADAM. I’ll be using CFLDAP to read from it and to write to it. I just need to replicate the existing ADAM into a test schema and then I’ll be updating and testing with that and will eventually move it to the live ADAM instance. I think the largest challenge will be to figure out how to use/manage/install and change ADAM. Once I figure that out, I suppose it will be pretty straight forward.

I’ll let you know how it turns out soon.

Oh, and let me know if you have any wisdom you would like to share if you have experience using ADAM.