The .NET Developer's Guide to Directory Services User Account Management
In this section, we take many of the abstract concepts we learned in the first part of the book and begin to apply them to real-world problems. One of the most common programming activities that most developers will perform against Active Directory and ADAM is user account management.
Active Directory user management is fairly straightforward once we know all of the basic rules. However, there are many interesting rules and behaviors concerning how attribute values in Active Directory are used to determine how user objects behave in Windows. In order to get the results we want, it is important to learn these rules and behaviors. We try to demonstrate most of them in this chapter.
To a lesser extent, we also investigate user account management in ADAM. ADAM is very similar to Active Directory in most respects, but it has a few significant differences worth pointing out.
Finding Users
When we speak of user objects for the remainder of this chapter, we are really talking about the user class in both Active Directory and ADAM. This is the class that acts as a security principal for Active Directory (and often, for ADAM). It is typically the class that we care the most about. The first thing we are likely to want to do with user accounts in Active Directory is find them. Given all that we know about searching from Chapters 4 and 5, this should be easy. Essentially, we just need to know where we want to search and what filter to use to find users.
Let’s start with the LDAP filter. Our goal is to build an LDAP filter that will find exactly what we want and that will be as efficient as possible. A few attributes in Active Directory distinguish user objects from other object types that we can use to build a filter:
- objectCategory
- objectClass
- sAMAccountName
- sAMAccountType
The objectCategory attribute has the advantage of being single-valued and indexed by default on all versions of Active Directory. This attribute is meant to be used to group common types of objects together so that we can search across all of them. Many of the schema classes related to users share the same value of person as their object category. While this is useful for searches in which we want to find information across many different types of user-related objects, it is not as useful for finding the user objects we typically care about (which are usually security principals). For example, in Active Directory, since both user and contact classes share the same objectCategory value of person, it alone will not tell them apart.
The objectClass attribute seems like a no-brainer, as (objectClass=user) will always find user objects exclusively. The problem here is that in many forests, the objectClass attribute is not indexed and it is always multivalued. As we know from the section titled Optimizing Search Performance, in Chapter 5, we generally want to try to avoid searches on nonindexed attributes, so using objectClass alone in a filter might not be the most efficient solution. This is why we see a lot of samples that search for user objects using an indexed filter like this:
(&(objectCategory=person)(objectClass=user))
This will get the job done, but we can do even better than this.
One key difference between contact objects and user objects in Active Directory is that user objects have a sAMAccountName attribute that is indexed by default. Thus, we could build a filter like this:
(&(objectCategory=person)(sAMAccountName=*))
This will separate the contacts from the users effectively. However, another approach is available that can find user objects directly, and it may be the most efficient technique for Active Directory:
(sAMAccountType=805306368)
Using the sAMAccountType attribute with a value of 805306368 accesses a single-valued, indexed attribute that uniquely defines user objects. The only downside here is that this attribute is not well documented, so it may not be recommended by Microsoft. However, in our investigations, it is effective.
One piece of good news is that all of these attributes are included in the global catalog, so we can use all of these filters there as well.
This should provide a foundation to build on for various user object searches. We may also wish to combine these filters with other attributes to find different subsets of user objects matching specific criteria. We will see some examples of that in the rest of this section.
Finding Users in ADAM
Things change a little bit for ADAM, because we don’t have things like sAMAccountName or sAMAccountType on an ADAM user. These attributes are actually from the auxiliary class called securityPrincipal. This class happens to be slightly different depending on whether it comes from Active Directory or ADAM. In this case, the securityPrincipal class does not contain sAMAccountName or related attributes in ADAM.
This means that we need to adjust our filters slightly to find our users in ADAM. It turns out that we can use some other indexed attributes, such as userPrincipalName, to find our users. However, since this attribute is not required, we have to be careful and ensure that all of our user objects have set a value for userPrincipalName.
As mentioned in Chapter 8, ADAM also includes the idea of a userProxy object that we often will want to return as well. To do so, we should look for common attributes found on both classes that would filter them for our needs. In this case, we know that both the user and userProxy classes contain userPrincipalName, so it is a good candidate for this purpose. We could use a simple filter such as (userPrincipalName=*) if we knew that all of our objects would have a value set for this attribute. However, since not all user or userProxy objects might have a value set for userPrincipalName, we would have to use objectCategory to be safe. For example:
(|(objectCategory=person)(objectCategory=userProxy))
The implications here are that if any other class in the directory also shared the same objectCategory of person or userProxy, they would also be returned. In default ADAM instances, this does not occur, but we should always check with our particular ADAM instance. Now, if we want to separate the two classes and treat them differently, we can use objectClass, as the classes are different for each type:
(&(objectClass=user)(objectCategory=person)) (&(objectClass=userProxy)(objectCategory=userProxy))
The first filter will return only user objects using objectCategory as an index, and the second will return only userProxy objects in the same manner. It is important to remember that we can customize ADAM heavily with application-specific classes. We should never rely on the fact that any particular class will be there or even be indexed. As such, these are only guidelines, and we really need to check the particular ADAM instance’s schema ourselves to determine what is there.