Archive for the ‘Technology’ category

Active Directory Authentication Part II

September 27th, 2008

This is a long overdue follow-up to my Active Directory Authentication post. I had all the best intentions of quickly following this post up with a second post so this is long overdue. It’s been just over a year though so there are lots of cobwebs so this is going to be a stream of consciousness dump…

First, there are two books that I found indespensible and the best resources I could find for detailed information about integrating with Active Directory and Windows security. The LogonUser and SSPI approach I mentioned in my first post a year ago originated from these books.

In an effort to just get some follow-up out there since I tend to get regular inquiries about my original post, Here’s a zip file of some sanitized code that includes the code for LogonUser, SSPI and a few other classes I used.
In the end I settled on LogonUser which seemed to give the best overall performance in a very decentralized, many sub-domain environment. There were also less anomalies as well. When using SSPI I found that if typical authentication took 140ms, some calls were taking 60.140s and 120.140s in one or two domains. This was rare and most likely a result of some sort of timeout and retry or fallback to another domain controller. I was not able to identify the root cause.

One side note regarding the LogonUse API. When the authentication type is set to “LOGON32 LOGON NETWORK” the LastLogon and LastLogonTimeStamp attributes on the domain controllers are not updated when authentication occurs. If you have any logic that relies on these attributes (For example if you to run a script against AD to check for user account inactivity), change the authentication type to “LOGON32 LOGON INTERACTIVE” to have these attributes updated as expected.

Lastly to test the differences between the three authentication types, I created a method to log detailed information about each authentication attempt including: elapsed time, samAccountName, domain, authentication result, user ip, domain server authenticated against (if I could figure it out), authentication web server. Without going into too much detail, I logged this info into a row in a database table on each attempt (this also inherently created a great resource for security auditing). This way I could query the data very easily to roll-up statistics based on which domain, what server, the IP subnet of the user, particular users, etc. Key to this though was to avoid any additional latency on the authentication request or a database dependency on the success of the authentication request, I created a BlockingQueue that waited for a log message to be placed on the queue and inserted it into the database. This happened out of band in another thread and if there was some failure, the queue would just continue to grow until the problem was resolved. This was very effective in measuring the performance of these different authentication methods.

Feel free to comment with any questions, I’ll do my best to recollect.

Expanding on Jira’s LDAP Integration

April 8th, 2008

I recently had the opportunity to do a fresh installation of Atlassian Jira and was faced again with how to integrate Jira with LDAP (in this case Active Directory). Jira does support simple authentication integration with LDAP via OSUser but requires that each LDAP user exist in Jira first. So if an LDAP user needs access to Jira, I have to go to Jira and create a new corresponding user record with the same user id as LDAP (sAMAccountName in this case). Ideally I would like Jira to synchronize LDAP users based on some query filter.

Luckily Jira has a perfect way to address this “opportunity”: Services. Jira Services are asynchronous scheduled units of work configured to run within Jira. I created a new service using JNDI called LDAPUserSyncService that can periodically query LDAP user objects based on configurable filter and create new Jira users for new users it finds in LDAP. I also added the ability to put a subset of users in a group called “internal-users” for users whose email matched a particular domain.

Its pretty simple. When the service runs, it queries LDAP, iterates through each user checking if the user already exists in LDAP, if not it created the user and puts it in the jira-users group and then checks if the email address contains myco.com (fictitious domain for this example) and if so put the user in the internal group as well. I just realized as I’m writing this though that I didn’t do anything to handle paging or results if the number of users is greater than the max returned by LDAP. Oh well, maybe another time…

public class LDAPUserSyncService extends AbstractService {

    private static org.apache.log4j.Category log =
            org.apache.log4j.Category.getInstance(LDAPUserSyncService.class);

    public static String INITCTX = "com.sun.jndi.ldap.LdapCtxFactory";
    public static String GROUP_INTERNAL =  "Internal-Users";
    public static String LDAP_USER_ID_ATTRIBUTE = "sAMAccountName";

    public void run() {
        try {
            log.debug("Running com.myco.jira.service.LDAPUserSyncService");
            String LDAP_HOST = getProperty("LDAP_HOST");
            String LDAP_USER = getProperty("LDAP_USER");
            String LDAP_PASSWORD = getProperty("LDAP_PASSWORD");
            String LDAP_SEARCHBASE = getProperty("LDAP_SEARCHBASE");
            String LDAP_FILTER = getProperty("LDAP_FILTER");   

            Hashtable env = new Hashtable();
            env.put(Context.INITIAL_CONTEXT_FACTORY, INITCTX);
            env.put(Context.PROVIDER_URL, LDAP_HOST);
            env.put(Context.SECURITY_AUTHENTICATION, "simple");
            env.put(Context.SECURITY_PRINCIPAL, LDAP_USER);
            env.put(Context.SECURITY_CREDENTIALS, LDAP_PASSWORD);

            DirContext ctx = new InitialDirContext(env);

            SearchControls constraints = new SearchControls();
            constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);

            NamingEnumeration results = ctx.search(LDAP_SEARCHBASE, LDAP_FILTER, constraints);

            while(results != null && results.hasMore()) {
                SearchResult sr = (SearchResult) results.next();
                Attributes attrs = sr.getAttributes();
                log.debug("Processing " + attrs.get("dn"));

                // need sn, givenName, mail
                String first = (attrs.get("givenName") != null) ? (String) attrs.get("givenName").get() : null;
                String last = (attrs.get("sn") != null) ? (String) attrs.get("sn").get() : null;
                String mail = (attrs.get("mail") != null) ? (String) attrs.get("mail").get() : null;
                String userId = (attrs.get(LDAP_USER_ID_ATTRIBUTE) != null) ? (String) attrs.get(LDAP_USER_ID_ATTRIBUTE).get() : null;

                if(first != null && last != null && mail != null && userId != null) {

                    // check if user is in Jira
                    if(!userExists(userId.toLowerCase())) {

                        // create user, if a myco company user then add to internal group
                        if(createJiraUser(userId, first, last, mail) != null) {
                            if(mail.contains("myco.com"))
                                addUserGroup(userId, GROUP_INTERNAL);
                        } else {
                            log.error("LDAP User " + userId + " could not be created");
                        }
                    }
                }
            }
        } catch (NamingException nex) {
            log.error("Caught exception trying to Synch LDAP Users: " + nex.toString());
        } catch (ObjectConfigurationException oce) {
            log.error("Caught exception trying to Synch LDAP Users.  Configuration setup failed: " + oce.toString());
        }
    }

    private User createJiraUser(String userId, String fname, String lname, String email) {

        UserManager userMgr = UserManager.getInstance();
        User osUser = null;
        try {
            osUser = userMgr.createUser(userId.toLowerCase());
            osUser.setFullName(fname + " " + lname);
            osUser.setEmail(email);

            Group jiraUserGroup = userMgr.getGroup("jira-users");
            osUser.addToGroup(jiraUserGroup);
            osUser.store();
            log.debug("Successfully added LDAP user "+ userId);
        } catch (ImmutableException e) {
            log.error("Error creating User in Jira: " + userId, e);
        } catch (DuplicateEntityException e) {
            log.error("Error creating User in Jira: " + userId, e);
        } catch (EntityNotFoundException e) {
            log.error("Could not find group jira-users.  Error creating User in Jira: " + userId, e);
        }

        // to be sure the user was created, return the reloaded user from Jira
        return osUser;

    }

    private boolean userExists(String userId) {
        UserManager userMgr = UserManager.getInstance();
        boolean exists = false;
        try {
            if(userMgr.getUser(userId) != null)
                exists = true;
        } catch (EntityNotFoundException e) {
            exists = false;
        }

        return exists;
    }

    private void addUserGroup(String userId, String groupName){
        UserManager userMgr = UserManager.getInstance();

        try {
            User osUser = userMgr.getUser(userId);

            Group group = userMgr.getGroup(groupName);
            osUser.addToGroup(group);
            osUser.store();
            log.debug("Successfully added LDAP user " + userId + " to group: " + groupName);
        } catch (ImmutableException e) {
            log.error("Error adding User to acis-users group: " + userId, e);
        } catch (EntityNotFoundException e) {
            log.error("Could not find group acis-users.  Error adding User to acis-users group:  " + userId, e);
        }
    }

    public ObjectConfiguration getObjectConfiguration() throws ObjectConfigurationException {
        return getObjectConfiguration("LDAPUSERSYNCSERVICE", "com/myco/jira/service/LDAPUserSyncService.xml", null);
    }
}

To configure a service within Jira navigate to Services in the System section of the Administration interface:

Here I’ve added the service you can see the parameters that can be customized for the service:

The parameters are configured in an XML file that is referenced by the getObjectConfiguration() method in the service code above:

<debugservice id="LDAPUserSyncService">
    <description>
    </description>
    <properties>

        <property>
            <key>LDAP_HOST</key>
            <name>LDAP Host (format: ldap://myco.com:389)</name>
            <type>string</type>
        </property>
        <property>
            <key>LDAP_USER</key>
            <name>LDAP User DN</name>
            <type>string</type>
        </property>
                <property>
            <key>LDAP_PASSWORD</key>
            <name>LDAP Password)</name>
            <type>string</type>
        </property>
        <property>
            <key>LDAP_SEARCHBASE</key>
            <name>LDAP Search Base DN</name>
            <type>string</type>
        </property>
        <property>
            <key>LDAP_FILTER</key>
            <name>LDAP Search Filter</name>
            <type>string</type>
        </property>

    </properties>
</debugservice>

And that’s it. New users are automatically created in Jira within 30 minutes of being created in LDAP. Ooo Rah.

Still Here

April 4th, 2008

I’d love to see stats on what percentage of all blog posts are an apology for not posting often enough. I have to assume the number is lower than I may expect since all the apologizing slackers (myself included), well, don’t post often enough. Anyway, that’s my passive aggressive apology.

I’ve been incredibly busy and have since transitioned back into consulting, taking a full time position with Avalon Consulting. I’m excited about my new role at Avalon, and the impact I’ll have there. I’ve recently had the opportunity to do some more work with Jira and set up a new Subversion environment. I should have some stuff to share there soon as well as pick back up on Grails.

On the cycling front, the first Cherry Creek time trial is Wednesday. I’m excited to see where my fitness is compared to last year at the same time. I’m about 13 lbs lighter if that’s any indication…

Hopefully more soon.

Colorado Springs Open Source Software Meetup Group

February 29th, 2008

Tonight I headed down to the Springs for the first meeting of the Colorado Springs Open Source Software Meetup Group. I learned about the group from this post on the Atlassian developer blog. Though it was a bit slow tonight, overall it was a good event and I think it will develop into a solid user group. My only concern is that the topic is too broad to successfully satisfy everyone’s interest. Tonight’s speaker was Kirstan Vandersluis who demoed the open source data integration platform XAware. There seems to be a lot of momentum in the commercially backed open source BI/EAI space lately with others including Pentaho and Apatar.

I walked away tonight with a free license of Intellij IDEA so thanks to Dave Booth from Jetbrains for the free swag! Definitely my favorite IDE, and I was actually in need of a license! And thanks TEKsystems for the food and the other sponsors as well!

I’m scheduled to present on Grails in May so if you’re in the area feel free to come heckle! Wow, four sentences ending in exclamation points (make that five)!

Part 2: Terracotta: 1, Grails Searchable Plugin+Me: 1

January 29th, 2008

Round Two of my quest to cluster the Grails Searchable Plugin index with Terracotta! In my first post I outlined my intent to use Terracotta to cluster the Grails Searchable Plugin (Lucene + Compass) using the Lucene RAMDirectory approach. Incidentally, my first attempt failed miserably.

As a quick confidence booster before setting out again, I successfully clustered the Grails Searchable Plugin using a JDBCDirectory store for the index. Since my last attempt I received some guidance from the guys at Terracotta and learned that Shay Banon is working with Terrocotta to develop a Compass module for Terracotta. Good stuff… check out the details of that here.

The first hurdle I needed to overcome was the classloader error:

Classloader name not set, instances defined from this
loader not supported in Terracotta
(loader: org.codehaus.groovy.grails.cli.support.GrailsRootLoader)

Before an object can be shared by Terracotta, the ClassLoader that loaded the object’s class has to be known to Terracotta. For core java and supported frameworks/container Terracotta takes care of registering relevant classloaders. For the GrailsRootLoader I can do this one of two ways:

  1. hack the grails source where the class loader is instantiated, registering it with Terracotta
  2. this can be converted into a config module that will inject the proper byte code at runtime so as to avoid any code tweaking.

The guys at Terracotta offered to help with #2 if I could get #1 working.

So I checked out the Grail trunk from SVN, and added these lines after the GrailsRootLoader is created:

 // create loader and execute main class
GrailsRootLoader loader = new GrailsRootLoader(lc);
((com.tc.object.loaders.NamedClassLoader) loader).__tc_setClassLoaderName("Grail Classloader");
com.tc.object.bytecode.hook.impl.ClassProcessorHelper.registerGlobalLoader((com.tc.object.loaders.NamedClassLoader) loader);

I built the Grails jars and replaced them in my grails-1.0-final-SNAPSHOT/dist directory. That resolved my classloader error! Next I went through about 100 iterations of root and lock configuration for my tc-config.xml as I dug and spun my way around the Compass source. I finally ended up with this for my tc-config.xml (keep in mind this can surely be optimized and is pretty brute force):

<?xml version="1.0" encoding="UTF-8" ?>

<tc:tc-config xmlns:tc="http://www.terracotta.org/config">
  <system>
    <configuration-model>development</configuration-model>
  </system>

  <servers>
    <server name="localhost" />
  </servers>

  <clients>
    <logs>%d/client-logs-%h</logs>
    <dso>

       <debugging>
        <runtime-logging>
          <lock-debug>true</lock-debug>
        </runtime-logging>
        <runtime-output-options>
          <full-stack>true</full-stack>
        </runtime-output-options>
      </debugging>

    </dso>
    <modules>
      <module name="clustered-lucene-2.0.0" version="2.5.0"/>
    </modules>
  </clients>

  <application>
    <dso>
      <instrumented-classes>
        <include>
          <class-expression>org.compass.core.lucene.engine..*</class-expression>
        </include>
        <include>
          <class-expression>org.apache.lucene..*</class-expression>
        </include>
      </instrumented-classes>
      <roots>
        <root>
          <field-name>org.compass.core.lucene.engine.store.RAMLuceneSearchEngineStore.ramIndexes</field-name>
        </root>
      </roots>
      <locks>
	<named-lock>
          <method-expression>* org.compass.core.lucene.engine.manager.DefaultLuceneSearchEngineIndexManager.*(..)</method-expression>
          <lock-level>write</lock-level>
          <lock-name>theLockName</lock-name>
        </named-lock>
        </locks>
     </dso>
  </application>
</tc:tc-config>

It worked! I was able to test with the Terracotta server running, and two Grails apps running in separate JVMs that all of the Domain class data that the first JVM indexed, could be searched by the second JVM and they both returned identical results. I also verified the existence of the objects under the root in the Terracotta Admin GUI. And finally, I shut both Grails app JVMs down and brought them back up and my index was still available.

What’s next? Work to get a config module for Terracotta and optimize my root and locking configuration. I’m very interested in what will come of the promised Compass/Terracotta integration. Also Clustering Grails is a much taller mountain. I was told that past attempts to cluster Groovy with Terracotta had not been too successful. For now I’m happy just to have climbed my little hill…

Using JDBCDirectory with the Grails Searable Plugin

January 24th, 2008

In my last post I mentioned that one of the options I was considering for clustering the Grails Searchable plugin, though not the preferred option, was to use the JDBCDirectory implementation of Lucene. I had this working in just 15 minutes and though it’s probably not the optimal approach, it is by far the simplest.

Just to set the stage, I’m running Grails RC3, Groovy 1.5.1, JDK 1.6, grails-searchable-0.4-SNAPSHOT and MySQL 5.0.45. I will configure the Compass to use the JdbcDirectory connection instead of file or ram and test that it can be updated and read by two instances of my Grails application.

First I created compass.cfg.xml in grails-app/conf (just needs to be somewhere in the classpath) with jdbc connection details:

<compass-core-config xmlns="http://www.opensymphony.com/compass/schema/core-config"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.opensymphony.com/compass/schema/core-config
          http://www.opensymphony.com/compass/schema/compass-core-config.xsd">
   <compass name="default">
     <connection>
         <jdbc dialect="org.apache.lucene.store.jdbc.dialect.MySQLDialect">
             <dataSourceProvider>
                 <driverManager url="jdbc:mysql://localhost/search?emulateLocators=true"
                                      username="root" password="password"
                                      driverClass="com.mysql.jdbc.Driver" />
             </dataSourceProvider>
             <fileEntries>
                 <fileEntry name="__default__">
                     <indexInput bufferSize="4096" />
                     <indexOutput bufferSize="4096" />
                 </fileEntry>
             </fileEntries>
         </jdbc>
     </connection>
   </compass>
</compass-core-config>

One thing I missed at first was the dialect setting in the jdbc tag. Make sure you select the correct dialect for your database. I used this as a reference.
In SearchableConfiguration.groovy I set compassConnection to null

String compassConnection = null

I started my grails app and hit just one more snag, I got a “packet too large” error while it was building the index. Turns out the default max_allowed_packet in MySQL on the server is 1M and since Lucene stores parts of the index in BLOBs I needed to up the max. I found this article and set the max_allowed_packet setting to 16M. I did this in my.ini:

[mysqld]
max_allowed_packet=16M

That was it! I tested by running two instances of my Grails application simultaneously and I modified one of my Domain class instances and executed a search on the other instance and within a few seconds, the new value was returned in my search.

The last thing I considered was configuring a Local Directory Cache in Compass. This seemed like it could offer some good optimization though it was unclear from the documentation how much control you have over the size of the cache, what gets cached, the TTL, etc. Also, I soon realized that this was introduced in the latest version of Compass (1.2) and isn’t the version that is bundled in the Searchable plugin.

Terracotta: 1, Grails Searchable Plugin+Me: 0

January 22nd, 2008

I’m on a quest to figure out how to cluster the Grails Searchable plugin that’s based on Lucene/Compass across multiple nodes with as little intrusion and in a way that’s as turnkey as possible. I’m in immediate need of a solution, and I think it would be a good contribution to the Searchable plugin.

To start here’s what I’m considering:

  1. Manage a local index per node. Probably disable mirrorChanges via Compass::GPS and rebuild the index on someinterval. This is not very desirable since it would eliminate the Compass:GPS capability, and there just must be a more elegant way…
  2. Use the JDBCDirectory implementation of Lucene and store the index in a sql database. This option is viable but has obvious performance implications that somewhat defeat the purpose. The configuration would be fairly clean using the native Compass XML config. This maybe a good fall back option to do some A/B testing against.
  3. Use Terracotta and RAMDirectory to handle synchronization of the index across nodes. This has a lot of promise and will be the path I head down first. Though I have no experience whatsoever with Terracotta

Another possibility is to look at Compass’ new support for Gigaspaces. Thanks Marcos for passing that on. This sounds interesting but “feels” fairly heavy compared to the others, though I know nothing about GigaSpaces or Coherence so I’m just talking out of my arse at this point!

I made my first attempt with Terracotta and Compass today. I dug deep enough into Compass to find that it manages the RAMDirectory objects in this class: org.compass.core.lucene.engine.store.RAMLuceneSearchEngineStore and manages each subindex RAMDirectory in a java.util.HashMap called ramIndexes. The first issue I had to overcome was that HashMap isn’t a synchronized data structure, and I was having problems configuring the locking in Terracotta for ramIdexes. I kept getting UnlockedSharedObjectException exceptions not matter what I tried.

After a little help from the Terracotta users list I ended up with a named lock. My tc-config.xml looked like this:

<?xml version="1.0" encoding="UTF-8" ?>
<tc:tc-config xmlns:tc="http://www.terracotta.org/config">
  <system>
    <configuration-model>development</configuration-model>
  </system>

  <servers>
    <server name="localhost" />
  </servers>

  <clients>
    <logs>%d/client-logs-%h</logs>
    <dso>
      <debugging>
        <runtime-logging>
          <lock-debug>true</lock-debug>
        </runtime-logging>
        <runtime-output-options>
          <full-stack>true</full-stack>
        </runtime-output-options>
      </debugging>
    </dso>
    <modules>
      <module name="clustered-lucene-2.0.0" version="2.5.0"/>
    </modules>
  </clients>

  <application>
    <dso>
      <instrumented-classes>
        <include>
          <class-expression>org.compass.core.lucene.engine.store.RAMLuceneSearchEngineStore</class-expression>
        </include>
      </instrumented-classes>
      <roots>
        <root>
          <field-name>org.compass.core.lucene.engine.store.RAMLuceneSearchEngineStore.ramIndexes</field-name>
        </root>
      </roots>
      <locks>
	<named-lock>
          <method-expression>* org.compass.core.lucene.engine.store.RAMLuceneSearchEngineStore.*(..)</method-expression>
          <lock-level>write</lock-level>
          <lock-name>theLockName</lock-name>
        </named-lock>
      </locks>
     </dso>
  </application>
</tc:tc-config>

That at least got me past the UnlockSharedObjectException, but tonight I hit a bigger roadblock. Terracotta is choking on Grails custom ClassLoader. I did a preliminary search on the error message below but came up short. I followed up on the Terracotta users’ list but I thought I’d share my progress and my intentions here in case anyone out there has any ideas…

Error creating bean with name 'compassGps': Cannot resolve reference to
bean'compass' while setting bean property 'compass'; nested exception is
org.springframework.beans.factory.BeanCreationException: Error creating
bean with name 'compass': FactoryBean threw exception on object creation;
nested exception is java.lang.IllegalStateException: Classloader name not set,
instances defined from this loader not supported in Terracotta (loader:
org.codehaus.groovy.grails.cli.support.GrailsRootLoader):
java.lang.IllegalStateException: Classloader name not set, instances
defined from this loader not supported in Terracotta (loader:
org.codehaus.groovy.grails.cli.support.GrailsRootLoader)

Stay tuned…

Update (Jan 29, 2008): See part 2 here

Building Grails Applications with Hudson

January 21st, 2008

I’m never surprised by the shear magnitude of stuff I don’t know about. Last week I stumbled upon Hudson as I was searching for help to configure Cruise Control to build and execute tests for my a Grails application. I found this post that introduced me to Hudson and got me started.

Let me first say that I was off to find a free solution. If I had the luxury of spending a little cash at this point I would have used Bamboo. I’m a big fan of Atlassian products and love what they are doing to integrate Jira, Fisheye, Bamboo, Crucible, Clover and Confluence. The traceability of code to issues to change sets to builds and tests is really cool. Anyway I digress.

So I found Hudson, and I was pleasantly surprised how easy it was to set up. I’m running on Windows with Tomcat 6 and JDK1.6. I simply deployed the Hudson WAR, and I was off to the races. Hudson’s configuration is all done via a web interface and the interface itself is very clean, especially compared to Cruise Control.

In Hudson I created a new job, selected SVN in the source code management section and entered the URL to my Grails app in the repository. Hudson also lets you configure a repository browser (ViewSVN, Fisheye, WebSVN or Sventon). Since I was already running Tomcat, I chose Sventon and quickly downloaded and deployed the WAR for Sventon. I set Hudson to poll SVN for changes every minute and for the build itself I told Hudson to use Ant (you need to set up ANT first in Hudsson) and pointed to the Grails build.xml.

I created two new targets in the build.xml: ‘all’ and ‘deploy’. The all target chains the depended targets together. I had originally had Hudson call test, war, deploy and doc separately but each invoked a compile separately.

<target name="all" depends="war, test, deploy, doc"
  description="build, test, deploy and build the javadoc"/>

I also created a deploy target that takes the war and deploys to Tomcat and property to specify the grails_env.

Under Post-build Actions I set the following properties:

  • Files to Archive = “**/*.war”
  • Javadoc Directory = “GRAILS-APP/docs/gapi”
  • Test Reports XMLs = “GRAILS-APP/test/reports/*.xml”

So in about an hour I had Hudson continually building, running my test cases, generating javadoc and deploying my WAR to Tomcat, and it was all relatively painless. Here’s what the job status screen looks like:
Hudson Screenshot

Going forward I’ll probably investigate using GANT instead of ANT, otherwise it’s serving the purpose.

Active Directory Authentication Part I

September 25th, 2007

One of the main reasons I started this blog was so that I would have a place to “give back” to the software development community. For those that read my blog for more personal updates, you’ll want to stop here. My daily work life depends heavily on open source projects, discussion forums and blogs posts like this one. In this post I’ll be sharing what I’ve learned regarding programmatic authentication to Active Directory for a custom website authentication scheme.

I recently redeveloped a centralized web-based authentication service for a large corporate intranet that leveraged RSA Access Manager as a web single sign-on (SSO) provider. The authentication service validates credentials against Active Directory and handles cases of expired password resets and locked accounts. Once a credential has been validated, the user Id is asserted to RSA Access Manager, creating a new session and then passing control over to RSA and redirecting onto the requested resource.

A few key points:

  • RSA Access Manager is a Java based application and all of the intranet applications are Java applications
  • The Active Directory forest has 42 child domains organized by business unit, all de-centrally administered, spread across the US in 18 states and connected by a wide area network with relatively high latency.
  • Across the 42 domains, there are well over 100 domain controllers, some Windows 2003, others Windows 2000
  • As I am a Java developer with no previous .Net experience, the first attempt to externalize authentication from RSA Access Manager was in Java via JAAS/GSS-API and then JNDI. Without going into detail, this turned out to be difficult given the diversity of the AD infrastructure.

    Next, since we had other needs to interface with Active Directory from our applications, I decided on a new approach. I developed a .Net Web Services API in C# to interface with our Active Directory infrastructure. The Web Services API encapsulated the complexity of the environment and allowed the caller to search, retrieve, and manipulate AD users and group information. This is now how we integrate our Java applications with AD. As part of this implementation in the same ASP.Net web application, I implemented the centralized authentication HTML service. Next I’ll dive into three iterations of the authentication functionality. I’ll save anything outside of the authentication portion for other posts if there’s interest.

    The first iteration leveraged Integrated Windows Authentication (IWA) via IIS. I won’t go into detail on this but will say we abandoned that approach because of the regular use of shared workstations and auto-login accounts.

    The second iteration used LDAP via ADSI:
    Given a domain, samAccountName and password, attempt to bind to the root of the domain of the user. If successful, continue. If the bind failed, one of these cases is true: the credential was invalid, the account password is expired (including “must change password on next login” situations, the account is locked (too many bad attempts) or the account is disabled. Therefore, bind with a known system credential to check the account status. If the account password is expired, notify the user and prompt the user to reset their password. Otherwise, if the account is locked, disabled, or looks OK, return that the authentication was unsuccessful.

    Below are several snippets of code:

    public static AuthenticationResult authAndStatus(string domain, string samAccountName,
        string password, String adminUsername, String adminPassword)
    {
        String domainAndUsername = domain + "\\\\" + samAccountName;
        DirectoryEntry de = getDirectoryEntry(domain, domainAndUsername, password);
        AuthenticationResult result = new AuthenticationResult();
        DirectoryEntry userDE = null;
    
        try
        {
            // Bind to the native AdsObject to force authentication.
            Object obj = de.NativeObject;
            result.authResult = AuthenticationResult.SUCCESSFUL;
    
        }
        catch (Exception ex)
        {
            // the bind failed
            result.authResult = AuthenticationResult.FAILED;
        }
    
        try
        {
            // get the account status
            de = getDirectoryEntry(domain, adminUsername, adminPassword);
            userDE = ADUtilities.getUserDE(de, "samAccountName", samAccountName,
                adminUsername, adminPassword);
            if (userDE != null)
            {
                AccountStatus accountStatus = new AccountStatus(userDE);
                result.accountStatus = accountStatus;
            }
            else
            {
                result.authResult = AuthenticationResult.INVALID;
            }
        }
        catch (Exception ex)
        {
            // need to trap, basically don't let the status check break
            //the authentication
        }
    
        return result;
    }
    
        public static DirectoryEntry getUserDE(DirectoryEntry de, String propertyName, String propertyValue, String adminUsername, String adminPassword)
        {
            DirectorySearcher search = new DirectorySearcher(de);
            search.Filter = "(" + propertyName + "=" + propertyValue + ")";
    
            SearchResult result = search.FindOne();
            if (result != null) {
                return new DirectoryEntry(result.Path, adminUsername, adminPassword);
            } else {
                return null;
            }
        }
    
    public static DirectoryEntry getDirectoryEntry(String domain, String adminUsername, String adminPassword)
    {
        String dePath = null;
    
        String adForestName = (String)System.AppDomain.CurrentDomain.GetData("adForestName");
        String forestDomainName = adForestName.Substring(0, adForestName.IndexOf('.'));
        String gcDNS = (String)System.AppDomain.CurrentDomain.GetData("adForestPreferredGlobalCatalog");
    
        if (domain != null && domain.ToUpper().Contains(".") && domain.ToUpper().Contains(adForestName.ToUpper()))
        {
            domain = (domain.Split(new char[] { '.' }))[0];
        }
    
        // if the domain is not null and not All
        if (domain != null && !domain.Trim().Equals("") && !domain.Equals("All"))
        {
            String fqdn = domain + "." + adForestName;
    
            // check if this is the root domain and if so rest the fqdn to the forest name
            if (domain.ToUpper().Equals(forestDomainName.ToUpper()))
                fqdn = adForestName;
    
            String ldapDCs = getLDAPDCs(fqdn);
            dePath = "LDAP://" + fqdn + "/" + ldapDCs;
    
        }
        else
        {
            String ldapDCs = getLDAPDCs(adForestName);
            // if the domain is null assume the global catalog
            dePath = "LDAP://" + gcDNS + ":3268/" + ldapDCs;
        }
    
        DirectoryEntry de = new DirectoryEntry(dePath, adminUsername, adminPassword, AuthenticationTypes.Secure | AuthenticationTypes.Sealing);
        return de;
    
    }
    
    public static DirectoryEntry getGCDirectoryEntryForDomain(String domain, String adminUsername, String adminPassword)
    {
        String dePath = null;
    
        String adForestName = (String)System.AppDomain.CurrentDomain.GetData("adForestName");
        String forestDomainName = adForestName.Substring(0, adForestName.IndexOf('.'));
        String gcDNS = (String)System.AppDomain.CurrentDomain.GetData("adForestPreferredGlobalCatalog");
    
        dePath = "LDAP://" + gcDNS + ":3268/" + getLDAPDCs(domain + "." + adForestName);
    
        DirectoryEntry de = new DirectoryEntry(dePath, adminUsername, adminPassword, AuthenticationTypes.Secure | AuthenticationTypes.Sealing);
        return de;
    
    }
    
    public static String getLDAPDCs(String fqdn)
    {
        String ldapDCs = "";
    
        if (fqdn != null) {
            char[] separator = new char[1];
            separator[0] = '.';
            String[] parts = fqdn.Split(separator);
    
            for (int i = 0; i < parts.Length; i++) {
                ldapDCs = ldapDCs + "dc=" + parts[i];
    
                if (i + 1 != parts.Length)
                    ldapDCs = ldapDCs + ",";
            }
        }
    
        return ldapDCs;
    }
    

    In my next post I describe how well this approach actually worked and the issues that prompted the search for another solution. Then I'll go into using SSPI and then the use of the LogonUser native function call...

    I FINALLY posted a follow-up to this here

    Broken Part III

    August 28th, 2007

    Severe focused blunt force trauma. That’s how my orthopedist described what caused my fracture. Apparently I broke it in an unusual location where the clavicle is in fact much thicker and closer to the center of my chest. He thinks I don’t need surgery, but I’ll find out for sure next week when he checks how the bones shift around as the swelling subsides. I have a 1cm reduction in my clavicle and there are at least 4 bone chips floating around as well as a general sloping of my shoulder.

    I’ve spent the last 3 nights and many waking hours in a borrowed recliner. It has been the only place I am reasonably comfortable. I’ll try to go into work tomorrow, but I doubt how useful I’ll be. This weekend we planned to go camping at Grand Lake in a small cabin. I’m threatening to stay home, but Jeanie’s threatening to bring the recliner :)

    I found a really cool new feature on with Google Maps today when mapping out directions to the doctors office. You can now modify the route by clicking and dragging points along the route. Check it out here.

    Powered by Web Design Company Plugins

    Switch to our mobile site