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:
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:
This will get the job done, but we can do even better than this.
Note: Behavior Change in Windows Server 2003
Windows Server 2003 Active Directory indexes the objectClass attribute by default, so this rule really only applies to Windows 2000 Active Directory now. Additionally, the schema administrators for your domain may have already indexed it, so check the schema before making assumptions.
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:
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:
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:
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:
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.
Note: New Versions of ADAM Include the userProxyFull Class
With later versions of ADAM, a new class called userProxyFull can be found that essentially mirrors the user class with the addition of the msDSBindProxy auxiliary class. This means that we cannot use objectCategory to distinguish between user and userProxyFull classes as we can with userProxy classes. We must instead rely upon the objectClass to distinguish between the two. This is another example of where we need to be cognizant of what our schema is for ADAM.
Part I: Fundamentals
Introduction to LDAP and Active Directory
Introduction to .NET Directory Services Programming
Binding and CRUD Operations with DirectoryEntry
Searching with the DirectorySearcher
Advanced LDAP Searches
Reading and Writing LDAP Attributes
Active Directory and ADAM Schema
Security in Directory Services Programming
Introduction to the ActiveDirectory Namespace
Part II: Practical Applications
Part III: Appendixes
Appendix A. Three Approaches to COM Interop with ADSI
Appendix B. LDAP Tools for Programmers
Appendix C. Troubleshooting and Help